diff --git a/.gitattributes b/.gitattributes
index 2a92ef0172..8d991b1183 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,4 +1,4 @@
-.github/workflows/*.lock.yml linguist-generated=true merge=ours
+.github/workflows/*.lock.yml linguist-generated=true
# Cross-platform tools rewrite these files, so keep their output deterministic.
java/**/*.java text eol=lf
diff --git a/.github/actions/setup-copilot/action.yml b/.github/actions/setup-copilot/action.yml
index 3769bc3751..a9c39a2a0a 100644
--- a/.github/actions/setup-copilot/action.yml
+++ b/.github/actions/setup-copilot/action.yml
@@ -4,6 +4,9 @@ outputs:
cli-path:
description: "Path to the Copilot CLI"
value: ${{ steps.cli-path.outputs.path }}
+ javascript-cli-path:
+ description: "Path to the JavaScript Copilot CLI entrypoint"
+ value: ${{ steps.cli-path.outputs.javascript-path }}
runs:
using: "composite"
steps:
@@ -23,16 +26,19 @@ runs:
- name: Set CLI path
id: cli-path
run: |
- # As of CLI 1.0.64-1 the @github/copilot package is a thin loader; the
- # runnable index.js ships in the installed platform package
- # (e.g. @github/copilot-linux-x64). Exactly one is installed.
- cli_path=$(ls "$(pwd)"/nodejs/node_modules/@github/copilot-*/index.js 2>/dev/null | head -n1)
+ cli_path=$(npm --prefix "$(pwd)/nodejs" run --silent prepare:runtime -- --print-path)
if [ -z "$cli_path" ]; then
- echo "Could not find @github/copilot platform package (index.js) under nodejs/node_modules" >&2
+ echo "Could not prepare the Copilot CLI runtime" >&2
+ exit 1
+ fi
+ javascript_cli_path=$(npm --prefix "$(pwd)/nodejs" run --silent prepare:runtime -- --print-legacy-path)
+ if [ -z "$javascript_cli_path" ]; then
+ echo "Could not prepare the Copilot CLI JavaScript entrypoint" >&2
exit 1
fi
echo "path=$cli_path" >> $GITHUB_OUTPUT
+ echo "javascript-path=$javascript_cli_path" >> $GITHUB_OUTPUT
shell: bash
- name: Verify CLI works
- run: node ${{ steps.cli-path.outputs.path }} --version
+ run: node "${{ steps.cli-path.outputs.javascript-path }}" --version
shell: bash
diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json
index 4be7dfbd5c..23d2b6242f 100644
--- a/.github/aw/actions-lock.json
+++ b/.github/aw/actions-lock.json
@@ -20,15 +20,10 @@
"version": "v7.0.1",
"sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"
},
- "github/gh-aw-actions/setup-cli@v0.83.1": {
- "repo": "github/gh-aw-actions/setup-cli",
- "version": "v0.83.1",
- "sha": "8bdba8075360648fe6802302a5b4e016361dc6ac"
- },
- "github/gh-aw-actions/setup@v0.83.1": {
+ "github/gh-aw-actions/setup@v0.88.2": {
"repo": "github/gh-aw-actions/setup",
- "version": "v0.83.1",
- "sha": "8bdba8075360648fe6802302a5b4e016361dc6ac"
+ "version": "v0.88.2",
+ "sha": "9271a1804551c0dc4fb0085a97979950aa2f8489"
}
}
}
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index a9bc22d0ec..476f4e5689 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -47,7 +47,7 @@
- Tools: each SDK has helper APIs to expose functions as tools; prefer the language's `DefineTool`/`@define_tool`/`CopilotTool.DefineTool` patterns (see language READMEs).
- Infinite sessions are enabled by default and persist workspace state to `~/.copilot/session-state/{sessionId}`; compaction events are emitted (`session.compaction_start`, `session.compaction_complete`). See language READMEs for usage.
- Streaming: when `streaming`/`Streaming=true` you receive delta events (`assistant.message_delta`, `assistant.reasoning_delta`) and final events (`assistant.message`, `assistant.reasoning`) — tests expect this behavior.
-- Type generation is centralized in `nodejs/scripts/generate-session-types.ts` and requires the `@github/copilot` schema to be present (often via `npm link` or installed package).
+- Type generation is centralized in `scripts/codegen/` and downloads schemas from the pinned `github/copilot-cli` release.
- Java code style: 4-space indent (Spotless + Eclipse formatter), fluent setter pattern for config classes, Javadoc required on public APIs (enforced by Checkstyle, except `json`/`events` packages).
- Java handlers return `CompletableFuture` (the Java equivalent of C# `async/await`). When porting from .NET: convert properties → getters/fluent setters, use Jackson (`ObjectMapper`, `@JsonProperty`) for serialization.
@@ -64,7 +64,7 @@
- SDK code: `nodejs/src`, `python/copilot`, `go`, `dotnet/src`, `rust/src`, `java/sdk/src/main/java`
- Unit tests: `nodejs/test`, `python/*`, `go/*`, `dotnet/test`, `rust/tests`, `java/sdk/src/test/java`
- E2E tests: `*/e2e/` folders that use the shared replay proxy and `test/snapshots/`, `java/sdk/src/test/java/**/e2e/`
-- Generated types: update schema in `@github/copilot` then run `cd nodejs && npm run generate:session-types` and commit generated files in `src/generated` or language generated location. Java generated types: `java/sdk/src/generated/java`
+- Generated types: update the pinned Copilot CLI version, run `cd nodejs && npm run generate`, and commit generated files in each language's generated location. Java generated types: `java/sdk/src/generated/java`
## Boundaries — files you must NOT hand-edit ⛔
diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md
index acec3f146c..4c983cc5ac 100644
--- a/.github/skills/agentic-workflows/SKILL.md
+++ b/.github/skills/agentic-workflows/SKILL.md
@@ -15,6 +15,8 @@ Repository overlay (optional):
Read only the files you need:
Load these files from `github/gh-aw` (they are not available locally).
+- `.github/aw/action-container-substitutions.md`
+- `.github/aw/agent-runtime-instructions.md`
- `.github/aw/agentic-chat.md`
- `.github/aw/agentic-workflows-mcp.md`
- `.github/aw/asciicharts.md`
@@ -22,6 +24,7 @@ Load these files from `github/gh-aw` (they are not available locally).
- `.github/aw/charts-trending.md`
- `.github/aw/charts.md`
- `.github/aw/cli-commands.md`
+- `.github/aw/compat.md`
- `.github/aw/configure-agentic-engine.md`
- `.github/aw/context.md`
- `.github/aw/create-agentic-workflow-trigger-details.md`
@@ -30,15 +33,24 @@ Load these files from `github/gh-aw` (they are not available locally).
- `.github/aw/debug-agentic-workflow.md`
- `.github/aw/dependabot.md`
- `.github/aw/deployment-status.md`
+- `.github/aw/designer-mappings.md`
- `.github/aw/designer.md`
+- `.github/aw/drive-memory.md`
+- `.github/aw/enclaves.md`
- `.github/aw/evals.md`
- `.github/aw/experiments.md`
- `.github/aw/github-agentic-workflows.md`
+- `.github/aw/github-mcp-server-pagination.md`
+- `.github/aw/github-mcp-server-tools.md`
- `.github/aw/github-mcp-server.md`
- `.github/aw/instructions.md`
+- `.github/aw/intent.md`
+- `.github/aw/jobs.md`
+- `.github/aw/linter-workflows.md`
- `.github/aw/llms.md`
- `.github/aw/loop.md`
- `.github/aw/lsp.md`
+- `.github/aw/maintainer.md`
- `.github/aw/mcp-clis.md`
- `.github/aw/memory-stateful-patterns.md`
- `.github/aw/memory.md`
@@ -47,7 +59,9 @@ Load these files from `github/gh-aw` (they are not available locally).
- `.github/aw/network.md`
- `.github/aw/optimize-agentic-workflow.md`
- `.github/aw/patterns.md`
+- `.github/aw/playwright.md`
- `.github/aw/pr-reviewer.md`
+- `.github/aw/release-workflow.md`
- `.github/aw/report.md`
- `.github/aw/reuse.md`
- `.github/aw/safe-outputs-automation.md`
@@ -61,10 +75,13 @@ Load these files from `github/gh-aw` (they are not available locally).
- `.github/aw/subagents.md`
- `.github/aw/syntax-agentic.md`
- `.github/aw/syntax-core.md`
+- `.github/aw/syntax-engine.md`
- `.github/aw/syntax-tools-imports.md`
- `.github/aw/syntax.md`
- `.github/aw/test-coverage.md`
- `.github/aw/test-expression.md`
+- `.github/aw/token-optimization-caching-budgets.md`
+- `.github/aw/token-optimization-observability.md`
- `.github/aw/token-optimization.md`
- `.github/aw/triggers.md`
- `.github/aw/update-agentic-workflow.md`
@@ -90,5 +107,6 @@ After loading the matching workflow prompt or skill, follow it directly:
- Choose workflow architecture and patterns: `.github/aw/patterns.md`
- Optimize token usage and cost: `.github/aw/token-optimization.md`
- Design long-running multi-agent research workflows: `.github/aw/multi-agent-research.md`
+- Add skills or agent plugins requested by the user (`skills:` / `plugins:` frontmatter, never on-the-fly installs): `.github/aw/skills.md`
When the task involves OTEL, OTLP, traces, observability backends, or telemetry-driven analysis, also read and follow `skills/otel-queries/SKILL.md` after loading the matching workflow prompt or skill.
diff --git a/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md b/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md
index d034b20377..a0fd94b943 100644
--- a/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md
+++ b/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md
@@ -33,7 +33,7 @@ The format is:
```yaml
models:
- - claude-sonnet-4.5
+ - claude-sonnet-5
conversations:
- messages:
- role: system
diff --git a/.github/skills/new-java-e2e-test-yaml-and-test/examples.md b/.github/skills/new-java-e2e-test-yaml-and-test/examples.md
index af82ef4dba..12971244a2 100644
--- a/.github/skills/new-java-e2e-test-yaml-and-test/examples.md
+++ b/.github/skills/new-java-e2e-test-yaml-and-test/examples.md
@@ -8,7 +8,7 @@ File: `test/snapshots/system_message_sections/should_use_replaced_identity_secti
```yaml
models:
- - claude-sonnet-4.5
+ - claude-sonnet-5
conversations:
- messages:
- role: system
@@ -73,7 +73,7 @@ File: `test/snapshots/system_message_transform/should_invoke_transform_callbacks
```yaml
models:
- - claude-sonnet-4.5
+ - claude-sonnet-5
conversations:
# First exchange: model decides to call tools
- messages:
diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml
deleted file mode 100644
index 28c4e67caf..0000000000
--- a/.github/workflows/agentics-maintenance.yml
+++ /dev/null
@@ -1,633 +0,0 @@
-# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
-#
-# ___ _ _
-# / _ \ | | (_)
-# | |_| | __ _ ___ _ __ | |_ _ ___
-# | _ |/ _` |/ _ \ '_ \| __| |/ __|
-# | | | | (_| | __/ | | | |_| | (__
-# \_| |_/\__, |\___|_| |_|\__|_|\___|
-# __/ |
-# _ _ |___/
-# | | | | / _| |
-# | | | | ___ _ __ _ __| |_| | _____ ____
-# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
-# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
-# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
-#
-#
-# To regenerate this workflow, run:
-# gh aw compile
-# Not all edits will cause changes to this file.
-#
-# For more information: https://github.github.com/gh-aw/introduction/overview/
-#
-# This file defines the generated agentic maintenance workflow for this repository.
-# It runs scheduled cleanup for expiring safe outputs and supports manual maintenance operations.
-#
-# This workflow is generated automatically when workflows use expiring safe outputs
-# or when repository maintenance features are enabled in .github/workflows/aw.json.
-#
-# To disable maintenance workflow generation, set in .github/workflows/aw.json:
-# {"maintenance": false}
-#
-# Agentic maintenance docs:
-# https://github.github.com/gh-aw/reference/ephemerals/#manual-maintenance-operations
-#
-name: Agentic Maintenance
-
-on:
- schedule:
- - cron: "37 0 * * *" # Daily (based on minimum expires: 30 days)
- workflow_dispatch:
- inputs:
- operation:
- description: 'Optional maintenance operation to run'
- required: false
- type: choice
- default: ''
- options:
- - ''
- - 'disable'
- - 'enable'
- - 'update'
- - 'upgrade'
- - 'safe_outputs'
- - 'create_labels'
- - 'activity_report'
- - 'close_agentic_workflows_issues'
- - 'clean_cache_memories'
- - 'update_pull_request_branches'
- - 'validate'
- - 'forecast'
- run_url:
- description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.'
- required: false
- type: string
- default: ''
- workflow_call:
- inputs:
- operation:
- description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate, forecast)'
- required: false
- type: string
- default: ''
- run_url:
- description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.'
- required: false
- type: string
- default: ''
- outputs:
- operation_completed:
- description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)'
- value: ${{ jobs.run_operation.outputs.operation || inputs.operation }}
- applied_run_url:
- description: 'The run URL that safe outputs were applied from'
- value: ${{ jobs.apply_safe_outputs.outputs.run_url }}
-
-permissions: {}
-
-jobs:
- close-expired-discussions:
- if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
- runs-on: ubuntu-slim
- permissions:
- discussions: write
- steps:
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Close expired discussions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_discussions.cjs');
- await main();
- close-expired-issues:
- if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
- runs-on: ubuntu-slim
- permissions:
- issues: write
- steps:
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Close expired issues
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_issues.cjs');
- await main();
- close-expired-pull-requests:
- if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
- runs-on: ubuntu-slim
- permissions:
- pull-requests: write
- steps:
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Close expired pull requests
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_pull_requests.cjs');
- await main();
-
- cleanup-cache-memory:
- if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }}
- runs-on: ubuntu-slim
- permissions:
- actions: write
- steps:
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Cleanup outdated cache-memory entries
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/cleanup_cache_memory.cjs');
- await main();
-
- run_operation:
- if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'update_pull_request_branches' && inputs.operation != 'validate' && inputs.operation != 'forecast' && (!(github.event.repository.fork)) }}
- runs-on: ubuntu-slim
- permissions:
- actions: write
- contents: write
- pull-requests: write
- outputs:
- operation: ${{ steps.record.outputs.operation }}
- steps:
- - name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- with:
- persist-credentials: false
-
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Check admin/maintainer permissions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
- await main();
-
- - name: Install gh-aw
- uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- version: v0.83.1
-
- - name: Run operation
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- GH_AW_OPERATION: ${{ inputs.operation }}
- GH_AW_CMD_PREFIX: gh aw
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs');
- await main();
-
- - name: Record outputs
- id: record
- env:
- GH_AW_OPERATION: ${{ inputs.operation }}
- run: echo "operation=$GH_AW_OPERATION" >> "$GITHUB_OUTPUT"
-
- update_pull_request_branches:
- if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches' && (!(github.event.repository.fork)) }}
- runs-on: ubuntu-slim
- permissions:
- contents: write
- pull-requests: write
- steps:
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Check admin/maintainer permissions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
- await main();
-
- - name: Update pull request branches
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/update_pull_request_branches.cjs');
- await main();
-
- apply_safe_outputs:
- if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }}
- runs-on: ubuntu-slim
- permissions:
- actions: read
- contents: write
- discussions: write
- issues: write
- pull-requests: write
- outputs:
- run_url: ${{ steps.record.outputs.run_url }}
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- with:
- sparse-checkout: |
- actions
- clean: false
- persist-credentials: false
-
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Check admin/maintainer permissions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
- await main();
-
- - name: Apply Safe Outputs
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- GH_AW_RUN_URL: ${{ inputs.run_url }}
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs');
- await main();
-
- - name: Record outputs
- id: record
- env:
- GH_AW_RUN_URL: ${{ inputs.run_url }}
- run: echo "run_url=$GH_AW_RUN_URL" >> "$GITHUB_OUTPUT"
-
- create_labels:
- if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }}
- runs-on: ubuntu-slim
- permissions:
- contents: read
- issues: write
- steps:
- - name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- with:
- persist-credentials: false
-
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Check admin/maintainer permissions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
- await main();
-
- - name: Install gh-aw
- uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- version: v0.83.1
-
- - name: Create missing labels
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_CMD_PREFIX: gh aw
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/create_labels.cjs');
- await main();
-
- activity_report:
- if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }}
- runs-on: ubuntu-slim
- timeout-minutes: 120
- permissions:
- actions: read
- contents: read
- issues: write
- steps:
- - name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- with:
- persist-credentials: false
-
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Check admin/maintainer permissions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
- await main();
-
- - name: Install gh-aw
- uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- version: v0.83.1
-
- - name: Restore activity report logs cache
- id: activity_report_logs_cache
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
- with:
- path: ./.cache/gh-aw/activity-report-logs
- key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}
- restore-keys: |
- ${{ runner.os }}-activity-report-logs-${{ github.repository }}-
- ${{ runner.os }}-activity-report-logs-
- - name: Download activity report logs
- timeout-minutes: 20
- shell: bash
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- GH_AW_CMD_PREFIX: gh aw
- run: |
- ${GH_AW_CMD_PREFIX} logs \
- --repo "$GITHUB_REPOSITORY" \
- --start-date -1w \
- --count 500 \
- --output ./.cache/gh-aw/activity-report-logs \
- --format markdown \
- --report-file ./.cache/gh-aw/activity-report-logs/report.md
-
- - name: Save activity report logs cache
- if: ${{ always() }}
- uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
- with:
- path: ./.cache/gh-aw/activity-report-logs
- key: ${{ steps.activity_report_logs_cache.outputs.cache-primary-key }}
-
- - name: Generate activity report issue
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const fs = require('node:fs');
- const reportPath = './.cache/gh-aw/activity-report-logs/report.md';
- if (!fs.existsSync(reportPath)) {
- core.warning('Activity report markdown not found at ' + reportPath + '; skipping issue creation.');
- return;
- }
- let reportBody = '';
- try {
- reportBody = fs.readFileSync(reportPath, 'utf8').trim();
- } catch (error) {
- core.warning('Failed to read activity report markdown at ' + reportPath + ': ' + error.message);
- return;
- }
- if (!reportBody) {
- core.warning('Activity report markdown is empty at ' + reportPath + '; skipping issue creation.');
- return;
- }
- const repoSlug = context.repo.owner + '/' + context.repo.repo;
- const body = [
- '### Agentic workflow activity report',
- '',
- 'Repository: ' + repoSlug,
- 'Generated at: ' + new Date().toISOString(),
- '',
- reportBody,
- ].join('\n');
- const createdIssue = await github.rest.issues.create({
- owner: context.repo.owner,
- repo: context.repo.repo,
- title: '[aw] agentic status report',
- body,
- labels: ['agentic-workflows'],
- });
- core.info('Created issue #' + createdIssue.data.number + ': ' + createdIssue.data.html_url);
-
- forecast_report:
- if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'forecast' && (!(github.event.repository.fork)) }}
- runs-on: ubuntu-slim
- timeout-minutes: 60
- permissions:
- actions: read
- contents: read
- issues: write
- steps:
- - name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- with:
- persist-credentials: false
-
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Check admin/maintainer permissions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
- await main();
-
- - name: Install gh-aw
- uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- version: v0.83.1
-
- - name: Restore forecast report logs cache
- id: forecast_report_logs_cache
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
- with:
- path: ./.github/aw/logs
- key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}
- restore-keys: |
- ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-
- ${{ runner.os }}-forecast-report-logs-
-
- - name: Generate forecast report
- id: generate_forecast_report
- timeout-minutes: 30
- shell: bash
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- DEBUG: "*"
- GH_AW_CMD_PREFIX: gh aw
- run: |
- mkdir -p ./.cache/gh-aw/forecast
- set +e
- ${GH_AW_CMD_PREFIX} forecast --repo "$GITHUB_REPOSITORY" --timeout 30 --verbose --json > ./.cache/gh-aw/forecast/report.json
- forecast_exit_code=$?
- set -e
- if [ "${forecast_exit_code}" -eq 124 ]; then
- echo '{"outcome":"timeout","message":"Forecast computation timed out after 30 minutes."}' > ./.cache/gh-aw/forecast/error.json
- echo "::error::Forecast computation timed out after 30 minutes."
- exit 1
- fi
- if [ "${forecast_exit_code}" -ne 0 ]; then
- echo '{"outcome":"error","message":"Forecast computation failed before producing a report."}' > ./.cache/gh-aw/forecast/error.json
- echo "::error::Forecast computation failed with exit code ${forecast_exit_code}."
- exit 1
- fi
-
- - name: Debug forecast logs folder
- if: ${{ always() }}
- shell: bash
- run: |
- if [ ! -d ./.github/aw/logs ]; then
- echo "Logs directory not found: ./.github/aw/logs"
- exit 0
- fi
- echo "Files under ./.github/aw/logs:"
- find ./.github/aw/logs -type f | sort
-
- - name: Save forecast report logs cache
- if: ${{ always() }}
- uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
- with:
- path: ./.github/aw/logs
- key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}
-
- - name: Generate forecast issue
- if: ${{ always() }}
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- FORECAST_STEP_OUTCOME: ${{ steps.generate_forecast_report.outcome }}
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/create_forecast_issue.cjs');
- await main();
-
- close_agentic_workflows_issues:
- if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues' && (!(github.event.repository.fork)) }}
- runs-on: ubuntu-slim
- permissions:
- issues: write
- steps:
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Check admin/maintainer permissions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
- await main();
-
- - name: Close no-repro agentic-workflows issues
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/close_agentic_workflows_issues.cjs');
- await main();
-
- validate_workflows:
- if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate' && (!(github.event.repository.fork)) }}
- runs-on: ubuntu-latest
- permissions:
- contents: read
- issues: write
- steps:
- - name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- with:
- persist-credentials: false
-
- - name: Setup Scripts
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
-
- - name: Check admin/maintainer permissions
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
- await main();
-
- - name: Install gh-aw
- uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
- with:
- version: v0.83.1
-
- - name: Validate workflows and file issue on findings
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_CMD_PREFIX: gh aw
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/run_validate_workflows.cjs');
- await main();
diff --git a/.github/workflows/cross-repo-issue-analysis.lock.yml b/.github/workflows/cross-repo-issue-analysis.lock.yml
index 510618f041..f5c636eb05 100644
--- a/.github/workflows/cross-repo-issue-analysis.lock.yml
+++ b/.github/workflows/cross-repo-issue-analysis.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"16de319b1db6be0d409d3055ca8fa9f619f2c0120dad678248a45dce07b880b4","body_hash":"653dfb46c89df98eca22ddfb802149d6ade32e9a7ad40dbdc51bfb6b0ba1c4a3","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","RUNTIME_TRIAGE_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"16de319b1db6be0d409d3055ca8fa9f619f2c0120dad678248a45dce07b880b4","body_hash":"653dfb46c89df98eca22ddfb802149d6ade32e9a7ad40dbdc51bfb6b0ba1c4a3","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","RUNTIME_TRIAGE_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_labels","create_issue","missing_data","missing_tool","noop"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,7 +26,7 @@
# Analyzes copilot-sdk issues to determine if a fix is needed in copilot-agent-runtime, then opens a linked issue there
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -35,22 +35,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "SDK Runtime Triage"
on:
@@ -73,9 +71,18 @@ permissions: {}
concurrency:
group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}"
+ queue: max
run-name: "SDK Runtime Triage"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.cross-repo-issue-analysis
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=SDK%20Runtime%20Triage,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
needs: pre_activation
@@ -93,6 +100,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -108,7 +116,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -118,34 +126,39 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Generate agentic run info
id: generate_aw_info
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "SDK Runtime Triage"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -167,9 +180,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -187,38 +202,37 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -227,38 +241,47 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Compute current body text
id: sanitized
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,s.symcb.com,s.symcd.com,security.ubuntu.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');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs');
+ const { main } = require(path.join(actionsDir, 'compute_text.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
@@ -270,63 +293,22 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_41a978c1ce3777a8_EOF'
-
- GH_AW_PROMPT_41a978c1ce3777a8_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_41a978c1ce3777a8_EOF'
-
- Tools: create_issue, add_labels(max:3), missing_tool, missing_data, noop
-
- GH_AW_PROMPT_41a978c1ce3777a8_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_41a978c1ce3777a8_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_41a978c1ce3777a8_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_41a978c1ce3777a8_EOF'
-
- {{#runtime-import .github/workflows/cross-repo-issue-analysis.md}}
- GH_AW_PROMPT_41a978c1ce3777a8_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: create_issue, add_labels(max:3), missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/cross-repo-issue-analysis.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
GH_AW_GITHUB_EVENT_ISSUE_TITLE: ${{ github.event.issue.title }}
@@ -334,14 +316,16 @@ jobs:
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
@@ -357,10 +341,12 @@ jobs:
GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -383,16 +369,20 @@ jobs:
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: activation
@@ -419,12 +409,19 @@ jobs:
copilot-requests: write
issues: read
pull-requests: read
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: crossrepoissueanalysis
outputs:
@@ -438,7 +435,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -446,11 +446,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -459,19 +460,28 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -507,16 +517,19 @@ jobs:
with:
github-token: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -525,13 +538,15 @@ jobs:
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -543,15 +558,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_f9dc8569c195ba44_EOF'
- {"add_labels":{"allowed":["runtime","sdk-fix-only","needs-investigation"],"max":3,"target":"triggering"},"create_issue":{"labels":["upstream-from-sdk","ai-triaged"],"max":1,"target-repo":"github/copilot-agent-runtime","title_prefix":"[copilot-sdk] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_f9dc8569c195ba44_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_labels\":{\"allowed\":[\"runtime\",\"sdk-fix-only\",\"needs-investigation\"],\"max\":3,\"target\":\"triggering\"},\"create_issue\":{\"labels\":[\"upstream-from-sdk\",\"ai-triaged\"],\"max\":1,\"target-repo\":\"github/copilot-agent-runtime\",\"title_prefix\":\"[copilot-sdk] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -584,6 +610,7 @@ jobs:
"create_issue": {
"defaultMax": 1,
"fields": {
+ "blocked_by": {},
"body": {
"required": true,
"type": "string",
@@ -695,9 +722,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -706,6 +735,7 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
@@ -713,33 +743,45 @@ jobs:
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_46604863f3d8e286_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -772,6 +814,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -783,7 +833,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -791,25 +841,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF
+ GH_AW_MCP_CONFIG_46604863f3d8e286_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -850,18 +907,33 @@ jobs:
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -879,14 +951,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(github:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(ls:*)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(tail:*)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(github:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(ls:*)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(tail:*)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -894,7 +971,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -915,7 +992,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 20
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -931,7 +1019,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -940,9 +1028,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN,RUNTIME_TRIAGE_TOKEN'
@@ -966,15 +1056,17 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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_GITHUB_REFS: "repo,github/copilot-agent-runtime"
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_API_URL: ${{ github.api_url }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -984,9 +1076,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -994,9 +1088,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -1010,9 +1106,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -1020,16 +1118,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1046,6 +1169,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1065,10 +1190,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
- contents: read
+ actions: read
issues: write
pull-requests: write
concurrency:
@@ -1085,7 +1210,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1094,15 +1219,16 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1110,42 +1236,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: safe-outputs-items
+ pattern: safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1159,6 +1272,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1181,9 +1296,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1213,7 +1330,7 @@ jobs:
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/cross-repo-issue-analysis.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1221,9 +1338,11 @@ jobs:
with:
github-token: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1238,9 +1357,11 @@ jobs:
with:
github-token: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1253,9 +1374,11 @@ jobs:
with:
github-token: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1268,9 +1391,11 @@ jobs:
with:
github-token: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1283,7 +1408,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "cross-repo-issue-analysis"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1297,6 +1422,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
@@ -1312,9 +1441,30 @@ jobs:
with:
github-token: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "SDK Runtime Triage"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/cross-repo-issue-analysis.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1326,6 +1476,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1336,7 +1487,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1345,15 +1496,22 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1361,10 +1519,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1373,7 +1533,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1397,21 +1557,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1419,82 +1565,52 @@ jobs:
WORKFLOW_NAME: "SDK Runtime Triage"
WORKFLOW_DESCRIPTION: "Analyzes copilot-sdk issues to determine if a fix is needed in copilot-agent-runtime, then opens a linked issue there"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1510,58 +1626,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "SDK Runtime Triage"
+ WORKFLOW_DESCRIPTION: "Analyzes copilot-sdk issues to determine if a fix is needed in copilot-agent-runtime, then opens a linked issue there"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
pre_activation:
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'runtime triage'
@@ -1577,15 +1740,15 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Check team membership for workflow
id: check_membership
@@ -1595,9 +1758,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs');
+ const { main } = require(path.join(actionsDir, 'check_membership.cjs'));
await main();
safe_outputs:
@@ -1608,7 +1773,6 @@ jobs:
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
runs-on: ubuntu-slim
permissions:
- contents: read
issues: write
pull-requests: write
timeout-minutes: 45
@@ -1622,7 +1786,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "cross-repo-issue-analysis"
@@ -1635,12 +1798,20 @@ jobs:
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }}
created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1649,15 +1820,18 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1665,7 +1839,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
@@ -1681,16 +1857,18 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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: "{\"add_labels\":{\"allowed\":[\"runtime\",\"sdk-fix-only\",\"needs-investigation\"],\"max\":3,\"target\":\"triggering\"},\"create_issue\":{\"labels\":[\"upstream-from-sdk\",\"ai-triaged\"],\"max\":1,\"target-repo\":\"github/copilot-agent-runtime\",\"title_prefix\":\"[copilot-sdk] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_labels\":{\"allowed\":[\"runtime\",\"sdk-fix-only\",\"needs-investigation\"],\"max\":3,\"target\":\"triggering\"},\"create_issue\":{\"labels\":[\"upstream-from-sdk\",\"ai-triaged\"],\"max\":1,\"target-repo\":\"github/copilot-agent-runtime\",\"title_prefix\":\"[copilot-sdk] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
with:
github-token: ${{ secrets.RUNTIME_TRIAGE_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1700,4 +1878,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/dotnet-sdk-tests.yml b/.github/workflows/dotnet-sdk-tests.yml
index 4695cae7f2..9b7216ad20 100644
--- a/.github/workflows/dotnet-sdk-tests.yml
+++ b/.github/workflows/dotnet-sdk-tests.yml
@@ -1,9 +1,6 @@
name: ".NET SDK Tests"
on:
- push:
- branches:
- - main
workflow_dispatch:
workflow_call:
@@ -11,6 +8,40 @@ permissions:
contents: read
jobs:
+ validate:
+ name: ".NET SDK Build and Format"
+ if: github.event.repository.fork == false
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ defaults:
+ run:
+ shell: bash
+ working-directory: ./dotnet
+ steps:
+ - uses: actions/checkout@v6.0.2
+ - uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: "10.0.x"
+ - uses: actions/setup-node@v6
+ with:
+ node-version: "22"
+
+ - name: Restore .NET dependencies
+ run: dotnet restore
+
+ - name: Run dotnet format check
+ run: |
+ if ! dotnet format --no-restore --verify-no-changes; then
+ echo "❌ dotnet format produced changes. Please run 'dotnet format' in dotnet"
+ exit 1
+ fi
+ echo "✅ dotnet format produced no changes"
+
+ # Build the whole solution once to validate every SDK target framework.
+ # Matrix cells below only need to build frameworks consumed by the tests.
+ - name: Build SDK
+ run: dotnet build --no-restore
+
test:
name: ".NET SDK Tests (${{ matrix.os }}, ${{ matrix.transport }}, ${{ matrix.backend }}, ${{ matrix.shard }})"
if: github.event.repository.fork == false
@@ -73,7 +104,65 @@ jobs:
- os: macos-latest
transport: default
backend: capi
- shard: "2b"
+ shard: "2b-pending"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-permission"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-auth"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-hooks"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-unit-p"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-provider"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-rpc-additional"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-rpc-agent"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-rpc-event-log"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-rpc-event-effects"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-rpc-mcp-skills"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-rpc-mcp-config"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-rpc-mcp-lifecycle"
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: "2b-rpc-q-z"
+ # Standalone extension tests require the legacy JavaScript entrypoint.
+ # Keep its subprocess tree isolated from the rest of the O/P/R shard.
+ - os: macos-latest
+ transport: default
+ backend: capi
+ shard: extensions
- os: macos-latest
transport: default
backend: capi
@@ -97,7 +186,7 @@ jobs:
# A hung test used to run until the runner died (~50 min) and the dying
# runner never uploaded its logs, so the failures were undiagnosable.
# Every healthy cell finishes well under 15 min.
- timeout-minutes: 30
+ timeout-minutes: 20
defaults:
run:
shell: bash
@@ -111,27 +200,16 @@ jobs:
with:
node-version: "22"
cache: "npm"
- cache-dependency-path: "./nodejs/package-lock.json"
+ cache-dependency-path: |
+ ./nodejs/package-lock.json
+ ./test/harness/package-lock.json
- - name: Install Node.js dependencies (for CLI version extraction)
+ - name: Install Node.js dependencies
working-directory: ./nodejs
run: npm ci --ignore-scripts
- name: Restore .NET dependencies
- run: dotnet restore
-
- - name: Run dotnet format check
- if: runner.os == 'Linux'
- run: |
- dotnet format --verify-no-changes
- if [ $? -ne 0 ]; then
- echo "❌ dotnet format produced changes. Please run 'dotnet format' in dotnet"
- exit 1
- fi
- echo "✅ dotnet format produced no changes"
-
- - name: Build SDK
- run: dotnet build --no-restore
+ run: dotnet restore test/GitHub.Copilot.SDK.Test.csproj
- name: Install test harness dependencies
working-directory: ./test/harness
@@ -153,26 +231,95 @@ jobs:
# --blame-hang names the offending test instead of letting it wedge
# the runner. No single test legitimately runs for 10 minutes; the
# whole suite normally finishes in about five.
- args=(--no-build -v n --blame-hang --blame-hang-timeout 10m --blame-hang-dump-type none)
+ # The validation job performs the full analyzer-enabled SDK build.
+ # Build only test-consumed frameworks here and do not repeat analyzers.
+ args=(
+ --no-restore
+ -v n
+ --blame-hang
+ --blame-hang-timeout 10m
+ --blame-hang-dump-type none
+ --logger "trx;LogFilePrefix=test-results"
+ --results-directory "$GITHUB_WORKSPACE/dotnet/TestResults"
+ -p:RunAnalyzers=false
+ )
filter="$DOTNET_TEST_FILTER"
if [[ "$DOTNET_TEST_SHARD" != "full" ]]; then
case "$DOTNET_TEST_SHARD" in
1)
- initials=(A C D H I J K L N Q S U W Y)
+ # M moved here after recent runs showed shard 2 was about two
+ # minutes slower; this keeps Windows and macOS balanced.
+ initials=(A C D H I J K L M N Q S U W Y)
shard_filter="FullyQualifiedName~GitHub.Copilot.Test.ConnectionToken"
;;
2)
- initials=(B E F G M O P R T V X Z)
+ initials=(B E F G O P R T V X Z)
shard_filter=""
;;
2a)
initials=(B E F G)
shard_filter=""
;;
- 2b)
- initials=(M O P R)
- shard_filter=""
+ 2b-pending)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PendingWorkResumeE2ETests"
+ ;;
+ 2b-permission)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PermissionE2ETests"
+ ;;
+ 2b-auth)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PerSessionAuthE2ETests"
+ ;;
+ 2b-hooks)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PreMcpToolCallHookE2ETests"
+ ;;
+ 2b-unit-p)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.Unit.P"
+ ;;
+ 2b-provider)
+ initials=(O)
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.ProviderEndpointE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RewindE2ETests|FullyQualifiedName~GitHub.Copilot.Test.Unit.R"
+ ;;
+ 2b-rpc-additional)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcAdditionalEdgeCasesE2ETests"
+ ;;
+ 2b-rpc-agent)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcAgentE2ETests"
+ ;;
+ 2b-rpc-event-log)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcEventLogE2ETests"
+ ;;
+ 2b-rpc-event-effects)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcEventSideEffectsE2ETests"
+ ;;
+ 2b-rpc-mcp-skills)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpAndSkillsE2ETests"
+ ;;
+ 2b-rpc-mcp-config)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpConfigE2ETests"
+ ;;
+ 2b-rpc-mcp-lifecycle)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpLifecycleE2ETests"
+ ;;
+ 2b-rpc-q-z)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcQueueE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcRemoteE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcScheduleE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerMiscE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerPluginsE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerRemoteControlE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcSessionStateE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcSessionStateExtrasE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellAndFleetE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellEdgeCaseE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellUserRequestedE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcTasksAndHandlersE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcUiEphemeralQueryE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcWorkspaceCheckpointsE2ETests"
+ ;;
+ extensions)
+ initials=()
+ shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcExtensionsLoadedE2ETests"
;;
2c)
initials=(T V X Z)
@@ -192,4 +339,13 @@ jobs:
if [[ -n "$filter" ]]; then
args+=(--filter "$filter")
fi
- dotnet test "${args[@]}"
+ dotnet test test/GitHub.Copilot.SDK.Test.csproj "${args[@]}"
+
+ - name: Upload .NET test diagnostics
+ if: failure()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: dotnet-test-diagnostics-${{ matrix.os }}-${{ matrix.transport }}-${{ matrix.backend }}-${{ matrix.shard }}-${{ github.run_attempt }}
+ path: dotnet/TestResults/
+ if-no-files-found: warn
+ retention-days: 7
diff --git a/.github/workflows/go-sdk-tests.yml b/.github/workflows/go-sdk-tests.yml
index 61d74d257e..4fb6fd0184 100644
--- a/.github/workflows/go-sdk-tests.yml
+++ b/.github/workflows/go-sdk-tests.yml
@@ -1,9 +1,6 @@
name: "Go SDK Tests"
on:
- push:
- branches:
- - main
workflow_dispatch:
workflow_call:
@@ -22,6 +19,7 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
transport: ["default", "inprocess"]
runs-on: ${{ matrix.os }}
+ timeout-minutes: 20
defaults:
run:
shell: bash
diff --git a/.github/workflows/handle-bug.lock.yml b/.github/workflows/handle-bug.lock.yml
index 153e882c44..6d2c916a4f 100644
--- a/.github/workflows/handle-bug.lock.yml
+++ b/.github/workflows/handle-bug.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"df4e7ec5b346a28c7de5353cbfb15d329d03eb7092f2ded784ee6602108461e6","body_hash":"376c982b907760113954510ef1aff70d22dcb172c7bb851b2fa3d82121bdbc1c","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c5b0e0eada050c6349c6630c62a7d91425c57a49758a3d8937e851eb3275e5f9","body_hash":"376c982b907760113954510ef1aff70d22dcb172c7bb851b2fa3d82121bdbc1c","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,7 +26,7 @@
# Handles issues classified as bugs by the triage classifier
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -34,22 +34,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Bug Handler"
on:
@@ -74,7 +72,7 @@ on:
description: URL of the first added comment
value: ${{ jobs.safe_outputs.outputs.comment_url }}
secrets:
- COPILOT_GITHUB_TOKEN:
+ GH_AW_DEFAULT_OTLP_HEADERS:
required: false
GH_AW_GITHUB_MCP_SERVER_TOKEN:
required: false
@@ -85,9 +83,18 @@ permissions: {}
concurrency:
group: "gh-aw-handle-bug-${{ github.run_id }}"
+ queue: max
run-name: "Bug Handler"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.handle-bug
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Bug%20Handler,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -102,6 +109,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -119,7 +127,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -127,10 +135,12 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Resolve host repo for activation checkout
id: resolve-host-repo
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -141,9 +151,11 @@ jobs:
JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs');
+ const { main } = require(path.join(actionsDir, 'resolve_host_repo.cjs'));
await main();
- name: Compute artifact prefix
id: artifact-prefix
@@ -155,27 +167,30 @@ jobs:
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Bug Handler"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -197,9 +212,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -217,15 +234,16 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Print cross-repo setup guidance
@@ -236,7 +254,7 @@ jobs:
echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup"
- name: Checkout .github and .agents folders
if: steps.resolve-host-repo.outputs.target_repo == github.repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
repository: ${{ steps.resolve-host-repo.outputs.target_repo }}
@@ -244,20 +262,18 @@ jobs:
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -266,27 +282,34 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -296,75 +319,36 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_04d69bd6df5739b0_EOF'
-
- GH_AW_PROMPT_04d69bd6df5739b0_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_04d69bd6df5739b0_EOF'
-
- Tools: add_comment, add_labels, missing_tool, missing_data, noop
-
- GH_AW_PROMPT_04d69bd6df5739b0_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_04d69bd6df5739b0_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_04d69bd6df5739b0_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_04d69bd6df5739b0_EOF'
-
- {{#runtime-import .github/workflows/handle-bug.md}}
- GH_AW_PROMPT_04d69bd6df5739b0_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, add_labels, missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/handle-bug.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -374,13 +358,14 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -394,22 +379,25 @@ jobs:
GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
- GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER,
- GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST
+ GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER
}
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.artifact-prefix.outputs.prefix }}activation
@@ -439,12 +427,19 @@ jobs:
concurrency:
group: "gh-aw-copilot-handle-bug-${{ inputs.issue_number }}"
queue: max
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: handlebug
outputs:
@@ -459,7 +454,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -467,11 +465,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -480,20 +479,29 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -523,16 +531,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -542,7 +553,9 @@ jobs:
GH_AW_GITHUB_MIN_INTEGRITY: 'none'
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Parse integrity filter lists
id: parse-guard-vars
@@ -554,8 +567,8 @@ jobs:
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -567,15 +580,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_18a2d1564ec59c01_EOF'
- {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["bug","enhancement","question","documentation"],"max":1,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_18a2d1564ec59c01_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -598,9 +622,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -608,6 +641,16 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -704,9 +747,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -715,38 +760,51 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -782,6 +840,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -793,7 +859,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -801,25 +867,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF
+ GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -831,22 +904,40 @@ jobs:
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
+ # --allow-tool github
+ # --allow-tool safeoutputs
+ # --allow-tool write
timeout-minutes: 20
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -864,14 +955,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -879,7 +975,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -900,7 +996,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 20
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -916,7 +1023,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -925,9 +1032,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -950,14 +1059,16 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -967,9 +1078,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -977,9 +1090,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -993,9 +1108,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -1003,16 +1120,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1031,6 +1173,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1050,10 +1194,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
- contents: read
+ actions: read
issues: write
pull-requests: write
concurrency:
@@ -1070,7 +1214,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1079,8 +1223,8 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
- name: Download agent output artifact
@@ -1088,7 +1232,8 @@ jobs:
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.activation.outputs.artifact_prefix }}agent,${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1096,42 +1241,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items
+ pattern: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1145,6 +1277,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1167,9 +1301,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1199,7 +1335,7 @@ jobs:
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/handle-bug.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1207,9 +1343,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1224,9 +1362,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1239,9 +1379,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1254,9 +1396,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1269,7 +1413,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "handle-bug"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1283,6 +1427,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
@@ -1298,9 +1446,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Bug Handler"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/handle-bug.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1312,6 +1481,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1322,7 +1492,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1331,16 +1501,23 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.agent.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.agent.outputs.artifact_prefix }}agent,${{ needs.agent.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1348,10 +1525,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1360,7 +1539,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1384,21 +1563,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1406,82 +1571,52 @@ jobs:
WORKFLOW_NAME: "Bug Handler"
WORKFLOW_DESCRIPTION: "Handles issues classified as bugs by the triage classifier"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1497,58 +1632,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Bug Handler"
+ WORKFLOW_DESCRIPTION: "Handles issues classified as bugs by the triage classifier"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ needs.agent.outputs.artifact_prefix }}detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1558,7 +1740,6 @@ jobs:
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
runs-on: ubuntu-slim
permissions:
- contents: read
issues: write
pull-requests: write
timeout-minutes: 45
@@ -1572,7 +1753,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "handle-bug"
@@ -1585,12 +1765,20 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1599,16 +1787,19 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.activation.outputs.artifact_prefix }}agent,${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1616,7 +1807,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
@@ -1632,16 +1825,18 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1651,4 +1846,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/handle-bug.md b/.github/workflows/handle-bug.md
index 8d426ce5d6..21f9532bc2 100644
--- a/.github/workflows/handle-bug.md
+++ b/.github/workflows/handle-bug.md
@@ -18,6 +18,8 @@ permissions:
pull-requests: read
copilot-requests: write
tools:
+ bash: []
+ cli-proxy: false
github:
toolsets: [default]
min-integrity: none
@@ -62,4 +64,4 @@ Based on your investigation, take **one** of the following actions:
**Always leave a comment** explaining your findings, even when confirming the issue is a bug. Include:
- What you investigated (which files/code paths you looked at)
- What you found (is the behavior intentional or not)
-- Why you applied the label you chose
+- Why you applied the label you chose
\ No newline at end of file
diff --git a/.github/workflows/handle-documentation.lock.yml b/.github/workflows/handle-documentation.lock.yml
index 0a5e68efe3..ea7cd2121d 100644
--- a/.github/workflows/handle-documentation.lock.yml
+++ b/.github/workflows/handle-documentation.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6c3b9dc8d0f7b54d44175db209cda34e37ea8c635d01ee0a5cba13675053f6cd","body_hash":"81c8287f5691cdc10ae8f60c004bb671d9b4942740d73fcc9646e28fbcd8790e","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6624c9bc7f3d51508aa278e0086a6066843a0331d8220f477c10f156f26f7960","body_hash":"81c8287f5691cdc10ae8f60c004bb671d9b4942740d73fcc9646e28fbcd8790e","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,7 +26,7 @@
# Handles issues classified as documentation-related by the triage classifier
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -34,22 +34,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Documentation Handler"
on:
@@ -74,7 +72,7 @@ on:
description: URL of the first added comment
value: ${{ jobs.safe_outputs.outputs.comment_url }}
secrets:
- COPILOT_GITHUB_TOKEN:
+ GH_AW_DEFAULT_OTLP_HEADERS:
required: false
GH_AW_GITHUB_MCP_SERVER_TOKEN:
required: false
@@ -85,9 +83,18 @@ permissions: {}
concurrency:
group: "gh-aw-handle-documentation-${{ github.run_id }}"
+ queue: max
run-name: "Documentation Handler"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.handle-documentation
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Documentation%20Handler,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -102,6 +109,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -119,7 +127,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -127,10 +135,12 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Resolve host repo for activation checkout
id: resolve-host-repo
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -141,9 +151,11 @@ jobs:
JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs');
+ const { main } = require(path.join(actionsDir, 'resolve_host_repo.cjs'));
await main();
- name: Compute artifact prefix
id: artifact-prefix
@@ -155,27 +167,30 @@ jobs:
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Documentation Handler"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -197,9 +212,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -217,15 +234,16 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Print cross-repo setup guidance
@@ -236,7 +254,7 @@ jobs:
echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup"
- name: Checkout .github and .agents folders
if: steps.resolve-host-repo.outputs.target_repo == github.repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
repository: ${{ steps.resolve-host-repo.outputs.target_repo }}
@@ -244,20 +262,18 @@ jobs:
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -266,27 +282,34 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -296,75 +319,36 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_b3d8e6ce75517df8_EOF'
-
- GH_AW_PROMPT_b3d8e6ce75517df8_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_b3d8e6ce75517df8_EOF'
-
- Tools: add_comment, add_labels, missing_tool, missing_data, noop
-
- GH_AW_PROMPT_b3d8e6ce75517df8_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_b3d8e6ce75517df8_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_b3d8e6ce75517df8_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_b3d8e6ce75517df8_EOF'
-
- {{#runtime-import .github/workflows/handle-documentation.md}}
- GH_AW_PROMPT_b3d8e6ce75517df8_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, add_labels, missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/handle-documentation.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -374,13 +358,14 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -394,22 +379,25 @@ jobs:
GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
- GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER,
- GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST
+ GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER
}
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.artifact-prefix.outputs.prefix }}activation
@@ -439,12 +427,19 @@ jobs:
concurrency:
group: "gh-aw-copilot-handle-documentation-${{ inputs.issue_number }}"
queue: max
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: handledocumentation
outputs:
@@ -459,7 +454,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -467,11 +465,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -480,20 +479,29 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -523,16 +531,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -542,7 +553,9 @@ jobs:
GH_AW_GITHUB_MIN_INTEGRITY: 'none'
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Parse integrity filter lists
id: parse-guard-vars
@@ -554,8 +567,8 @@ jobs:
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -567,15 +580,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_6c1b251bac7b1edb_EOF'
- {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["documentation"],"max":1,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_6c1b251bac7b1edb_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"documentation\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -598,9 +622,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -608,6 +641,16 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -704,9 +747,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -715,38 +760,51 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -782,6 +840,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -793,7 +859,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -801,25 +867,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF
+ GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -831,22 +904,40 @@ jobs:
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
+ # --allow-tool github
+ # --allow-tool safeoutputs
+ # --allow-tool write
timeout-minutes: 5
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -864,14 +955,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -879,7 +975,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 5
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -900,7 +996,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 5
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -916,7 +1023,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -925,9 +1032,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -950,14 +1059,16 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -967,9 +1078,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -977,9 +1090,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -993,9 +1108,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -1003,16 +1120,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1031,6 +1173,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1050,10 +1194,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
- contents: read
+ actions: read
issues: write
pull-requests: write
concurrency:
@@ -1070,7 +1214,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1079,8 +1223,8 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
- name: Download agent output artifact
@@ -1088,7 +1232,8 @@ jobs:
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.activation.outputs.artifact_prefix }}agent,${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1096,42 +1241,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items
+ pattern: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1145,6 +1277,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1167,9 +1301,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1199,7 +1335,7 @@ jobs:
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/handle-documentation.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1207,9 +1343,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1224,9 +1362,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1239,9 +1379,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1254,9 +1396,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1269,7 +1413,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "handle-documentation"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1283,6 +1427,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
@@ -1298,9 +1446,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Documentation Handler"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/handle-documentation.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1312,6 +1481,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1322,7 +1492,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1331,16 +1501,23 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.agent.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.agent.outputs.artifact_prefix }}agent,${{ needs.agent.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1348,10 +1525,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1360,7 +1539,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1384,21 +1563,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1406,82 +1571,52 @@ jobs:
WORKFLOW_NAME: "Documentation Handler"
WORKFLOW_DESCRIPTION: "Handles issues classified as documentation-related by the triage classifier"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1497,58 +1632,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Documentation Handler"
+ WORKFLOW_DESCRIPTION: "Handles issues classified as documentation-related by the triage classifier"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ needs.agent.outputs.artifact_prefix }}detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1558,7 +1740,6 @@ jobs:
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
runs-on: ubuntu-slim
permissions:
- contents: read
issues: write
pull-requests: write
timeout-minutes: 45
@@ -1572,7 +1753,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "handle-documentation"
@@ -1585,12 +1765,20 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1599,16 +1787,19 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.activation.outputs.artifact_prefix }}agent,${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1616,7 +1807,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
@@ -1632,16 +1825,18 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"documentation\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"documentation\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1651,4 +1846,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/handle-documentation.md b/.github/workflows/handle-documentation.md
index 12449a85ca..543d7f71d5 100644
--- a/.github/workflows/handle-documentation.md
+++ b/.github/workflows/handle-documentation.md
@@ -18,6 +18,8 @@ permissions:
pull-requests: read
copilot-requests: write
tools:
+ bash: []
+ cli-proxy: false
github:
toolsets: [default]
min-integrity: none
@@ -44,4 +46,4 @@ You are an AI agent that handles issues classified as documentation-related in t
4. Leave a comment that includes:
- A summary of the documentation gap (what is missing, incorrect, or unclear)
- Which documentation pages, files, or sections are affected
- - A brief description of what content should be added or improved to resolve the issue
+ - A brief description of what content should be added or improved to resolve the issue
\ No newline at end of file
diff --git a/.github/workflows/handle-enhancement.lock.yml b/.github/workflows/handle-enhancement.lock.yml
index 5943203871..b87f3c22a3 100644
--- a/.github/workflows/handle-enhancement.lock.yml
+++ b/.github/workflows/handle-enhancement.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f194730258943dec72121a119dba066ff5fee588d79f69fddddd4ca29b56ffd4","body_hash":"624219976b9b7078c6bb11c4177925478cfd8316fe8de535a581bdd176eda825","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c7efbd4a51f5a1e6f8b189c420a07337135cdb5f25290588d167dc9b4ae1c117","body_hash":"624219976b9b7078c6bb11c4177925478cfd8316fe8de535a581bdd176eda825","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,7 +26,7 @@
# Handles issues classified as enhancements by the triage classifier
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -34,22 +34,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Enhancement Handler"
on:
@@ -74,7 +72,7 @@ on:
description: URL of the first added comment
value: ${{ jobs.safe_outputs.outputs.comment_url }}
secrets:
- COPILOT_GITHUB_TOKEN:
+ GH_AW_DEFAULT_OTLP_HEADERS:
required: false
GH_AW_GITHUB_MCP_SERVER_TOKEN:
required: false
@@ -85,9 +83,18 @@ permissions: {}
concurrency:
group: "gh-aw-handle-enhancement-${{ github.run_id }}"
+ queue: max
run-name: "Enhancement Handler"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.handle-enhancement
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Enhancement%20Handler,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -102,6 +109,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -119,7 +127,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -127,10 +135,12 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Resolve host repo for activation checkout
id: resolve-host-repo
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -141,9 +151,11 @@ jobs:
JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs');
+ const { main } = require(path.join(actionsDir, 'resolve_host_repo.cjs'));
await main();
- name: Compute artifact prefix
id: artifact-prefix
@@ -155,27 +167,30 @@ jobs:
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Enhancement Handler"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -197,9 +212,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -217,15 +234,16 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Print cross-repo setup guidance
@@ -236,7 +254,7 @@ jobs:
echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup"
- name: Checkout .github and .agents folders
if: steps.resolve-host-repo.outputs.target_repo == github.repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
repository: ${{ steps.resolve-host-repo.outputs.target_repo }}
@@ -244,20 +262,18 @@ jobs:
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -266,27 +282,34 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -296,75 +319,36 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_e59da2f8e25b61b4_EOF'
-
- GH_AW_PROMPT_e59da2f8e25b61b4_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_e59da2f8e25b61b4_EOF'
-
- Tools: add_comment, add_labels, missing_tool, missing_data, noop
-
- GH_AW_PROMPT_e59da2f8e25b61b4_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_e59da2f8e25b61b4_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_e59da2f8e25b61b4_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_e59da2f8e25b61b4_EOF'
-
- {{#runtime-import .github/workflows/handle-enhancement.md}}
- GH_AW_PROMPT_e59da2f8e25b61b4_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, add_labels, missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/handle-enhancement.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -374,13 +358,14 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -394,22 +379,25 @@ jobs:
GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
- GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER,
- GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST
+ GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER
}
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.artifact-prefix.outputs.prefix }}activation
@@ -439,12 +427,19 @@ jobs:
concurrency:
group: "gh-aw-copilot-handle-enhancement-${{ inputs.issue_number }}"
queue: max
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: handleenhancement
outputs:
@@ -459,7 +454,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -467,11 +465,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -480,20 +479,29 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -523,16 +531,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -542,7 +553,9 @@ jobs:
GH_AW_GITHUB_MIN_INTEGRITY: 'none'
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Parse integrity filter lists
id: parse-guard-vars
@@ -554,8 +567,8 @@ jobs:
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -567,15 +580,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_a36bb21781ffc2fb_EOF'
- {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["enhancement"],"max":1,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_a36bb21781ffc2fb_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"enhancement\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -598,9 +622,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -608,6 +641,16 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -704,9 +747,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -715,38 +760,51 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -782,6 +840,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -793,7 +859,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -801,25 +867,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF
+ GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -831,22 +904,40 @@ jobs:
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
+ # --allow-tool github
+ # --allow-tool safeoutputs
+ # --allow-tool write
timeout-minutes: 5
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -864,14 +955,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -879,7 +975,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 5
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -900,7 +996,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 5
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -916,7 +1023,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -925,9 +1032,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -950,14 +1059,16 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -967,9 +1078,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -977,9 +1090,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -993,9 +1108,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -1003,16 +1120,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1031,6 +1173,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1050,10 +1194,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
- contents: read
+ actions: read
issues: write
pull-requests: write
concurrency:
@@ -1070,7 +1214,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1079,8 +1223,8 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
- name: Download agent output artifact
@@ -1088,7 +1232,8 @@ jobs:
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.activation.outputs.artifact_prefix }}agent,${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1096,42 +1241,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items
+ pattern: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1145,6 +1277,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1167,9 +1301,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1199,7 +1335,7 @@ jobs:
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/handle-enhancement.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1207,9 +1343,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1224,9 +1362,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1239,9 +1379,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1254,9 +1396,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1269,7 +1413,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "handle-enhancement"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1283,6 +1427,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
@@ -1298,9 +1446,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Enhancement Handler"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/handle-enhancement.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1312,6 +1481,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1322,7 +1492,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1331,16 +1501,23 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.agent.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.agent.outputs.artifact_prefix }}agent,${{ needs.agent.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1348,10 +1525,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1360,7 +1539,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1384,21 +1563,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1406,82 +1571,52 @@ jobs:
WORKFLOW_NAME: "Enhancement Handler"
WORKFLOW_DESCRIPTION: "Handles issues classified as enhancements by the triage classifier"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1497,58 +1632,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Enhancement Handler"
+ WORKFLOW_DESCRIPTION: "Handles issues classified as enhancements by the triage classifier"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ needs.agent.outputs.artifact_prefix }}detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1558,7 +1740,6 @@ jobs:
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
runs-on: ubuntu-slim
permissions:
- contents: read
issues: write
pull-requests: write
timeout-minutes: 45
@@ -1572,7 +1753,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "handle-enhancement"
@@ -1585,12 +1765,20 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1599,16 +1787,19 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.activation.outputs.artifact_prefix }}agent,${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1616,7 +1807,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
@@ -1632,16 +1825,18 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"enhancement\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"enhancement\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1651,4 +1846,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/handle-enhancement.md b/.github/workflows/handle-enhancement.md
index 9043c181c1..b7c06c1ba9 100644
--- a/.github/workflows/handle-enhancement.md
+++ b/.github/workflows/handle-enhancement.md
@@ -18,6 +18,8 @@ permissions:
pull-requests: read
copilot-requests: write
tools:
+ bash: []
+ cli-proxy: false
github:
toolsets: [default]
min-integrity: none
@@ -34,4 +36,4 @@ timeout-minutes: 5
# Enhancement Handler
-Add the `enhancement` label to issue #${{ inputs.issue_number }}.
+Add the `enhancement` label to issue #${{ inputs.issue_number }}.
\ No newline at end of file
diff --git a/.github/workflows/handle-question.lock.yml b/.github/workflows/handle-question.lock.yml
index 0093edce0e..2c319aa805 100644
--- a/.github/workflows/handle-question.lock.yml
+++ b/.github/workflows/handle-question.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"92158ba4f7e373cb65457b060c7645569b32c756c13c025837491f6809cf694f","body_hash":"1bdd19aae2095beb6e3fcf7af755cd102d424de3a8727ef6e4674815950c7e8b","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"d091d519856c3b08a2355cc9d035c3a6e249aa088c107cdfb5b00e8e9aea67bc","body_hash":"1bdd19aae2095beb6e3fcf7af755cd102d424de3a8727ef6e4674815950c7e8b","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,7 +26,7 @@
# Handles issues classified as questions by the triage classifier
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -34,22 +34,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Question Handler"
on:
@@ -74,7 +72,7 @@ on:
description: URL of the first added comment
value: ${{ jobs.safe_outputs.outputs.comment_url }}
secrets:
- COPILOT_GITHUB_TOKEN:
+ GH_AW_DEFAULT_OTLP_HEADERS:
required: false
GH_AW_GITHUB_MCP_SERVER_TOKEN:
required: false
@@ -85,9 +83,18 @@ permissions: {}
concurrency:
group: "gh-aw-handle-question-${{ github.run_id }}"
+ queue: max
run-name: "Question Handler"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.handle-question
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Question%20Handler,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -102,6 +109,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -119,7 +127,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -127,10 +135,12 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Question Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Resolve host repo for activation checkout
id: resolve-host-repo
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -141,9 +151,11 @@ jobs:
JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs');
+ const { main } = require(path.join(actionsDir, 'resolve_host_repo.cjs'));
await main();
- name: Compute artifact prefix
id: artifact-prefix
@@ -155,27 +167,30 @@ jobs:
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Question Handler"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -197,9 +212,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -217,15 +234,16 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Print cross-repo setup guidance
@@ -236,7 +254,7 @@ jobs:
echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup"
- name: Checkout .github and .agents folders
if: steps.resolve-host-repo.outputs.target_repo == github.repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
repository: ${{ steps.resolve-host-repo.outputs.target_repo }}
@@ -244,20 +262,18 @@ jobs:
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -266,27 +282,34 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -296,75 +319,36 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_a6cfd5b92b97c528_EOF'
-
- GH_AW_PROMPT_a6cfd5b92b97c528_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_a6cfd5b92b97c528_EOF'
-
- Tools: add_comment, add_labels, missing_tool, missing_data, noop
-
- GH_AW_PROMPT_a6cfd5b92b97c528_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_a6cfd5b92b97c528_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_a6cfd5b92b97c528_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_a6cfd5b92b97c528_EOF'
-
- {{#runtime-import .github/workflows/handle-question.md}}
- GH_AW_PROMPT_a6cfd5b92b97c528_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, add_labels, missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/handle-question.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -374,13 +358,14 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -394,22 +379,25 @@ jobs:
GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
- GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER,
- GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST
+ GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER
}
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.artifact-prefix.outputs.prefix }}activation
@@ -439,12 +427,19 @@ jobs:
concurrency:
group: "gh-aw-copilot-handle-question-${{ inputs.issue_number }}"
queue: max
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: handlequestion
outputs:
@@ -459,7 +454,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -467,11 +465,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -480,20 +479,29 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Question Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -523,16 +531,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -542,7 +553,9 @@ jobs:
GH_AW_GITHUB_MIN_INTEGRITY: 'none'
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Parse integrity filter lists
id: parse-guard-vars
@@ -554,8 +567,8 @@ jobs:
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -567,15 +580,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_564a988b74c338ef_EOF'
- {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["question"],"max":1,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_564a988b74c338ef_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"question\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -598,9 +622,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -608,6 +641,16 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -704,9 +747,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -715,38 +760,51 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -782,6 +840,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -793,7 +859,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -801,25 +867,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF
+ GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -831,22 +904,40 @@ jobs:
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
+ # --allow-tool github
+ # --allow-tool safeoutputs
+ # --allow-tool write
timeout-minutes: 5
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -864,14 +955,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -879,7 +975,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 5
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -900,7 +996,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 5
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -916,7 +1023,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -925,9 +1032,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -950,14 +1059,16 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -967,9 +1078,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -977,9 +1090,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -993,9 +1108,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -1003,16 +1120,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1031,6 +1173,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1050,10 +1194,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
- contents: read
+ actions: read
issues: write
pull-requests: write
concurrency:
@@ -1070,7 +1214,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1079,8 +1223,8 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Question Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
- name: Download agent output artifact
@@ -1088,7 +1232,8 @@ jobs:
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.activation.outputs.artifact_prefix }}agent,${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1096,42 +1241,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items
+ pattern: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1145,6 +1277,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1167,9 +1301,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1199,7 +1335,7 @@ jobs:
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/handle-question.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1207,9 +1343,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1224,9 +1362,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1239,9 +1379,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1254,9 +1396,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1269,7 +1413,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "handle-question"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1283,6 +1427,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
@@ -1298,9 +1446,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Question Handler"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/handle-question.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1312,6 +1481,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1322,7 +1492,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1331,16 +1501,23 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Question Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.agent.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.agent.outputs.artifact_prefix }}agent,${{ needs.agent.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1348,10 +1525,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1360,7 +1539,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1384,21 +1563,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1406,82 +1571,52 @@ jobs:
WORKFLOW_NAME: "Question Handler"
WORKFLOW_DESCRIPTION: "Handles issues classified as questions by the triage classifier"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1497,58 +1632,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Question Handler"
+ WORKFLOW_DESCRIPTION: "Handles issues classified as questions by the triage classifier"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ needs.agent.outputs.artifact_prefix }}detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1558,7 +1740,6 @@ jobs:
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
runs-on: ubuntu-slim
permissions:
- contents: read
issues: write
pull-requests: write
timeout-minutes: 45
@@ -1572,7 +1753,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "handle-question"
@@ -1585,12 +1765,20 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1599,16 +1787,19 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Question Handler"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }}
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ pattern: "{${{ needs.activation.outputs.artifact_prefix }}agent,${{ needs.activation.outputs.artifact_prefix }}agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1616,7 +1807,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
@@ -1632,16 +1825,18 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"question\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"question\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1651,4 +1846,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/handle-question.md b/.github/workflows/handle-question.md
index 21a10d468a..eb40378636 100644
--- a/.github/workflows/handle-question.md
+++ b/.github/workflows/handle-question.md
@@ -18,6 +18,8 @@ permissions:
pull-requests: read
copilot-requests: write
tools:
+ bash: []
+ cli-proxy: false
github:
toolsets: [default]
min-integrity: none
@@ -34,4 +36,4 @@ timeout-minutes: 5
# Question Handler
-Add the `question` label to issue #${{ inputs.issue_number }}.
+Add the `question` label to issue #${{ inputs.issue_number }}.
\ No newline at end of file
diff --git a/.github/workflows/issue-classification.lock.yml b/.github/workflows/issue-classification.lock.yml
index 041fb94b73..c0020170f3 100644
--- a/.github/workflows/issue-classification.lock.yml
+++ b/.github/workflows/issue-classification.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"797f7487a67c2fa4465cb3fd31e17c9f0620bb232b2a4fb693c62cb76d5d5a36","body_hash":"8e7ac9b7bb6ab07630a10a4a016108ba59f70feadf82a7391ca0ba5504e14bff","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c68d65ddf05c945729544eaf3436f32818047cc55a9f8699db993f5b3db744bc","body_hash":"8e7ac9b7bb6ab07630a10a4a016108ba59f70feadf82a7391ca0ba5504e14bff","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","call_workflow","handle_bug","handle_documentation","handle_enhancement","handle_question","missing_data","missing_tool","noop"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,7 +26,7 @@
# Classifies newly opened issues and delegates to type-specific handler workflows
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -34,22 +34,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Issue Classification Agent"
on:
@@ -73,9 +71,18 @@ permissions: {}
concurrency:
group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}"
+ queue: max
run-name: "Issue Classification Agent"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.issue-classification
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Issue%20Classification%20Agent,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -90,6 +97,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -105,7 +113,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -113,34 +121,39 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Generate agentic run info
id: generate_aw_info
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Issue Classification Agent"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -162,9 +175,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -182,38 +197,37 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -222,38 +236,47 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Compute current body text
id: sanitized
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,s.symcb.com,s.symcd.com,security.ubuntu.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');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs');
+ const { main } = require(path.join(actionsDir, 'compute_text.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
@@ -265,63 +288,22 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_2b9644ded951c90e_EOF'
-
- GH_AW_PROMPT_2b9644ded951c90e_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_2b9644ded951c90e_EOF'
-
- Tools: add_comment, call_workflow, missing_tool, missing_data, noop
-
- GH_AW_PROMPT_2b9644ded951c90e_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_2b9644ded951c90e_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_2b9644ded951c90e_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_2b9644ded951c90e_EOF'
-
- {{#runtime-import .github/workflows/issue-classification.md}}
- GH_AW_PROMPT_2b9644ded951c90e_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, handle_bug, handle_enhancement, handle_question, handle_documentation, missing_tool, missing_data, noop\nShared budgets: call-workflow [handle_bug, handle_enhancement, handle_question, handle_documentation](max:1 total)\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/issue-classification.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
GH_AW_GITHUB_EVENT_ISSUE_TITLE: ${{ github.event.issue.title }}
@@ -329,14 +311,16 @@ jobs:
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
@@ -348,13 +332,14 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -370,22 +355,25 @@ jobs:
GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
- GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER,
- GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST
+ GH_AW_INPUTS_ISSUE_NUMBER: process.env.GH_AW_INPUTS_ISSUE_NUMBER
}
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: activation
@@ -412,12 +400,19 @@ jobs:
copilot-requests: write
issues: read
pull-requests: read
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: issueclassification
outputs:
@@ -431,7 +426,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -439,11 +437,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -452,19 +451,28 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -494,16 +502,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -513,7 +524,9 @@ jobs:
GH_AW_GITHUB_MIN_INTEGRITY: 'none'
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Parse integrity filter lists
id: parse-guard-vars
@@ -525,8 +538,8 @@ jobs:
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -538,15 +551,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_3e3303377640f8c6_EOF'
- {"add_comment":{"max":1,"target":"triggering"},"call_workflow":{"max":1,"workflow_files":{"handle-bug":"./.github/workflows/handle-bug.lock.yml","handle-documentation":"./.github/workflows/handle-documentation.lock.yml","handle-enhancement":"./.github/workflows/handle-enhancement.lock.yml","handle-question":"./.github/workflows/handle-question.lock.yml"},"workflows":["handle-bug","handle-enhancement","handle-question","handle-documentation"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_3e3303377640f8c6_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"triggering\"},\"call_workflow\":{\"max\":1,\"workflow_files\":{\"handle-bug\":\"./.github/workflows/handle-bug.lock.yml\",\"handle-documentation\":\"./.github/workflows/handle-documentation.lock.yml\",\"handle-enhancement\":\"./.github/workflows/handle-enhancement.lock.yml\",\"handle-question\":\"./.github/workflows/handle-question.lock.yml\"},\"workflows\":[\"handle-bug\",\"handle-enhancement\",\"handle-question\",\"handle-documentation\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -677,9 +701,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -687,6 +720,33 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
+ }
+ }
+ },
+ "call_workflow": {
+ "defaultMax": 1,
+ "fields": {
+ "inputs": {
+ "type": "object"
+ },
+ "workflow_name": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256,
+ "minLength": 1,
+ "pattern": ".*\\S.*",
+ "patternError": "must not be empty"
}
}
},
@@ -767,9 +827,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -778,38 +840,51 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -845,6 +920,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -856,7 +939,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -864,25 +947,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF
+ GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -894,22 +984,40 @@ jobs:
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
+ # --allow-tool github
+ # --allow-tool safeoutputs
+ # --allow-tool write
timeout-minutes: 10
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -927,14 +1035,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -942,7 +1055,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 10
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -963,7 +1076,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 10
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -979,7 +1103,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -988,9 +1112,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -1013,14 +1139,16 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -1030,9 +1158,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -1040,9 +1170,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -1056,9 +1188,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -1066,16 +1200,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1094,6 +1253,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1121,7 +1282,7 @@ jobs:
issue_number: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).issue_number }}
payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }}
secrets:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_DEFAULT_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
@@ -1142,7 +1303,7 @@ jobs:
issue_number: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).issue_number }}
payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }}
secrets:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_DEFAULT_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
@@ -1163,7 +1324,7 @@ jobs:
issue_number: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).issue_number }}
payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }}
secrets:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_DEFAULT_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
@@ -1184,7 +1345,7 @@ jobs:
issue_number: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).issue_number }}
payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }}
secrets:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_DEFAULT_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
@@ -1201,10 +1362,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
- contents: read
+ actions: read
issues: write
pull-requests: write
concurrency:
@@ -1221,7 +1382,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1230,15 +1391,16 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1246,42 +1408,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: safe-outputs-items
+ pattern: safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1295,6 +1444,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1317,9 +1468,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1349,7 +1502,7 @@ jobs:
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-classification.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1357,9 +1510,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1374,9 +1529,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1389,9 +1546,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1404,9 +1563,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1419,7 +1580,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "issue-classification"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1433,6 +1594,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
@@ -1448,9 +1613,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Issue Classification Agent"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-classification.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1462,6 +1648,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1472,7 +1659,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1481,15 +1668,22 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1497,10 +1691,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1509,7 +1705,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1533,21 +1729,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1555,82 +1737,52 @@ jobs:
WORKFLOW_NAME: "Issue Classification Agent"
WORKFLOW_DESCRIPTION: "Classifies newly opened issues and delegates to type-specific handler workflows"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1646,58 +1798,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Issue Classification Agent"
+ WORKFLOW_DESCRIPTION: "Classifies newly opened issues and delegates to type-specific handler workflows"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1707,7 +1906,6 @@ jobs:
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
runs-on: ubuntu-slim
permissions:
- contents: read
issues: write
pull-requests: write
timeout-minutes: 45
@@ -1721,7 +1919,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "issue-classification"
@@ -1736,12 +1933,20 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1750,15 +1955,18 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1766,7 +1974,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
@@ -1782,16 +1992,18 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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: "{\"add_comment\":{\"max\":1,\"target\":\"triggering\"},\"call_workflow\":{\"max\":1,\"workflow_files\":{\"handle-bug\":\"./.github/workflows/handle-bug.lock.yml\",\"handle-documentation\":\"./.github/workflows/handle-documentation.lock.yml\",\"handle-enhancement\":\"./.github/workflows/handle-enhancement.lock.yml\",\"handle-question\":\"./.github/workflows/handle-question.lock.yml\"},\"workflows\":[\"handle-bug\",\"handle-enhancement\",\"handle-question\",\"handle-documentation\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"triggering\"},\"call_workflow\":{\"max\":1,\"workflow_files\":{\"handle-bug\":\"./.github/workflows/handle-bug.lock.yml\",\"handle-documentation\":\"./.github/workflows/handle-documentation.lock.yml\",\"handle-enhancement\":\"./.github/workflows/handle-enhancement.lock.yml\",\"handle-question\":\"./.github/workflows/handle-question.lock.yml\"},\"workflows\":[\"handle-bug\",\"handle-enhancement\",\"handle-question\",\"handle-documentation\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1801,4 +2013,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/issue-classification.md b/.github/workflows/issue-classification.md
index b1e3345f91..8b111cd1db 100644
--- a/.github/workflows/issue-classification.md
+++ b/.github/workflows/issue-classification.md
@@ -16,6 +16,8 @@ permissions:
pull-requests: read
copilot-requests: write
tools:
+ bash: []
+ cli-proxy: false
github:
toolsets: [default]
min-integrity: none
@@ -123,4 +125,4 @@ Common areas of issues:
- Issue number: ${{ github.event.issue.number || inputs.issue_number }}
- Issue title: ${{ github.event.issue.title }}
-Use the GitHub tools to fetch the full issue details, especially when triggered manually via `workflow_dispatch`.
+Use the GitHub tools to fetch the full issue details, especially when triggered manually via `workflow_dispatch`.
\ No newline at end of file
diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml
index e24584f26d..e6b1410607 100644
--- a/.github/workflows/issue-triage.lock.yml
+++ b/.github/workflows/issue-triage.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b343e48e59d56a2461bafa8c08f4f37d4242a56fa662b83c2f0cad16262682e5","body_hash":"30994be7c5c23b102c12a56a325ac313e413a2507dff11d0dc695899379bfbd0","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b343e48e59d56a2461bafa8c08f4f37d4242a56fa662b83c2f0cad16262682e5","body_hash":"30994be7c5c23b102c12a56a325ac313e413a2507dff11d0dc695899379bfbd0","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","close_issue","missing_data","missing_tool","noop","update_issue"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,7 +26,7 @@
# Triages newly opened issues by labeling, acknowledging, requesting clarification, and closing duplicates
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -34,22 +34,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Issue Triage Agent"
on:
@@ -73,9 +71,18 @@ permissions: {}
concurrency:
group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}"
+ queue: max
run-name: "Issue Triage Agent"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.issue-triage
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Issue%20Triage%20Agent,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -90,6 +97,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -105,7 +113,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -113,34 +121,39 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Generate agentic run info
id: generate_aw_info
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Issue Triage Agent"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -162,9 +175,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -182,38 +197,37 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -222,38 +236,47 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Compute current body text
id: sanitized
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,s.symcb.com,s.symcd.com,security.ubuntu.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');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs');
+ const { main } = require(path.join(actionsDir, 'compute_text.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
@@ -265,63 +288,22 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_39930e94844c6d8f_EOF'
-
- GH_AW_PROMPT_39930e94844c6d8f_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_39930e94844c6d8f_EOF'
-
- Tools: add_comment(max:2), close_issue, update_issue, add_labels(max:10), missing_tool, missing_data, noop
-
- GH_AW_PROMPT_39930e94844c6d8f_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_39930e94844c6d8f_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_39930e94844c6d8f_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_39930e94844c6d8f_EOF'
-
- {{#runtime-import .github/workflows/issue-triage.md}}
- GH_AW_PROMPT_39930e94844c6d8f_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment(max:2), close_issue, update_issue, add_labels(max:10), missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/issue-triage.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
GH_AW_GITHUB_EVENT_ISSUE_TITLE: ${{ github.event.issue.title }}
@@ -329,14 +311,16 @@ jobs:
GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_54492A5B: ${{ github.event.issue.number || inputs.issue_number }}
@@ -351,10 +335,12 @@ jobs:
GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -376,16 +362,20 @@ jobs:
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: activation
@@ -412,12 +402,19 @@ jobs:
copilot-requests: write
issues: read
pull-requests: read
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: issuetriage
outputs:
@@ -431,7 +428,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -439,11 +439,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -452,19 +453,28 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -494,16 +504,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -512,13 +525,15 @@ jobs:
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -530,15 +545,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_ee19492f88e4cc0b_EOF'
- {"add_comment":{"max":2},"add_labels":{"allowed":["bug","enhancement","question","documentation","sdk/dotnet","sdk/go","sdk/java","sdk/nodejs","sdk/python","priority/high","priority/low","testing","security","needs-info","duplicate"],"issue_intent":true,"max":10,"target":"triggering"},"close_issue":{"issue_intent":true,"max":1,"target":"triggering"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"triggering"}}
- GH_AW_SAFE_OUTPUTS_CONFIG_ee19492f88e4cc0b_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":2},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\",\"sdk/dotnet\",\"sdk/go\",\"sdk/java\",\"sdk/nodejs\",\"sdk/python\",\"priority/high\",\"priority/low\",\"testing\",\"security\",\"needs-info\",\"duplicate\"],\"issue_intent\":true,\"max\":10,\"target\":\"triggering\"},\"close_issue\":{\"issue_intent\":true,\"max\":1,\"target\":\"triggering\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"triggering\"}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -582,9 +608,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -592,6 +627,16 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -628,6 +673,9 @@ jobs:
],
"x-strip-on-error": true
},
+ "duplicate_of": {
+ "issueOrPRNumber": true
+ },
"issue_number": {
"optionalPositiveInteger": true
},
@@ -774,9 +822,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -785,6 +835,7 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -792,33 +843,45 @@ jobs:
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_46604863f3d8e286_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -851,6 +914,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -862,7 +933,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -870,25 +941,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF
+ GH_AW_MCP_CONFIG_46604863f3d8e286_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -904,18 +982,33 @@ jobs:
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -933,14 +1026,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -948,7 +1046,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 10
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -969,7 +1067,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 10
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -985,7 +1094,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -994,9 +1103,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -1019,14 +1130,16 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -1036,9 +1149,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -1046,9 +1161,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -1062,9 +1179,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -1072,16 +1191,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1098,6 +1242,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1117,10 +1263,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
- contents: read
+ actions: read
issues: write
pull-requests: write
concurrency:
@@ -1137,7 +1283,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1146,15 +1292,16 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1162,42 +1309,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: safe-outputs-items
+ pattern: safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1211,6 +1345,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1233,9 +1369,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1265,7 +1403,7 @@ jobs:
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1273,9 +1411,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1290,9 +1430,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1305,9 +1447,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1320,9 +1464,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1335,7 +1481,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "issue-triage"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1349,6 +1495,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
@@ -1364,9 +1514,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Issue Triage Agent"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1378,6 +1549,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1388,7 +1560,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1397,15 +1569,22 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1413,10 +1592,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1425,7 +1606,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1449,21 +1630,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1471,82 +1638,52 @@ jobs:
WORKFLOW_NAME: "Issue Triage Agent"
WORKFLOW_DESCRIPTION: "Triages newly opened issues by labeling, acknowledging, requesting clarification, and closing duplicates"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1562,58 +1699,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Issue Triage Agent"
+ WORKFLOW_DESCRIPTION: "Triages newly opened issues by labeling, acknowledging, requesting clarification, and closing duplicates"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1623,7 +1807,6 @@ jobs:
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
runs-on: ubuntu-slim
permissions:
- contents: read
issues: write
pull-requests: write
timeout-minutes: 45
@@ -1637,7 +1820,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "issue-triage"
@@ -1650,12 +1832,20 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1664,15 +1854,18 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1680,7 +1873,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
@@ -1696,16 +1891,18 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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: "{\"add_comment\":{\"max\":2},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\",\"sdk/dotnet\",\"sdk/go\",\"sdk/java\",\"sdk/nodejs\",\"sdk/python\",\"priority/high\",\"priority/low\",\"testing\",\"security\",\"needs-info\",\"duplicate\"],\"issue_intent\":true,\"max\":10,\"target\":\"triggering\"},\"close_issue\":{\"issue_intent\":true,\"max\":1,\"target\":\"triggering\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"triggering\"}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":2},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\",\"sdk/dotnet\",\"sdk/go\",\"sdk/java\",\"sdk/nodejs\",\"sdk/python\",\"priority/high\",\"priority/low\",\"testing\",\"security\",\"needs-info\",\"duplicate\"],\"issue_intent\":true,\"max\":10,\"target\":\"triggering\"},\"close_issue\":{\"issue_intent\":true,\"max\":1,\"target\":\"triggering\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"triggering\"}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1715,4 +1912,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml b/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml
index e94e0775c0..7554b0bb76 100644
--- a/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml
+++ b/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a5f19a89f89b0693f86ca89ea90e3a633fe19c17bf4d27214fa9124429cdc156","body_hash":"8db09798070cbcba22c42c50a316ae45c8e8c650eeb23c556b44fde8d519550a","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"03beaef805d9bdf4b878b9fd1dd47c62793f30f179373406e081b88a9817b182","body_hash":"f535cb24328c6e9b3963e72de09404b08a4f4580a8174bc6ad580c0f005837ae","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_release_by_tag","get_tag","list_branches","list_commits","list_releases","list_starred_repositories","list_tags","search_code","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","missing_data","missing_tool","noop","push_to_pull_request_branch"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -24,12 +24,12 @@
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
# Adapt handwritten Java SDK code to work with regenerated types after a
-# @github/copilot version bump. Assumes codegen succeeded and generated code
+# Copilot CLI release update. Assumes codegen succeeded and generated code
# compiles. Fixes handwritten source and tests only.
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
# - GH_AW_CI_TRIGGER_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -37,22 +37,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Java Handwritten Code Adaptation After CLI Upgrade"
on:
@@ -76,9 +74,18 @@ permissions: {}
concurrency:
group: "gh-aw-${{ github.workflow }}-${{ github.ref || github.run_id }}"
+ queue: max
run-name: "Java Handwritten Code Adaptation After CLI Upgrade"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.java-adapt-handwritten-code-to-accept-upgrade-changes
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Java%20Handwritten%20Code%20Adaptation%20After%20CLI%20Upgrade,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -92,6 +99,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -105,7 +113,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -113,34 +121,39 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Generate agentic run info
id: generate_aw_info
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -162,9 +175,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -182,38 +197,37 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -222,27 +236,34 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"file\":\"safe_outputs_push_to_pr_branch.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -253,79 +274,37 @@ jobs:
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_BRANCH: ${{ inputs.branch }}
GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_67432b380d9d8ebb_EOF'
-
- GH_AW_PROMPT_67432b380d9d8ebb_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_67432b380d9d8ebb_EOF'
-
- Tools: add_comment(max:10), push_to_pull_request_branch, missing_tool, missing_data, noop
- GH_AW_PROMPT_67432b380d9d8ebb_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md"
- cat << 'GH_AW_PROMPT_67432b380d9d8ebb_EOF'
-
- GH_AW_PROMPT_67432b380d9d8ebb_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_67432b380d9d8ebb_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_67432b380d9d8ebb_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_67432b380d9d8ebb_EOF'
-
- {{#runtime-import .github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md}}
- GH_AW_PROMPT_67432b380d9d8ebb_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment(max:10), push_to_pull_request_branch, missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_INPUTS_BRANCH: ${{ inputs.branch }}
GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -339,10 +318,12 @@ jobs:
GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -363,16 +344,20 @@ jobs:
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: activation
@@ -398,12 +383,19 @@ jobs:
actions: read
contents: read
copilot-requests: write
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: javaadapthandwrittencodetoacceptupgradechanges
outputs:
@@ -417,7 +409,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -425,11 +420,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -438,19 +434,28 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -480,16 +485,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -498,13 +506,15 @@ jobs:
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -516,15 +526,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_fcd407b1cd819e9a_EOF'
- {"add_comment":{"max":10,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"required_labels":["dependencies","sdk/java"],"target":"*"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_fcd407b1cd819e9a_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"required_labels\":[\"dependencies\",\"sdk/java\"],\"target\":\"*\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -546,9 +567,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -556,6 +586,16 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -632,6 +672,10 @@ jobs:
},
"pull_request_number": {
"issueOrPRNumber": true
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
}
}
},
@@ -655,9 +699,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -666,6 +712,7 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -673,33 +720,45 @@ jobs:
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_58d53a00b5a25078_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_33dc71f92a389606_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -732,6 +791,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -743,7 +810,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -751,25 +818,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_58d53a00b5a25078_EOF
+ GH_AW_MCP_CONFIG_33dc71f92a389606_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -785,18 +859,33 @@ jobs:
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"patch-diff.githubusercontent.com\",\"patchdiff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"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\",\"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\",\"patch-diff.githubusercontent.com\",\"patchdiff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -814,14 +903,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -829,7 +923,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 60
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -850,7 +944,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 60
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -866,7 +971,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -875,9 +980,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -900,14 +1007,16 @@ 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,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,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,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,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -917,9 +1026,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -927,9 +1038,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -943,9 +1056,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -953,16 +1068,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -979,6 +1119,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -998,9 +1140,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
+ actions: read
contents: write
issues: write
pull-requests: write
@@ -1018,7 +1161,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1027,15 +1170,16 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1043,42 +1187,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: safe-outputs-items
+ pattern: safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1092,6 +1223,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1114,9 +1247,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1154,9 +1289,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1171,9 +1308,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1186,9 +1325,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1201,9 +1342,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1216,7 +1359,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "java-adapt-handwritten-code-to-accept-upgrade-changes"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1230,6 +1373,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }}
GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }}
@@ -1247,9 +1394,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1261,6 +1429,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1271,7 +1440,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1280,15 +1449,22 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1296,10 +1472,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1308,7 +1486,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1332,104 +1510,60 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
- WORKFLOW_DESCRIPTION: "Adapt handwritten Java SDK code to work with regenerated types after a\n@github/copilot version bump. Assumes codegen succeeded and generated code\ncompiles. Fixes handwritten source and tests only."
+ WORKFLOW_DESCRIPTION: "Adapt handwritten Java SDK code to work with regenerated types after a\nCopilot CLI release update. Assumes codegen succeeded and generated code\ncompiles. Fixes handwritten source and tests only."
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1445,58 +1579,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
+ WORKFLOW_DESCRIPTION: "Adapt handwritten Java SDK code to work with regenerated types after a\nCopilot CLI release update. Assumes codegen succeeded and generated code\ncompiles. Fixes handwritten source and tests only."
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1520,7 +1701,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "java-adapt-handwritten-code-to-accept-upgrade-changes"
@@ -1533,14 +1713,22 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
push_commit_sha: ${{ steps.process_safe_outputs.outputs.push_commit_sha }}
push_commit_url: ${{ steps.process_safe_outputs.outputs.push_commit_url }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1549,15 +1737,18 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1565,7 +1756,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Download patch artifact
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -1574,7 +1767,7 @@ jobs:
path: /tmp/gh-aw/
- name: Checkout repository
if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch')
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: true
token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -1600,17 +1793,19 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,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,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,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,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: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"required_labels\":[\"dependencies\",\"sdk/java\"],\"target\":\"*\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"required_labels\":[\"dependencies\",\"sdk/java\"],\"target\":\"*\"},\"report_incomplete\":{}}"
GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1620,4 +1815,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md b/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md
index dd1bfe2bbc..91f11d1f86 100644
--- a/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md
+++ b/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md
@@ -1,7 +1,7 @@
---
description: |
Adapt handwritten Java SDK code to work with regenerated types after a
- @github/copilot version bump. Assumes codegen succeeded and generated code
+ Copilot CLI release update. Assumes codegen succeeded and generated code
compiles. Fixes handwritten source and tests only.
on:
@@ -45,14 +45,13 @@ safe-outputs:
# Java Handwritten Code Adaptation After CLI Upgrade
-You are an automation agent that fixes handwritten Java SDK source and test code after a `@github/copilot` version bump has regenerated the typed schemas.
+You are an automation agent that fixes handwritten Java SDK source and test code after a Copilot CLI release update has regenerated the typed schemas.
## Assumptions
- The branch `${{ inputs.branch }}` already has:
- - Updated `java/scripts/codegen/package.json` with the new version
+ - Updated the shared CLI release pin in `nodejs/package.json`
- Regenerated `java/sdk/src/generated/java/` code that compiles successfully
- - Updated the Java POM CLI/version pin property
- Your job is ONLY to fix **handwritten** code, NOT generated code.
## Boundaries
@@ -147,7 +146,7 @@ If this passes, commit and push:
```bash
git add java/sdk/src/main/java java/sdk/src/test/java
-git commit -m "Fix handwritten Java code for @github/copilot schema changes
+git commit -m "Fix handwritten Java code for CLI schema changes
Adapt constructor calls, enum references, and test assertions to match
regenerated types after CLI version bump."
diff --git a/.github/workflows/java-codegen-check.yml b/.github/workflows/java-codegen-check.yml
index f2f4527966..e490a5cf4e 100644
--- a/.github/workflows/java-codegen-check.yml
+++ b/.github/workflows/java-codegen-check.yml
@@ -5,11 +5,13 @@ on:
branches:
- main
paths:
+ - 'nodejs/package.json'
- 'java/scripts/codegen/**'
- 'java/sdk/src/generated/**'
- '.github/workflows/java-codegen-check.yml'
pull_request:
paths:
+ - 'nodejs/package.json'
- 'java/scripts/codegen/**'
- 'java/sdk/src/generated/**'
- '.github/workflows/java-codegen-check.yml'
@@ -48,9 +50,13 @@ jobs:
working-directory: ./java/scripts/codegen
run: npm ci
+ - name: Test schema fetcher
+ working-directory: ./java/scripts/codegen
+ run: npm test
+
- name: Run codegen
working-directory: ./java/scripts/codegen
- run: npx tsx java.ts
+ run: npm run generate
- name: Check for uncommitted changes
id: check-changes
@@ -68,7 +74,7 @@ jobs:
- name: Fail on stale generated files (push to main)
if: steps.check-changes.outputs.changed == 'true' && github.event_name != 'pull_request'
run: |
- echo "::error::Generated files are out of date. Run 'cd java/scripts/codegen && npx tsx java.ts' and commit the changes."
+ echo "::error::Generated files are out of date. Run 'cd java/scripts/codegen && npm run generate' and commit the changes."
git diff
exit 1
@@ -93,7 +99,7 @@ jobs:
if: steps.push-regen.outcome == 'failure'
run: |
echo "::error::Could not push regenerated files to the PR branch. This is expected for Dependabot PRs (read-only token) and fork PRs."
- echo "To fix: check out this PR branch locally, run 'cd java/scripts/codegen && npx tsx java.ts', commit, and push."
+ echo "To fix: check out this PR branch locally, run 'cd java/scripts/codegen && npm run generate', commit, and push."
exit 1
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
diff --git a/.github/workflows/java-codegen-fix.lock.yml b/.github/workflows/java-codegen-fix.lock.yml
index 1d8d28c439..fa64a0fe88 100644
--- a/.github/workflows/java-codegen-fix.lock.yml
+++ b/.github/workflows/java-codegen-fix.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b0390c9ab9beb0d7e106314299e89e486269ab7f64d8489d5021132d79aa6b9b","body_hash":"63d6ce13a5131b158ddffb10a469aa59e0fdc2278eec4d8de7f6763e0b6f2ea2","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b0390c9ab9beb0d7e106314299e89e486269ab7f64d8489d5021132d79aa6b9b","body_hash":"c7cc7984b1d512e871371a7fd99bc4f7e4615d4192348170eca763cf584f4855","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_release_by_tag","get_tag","list_branches","list_commits","list_releases","list_starred_repositories","list_tags","search_code","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","missing_data","missing_tool","noop","push_to_pull_request_branch"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -27,8 +27,8 @@
# mvn verify fails after code generation changes.
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
# - GH_AW_CI_TRIGGER_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -36,22 +36,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Java Codegen Agentic Fix"
on:
@@ -79,9 +77,18 @@ permissions: {}
concurrency:
group: "gh-aw-${{ github.workflow }}-${{ github.ref || github.run_id }}"
+ queue: max
run-name: "Java Codegen Agentic Fix"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.java-codegen-fix
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Java%20Codegen%20Agentic%20Fix,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -95,6 +102,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -108,7 +116,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -116,34 +124,39 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Generate agentic run info
id: generate_aw_info
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Java Codegen Agentic Fix"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -165,9 +178,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -185,38 +200,37 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -225,27 +239,34 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"file\":\"safe_outputs_push_to_pr_branch.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -257,80 +278,38 @@ jobs:
GH_AW_INPUTS_BRANCH: ${{ inputs.branch }}
GH_AW_INPUTS_ERROR_SUMMARY: ${{ inputs.error_summary }}
GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_7834a0b5f08e9149_EOF'
-
- GH_AW_PROMPT_7834a0b5f08e9149_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_7834a0b5f08e9149_EOF'
-
- Tools: add_comment(max:5), push_to_pull_request_branch, missing_tool, missing_data, noop
- GH_AW_PROMPT_7834a0b5f08e9149_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md"
- cat << 'GH_AW_PROMPT_7834a0b5f08e9149_EOF'
-
- GH_AW_PROMPT_7834a0b5f08e9149_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_7834a0b5f08e9149_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_7834a0b5f08e9149_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_7834a0b5f08e9149_EOF'
-
- {{#runtime-import .github/workflows/java-codegen-fix.md}}
- GH_AW_PROMPT_7834a0b5f08e9149_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment(max:5), push_to_pull_request_branch, missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/java-codegen-fix.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_INPUTS_BRANCH: ${{ inputs.branch }}
GH_AW_INPUTS_ERROR_SUMMARY: ${{ inputs.error_summary }}
GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -345,10 +324,12 @@ jobs:
GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -370,16 +351,20 @@ jobs:
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: activation
@@ -405,12 +390,19 @@ jobs:
actions: read
contents: read
copilot-requests: write
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: javacodegenfix
outputs:
@@ -424,7 +416,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -432,11 +427,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -445,19 +441,28 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -487,16 +492,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -505,13 +513,15 @@ jobs:
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -523,15 +533,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_cf285131e299ca5f_EOF'
- {"add_comment":{"max":5,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"required_labels":["dependencies"],"target":"*"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_cf285131e299ca5f_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":5,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"required_labels\":[\"dependencies\"],\"target\":\"*\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -553,9 +574,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -563,6 +593,16 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -639,6 +679,10 @@ jobs:
},
"pull_request_number": {
"issueOrPRNumber": true
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
}
}
},
@@ -662,9 +706,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -673,6 +719,7 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -680,33 +727,45 @@ jobs:
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_58d53a00b5a25078_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_33dc71f92a389606_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -739,6 +798,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -750,7 +817,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -758,25 +825,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_58d53a00b5a25078_EOF
+ GH_AW_MCP_CONFIG_33dc71f92a389606_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -792,18 +866,33 @@ jobs:
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"patch-diff.githubusercontent.com\",\"patchdiff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"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\",\"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\",\"patch-diff.githubusercontent.com\",\"patchdiff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -821,14 +910,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -836,7 +930,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 60
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -857,7 +951,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 60
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -873,7 +978,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -882,9 +987,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -907,14 +1014,16 @@ 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,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,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,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,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -924,9 +1033,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -934,9 +1045,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -950,9 +1063,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -960,16 +1075,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -986,6 +1126,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1005,9 +1147,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
+ actions: read
contents: write
issues: write
pull-requests: write
@@ -1025,7 +1168,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1034,15 +1177,16 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1050,42 +1194,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: safe-outputs-items
+ pattern: safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1099,6 +1230,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1121,9 +1254,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1161,9 +1296,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1178,9 +1315,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1193,9 +1332,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1208,9 +1349,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1223,7 +1366,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "java-codegen-fix"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1237,6 +1380,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }}
GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }}
@@ -1254,9 +1401,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Java Codegen Agentic Fix"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/java-codegen-fix.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1268,6 +1436,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1278,7 +1447,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1287,15 +1456,22 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1303,10 +1479,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1315,7 +1493,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1339,21 +1517,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1361,82 +1525,52 @@ jobs:
WORKFLOW_NAME: "Java Codegen Agentic Fix"
WORKFLOW_DESCRIPTION: "Agentic fix for Java codegen-related build/test failures. Invoked when\nmvn verify fails after code generation changes."
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1452,58 +1586,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Java Codegen Agentic Fix"
+ WORKFLOW_DESCRIPTION: "Agentic fix for Java codegen-related build/test failures. Invoked when\nmvn verify fails after code generation changes."
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1527,7 +1708,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "java-codegen-fix"
@@ -1540,14 +1720,22 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
push_commit_sha: ${{ steps.process_safe_outputs.outputs.push_commit_sha }}
push_commit_url: ${{ steps.process_safe_outputs.outputs.push_commit_url }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1556,15 +1744,18 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1572,7 +1763,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Download patch artifact
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -1581,7 +1774,7 @@ jobs:
path: /tmp/gh-aw/
- name: Checkout repository
if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch')
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: true
token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -1607,17 +1800,19 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,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,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,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,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: "{\"add_comment\":{\"max\":5,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"required_labels\":[\"dependencies\"],\"target\":\"*\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":5,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"required_labels\":[\"dependencies\"],\"target\":\"*\"},\"report_incomplete\":{}}"
GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1627,4 +1822,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/java-codegen-fix.md b/.github/workflows/java-codegen-fix.md
index b1dcb1f636..8f6a845433 100644
--- a/.github/workflows/java-codegen-fix.md
+++ b/.github/workflows/java-codegen-fix.md
@@ -52,7 +52,7 @@ You are an automation agent that fixes Java compilation and test failures caused
## Context
-A Dependabot PR bumped the `@github/copilot` npm dependency in `java/scripts/codegen/package.json`. The `java-codegen-check` workflow ran the code generator (`java/scripts/codegen/java.ts`) against the new schemas and `mvn verify` subsequently failed. Your job is to fix **both** the code generator script (if needed) and the handwritten SDK/test source code so the build passes.
+A Copilot CLI release pin update fetched new schemas from GitHub Releases. The `java-codegen-check` workflow ran the code generator (`java/scripts/codegen/java.ts`) against those schemas and `mvn verify` subsequently failed. Your job is to fix **both** the code generator script (if needed) and the handwritten SDK/test source code so the build passes.
**❌❌❌ YOU MUST NEVER EDIT any of the java source code in `java/sdk/src/generated/` directly.** ✅✅Rather, the way to affect changes in these files is to change the code generator script and re-generate the classes in `java/sdk/src/generated`.
@@ -66,9 +66,9 @@ ${{ inputs.error_summary }}
## Architecture overview
-The code generator (`java/scripts/codegen/java.ts`) reads JSON schemas from `node_modules/@github/copilot/schemas/` and produces Java source files under `java/sdk/src/generated/java/`. These generated types are consumed by handwritten code in `java/sdk/src/main/java/` (primarily `CopilotSession.java`) and tested by handwritten tests in `java/sdk/src/test/java/`.
+The code generator (`java/scripts/codegen/java.ts`) reads JSON schemas from `java/scripts/codegen/target/schemas/`. The schemas are extracted from the pinned `github-copilot--linux-x64.tgz` GitHub Release asset by `fetch-schemas.mjs`. The generator produces Java source files under `java/sdk/src/generated/java/`. These generated types are consumed by handwritten code in `java/sdk/src/main/java/` (primarily `CopilotSession.java`) and tested by handwritten tests in `java/sdk/src/test/java/`.
-When `@github/copilot` is bumped, the schemas may change in ways the code generator does not yet handle. Common schema changes include:
+When the Copilot CLI release pin is bumped, the schemas may change in ways the code generator does not yet handle. Common schema changes include:
- **`$ref` references**: Inline nested type definitions replaced with `$ref` pointers to `#/definitions/` entries. The code generator must resolve these references and emit standalone Java types instead of nested records.
- **Field type changes**: Numeric fields changing between `double`, `Long`, `int`, etc.
@@ -97,10 +97,10 @@ mvn --version
node --version
```
-Install codegen dependencies:
+Install codegen dependencies and fetch the pinned release schemas:
```bash
-cd java/scripts/codegen && npm ci && cd ../../..
+cd java/scripts/codegen && npm ci && npm run fetch:schemas && cd ../../..
```
### Step 1: Reproduce the failure
@@ -135,13 +135,13 @@ To diagnose, compare the current schemas with the generated output:
```bash
# List available schemas
-ls java/scripts/codegen/node_modules/@github/copilot/schemas/
+ls java/scripts/codegen/target/schemas/
# Check for $ref usage in schemas (indicates the codegen may need $ref resolution)
-grep -r '"$ref"' java/scripts/codegen/node_modules/@github/copilot/schemas/ | head -20
+grep -r '"$ref"' java/scripts/codegen/target/schemas/ | head -20
# Look at a specific schema that relates to failing types
-cat java/scripts/codegen/node_modules/@github/copilot/schemas/.json | head -80
+head -80 java/scripts/codegen/target/schemas/.json
```
### Step 3: Fix the code generator (if needed)
@@ -157,7 +157,7 @@ If the diagnosis shows the code generator does not handle the new schema format:
3. **Re-run code generation** to produce updated generated files:
```bash
- cd java/scripts/codegen && npx tsx java.ts && cd ../../..
+ cd java/scripts/codegen && npm run generate && cd ../../..
```
4. **Verify the generated output** looks reasonable:
@@ -213,7 +213,7 @@ After `mvn verify` passes, commit all changes and use the `push-to-pull-request-
```bash
git add -A
-git commit -m "Fix Java codegen and build failures after @github/copilot update
+git commit -m "Fix Java codegen and build failures after CLI update
Automated fix applied by java-codegen-fix workflow."
```
@@ -236,7 +236,7 @@ Do **NOT** push broken code.
## Important constraints
-- **NEVER** hand-edit files under `java/sdk/src/generated/java/` — these are auto-generated. They are updated by running `cd java/scripts/codegen && npx tsx java.ts`.
+- **NEVER** hand-edit files under `java/sdk/src/generated/java/` — these are auto-generated. They are updated by running `cd java/scripts/codegen && npm run generate`.
- **NEVER** modify `java/sdk/pom.xml` — build config is not in scope
- **NEVER** modify `java/scripts/codegen/package.json` or `java/scripts/codegen/package-lock.json` — dependency versions are not in scope
- **NEVER** modify files under `.github/` — workflow files are not in scope
diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml
index fd07aae155..d1ef78f66b 100644
--- a/.github/workflows/java-publish-maven.yml
+++ b/.github/workflows/java-publish-maven.yml
@@ -90,12 +90,13 @@ jobs:
development_version: ${{ steps.versions.outputs.development_version }}
release_tag: ${{ steps.release-identity.outputs.release_tag }}
tag_commit: ${{ steps.release-identity.outputs.tag_commit }}
- pre_prepare_commit: ${{ steps.pre-prepare.outputs.pre_prepare_commit }}
+ pre_prepare_commit: ${{ steps.update-docs.outputs.pre_prepare_commit }}
post_prepare_commit: ${{ steps.release-identity.outputs.post_prepare_commit }}
docs_commit: ${{ steps.update-docs.outputs.docs_commit_sha }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
+ ref: main
fetch-depth: 0
token: ${{ secrets.JAVA_RELEASE_TOKEN }}
@@ -150,16 +151,34 @@ jobs:
run: |
VERSION="${{ steps.versions.outputs.release_version }}"
DEV_VERSION="${{ steps.versions.outputs.development_version }}"
- ./scripts/test-update-documentation-versions.sh
- ./scripts/update-documentation-versions.sh "$VERSION" "$DEV_VERSION" README.md sdk/jbang-example.java
- git add README.md sdk/jbang-example.java
- git commit -m "docs: update version references to ${VERSION}"
- echo "docs_commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- git push origin main
-
- - name: Record rollback base
- id: pre-prepare
- run: echo "pre_prepare_commit=$(git rev-parse HEAD^)" >> "$GITHUB_OUTPUT"
+
+ for attempt in 1 2 3; do
+ git fetch origin main
+ git reset --hard origin/main
+ if [ -z "${{ inputs.releaseVersion }}" ]; then
+ LIVE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+ if [ "${LIVE_VERSION%-SNAPSHOT}" != "$VERSION" ]; then
+ echo "::error::main advanced from release version $VERSION to ${LIVE_VERSION%-SNAPSHOT}; restart the release with the current version."
+ exit 1
+ fi
+ fi
+ ./scripts/test-update-documentation-versions.sh
+ ./scripts/update-documentation-versions.sh "$VERSION" "$DEV_VERSION" README.md sdk/jbang-example.java
+ git add README.md sdk/jbang-example.java
+ git commit -m "docs: update version references to ${VERSION}"
+
+ if git push origin HEAD:main; then
+ echo "pre_prepare_commit=$(git rev-parse HEAD^)" >> "$GITHUB_OUTPUT"
+ echo "docs_commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
+ break
+ fi
+
+ if [ "$attempt" -eq 3 ]; then
+ echo "::error::main continued to advance while preparing the Java release."
+ exit 1
+ fi
+ echo "main advanced during release preparation; retrying from the latest commit."
+ done
- name: Prepare Release
run: |
diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml
index 36310d26b5..514f17c758 100644
--- a/.github/workflows/java-sdk-tests.yml
+++ b/.github/workflows/java-sdk-tests.yml
@@ -1,21 +1,17 @@
name: "Java SDK Tests"
on:
- push:
- branches:
- - main
- paths:
- - "java/**"
- - "test/**"
- - ".github/workflows/java-sdk-tests.yml"
- - ".github/actions/setup-copilot/**"
- - ".github/actions/java-test-report/**"
workflow_dispatch:
workflow_call:
permissions:
contents: read
+env:
+ MAVEN_OPTS: >-
+ -Daether.connector.http.retryHandler.count=3
+ -Daether.connector.http.retryHandler.serviceUnavailable=429,502,503
+
jobs:
java-sdk-inprocess:
name: "Java SDK InProcess Tests (${{ matrix.classifier }})"
@@ -63,7 +59,7 @@ jobs:
run: mvn clean verify -Pinprocess ${{ matrix.maven-args }}
- name: Generate Test Report Summary
- if: always()
+ if: failure()
uses: ./.github/actions/java-test-report
with:
title: "Copilot Java SDK :: Test Results InProcess"
@@ -130,11 +126,12 @@ jobs:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
- name: java-native-publication-linux-arm64-${{ github.run_id }}-${{ github.run_attempt }}
+ name: java-native-publication-linux-arm64-${{ github.run_id }}
path: |
java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-linux-arm64.jar
java/copilot-native/target/linux-arm64-${{ steps.build.outputs.version }}.sha256
if-no-files-found: error
+ overwrite: true
retention-days: 1
java-native-publication-windows:
@@ -187,11 +184,12 @@ jobs:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
- name: java-native-publication-win32-x64-${{ github.run_id }}-${{ github.run_attempt }}
+ name: java-native-publication-win32-x64-${{ github.run_id }}
path: |
java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-win32-x64.jar
java/copilot-native/target/win32-x64-${{ steps.build.outputs.version }}.sha256
if-no-files-found: error
+ overwrite: true
retention-days: 1
java-native-publication-windows-arm64:
@@ -244,11 +242,12 @@ jobs:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
- name: java-native-publication-win32-arm64-${{ github.run_id }}-${{ github.run_attempt }}
+ name: java-native-publication-win32-arm64-${{ github.run_id }}
path: |
java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-win32-arm64.jar
java/copilot-native/target/win32-arm64-${{ steps.build.outputs.version }}.sha256
if-no-files-found: error
+ overwrite: true
retention-days: 1
java-native-publication-darwin:
@@ -302,11 +301,12 @@ jobs:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
- name: java-native-publication-darwin-arm64-${{ github.run_id }}-${{ github.run_attempt }}
+ name: java-native-publication-darwin-arm64-${{ github.run_id }}
path: |
java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-darwin-arm64.jar
java/copilot-native/target/darwin-arm64-${{ steps.build.outputs.version }}.sha256
if-no-files-found: error
+ overwrite: true
retention-days: 1
java-native-publication-assembly:
@@ -342,22 +342,22 @@ jobs:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
- name: java-native-publication-linux-arm64-${{ github.run_id }}-${{ github.run_attempt }}
+ name: java-native-publication-linux-arm64-${{ github.run_id }}
path: ${{ github.workspace }}/java/native-publication-input/linux-arm64
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
- name: java-native-publication-win32-x64-${{ github.run_id }}-${{ github.run_attempt }}
+ name: java-native-publication-win32-x64-${{ github.run_id }}
path: ${{ github.workspace }}/java/native-publication-input/windows
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
- name: java-native-publication-win32-arm64-${{ github.run_id }}-${{ github.run_attempt }}
+ name: java-native-publication-win32-arm64-${{ github.run_id }}
path: ${{ github.workspace }}/java/native-publication-input/windows-arm64
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
- name: java-native-publication-darwin-arm64-${{ github.run_id }}-${{ github.run_attempt }}
+ name: java-native-publication-darwin-arm64-${{ github.run_id }}
path: ${{ github.workspace }}/java/native-publication-input/darwin
- name: Verify native inputs and deploy the complete local release
@@ -410,6 +410,7 @@ jobs:
mvn -B -pl copilot-native deploy -Prelease -DskipTests \
-Dcopilot.native.libc=glibc \
-Dcopilot.native.test.local.publication=true \
+ -DskipPublishing=true \
"-Dcopilot.native.external.linux.arm64.classifier.path=$LINUX_ARM64_JAR" \
"-Dcopilot.native.external.win32.classifier.path=$WINDOWS_JAR" \
"-Dcopilot.native.external.win32.arm64.classifier.path=$WINDOWS_ARM64_JAR" \
@@ -458,7 +459,12 @@ jobs:
run: mvn javadoc:javadoc -q
- name: Verify CLI works
- run: node ../nodejs/node_modules/@github/copilot/npm-loader.js --version
+ run: |
+ npm --prefix ../nodejs ci --ignore-scripts
+ cli_path=$(npm --prefix ../nodejs run --silent prepare:runtime -- --print-path)
+ test -x "$cli_path"
+ legacy_cli=$(npm --prefix ../nodejs run --silent prepare:runtime -- --print-legacy-path)
+ node "$legacy_cli" --version
- name: Run spotless check
if: matrix.test-jdk == '25'
@@ -481,9 +487,8 @@ jobs:
if: matrix.test-jdk == '25'
env:
CI: "true"
- run: |
- node copilot-native/scripts/validate-native-host.mjs linux-x64
- mvn verify -Dskip.test.harness=true -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=false
+ # Native artifacts are built and exercised by every classifier job.
+ run: mvn -pl sdk verify -Dskip.test.harness=true
- name: Switch to JDK 17
if: matrix.test-jdk == '17'
@@ -502,7 +507,7 @@ jobs:
mvn -pl sdk jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test-jdk-banner surefire:test failsafe:integration-test failsafe:verify jacoco:report@build-coverage-report-from-tests -Denforcer.skip=true
- name: Upload test results for site generation
- if: success() && github.ref == 'refs/heads/main' && matrix.test-jdk == '25'
+ if: success() && github.event_name == 'merge_group' && matrix.test-jdk == '25'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: test-results-for-site
diff --git a/.github/workflows/java-smoke-test.yml b/.github/workflows/java-smoke-test.yml
index e7e9a417d2..5f808f8d0d 100644
--- a/.github/workflows/java-smoke-test.yml
+++ b/.github/workflows/java-smoke-test.yml
@@ -29,27 +29,8 @@ jobs:
distribution: "microsoft"
cache: "maven"
- - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v6
- with:
- node-version: 22
-
- - name: Read pinned @github/copilot version from pom.xml
- id: cli-version
- run: |
- PROP="readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync"
- VERSION=$(sed -n "s|.*<${PROP}>\(.*\)${PROP}>.*|\1|p" pom.xml | head -n 1 | tr -d '[:space:]')
- if [[ -z "$VERSION" || "$VERSION" == "PRIMER_TO_REPLACE" ]]; then
- echo "::error::Could not read pinned @github/copilot version from pom.xml property <${PROP}>" >&2
- exit 1
- fi
- echo "Pinned @github/copilot version: $VERSION"
- echo "version=$VERSION" >> "$GITHUB_OUTPUT"
-
- - name: Install Copilot CLI globally (pinned to pom.xml version)
- run: npm install -g "@github/copilot@${{ steps.cli-version.outputs.version }}"
-
- - name: Verify CLI works
- run: copilot --version
+ - uses: ./.github/actions/setup-copilot
+ id: setup-copilot
- name: Build SDK and install to local repo
run: mvn -DskipTests -Pskip-test-harness clean install
@@ -57,6 +38,7 @@ jobs:
- name: Create and run smoke test via Copilot CLI
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_CLI_JS: ${{ steps.setup-copilot.outputs.javascript-cli-path }}
run: |
cat > /tmp/smoke-test-prompt.txt << 'PROMPT_EOF'
You are running inside the copilot-sdk monorepo, in the java/ subdirectory.
@@ -74,7 +56,7 @@ jobs:
If any step fails, exit with a non-zero exit code. Do not silently fix errors.
PROMPT_EOF
- copilot --yolo --prompt "$(cat /tmp/smoke-test-prompt.txt)"
+ node "$COPILOT_CLI_JS" --yolo --prompt "$(cat /tmp/smoke-test-prompt.txt)"
- name: Run smoke test jar
env:
@@ -102,27 +84,8 @@ jobs:
distribution: "microsoft"
cache: "maven"
- - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v6
- with:
- node-version: 22
-
- - name: Read pinned @github/copilot version from pom.xml
- id: cli-version
- run: |
- PROP="readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync"
- VERSION=$(sed -n "s|.*<${PROP}>\(.*\)${PROP}>.*|\1|p" pom.xml | head -n 1 | tr -d '[:space:]')
- if [[ -z "$VERSION" || "$VERSION" == "PRIMER_TO_REPLACE" ]]; then
- echo "::error::Could not read pinned @github/copilot version from pom.xml property <${PROP}>" >&2
- exit 1
- fi
- echo "Pinned @github/copilot version: $VERSION"
- echo "version=$VERSION" >> "$GITHUB_OUTPUT"
-
- - name: Install Copilot CLI globally (pinned to pom.xml version)
- run: npm install -g "@github/copilot@${{ steps.cli-version.outputs.version }}"
-
- - name: Verify CLI works
- run: copilot --version
+ - uses: ./.github/actions/setup-copilot
+ id: setup-copilot
- name: Build SDK and install to local repo
run: mvn -DskipTests -Pskip-test-harness clean install
@@ -130,6 +93,7 @@ jobs:
- name: Create and run smoke test via Copilot CLI
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_CLI_JS: ${{ steps.setup-copilot.outputs.javascript-cli-path }}
run: |
cat > /tmp/smoke-test-prompt.txt << 'PROMPT_EOF'
You are running inside the copilot-sdk monorepo, in the java/ subdirectory.
@@ -150,7 +114,7 @@ jobs:
If any step fails, exit with a non-zero exit code. Do not silently fix errors.
PROMPT_EOF
- copilot --yolo --prompt "$(cat /tmp/smoke-test-prompt.txt)"
+ node "$COPILOT_CLI_JS" --yolo --prompt "$(cat /tmp/smoke-test-prompt.txt)"
- name: Run smoke test jar
env:
diff --git a/.github/workflows/nodejs-sdk-tests.yml b/.github/workflows/nodejs-sdk-tests.yml
index 4c31f79cc4..894f3b5d0d 100644
--- a/.github/workflows/nodejs-sdk-tests.yml
+++ b/.github/workflows/nodejs-sdk-tests.yml
@@ -4,9 +4,6 @@ env:
HUSKY: 0
on:
- push:
- branches:
- - main
workflow_dispatch:
workflow_call:
@@ -52,10 +49,26 @@ jobs:
- name: Build SDK
run: npm run build
+ - name: Build and verify release packages
+ if: runner.os == 'Linux' && matrix.transport == 'default'
+ run: |
+ npm run pack:release
+ npm run verify:release-packages
+
- name: Install test harness dependencies
working-directory: ./test/harness
run: npm ci --ignore-scripts
+ - name: Run test harness tests
+ if: runner.os == 'Linux' && matrix.transport == 'default'
+ working-directory: ./test/harness
+ run: npm test
+
+ - name: Prepare Copilot CLI runtime
+ run: |
+ runtime_path=$(npm run --silent prepare:runtime -- --print-path)
+ echo "COPILOT_CLI_PATH=$runtime_path" >> "$GITHUB_ENV"
+
- name: Warm up PowerShell
if: runner.os == 'Windows'
run: pwsh.exe -Command "Write-Host 'PowerShell ready'"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 98bf236900..5e1d277259 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -107,19 +107,19 @@ jobs:
- name: Build
run: npm run build
- name: Pack
- id: pack
run: |
- TARBALL="$(npm pack . --json | jq -r '.[0].filename')"
- if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
- echo "::error::npm pack did not produce a tarball."
+ npm run pack:release
+ TARBALL_COUNT="$(find . -maxdepth 1 -name 'github-copilot-sdk-*.tgz' | wc -l | tr -d ' ')"
+ if [ "$TARBALL_COUNT" -ne 9 ]; then
+ echo "::error::Expected nine Node.js package tarballs, found $TARBALL_COUNT."
exit 1
fi
- echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT"
+ npm run verify:release-packages
- name: Upload artifact
uses: actions/upload-artifact@v7.0.0
with:
name: nodejs-package
- path: nodejs/${{ steps.pack.outputs.tarball }}
+ path: nodejs/github-copilot-sdk-*.tgz
if-no-files-found: error
publish-nodejs:
@@ -150,12 +150,29 @@ jobs:
set -euo pipefail
shopt -s nullglob
TARBALLS=(./dist/*.tgz)
- if [ "${#TARBALLS[@]}" -ne 1 ]; then
- echo "::error::Expected exactly one Node.js package tarball, found ${#TARBALLS[@]}."
+ if [ "${#TARBALLS[@]}" -ne 9 ]; then
+ echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}."
+ exit 1
+ fi
+ MAIN_TARBALL=""
+ for TARBALL in "${TARBALLS[@]}"; do
+ PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)"
+ if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then
+ MAIN_TARBALL="$TARBALL"
+ continue
+ fi
+ node nodejs/scripts/npm-release.js publish \
+ "$TARBALL" \
+ "$DIST_TAG" \
+ https://registry.npmjs.org \
+ public
+ done
+ if [ -z "$MAIN_TARBALL" ]; then
+ echo "::error::Main @github/copilot-sdk tarball not found."
exit 1
fi
node nodejs/scripts/npm-release.js publish \
- "${TARBALLS[0]}" \
+ "$MAIN_TARBALL" \
"$DIST_TAG" \
https://registry.npmjs.org \
public
@@ -209,12 +226,29 @@ jobs:
fi
shopt -s nullglob
TARBALLS=(./dist/*.tgz)
- if [ "${#TARBALLS[@]}" -ne 1 ]; then
- echo "::error::Expected exactly one Node.js package tarball, found ${#TARBALLS[@]}."
+ if [ "${#TARBALLS[@]}" -ne 9 ]; then
+ echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}."
+ exit 1
+ fi
+ MAIN_TARBALL=""
+ for TARBALL in "${TARBALLS[@]}"; do
+ PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)"
+ if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then
+ MAIN_TARBALL="$TARBALL"
+ continue
+ fi
+ node nodejs/scripts/npm-release.js publish \
+ "$TARBALL" \
+ "$DIST_TAG" \
+ "$FEED_URL" \
+ azure
+ done
+ if [ -z "$MAIN_TARBALL" ]; then
+ echo "::error::Main @github/copilot-sdk tarball not found."
exit 1
fi
node nodejs/scripts/npm-release.js publish \
- "${TARBALLS[0]}" \
+ "$MAIN_TARBALL" \
"$DIST_TAG" \
"$FEED_URL" \
azure
@@ -327,9 +361,6 @@ jobs:
node-version: "22.x"
- name: Set up uv
uses: astral-sh/setup-uv@v7
- - name: Install Node.js dependencies (for CLI version)
- working-directory: ./nodejs
- run: npm ci --ignore-scripts
- name: Set version
run: sed -i "s/^version = .*/version = \"${{ needs.version.outputs.version }}\"/" pyproject.toml
- name: Inject CLI version
diff --git a/.github/workflows/python-sdk-tests.yml b/.github/workflows/python-sdk-tests.yml
index 1ea9739756..aa6e803372 100644
--- a/.github/workflows/python-sdk-tests.yml
+++ b/.github/workflows/python-sdk-tests.yml
@@ -4,9 +4,6 @@ env:
PYTHONUTF8: 1
on:
- push:
- branches:
- - main
workflow_dispatch:
workflow_call:
@@ -27,6 +24,7 @@ jobs:
python-version: ["3.11"]
transport: ["default", "inprocess"]
runs-on: ${{ matrix.os }}
+ timeout-minutes: 20
defaults:
run:
shell: bash
@@ -52,7 +50,7 @@ jobs:
- name: Install Node.js dependencies (for CLI in tests)
working-directory: ./nodejs
- run: npm ci --ignore-scripts
+ run: npm ci --ignore-scripts --fetch-retries=4 --fetch-retry-mintimeout=10000 --fetch-retry-maxtimeout=60000
- name: Run ruff format check
run: uv run ruff format --check .
@@ -65,7 +63,7 @@ jobs:
- name: Install test harness dependencies
working-directory: ./test/harness
- run: npm ci --ignore-scripts
+ run: npm ci --ignore-scripts --fetch-retries=4 --fetch-retry-mintimeout=10000 --fetch-retry-maxtimeout=60000
- name: Warm up PowerShell
if: runner.os == 'Windows'
diff --git a/.github/workflows/release-changelog.lock.yml b/.github/workflows/release-changelog.lock.yml
index 23b19d9b82..558cdc2e27 100644
--- a/.github/workflows/release-changelog.lock.yml
+++ b/.github/workflows/release-changelog.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a4a0859e0103be270433c7fe1926a346c46271b4b530b2c08fa5d606d0ab75c4","body_hash":"490b25b529910b1b087df624fd59eaef52e142e84b9503ca1cf87631f4c36b53","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a4a0859e0103be270433c7fe1926a346c46271b4b530b2c08fa5d606d0ab75c4","body_hash":"490b25b529910b1b087df624fd59eaef52e142e84b9503ca1cf87631f4c36b53","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["create_pull_request","missing_data","missing_tool","noop","update_release"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,8 +26,8 @@
# Generates release notes from merged PRs/commits. Triggered by the publish workflow or manually via workflow_dispatch.
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
# - GH_AW_CI_TRIGGER_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -35,22 +35,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Release Changelog Generator"
on:
@@ -70,9 +68,18 @@ permissions: {}
concurrency:
group: "gh-aw-${{ github.workflow }}"
+ queue: max
run-name: "Release Changelog Generator"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.release-changelog
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Release%20Changelog%20Generator,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
runs-on: ubuntu-slim
@@ -86,6 +93,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -99,7 +107,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -107,34 +115,39 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Generate agentic run info
id: generate_aw_info
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "Release Changelog Generator"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -156,9 +169,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -176,38 +191,37 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -216,27 +230,34 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"file\":\"safe_outputs_create_pull_request.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -246,79 +267,37 @@ jobs:
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_c642707f673b9ac4_EOF'
-
- GH_AW_PROMPT_c642707f673b9ac4_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_c642707f673b9ac4_EOF'
-
- Tools: create_pull_request, update_release, missing_tool, missing_data, noop
- GH_AW_PROMPT_c642707f673b9ac4_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
- cat << 'GH_AW_PROMPT_c642707f673b9ac4_EOF'
-
- GH_AW_PROMPT_c642707f673b9ac4_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_c642707f673b9ac4_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_c642707f673b9ac4_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_c642707f673b9ac4_EOF'
-
- {{#runtime-import .github/workflows/release-changelog.md}}
- GH_AW_PROMPT_c642707f673b9ac4_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: create_pull_request, update_release, missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/release-changelog.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_GITHUB_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -331,10 +310,12 @@ jobs:
GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -354,16 +335,20 @@ jobs:
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: activation
@@ -391,12 +376,19 @@ jobs:
copilot-requests: write
issues: read
pull-requests: read
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: releasechangelog
outputs:
@@ -410,7 +402,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -418,11 +413,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -431,19 +427,28 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -473,16 +478,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -491,13 +499,15 @@ jobs:
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -509,15 +519,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_269226b895ff9733_EOF'
- {"create_pull_request":{"draft":false,"labels":["automation","changelog"],"max":1,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","title_prefix":"[changelog] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_release":{"max":1}}
- GH_AW_SAFE_OUTPUTS_CONFIG_269226b895ff9733_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"labels\":[\"automation\",\"changelog\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[changelog] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{},\"update_release\":{\"max\":1}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -551,6 +572,12 @@ jobs:
"sanitize": true,
"maxLength": 256
},
+ "dependencies": {
+ "type": "array",
+ "itemType": "string",
+ "itemSanitize": true,
+ "itemMaxLength": 256
+ },
"draft": {
"type": "boolean"
},
@@ -564,6 +591,18 @@ jobs:
"type": "string",
"maxLength": 256
},
+ "stack_position": {
+ "optionalPositiveInteger": true
+ },
+ "stack_root": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
+ },
"title": {
"required": true,
"type": "string",
@@ -675,9 +714,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -686,6 +727,7 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -693,33 +735,45 @@ jobs:
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_46604863f3d8e286_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -752,6 +806,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -763,7 +825,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -771,25 +833,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF
+ GH_AW_MCP_CONFIG_46604863f3d8e286_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -805,18 +874,33 @@ jobs:
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -834,14 +918,19 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -849,7 +938,7 @@ jobs:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 15
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -870,7 +959,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 15
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -886,7 +986,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -895,9 +995,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -920,14 +1022,16 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -937,9 +1041,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -947,9 +1053,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -963,9 +1071,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -973,16 +1083,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -999,6 +1134,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1018,9 +1155,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
+ actions: read
contents: write
issues: write
pull-requests: write
@@ -1038,7 +1176,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1047,15 +1185,16 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1063,42 +1202,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: safe-outputs-items
+ pattern: safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1112,6 +1238,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1134,9 +1262,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1166,7 +1296,7 @@ jobs:
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/release-changelog.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1174,9 +1304,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1191,9 +1323,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1206,9 +1340,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1221,9 +1357,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1236,7 +1374,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "release-changelog"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1250,6 +1388,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }}
GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }}
@@ -1267,9 +1409,30 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "Release Changelog Generator"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/release-changelog.md"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1281,6 +1444,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1291,7 +1455,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1300,15 +1464,22 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1316,10 +1487,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1328,7 +1501,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1352,21 +1525,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1374,82 +1533,52 @@ jobs:
WORKFLOW_NAME: "Release Changelog Generator"
WORKFLOW_DESCRIPTION: "Generates release notes from merged PRs/commits. Triggered by the publish workflow or manually via workflow_dispatch."
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: detection
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1465,58 +1594,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "Release Changelog Generator"
+ WORKFLOW_DESCRIPTION: "Generates release notes from merged PRs/commits. Triggered by the publish workflow or manually via workflow_dispatch."
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1540,7 +1716,6 @@ jobs:
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_WORKFLOW_ID: "release-changelog"
@@ -1553,12 +1728,20 @@ jobs:
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }}
created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1567,15 +1750,18 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1583,7 +1769,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Download patch artifact
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -1592,7 +1780,7 @@ jobs:
path: /tmp/gh-aw/
- name: Checkout repository
if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request')
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: true
token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -1618,17 +1806,19 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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_pull_request\":{\"draft\":false,\"labels\":[\"automation\",\"changelog\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[changelog] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_release\":{\"max\":1}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"labels\":[\"automation\",\"changelog\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[changelog] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{},\"update_release\":{\"max\":1}}"
GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1638,4 +1828,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/required-checks.yml b/.github/workflows/required-checks.yml
index dbd79fd4cf..0fb8acf70b 100644
--- a/.github/workflows/required-checks.yml
+++ b/.github/workflows/required-checks.yml
@@ -10,6 +10,10 @@ permissions:
contents: read
pull-requests: read
+concurrency:
+ group: sdk-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
jobs:
changes:
name: Select SDK workflows
@@ -33,42 +37,41 @@ jobs:
- '.github/workflows/required-checks.yml'
nodejs:
- '{nodejs/**,test/**,.github/workflows/nodejs-sdk-tests.yml}'
- - '!nodejs/scripts/**'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
- '!**/.editorconfig'
- '!**/*.{png,jpg,jpeg,gif,svg}'
python:
- - '{python/**,test/**,nodejs/package.json,.github/workflows/python-sdk-tests.yml}'
+ - '{python/**,test/**,nodejs/package.json,nodejs/scripts/**,.github/workflows/python-sdk-tests.yml}'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
- '!**/.editorconfig'
- '!**/*.{png,jpg,jpeg,gif,svg}'
go:
- - '{go/**,test/**,nodejs/package.json,.github/workflows/go-sdk-tests.yml,.github/actions/setup-copilot/**}'
+ - '{go/**,test/**,nodejs/package.json,nodejs/scripts/**,.github/workflows/go-sdk-tests.yml,.github/actions/setup-copilot/**}'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
- '!**/.editorconfig'
- '!**/*.{png,jpg,jpeg,gif,svg}'
dotnet:
- - '{dotnet/**,test/**,nodejs/package.json,.github/workflows/dotnet-sdk-tests.yml}'
+ - '{dotnet/**,test/**,nodejs/package.json,nodejs/scripts/**,.github/workflows/dotnet-sdk-tests.yml}'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
- '!**/.editorconfig'
- '!**/*.{png,jpg,jpeg,gif,svg}'
java:
- - '{java/**,test/**,.github/workflows/java-sdk-tests.yml,.github/actions/setup-copilot/**,.github/actions/java-test-report/**}'
+ - '{java/**,test/**,nodejs/package.json,nodejs/scripts/**,.github/workflows/java-sdk-tests.yml,.github/actions/setup-copilot/**,.github/actions/java-test-report/**}'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
- '!**/.editorconfig'
- '!**/*.{png,jpg,jpeg,gif,svg}'
rust:
- - '{rust/**,test/**,nodejs/package.json,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}'
+ - '{rust/**,test/**,nodejs/package.json,nodejs/scripts/**,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
diff --git a/.github/workflows/rust-sdk-tests.yml b/.github/workflows/rust-sdk-tests.yml
index 7fdac3b818..828459126b 100644
--- a/.github/workflows/rust-sdk-tests.yml
+++ b/.github/workflows/rust-sdk-tests.yml
@@ -1,9 +1,6 @@
name: "Rust SDK Tests"
on:
- push:
- branches:
- - main
workflow_dispatch:
workflow_call:
@@ -23,6 +20,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
+ timeout-minutes: 20
defaults:
run:
shell: bash
@@ -38,17 +36,6 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.94.0"
- components: rustfmt, clippy
-
- # Nightly rustfmt for unstable format options (group_imports,
- # imports_granularity, reorder_impl_items) — pinned in
- # `.rustfmt.nightly.toml`.
- - name: Install nightly rustfmt
- if: runner.os == 'Linux'
- uses: dtolnay/rust-toolchain@master
- with:
- toolchain: nightly-2026-04-14
- components: rustfmt
- uses: Swatinem/rust-cache@v2
with:
@@ -56,11 +43,11 @@ jobs:
prefix-key: v1-rust-no-bin
cache-bin: false
- - name: Read pinned @github/copilot CLI version
+ - name: Read pinned Copilot CLI version
id: cli-version
working-directory: ./nodejs
run: |
- version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version")
+ version=$(node -p "require('./package.json').copilotCliVersion")
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Pinned CLI version: $version"
@@ -73,23 +60,6 @@ jobs:
path: ./rust/.bundled-cli-cache
key: bundled-cli-${{ matrix.os }}-${{ steps.cli-version.outputs.version }}
- - name: cargo fmt --check (nightly)
- if: runner.os == 'Linux'
- run: cargo +nightly-2026-04-14 fmt --all -- --config-path .rustfmt.nightly.toml --check
-
- - name: cargo clippy
- if: runner.os == 'Linux'
- env:
- BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache
- run: cargo clippy --all-targets --features test-support,bundled-in-process -- --no-deps -D warnings -D clippy::unwrap_used -D clippy::disallowed_macros -D clippy::await_holding_invalid_type
-
- - name: cargo doc
- if: runner.os == 'Linux'
- env:
- RUSTDOCFLAGS: "-D warnings"
- BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache
- run: cargo doc --no-deps --all-features
-
- name: Install test harness dependencies
working-directory: ./test/harness
run: npm ci --ignore-scripts
@@ -112,6 +82,112 @@ jobs:
# The dedicated `bundle` job below exercises the embed pipeline.
run: cargo test --no-default-features --features test-support -- --test-threads=4 --nocapture
+ clippy:
+ name: "Rust SDK Format and Clippy"
+ if: github.event.repository.fork == false
+ env:
+ POWERSHELL_UPDATECHECK: Off
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ defaults:
+ run:
+ shell: bash
+ working-directory: ./rust
+ steps:
+ - uses: actions/checkout@v6.0.2
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
+ with:
+ toolchain: "1.94.0"
+ components: clippy
+
+ # Nightly rustfmt for unstable format options (group_imports,
+ # imports_granularity, reorder_impl_items) — pinned in
+ # `.rustfmt.nightly.toml`.
+ - name: Install nightly rustfmt
+ uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30
+ with:
+ toolchain: nightly-2026-04-14
+ components: rustfmt
+
+ - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
+ with:
+ workspaces: "rust"
+ prefix-key: v1-rust-no-bin
+ cache-bin: false
+
+ - name: Read pinned Copilot CLI version
+ id: cli-version
+ working-directory: ./nodejs
+ run: |
+ version=$(node -p "require('./package.json').copilotCliVersion")
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+ echo "Pinned CLI version: $version"
+
+ - name: Cache bundled CLI archives
+ uses: actions/cache@v4
+ with:
+ path: ./rust/.bundled-cli-cache
+ key: bundled-cli-ubuntu-latest-${{ steps.cli-version.outputs.version }}
+
+ - name: cargo fmt --check (nightly)
+ run: cargo +nightly-2026-04-14 fmt --all -- --config-path .rustfmt.nightly.toml --check
+
+ - name: cargo clippy
+ env:
+ BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache
+ run: cargo clippy --all-targets --features test-support,bundled-in-process -- --no-deps -D warnings -D clippy::unwrap_used -D clippy::disallowed_macros -D clippy::await_holding_invalid_type
+
+ doc:
+ name: "Rust SDK Docs"
+ if: github.event.repository.fork == false
+ env:
+ POWERSHELL_UPDATECHECK: Off
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ defaults:
+ run:
+ shell: bash
+ working-directory: ./rust
+ steps:
+ - uses: actions/checkout@v6.0.2
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
+ with:
+ toolchain: "1.94.0"
+
+ - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
+ with:
+ workspaces: "rust"
+ prefix-key: v1-rust-no-bin
+ cache-bin: false
+
+ - name: Read pinned Copilot CLI version
+ id: cli-version
+ working-directory: ./nodejs
+ run: |
+ version=$(node -p "require('./package.json').copilotCliVersion")
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+ echo "Pinned CLI version: $version"
+
+ - name: Cache bundled CLI archives
+ uses: actions/cache@v4
+ with:
+ path: ./rust/.bundled-cli-cache
+ key: bundled-cli-ubuntu-latest-${{ steps.cli-version.outputs.version }}
+
+ - name: cargo doc
+ env:
+ RUSTDOCFLAGS: "-D warnings"
+ BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache
+ run: cargo doc --no-deps --all-features
+
# Exercises the in-process FFI transport (`Transport::InProcess`, the Rust
# analogue of the .NET `RuntimeConnection.ForInProcess()`), mirroring the
# `inprocess` transport cell in dotnet-sdk-tests.yml. Sets
@@ -133,6 +209,7 @@ jobs:
# TODO: Re-enable Windows after fixing the napi-oop peer shutdown crash.
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
+ timeout-minutes: 20
defaults:
run:
shell: bash
@@ -154,11 +231,11 @@ jobs:
prefix-key: v1-rust-no-bin
cache-bin: false
- - name: Read pinned @github/copilot CLI version
+ - name: Read pinned Copilot CLI version
id: cli-version
working-directory: ./nodejs
run: |
- version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version")
+ version=$(node -p "require('./package.json').copilotCliVersion")
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Pinned CLI version: $version"
@@ -207,6 +284,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
+ timeout-minutes: 20
defaults:
run:
shell: bash
@@ -229,11 +307,11 @@ jobs:
prefix-key: v1-rust-no-bin
cache-bin: false
- - name: Read pinned @github/copilot CLI version
+ - name: Read pinned Copilot CLI version
id: cli-version
working-directory: ./nodejs
run: |
- version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version")
+ version=$(node -p "require('./package.json').copilotCliVersion")
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Pinned CLI version: $version"
diff --git a/.github/workflows/sdk-canary.yml b/.github/workflows/sdk-canary.yml
index 95f8b1c926..7425f8e720 100644
--- a/.github/workflows/sdk-canary.yml
+++ b/.github/workflows/sdk-canary.yml
@@ -1,391 +1,428 @@
-name: "SDK Canary Test/Publish"
-
-# Nightly-style canary pipeline. First installs an explicit version of the
-# @github/copilot runtime, builds the Node SDK, and runs the Node e2e suite
-# against it to prove runtime <-> SDK compatibility. When that gate passes (and
-# mode allows), publishes an SDK canary pinned to the tested runtime to the
-# internal Azure Artifacts feed only (never public npm).
-
-env:
- HUSKY: 0
- # Internal org-scoped Azure Artifacts feed — single source of truth so the
- # feed name isn't repeated across steps. The SDK canary publishes here and
- # (when runtime_source=internal) installs the runtime from here; it must NEVER
- # reach public npm (@github/copilot-sdk is a live public package).
- FEED_URL: https://pkgs.dev.azure.com/devdiv/_packaging/copilot-canary/npm/registry/
- # Azure DevOps resource ID used to mint an ADO access token for the feed.
- ADO_RESOURCE: 499b84ac-1321-427f-aa17-267ca6975798
-
-on:
- workflow_dispatch:
- inputs:
- runtime_version:
- description: "Exact @github/copilot version to test (e.g. 1.0.69 or 1.0.70-canary.)"
- required: true
- type: string
- runtime_source:
- description: "Where to install the runtime from"
- required: true
- type: choice
- options:
- - public
- - internal
- default: public
- mode:
- description: "publish (tests must pass), publish-force (publish even if tests fail), or tests-only (run gate, never publish)"
- required: false
- type: choice
- default: publish
- options:
- - publish
- - publish-force
- - tests-only
- repository_dispatch:
- types: [runtime-canary]
-
-permissions:
- contents: read
- id-token: write
-
-# Serialize runs per ref so two overlapping canary runs can't race the feed
-# publish. cancel-in-progress: false — never kill an in-flight publish.
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: false
-
-jobs:
- resolve:
- name: "Resolve runtime inputs"
- if: github.event.repository.fork == false
- runs-on: ubuntu-latest
- permissions: {}
- outputs:
- RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }}
- RUNTIME_SOURCE: ${{ steps.normalize.outputs.RUNTIME_SOURCE }}
- PUBLISH_MODE: ${{ steps.normalize.outputs.PUBLISH_MODE }}
- steps:
- # Normalize whichever trigger fired into a single (RUNTIME_VERSION,
- # RUNTIME_SOURCE, PUBLISH_MODE) triple that every downstream step
- # references. workflow_dispatch reads the human-supplied inputs;
- # repository_dispatch reads client_payload and forces source=internal
- # (a runtime canary only exists on the feed), defaulting mode to publish.
- - name: Normalize inputs
- id: normalize
- env:
- EVENT_NAME: ${{ github.event_name }}
- INPUT_VERSION: ${{ inputs.runtime_version }}
- INPUT_SOURCE: ${{ inputs.runtime_source }}
- INPUT_MODE: ${{ inputs.mode }}
- PAYLOAD_VERSION: ${{ github.event.client_payload.runtime_version }}
- PAYLOAD_SOURCE: ${{ github.event.client_payload.runtime_source }}
- PAYLOAD_MODE: ${{ github.event.client_payload.mode }}
- run: |
- set -euo pipefail
- case "$EVENT_NAME" in
- workflow_dispatch)
- VERSION="$INPUT_VERSION"
- SOURCE="$INPUT_SOURCE"
- MODE="$INPUT_MODE"
- ;;
- repository_dispatch)
- VERSION="$PAYLOAD_VERSION"
- # A runtime canary only ever exists on the internal feed.
- SOURCE="${PAYLOAD_SOURCE:-internal}"
- MODE="${PAYLOAD_MODE:-publish}"
- ;;
- *)
- echo "::error::Unsupported event '$EVENT_NAME'."
- exit 1
- ;;
- esac
- if [ -z "$VERSION" ]; then echo "::error::Could not determine runtime version."; exit 1; fi
- if [ -z "$SOURCE" ]; then SOURCE="public"; fi
- case "$SOURCE" in
- public|internal) ;;
- *) echo "::error::Invalid runtime source '$SOURCE'. Expected one of: public, internal."; exit 1 ;;
- esac
- if [ -z "$MODE" ]; then MODE="publish"; fi
- case "$MODE" in
- publish|publish-force|tests-only) ;;
- *) echo "::error::Invalid publish mode '$MODE'. Expected one of: publish, publish-force, tests-only."; exit 1 ;;
- esac
- echo "Resolved RUNTIME_VERSION=$VERSION RUNTIME_SOURCE=$SOURCE PUBLISH_MODE=$MODE"
- echo "RUNTIME_VERSION=$VERSION" >> "$GITHUB_OUTPUT"
- echo "RUNTIME_SOURCE=$SOURCE" >> "$GITHUB_OUTPUT"
- echo "PUBLISH_MODE=$MODE" >> "$GITHUB_OUTPUT"
-
- - name: Validate runtime version (semver)
- env:
- RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }}
- run: |
- if [[ ! "$RUNTIME_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then
- echo "::error::Invalid runtime version '$RUNTIME_VERSION'. Expected semver (e.g. 1.0.69 or 1.0.70-canary.abc123)."
- exit 1
- fi
-
- test:
- name: "E2E tests (${{ matrix.os }})"
- needs: resolve
- if: github.event.repository.fork == false
- environment: cicd
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- runs-on: ${{ matrix.os }}
- env:
- POWERSHELL_UPDATECHECK: Off
- RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }}
- RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }}
- defaults:
- run:
- shell: bash
- working-directory: ./nodejs
- steps:
- - uses: actions/checkout@v6.0.2
-
- - uses: actions/setup-node@v6
- with:
- cache: "npm"
- cache-dependency-path: "./nodejs/package-lock.json"
- node-version: 22
-
- - name: Install SDK dependencies
- run: npm ci --ignore-scripts
-
- - name: Install test harness dependencies
- working-directory: ./test/harness
- run: npm ci --ignore-scripts
-
- - name: Azure Login (OIDC -> id-cpd-ci)
- if: env.RUNTIME_SOURCE == 'internal'
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
- with:
- client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci
- tenant-id: "${{ vars.CPD_ID_TENANT_ID }}"
- allow-no-subscriptions: true
-
- # Route ONLY @github/* (the runtime + its 8 platform packages) to the
- # internal feed via a scoped registry. All other deps (e.g. detect-libc)
- # still resolve from public npm. A global --registry would break because
- # detect-libc is not on the feed.
- - name: Configure canary feed (.npmrc)
- if: env.RUNTIME_SOURCE == 'internal'
- run: |
- set -euo pipefail
- TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)"
- echo "::add-mask::$TOKEN"
- # Derive the protocol-relative auth scopes from FEED_URL so the feed
- # name lives in exactly one place (the workflow-level env).
- FEED_AUTH_REGISTRY="${FEED_URL#https:}"
- FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}"
- NPMRC="$(printf '%s\n' \
- "@github:registry=${FEED_URL}" \
- "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \
- "${FEED_AUTH_BASE}:_authToken=${TOKEN}")"
- printf '%s\n' "$NPMRC" > .npmrc
- echo "Wrote scoped @github registry .npmrc to ./nodejs"
-
- - name: Override runtime version
- run: |
- set -euo pipefail
- echo "Installing @github/copilot@${RUNTIME_VERSION} (source: ${RUNTIME_SOURCE})"
- npm install "@github/copilot@${RUNTIME_VERSION}" --save-exact --ignore-scripts
-
- - name: Verify installed runtime
- run: |
- set -euo pipefail
- node -e '
- const fs = require("fs");
- const expected = process.env.RUNTIME_VERSION;
- const pkg = require("./node_modules/@github/copilot/package.json");
- if (pkg.version !== expected) {
- console.error(`::error::Installed @github/copilot version ${pkg.version} does not match requested ${expected}`);
- process.exit(1);
- }
- const dir = "./node_modules/@github";
- const entries = fs.readdirSync(dir).filter((d) => d.startsWith("copilot-"));
- const plat = process.platform === "win32" ? "win32" : process.platform === "darwin" ? "darwin" : "linux";
- const arch = process.arch;
- const match = entries.find((d) => d.includes(plat) && d.includes(arch));
- if (!match) {
- console.error(`::error::No @github/copilot platform optional dep for ${plat}-${arch}. Present: ${entries.join(", ") || "(none)"}`);
- process.exit(1);
- }
- const platPkg = require(`${dir}/${match}/package.json`);
- if (platPkg.version !== expected) {
- console.error(`::error::Platform package @github/${match} version ${platPkg.version} does not match requested ${expected}`);
- process.exit(1);
- }
- console.log(`Verified @github/copilot@${pkg.version} with platform package @github/${match}@${platPkg.version}`);
- '
-
- - name: Build SDK
- run: npm run build
-
- - name: Warm up PowerShell
- if: runner.os == 'Windows'
- run: pwsh.exe -Command "Write-Host 'PowerShell ready'"
-
- - name: Run Node.js SDK e2e tests
- env:
- COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }}
- run: npm test
-
- publish:
- name: "Publish SDK canary (internal feed)"
- needs: [resolve, test]
- # Publish runs only when the gate permits it. Mode governs behavior:
- # - tests-only: never publish (skips this job entirely).
- # - publish: publish only when the e2e gate is green (the default for both
- # the human and automated triggers).
- # - publish-force: publish even on a non-green gate — a human-acknowledged
- # flake override, audited via the ::warning:: step below and the run actor.
- # publish-force only skips the e2e *signal* — the publish job still runs the
- # build (so a broken build can't publish) and enforces the feed-only guards.
- if: >
- !cancelled() &&
- github.event.repository.fork == false &&
- needs.resolve.result == 'success' &&
- needs.resolve.outputs.PUBLISH_MODE != 'tests-only' &&
- (needs.test.result == 'success' ||
- needs.resolve.outputs.PUBLISH_MODE == 'publish-force')
- environment: cicd
- runs-on: ubuntu-latest
- permissions:
- contents: read
- id-token: write
- env:
- RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }}
- defaults:
- run:
- shell: bash
- working-directory: ./nodejs
- steps:
- - name: Warn — publishing despite failed e2e gate (publish-force)
- # always() so this audit is never skipped by prior-step status; it fires
- # specifically when publish proceeded on a non-green gate via publish-force.
- # Runs at the workspace root because it executes before checkout, so the
- # job's default working-directory (./nodejs) does not exist yet.
- if: always() && needs.test.result != 'success' && needs.resolve.outputs.PUBLISH_MODE == 'publish-force'
- working-directory: ${{ github.workspace }}
- run: |
- echo "::warning title=e2e gate bypassed::Publishing SDK canary despite a non-passing e2e gate (test job result: ${{ needs.test.result }}) via publish-force. Triggered by '${{ github.actor }}' through '${{ github.event_name }}'. The e2e signal was bypassed; build + feed-only guards still apply."
-
- - uses: actions/checkout@v6.0.2
-
- - uses: actions/setup-node@v6
- with:
- node-version: 22
-
- # Default public registry: installs build deps and the currently pinned
- # runtime. Do NOT write any feed .npmrc or scoped @github:registry line
- # here, or npm ci would try to fetch the runtime from the upstream-less
- # feed and 404.
- - name: Install SDK dependencies
- run: npm ci --ignore-scripts
-
- - name: Compute SDK canary version
- id: sdkver
- env:
- RUN_NUMBER: ${{ github.run_number }}
- SHA: ${{ github.sha }}
- run: |
- set -euo pipefail
- SHORT_SHA="${SHA:0:7}"
- # Base the canary on the NEXT patch of the public SDK latest so canaries
- # correlate with public releases: they sort ABOVE the current public
- # latest and BELOW the eventual real release of that next patch (a
- # prerelease of X.Y.Z always sorts below X.Y.Z), so a canary can never
- # shadow the real release when it ships.
- # Reuse the repo's own version helper (scripts/get-version.js) so this
- # stays consistent with publish.yml: `current` returns the latest public
- # dist-tag version, read-only from public npm (never the feed), then
- # we bump the patch ourselves to keep strict patch+1 semantics.
- PUBLIC_LATEST="$(node scripts/get-version.js current || true)"
- BASE="${PUBLIC_LATEST%%-*}"; BASE="${BASE%%+*}"
- if [[ "$BASE" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
- NEXT="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$(( BASH_REMATCH[3] + 1 ))"
- else
- echo "::error::Could not resolve public SDK latest version (got '$PUBLIC_LATEST'); refusing to publish a canary with an unknown base."
- exit 1
- fi
- SDK_VERSION="${NEXT}-canary.${RUN_NUMBER}.g${SHORT_SHA}"
- if [[ ! "$SDK_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then
- echo "::error::Computed SDK canary version '$SDK_VERSION' is not valid semver."
- exit 1
- fi
- echo "SDK canary version: $SDK_VERSION"
- echo "SDK_VERSION=$SDK_VERSION" >> "$GITHUB_OUTPUT"
-
- - name: Set package version and pin runtime dependency
- env:
- SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }}
- run: |
- set -euo pipefail
- npm version "$SDK_VERSION" --no-git-tag-version --allow-same-version
- # Exact pin (no caret) so the published SDK canary depends on precisely
- # the runtime version that was just tested by the e2e gate.
- npm pkg set "dependencies.@github/copilot=$RUNTIME_VERSION"
- echo "Pinned @github/copilot to $(npm pkg get dependencies.@github/copilot)"
-
- - name: Build SDK
- run: npm run build
-
- - name: Azure Login (OIDC -> id-cpd-ci)
- uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
- with:
- client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci
- tenant-id: "${{ vars.CPD_ID_TENANT_ID }}"
- allow-no-subscriptions: true
-
- # Auth-only .npmrc: just the two token lines, NO scoped registry line.
- # The publish target is supplied explicitly via publishConfig + --registry.
- - name: Configure feed auth (.npmrc)
- run: |
- set -euo pipefail
- TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)"
- echo "::add-mask::$TOKEN"
- # Derive the protocol-relative auth scopes from FEED_URL (single source
- # of truth). NO scoped @github:registry line here — publish target is
- # supplied explicitly via publishConfig + --registry.
- FEED_AUTH_REGISTRY="${FEED_URL#https:}"
- FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}"
- printf '%s\n' \
- "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \
- "${FEED_AUTH_BASE}:_authToken=${TOKEN}" > .npmrc
- echo "Wrote auth-only .npmrc to ./nodejs"
-
- # Belt and suspenders (2 of 3): pin the publish target in the package too.
- - name: Set publishConfig registry
- run: npm pkg set "publishConfig.registry=$FEED_URL"
-
- # Belt and suspenders (3 of 3): fail loudly unless the effective publish
- # target is the internal feed. Guards against ever reaching public npm.
- - name: Assert publish target is the internal feed
- run: |
- set -euo pipefail
- EFFECTIVE="$(npm pkg get publishConfig.registry | tr -d '"')"
- echo "Effective publishConfig.registry: $EFFECTIVE"
- if [ "$EFFECTIVE" != "$FEED_URL" ]; then
- echo "::error::publishConfig.registry ('$EFFECTIVE') is not the internal feed ('$FEED_URL'). Refusing to publish."
- exit 1
- fi
-
- - name: Publish SDK canary to internal feed
- run: npm publish --registry "$FEED_URL"
-
- - name: Summarize published canary
- env:
- SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }}
- run: |
- set -euo pipefail
- {
- echo "## SDK canary published"
- echo ""
- echo "| | |"
- echo "| --- | --- |"
- echo "| Runtime consumed | \`@github/copilot@${RUNTIME_VERSION}\` |"
- echo "| Canary SDK produced | \`@github/copilot-sdk@${SDK_VERSION}\` |"
- echo "| Feed | ${FEED_URL} |"
- } >> "$GITHUB_STEP_SUMMARY"
+name: "SDK Canary Test/Publish"
+
+# Nightly-style canary pipeline. First installs an explicit version of the
+# @github/copilot runtime, builds the Node SDK, and runs the Node e2e suite
+# against it to prove runtime <-> SDK compatibility. When that gate passes (and
+# mode allows), publishes an SDK canary pinned to the tested runtime to the
+# internal Azure Artifacts feed only (never public npm).
+
+env:
+ HUSKY: 0
+ # Internal org-scoped Azure Artifacts feed — single source of truth so the
+ # feed name isn't repeated across steps. The SDK canary publishes here and
+ # (when runtime_source=internal) installs the runtime from here; it must NEVER
+ # reach public npm (@github/copilot-sdk is a live public package).
+ FEED_URL: https://pkgs.dev.azure.com/devdiv/_packaging/copilot-canary/npm/registry/
+ # Azure DevOps resource ID used to mint an ADO access token for the feed.
+ ADO_RESOURCE: 499b84ac-1321-427f-aa17-267ca6975798
+
+on:
+ workflow_dispatch:
+ inputs:
+ runtime_version:
+ description: "Exact github/copilot-cli release (public) or @github/copilot package version (internal)"
+ required: true
+ type: string
+ runtime_source:
+ description: "Where to install the runtime from"
+ required: true
+ type: choice
+ options:
+ - public
+ - internal
+ default: public
+ mode:
+ description: "publish (tests must pass), publish-force (publish even if tests fail), or tests-only (run gate, never publish)"
+ required: false
+ type: choice
+ default: publish
+ options:
+ - publish
+ - publish-force
+ - tests-only
+ repository_dispatch:
+ types: [runtime-canary]
+
+permissions:
+ contents: read
+ id-token: write
+
+# Serialize runs per ref so two overlapping canary runs can't race the feed
+# publish. cancel-in-progress: false — never kill an in-flight publish.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: false
+
+jobs:
+ resolve:
+ name: "Resolve runtime inputs"
+ if: github.event.repository.fork == false
+ runs-on: ubuntu-latest
+ permissions: {}
+ outputs:
+ RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }}
+ RUNTIME_SOURCE: ${{ steps.normalize.outputs.RUNTIME_SOURCE }}
+ PUBLISH_MODE: ${{ steps.normalize.outputs.PUBLISH_MODE }}
+ steps:
+ # Normalize whichever trigger fired into a single (RUNTIME_VERSION,
+ # RUNTIME_SOURCE, PUBLISH_MODE) triple that every downstream step
+ # references. workflow_dispatch reads the human-supplied inputs;
+ # repository_dispatch reads client_payload and forces source=internal
+ # (a runtime canary only exists on the feed), defaulting mode to publish.
+ - name: Normalize inputs
+ id: normalize
+ env:
+ EVENT_NAME: ${{ github.event_name }}
+ INPUT_VERSION: ${{ inputs.runtime_version }}
+ INPUT_SOURCE: ${{ inputs.runtime_source }}
+ INPUT_MODE: ${{ inputs.mode }}
+ PAYLOAD_VERSION: ${{ github.event.client_payload.runtime_version }}
+ PAYLOAD_SOURCE: ${{ github.event.client_payload.runtime_source }}
+ PAYLOAD_MODE: ${{ github.event.client_payload.mode }}
+ run: |
+ set -euo pipefail
+ case "$EVENT_NAME" in
+ workflow_dispatch)
+ VERSION="$INPUT_VERSION"
+ SOURCE="$INPUT_SOURCE"
+ MODE="$INPUT_MODE"
+ ;;
+ repository_dispatch)
+ VERSION="$PAYLOAD_VERSION"
+ # A runtime canary only ever exists on the internal feed.
+ SOURCE="${PAYLOAD_SOURCE:-internal}"
+ MODE="${PAYLOAD_MODE:-publish}"
+ ;;
+ *)
+ echo "::error::Unsupported event '$EVENT_NAME'."
+ exit 1
+ ;;
+ esac
+ if [ -z "$VERSION" ]; then echo "::error::Could not determine runtime version."; exit 1; fi
+ if [ -z "$SOURCE" ]; then SOURCE="public"; fi
+ case "$SOURCE" in
+ public|internal) ;;
+ *) echo "::error::Invalid runtime source '$SOURCE'. Expected one of: public, internal."; exit 1 ;;
+ esac
+ if [ -z "$MODE" ]; then MODE="publish"; fi
+ case "$MODE" in
+ publish|publish-force|tests-only) ;;
+ *) echo "::error::Invalid publish mode '$MODE'. Expected one of: publish, publish-force, tests-only."; exit 1 ;;
+ esac
+ echo "Resolved RUNTIME_VERSION=$VERSION RUNTIME_SOURCE=$SOURCE PUBLISH_MODE=$MODE"
+ echo "RUNTIME_VERSION=$VERSION" >> "$GITHUB_OUTPUT"
+ echo "RUNTIME_SOURCE=$SOURCE" >> "$GITHUB_OUTPUT"
+ echo "PUBLISH_MODE=$MODE" >> "$GITHUB_OUTPUT"
+
+ - name: Validate runtime version (semver)
+ env:
+ RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }}
+ run: |
+ if [[ ! "$RUNTIME_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then
+ echo "::error::Invalid runtime version '$RUNTIME_VERSION'. Expected semver (e.g. 1.0.69 or 1.0.70-canary.abc123)."
+ exit 1
+ fi
+
+ test:
+ name: "E2E tests (${{ matrix.os }})"
+ needs: resolve
+ if: github.event.repository.fork == false
+ environment: cicd
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ env:
+ POWERSHELL_UPDATECHECK: Off
+ RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }}
+ RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }}
+ defaults:
+ run:
+ shell: bash
+ working-directory: ./nodejs
+ steps:
+ - uses: actions/checkout@v6.0.2
+
+ - uses: actions/setup-node@v6
+ with:
+ cache: "npm"
+ cache-dependency-path: "./nodejs/package-lock.json"
+ node-version: 22
+
+ - name: Install SDK dependencies
+ run: npm ci --ignore-scripts
+
+ - name: Install test harness dependencies
+ working-directory: ./test/harness
+ run: npm ci --ignore-scripts
+
+ - name: Azure Login (OIDC -> id-cpd-ci)
+ if: env.RUNTIME_SOURCE == 'internal'
+ uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
+ with:
+ client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci
+ tenant-id: "${{ vars.CPD_ID_TENANT_ID }}"
+ allow-no-subscriptions: true
+
+ # Route ONLY @github/* (the runtime + its platform packages) to the
+ # internal feed via a scoped registry. All other deps (e.g. detect-libc)
+ # still resolve from public npm. A global --registry would break because
+ # detect-libc is not on the feed.
+ - name: Configure canary feed (.npmrc)
+ if: env.RUNTIME_SOURCE == 'internal'
+ run: |
+ set -euo pipefail
+ TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)"
+ echo "::add-mask::$TOKEN"
+ # Derive the protocol-relative auth scopes from FEED_URL so the feed
+ # name lives in exactly one place (the workflow-level env).
+ FEED_AUTH_REGISTRY="${FEED_URL#https:}"
+ FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}"
+ NPMRC="$(printf '%s\n' \
+ "@github:registry=${FEED_URL}" \
+ "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \
+ "${FEED_AUTH_BASE}:_authToken=${TOKEN}")"
+ printf '%s\n' "$NPMRC" > .npmrc
+ echo "Wrote scoped @github registry .npmrc to ./nodejs"
+
+ - name: Override runtime version
+ run: |
+ set -euo pipefail
+ if [ "$RUNTIME_SOURCE" = "internal" ]; then
+ echo "Installing internal @github/copilot@${RUNTIME_VERSION}"
+ npm install "@github/copilot@${RUNTIME_VERSION}" --save-exact --ignore-scripts
+ node scripts/set-cli-version.js "$RUNTIME_VERSION" --npm-package
+ else
+ echo "Pinning github/copilot-cli release ${RUNTIME_VERSION}"
+ node scripts/set-cli-version.js "$RUNTIME_VERSION"
+ npm install --ignore-scripts
+ fi
+
+ - name: Verify release runtime
+ run: |
+ set -euo pipefail
+ runtime_path=$(npm run --silent prepare:runtime -- --print-path)
+ node -e "
+ const fs = require('node:fs');
+ const path = require('node:path');
+ const runtime = process.argv[1];
+ const runtimeStat = fs.statSync(runtime);
+ if (!runtimeStat.isFile()) throw new Error('Runtime wrapper is not a file');
+ if (process.platform !== 'win32' && (runtimeStat.mode & 0o111) === 0) {
+ throw new Error('Runtime wrapper is not executable');
+ }
+ if (!fs.statSync(path.join(path.dirname(runtime), 'runtime.node')).isFile()) {
+ throw new Error('runtime.node is not adjacent to the runtime wrapper');
+ }
+ " "$runtime_path"
+ legacy_path=$(npm run --silent prepare:runtime -- --print-legacy-path)
+ node "$legacy_path" --version | grep -F "$RUNTIME_VERSION"
+ echo "COPILOT_CLI_PATH=$runtime_path" >> "$GITHUB_ENV"
+
+ - name: Build SDK
+ run: npm run build
+
+ - name: Warm up PowerShell
+ if: runner.os == 'Windows'
+ run: pwsh.exe -Command "Write-Host 'PowerShell ready'"
+
+ - name: Run Node.js SDK e2e tests
+ env:
+ COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }}
+ run: npm test
+
+ publish:
+ name: "Publish SDK canary (internal feed)"
+ needs: [resolve, test]
+ # Publish runs only when the gate permits it. Mode governs behavior:
+ # - tests-only: never publish (skips this job entirely).
+ # - publish: publish only when the e2e gate is green (the default for both
+ # the human and automated triggers).
+ # - publish-force: publish even on a non-green gate — a human-acknowledged
+ # flake override, audited via the ::warning:: step below and the run actor.
+ # publish-force only skips the e2e *signal* — the publish job still runs the
+ # build (so a broken build can't publish) and enforces the feed-only guards.
+ if: >
+ !cancelled() &&
+ github.event.repository.fork == false &&
+ needs.resolve.result == 'success' &&
+ needs.resolve.outputs.PUBLISH_MODE != 'tests-only' &&
+ (needs.test.result == 'success' ||
+ needs.resolve.outputs.PUBLISH_MODE == 'publish-force')
+ environment: cicd
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
+ env:
+ RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }}
+ RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }}
+ defaults:
+ run:
+ shell: bash
+ working-directory: ./nodejs
+ steps:
+ - name: Warn — publishing despite failed e2e gate (publish-force)
+ # always() so this audit is never skipped by prior-step status; it fires
+ # specifically when publish proceeded on a non-green gate via publish-force.
+ # Runs at the workspace root because it executes before checkout, so the
+ # job's default working-directory (./nodejs) does not exist yet.
+ if: always() && needs.test.result != 'success' && needs.resolve.outputs.PUBLISH_MODE == 'publish-force'
+ working-directory: ${{ github.workspace }}
+ run: |
+ echo "::warning title=e2e gate bypassed::Publishing SDK canary despite a non-passing e2e gate (test job result: ${{ needs.test.result }}) via publish-force. Triggered by '${{ github.actor }}' through '${{ github.event_name }}'. The e2e signal was bypassed; build + feed-only guards still apply."
+
+ - uses: actions/checkout@v6.0.2
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+
+ # Default public registry: installs build deps and the currently pinned
+ # runtime. Do NOT write any feed .npmrc or scoped @github:registry line
+ # here, or npm ci would try to fetch the runtime from the upstream-less
+ # feed and 404.
+ - name: Install SDK dependencies
+ run: npm ci --ignore-scripts
+
+ - name: Compute SDK canary version
+ id: sdkver
+ env:
+ RUN_NUMBER: ${{ github.run_number }}
+ SHA: ${{ github.sha }}
+ run: |
+ set -euo pipefail
+ SHORT_SHA="${SHA:0:7}"
+ # Base the canary on the NEXT patch of the public SDK latest so canaries
+ # correlate with public releases: they sort ABOVE the current public
+ # latest and BELOW the eventual real release of that next patch (a
+ # prerelease of X.Y.Z always sorts below X.Y.Z), so a canary can never
+ # shadow the real release when it ships.
+ # Reuse the repo's own version helper (scripts/get-version.js) so this
+ # stays consistent with publish.yml: `current` returns the latest public
+ # dist-tag version, read-only from public npm (never the feed), then
+ # we bump the patch ourselves to keep strict patch+1 semantics.
+ PUBLIC_LATEST="$(node scripts/get-version.js current || true)"
+ BASE="${PUBLIC_LATEST%%-*}"; BASE="${BASE%%+*}"
+ if [[ "$BASE" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
+ NEXT="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$(( BASH_REMATCH[3] + 1 ))"
+ else
+ echo "::error::Could not resolve public SDK latest version (got '$PUBLIC_LATEST'); refusing to publish a canary with an unknown base."
+ exit 1
+ fi
+ SDK_VERSION="${NEXT}-canary.${RUN_NUMBER}.g${SHORT_SHA}"
+ if [[ ! "$SDK_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then
+ echo "::error::Computed SDK canary version '$SDK_VERSION' is not valid semver."
+ exit 1
+ fi
+ echo "SDK canary version: $SDK_VERSION"
+ echo "SDK_VERSION=$SDK_VERSION" >> "$GITHUB_OUTPUT"
+
+ - name: Set package and runtime versions
+ env:
+ SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }}
+ run: |
+ set -euo pipefail
+ npm version "$SDK_VERSION" --no-git-tag-version --allow-same-version
+ if [ "$RUNTIME_SOURCE" = "internal" ]; then
+ npm pkg set "dependencies.@github/copilot=$RUNTIME_VERSION"
+ node scripts/set-cli-version.js "$RUNTIME_VERSION" --npm-package
+ else
+ node scripts/set-cli-version.js "$RUNTIME_VERSION"
+ fi
+ echo "Pinned github/copilot-cli release to $(npm pkg get copilotCliVersion)"
+
+ - name: Build SDK
+ run: npm run build
+
+ - name: Package public release runtimes
+ if: env.RUNTIME_SOURCE == 'public'
+ run: npm run pack:release
+
+ - name: Azure Login (OIDC -> id-cpd-ci)
+ uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
+ with:
+ client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci
+ tenant-id: "${{ vars.CPD_ID_TENANT_ID }}"
+ allow-no-subscriptions: true
+
+ # Auth-only .npmrc: just the two token lines, NO scoped registry line.
+ # The publish target is supplied explicitly via publishConfig + --registry.
+ - name: Configure feed auth (.npmrc)
+ run: |
+ set -euo pipefail
+ TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)"
+ echo "::add-mask::$TOKEN"
+ # Derive the protocol-relative auth scopes from FEED_URL (single source
+ # of truth). NO scoped @github:registry line here — publish target is
+ # supplied explicitly via publishConfig + --registry.
+ FEED_AUTH_REGISTRY="${FEED_URL#https:}"
+ FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}"
+ printf '%s\n' \
+ "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \
+ "${FEED_AUTH_BASE}:_authToken=${TOKEN}" > .npmrc
+ echo "Wrote auth-only .npmrc to ./nodejs"
+
+ # Belt and suspenders (2 of 3): pin the publish target in the package too.
+ - name: Set publishConfig registry
+ run: npm pkg set "publishConfig.registry=$FEED_URL"
+
+ # Belt and suspenders (3 of 3): fail loudly unless the effective publish
+ # target is the internal feed. Guards against ever reaching public npm.
+ - name: Assert publish target is the internal feed
+ run: |
+ set -euo pipefail
+ EFFECTIVE="$(npm pkg get publishConfig.registry | tr -d '"')"
+ echo "Effective publishConfig.registry: $EFFECTIVE"
+ if [ "$EFFECTIVE" != "$FEED_URL" ]; then
+ echo "::error::publishConfig.registry ('$EFFECTIVE') is not the internal feed ('$FEED_URL'). Refusing to publish."
+ exit 1
+ fi
+
+ - name: Publish SDK canary to internal feed
+ run: |
+ set -euo pipefail
+ if [ "$RUNTIME_SOURCE" = "internal" ]; then
+ node scripts/npm-release.js publish . canary "$FEED_URL" azure
+ exit
+ fi
+ shopt -s nullglob
+ TARBALLS=(./github-copilot-sdk-*.tgz)
+ if [ "${#TARBALLS[@]}" -ne 9 ]; then
+ echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}."
+ exit 1
+ fi
+ MAIN_TARBALL=""
+ for TARBALL in "${TARBALLS[@]}"; do
+ PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)"
+ if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then
+ MAIN_TARBALL="$TARBALL"
+ else
+ node scripts/npm-release.js publish "$TARBALL" canary "$FEED_URL" azure
+ fi
+ done
+ if [ -z "$MAIN_TARBALL" ]; then
+ echo "::error::Main @github/copilot-sdk tarball not found."
+ exit 1
+ fi
+ node scripts/npm-release.js publish "$MAIN_TARBALL" canary "$FEED_URL" azure
+
+ - name: Summarize published canary
+ env:
+ SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }}
+ run: |
+ set -euo pipefail
+ {
+ echo "## SDK canary published"
+ echo ""
+ echo "| | |"
+ echo "| --- | --- |"
+ if [ "$RUNTIME_SOURCE" = "public" ]; then
+ echo "| Runtime consumed | \`github/copilot-cli@${RUNTIME_VERSION}\` release assets |"
+ else
+ echo "| Runtime consumed | \`@github/copilot@${RUNTIME_VERSION}\` |"
+ fi
+ echo "| Canary SDK produced | \`@github/copilot-sdk@${SDK_VERSION}\` |"
+ echo "| Feed | ${FEED_URL} |"
+ } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/sdk-consistency-review.lock.yml b/.github/workflows/sdk-consistency-review.lock.yml
index bc33be9ad1..cf977b5d53 100644
--- a/.github/workflows/sdk-consistency-review.lock.yml
+++ b/.github/workflows/sdk-consistency-review.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"fb73d13f101fc375308576a64180f63934cc9e8306cb6ef6303f1b9788d9df28","body_hash":"cc60c817de34cdb662ae4c091203c67a5ef240ca0165b0cd26a842f03b22614f","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}],"has_pull_request":true}
-# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"79b4a25722897db4bbdb7fa862f579bd3c37e12d0ce147e46d618ab79bce67c4","body_hash":"cc60c817de34cdb662ae4c091203c67a5ef240ca0165b0cd26a842f03b22614f","compiler_version":"v0.88.2","strict":true,"agent_id":"copilot","agent_model":"${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}","detection_agent_id":"copilot","detection_agent_model":"${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}","engine_versions":{"copilot":"1.0.80"}}
+# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"9271a1804551c0dc4fb0085a97979950aa2f8489","version":"v0.88.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12","digest":"sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12","digest":"sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12","digest":"sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.15","digest":"sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"has_pull_request":true,"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","create_pull_request_review_comment","missing_data","missing_tool","noop"]}]}
+# This file was automatically generated by gh-aw (v0.88.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,7 +26,7 @@
# Reviews PRs to ensure features are implemented consistently across all SDK language implementations
#
# Secrets used:
-# - COPILOT_GITHUB_TOKEN
+# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -34,22 +34,20 @@
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
-# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
-# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+# - github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c
-# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
-# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83
-# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b
-# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
+# - ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32
+# - ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
+# - ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e
+# - ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e
+# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "SDK Consistency Review Agent"
on:
@@ -88,9 +86,20 @@ concurrency:
run-name: "SDK Consistency Review Agent"
+env:
+ OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
+ OTEL_SERVICE_NAME: gh-aw.sdk-consistency-review
+ OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=SDK%20Consistency%20Review%20Agent,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
+ OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
+ GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
+ GH_AW_OTLP_IF_MISSING: ignore
+
jobs:
activation:
- if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id
+ if: >
+ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) &&
+ ((github.event_name != 'pull_request' && github.event_name != 'pull_request_review') || github.event.pull_request.stack == null ||
+ github.event.pull_request.stack.position == github.event.pull_request.stack.size)
runs-on: ubuntu-slim
permissions:
actions: read
@@ -103,6 +112,7 @@ jobs:
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
+ daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
@@ -118,7 +128,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -126,34 +136,39 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Generate agentic run info
id: generate_aw_info
env:
GH_AW_INFO_ENGINE_ID: "copilot"
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
- GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AGENT_VERSION: "1.0.73"
- GH_AW_INFO_CLI_VERSION: "v0.83.1"
+ GH_AW_INFO_MODEL: "${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AGENT_VERSION: "1.0.80"
+ GH_AW_INFO_CLI_VERSION: "v0.88.2"
GH_AW_INFO_WORKFLOW_NAME: "SDK Consistency Review Agent"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_COMPILED_STRICT: "true"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
@@ -175,9 +190,11 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs');
+ const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
@@ -195,38 +212,37 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs');
+ const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github
.agents
- .antigravity
.claude
.codex
.gemini
- .opencode
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -235,38 +251,47 @@ jobs:
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.83.1"
+ GH_AW_COMPILED_VERSION: "v0.88.2"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
+ const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Compute current body text
id: sanitized
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,s.symcb.com,s.symcd.com,security.ubuntu.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');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs');
+ const { main } = require(path.join(actionsDir, 'compute_text.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -277,77 +302,38 @@ jobs:
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }}
- # poutine:ignore untrusted_checkout_exec
- run: |
- bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
- {
- cat << 'GH_AW_PROMPT_96d45caa4ffc7593_EOF'
-
- GH_AW_PROMPT_96d45caa4ffc7593_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_96d45caa4ffc7593_EOF'
-
- Tools: add_comment, create_pull_request_review_comment(max:10), missing_tool, missing_data, noop
-
- GH_AW_PROMPT_96d45caa4ffc7593_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_96d45caa4ffc7593_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if github.actor}}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if github.repository}}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if github.workspace}}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
- - **issue-number**: #__GH_AW_EXPR_802A9F6A__
- {{/if}}
- {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
- - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
- {{/if}}
- {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
- - **pull-request-number**: #__GH_AW_EXPR_463A214A__
- {{/if}}
- {{#if github.event.comment.id || github.aw.context.comment_id}}
- - **comment-id**: __GH_AW_EXPR_FF1D34CE__
- {{/if}}
- {{#if github.run_id}}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_96d45caa4ffc7593_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_96d45caa4ffc7593_EOF'
-
- {{#runtime-import .github/workflows/sdk-consistency-review.md}}
- GH_AW_PROMPT_96d45caa4ffc7593_EOF
- } > "$GH_AW_PROMPT"
+ GH_AW_PROMPT_CONTENT_0000: "\n"
+ GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, create_pull_request_review_comment(max:10), missing_tool, missing_data, noop\n"
+ GH_AW_PROMPT_CONTENT_0002: "\n"
+ GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n"
+ GH_AW_PROMPT_CONTENT_0004: "\n"
+ GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/sdk-consistency-review.md}}\n"
+ with:
+ script: |
+ const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
+ await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_EXPR_A0E5D436: ${{ github.event.pull_request.number || inputs.pr_number }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -361,10 +347,12 @@ jobs:
GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+ const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
@@ -385,16 +373,20 @@ jobs:
});
- name: Validate prompt placeholders
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- # poutine:ignore untrusted_checkout_exec
- run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
+ - name: Stage prompt files for artifact upload
+ run: |
+ mkdir -p /tmp/gh-aw/aw-prompts
+ cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
- if: success()
+ if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: activation
@@ -421,12 +413,19 @@ jobs:
copilot-requests: write
issues: read
pull-requests: read
+ timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_PR_HEAD_BASE_BRANCH: ""
+ GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
+ GH_AW_PR_HEAD_BASE_REF: ""
+ GH_AW_PR_HEAD_BASE_REPO: ""
+ GH_AW_PR_HEAD_BASE_SHA: ""
+ GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: sdkconsistencyreview
outputs:
@@ -440,7 +439,10 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
+ missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
+ missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
@@ -448,11 +450,12 @@ jobs:
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
+ shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -461,19 +464,28 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
+ env:
+ GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
+ if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
+ echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
+ fi
{
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
+ - name: Check OTLP telemetry configuration
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -503,16 +515,19 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
await main();
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
+ GH_AW_COMPILED_VERSION: v0.88.2
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -521,13 +536,15 @@ jobs:
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
with:
script: |
- const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Restore agent config folders from base branch
if: steps.checkout-pr.outcome == 'success'
env:
- GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi"
- GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
+ GH_AW_AGENT_FOLDERS: ".agents .github"
+ GH_AW_AGENT_FILES: "AGENTS.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- name: Restore inline sub-agents from activation artifact
env:
@@ -539,15 +556,26 @@ jobs:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- - name: Generate Safe Outputs Config
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f ghcr.io/github/gh-aw-mcpg:v0.4.15@sha256:60cd97533e93d8e7be36b979c0f08a70846189bda6190f28bbd6d427bc0d9b6e ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
+ - name: Prepare Safe Outputs Directories
run: |
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_05b64a640c1d5c26_EOF'
- {"add_comment":{"hide_older_comments":true,"max":1},"create_pull_request_review_comment":{"max":10,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_05b64a640c1d5c26_EOF
+ - name: Generate Safe Outputs Config
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
+ GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
+ GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"create_pull_request_review_comment\":{\"max\":10,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'create_files.cjs'));
+ await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -570,9 +598,18 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
+ "comment_id": {
+ "optionalPositiveInteger": true
+ },
"item_number": {
"issueOrPRNumber": true
},
+ "pr": {
+ "issueOrPRNumber": true
+ },
+ "pr_number": {
+ "issueOrPRNumber": true
+ },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -580,6 +617,16 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
+ },
+ "target": {
+ "type": "string",
+ "enum": [
+ "status"
+ ]
+ },
+ "temporary_id": {
+ "type": "string",
+ "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -697,9 +744,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
+ const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
@@ -708,6 +757,7 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
+ GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -715,33 +765,45 @@ jobs:
run: |
set -eo pipefail
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
+ if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
+ GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
+ cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
+ fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
- MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${MCP_GATEWAY_API_KEY}"
- export MCP_GATEWAY_API_KEY
+ MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
+ export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
+ export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
+ export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
+ export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
+ export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
+ export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
+ export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.15'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_46604863f3d8e286_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.6.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -774,6 +836,14 @@ jobs:
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
+ "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
+ "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
+ "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
+ "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
+ "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
+ "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
+ "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
+ "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -785,7 +855,7 @@ jobs:
"accept": [
"*"
],
- "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }}
+ "sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
@@ -793,25 +863,32 @@ jobs:
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "startupTimeout": 120
+ "startupTimeout": 120,
+ "opentelemetry": {
+ "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
+ "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
+ "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
+ }
}
}
- GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF
+ GH_AW_MCP_CONFIG_46604863f3d8e286_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs');
+ const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
@@ -827,18 +904,33 @@ jobs:
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
+ trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"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\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="1000"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\",\"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\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
@@ -856,22 +948,28 @@ jobs:
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ GH_AW_AWF_ENGINE_NAME=copilot \
+ GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
+ GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
+ GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
+ bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
+ GH_AW_MODEL_FALLBACK: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_PHASE: agent
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: 15
- GH_AW_VERSION: v0.83.1
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -892,7 +990,18 @@ jobs:
if: always()
id: detect-agent-errors
continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
+ env:
+ GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
+ GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 15
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
+ await main();
- name: Configure Git credentials
env:
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -908,7 +1017,7 @@ jobs:
continue-on-error: true
env:
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
- MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
@@ -917,9 +1026,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
@@ -942,14 +1053,16 @@ 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.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
@@ -959,9 +1072,11 @@ jobs:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
@@ -969,9 +1084,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
@@ -985,9 +1102,11 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
@@ -995,16 +1114,41 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs');
+ const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
+ - name: Generate observability summary
+ if: always()
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
+ await main(core);
- name: Write agent output placeholder if missing
if: always()
run: |
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Small dedicated copy of the agent output so safe-output processing
+ # survives a failed or timed-out upload of the larger agent artifact
+ - name: Upload agent output fallback artifact
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: agent-output-fallback
+ path: |
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/safeoutputs.jsonl
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1021,6 +1165,8 @@ jobs:
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/agent/
/tmp/gh-aw/github_rate_limits.jsonl
+ /tmp/gh-aw/otel.jsonl
+ /tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
@@ -1040,10 +1186,10 @@ jobs:
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
- needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true')
+ needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
- contents: read
+ actions: read
issues: write
pull-requests: write
concurrency:
@@ -1060,7 +1206,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1069,15 +1215,16 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1085,42 +1232,29 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- - name: Download safe outputs items manifest
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Download detection artifact
+ id: download-detection-artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: safe-outputs-items
+ pattern: safe-outputs-items
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
- run: |
- mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection
- echo "Usage artifact source file status:"
- for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do
- [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file"
- done
- [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true
- [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true
- [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true
- [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true
- [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true
- [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true
- [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true
- [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl
- [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl
- mkdir -p /tmp/gh-aw/usage/activity
- node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs"
- find /tmp/gh-aw/usage -type f -print | sort
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
@@ -1134,6 +1268,8 @@ jobs:
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
+ /tmp/gh-aw/usage/graders/grader_manifest.json
+ /tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
@@ -1156,9 +1292,11 @@ jobs:
with:
github-token: ${{ github.token }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs');
+ const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
@@ -1189,7 +1327,7 @@ jobs:
GH_AW_TRACKER_ID: "sdk-consistency-review"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
@@ -1197,9 +1335,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
@@ -1215,9 +1355,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record missing tool
id: missing_tool
@@ -1231,9 +1373,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
@@ -1247,9 +1391,11 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
+ const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
@@ -1263,7 +1409,7 @@ jobs:
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "sdk-consistency-review"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
+ GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
@@ -1277,6 +1423,10 @@ jobs:
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
+ GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
+ GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
+ GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
+ GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
@@ -1292,9 +1442,31 @@ jobs:
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
+ await main();
+ - name: Report failed jobs
+ id: report_failed_jobs
+ if: always()
+ 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_WORKFLOW_NAME: "SDK Consistency Review Agent"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/sdk-consistency-review.md"
+ GH_AW_TRACKER_ID: "sdk-consistency-review"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_REPORT_FAILED_JOBS: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
await main();
detection:
@@ -1306,6 +1478,7 @@ jobs:
permissions:
contents: read
copilot-requests: write
+ timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
@@ -1316,7 +1489,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1325,15 +1498,22 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Download activation artifact
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1341,10 +1521,12 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# --- Threat Detection ---
@@ -1353,7 +1535,7 @@ jobs:
rm -rf /tmp/gh-aw/sandbox/firewall/logs
rm -rf /tmp/gh-aw/sandbox/firewall/audit
- name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.12@sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.12@sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32 ghcr.io/github/gh-aw-firewall/squid:0.28.12@sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1377,21 +1559,7 @@ jobs:
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
- mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
- rm -f /tmp/gh-aw/agent_usage.json
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
- echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
- fi
- cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
- for f in /tmp/gh-aw/aw-*.patch; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- for f in /tmp/gh-aw/aw-*.bundle; do
- [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- done
- echo "Prepared threat detection files:"
- ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1399,82 +1567,53 @@ jobs:
WORKFLOW_NAME: "SDK Consistency Review Agent"
WORKFLOW_DESCRIPTION: "Reviews PRs to ensure features are implemented consistently across all SDK language implementations"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ const { main } = require(path.join(actionsDir, 'setup_threat_detection.cjs'));
await main();
- name: Ensure threat-detection directory and log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- with:
- node-version: '24'
- package-manager-cache: false
+ - name: Install AWF binary
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.12 --rootless
- name: Install GitHub Copilot CLI
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38
- - name: Execute GitHub Copilot CLI
+ GH_AW_COMPILED_VERSION: v0.88.2
+ - name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
run: |
- set -o pipefail
- printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
- trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT
- mkdir -p "$HOME/.copilot"
- printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
- export XDG_CONFIG_HOME="$HOME"
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
- export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
- GH_AW_DOCKER_HOST=""
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- GH_AW_DOCKER_HOST="${DOCKER_HOST}"
- fi
- if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
- _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
- fi
- GH_AW_TOOL_CACHE_MOUNT=""
- GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
- if [ -d "$GH_AW_TOOL_CACHE" ]; then
- if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
- GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
- fi
- fi
- # shellcheck disable=SC1003,SC2016,SC2086
- 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
+ - name: Execute threat detection with AWF
+ id: detection_agentic_execution
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}
+ GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
+ GH_AW_MODEL_FALLBACK: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.83.1
+ GH_AW_TIMEOUT_MINUTES: 10
+ GH_AW_VERSION: v0.88.2
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -1490,58 +1629,105 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- - name: Parse threat detection token usage for step summary
- id: parse_detection_token_usage
- if: always()
+ WORKFLOW_NAME: "SDK Consistency Review Agent"
+ WORKFLOW_DESCRIPTION: "Reviews PRs to ensure features are implemented consistently across all SDK language implementations"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
+ run: |
+ set -o pipefail
+ printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
+ GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
+ if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
+ echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
+ exit 127
+ fi
+ GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
+ mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
+ if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
+ cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
+ fi
+ chmod 755 "$GH_AW_COPILOT_BIN"
+
+ (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
+ GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
+ if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
+ GH_AW_MAX_AI_CREDITS="400"
+ fi
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.12/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.12,squid=sha256:52c34aca98d2a6833c329f1505912a6949c4fda16618c010c979bd59ea99254f,agent=sha256:390051be4ed1847f774fd8980b61d3a3523574c0175d00c3fc7cdf2002a88202,api-proxy=sha256:d7d533d87c80d87ff91ac0e21e9299055c3beedff1536262b97ed700fb065a32,cli-proxy=sha256:5250629d48eaedfedf2e948785228e8da29eec2a83cbab58ea0751c14a7b021d\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
+ export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
+ GH_AW_DOCKER_HOST=""
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ GH_AW_DOCKER_HOST="${DOCKER_HOST}"
+ fi
+ if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
+ _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
+ printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ fi
+ GH_AW_TOOL_CACHE_MOUNT=""
+ GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
+ if [ -d "$GH_AW_TOOL_CACHE" ]; then
+ if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
+ GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
+ fi
+ fi
+ # shellcheck disable=SC1003,SC2016,SC2086
+ 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" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ - name: Render detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
- env:
- GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
+ const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- - name: Upload threat detection log
+ - name: Copy detection firewall logs
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ continue-on-error: true
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
+ if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
+ if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
+ - name: Upload threat detection artifact
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detection
- path: /tmp/gh-aw/threat-detection/detection.log
+ path: |
+ /tmp/gh-aw/threat-detection/detection_result.json
+ /tmp/gh-aw/threat-detection/sandbox/firewall/logs/
+ /tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- - name: Parse and conclude threat detection
- id: detection_conclusion
+ - name: Parse threat detection token usage for step summary
+ id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
+ with:
+ script: |
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
+ await main();
+ - name: Conclude threat detection
+ id: detection_conclusion
+ if: always()
+ continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- with:
- script: |
- try {
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- } catch (loadErr) {
- const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false';
- const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure';
- const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr));
- core.error(msg);
- core.setOutput('reason', 'parse_error');
- if (continueOnError && !detectionExecutionFailed) {
- core.warning('\u26A0\uFE0F ' + msg);
- core.setOutput('conclusion', 'warning');
- core.setOutput('success', 'false');
- } else {
- core.setOutput('conclusion', 'failure');
- core.setOutput('success', 'false');
- core.setFailed(msg);
- }
- }
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
safe_outputs:
needs:
@@ -1551,7 +1737,6 @@ jobs:
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
runs-on: ubuntu-slim
permissions:
- contents: read
issues: write
pull-requests: write
timeout-minutes: 45
@@ -1564,8 +1749,8 @@ jobs:
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
GH_AW_ENGINE_ID: "copilot"
- GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- GH_AW_ENGINE_VERSION: "1.0.73"
+ GH_AW_ENGINE_MODEL: "${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}"
+ GH_AW_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_TRACKER_ID: "sdk-consistency-review"
@@ -1579,12 +1764,20 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
+ process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
+ process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
+ process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
+ process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
+ process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
+ process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1
+ uses: github/gh-aw-actions/setup@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1593,15 +1786,18 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.73"
- GH_AW_INFO_AWF_VERSION: "v0.27.38"
+ GH_AW_INFO_VERSION: "1.0.80"
+ GH_AW_INFO_AWF_VERSION: "v0.28.12"
GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Mask OTLP telemetry headers
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- name: agent
+ pattern: "{agent,agent-output-fallback}"
+ merge-multiple: true
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1609,7 +1805,9 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/
find "/tmp/gh-aw/" -type f -print
- echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ if [ -f "/tmp/gh-aw/agent_output.json" ]; then
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ fi
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
@@ -1625,16 +1823,18 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,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,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "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,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,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: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"create_pull_request_review_comment\":{\"max\":10,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"create_pull_request_review_comment\":{\"max\":10,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ const path = require('path');
+ const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
+ const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
await main();
- name: Upload Safe Outputs Items
if: always()
@@ -1644,4 +1844,5 @@ jobs:
path: |
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
+ /tmp/gh-aw/safe-output-errors.json
if-no-files-found: ignore
diff --git a/.github/workflows/sdk-consistency-review.md b/.github/workflows/sdk-consistency-review.md
index 550d9349d0..4d1c7e4511 100644
--- a/.github/workflows/sdk-consistency-review.md
+++ b/.github/workflows/sdk-consistency-review.md
@@ -1,6 +1,7 @@
---
description: Reviews PRs to ensure features are implemented consistently across all SDK language implementations
tracker-id: sdk-consistency-review
+model: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}
on:
roles: all
pull_request:
@@ -35,6 +36,10 @@ safe-outputs:
max: 1
hide-older-comments: true
allowed-reasons: [outdated]
+ threat-detection:
+ engine:
+ id: copilot
+ model: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}
timeout-minutes: 15
---
diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml
index 9646366ad5..f1e4d9fca9 100644
--- a/.github/workflows/update-copilot-dependency.yml
+++ b/.github/workflows/update-copilot-dependency.yml
@@ -1,10 +1,10 @@
-name: "Update @github/copilot Dependency"
+name: "Update Copilot CLI Version"
on:
workflow_dispatch:
inputs:
version:
- description: "Target version of @github/copilot (e.g. 0.0.420)"
+ description: "Target github/copilot-cli release version (e.g. 1.0.83-0)"
required: true
type: string
@@ -14,7 +14,7 @@ permissions:
jobs:
update:
- name: "Update @github/copilot to ${{ inputs.version }}"
+ name: "Update Copilot CLI to ${{ inputs.version }}"
runs-on: ubuntu-latest
steps:
- name: Validate version input
@@ -56,17 +56,13 @@ jobs:
toolchain: nightly-2026-04-14
components: rustfmt
- - name: Update @github/copilot in nodejs
+ - name: Update the Node.js CLI release pin
env:
VERSION: ${{ inputs.version }}
working-directory: ./nodejs
- run: npm install "@github/copilot@$VERSION"
-
- - name: Update @github/copilot in test harness
- env:
- VERSION: ${{ inputs.version }}
- working-directory: ./test/harness
- run: npm install "@github/copilot@$VERSION"
+ run: |
+ node scripts/set-cli-version.js "$VERSION"
+ npm install --ignore-scripts
- name: Refresh nodejs/samples lockfile
working-directory: ./nodejs/samples
@@ -91,25 +87,6 @@ jobs:
java-version: "25"
distribution: "microsoft"
- - name: Update @github/copilot in Java codegen
- env:
- VERSION: ${{ inputs.version }}
- working-directory: ./java/scripts/codegen
- run: npm install "@github/copilot@$VERSION"
-
- - name: Update Java POM CLI version property
- env:
- VERSION: ${{ inputs.version }}
- working-directory: ./java
- run: |
- PROP="readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync"
- sed -i -E "s|(<${PROP}>)[^<]*(${PROP}>)|\1^${VERSION}\2|" pom.xml
- # Use fixed-string matching (-F) because npm versions contain regex
- # metacharacters: '^' (caret ranges) and '.' (dots in semver) would
- # otherwise be interpreted as start-of-line and any-char respectively,
- # causing false negatives or spurious matches.
- grep -qF "<${PROP}>^${VERSION}${PROP}>" pom.xml
-
- name: Run Java codegen
working-directory: ./java
run: mvn generate-sources -Pcodegen
@@ -165,22 +142,23 @@ jobs:
exit 0
fi
- git commit -m "Update @github/copilot to $VERSION
+ git commit -m "Update Copilot CLI to $VERSION
- - Updated nodejs and test harness dependencies
+ - Updated the shared CLI release pin
- Re-ran code generators
- Formatted generated code"
git push origin "$BRANCH" --force-with-lease
PR_BODY=$(cat <<'BODY_EOF'
- Automated update of `@github/copilot` to version `PLACEHOLDER_VERSION`.
+ Automated update of the Copilot CLI release to version `PLACEHOLDER_VERSION`.
### Changes
- - Updated `@github/copilot` in `nodejs/package.json` and `test/harness/package.json`
+ - Updated the shared release pin in `nodejs/package.json`
+ - Validated the release assets listed in `SHA256SUMS.txt`
- Re-ran all code generators (`scripts/codegen`)
- Formatted generated output
- - Updated Java codegen dependency, POM property, and regenerated Java types
+ - Regenerated Java types from the pinned CLI release schemas
### Java Handwritten Code Adaptation Plan
@@ -208,7 +186,7 @@ jobs:
### Next steps
When ready, click **Ready for review** to trigger CI checks.
- > Created by the **Update @github/copilot Dependency** workflow.
+ > Created by the **Update Copilot CLI Version** workflow.
BODY_EOF
)
PR_BODY="${PR_BODY//PLACEHOLDER_VERSION/$VERSION}"
@@ -224,7 +202,7 @@ jobs:
else
gh pr create \
--draft \
- --title "Update @github/copilot to $VERSION" \
+ --title "Update Copilot CLI to $VERSION" \
--body "$PR_BODY" \
--base main \
--head "$BRANCH"
diff --git a/.github/workflows/verify-compiled.yml b/.github/workflows/verify-compiled.yml
index 1a3dbb96fd..3cc5110225 100644
--- a/.github/workflows/verify-compiled.yml
+++ b/.github/workflows/verify-compiled.yml
@@ -17,9 +17,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install gh-aw CLI
- uses: github/gh-aw-actions/setup-cli@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10
+ uses: github/gh-aw-actions/setup-cli@9271a1804551c0dc4fb0085a97979950aa2f8489 # v0.88.2
with:
- version: v0.83.1
+ version: v0.88.2
- name: Recompile workflows
# Full-repository compile so the diff check below covers all workflows.
run: gh aw compile
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0184a553db..788b983621 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,14 @@ See [GitHub Releases](https://github.com/github/copilot-sdk/releases) for the fu
## [Unreleased]
+### Feature: cancellation for host-owned external tools
+
+Host-owned external tool callbacks are now cancelled when their runtime request completes or their SDK session terminates. The cancellation primitive is idiomatic per SDK: .NET passes a request token to `AIFunction`, Node.js exposes `ToolInvocation.signal`, Go cancels `ToolInvocation.TraceContext`, Java cancels the returned `CompletableFuture`, Python cancels the handler task, and Rust drops the handler future. Go handlers that retain `TraceContext` for background work must derive a separate lifetime because the invocation context is cancelled when the request ends.
+
+### Feature: declare application identity with client info
+
+Client options now accept optional client info (application name and version, integration name and version) across all six SDKs, exposed idiomatically per language (`clientInfo` in Node.js, `client_info` in Python and Rust, `ClientInfo` in Go and .NET, `setClientInfo` in Java). When set, the SDK forwards it on the `server.connect` handshake so the telemetry the runtime emits on the connection is attributed to the application and its Copilot integration instead of the runtime's own build. All fields are optional, and leaving client info unset keeps the runtime's default attribution. See [Client info](./docs/features/client-info.md).
+
### Feature: Node Agent Factories pagination and run notifications
The experimental Node.js Agent Factories convenience API now supports paginated run history. Existing `session.factory.listRuns()` calls still return the runs array, while calls with `afterSeq`, `beforeSeq`, or `limit` return the full page with cursor and truncation metadata.
@@ -40,6 +48,28 @@ const session = await joinSession({
const token = process.env.GITHUB_TOKEN;
```
+### Feature: early session-event subscription (Rust)
+
+The Rust SDK can now observe every event routed to a session, starting with that session's very first routed event. `Client::prepare_session` and `Client::prepare_resume_session` return an inert `PreparedSession` that owns the session's event channel, so a subscription can be installed *before* any protocol activity begins:
+
+```rust
+let prepared = client.prepare_session(
+ SessionConfig::default().with_event_buffer_capacity(2048),
+)?;
+let mut events = prepared.subscribe();
+let session = prepared.start().await?;
+```
+
+Previously, `Session::subscribe` could only be called on the returned session, so events the runtime emitted while `session.create` / `session.resume` was still in flight were broadcast with no receiver installed and dropped. Ephemeral events such as `session.idle` are not persisted, so they could not be recovered with `getMessages` either.
+
+The guarantee is scoped to *routed* events. For cloud sessions where the server assigns the session ID, the SDK cannot route notifications until the `session.create` response arrives and the ID is known, so events emitted before that point are not routable to any session. Pin `session_id` on the config to get router registration before the RPC, and with it complete pre-response coverage.
+
+`prepare_*` is synchronous and inert: it validates the buffer capacity and allocates a local channel, and performs no router registration, task spawn, or wire activity until `start()` is first polled. `start(self)` consumes the handle and `PreparedSession` is not `Clone`, so a prepared session can never produce two event loops. Dropping an unstarted handle leaves no state behind; dropping a polled `start()` future cancels the startup and unregisters the session, so a retry with the same session ID succeeds. Session registrations now carry an ownership identity, so cleanup removes only the exact registration it owns and an abandoned startup can never evict a same-ID retry (or a session that replaced it).
+
+Both `SessionConfig` and `ResumeSessionConfig` gained a runtime-only `event_buffer_capacity` option (default 512, `Some(0)` rejected as an invalid config). The buffer is finite, so slow subscribers observe `Lagged` rather than applying backpressure; consumers that need a lossless view of a large startup burst must size the buffer accordingly or drain concurrently with `start()`.
+
+`create_session` and `resume_session` are unchanged wrappers over `prepare_*(...)?.start()` with identical RPC sequences and error kinds.
+
### Feature: host-injected managed settings permissions
Session create and resume accept a new optional `managedSettings` option that injects an enterprise permissions policy at session startup, alongside the existing `enableManagedSettings` self-fetch flag. The current contract is permissions-only: `disableBypassPermissionsMode` (the literal `"disable"`), plus `deny`, `ask`, and `allow` rule lists. The layer composes restrictively with any server- or device-level managed settings (deny/ask are unioned, every present allow list must admit a tool, and `disableBypassPermissionsMode` is deny-wins).
diff --git a/docs/features/README.md b/docs/features/README.md
index f97140b784..5ea070a5aa 100644
--- a/docs/features/README.md
+++ b/docs/features/README.md
@@ -20,6 +20,7 @@ These guides cover the capabilities you can add to your Copilot SDK application.
| [Image Input](./image-input.md) | Send images to sessions as attachments |
| [Streaming Events](./streaming-events.md) | Subscribe to real-time session events (40+ event types) |
| [Usage and Billing](./usage-and-billing.md) | Read token counts, context-window utilization, AI credit cost, and account quota |
+| [Client info](./client-info.md) | Declare application and integration identity for runtime telemetry attribution |
| [Steering & Queueing](./steering-and-queueing.md) | Control message delivery—immediate steering vs. sequential queueing |
| [Context Clearing](./context-management.md) | Replace conversation context safely with terminal tools |
| [Session Persistence](./session-persistence.md) | Resume sessions across restarts, manage session storage |
diff --git a/docs/features/client-info.md b/docs/features/client-info.md
new file mode 100644
index 0000000000..f72780e41b
--- /dev/null
+++ b/docs/features/client-info.md
@@ -0,0 +1,241 @@
+# Client info
+
+Client info identifies the application using the Copilot SDK and, when applicable, a specific integration within it. An integration is an identifiable sub-part of the application through which the SDK is used, such as an extension or plugin. Set the optional `clientInfo` client option to attribute runtime telemetry for that connection to your application instead of the runtime's own build.
+
+## When to set client info
+
+Set client info when your SDK application represents a distinct product, service, or integration whose runtime activity should be attributed consistently.
+
+Leave client info unset for scripts, one-off tools, and jobs that do not represent a distinct application. The runtime then keeps its default attribution.
+
+Client info has four optional string fields. Set the fields you know and omit the rest. The SDK includes client info in the `server.connect` handshake only when at least one field has a non-empty value.
+
+| Field | Example | Meaning |
+|---|---|---|
+| `applicationName` | `"vscode"` | Name of the application using the SDK |
+| `applicationVersion` | `"1.124.2"` | Version of the application using the SDK |
+| `integrationName` | `"copilot-chat"` | Name of the extension, plugin, or other application sub-part using the SDK |
+| `integrationVersion` | `"0.54.0"` | Version of that extension, plugin, or application sub-part |
+
+For a standalone application without a distinct integration, set only the application fields. For example, a developer portal could set `applicationName` to `"acme-developer-portal"` and `applicationVersion` to `"2.4.0"`, leaving both integration fields unset.
+
+The SDK sends client info once when it establishes the connection. The identity applies for the lifetime of that connection.
+
+## Configure client info
+
+Pass client info when you create the client:
+
+
+TypeScript
+
+
+```typescript
+import { CopilotClient } from "@github/copilot-sdk";
+
+async function main() {
+ const client = new CopilotClient({
+ clientInfo: {
+ applicationName: "vscode",
+ applicationVersion: "1.124.2",
+ integrationName: "copilot-chat",
+ integrationVersion: "0.54.0",
+ },
+ });
+
+ await client.start();
+}
+
+main();
+```
+
+
+```typescript
+import { CopilotClient } from "@github/copilot-sdk";
+
+const client = new CopilotClient({
+ clientInfo: {
+ applicationName: "vscode",
+ applicationVersion: "1.124.2",
+ integrationName: "copilot-chat",
+ integrationVersion: "0.54.0",
+ },
+});
+
+await client.start();
+```
+
+
+
+
+Python
+
+
+```python
+from copilot import CopilotClient
+
+client = CopilotClient(
+ client_info={
+ "application_name": "vscode",
+ "application_version": "1.124.2",
+ "integration_name": "copilot-chat",
+ "integration_version": "0.54.0",
+ },
+)
+await client.start()
+```
+
+
+
+
+Go
+
+
+```go
+package main
+
+import (
+ "context"
+
+ copilot "github.com/github/copilot-sdk/go"
+)
+
+func main() {
+ ctx := context.Background()
+ client := copilot.NewClient(&copilot.ClientOptions{
+ ClientInfo: &copilot.ClientInfo{
+ ApplicationName: "vscode",
+ ApplicationVersion: "1.124.2",
+ IntegrationName: "copilot-chat",
+ IntegrationVersion: "0.54.0",
+ },
+ })
+ if err := client.Start(ctx); err != nil {
+ return
+ }
+}
+```
+
+
+```go
+client := copilot.NewClient(&copilot.ClientOptions{
+ ClientInfo: &copilot.ClientInfo{
+ ApplicationName: "vscode",
+ ApplicationVersion: "1.124.2",
+ IntegrationName: "copilot-chat",
+ IntegrationVersion: "0.54.0",
+ },
+})
+if err := client.Start(ctx); err != nil {
+ return err
+}
+```
+
+
+
+
+.NET
+
+```csharp
+using GitHub.Copilot;
+
+await using var client = new CopilotClient(new CopilotClientOptions
+{
+ ClientInfo = new CopilotClientInfo
+ {
+ ApplicationName = "vscode",
+ ApplicationVersion = "1.124.2",
+ IntegrationName = "copilot-chat",
+ IntegrationVersion = "0.54.0",
+ },
+});
+
+await client.StartAsync();
+```
+
+
+
+
+Java
+
+
+```java
+import com.github.copilot.CopilotClient;
+import com.github.copilot.rpc.ClientInfo;
+import com.github.copilot.rpc.CopilotClientOptions;
+
+public class ClientInfoExample {
+ public static void main(String[] args) throws Exception {
+ var options = new CopilotClientOptions()
+ .setClientInfo(new ClientInfo()
+ .setApplicationName("vscode")
+ .setApplicationVersion("1.124.2")
+ .setIntegrationName("copilot-chat")
+ .setIntegrationVersion("0.54.0"));
+
+ var client = new CopilotClient(options);
+ client.start().get();
+ }
+}
+```
+
+
+```java
+var options = new CopilotClientOptions()
+ .setClientInfo(new ClientInfo()
+ .setApplicationName("vscode")
+ .setApplicationVersion("1.124.2")
+ .setIntegrationName("copilot-chat")
+ .setIntegrationVersion("0.54.0"));
+
+var client = new CopilotClient(options);
+client.start().get();
+```
+
+
+
+
+Rust
+
+
+```rust
+use github_copilot_sdk::{Client, ClientInfo, ClientOptions};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let _client = Client::start(
+ ClientOptions::new().with_client_info(
+ ClientInfo::new()
+ .with_application_name("vscode")
+ .with_application_version("1.124.2")
+ .with_integration_name("copilot-chat")
+ .with_integration_version("0.54.0"),
+ ),
+ )
+ .await?;
+ Ok(())
+}
+```
+
+
+```rust
+use github_copilot_sdk::{Client, ClientInfo, ClientOptions};
+
+let client = Client::start(
+ ClientOptions::new().with_client_info(
+ ClientInfo::new()
+ .with_application_name("vscode")
+ .with_application_version("1.124.2")
+ .with_integration_name("copilot-chat")
+ .with_integration_version("0.54.0"),
+ ),
+)
+.await?;
+```
+
+
+
+## Notes
+
+* Client info is advisory. The runtime can ignore values that do not match the expected format, such as an invalid version string.
+* Setting client info changes how the runtime attributes its telemetry. It does not change what the runtime records.
+* If every field is unset or empty, the SDK omits client info from the handshake and the runtime keeps its default attribution.
diff --git a/docs/features/hooks.md b/docs/features/hooks.md
index 6a78339901..a3cfc0f503 100644
--- a/docs/features/hooks.md
+++ b/docs/features/hooks.md
@@ -22,13 +22,13 @@ flowchart LR
| Hook | When it fires | What you can do |
| ------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------ |
-| [`onSessionStart`](../hooks/session-lifecycle.md#session-start) | Session begins (new or resumed) | Inject context, load preferences |
+| [`onSessionStart`](../hooks/session-lifecycle.md#session-start-hook) | Session begins (new or resumed) | Inject context, load preferences |
| [`onUserPromptSubmitted`](../hooks/user-prompt-submitted.md) | User sends a message | Rewrite prompts, add context, filter input |
| [`onUserPromptTransformed`](../hooks/user-prompt-transformed.md) | Runtime builds the model prompt | Inspect or replace model-facing content |
| [`onPreToolUse`](../hooks/pre-tool-use.md) | Before a tool executes | Allow / deny / modify the call |
| [`onPostToolUse`](../hooks/post-tool-use.md) | After a tool returns (success only) | Transform results, redact secrets, audit |
| [`onPostToolUseFailure`](../hooks/post-tool-use.md#failure-variant) | After a tool returns a failure | Inject retry guidance, log failures |
-| [`onSessionEnd`](../hooks/session-lifecycle.md#session-end) | Session ends | Clean up, record metrics |
+| [`onSessionEnd`](../hooks/session-lifecycle.md#session-end-hook) | Session ends | Clean up, record metrics |
| [`onErrorOccurred`](../hooks/error-handling.md) | An error is raised | Custom logging, retry logic, alerts |
All hooks are **optional**—register only the ones you need. Returning `null` (or the language equivalent) from any hook tells the SDK to continue with default behavior.
diff --git a/docs/features/session-persistence.md b/docs/features/session-persistence.md
index 3bfff10d0f..869af9b9a4 100644
--- a/docs/features/session-persistence.md
+++ b/docs/features/session-persistence.md
@@ -242,6 +242,7 @@ When resuming a session, you can optionally reconfigure many settings. This is u
| `availableTools` | Restrict which tools are available |
| `excludedTools` | Disable specific tools |
| `provider` | Re-provide BYOK credentials (required for BYOK sessions) |
+| `capi.autoTier` | Override the persisted Auto routing preference |
| `reasoningEffort` | Adjust reasoning effort level |
| `streaming` | Enable/disable streaming responses |
| `workingDirectory` | Change the working directory |
@@ -253,6 +254,63 @@ When resuming a session, you can optionally reconfigure many settings. This is u
| `disabledSkills` | Skills to disable |
| `infiniteSessions` | Configure infinite session behavior |
+### Auto tier persistence
+
+With `model: "auto"`, the optional `capi.autoTier` setting selects an Auto routing preference: `efficiency`, `balance`, or `intelligence`. In Python, use `capi={"auto_tier": "balance"}`. This requires Copilot CLI `1.0.82-1` or later with V2 Auto routing; V1 Auto requests are unchanged.
+
+The runtime persists the selected tier, so applications do not need to resend it on every resume:
+
+* Omitting the tier when creating a session uses the runtime's default routing behavior.
+* A cold resume restores the persisted tier. Supplying an explicit tier overrides the restored value for the new activation.
+* When resuming a session already resident in the runtime, omitting the tier preserves the current selection and supplying the same tier is a no-op. Supplying a different tier requests a safe switch that the runtime applies after the resume succeeds; it cannot change a turn that is already in flight.
+* Older sessions without a persisted tier retain default routing behavior.
+
+Tier selection is not a live model-switch operation. The SDK forwards the preference; the runtime owns persistence and validation.
+
+The `session.start` and `session.resume` events expose the selected tier in their optional `data.autoTier` field (`data.auto_tier` in Python). When no tier is selected, the field is omitted.
+
+### Changing the Auto tier during a session
+
+Call `setAutoTier` to change the routing preference on a live session without changing the selected model. Pass `null` (Python `None`, Go `nil`) to return to the provider's default Auto routing. This requires Copilot CLI `1.0.83-4` or later, which is newer than the `1.0.82-1` needed to select a tier when creating or resuming a session.
+
+```typescript
+const result = await session.setAutoTier("intelligence");
+if (result.status === "pending") {
+ // Accepted, but not yet in effect.
+}
+```
+
+The runtime does not apply the preference immediately. It records the request and commits it only when a later user turn using the `auto` model successfully obtains a usable model from the provider. A `pending` status therefore confirms that the request was accepted, not that it took effect. Only the most recent request survives: a new request replaces any earlier one that no turn has claimed yet.
+
+Watch for the outcome through these events:
+
+* `session.model_change` when the preference commits.
+* `session.auto_tier_switch_failed` when it does not. This event is ephemeral, so the runtime never persists or replays it on resume. Its `reason` field is one of `policy_rejected`, `request_failed`, `setup_failed`, or `unsupported`, and the previously effective preference stays active.
+
+You can also read the authoritative state at any time through the session's `model.getCurrent` RPC method, which reports the committed `autoTier`, any unclaimed `pendingAutoTier`, and the `activatingAutoTier` currently claimed by an in-progress activation.
+
+| SDK | Change the tier | Return to provider-default routing |
+|-----|-----------------|------------------------------------|
+| Node.js | `session.setAutoTier("balance")` | `session.setAutoTier(null)` |
+| Python | `session.set_auto_tier("balance")` | `session.set_auto_tier(None)` |
+| Go | `session.SetAutoTier(ctx, &tier)` | `session.SetAutoTier(ctx, nil)` |
+| .NET | `session.SetAutoTierAsync(AutoTier.Balance)` | `session.SetAutoTierAsync(null)` |
+| Rust | `session.set_auto_tier(Some(AutoTier::Balance))` | `session.set_auto_tier(None)` |
+| Java | `session.setAutoTier(AutoTier.BALANCE)` | `session.setAutoTier(null)` |
+
+To select the `auto` model and its routing preference in a single call, stage the tier on the model switch instead. The runtime rejects this option when the model is anything other than `auto`.
+
+| SDK | Stage a tier with the switch | Reset to provider-default routing |
+|-----|------------------------------|-----------------------------------|
+| Node.js | `setModel("auto", { autoTier: "balance" })` | `setModel("auto", { autoTier: null })` |
+| Python | `set_model("auto", auto_tier="balance")` | `set_model("auto", auto_tier=None)` |
+| Go | `SetModelOptions{AutoTier: &tier}` | `SetModelOptions{ResetAutoTier: true}` |
+| .NET | `new SetModelOptions { AutoTier = AutoTier.Balance }` | `new SetModelOptions { ResetAutoTier = true }` |
+| Rust | `SetModelOptions::default().with_auto_tier(AutoTier::Balance)` | `SetModelOptions::default().with_reset_auto_tier()` |
+| Java | `new SetModelOptions().setModel("auto").setAutoTier(AutoTier.BALANCE)` | `new SetModelOptions().setModel("auto").setResetAutoTier(true)` |
+
+Node.js, Python, and Rust express all three states in a single value: Node.js and Python because `null`/`None` is distinguishable from an omitted argument, and Rust because `AutoTierPreference::Reset` is a distinct variant of the same option. Go, .NET, and Java have no way to distinguish "reset" from "unset" in one value, so they carry a separate reset flag. Omitting both always means "leave the current preference alone."
+
### Example: changing model on resume
```typescript
diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md
index 10f111d9f9..0292f98e00 100644
--- a/docs/features/streaming-events.md
+++ b/docs/features/streaming-events.md
@@ -218,6 +218,48 @@ session.on(AssistantMessageDeltaEvent.class, event ->
> [!TIP]
> **(TypeScript)** The TypeScript SDK uses a discriminated union—when you match on `event.type`, the `data` payload is automatically narrowed to the correct shape.
+## Subscribing before a session starts
+
+A session can emit events before its create or resume call returns. The agent may already be working—especially on resume with `continuePendingWork`—and ephemeral events such as `session.idle` are never written to the session log, so `getMessages` cannot recover them afterwards. A subscription installed after the session handle exists misses that startup window.
+
+> [!TIP]
+> **(Rust)** `Client::prepare_session` and `Client::prepare_resume_session` return a `PreparedSession` that owns the session's event channel before any protocol activity happens. Subscribe first, then call `start()`.
+
+```rust
+use github_copilot_sdk::{Client, SessionConfig};
+
+async fn create_without_missing_startup_events(
+ client: &Client,
+) -> Result<(), github_copilot_sdk::Error> {
+ let prepared = client.prepare_session(
+ SessionConfig::default().with_event_buffer_capacity(2048),
+ )?;
+
+ // Installed before any wire activity: nothing is dropped for lack of a receiver.
+ let mut events = prepared.subscribe();
+ tokio::spawn(async move {
+ while let Ok(event) = events.recv().await {
+ println!("{}", event.event_type);
+ }
+ });
+
+ let session = prepared.start().await?;
+ let _ = session;
+ Ok(())
+}
+```
+
+`prepare_*` is synchronous and inert: it validates the buffer capacity, allocates a local channel, and does nothing else. No session is registered and nothing reaches the CLI until `start()` is first polled. Dropping a prepared session that was never started leaves no state behind and closes its subscriptions; dropping the `start()` future cancels the in-flight startup and unregisters the session, so a retry with the same session ID succeeds. Cleanup is scoped to the exact registration the abandoned startup owned, so it cannot evict a retry that has already taken over the same session ID.
+
+Startup buffering is worth planning for:
+
+* The event buffer is finite—512 events unless `event_buffer_capacity` overrides it. A capacity of `0` is rejected with an invalid-config error rather than clamped.
+* Slow subscribers observe a `Lagged` error reporting how many events were skipped. They never apply backpressure to the session's event loop.
+* Consumers that need a lossless view of a large startup burst must either configure a capacity that covers it or drain the subscription concurrently with `start()`.
+
+> [!NOTE]
+> For cloud sessions where the server assigns the session ID, the SDK cannot route notifications until the create response arrives and the ID is known. Events emitted before that point are not routable to any session. The guarantee is narrower: routed events are never dropped for lack of an installed receiver. Pin `session_id` on the config to get routing—and full pre-response coverage—from the first byte.
+
## Render only the parent agent response
Sub-agent events share the parent session stream and include envelope-level `agentId`. Root/main agent events and session-level events omit `agentId`, so main-chat renderers can ignore assistant events where `agentId` is set and route those events to traces or progress UI instead.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 53b6497fdb..5512da8531 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -19,8 +19,7 @@ Copilot: In Tokyo it's 75°F and sunny. Great day to be outside!
Before you begin, make sure you have:
* **GitHub Copilot CLI** installed and authenticated (the Node.js, Python, and .NET SDKs provide the CLI automatically—see [Bundled CLI](./setup/bundled-cli.md). Required for Go, Java, and Rust unless using their application-level CLI bundling features.)
-* Your preferred language runtime:
- * **Node.js** 20+ or **Python** 3.11+ or **Go** 1.24+ or **Rust** 1.94+ or **Java** 17+ or **.NET** 8.0+
+* Your preferred language runtime, at or above the version its SDK requires. Each SDK states its own floor in the Prerequisites section of its README: [Node.js](../nodejs/README.md#prerequisites), [Python](../python/README.md#prerequisites), [Go](../go/README.md#prerequisites), [Rust](../rust/README.md#prerequisites), [Java](../java/README.md#prerequisites), [.NET](../dotnet/README.md#prerequisites).
Verify the CLI is working:
diff --git a/docs/hooks/hooks-overview.md b/docs/hooks/hooks-overview.md
index 8d5583e996..6177d1d2c9 100644
--- a/docs/hooks/hooks-overview.md
+++ b/docs/hooks/hooks-overview.md
@@ -17,10 +17,10 @@ Hooks allow you to intercept and customize the behavior of Copilot sessions at k
| [`onPostToolUseFailure`](./post-tool-use.md#failure-variant) | After a tool execution whose result was a failure | Inject retry guidance, log failures |
| [`onUserPromptSubmitted`](./user-prompt-submitted.md) | When user sends a message | Prompt modification, filtering |
| [`onUserPromptTransformed`](./user-prompt-transformed.md) | After runtime prompt transformation | Inspect or replace model-facing content |
-| [`onSessionStart`](./session-lifecycle.md#session-start) | Session begins | Add context, configure session |
-| [`onSessionEnd`](./session-lifecycle.md#session-end) | Session ends | Cleanup, analytics |
+| [`onSessionStart`](./session-lifecycle.md#session-start-hook) | Session begins | Add context, configure session |
+| [`onSessionEnd`](./session-lifecycle.md#session-end-hook) | Session ends | Cleanup, analytics |
| [`onErrorOccurred`](./error-handling.md) | Error happens | Custom error handling |
-| [`onAgentStop`](./session-lifecycle.md#agent-stop) | Top-level agent naturally stops | Validate completion or request another turn |
+| [`onAgentStop`](./session-lifecycle.md#agent-stop-hook) | Top-level agent naturally stops | Validate completion or request another turn |
## Quick start
@@ -266,7 +266,7 @@ const session = await client.createSession({
* **[User Prompt Submitted Hook](./user-prompt-submitted.md)** - Modify user prompts
* **[User Prompt Transformed Hook](./user-prompt-transformed.md)** - Replace model-facing prompts
* **[Session Lifecycle Hooks](./session-lifecycle.md)** - Session start and end
-* **[Agent Stop Hook](./session-lifecycle.md#agent-stop)** - Validate completion before the agent stops
+* **[Agent Stop Hook](./session-lifecycle.md#agent-stop-hook)** - Validate completion before the agent stops
* **[Error Handling Hook](./error-handling.md)** - Custom error handling
## See also
diff --git a/docs/hooks/session-lifecycle.md b/docs/hooks/session-lifecycle.md
index 485752601d..558a729ae7 100644
--- a/docs/hooks/session-lifecycle.md
+++ b/docs/hooks/session-lifecycle.md
@@ -7,7 +7,7 @@ Session lifecycle hooks let you respond to session start and end events. Use the
* Track session metrics and analytics
* Configure session behavior dynamically
-## Session start hook {#session-start}
+## Session start hook
The `onSessionStart` hook is called when a session begins (new or resumed).
@@ -250,7 +250,7 @@ const session = await client.createSession({
});
```
-## Session end hook {#session-end}
+## Session end hook
The `onSessionEnd` hook is called when a session ends.
@@ -540,7 +540,7 @@ Session Summary:
});
```
-## Agent stop hook {#agent-stop}
+## Agent stop hook
The agent stop hook runs when the top-level agent naturally reaches the end of a turn. It is separate from `onSessionEnd`: the session remains active, and the hook can request another agent turn.
diff --git a/dotnet/README.md b/dotnet/README.md
index 23a78030b4..9b9ca42c60 100644
--- a/dotnet/README.md
+++ b/dotnet/README.md
@@ -14,6 +14,13 @@ To use the SDK, you'll need:
dotnet add package GitHub.Copilot.SDK
```
+The package downloads the pinned Copilot CLI runtime for the build RID from the
+matching `github/copilot-cli` GitHub release and verifies the archive against
+that release's `SHA256SUMS.txt`. Set `CopilotCliReleaseBaseUrl` in MSBuild (or
+`COPILOT_CLI_DOWNLOAD_BASE_URL` in the environment) to use a release mirror.
+Set `CopilotCliBinaryPath` to copy a preinstalled binary instead, or set
+`CopilotSkipCliDownload=true` to omit runtime acquisition.
+
## Run the Samples
Try the interactive chat sample (from the repo root):
@@ -284,6 +291,27 @@ await session2.DisposeAsync();
---
+## Auto routing tiers
+
+Change the Auto routing preference without changing the selected model. The runtime does not apply the preference immediately: it records the request and commits it only when a later user turn using the `auto` model successfully obtains a usable model from the provider, so a `pending` status confirms acceptance rather than effect. Only the most recent request survives.
+
+Watch for the outcome through the `session.model_change` event on success or the ephemeral `session.auto_tier_switch_failed` event on failure. Read the authoritative committed, pending, and activating preferences at any time through the session's `model.getCurrent` RPC method.
+
+```csharp
+var result = await session.SetAutoTierAsync(AutoTier.Intelligence);
+if (result.Status == ModelSwitchAutoTierStatus.Pending)
+{
+ // Accepted, but not yet in effect.
+}
+
+// Return to the provider's default Auto routing.
+await session.SetAutoTierAsync(null);
+```
+
+`SetModelAsync` accepts the same preference through `SetModelOptions.AutoTier`, which stages the tier atomically with selecting `auto`. Set `ResetAutoTier` instead to return to provider-default routing; the two options are mutually exclusive.
+
+See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) for the full lifecycle rules.
+
## Event Types
Sessions emit various events during processing. Each event type is a class that inherits from `SessionEvent`:
diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs
index 141473f535..cbc7d5fa2d 100644
--- a/dotnet/src/Client.cs
+++ b/dotnet/src/Client.cs
@@ -177,7 +177,7 @@ public CopilotClient(CopilotClientOptions? options = null)
throw new ArgumentException("GitHubToken and UseLoggedInUser cannot be combined with RuntimeConnection.ForUri (the existing runtime manages its own auth).", nameof(options));
}
var parsed = ParseRuntimeUrl(uri.Url);
- _optionsHost = parsed.Host;
+ _optionsHost = parsed.Host.Trim('[', ']');
_optionsPort = parsed.Port;
break;
@@ -308,7 +308,7 @@ private static RuntimeConnection ResolveDefaultConnection(CopilotClientOptions o
///
/// Parses a runtime URL into a URI with host and port.
///
- /// The URL to parse. Supports formats: "port", "host:port", "http://host:port".
+ /// The URL to parse. Supports formats: "port", "host:port", "[ipv6]:port", "http://host:port".
private static Uri ParseRuntimeUrl(string url)
{
// If it's just a port number, treat as localhost
@@ -597,6 +597,10 @@ public async Task StopAsync()
///
public async Task ForceStopAsync()
{
+ foreach (var session in _sessions.Values)
+ {
+ session.CancelPendingExternalTools();
+ }
_sessions.Clear();
ClearGitHubTokenProviders();
@@ -762,7 +766,13 @@ private static async Task CleanupCliProcessAsync(Process childProcess, ProcessSt
s_stderrPumpShutdownTimeout);
}
- AddCleanupError(errors, ex, logger);
+ // Once the owned process has exited, stderr is diagnostic-only. A descendant
+ // can briefly retain the inherited pipe on Windows, but that must not turn a
+ // successful process shutdown into a client cleanup failure.
+ if (!processExited)
+ {
+ AddCleanupError(errors, ex, logger);
+ }
}
catch (Exception ex)
{
@@ -1356,7 +1366,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance
}
catch (Exception ex)
{
- session?.RemoveFromClient();
+ session?.Unregister();
if (ex is not OperationCanceledException)
{
@@ -1561,7 +1571,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes
}
catch (Exception ex)
{
- session?.RemoveFromClient();
+ session?.Unregister();
if (ex is not OperationCanceledException)
{
LoggingHelpers.LogTiming(_logger, LogLevel.Warning, ex,
@@ -2182,7 +2192,12 @@ [new ConnectHandshakeRequest(
// handler is registered (mirrors the runtime, which reads this flag on the
// `connect` handshake so the first session's un-replayable `session.start`
// event is forwarded). Also sent on session.create/resume for older CLIs.
- _options.OnGitHubTelemetry != null ? true : null)],
+ _options.OnGitHubTelemetry != null ? true : null,
+ // Declare the integrating application's identity so the runtime attributes the
+ // telemetry it emits on this connection to a consistent surface instead
+ // of its own build. Null when the app didn't supply it.
+ ConnectHandshakeClientInfo.From(_options.ClientInfo),
+ SupportedTaskKinds: [TaskKind.Agent, TaskKind.Client, TaskKind.Shell])],
connection.StderrBuffer,
cancellationToken);
serverVersion = (int)connectResponse.ProtocolVersion;
@@ -2540,17 +2555,23 @@ private static string ResolveRuntimePathForExplicitCli(string cliPath)
var fullEntrypoint = Path.GetFullPath(cliPath);
var directory = Path.GetDirectoryName(fullEntrypoint)
?? throw new InvalidOperationException($"Could not determine directory for '{cliPath}'.");
- var flatLibraryPath = Path.Combine(directory, FfiRuntimeHost.GetRuntimeLibraryFileName());
+ var flatLibraryPath = Path.GetFullPath(
+ $"{directory}{Path.DirectorySeparatorChar}{FfiRuntimeHost.GetRuntimeLibraryFileName()}");
if (File.Exists(flatLibraryPath))
{
return flatLibraryPath;
}
+ var adjacentPrebuildPath = Path.Combine(directory, "runtime.node");
+ if (File.Exists(adjacentPrebuildPath))
+ {
+ return adjacentPrebuildPath;
+ }
var prebuildsLibraryPath = Path.Combine(
directory, "prebuilds", GetNapiPrebuildsFolderOrThrow(), "runtime.node");
return File.Exists(prebuildsLibraryPath)
? prebuildsLibraryPath
: throw new InvalidOperationException(
- $"FFI runtime library not found. Looked for '{flatLibraryPath}' and '{prebuildsLibraryPath}'.");
+ $"FFI runtime library not found. Looked for '{flatLibraryPath}', '{adjacentPrebuildPath}', and '{prebuildsLibraryPath}'.");
}
///
@@ -2676,6 +2697,7 @@ private async Task ConnectToServerAsync(Process? cliProcess, string?
ClientGlobalApiRegistration.RegisterClientGlobalApiHandlers(rpc, _clientGlobalApis);
}
rpc.StartListening();
+ _ = CancelExternalToolsWhenConnectionClosesAsync(rpc);
LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null,
"CopilotClient.ConnectToServerAsync transport setup complete. Elapsed={Elapsed}",
setupTimestamp);
@@ -2688,17 +2710,50 @@ private async Task ConnectToServerAsync(Process? cliProcess, string?
catch
{
try { rpc?.Dispose(); }
- catch (Exception ex) { _logger.LogDebug(ex, "Failed to dispose JSON-RPC connection after startup failure"); }
+ catch (Exception ex) when (IsRecoverableConnectionCleanupFailure(ex))
+ {
+ _logger.LogDebug(ex, "Failed to dispose JSON-RPC connection after startup failure");
+ }
if (networkStream is not null)
{
try { await networkStream.DisposeAsync(); }
- catch (Exception ex) { _logger.LogDebug(ex, "Failed to dispose TCP stream after startup failure"); }
+ catch (Exception ex) when (IsRecoverableConnectionCleanupFailure(ex))
+ {
+ _logger.LogDebug(ex, "Failed to dispose TCP stream after startup failure");
+ }
}
throw;
}
}
+ private static bool IsRecoverableConnectionCleanupFailure(Exception exception)
+ => exception is not OutOfMemoryException
+ and not StackOverflowException
+ and not AccessViolationException
+ and not AppDomainUnloadedException;
+
+ private async Task CancelExternalToolsWhenConnectionClosesAsync(JsonRpc rpc)
+ {
+ await Task.WhenAny(rpc.Completion).ConfigureAwait(false);
+ if (rpc.Completion.Exception is { } exception)
+ {
+ _logger.LogDebug(exception, "JSON-RPC connection completed with an error");
+ }
+
+ var connectionTask = _connectionTask;
+ if (connectionTask is null
+ || connectionTask.Status != System.Threading.Tasks.TaskStatus.RanToCompletion
+ || !ReferenceEquals(connectionTask.Result.Rpc, rpc))
+ {
+ return;
+ }
+ foreach (var session in _sessions.Values)
+ {
+ session.CancelPendingExternalTools();
+ }
+ }
+
private static JsonSerializerOptions SerializerOptionsForMessageFormatter { get; } = CreateSerializerOptions();
///
@@ -3187,7 +3242,43 @@ internal record GetSessionMetadataResponse(
internal record ConnectHandshakeRequest(
string? Token,
- [property: JsonPropertyName("enableGitHubTelemetryForwarding")] bool? EnableGitHubTelemetryForwarding = null);
+ [property: JsonPropertyName("enableGitHubTelemetryForwarding")] bool? EnableGitHubTelemetryForwarding = null,
+ [property: JsonPropertyName("clientInfo")] ConnectHandshakeClientInfo? ClientInfo = null,
+ [property: JsonPropertyName("supportedTaskKinds")] IList? SupportedTaskKinds = null);
+
+ internal record ConnectHandshakeClientInfo(
+ [property: JsonPropertyName("editorName")] string? EditorName = null,
+ [property: JsonPropertyName("editorVersion")] string? EditorVersion = null,
+ [property: JsonPropertyName("extensionName")] string? ExtensionName = null,
+ [property: JsonPropertyName("extensionVersion")] string? ExtensionVersion = null)
+ {
+ ///
+ /// Maps the public onto the connect wire
+ /// shape, dropping empty fields. Returns when no
+ /// identity was supplied so the handshake omits clientInfo and the
+ /// runtime keeps its default attribution.
+ ///
+ public static ConnectHandshakeClientInfo? From(CopilotClientInfo? info)
+ {
+ if (info is null)
+ {
+ return null;
+ }
+
+ var editorName = NullIfEmpty(info.ApplicationName);
+ var editorVersion = NullIfEmpty(info.ApplicationVersion);
+ var extensionName = NullIfEmpty(info.IntegrationName);
+ var extensionVersion = NullIfEmpty(info.IntegrationVersion);
+ if (editorName is null && editorVersion is null && extensionName is null && extensionVersion is null)
+ {
+ return null;
+ }
+
+ return new ConnectHandshakeClientInfo(editorName, editorVersion, extensionName, extensionVersion);
+ }
+
+ private static string? NullIfEmpty(string? value) => string.IsNullOrEmpty(value) ? null : value;
+ }
internal record BuiltinPluginDirectoriesRequest(
string[] Paths);
@@ -3227,6 +3318,7 @@ internal record HooksInvokeResponse(
[JsonSerializable(typeof(GetSessionMetadataRequest))]
[JsonSerializable(typeof(GetSessionMetadataResponse))]
[JsonSerializable(typeof(ConnectHandshakeRequest))]
+ [JsonSerializable(typeof(ConnectHandshakeClientInfo))]
[JsonSerializable(typeof(BuiltinPluginDirectoriesRequest))]
[JsonSerializable(typeof(McpOAuthTokenStorageMode))]
[JsonSerializable(typeof(EmbeddingCacheStorageMode))]
diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs
index c7be742ee2..2c74ab4bde 100644
--- a/dotnet/src/Generated/Rpc.cs
+++ b/dotnet/src/Generated/Rpc.cs
@@ -56,6 +56,10 @@ internal sealed class ConnectResult
[JsonPropertyName("protocolVersion")]
public long ProtocolVersion { get; set; }
+ /// Task kinds the server may return to this connection.
+ [JsonPropertyName("taskKinds")]
+ public IList? TaskKinds { get; set; }
+
/// Server package version.
[JsonPropertyName("version")]
public string Version { get; set; } = string.Empty;
@@ -94,11 +98,78 @@ internal sealed class ConnectRequest
[JsonPropertyName("enableGitHubTelemetryForwarding")]
public bool? EnableGitHubTelemetryForwarding { get; set; }
+ /// Task kinds this connection can decode when observing session tasks. Omit to retain agent and shell compatibility.
+ [JsonPropertyName("supportedTaskKinds")]
+ public IList? SupportedTaskKinds { get; set; }
+
/// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN.
[JsonPropertyName("token")]
public string? Token { get; set; }
}
+/// One server-discovered hook action from user, repository, plugin, or managed-policy configuration.
+[Experimental(Diagnostics.Experimental)]
+public sealed class DiscoveredHook
+{
+ /// Durable content hash used by hook enablement. Identical actions may intentionally share this key. Omitted when changing the user's disabled-hooks setting cannot change the action's current server-discovered state, including managed-policy hooks, session-start prompt actions, actions suppressed by disable-all settings, and projectless plugin actions that require project-directory expansion.
+ [JsonPropertyName("disableKey")]
+ public string? DisableKey { get; set; }
+
+ /// Whether this action is enabled under the server-side discovery settings. Concrete sessions may differ because they can add session-specific directories, plugins, or trust. False when its disable key is present in the user's disabled-hooks setting or disable-all settings suppress the action.
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; }
+
+ /// Hook event that invokes this action.
+ [JsonPropertyName("hookType")]
+ public HookType HookType { get; set; }
+
+ /// Deterministic identifier for this server-discovered action row. It remains stable while the project, origin, source, event, action content, and duplicate ordinal are unchanged. This is row identity, not the key persisted in disabledHooks.
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// Configuration tier that contributed this hook action.
+ [JsonPropertyName("origin")]
+ public HookOrigin Origin { get; set; }
+
+ /// Input project path for which this server-side action was resolved. Set on every row returned for project-scoped discovery, including repeated user and policy actions.
+ [JsonPropertyName("projectPath")]
+ public string? ProjectPath { get; set; }
+
+ /// Human-readable source label, such as a hook file path, settings source, or plugin name.
+ [JsonPropertyName("source")]
+ public string? Source { get; set; }
+}
+
+/// Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources.
+[Experimental(Diagnostics.Experimental)]
+public sealed class HooksDiscoverResult
+{
+ /// Errors for hook sources or actions that could not be loaded, making the result partially incomplete. Other valid actions are still returned. Project-resolution and repository-settings errors are prefixed with their project path.
+ [JsonPropertyName("errors")]
+ public IList Errors { get => field ??= []; set; }
+
+ /// All discovered hook actions. Byte-identical actions remain separate rows even when they share a disable key.
+ [JsonPropertyName("hooks")]
+ public IList Hooks { get => field ??= []; set; }
+
+ /// Non-fatal source-loading warnings. Discovery remains complete for the affected source, although the source had a recoverable issue. Repository-settings warnings are prefixed with their project path when attribution is available.
+ [JsonPropertyName("warnings")]
+ public IList Warnings { get => field ??= []; set; }
+}
+
+/// Optional project paths and host-exclusion behavior for server-scoped hook discovery.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class HooksDiscoverRequest
+{
+ /// When true, omit host-owned user and plugin hook rows and their diagnostics. Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings.
+ [JsonPropertyName("excludeHostHooks")]
+ public bool? ExcludeHostHooks { get; set; }
+
+ /// Optional project directory paths whose trusted repository and project-expanded plugin hooks should be discovered. When omitted or empty, user, managed-policy, and globally enabled installed or explicit plugin hooks are returned without project expansion.
+ [JsonPropertyName("projectPaths")]
+ public IList? ProjectPaths { get; set; }
+}
+
/// Active server-driven promotion for a model, including its discount and optional expiry.
[Experimental(Diagnostics.Experimental)]
public sealed class ModelBillingPromo
@@ -118,6 +189,10 @@ public sealed class ModelBillingPromo
/// Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it when present.
[JsonPropertyName("message")]
public string? Message { get; set; }
+
+ /// Whether the service asked hosts to give this promotion a prominent surface, such as a dedicated banner, in addition to listing it with the model. `true` requests that surface and `false` asks for the model list only. Absent means the service expressed no preference — for example a response that predates the field — so hosts should apply their own default rather than read it as `false`.
+ [JsonPropertyName("showBanner")]
+ public bool? ShowBanner { get; set; }
}
/// Long context tier pricing (available for models with extended context windows).
@@ -366,6 +441,10 @@ public sealed class Model
[JsonPropertyName("infoMessages")]
public IList? InfoMessages { get; set; }
+ /// Provider-supplied model metadata. Keys and JSON-compatible values are preserved unchanged. This is factual metadata published by the model provider; it carries no picker or UX semantics.
+ [JsonPropertyName("metadata")]
+ public IDictionary? Metadata { get; set; }
+
/// Model capability category for grouping in the model picker.
[JsonPropertyName("modelPickerCategory")]
public ModelPickerCategory? ModelPickerCategory { get; set; }
@@ -1841,6 +1920,11 @@ public partial class McpPlanInstallResultNetworkFailure : McpPlanInstallResult
[JsonPropertyName("reason")]
public required CatalogNetworkFailureReason Reason { get; set; }
+ /// Bounded cooldown in seconds before another catalog request should be attempted, when the authority supplied a numeric Retry-After value or the runtime applied its documented fallback.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("retryAfterSeconds")]
+ public int? RetryAfterSeconds { get; set; }
+
/// HTTP status code, when the failure was a rejected response.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("statusCode")]
@@ -2159,6 +2243,10 @@ internal sealed class McpConfigUpdateRequest
[Experimental(Diagnostics.Experimental)]
internal sealed class McpConfigRemoveRequest
{
+ /// OAuth Client ID Metadata Document URL whose persisted credentials should also be removed.
+ [JsonPropertyName("authClientIdMetadataUrl")]
+ public string? AuthClientIdMetadataUrl { get; set; }
+
/// Name of the MCP server to remove.
[RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")]
@@ -2655,6 +2743,11 @@ public partial class CatalogSearchResultNetworkFailure : CatalogSearchResult
[JsonPropertyName("reason")]
public required CatalogNetworkFailureReason Reason { get; set; }
+ /// Bounded cooldown in seconds before another catalog request should be attempted, when the authority supplied a numeric Retry-After value or the runtime applied its documented fallback.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("retryAfterSeconds")]
+ public int? RetryAfterSeconds { get; set; }
+
/// HTTP status code, when the failure was a rejected response.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("statusCode")]
@@ -2762,7 +2855,7 @@ internal sealed class CatalogSearchRequest
[JsonPropertyName("limit")]
public int? Limit { get; set; }
- /// Free-text search query. Never written to logs or telemetry.
+ /// Free-text search query. Persisted as tool input for session continuity, but omitted from telemetry.
[RegularExpression("\\S")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")]
[MinLength(1)]
@@ -2828,6 +2921,10 @@ public sealed class PluginInstallResult
/// Number of skills discovered and installed from the plugin.
[JsonPropertyName("skillsInstalled")]
public long SkillsInstalled { get; set; }
+
+ /// Where the completed plugin tree was staged before atomic promotion.
+ [JsonPropertyName("stagingMode")]
+ public PluginInstallStagingMode? StagingMode { get; set; }
}
/// Plugin source and optional working directory for relative-path resolution.
@@ -3227,7 +3324,7 @@ internal sealed class SkillsConfigSetSkillDisabledRequest
public string Name { get; set; } = string.Empty;
}
-/// Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path.
+/// Agent metadata, including identifiers, display details, source, tools, model, models, MCP servers, skills, and file path.
[Experimental(Diagnostics.Experimental)]
public sealed class AgentInfo
{
@@ -3252,6 +3349,14 @@ public sealed class AgentInfo
[JsonPropertyName("model")]
public string? Model { get; set; }
+ /// Whether authored models are preferences or required constraints.
+ [JsonPropertyName("modelPolicy")]
+ public AgentModelPolicy? ModelPolicy { get; set; }
+
+ /// Authored preferred model ids for this agent, in priority order. Runtime model selection chooses the first available model; omitted means no authored preference.
+ [JsonPropertyName("models")]
+ public IList? Models { get; set; }
+
/// Name of the agent. Use `id` as the stable selection identifier.
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
@@ -4216,6 +4321,48 @@ internal sealed class SessionsGetMetadataRequest
public string SessionId { get; set; } = string.Empty;
}
+/// Batch of session events returned by a read, with cursor and continuation metadata.
+[Experimental(Diagnostics.Experimental)]
+public sealed class EventsReadResult
+{
+ /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly).
+ [JsonPropertyName("cursor")]
+ public string Cursor { get; set; } = string.Empty;
+
+ /// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history. For a forward read the fallback starts from the beginning of the remaining history; for a backward read it falls back to the tail (the newest window). Because the fallback page is a fresh boundary snapshot rather than a continuation of the requested cursor, it may overlap events the consumer has already rendered — a backward fallback to the tail in particular can repeat the newest window. On 'expired', consumers should reset or rebase their local pagination state (or deduplicate by event id) before continuing from the returned cursor rather than blindly appending/prepending the fallback page.
+ [JsonPropertyName("cursorStatus")]
+ public EventsCursorStatus CursorStatus { get; set; }
+
+ /// Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order.
+ [JsonPropertyName("events")]
+ public IList Events { get => field ??= []; set; }
+
+ /// True when more events are available in the read's direction. For a forward read, true means the batch returned `max` events and more are available immediately. For a backward read, true means older persisted events remain before the returned window.
+ [JsonPropertyName("hasMore")]
+ public bool HasMore { get; set; }
+}
+
+/// Pagination options for reading an inactive or active local session's persisted event journal.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class SessionsReadPersistedEventsRequest
+{
+ /// Opaque cursor returned by a previous persisted-event read. Omit on the first call.
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; set; }
+
+ /// Direction to page through persisted history. Forward starts at the beginning; backward starts with the newest events. Events in each page remain chronological.
+ [JsonPropertyName("direction")]
+ public EventsReadDirection? Direction { get; set; }
+
+ /// Maximum number of events to return in this batch (1–1000, default 200).
+ [JsonPropertyName("max")]
+ public long? Max { get; set; }
+
+ /// Session ID whose persisted event journal should be read.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
/// Recent local session IDs that contain user-visible history.
[Experimental(Diagnostics.Experimental)]
internal sealed class SessionsListNonEmptySessionIdsResult
@@ -6244,6 +6391,7 @@ internal sealed class CanvasProviderUnregisterRequest
[JsonDerivedType(typeof(FactoryRunFailureFactoryResumeDeclined), "factory_resume_declined")]
[JsonDerivedType(typeof(FactoryRunFailureFactoryDurableFailure), "factory_durable_failure")]
[JsonDerivedType(typeof(FactoryRunFailureFactoryAccountingIncomplete), "factory_accounting_incomplete")]
+[JsonDerivedType(typeof(FactoryRunFailureFactoryProviderDisconnected), "factory_provider_disconnected")]
public partial class FactoryRunFailure
{
/// The type discriminator.
@@ -6329,15 +6477,33 @@ public partial class FactoryRunFailureFactoryAccountingIncomplete : FactoryRunFa
public required string RunId { get; set; }
}
+/// The extension that owns the factory disconnected while the run was executing, so the host halted it. The run's journaled subagent results are preserved so a resume can reuse them.
+/// The factory_provider_disconnected variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class FactoryRunFailureFactoryProviderDisconnected : FactoryRunFailure
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "factory_provider_disconnected";
+
+ /// Factory run identifier.
+ [JsonPropertyName("runId")]
+ public required string RunId { get; set; }
+}
+
/// Complete current or terminal factory run envelope.
[Experimental(Diagnostics.Experimental)]
public sealed class FactoryRunResult
{
+ /// One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime.
+ [JsonPropertyName("attempt")]
+ public long? Attempt { get; set; }
+
/// Error message for an errored run.
[JsonPropertyName("error")]
public string? Error { get; set; }
- /// Machine-readable failure details for an errored run.
+ /// Machine-readable failure details for a halted or errored run.
[JsonPropertyName("failure")]
public FactoryRunFailure? Failure { get; set; }
@@ -7211,10 +7377,18 @@ internal sealed class FactoryJournalPutRequest
public string SessionId { get; set; } = string.Empty;
}
-/// The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
+/// The session's authoritative model snapshot. Auto preference fields are configuration for the virtual `auto` model and do not change the selected model identifier. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
[Experimental(Diagnostics.Experimental)]
public sealed class CurrentModel
{
+ /// Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing.
+ [JsonPropertyName("activatingAutoTier")]
+ public AutoTier? ActivatingAutoTier { get; set; }
+
+ /// Auto preference currently committed for the session. This can remain available while another model is selected so a later switch to `auto` can reuse it.
+ [JsonPropertyName("autoTier")]
+ public AutoTier? AutoTier { get; set; }
+
/// Context tier for models that support multiple context-window sizes.
[JsonPropertyName("contextTier")]
public ContextTier? ContextTier { get; set; }
@@ -7223,6 +7397,10 @@ public sealed class CurrentModel
[JsonPropertyName("modelId")]
public string? ModelId { get; set; }
+ /// Latest unclaimed Auto preference waiting for a future user turn. Null means the pending request is returning to provider-default routing.
+ [JsonPropertyName("pendingAutoTier")]
+ public AutoTier? PendingAutoTier { get; set; }
+
/// Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot.
[JsonPropertyName("reasoningEffort")]
public string? ReasoningEffort { get; set; }
@@ -7278,6 +7456,10 @@ public sealed class ModelSwitchToResult
[JsonPropertyName("modelId")]
public string? ModelId { get; set; }
+ /// Authoritative model and Auto preference state after an immediate switch. For deferred switches this remains the current state until the queued change drains.
+ [JsonPropertyName("modelState")]
+ public CurrentModel? ModelState { get; set; }
+
/// Persistence failure encountered after applying the model switch.
[JsonPropertyName("persistenceError")]
public string? PersistenceError { get; set; }
@@ -7402,6 +7584,10 @@ public sealed class ModelPickerPersistenceRequest
[Experimental(Diagnostics.Experimental)]
internal sealed class ModelSwitchToRequest
{
+ /// Optional Auto routing preference to stage atomically with selecting `auto`. Pass null to return to provider-default Auto routing. This field is rejected when `modelId` is not `auto`.
+ [JsonPropertyName("autoTier")]
+ public AutoTier? AutoTier { get; set; }
+
/// Explicit response to a model-switch compaction preflight. Omit to request a confirmation projection when compaction is necessary.
[JsonPropertyName("compactionDecision")]
public string? CompactionDecision { get; set; }
@@ -7454,7 +7640,7 @@ internal sealed class ModelSwitchToRequest
[JsonPropertyName("sessionId")]
public string SessionId { get; set; } = string.Empty;
- /// Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted.
+ /// Origin to record on the effective `session.model_change` event for trusted in-process calls. Transport SDK calls are always recorded as `sdk`, regardless of this value.
[JsonPropertyName("source")]
public ModelChangeSource? Source { get; set; }
@@ -7463,6 +7649,48 @@ internal sealed class ModelSwitchToRequest
public Verbosity? Verbosity { get; set; }
}
+/// Immediate acknowledgement and Auto preference snapshot after a switch request. This result never implies that a pending preference committed.
+[Experimental(Diagnostics.Experimental)]
+public sealed class ModelSwitchAutoTierResult
+{
+ /// Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing.
+ [JsonPropertyName("activatingAutoTier")]
+ public AutoTier? ActivatingAutoTier { get; set; }
+
+ /// Auto preference currently committed for the session.
+ [JsonPropertyName("effectiveAutoTier")]
+ public AutoTier? EffectiveAutoTier { get; set; }
+
+ /// Latest unclaimed Auto preference waiting for a future user turn.
+ [JsonPropertyName("pendingAutoTier")]
+ public AutoTier? PendingAutoTier { get; set; }
+
+ /// Immediate request status. `pending` means accepted but not committed.
+ [JsonPropertyName("status")]
+ public ModelSwitchAutoTierStatus Status { get; set; }
+
+ /// Earlier unclaimed preference replaced by this request. This can be present with either status, including when selecting the effective preference cancels pending work.
+ [JsonPropertyName("supersededAutoTier")]
+ public AutoTier? SupersededAutoTier { get; set; }
+}
+
+/// An Auto preference request for the session. This updates Auto configuration only; it does not change the selected model to `auto`.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class ModelSwitchAutoTierRequest
+{
+ /// Auto preference to activate when a future user turn using the `auto` model safely mints a replacement model and token pair. Pass null to return to provider-default Auto routing.
+ [JsonPropertyName("autoTier")]
+ public AutoTier? AutoTier { get; set; }
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+
+ /// Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted.
+ [JsonPropertyName("source")]
+ public ModelChangeSource? Source { get; set; }
+}
+
/// Managed, repository, and CLI model overrides to overlay onto the session at startup.
[Experimental(Diagnostics.Experimental)]
internal sealed class ModelApplyStartupOverlayRequest
@@ -7479,6 +7707,10 @@ internal sealed class ModelApplyStartupOverlayRequest
[JsonPropertyName("deviceManagedModel")]
public string? DeviceManagedModel { get; set; }
+ /// Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and an explicit user selection still wins.
+ [JsonPropertyName("policyHelperModel")]
+ public string? PolicyHelperModel { get; set; }
+
/// Context tier selected by repository settings, when configured.
[JsonPropertyName("repoContextTier")]
public string? RepoContextTier { get; set; }
@@ -8333,6 +8565,80 @@ internal sealed class WorkspacesDiffRequest
public string SessionId { get; set; } = string.Empty;
}
+/// Current per-window credit limit and consumption for an autopilot objective.
+[Experimental(Diagnostics.Experimental)]
+public sealed class AutopilotObjectiveCreditLimit
+{
+ /// Configured AI-credit cap, when one is set.
+ [JsonPropertyName("credits")]
+ public double? Credits { get; set; }
+
+ /// Window consumption in fractional AI credits, for display.
+ [JsonPropertyName("creditsUsed")]
+ public double CreditsUsed { get; set; }
+
+ /// Exact window consumption in non-negative integer nano-AIU, encoded as a decimal string.
+ [RegularExpression("^[0-9]+$")]
+ [JsonPropertyName("creditsUsedNanoAiu")]
+ public string CreditsUsedNanoAiu { get; set; } = string.Empty;
+}
+
+/// Public, persistence-independent projection of an autopilot objective.
+[Experimental(Diagnostics.Experimental)]
+public sealed class AutopilotObjectiveState
+{
+ /// Optional summary recorded when the objective completed.
+ [JsonPropertyName("completionSummary")]
+ public string? CompletionSummary { get; set; }
+
+ /// Exact lifetime AI-credit consumption in non-negative integer nano-AIU, encoded as a decimal string.
+ [RegularExpression("^[0-9]+$")]
+ [JsonPropertyName("creditCountNanoAiu")]
+ public string CreditCountNanoAiu { get; set; } = string.Empty;
+
+ /// Current per-window consumption and optional cap, when a credit-tracking window is present.
+ [JsonPropertyName("creditLimit")]
+ public AutopilotObjectiveCreditLimit? CreditLimit { get; set; }
+
+ /// Session-local objective identifier.
+ [JsonPropertyName("id")]
+ public long Id { get; set; }
+
+ /// User-provided objective text.
+ [JsonPropertyName("objective")]
+ public string Objective { get; set; } = string.Empty;
+
+ /// Optional reason the objective is paused.
+ [JsonPropertyName("pauseReason")]
+ public string? PauseReason { get; set; }
+
+ /// Current normalized lifecycle status.
+ [JsonPropertyName("status")]
+ public AutopilotObjectiveStatus Status { get; set; }
+
+ /// Number of objective turns started.
+ [JsonPropertyName("turnCount")]
+ public long TurnCount { get; set; }
+}
+
+/// Canonical runtime state for the session's current autopilot objective.
+[Experimental(Diagnostics.Experimental)]
+public sealed class AutopilotObjectiveGetStateResult
+{
+ /// Current objective state, or `null` when the session has no objective.
+ [JsonPropertyName("state")]
+ public AutopilotObjectiveState? State { get; set; }
+}
+
+/// Identifies the target session.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class SessionAutopilotObjectiveGetStateRequest
+{
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
/// Characters that, when typed in the composer, should trigger a `completions.request`. Empty when the session has no host-driven completions (e.g. local sessions, or a relay host that does not advertise `completionTriggerCharacters`).
[Experimental(Diagnostics.Experimental)]
public sealed class CompletionsGetTriggerCharactersResult
@@ -8603,13 +8909,14 @@ internal sealed class TasksStartAgentRequest
public string SessionId { get; set; } = string.Empty;
}
-/// Tracked task union returned by task APIs, containing either an agent task or a shell task.
+/// Tracked task union returned by task APIs, containing an agent, client, or shell task.
/// Polymorphic base type discriminated by type.
[Experimental(Diagnostics.Experimental)]
[JsonPolymorphic(
TypeDiscriminatorPropertyName = "type",
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
[JsonDerivedType(typeof(TaskInfoAgent), "agent")]
+[JsonDerivedType(typeof(TaskInfoClient), "client")]
[JsonDerivedType(typeof(TaskInfoShell), "shell")]
public partial class TaskInfo
{
@@ -8718,6 +9025,143 @@ public partial class TaskInfoAgent : TaskInfo
public required string ToolCallId { get; set; }
}
+/// Public owner attribution for a client-owned task. Identifiers are opaque and never authorize requests.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TaskClientOwner
+{
+ /// ISO 8601 timestamp when the bound join disconnected.
+ [JsonPropertyName("disconnectedAt")]
+ public DateTimeOffset? DisconnectedAt { get; set; }
+
+ /// Display-only owner name.
+ [JsonPropertyName("displayName")]
+ public string? DisplayName { get; set; }
+
+ /// Opaque identity of the currently or most recently bound session join.
+ [JsonPropertyName("joinId")]
+ public string JoinId { get; set; } = string.Empty;
+
+ /// Class of the task owner.
+ [JsonPropertyName("kind")]
+ public TaskClientOwnerKind Kind { get; set; }
+
+ /// Opaque session-scoped participant identity.
+ [JsonPropertyName("participantId")]
+ public string ParticipantId { get; set; } = string.Empty;
+
+ /// Whether this task's bound join is currently connected.
+ [JsonPropertyName("presence")]
+ public TaskClientOwnerPresence Presence { get; set; }
+
+ /// Display-only owner source.
+ [JsonPropertyName("source")]
+ public string? Source { get; set; }
+}
+
+/// Tracked client-owned task metadata.
+/// The client variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class TaskInfoClient : TaskInfo
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "client";
+
+ /// ISO 8601 timestamp when the current active segment started.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("activeStartedAt")]
+ public DateTimeOffset? ActiveStartedAt { get; set; }
+
+ /// Accumulated active execution time in milliseconds.
+ [JsonPropertyName("activeTimeMs")]
+ public required long ActiveTimeMs { get; set; }
+
+ /// Whether the currently bound owner can receive a cancellation request.
+ [JsonPropertyName("canCancel")]
+ public required bool CanCancel { get; set; }
+
+ /// Human-readable reason for terminal cancellation.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("cancellationReason")]
+ public string? CancellationReason { get; set; }
+
+ /// Owner-scoped registration and reclaim key.
+ [JsonPropertyName("clientTaskId")]
+ public required string ClientTaskId { get; set; }
+
+ /// ISO 8601 timestamp when the task reached a terminal status.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("completedAt")]
+ public DateTimeOffset? CompletedAt { get; set; }
+
+ /// Task description.
+ [JsonPropertyName("description")]
+ public required string Description { get; set; }
+
+ /// Optional task display name.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("displayName")]
+ public string? DisplayName { get; set; }
+
+ /// Human-readable terminal failure message.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("error")]
+ public string? Error { get; set; }
+
+ /// Optional owner-supplied terminal failure code.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("errorCode")]
+ public string? ErrorCode { get; set; }
+
+ /// Execution mode, which is always background for client-owned tasks.
+ [JsonPropertyName("executionMode")]
+ public required TaskClientExecutionMode ExecutionMode { get; set; }
+
+ /// Canonical runtime-generated task identifier.
+ [JsonPropertyName("id")]
+ public required string Id { get; set; }
+
+ /// ISO 8601 timestamp when the connected owner entered idle status.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("idleSince")]
+ public DateTimeOffset? IdleSince { get; set; }
+
+ /// ISO 8601 timestamp of the most recent orphan transition.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("orphanedAt")]
+ public DateTimeOffset? OrphanedAt { get; set; }
+
+ /// Public attribution and presence for the task owner.
+ [JsonPropertyName("owner")]
+ public required TaskClientOwner Owner { get; set; }
+
+ /// ISO 8601 timestamp of the most recent successful reclaim.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("reclaimedAt")]
+ public DateTimeOffset? ReclaimedAt { get; set; }
+
+ /// Opaque successful terminal result supplied by the task owner.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("result")]
+ public JsonElement? Result { get; set; }
+
+ /// Sequence number of the latest accepted owner update.
+ [JsonPropertyName("sequence")]
+ public required long Sequence { get; set; }
+
+ /// ISO 8601 timestamp when the task started.
+ [JsonPropertyName("startedAt")]
+ public required DateTimeOffset StartedAt { get; set; }
+
+ /// Client task lifecycle status.
+ [JsonPropertyName("status")]
+ public required TaskClientStatus Status { get; set; }
+
+ /// ISO 8601 timestamp of the latest accepted lifecycle change.
+ [JsonPropertyName("updatedAt")]
+ public required DateTimeOffset UpdatedAt { get; set; }
+}
+
/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID.
/// The shell variant of .
[Experimental(Diagnostics.Experimental)]
@@ -8795,6 +9239,299 @@ internal sealed class SessionTasksListRequest
public string SessionId { get; set; } = string.Empty;
}
+/// Tracked client-owned task metadata.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TaskClientInfo
+{
+ /// ISO 8601 timestamp when the current active segment started.
+ [JsonPropertyName("activeStartedAt")]
+ public DateTimeOffset? ActiveStartedAt { get; set; }
+
+ /// Accumulated active execution time in milliseconds.
+ [JsonPropertyName("activeTimeMs")]
+ public long ActiveTimeMs { get; set; }
+
+ /// Whether the currently bound owner can receive a cancellation request.
+ [JsonPropertyName("canCancel")]
+ public bool CanCancel { get; set; }
+
+ /// Human-readable reason for terminal cancellation.
+ [JsonPropertyName("cancellationReason")]
+ public string? CancellationReason { get; set; }
+
+ /// Owner-scoped registration and reclaim key.
+ [JsonPropertyName("clientTaskId")]
+ public string ClientTaskId { get; set; } = string.Empty;
+
+ /// ISO 8601 timestamp when the task reached a terminal status.
+ [JsonPropertyName("completedAt")]
+ public DateTimeOffset? CompletedAt { get; set; }
+
+ /// Task description.
+ [JsonPropertyName("description")]
+ public string Description { get; set; } = string.Empty;
+
+ /// Optional task display name.
+ [JsonPropertyName("displayName")]
+ public string? DisplayName { get; set; }
+
+ /// Human-readable terminal failure message.
+ [JsonPropertyName("error")]
+ public string? Error { get; set; }
+
+ /// Optional owner-supplied terminal failure code.
+ [JsonPropertyName("errorCode")]
+ public string? ErrorCode { get; set; }
+
+ /// Execution mode, which is always background for client-owned tasks.
+ [JsonPropertyName("executionMode")]
+ public TaskClientExecutionMode ExecutionMode { get; set; }
+
+ /// Canonical runtime-generated task identifier.
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// ISO 8601 timestamp when the connected owner entered idle status.
+ [JsonPropertyName("idleSince")]
+ public DateTimeOffset? IdleSince { get; set; }
+
+ /// ISO 8601 timestamp of the most recent orphan transition.
+ [JsonPropertyName("orphanedAt")]
+ public DateTimeOffset? OrphanedAt { get; set; }
+
+ /// Public attribution and presence for the task owner.
+ [JsonPropertyName("owner")]
+ public TaskClientOwner Owner { get => field ??= new(); set; }
+
+ /// ISO 8601 timestamp of the most recent successful reclaim.
+ [JsonPropertyName("reclaimedAt")]
+ public DateTimeOffset? ReclaimedAt { get; set; }
+
+ /// Opaque successful terminal result supplied by the task owner.
+ [JsonPropertyName("result")]
+ public JsonElement? Result { get; set; }
+
+ /// Sequence number of the latest accepted owner update.
+ [JsonPropertyName("sequence")]
+ public long Sequence { get; set; }
+
+ /// ISO 8601 timestamp when the task started.
+ [JsonPropertyName("startedAt")]
+ public DateTimeOffset StartedAt { get; set; }
+
+ /// Client task lifecycle status.
+ [JsonPropertyName("status")]
+ public TaskClientStatus Status { get; set; }
+
+ /// Task kind.
+ [JsonPropertyName("type")]
+ public TaskClientType Type { get; set; }
+
+ /// ISO 8601 timestamp of the latest accepted lifecycle change.
+ [JsonPropertyName("updatedAt")]
+ public DateTimeOffset UpdatedAt { get; set; }
+}
+
+/// Result of registering or reclaiming a client-owned task.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TasksRegisterResult
+{
+ /// True only when this invocation created a new task.
+ [JsonPropertyName("created")]
+ public bool Created { get; set; }
+
+ /// True only when this invocation reclaimed an orphaned task.
+ [JsonPropertyName("reclaimed")]
+ public bool Reclaimed { get; set; }
+
+ /// Authoritative registered or reclaimed task.
+ [JsonPropertyName("task")]
+ public TaskClientInfo Task { get => field ??= new(); set; }
+}
+
+/// Registers or reclaims a client-owned task.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class TasksRegisterRequest
+{
+ /// Whether the owner supports runtime cancellation requests.
+ [JsonPropertyName("cancellable")]
+ public bool Cancellable { get; set; }
+
+ /// Owner-scoped idempotency key used for registration and reclaim.
+ [JsonPropertyName("clientTaskId")]
+ public string ClientTaskId { get; set; } = string.Empty;
+
+ /// Human-readable description of the external work.
+ [JsonPropertyName("description")]
+ public string Description { get; set; } = string.Empty;
+
+ /// Optional short display name for the external work.
+ [JsonPropertyName("displayName")]
+ public string? DisplayName { get; set; }
+
+ /// Expected current sequence for idempotent registration or orphan reclaim.
+ [JsonPropertyName("expectedSequence")]
+ public long? ExpectedSequence { get; set; }
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+
+ /// Task kind.
+ [JsonPropertyName("type")]
+ public TaskClientType Type { get; set; }
+}
+
+/// Result of publishing a client-owned task update.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TasksUpdateResult
+{
+ /// Whether this invocation changed task state.
+ [JsonPropertyName("applied")]
+ public bool Applied { get; set; }
+
+ /// Whether this invocation repeated the latest accepted update.
+ [JsonPropertyName("duplicate")]
+ public bool Duplicate { get; set; }
+
+ /// Authoritative task after processing the update.
+ [JsonPropertyName("task")]
+ public TaskClientInfo Task { get => field ??= new(); set; }
+}
+
+/// Progress or terminal update for a client-owned task.
+/// Polymorphic base type discriminated by kind.
+[Experimental(Diagnostics.Experimental)]
+[JsonPolymorphic(
+ TypeDiscriminatorPropertyName = "kind",
+ UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
+[JsonDerivedType(typeof(TaskClientUpdateProgress), "progress")]
+[JsonDerivedType(typeof(TaskClientUpdateCompleted), "completed")]
+[JsonDerivedType(typeof(TaskClientUpdateFailed), "failed")]
+[JsonDerivedType(typeof(TaskClientUpdateCancelled), "cancelled")]
+public partial class TaskClientUpdate
+{
+ /// The type discriminator.
+ [JsonPropertyName("kind")]
+ public virtual string Kind { get; set; } = string.Empty;
+}
+
+
+/// Publishes nonterminal progress for a running or idle client task.
+/// The progress variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class TaskClientUpdateProgress : TaskClientUpdate
+{
+ ///
+ [JsonIgnore]
+ public override string Kind => "progress";
+
+ /// Optional progress message appended to recent activity when nonempty.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("message")]
+ public string? Message { get; set; }
+
+ /// Optional completion percentage; null clears the current percentage.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("percentage")]
+ public double? Percentage { get; set; }
+
+ /// Optional progress phase; null clears the current phase.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("phase")]
+ public string? Phase { get; set; }
+
+ /// Optional active status transition.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("status")]
+ public TaskClientActiveStatus? Status { get; set; }
+}
+
+/// Reports successful terminal completion.
+/// The completed variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class TaskClientUpdateCompleted : TaskClientUpdate
+{
+ ///
+ [JsonIgnore]
+ public override string Kind => "completed";
+
+ /// Optional final progress message.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("message")]
+ public string? Message { get; set; }
+
+ /// Optional opaque successful terminal result.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("result")]
+ public JsonElement? Result { get; set; }
+}
+
+/// Reports terminal failure.
+/// The failed variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class TaskClientUpdateFailed : TaskClientUpdate
+{
+ ///
+ [JsonIgnore]
+ public override string Kind => "failed";
+
+ /// Optional owner-supplied terminal failure code.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("code")]
+ public string? Code { get; set; }
+
+ /// Human-readable terminal failure message.
+ [JsonPropertyName("error")]
+ public required string Error { get; set; }
+
+ /// Optional final progress message.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("message")]
+ public string? Message { get; set; }
+}
+
+/// Reports terminal cancellation after external work stopped.
+/// The cancelled variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class TaskClientUpdateCancelled : TaskClientUpdate
+{
+ ///
+ [JsonIgnore]
+ public override string Kind => "cancelled";
+
+ /// Optional final progress message.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("message")]
+ public string? Message { get; set; }
+
+ /// Optional human-readable cancellation reason.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("reason")]
+ public string? Reason { get; set; }
+}
+
+/// Updates a client-owned task.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class TasksUpdateRequest
+{
+ /// Canonical runtime-generated task identifier.
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// Owner update sequence to apply.
+ [JsonPropertyName("sequence")]
+ public long Sequence { get; set; }
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+
+ /// Progress or terminal update payload.
+ [JsonPropertyName("update")]
+ public TaskClientUpdate Update { get => field ??= new(); set; }
+}
+
/// Refresh metadata for any detached background shells the runtime knows about. Use after a long pause to pick up exit/output state for shells running outside the agent loop.
[Experimental(Diagnostics.Experimental)]
public sealed class TasksRefreshResult
@@ -8825,13 +9562,16 @@ internal sealed class SessionTasksWaitForPendingRequest
public string SessionId { get; set; } = string.Empty;
}
-/// Polymorphic base type discriminated by type.
+/// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked.
+/// Polymorphic base type discriminated by type.
+[Experimental(Diagnostics.Experimental)]
[JsonPolymorphic(
TypeDiscriminatorPropertyName = "type",
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
-[JsonDerivedType(typeof(TasksGetProgressResultProgressAgent), "agent")]
-[JsonDerivedType(typeof(TasksGetProgressResultProgressShell), "shell")]
-public partial class TasksGetProgressResultProgress
+[JsonDerivedType(typeof(TaskProgressAgent), "agent")]
+[JsonDerivedType(typeof(TaskProgressClient), "client")]
+[JsonDerivedType(typeof(TaskProgressShell), "shell")]
+public partial class TaskProgress
{
/// The type discriminator.
[JsonPropertyName("type")]
@@ -8853,8 +9593,9 @@ public sealed class TaskProgressLine
}
/// Progress snapshot for an agent task, with recent activity lines and optional latest intent.
-/// The agent variant of .
-public partial class TasksGetProgressResultProgressAgent : TasksGetProgressResultProgress
+/// The agent variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class TaskProgressAgent : TaskProgress
{
///
[JsonIgnore]
@@ -8870,9 +9611,51 @@ public partial class TasksGetProgressResultProgressAgent : TasksGetProgressResul
public required IList RecentActivity { get; set; }
}
+/// Generic progress for a client-owned task.
+/// The client variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class TaskProgressClient : TaskProgress
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "client";
+
+ /// Most recent nonempty progress message.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("lastMessage")]
+ public string? LastMessage { get; set; }
+
+ /// Current completion percentage from zero through one hundred.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("percentage")]
+ public double? Percentage { get; set; }
+
+ /// Current owner-defined progress phase.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("phase")]
+ public string? Phase { get; set; }
+
+ /// Recent server-timestamped progress messages.
+ [JsonPropertyName("recentActivity")]
+ public required IList RecentActivity { get; set; }
+
+ /// Sequence number of the latest accepted owner update.
+ [JsonPropertyName("sequence")]
+ public required long Sequence { get; set; }
+
+ /// Current client task lifecycle status.
+ [JsonPropertyName("status")]
+ public required TaskClientStatus Status { get; set; }
+
+ /// ISO 8601 timestamp of the latest accepted lifecycle change.
+ [JsonPropertyName("updatedAt")]
+ public required DateTimeOffset UpdatedAt { get; set; }
+}
+
/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID.
-/// The shell variant of .
-public partial class TasksGetProgressResultProgressShell : TasksGetProgressResultProgress
+/// The shell variant of .
+[Experimental(Diagnostics.Experimental)]
+public partial class TaskProgressShell : TaskProgress
{
///
[JsonIgnore]
@@ -8894,7 +9677,7 @@ public sealed class TasksGetProgressResult
{
/// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked.
[JsonPropertyName("progress")]
- public TasksGetProgressResultProgress? Progress { get; set; }
+ public TaskProgress? Progress { get; set; }
}
/// Identifier of the background task to fetch progress for.
@@ -9117,6 +9900,10 @@ public sealed class SkillsInvokedSkill
[JsonPropertyName("content")]
public string Content { get; set; } = string.Empty;
+ /// Whether model invocation was disabled when this skill was invoked.
+ [JsonPropertyName("disableModelInvocation")]
+ public bool? DisableModelInvocation { get; set; }
+
/// Turn number when the skill was invoked.
[JsonPropertyName("invokedAtTurn")]
public long InvokedAtTurn { get; set; }
@@ -9125,7 +9912,7 @@ public sealed class SkillsInvokedSkill
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
- /// Path to the SKILL.md file.
+ /// Path to the SKILL.md file, or an empty string for an SDK-provided skill without a filesystem identity.
[JsonPropertyName("path")]
public string Path { get; set; } = string.Empty;
}
@@ -9275,6 +10062,10 @@ public sealed class McpServer
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
+ /// Server-advertised metadata for a connected server. Omitted when no live connection metadata is available, including while pending or when failed, disabled, stopped, or not configured.
+ [JsonPropertyName("serverMetadata")]
+ public McpServerMetadata? ServerMetadata { get; set; }
+
/// Configuration source: user, workspace, plugin, or builtin.
[JsonPropertyName("source")]
public McpServerSource? Source { get; set; }
@@ -10959,7 +11750,7 @@ public sealed class OptionsUpdateAdditionalContentExclusionPolicy
[Experimental(Diagnostics.Experimental)]
public sealed class CapiSessionOptions
{
- /// Routing preference used when the session model is `auto`. The runtime persists the preference across cold resume. When omitted, the default routing behavior is used. Resuming an already-resident session cannot change its preference.
+ /// Routing preference for sessions whose model is `auto`. On create or cold resume, this establishes the preference sent as `tier` on CAPI `/auto` requests; when omitted on cold resume, the runtime restores the last committed preference. On resident resume, a different value requests a safe switch after resume succeeds and cannot change an in-flight turn. Successful switches are persisted for later cold resume. When no preference is supplied or restored, CAPI default routing is used.
[JsonPropertyName("autoTier")]
public AutoTier? AutoTier { get; set; }
@@ -11138,7 +11929,7 @@ public sealed class SandboxConfigUserPolicyNetworkProxy
[JsonPropertyName("password")]
public string? Password { get; set; }
- /// Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted. Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in the separate `username`/`password` fields. A credential-free http:// loopback URL is routed through the localhost proxy automatically; loopback covers localhost and any *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is.
+ /// Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted; an explicit port must be between 1 and 65535. Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in the separate `username`/`password` fields. A credential-free http:// loopback proxy URL is routed through the localhost proxy automatically; loopback covers localhost and any *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is.
[JsonPropertyName("url")]
public string Url { get; set; } = string.Empty;
@@ -11159,7 +11950,7 @@ public sealed class SandboxConfigUserPolicyNetwork
[JsonPropertyName("allowOutbound")]
public bool? AllowOutbound { get; set; }
- /// HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS. Credentials go in the separate `username`/`password` fields. A credential-free http:// loopback proxy URL is routed through the localhost proxy automatically; an https:// or authenticated loopback URL is used as-is.
+ /// HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint, requires that endpoint to be reachable over IPv4 (the [::] dual-stack wildcard is accepted and routed through the IPv4 gateway), and does not support proxy credentials. macOS relies on applications honoring proxy environment variables. Windows also configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's networking stack. Configure supported credentials in the separate `username` and `password` fields. A credential-free http:// loopback URL uses the localhost proxy form, while an https:// or authenticated loopback URL uses the URL form.
[JsonPropertyName("proxy")]
public SandboxConfigUserPolicyNetworkProxy? Proxy { get; set; }
}
@@ -11202,6 +11993,10 @@ public sealed class SandboxConfig
[JsonPropertyName("addCurrentWorkingDirectory")]
public bool? AddCurrentWorkingDirectory { get; set; }
+ /// Whether the agent may request that an individual command run outside the sandbox, which the host then approves or denies through the usual permission flow. A host capability flag rather than part of the policy: it is stripped from the effective spawn policy and only has an effect while `enabled` is true. Fail-closed, unlike the opt-out flags on this object: omitting it offers no bypass. Default: false (opt-in).
+ [JsonPropertyName("allowBypass")]
+ public bool? AllowBypass { get; set; }
+
/// Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out).
[JsonPropertyName("allowDevToolAccess")]
public bool? AllowDevToolAccess { get; set; }
@@ -11214,6 +12009,24 @@ public sealed class SandboxConfig
[JsonPropertyName("enabled")]
public bool Enabled { get; set; }
+ /// The `sandboxLspServers` counterpart of `managedMcpRoutingLocked`.
+ [JsonInclude]
+ [JsonPropertyName("managedLspRoutingLocked")]
+ internal bool? ManagedLspRoutingLocked { get; set; }
+
+ /// Set by the runtime when a managed policy forced `sandboxMcpServers` on and took the local opt-out away. Provenance rather than policy: it lets a sandbox startup failure point at the administrator instead of a setting the next managed merge would override, and it is ignored when comparing two configs for change. Only the managed merge may set it; a caller-supplied value is stripped.
+ [JsonInclude]
+ [JsonPropertyName("managedMcpRoutingLocked")]
+ internal bool? ManagedMcpRoutingLocked { get; set; }
+
+ /// Whether language servers the session launches are confined by the sandbox. Only an explicit `false` opts out. Ignored while `enabled` is false. Default: true (enabled by default; set to false to opt out).
+ [JsonPropertyName("sandboxLspServers")]
+ public bool? SandboxLspServers { get; set; }
+
+ /// Whether MCP servers the session launches are confined by the sandbox. Only an explicit `false` opts out; doing so also lets remote-MCP egress leave the sandbox, so the flag and `enabled` are always read together. Ignored while `enabled` is false. Default: true (enabled by default; set to false to opt out).
+ [JsonPropertyName("sandboxMcpServers")]
+ public bool? SandboxMcpServers { get; set; }
+
/// User-managed sandbox policy fragment merged into the auto-discovered base policy.
[JsonPropertyName("userPolicy")]
public SandboxConfigUserPolicy? UserPolicy { get; set; }
@@ -11399,7 +12212,7 @@ internal sealed class SessionUpdateOptionsParams
[JsonPropertyName("enableSessionStore")]
public bool? EnableSessionStore { get; set; }
- /// Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset.
+ /// Whether skill loading is enabled. Explicit false disables every source, including a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless an SDK skill provider is registered.
[JsonPropertyName("enableSkills")]
public bool? EnableSkills { get; set; }
@@ -12929,6 +13742,10 @@ public sealed class SubagentSettingsEntry
/// Model override for matching subagents.
[JsonPropertyName("model")]
public string? Model { get; set; }
+
+ /// Whether the configured model strategy is preferred or required.
+ [JsonPropertyName("modelPolicy")]
+ public AgentModelPolicy? ModelPolicy { get; set; }
}
/// Configured per-agent subagent overrides.
@@ -13100,6 +13917,11 @@ public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocat
[JsonPropertyName("message")]
public string? Message { get; set; }
+ /// Optional target session mode applied without submitting an agent prompt.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("mode")]
+ public SessionMode? Mode { get; set; }
+
/// True when the invocation mutated user runtime settings; consumers caching settings should refresh.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("runtimeSettingsChanged")]
@@ -13154,6 +13976,10 @@ public partial class SlashCommandInvocationResultSelectSubcommand : SlashCommand
[Experimental(Diagnostics.Experimental)]
public sealed class SlashCommandTimelineEntry
{
+ /// What the user must do to recover, when the entry reports a failure the runtime knows an action for. The `text` never names a client affordance, so a client that offers one renders it from this value.
+ [JsonPropertyName("remediation")]
+ public RemediationAction? Remediation { get; set; }
+
/// Text displayed for the timeline entry.
[JsonPropertyName("text")]
public string Text { get; set; } = string.Empty;
@@ -16641,6 +17467,10 @@ public sealed class QueuePendingItems
/// Whether this item is a queued user message or a queued slash command / model change.
[JsonPropertyName("kind")]
public QueuePendingItemsKind Kind { get; set; }
+
+ /// Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes.
+ [JsonPropertyName("messageId")]
+ public string? MessageId { get; set; }
}
/// Snapshot of the session's pending queued items and immediate-steering messages.
@@ -17063,27 +17893,6 @@ internal sealed class SessionQueueProcessRequest
public string SessionId { get; set; } = string.Empty;
}
-/// Batch of session events returned by a read, with cursor and continuation metadata.
-[Experimental(Diagnostics.Experimental)]
-public sealed class EventsReadResult
-{
- /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly).
- [JsonPropertyName("cursor")]
- public string Cursor { get; set; } = string.Empty;
-
- /// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history. For a forward read the fallback starts from the beginning of the remaining history; for a backward read it falls back to the tail (the newest window). Because the fallback page is a fresh boundary snapshot rather than a continuation of the requested cursor, it may overlap events the consumer has already rendered — a backward fallback to the tail in particular can repeat the newest window. On 'expired', consumers should reset or rebase their local pagination state (or deduplicate by event id) before continuing from the returned cursor rather than blindly appending/prepending the fallback page.
- [JsonPropertyName("cursorStatus")]
- public EventsCursorStatus CursorStatus { get; set; }
-
- /// Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order.
- [JsonPropertyName("events")]
- public IList Events { get => field ??= []; set; }
-
- /// True when more events are available in the read's direction. For a forward read, true means the batch returned `max` events and more are available immediately. For a backward read, true means older persisted events remain before the returned window.
- [JsonPropertyName("hasMore")]
- public bool HasMore { get; set; }
-}
-
/// Cursor, batch size, and optional long-poll/filter parameters for reading session events.
[Experimental(Diagnostics.Experimental)]
internal sealed class EventLogReadRequest
@@ -17942,6 +18751,40 @@ public sealed class FactoryAbortRequest
public string SessionId { get; set; } = string.Empty;
}
+/// Whether the client authoritatively confirmed its external work stopped.
+[Experimental(Diagnostics.Experimental)]
+public sealed class ClientTaskCancelResult
+{
+ /// True only when the owner confirms that external work stopped before responding.
+ [JsonPropertyName("cancelled")]
+ public bool Cancelled { get; set; }
+}
+
+/// Runtime-to-owner cancellation request for a client-owned task.
+[Experimental(Diagnostics.Experimental)]
+public sealed class ClientTaskCancelRequest
+{
+ /// Opaque identifier shared by coalesced cancellation callers.
+ [JsonPropertyName("cancellationId")]
+ public string CancellationId { get; set; } = string.Empty;
+
+ /// Owner-scoped task key included for correlation.
+ [JsonPropertyName("clientTaskId")]
+ public string ClientTaskId { get; set; } = string.Empty;
+
+ /// Canonical runtime-generated task identifier.
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// Reason the runtime requests cancellation.
+ [JsonPropertyName("reason")]
+ public ClientTaskCancelReason Reason { get; set; }
+
+ /// Session that owns the client task.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
/// Describes a filesystem error.
[Experimental(Diagnostics.Experimental)]
public sealed class SessionFsError
@@ -18795,6 +19638,249 @@ public sealed class GitHubTokenAcquireRequest
public string? SessionId { get; set; }
}
+/// Closed set of public task kinds a connection can negotiate.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct TaskKind : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public TaskKind(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Runtime-owned background agent task.
+ public static TaskKind Agent { get; } = new("agent");
+
+ /// Runtime-owned shell task.
+ public static TaskKind Shell { get; } = new("shell");
+
+ /// Client-owned externally executed task.
+ public static TaskKind Client { get; } = new("client");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(TaskKind left, TaskKind right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(TaskKind left, TaskKind right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is TaskKind other && Equals(other);
+
+ ///
+ public bool Equals(TaskKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override TaskKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, TaskKind value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskKind));
+ }
+ }
+}
+
+
+/// Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally support callback-only events.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct HookType : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public HookType(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Runs before a tool is invoked.
+ public static HookType PreToolUse { get; } = new("preToolUse");
+
+ /// Runs before an MCP tool is invoked.
+ public static HookType PreMcpToolCall { get; } = new("preMcpToolCall");
+
+ /// Runs after a tool completes successfully.
+ public static HookType PostToolUse { get; } = new("postToolUse");
+
+ /// Runs after a tool fails.
+ public static HookType PostToolUseFailure { get; } = new("postToolUseFailure");
+
+ /// Runs after the user submits a prompt.
+ public static HookType UserPromptSubmitted { get; } = new("userPromptSubmitted");
+
+ /// Runs after the runtime transforms the submitted prompt for the model, before it is added to session history.
+ public static HookType UserPromptTransformed { get; } = new("userPromptTransformed");
+
+ /// Runs when a session starts.
+ public static HookType SessionStart { get; } = new("sessionStart");
+
+ /// Runs when a session ends.
+ public static HookType SessionEnd { get; } = new("sessionEnd");
+
+ /// Runs after an agent result is produced.
+ public static HookType PostResult { get; } = new("postResult");
+
+ /// Runs before a pull request description is generated.
+ public static HookType PrePRDescription { get; } = new("prePRDescription");
+
+ /// Runs when the agent encounters an error.
+ public static HookType ErrorOccurred { get; } = new("errorOccurred");
+
+ /// Runs when the agent stops.
+ public static HookType AgentStop { get; } = new("agentStop");
+
+ /// Runs when a subagent starts.
+ public static HookType SubagentStart { get; } = new("subagentStart");
+
+ /// Runs when a subagent stops.
+ public static HookType SubagentStop { get; } = new("subagentStop");
+
+ /// Runs before conversation context is compacted.
+ public static HookType PreCompact { get; } = new("preCompact");
+
+ /// Runs when the agent requests permission.
+ public static HookType PermissionRequest { get; } = new("permissionRequest");
+
+ /// Runs when the agent emits a notification.
+ public static HookType Notification { get; } = new("notification");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(HookType left, HookType right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(HookType left, HookType right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is HookType other && Equals(other);
+
+ ///
+ public bool Equals(HookType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override HookType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, HookType value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HookType));
+ }
+ }
+}
+
+
+/// Configuration tier that contributed a discovered hook action.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct HookOrigin : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public HookOrigin(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Hook loaded from user settings or the user's hook directory.
+ public static HookOrigin User { get; } = new("user");
+
+ /// Hook loaded from repository settings or the repository hook directory.
+ public static HookOrigin Repository { get; } = new("repository");
+
+ /// Hook provided by an enabled installed or explicit plugin. Projectless rows omit projectPath and do not expand a project directory.
+ public static HookOrigin Plugin { get; } = new("plugin");
+
+ /// Hook enforced by centrally managed policy.
+ public static HookOrigin Policy { get; } = new("policy");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(HookOrigin left, HookOrigin right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(HookOrigin left, HookOrigin right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is HookOrigin other && Equals(other);
+
+ ///
+ public bool Equals(HookOrigin other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override HookOrigin Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, HookOrigin value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HookOrigin));
+ }
+ }
+}
+
+
/// Resolved Anthropic adaptive-thinking capability for a model.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -20285,7 +21371,16 @@ public CatalogNetworkFailureReason(string value)
/// The connection was refused or reset.
public static CatalogNetworkFailureReason ConnectionRefused { get; } = new("connection-refused");
- /// The authority returned a status the runtime treats as a failure.
+ /// The configured proxy returned 407 and requires authentication.
+ public static CatalogNetworkFailureReason ProxyAuthenticationRequired { get; } = new("proxy-authentication-required");
+
+ /// The authority rate-limited requests and supplied or implied a bounded cooldown.
+ public static CatalogNetworkFailureReason RateLimited { get; } = new("rate-limited");
+
+ /// The authority returned a transient 5xx response.
+ public static CatalogNetworkFailureReason ServiceUnavailable { get; } = new("service-unavailable");
+
+ /// The authority returned another status the runtime treats as a failure.
public static CatalogNetworkFailureReason HttpStatus { get; } = new("http-status");
/// The response exceeded the permitted size.
@@ -21069,6 +22164,69 @@ public override void Write(Utf8JsonWriter writer, CatalogCandidateKind value, Js
}
+/// Where completed plugin content was staged before atomic promotion.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct PluginInstallStagingMode : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public PluginInstallStagingMode(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// A sibling of the installed-plugins root, outside the recursively watched tree.
+ public static PluginInstallStagingMode External { get; } = new("external");
+
+ /// A sibling of the destination plugin directory, used when external staging is unavailable.
+ public static PluginInstallStagingMode DestinationSibling { get; } = new("destination_sibling");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(PluginInstallStagingMode left, PluginInstallStagingMode right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(PluginInstallStagingMode left, PluginInstallStagingMode right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is PluginInstallStagingMode other && Equals(other);
+
+ ///
+ public bool Equals(PluginInstallStagingMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override PluginInstallStagingMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, PluginInstallStagingMode value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PluginInstallStagingMode));
+ }
+ }
+}
+
+
/// Which tier this directory belongs to.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -22278,6 +23436,132 @@ public override void Write(Utf8JsonWriter writer, SessionSource value, JsonSeria
}
+/// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history (the beginning for a forward read, the tail for a backward read). The fallback page is a fresh boundary snapshot, not a continuation of the requested cursor, so it may overlap already-rendered events; on 'expired' a consumer should reset/rebase its pagination state (or deduplicate by event id) before continuing from the returned cursor.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct EventsCursorStatus : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public EventsCursorStatus(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// The cursor was applied successfully.
+ public static EventsCursorStatus Ok { get; } = new("ok");
+
+ /// The cursor referred to history that is no longer available.
+ public static EventsCursorStatus Expired { get; } = new("expired");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(EventsCursorStatus left, EventsCursorStatus right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(EventsCursorStatus left, EventsCursorStatus right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is EventsCursorStatus other && Equals(other);
+
+ ///
+ public bool Equals(EventsCursorStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override EventsCursorStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, EventsCursorStatus value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsCursorStatus));
+ }
+ }
+}
+
+
+/// Direction to page through the session's persisted event history. 'forward' pages from the cursor toward newer events; 'backward' returns the newest window first (tail-first) and pages toward older events. Events within a returned batch are always chronological (oldest-to-newest), even for a backward read.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct EventsReadDirection : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public EventsReadDirection(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Page from the cursor toward newer events (default).
+ public static EventsReadDirection Forward { get; } = new("forward");
+
+ /// Tail-first: return the newest events and page toward older events.
+ public static EventsReadDirection Backward { get; } = new("backward");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(EventsReadDirection left, EventsReadDirection right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(EventsReadDirection left, EventsReadDirection right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is EventsReadDirection other && Equals(other);
+
+ ///
+ public bool Equals(EventsReadDirection other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override EventsReadDirection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, EventsReadDirection value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsReadDirection));
+ }
+ }
+}
+
+
/// Kind of attention required when status === "attention". Meaningful only when status === "attention".
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -23721,6 +25005,69 @@ public override void Write(Utf8JsonWriter writer, FactoryLogLineKind value, Json
}
+/// Whether the requested preference was already effective or was accepted for later transactional activation.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct ModelSwitchAutoTierStatus : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public ModelSwitchAutoTierStatus(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// The requested preference is already effective. No activation is pending for it, although this request may have cancelled an earlier unclaimed preference reported in `supersededAutoTier`.
+ public static ModelSwitchAutoTierStatus Unchanged { get; } = new("unchanged");
+
+ /// The request was accepted but has not committed. A later user turn using the `auto` model must mint and validate the replacement before it becomes effective.
+ public static ModelSwitchAutoTierStatus Pending { get; } = new("pending");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(ModelSwitchAutoTierStatus left, ModelSwitchAutoTierStatus right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(ModelSwitchAutoTierStatus left, ModelSwitchAutoTierStatus right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is ModelSwitchAutoTierStatus other && Equals(other);
+
+ ///
+ public bool Equals(ModelSwitchAutoTierStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override ModelSwitchAutoTierStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, ModelSwitchAutoTierStatus value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelSwitchAutoTierStatus));
+ }
+ }
+}
+
+
/// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -23985,6 +25332,72 @@ public override void Write(Utf8JsonWriter writer, HistoryRewindUnavailableReason
}
+/// Current normalized autopilot objective lifecycle status.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct AutopilotObjectiveStatus : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public AutopilotObjectiveStatus(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// The objective is actively running.
+ public static AutopilotObjectiveStatus Active { get; } = new("active");
+
+ /// The objective is paused and may be resumed.
+ public static AutopilotObjectiveStatus Paused { get; } = new("paused");
+
+ /// The objective completed.
+ public static AutopilotObjectiveStatus Completed { get; } = new("completed");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(AutopilotObjectiveStatus left, AutopilotObjectiveStatus right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(AutopilotObjectiveStatus left, AutopilotObjectiveStatus right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is AutopilotObjectiveStatus other && Equals(other);
+
+ ///
+ public bool Equals(AutopilotObjectiveStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override AutopilotObjectiveStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, AutopilotObjectiveStatus value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutopilotObjectiveStatus));
+ }
+ }
+}
+
+
/// Whether task execution is synchronously awaited or managed in the background.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -24120,6 +25533,267 @@ public override void Write(Utf8JsonWriter writer, TaskStatus value, JsonSerializ
}
+/// Client-owned tasks always execute outside the runtime in background mode.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct TaskClientExecutionMode : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public TaskClientExecutionMode(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Gets the background value.
+ public static TaskClientExecutionMode Background { get; } = new("background");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(TaskClientExecutionMode left, TaskClientExecutionMode right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(TaskClientExecutionMode left, TaskClientExecutionMode right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is TaskClientExecutionMode other && Equals(other);
+
+ ///
+ public bool Equals(TaskClientExecutionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override TaskClientExecutionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, TaskClientExecutionMode value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskClientExecutionMode));
+ }
+ }
+}
+
+
+/// Connection class owning a client task.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct TaskClientOwnerKind : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public TaskClientOwnerKind(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// A discovered extension connection owns the task.
+ public static TaskClientOwnerKind Extension { get; } = new("extension");
+
+ /// A generic SDK connection owns the task.
+ public static TaskClientOwnerKind Sdk { get; } = new("sdk");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(TaskClientOwnerKind left, TaskClientOwnerKind right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(TaskClientOwnerKind left, TaskClientOwnerKind right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is TaskClientOwnerKind other && Equals(other);
+
+ ///
+ public bool Equals(TaskClientOwnerKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override TaskClientOwnerKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, TaskClientOwnerKind value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskClientOwnerKind));
+ }
+ }
+}
+
+
+/// Presence of the task's bound join.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct TaskClientOwnerPresence : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public TaskClientOwnerPresence(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// The bound session join is connected.
+ public static TaskClientOwnerPresence Connected { get; } = new("connected");
+
+ /// The bound session join is disconnected.
+ public static TaskClientOwnerPresence Disconnected { get; } = new("disconnected");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(TaskClientOwnerPresence left, TaskClientOwnerPresence right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(TaskClientOwnerPresence left, TaskClientOwnerPresence right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is TaskClientOwnerPresence other && Equals(other);
+
+ ///
+ public bool Equals(TaskClientOwnerPresence other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override TaskClientOwnerPresence Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, TaskClientOwnerPresence value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskClientOwnerPresence));
+ }
+ }
+}
+
+
+/// Lifecycle status of a client-owned task.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct TaskClientStatus : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public TaskClientStatus(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// The external owner is actively working.
+ public static TaskClientStatus Running { get; } = new("running");
+
+ /// The external owner is connected but waiting.
+ public static TaskClientStatus Idle { get; } = new("idle");
+
+ /// The owner reported successful completion.
+ public static TaskClientStatus Completed { get; } = new("completed");
+
+ /// The owner reported failure.
+ public static TaskClientStatus Failed { get; } = new("failed");
+
+ /// The owner reported or confirmed cancellation.
+ public static TaskClientStatus Cancelled { get; } = new("cancelled");
+
+ /// The bound owner join disappeared; external executor state is unknown.
+ public static TaskClientStatus Orphaned { get; } = new("orphaned");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(TaskClientStatus left, TaskClientStatus right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(TaskClientStatus left, TaskClientStatus right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is TaskClientStatus other && Equals(other);
+
+ ///
+ public bool Equals(TaskClientStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override TaskClientStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, TaskClientStatus value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskClientStatus));
+ }
+ }
+}
+
+
/// Whether the shell runs inside a managed PTY session or as an independent background process.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -24183,6 +25857,129 @@ public override void Write(Utf8JsonWriter writer, TaskShellInfoAttachmentMode va
}
+/// Discriminator for a client-owned task.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct TaskClientType : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public TaskClientType(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Gets the client value.
+ public static TaskClientType Client { get; } = new("client");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(TaskClientType left, TaskClientType right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(TaskClientType left, TaskClientType right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is TaskClientType other && Equals(other);
+
+ ///
+ public bool Equals(TaskClientType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override TaskClientType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, TaskClientType value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskClientType));
+ }
+ }
+}
+
+
+/// Active status a client owner may publish with a progress update.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct TaskClientActiveStatus : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public TaskClientActiveStatus(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// The external owner is actively working.
+ public static TaskClientActiveStatus Running { get; } = new("running");
+
+ /// The external owner is connected but waiting.
+ public static TaskClientActiveStatus Idle { get; } = new("idle");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(TaskClientActiveStatus left, TaskClientActiveStatus right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(TaskClientActiveStatus left, TaskClientActiveStatus right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is TaskClientActiveStatus other && Equals(other);
+
+ ///
+ public bool Equals(TaskClientActiveStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override TaskClientActiveStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, TaskClientActiveStatus value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskClientActiveStatus));
+ }
+ }
+}
+
+
/// Consumer allowed to call an MCP tool.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -28568,69 +30365,6 @@ public override void Write(Utf8JsonWriter writer, QueuePendingItemsKind value, J
}
-/// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history (the beginning for a forward read, the tail for a backward read). The fallback page is a fresh boundary snapshot, not a continuation of the requested cursor, so it may overlap already-rendered events; on 'expired' a consumer should reset/rebase its pagination state (or deduplicate by event id) before continuing from the returned cursor.
-[Experimental(Diagnostics.Experimental)]
-[JsonConverter(typeof(Converter))]
-[DebuggerDisplay("{Value,nq}")]
-public readonly struct EventsCursorStatus : IEquatable
-{
- private readonly string? _value;
-
- /// Initializes a new instance of the struct.
- /// The value to associate with this .
- [JsonConstructor]
- public EventsCursorStatus(string value)
- {
- ArgumentException.ThrowIfNullOrWhiteSpace(value);
- _value = value;
- }
-
- /// Gets the value associated with this .
- public string Value => _value ?? string.Empty;
-
- /// The cursor was applied successfully.
- public static EventsCursorStatus Ok { get; } = new("ok");
-
- /// The cursor referred to history that is no longer available.
- public static EventsCursorStatus Expired { get; } = new("expired");
-
- /// Returns a value indicating whether two instances are equivalent.
- public static bool operator ==(EventsCursorStatus left, EventsCursorStatus right) => left.Equals(right);
-
- /// Returns a value indicating whether two instances are not equivalent.
- public static bool operator !=(EventsCursorStatus left, EventsCursorStatus right) => !(left == right);
-
- ///
- public override bool Equals(object? obj) => obj is EventsCursorStatus other && Equals(other);
-
- ///
- public bool Equals(EventsCursorStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
-
- ///
- public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
-
- ///
- public override string ToString() => Value;
-
- /// Provides a for serializing instances.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public sealed class Converter : JsonConverter
- {
- ///
- public override EventsCursorStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
- }
-
- ///
- public override void Write(Utf8JsonWriter writer, EventsCursorStatus value, JsonSerializerOptions options)
- {
- GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsCursorStatus));
- }
- }
-}
-
-
/// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -28694,69 +30428,6 @@ public override void Write(Utf8JsonWriter writer, EventsAgentScope value, JsonSe
}
-/// Direction to page through the session's persisted event history. 'forward' pages from the cursor toward newer events; 'backward' returns the newest window first (tail-first) and pages toward older events. Events within a returned batch are always chronological (oldest-to-newest), even for a backward read.
-[Experimental(Diagnostics.Experimental)]
-[JsonConverter(typeof(Converter))]
-[DebuggerDisplay("{Value,nq}")]
-public readonly struct EventsReadDirection : IEquatable
-{
- private readonly string? _value;
-
- /// Initializes a new instance of the struct.
- /// The value to associate with this .
- [JsonConstructor]
- public EventsReadDirection(string value)
- {
- ArgumentException.ThrowIfNullOrWhiteSpace(value);
- _value = value;
- }
-
- /// Gets the value associated with this .
- public string Value => _value ?? string.Empty;
-
- /// Page from the cursor toward newer events (default).
- public static EventsReadDirection Forward { get; } = new("forward");
-
- /// Tail-first: return the newest events and page toward older events.
- public static EventsReadDirection Backward { get; } = new("backward");
-
- /// Returns a value indicating whether two instances are equivalent.
- public static bool operator ==(EventsReadDirection left, EventsReadDirection right) => left.Equals(right);
-
- /// Returns a value indicating whether two instances are not equivalent.
- public static bool operator !=(EventsReadDirection left, EventsReadDirection right) => !(left == right);
-
- ///
- public override bool Equals(object? obj) => obj is EventsReadDirection other && Equals(other);
-
- ///
- public bool Equals(EventsReadDirection other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
-
- ///
- public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
-
- ///
- public override string ToString() => Value;
-
- /// Provides a for serializing instances.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public sealed class Converter : JsonConverter
- {
- ///
- public override EventsReadDirection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
- }
-
- ///
- public override void Write(Utf8JsonWriter writer, EventsReadDirection value, JsonSerializerOptions options)
- {
- GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsReadDirection));
- }
- }
-}
-
-
/// Client population used for the prediction baseline.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -29147,6 +30818,69 @@ public override void Write(Utf8JsonWriter writer, SessionVisibilityStatus value,
}
+/// Why the runtime requests client-task cancellation.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct ClientTaskCancelReason : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public ClientTaskCancelReason(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// A caller requested task cancellation.
+ public static ClientTaskCancelReason CancelRequested { get; } = new("cancel_requested");
+
+ /// The session is shutting down.
+ public static ClientTaskCancelReason SessionShutdown { get; } = new("session_shutdown");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(ClientTaskCancelReason left, ClientTaskCancelReason right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(ClientTaskCancelReason left, ClientTaskCancelReason right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is ClientTaskCancelReason other && Equals(other);
+
+ ///
+ public bool Equals(ClientTaskCancelReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override ClientTaskCancelReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, ClientTaskCancelReason value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ClientTaskCancelReason));
+ }
+ }
+}
+
+
/// Error classification.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -29555,13 +31289,14 @@ public async Task PingAsync(string? message = null, CancellationToke
/// Performs the SDK server connection handshake and validates the optional connection token. Marked internal because this is JSON-RPC transport plumbing invoked automatically by an SDK client's own `connect()` wrapper, not a user-facing method. Stays internal as long as the SDK client owns the handshake; would only become public if the SDK ever exposed the raw schema surface to consumers without a connection wrapper.
/// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events.
/// Identity of the integrating host. Optional; omit it to keep the default attribution.
+ /// Task kinds this connection can decode when observing session tasks. Omit to retain agent and shell compatibility.
/// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN.
/// The to monitor for cancellation requests. The default is .
/// Handshake result reporting the server's protocol version and package version on success.
[Experimental(Diagnostics.Experimental)]
- internal async Task ConnectAsync(bool? enableGitHubTelemetryForwarding = null, ConnectClientInfo? clientInfo = null, string? token = null, CancellationToken cancellationToken = default)
+ internal async Task ConnectAsync(bool? enableGitHubTelemetryForwarding = null, ConnectClientInfo? clientInfo = null, IList? supportedTaskKinds = null, string? token = null, CancellationToken cancellationToken = default)
{
- var request = new ConnectRequest { EnableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding, ClientInfo = clientInfo, Token = token };
+ var request = new ConnectRequest { EnableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding, ClientInfo = clientInfo, SupportedTaskKinds = supportedTaskKinds, Token = token };
return await CopilotClient.InvokeRpcAsync(_rpc, "connect", [request], cancellationToken);
}
@@ -29573,6 +31308,12 @@ public async Task RegisterExtensionLaunchProviderAsync(CancellationToken cancell
await CopilotClient.InvokeRpcAsync(_rpc, "registerExtensionLaunchProvider", [], cancellationToken);
}
+ /// Hooks APIs.
+ public ServerHooksApi Hooks =>
+ field ??
+ Interlocked.CompareExchange(ref field, new(_rpc), null) ??
+ field;
+
/// Models APIs.
public ServerModelsApi Models =>
field ??
@@ -29688,6 +31429,29 @@ public async Task RegisterExtensionLaunchProviderAsync(CancellationToken cancell
field;
}
+/// Provides server-scoped Hooks APIs.
+[Experimental(Diagnostics.Experimental)]
+public sealed class ServerHooksApi
+{
+ private readonly JsonRpc _rpc;
+
+ internal ServerHooksApi(JsonRpc rpc)
+ {
+ _rpc = rpc;
+ }
+
+ /// Discovers hook actions enabled under server-side discovery settings from user, repository, plugin, and managed-policy sources.
+ /// Optional project directory paths whose trusted repository and project-expanded plugin hooks should be discovered. When omitted or empty, user, managed-policy, and globally enabled installed or explicit plugin hooks are returned without project expansion.
+ /// When true, omit host-owned user and plugin hook rows and their diagnostics. Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings.
+ /// The to monitor for cancellation requests. The default is .
+ /// Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources.
+ public async Task DiscoverAsync(IList? projectPaths = null, bool? excludeHostHooks = null, CancellationToken cancellationToken = default)
+ {
+ var request = new HooksDiscoverRequest { ProjectPaths = projectPaths, ExcludeHostHooks = excludeHostHooks };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "hooks.discover", [request], cancellationToken);
+ }
+}
+
/// Provides server-scoped Models APIs.
[Experimental(Diagnostics.Experimental)]
public sealed class ServerModelsApi
@@ -29920,12 +31684,13 @@ public async Task UpdateAsync(string name, object config, CancellationToken canc
/// Removes an MCP server from user configuration.
/// Name of the MCP server to remove.
+ /// OAuth Client ID Metadata Document URL whose persisted credentials should also be removed.
/// The to monitor for cancellation requests. The default is .
- public async Task RemoveAsync(string name, CancellationToken cancellationToken = default)
+ public async Task RemoveAsync(string name, string? authClientIdMetadataUrl = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(name);
- var request = new McpConfigRemoveRequest { Name = name };
+ var request = new McpConfigRemoveRequest { Name = name, AuthClientIdMetadataUrl = authClientIdMetadataUrl };
await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.remove", [request], cancellationToken);
}
@@ -30014,7 +31779,7 @@ internal ServerCatalogApi(JsonRpc rpc)
/// Requests a bounded catalog search. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with search available returns inert candidate summaries, each with an opaque single-use handle scoped to this runtime instance; a runtime without it returns the typed search-unavailable result. Public authorities may be searched anonymously, while an authority that requires credentials yields the typed authentication-required result. All returned text, URLs, and package metadata are untrusted external data and can never trigger instructions, tools, or installation. Read-only: nothing is installed, configured, or persisted.
/// Protocol version and capabilities the caller requires.
- /// Free-text search query. Never written to logs or telemetry.
+ /// Free-text search query. Persisted as tool input for session continuity, but omitted from telemetry.
/// Maximum number of candidates to return. Defaults to 10 when omitted.
/// Restrict results to these candidate kinds. When omitted, every kind the runtime supports is searched.
/// The to monitor for cancellation requests. The default is .
@@ -30458,6 +32223,13 @@ public async Task ReadAsync(CancellationToken cancell
{
return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.read", [], cancellationToken);
}
+
+ /// Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `<cacheHome>/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed.
+ /// The to monitor for cancellation requests. The default is .
+ public async Task ClearCacheAsync(CancellationToken cancellationToken = default)
+ {
+ await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.clearCache", [], cancellationToken);
+ }
}
/// Provides server-scoped Runtime APIs.
@@ -30631,6 +32403,21 @@ internal async Task GetMetadataAsync(string sessionId
return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.getMetadata", [request], cancellationToken);
}
+ /// Reads a page of durable events directly from a local session's persisted journal without creating, resuming, or activating the session. The initial backward read uses a bounded tail scan for fast first paint; cursor continuations preserve the session event-log paging semantics. Persisted events may omit payloads that are reconstructed only for an active session.
+ /// Session ID whose persisted event journal should be read.
+ /// Opaque cursor returned by a previous persisted-event read. Omit on the first call.
+ /// Maximum number of events to return in this batch (1–1000, default 200).
+ /// Direction to page through persisted history. Forward starts at the beginning; backward starts with the newest events. Events in each page remain chronological.
+ /// The to monitor for cancellation requests. The default is .
+ /// Batch of session events returned by a read, with cursor and continuation metadata.
+ public async Task ReadPersistedEventsAsync(string sessionId, string? cursor = null, long? max = null, EventsReadDirection? direction = null, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(sessionId);
+
+ var request = new SessionsReadPersistedEventsRequest { SessionId = sessionId, Cursor = cursor, Max = max, Direction = direction };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.readPersistedEvents", [request], cancellationToken);
+ }
+
/// Lists recent local session IDs that contain user-visible history, omitting housekeeping-only sessions.
/// Maximum number of session IDs to return.
/// The to monitor for cancellation requests. The default is .
@@ -31035,6 +32822,12 @@ internal SessionRpc(CopilotSession session)
Interlocked.CompareExchange(ref field, new(_session), null) ??
field;
+ /// AutopilotObjective APIs.
+ public AutopilotObjectiveApi AutopilotObjective =>
+ field ??
+ Interlocked.CompareExchange(ref field, new(_session), null) ??
+ field;
+
/// Completions APIs.
public CompletionsApi Completions =>
field ??
@@ -31927,9 +33720,9 @@ internal ModelApi(CopilotSession session)
_session = session;
}
- /// Gets the currently selected model for the session.
+ /// Gets the session's authoritative model snapshot, including the committed Auto preference and any newer unclaimed Auto preference waiting for a future user turn.
/// The to monitor for cancellation requests. The default is .
- /// The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
+ /// The session's authoritative model snapshot. Auto preference fields are configuration for the virtual `auto` model and do not change the selected model identifier. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
public async Task GetCurrentAsync(CancellationToken cancellationToken = default)
{
_session.ThrowIfDisposed();
@@ -31940,12 +33733,13 @@ public async Task GetCurrentAsync(CancellationToken cancellationTo
/// Switches the session to a model and optional reasoning configuration.
/// Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model.
+ /// Optional Auto routing preference to stage atomically with selecting `auto`. Pass null to return to provider-default Auto routing. This field is rejected when `modelId` is not `auto`.
/// Reasoning effort level to use for the model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. "none" disables reasoning. When omitted, no effort override is applied.
/// Reasoning summary mode to request for supported model clients.
/// Output verbosity level to request for supported models.
/// Override individual model capabilities resolved by the runtime.
/// Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier.
- /// Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted.
+ /// Origin to record on the effective `session.model_change` event for trusted in-process calls. Transport SDK calls are always recorded as `sdk`, regardless of this value.
/// When true, defer this switch (enqueue it) if another model change is already queued, even when no turn is active — so it drains last (FIFO) and wins over the already-queued change. Intended for genuine user-initiated model selections; internal restore/reapply switches omit it and apply immediately when no turn is active. When no other model change is queued this has no effect (a switch still applies immediately unless a turn is active).
/// Explicit response to a model-switch compaction preflight. Omit to request a confirmation projection when compaction is necessary.
/// When true, evaluate context-window compaction policy before applying the switch.
@@ -31955,18 +33749,32 @@ public async Task GetCurrentAsync(CancellationToken cancellationTo
/// Optional settings context and explicit-override flags used to persist a picker selection.
/// The to monitor for cancellation requests. The default is .
/// The model identifier active on the session after the switch.
- public async Task SwitchToAsync(string modelId, string? reasoningEffort = null, ReasoningSummary? reasoningSummary = null, Verbosity? verbosity = null, ModelCapabilitiesOverride? modelCapabilities = null, ContextTier? contextTier = null, ModelChangeSource? source = null, bool? deferIfModelChangeQueued = null, string? compactionDecision = null, bool? runCompactionPreflight = null, string? repoScope = null, string? modelChangeScope = null, bool? requireAvailable = null, ModelPickerPersistenceRequest? pickerPersistence = null, CancellationToken cancellationToken = default)
+ public async Task SwitchToAsync(string modelId, AutoTier? autoTier = null, string? reasoningEffort = null, ReasoningSummary? reasoningSummary = null, Verbosity? verbosity = null, ModelCapabilitiesOverride? modelCapabilities = null, ContextTier? contextTier = null, ModelChangeSource? source = null, bool? deferIfModelChangeQueued = null, string? compactionDecision = null, bool? runCompactionPreflight = null, string? repoScope = null, string? modelChangeScope = null, bool? requireAvailable = null, ModelPickerPersistenceRequest? pickerPersistence = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(modelId);
_session.ThrowIfDisposed();
- var request = new ModelSwitchToRequest { SessionId = _session.SessionId, ModelId = modelId, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ModelCapabilities = modelCapabilities, ContextTier = contextTier, Source = source, DeferIfModelChangeQueued = deferIfModelChangeQueued, CompactionDecision = compactionDecision, RunCompactionPreflight = runCompactionPreflight, RepoScope = repoScope, ModelChangeScope = modelChangeScope, RequireAvailable = requireAvailable, PickerPersistence = pickerPersistence };
+ var request = new ModelSwitchToRequest { SessionId = _session.SessionId, ModelId = modelId, AutoTier = autoTier, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ModelCapabilities = modelCapabilities, ContextTier = contextTier, Source = source, DeferIfModelChangeQueued = deferIfModelChangeQueued, CompactionDecision = compactionDecision, RunCompactionPreflight = runCompactionPreflight, RepoScope = repoScope, ModelChangeScope = modelChangeScope, RequireAvailable = requireAvailable, PickerPersistence = pickerPersistence };
return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.switchTo", [request], cancellationToken);
}
+ /// Requests an Auto preference change without changing the session's selected model. The latest unclaimed request wins; the runtime commits it only after a later prompt using the `auto` model mints a usable model and token pair. A `pending` response confirms that the request was accepted, not that it committed. Observe eventual success through `session.model_change`, failure through the ephemeral `session.auto_tier_switch_failed` event, or current unclaimed state through `session.model.getCurrent`.
+ /// Auto preference to activate when a future user turn using the `auto` model safely mints a replacement model and token pair. Pass null to return to provider-default Auto routing.
+ /// Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted.
+ /// The to monitor for cancellation requests. The default is .
+ /// Immediate acknowledgement and Auto preference snapshot after a switch request. This result never implies that a pending preference committed.
+ public async Task SwitchAutoTierAsync(AutoTier? autoTier, ModelChangeSource? source = null, CancellationToken cancellationToken = default)
+ {
+ _session.ThrowIfDisposed();
+
+ var request = new ModelSwitchAutoTierRequest { SessionId = _session.SessionId, AutoTier = autoTier, Source = source };
+ return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.switchAutoTier", [request], cancellationToken);
+ }
+
/// Resolves and applies organization-managed and repository model overlays.
/// Model required by device-managed policy, when configured.
/// Model required by server-managed policy, when configured.
+ /// Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and an explicit user selection still wins.
/// Model selected by repository settings, when configured.
/// Reasoning effort selected by repository settings, when configured.
/// Context tier selected by repository settings, when configured.
@@ -31974,11 +33782,11 @@ public async Task SwitchToAsync(string modelId, string? rea
/// Whether the overlay is being applied while resuming a deferred session.
/// The to monitor for cancellation requests. The default is .
/// The model identifier active on the session after the switch.
- internal async Task ApplyStartupOverlayAsync(string? deviceManagedModel = null, string? serverManagedModel = null, string? repoModel = null, string? repoReasoningEffort = null, string? repoContextTier = null, string? cliModel = null, bool? deferredResume = null, CancellationToken cancellationToken = default)
+ internal async Task ApplyStartupOverlayAsync(string? deviceManagedModel = null, string? serverManagedModel = null, string? policyHelperModel = null, string? repoModel = null, string? repoReasoningEffort = null, string? repoContextTier = null, string? cliModel = null, bool? deferredResume = null, CancellationToken cancellationToken = default)
{
_session.ThrowIfDisposed();
- var request = new ModelApplyStartupOverlayRequest { SessionId = _session.SessionId, DeviceManagedModel = deviceManagedModel, ServerManagedModel = serverManagedModel, RepoModel = repoModel, RepoReasoningEffort = repoReasoningEffort, RepoContextTier = repoContextTier, CliModel = cliModel, DeferredResume = deferredResume };
+ var request = new ModelApplyStartupOverlayRequest { SessionId = _session.SessionId, DeviceManagedModel = deviceManagedModel, ServerManagedModel = serverManagedModel, PolicyHelperModel = policyHelperModel, RepoModel = repoModel, RepoReasoningEffort = repoReasoningEffort, RepoContextTier = repoContextTier, CliModel = cliModel, DeferredResume = deferredResume };
return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.applyStartupOverlay", [request], cancellationToken);
}
@@ -32376,6 +34184,29 @@ public async Task DiffAsync(WorkspaceDiffMode mode, bool? i
}
}
+/// Provides session-scoped AutopilotObjective APIs.
+[Experimental(Diagnostics.Experimental)]
+public sealed class AutopilotObjectiveApi
+{
+ private readonly CopilotSession _session;
+
+ internal AutopilotObjectiveApi(CopilotSession session)
+ {
+ _session = session;
+ }
+
+ /// Reads the current canonical autopilot objective state for this session.
+ /// The to monitor for cancellation requests. The default is .
+ /// Canonical runtime state for the session's current autopilot objective.
+ public async Task GetStateAsync(CancellationToken cancellationToken = default)
+ {
+ _session.ThrowIfDisposed();
+
+ var request = new SessionAutopilotObjectiveGetStateRequest { SessionId = _session.SessionId };
+ return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.autopilotObjective.getState", [request], cancellationToken);
+ }
+}
+
/// Provides session-scoped Completions APIs.
[Experimental(Diagnostics.Experimental)]
public sealed class CompletionsApi
@@ -32584,6 +34415,41 @@ public async Task ListAsync(CancellationToken cancellationToken = defa
return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tasks.list", [request], cancellationToken);
}
+ /// Registers a client-owned task, or reclaims an orphaned task belonging to the same extension principal.
+ /// Task kind.
+ /// Owner-scoped idempotency key used for registration and reclaim.
+ /// Human-readable description of the external work.
+ /// Whether the owner supports runtime cancellation requests.
+ /// Optional short display name for the external work.
+ /// Expected current sequence for idempotent registration or orphan reclaim.
+ /// The to monitor for cancellation requests. The default is .
+ /// Result of registering or reclaiming a client-owned task.
+ public async Task RegisterAsync(TaskClientType type, string clientTaskId, string description, bool cancellable, string? displayName = null, long? expectedSequence = null, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(clientTaskId);
+ ArgumentNullException.ThrowIfNull(description);
+ _session.ThrowIfDisposed();
+
+ var request = new TasksRegisterRequest { SessionId = _session.SessionId, Type = type, ClientTaskId = clientTaskId, Description = description, Cancellable = cancellable, DisplayName = displayName, ExpectedSequence = expectedSequence };
+ return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tasks.register", [request], cancellationToken);
+ }
+
+ /// Publishes generic progress or a terminal outcome for a client-owned task.
+ /// Canonical runtime-generated task identifier.
+ /// Owner update sequence to apply.
+ /// Progress or terminal update payload.
+ /// The to monitor for cancellation requests. The default is .
+ /// Result of publishing a client-owned task update.
+ public async Task UpdateAsync(string id, long sequence, TaskClientUpdate update, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(id);
+ ArgumentNullException.ThrowIfNull(update);
+ _session.ThrowIfDisposed();
+
+ var request = new TasksUpdateRequest { SessionId = _session.SessionId, Id = id, Sequence = sequence, Update = update };
+ return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tasks.update", [request], cancellationToken);
+ }
+
/// Refreshes metadata for any detached background shells the runtime knows about.
/// The to monitor for cancellation requests. The default is .
/// Refresh metadata for any detached background shells the runtime knows about. Use after a long pause to pick up exit/output state for shells running outside the agent loop.
@@ -33435,7 +35301,7 @@ internal OptionsApi(CopilotSession session)
/// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism.
/// Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt).
/// Whether to enable cross-session store writes and reads.
- /// Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset.
+ /// Whether skill loading is enabled. Explicit false disables every source, including a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless an SDK skill provider is registered.
/// Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier.
/// Optional session limits. Pass null to clear the session limits.
/// The to monitor for cancellation requests. The default is .
@@ -35287,6 +37153,17 @@ public interface IFactoryHandler
Task AbortAsync(FactoryAbortRequest request, CancellationToken cancellationToken = default);
}
+/// Handles `tasks` client session API methods.
+[Experimental(Diagnostics.Experimental)]
+public interface ITasksHandler
+{
+ /// Asks the client currently bound to a client-owned session task to confirm that its external work stopped.
+ /// Runtime-to-owner cancellation request for a client-owned task.
+ /// The to monitor for cancellation requests. The default is .
+ /// Whether the client authoritatively confirmed its external work stopped.
+ Task CancelAsync(ClientTaskCancelRequest request, CancellationToken cancellationToken = default);
+}
+
/// Handles `sessionFs` client session API methods.
[Experimental(Diagnostics.Experimental)]
public interface ISessionFsHandler
@@ -35387,6 +37264,9 @@ public sealed class ClientSessionApiHandlers
/// Optional handler for Factory client session API methods.
public IFactoryHandler? Factory { get; set; }
+ /// Optional handler for Tasks client session API methods.
+ public ITasksHandler? Tasks { get; set; }
+
/// Optional handler for SessionFs client session API methods.
public ISessionFsHandler? SessionFs { get; set; }
@@ -35422,6 +37302,12 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) =>
+ {
+ var handler = getHandlers(request.SessionId).Tasks;
+ if (handler is null) throw new InvalidOperationException($"No tasks handler registered for session: {request.SessionId}");
+ return await handler.CancelAsync(request, cancellationToken);
+ }), singleObjectParam: true);
rpc.SetLocalRpcMethod("sessionFs.readFile", (Func>)(async (request, cancellationToken) =>
{
var handler = getHandlers(request.SessionId).SessionFs;
@@ -35640,6 +37526,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedCancelPhase), TypeInfoPropertyName = "SessionEventsAgentInterruptedCancelPhase")]
[JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedData), TypeInfoPropertyName = "SessionEventsAgentInterruptedData")]
[JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedEvent), TypeInfoPropertyName = "SessionEventsAgentInterruptedEvent")]
+[JsonSerializable(typeof(GitHub.Copilot.AgentModelPolicy), TypeInfoPropertyName = "SessionEventsAgentModelPolicy")]
+[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseActivityEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseActivityEvent")]
[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseCompletedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseCompletedEvent")]
[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseFailedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseFailedEvent")]
[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseStartedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseStartedEvent")]
@@ -35713,6 +37601,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchRequestedEvent), TypeInfoPropertyName = "SessionEventsAutoModeSwitchRequestedEvent")]
[JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchResponse), TypeInfoPropertyName = "SessionEventsAutoModeSwitchResponse")]
[JsonSerializable(typeof(GitHub.Copilot.AutoTier), TypeInfoPropertyName = "SessionEventsAutoTier")]
+[JsonSerializable(typeof(GitHub.Copilot.AutoTierSwitchFailureReason), TypeInfoPropertyName = "SessionEventsAutoTierSwitchFailureReason")]
[JsonSerializable(typeof(GitHub.Copilot.AutopilotObjectiveChangedOperation), TypeInfoPropertyName = "SessionEventsAutopilotObjectiveChangedOperation")]
[JsonSerializable(typeof(GitHub.Copilot.AutopilotObjectiveChangedStatus), TypeInfoPropertyName = "SessionEventsAutopilotObjectiveChangedStatus")]
[JsonSerializable(typeof(GitHub.Copilot.BinaryAssetReference), TypeInfoPropertyName = "SessionEventsBinaryAssetReference")]
@@ -35745,6 +37634,10 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(GitHub.Copilot.CompactionCompleteCompactionTokensUsed), TypeInfoPropertyName = "SessionEventsCompactionCompleteCompactionTokensUsed")]
[JsonSerializable(typeof(GitHub.Copilot.CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail), TypeInfoPropertyName = "SessionEventsCompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail")]
[JsonSerializable(typeof(GitHub.Copilot.CompactionTrigger), TypeInfoPropertyName = "SessionEventsCompactionTrigger")]
+[JsonSerializable(typeof(GitHub.Copilot.CompletionReceiptEventRange), TypeInfoPropertyName = "SessionEventsCompletionReceiptEventRange")]
+[JsonSerializable(typeof(GitHub.Copilot.CompletionReceiptFinalTool), TypeInfoPropertyName = "SessionEventsCompletionReceiptFinalTool")]
+[JsonSerializable(typeof(GitHub.Copilot.CompletionReceiptStopReason), TypeInfoPropertyName = "SessionEventsCompletionReceiptStopReason")]
+[JsonSerializable(typeof(GitHub.Copilot.CompletionReceiptToolStatus), TypeInfoPropertyName = "SessionEventsCompletionReceiptToolStatus")]
[JsonSerializable(typeof(GitHub.Copilot.ContextTier), TypeInfoPropertyName = "SessionEventsContextTier")]
[JsonSerializable(typeof(GitHub.Copilot.CustomAgentsUpdatedAgent), TypeInfoPropertyName = "SessionEventsCustomAgentsUpdatedAgent")]
[JsonSerializable(typeof(GitHub.Copilot.ElicitationCompletedAction), TypeInfoPropertyName = "SessionEventsElicitationCompletedAction")]
@@ -35782,7 +37675,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(GitHub.Copilot.FusionFollowUpAction), TypeInfoPropertyName = "SessionEventsFusionFollowUpAction")]
[JsonSerializable(typeof(GitHub.Copilot.FusionFollowUpRecommendation), TypeInfoPropertyName = "SessionEventsFusionFollowUpRecommendation")]
[JsonSerializable(typeof(GitHub.Copilot.FusionPattern), TypeInfoPropertyName = "SessionEventsFusionPattern")]
+[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseActivityKind), TypeInfoPropertyName = "SessionEventsFusionPhaseActivityKind")]
[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseKind), TypeInfoPropertyName = "SessionEventsFusionPhaseKind")]
+[JsonSerializable(typeof(GitHub.Copilot.FusionPhasePlanStep), TypeInfoPropertyName = "SessionEventsFusionPhasePlanStep")]
[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseStatus), TypeInfoPropertyName = "SessionEventsFusionPhaseStatus")]
[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseUsage), TypeInfoPropertyName = "SessionEventsFusionPhaseUsage")]
[JsonSerializable(typeof(GitHub.Copilot.FusionProjectionMode), TypeInfoPropertyName = "SessionEventsFusionProjectionMode")]
@@ -35825,6 +37720,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(GitHub.Copilot.McpOauthWWWAuthenticateParams), TypeInfoPropertyName = "SessionEventsMcpOauthWWWAuthenticateParams")]
[JsonSerializable(typeof(GitHub.Copilot.McpPromptsListChangedEvent), TypeInfoPropertyName = "SessionEventsMcpPromptsListChangedEvent")]
[JsonSerializable(typeof(GitHub.Copilot.McpResourcesListChangedEvent), TypeInfoPropertyName = "SessionEventsMcpResourcesListChangedEvent")]
+[JsonSerializable(typeof(GitHub.Copilot.McpServerMetadata), TypeInfoPropertyName = "SessionEventsMcpServerMetadata")]
[JsonSerializable(typeof(GitHub.Copilot.McpServerSource), TypeInfoPropertyName = "SessionEventsMcpServerSource")]
[JsonSerializable(typeof(GitHub.Copilot.McpServerStatus), TypeInfoPropertyName = "SessionEventsMcpServerStatus")]
[JsonSerializable(typeof(GitHub.Copilot.McpServerTransport), TypeInfoPropertyName = "SessionEventsMcpServerTransport")]
@@ -35898,6 +37794,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(GitHub.Copilot.PromptCacheBreakData), TypeInfoPropertyName = "SessionEventsPromptCacheBreakData")]
[JsonSerializable(typeof(GitHub.Copilot.PromptCacheBreakEvent), TypeInfoPropertyName = "SessionEventsPromptCacheBreakEvent")]
[JsonSerializable(typeof(GitHub.Copilot.ReasoningSummary), TypeInfoPropertyName = "SessionEventsReasoningSummary")]
+[JsonSerializable(typeof(GitHub.Copilot.RemediationAction), TypeInfoPropertyName = "SessionEventsRemediationAction")]
[JsonSerializable(typeof(GitHub.Copilot.SamplingCompletedData), TypeInfoPropertyName = "SessionEventsSamplingCompletedData")]
[JsonSerializable(typeof(GitHub.Copilot.SamplingCompletedEvent), TypeInfoPropertyName = "SessionEventsSamplingCompletedEvent")]
[JsonSerializable(typeof(GitHub.Copilot.SamplingRequestedData), TypeInfoPropertyName = "SessionEventsSamplingRequestedData")]
@@ -36052,6 +37949,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(AuthIdentity))]
[JsonSerializable(typeof(AuthInfo))]
[JsonSerializable(typeof(AuthValidationError))]
+[JsonSerializable(typeof(AutopilotObjectiveCreditLimit))]
+[JsonSerializable(typeof(AutopilotObjectiveGetStateResult))]
+[JsonSerializable(typeof(AutopilotObjectiveState))]
[JsonSerializable(typeof(BuiltInModelCatalog))]
[JsonSerializable(typeof(BuiltInModelCatalogEntry))]
[JsonSerializable(typeof(BuiltinToolDescriptor))]
@@ -36084,6 +37984,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(CatalogNegotiatedContract))]
[JsonSerializable(typeof(CatalogSearchRequest))]
[JsonSerializable(typeof(CatalogSearchResult))]
+[JsonSerializable(typeof(ClientTaskCancelRequest))]
+[JsonSerializable(typeof(ClientTaskCancelResult))]
[JsonSerializable(typeof(CommandList))]
[JsonSerializable(typeof(CommandsFinalizeInvocationEffectRequest))]
[JsonSerializable(typeof(CommandsFinalizeInvocationEffectRequestEffect))]
@@ -36129,6 +38031,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(DiscoveredExtensions))]
[JsonSerializable(typeof(DiscoveredExtensionsDisableRequest))]
[JsonSerializable(typeof(DiscoveredExtensionsEnableRequest))]
+[JsonSerializable(typeof(DiscoveredHook))]
[JsonSerializable(typeof(DiscoveredMcpServer))]
[JsonSerializable(typeof(EnqueueCommandParams))]
[JsonSerializable(typeof(EnqueueCommandResult))]
@@ -36213,6 +38116,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(HistorySummarizeForHandoffResult))]
[JsonSerializable(typeof(HistoryTruncateRequest))]
[JsonSerializable(typeof(HistoryTruncateResult))]
+[JsonSerializable(typeof(HooksDiscoverRequest))]
+[JsonSerializable(typeof(HooksDiscoverResult))]
[JsonSerializable(typeof(IDictionary))]
[JsonSerializable(typeof(IList))]
[JsonSerializable(typeof(IList))]
@@ -36387,6 +38292,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(ModelPolicy))]
[JsonSerializable(typeof(ModelSetReasoningEffortRequest))]
[JsonSerializable(typeof(ModelSetReasoningEffortResult))]
+[JsonSerializable(typeof(ModelSwitchAutoTierRequest))]
+[JsonSerializable(typeof(ModelSwitchAutoTierResult))]
[JsonSerializable(typeof(ModelSwitchConfirmation))]
[JsonSerializable(typeof(ModelSwitchToRequest))]
[JsonSerializable(typeof(ModelSwitchToResult))]
@@ -36586,6 +38493,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(SessionAuthLogoutUserRequest))]
[JsonSerializable(typeof(SessionAuthStatus))]
[JsonSerializable(typeof(SessionAuthSwitchRequest))]
+[JsonSerializable(typeof(SessionAutopilotObjectiveGetStateRequest))]
[JsonSerializable(typeof(SessionBulkDeleteResult))]
[JsonSerializable(typeof(SessionCancelAllBackgroundAgentsRequest))]
[JsonSerializable(typeof(SessionCanvasListOpenRequest))]
@@ -36760,6 +38668,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(SessionsLoadDeferredRepoHooksRequest))]
[JsonSerializable(typeof(SessionsOpenProgress))]
[JsonSerializable(typeof(SessionsPruneOldRequest))]
+[JsonSerializable(typeof(SessionsReadPersistedEventsRequest))]
[JsonSerializable(typeof(SessionsRegisterExtensionToolsOnSessionOptions))]
[JsonSerializable(typeof(SessionsReleaseLockRequest))]
[JsonSerializable(typeof(SessionsReleaseLockResult))]
@@ -36806,27 +38715,34 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH
[JsonSerializable(typeof(SlashCommandSelectSubcommandOption))]
[JsonSerializable(typeof(SlashCommandTimelineEntry))]
[JsonSerializable(typeof(SubagentSettingsEntry))]
+[JsonSerializable(typeof(TaskClientInfo))]
+[JsonSerializable(typeof(TaskClientOwner))]
+[JsonSerializable(typeof(TaskClientUpdate))]
[JsonSerializable(typeof(TaskCompleteData))]
[JsonSerializable(typeof(TaskCompletionDecision))]
[JsonSerializable(typeof(TaskInfo))]
[JsonSerializable(typeof(TaskList))]
+[JsonSerializable(typeof(TaskProgress))]
[JsonSerializable(typeof(TaskProgressLine))]
[JsonSerializable(typeof(TasksCancelRequest))]
[JsonSerializable(typeof(TasksCancelResult))]
[JsonSerializable(typeof(TasksGetCurrentPromotableResult))]
[JsonSerializable(typeof(TasksGetProgressRequest))]
[JsonSerializable(typeof(TasksGetProgressResult))]
-[JsonSerializable(typeof(TasksGetProgressResultProgress))]
[JsonSerializable(typeof(TasksPromoteCurrentToBackgroundResult))]
[JsonSerializable(typeof(TasksPromoteToBackgroundRequest))]
[JsonSerializable(typeof(TasksPromoteToBackgroundResult))]
[JsonSerializable(typeof(TasksRefreshResult))]
+[JsonSerializable(typeof(TasksRegisterRequest))]
+[JsonSerializable(typeof(TasksRegisterResult))]
[JsonSerializable(typeof(TasksRemoveRequest))]
[JsonSerializable(typeof(TasksRemoveResult))]
[JsonSerializable(typeof(TasksSendMessageRequest))]
[JsonSerializable(typeof(TasksSendMessageResult))]
[JsonSerializable(typeof(TasksStartAgentRequest))]
[JsonSerializable(typeof(TasksStartAgentResult))]
+[JsonSerializable(typeof(TasksUpdateRequest))]
+[JsonSerializable(typeof(TasksUpdateResult))]
[JsonSerializable(typeof(TasksWaitForPendingResult))]
[JsonSerializable(typeof(TelemetrySetFeatureOverridesRequest))]
[JsonSerializable(typeof(Tool))]
diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs
index 6bfe202bff..58c8a2c9cc 100644
--- a/dotnet/src/Generated/SessionEvents.cs
+++ b/dotnet/src/Generated/SessionEvents.cs
@@ -26,6 +26,7 @@ namespace GitHub.Copilot;
IgnoreUnrecognizedTypeDiscriminators = true)]
[JsonDerivedType(typeof(AbortEvent), "abort")]
[JsonDerivedType(typeof(AgentInterruptedEvent), "agent.interrupted")]
+[JsonDerivedType(typeof(AssistantFusionPhaseActivityEvent), "assistant.fusion_phase_activity")]
[JsonDerivedType(typeof(AssistantFusionPhaseCompletedEvent), "assistant.fusion_phase_completed")]
[JsonDerivedType(typeof(AssistantFusionPhaseFailedEvent), "assistant.fusion_phase_failed")]
[JsonDerivedType(typeof(AssistantFusionPhaseStartedEvent), "assistant.fusion_phase_started")]
@@ -83,6 +84,7 @@ namespace GitHub.Copilot;
[JsonDerivedType(typeof(SessionLimitsExhaustedCompletedEvent), "session_limits_exhausted.completed")]
[JsonDerivedType(typeof(SessionLimitsExhaustedRequestedEvent), "session_limits_exhausted.requested")]
[JsonDerivedType(typeof(SessionAutoModeResolvedEvent), "session.auto_mode_resolved")]
+[JsonDerivedType(typeof(SessionAutoTierSwitchFailedEvent), "session.auto_tier_switch_failed")]
[JsonDerivedType(typeof(SessionAutopilotObjectiveChangedEvent), "session.autopilot_objective_changed")]
[JsonDerivedType(typeof(SessionBackgroundTasksChangedEvent), "session.background_tasks_changed")]
[JsonDerivedType(typeof(SessionBinaryAssetEvent), "session.binary_asset")]
@@ -94,6 +96,7 @@ namespace GitHub.Copilot;
[JsonDerivedType(typeof(SessionCanvasUnavailableEvent), "session.canvas.unavailable")]
[JsonDerivedType(typeof(SessionCompactionCompleteEvent), "session.compaction_complete")]
[JsonDerivedType(typeof(SessionCompactionStartEvent), "session.compaction_start")]
+[JsonDerivedType(typeof(SessionCompletionReceiptEvent), "session.completion_receipt")]
[JsonDerivedType(typeof(SessionContextChangedEvent), "session.context_changed")]
[JsonDerivedType(typeof(SessionContextClearedEvent), "session.context_cleared")]
[JsonDerivedType(typeof(SessionCustomAgentsUpdatedEvent), "session.custom_agents_updated")]
@@ -110,9 +113,12 @@ namespace GitHub.Copilot;
[JsonDerivedType(typeof(SessionInfoEvent), "session.info")]
[JsonDerivedType(typeof(SessionManagedSettingsEnforcedEvent), "session.managed_settings_enforced")]
[JsonDerivedType(typeof(SessionManagedSettingsResolvedEvent), "session.managed_settings_resolved")]
+[JsonDerivedType(typeof(SessionMcpServerNeedsReconnectEvent), "session.mcp_server_needs_reconnect")]
+[JsonDerivedType(typeof(SessionMcpServerRemovedEvent), "session.mcp_server_removed")]
[JsonDerivedType(typeof(SessionMcpServerStatusChangedEvent), "session.mcp_server_status_changed")]
[JsonDerivedType(typeof(SessionMcpServersLoadedEvent), "session.mcp_servers_loaded")]
[JsonDerivedType(typeof(SessionModeChangedEvent), "session.mode_changed")]
+[JsonDerivedType(typeof(SessionModeNoticeDeliveredEvent), "session.mode_notice_delivered")]
[JsonDerivedType(typeof(SessionModelChangeEvent), "session.model_change")]
[JsonDerivedType(typeof(SessionPermissionsChangedEvent), "session.permissions_changed")]
[JsonDerivedType(typeof(SessionPlanChangedEvent), "session.plan_changed")]
@@ -365,6 +371,19 @@ public sealed partial class SessionModelChangeEvent : SessionEvent
public required SessionModelChangeData Data { get; set; }
}
+/// A transient Auto preference failure emitted when the runtime cannot mint or accept a usable model and token pair. The previously effective preference remains active, so SDK clients can surface a non-blocking failure without changing their committed-tier state. This event is ephemeral and is not persisted or replayed on resume.
+/// Represents the session.auto_tier_switch_failed event.
+public sealed partial class SessionAutoTierSwitchFailedEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "session.auto_tier_switch_failed";
+
+ /// The session.auto_tier_switch_failed event payload.
+ [JsonPropertyName("data")]
+ public required SessionAutoTierSwitchFailedData Data { get; set; }
+}
+
/// Agent mode change details including previous and new modes.
/// Represents the session.mode_changed event.
public sealed partial class SessionModeChangedEvent : SessionEvent
@@ -378,6 +397,19 @@ public sealed partial class SessionModeChangedEvent : SessionEvent
public required SessionModeChangedData Data { get; set; }
}
+/// Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume.
+/// Represents the session.mode_notice_delivered event.
+public sealed partial class SessionModeNoticeDeliveredEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "session.mode_notice_delivered";
+
+ /// The session.mode_notice_delivered event payload.
+ [JsonPropertyName("data")]
+ public required SessionModeNoticeDeliveredData Data { get; set; }
+}
+
/// Session limits update details. Null clears the limits.
/// Represents the session.session_limits_changed event.
public sealed partial class SessionSessionLimitsChangedEvent : SessionEvent
@@ -587,6 +619,20 @@ public sealed partial class SessionTaskCompleteEvent : SessionEvent
public required SessionTaskCompleteData Data { get; set; }
}
+/// Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted.
+/// Represents the session.completion_receipt event.
+[Experimental(Diagnostics.Experimental)]
+public sealed partial class SessionCompletionReceiptEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "session.completion_receipt";
+
+ /// The session.completion_receipt event payload.
+ [JsonPropertyName("data")]
+ public required SessionCompletionReceiptData Data { get; set; }
+}
+
/// Experimental transient signal that HydraFusion routing has started for an eligible turn.
/// Represents the session.fusion_route_started event.
[Experimental(Diagnostics.Experimental)]
@@ -735,6 +781,20 @@ public sealed partial class AssistantFusionPhaseStartedEvent : SessionEvent
public required AssistantFusionPhaseStartedData Data { get; set; }
}
+/// Experimental content-safe activity signal for a running HydraFusion phase.
+/// Represents the assistant.fusion_phase_activity event.
+[Experimental(Diagnostics.Experimental)]
+public sealed partial class AssistantFusionPhaseActivityEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "assistant.fusion_phase_activity";
+
+ /// The assistant.fusion_phase_activity event payload.
+ [JsonPropertyName("data")]
+ public required AssistantFusionPhaseActivityData Data { get; set; }
+}
+
/// Experimental durable HydraFusion phase output and lossless replay checkpoint.
/// Represents the assistant.fusion_phase_completed event.
[Experimental(Diagnostics.Experimental)]
@@ -1546,7 +1606,7 @@ public sealed partial class SessionAutoModeResolvedEvent : SessionEvent
public required SessionAutoModeResolvedData Data { get; set; }
}
-/// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes.
+/// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes.
/// Represents the session.managed_settings_resolved event.
[Experimental(Diagnostics.Experimental)]
public sealed partial class SessionManagedSettingsResolvedEvent : SessionEvent
@@ -1746,6 +1806,32 @@ public sealed partial class SessionMcpServerStatusChangedEvent : SessionEvent
public required SessionMcpServerStatusChangedData Data { get; set; }
}
+/// Payload of `session.mcp_server_removed` identifying an MCP server the graph no longer runs.
+/// Represents the session.mcp_server_removed event.
+public sealed partial class SessionMcpServerRemovedEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "session.mcp_server_removed";
+
+ /// The session.mcp_server_removed event payload.
+ [JsonPropertyName("data")]
+ public required SessionMcpServerRemovedData Data { get; set; }
+}
+
+/// Payload of `session.mcp_server_needs_reconnect` identifying an MCP server whose connection must be re-established.
+/// Represents the session.mcp_server_needs_reconnect event.
+public sealed partial class SessionMcpServerNeedsReconnectEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "session.mcp_server_needs_reconnect";
+
+ /// The session.mcp_server_needs_reconnect event payload.
+ [JsonPropertyName("data")]
+ public required SessionMcpServerNeedsReconnectData Data { get; set; }
+}
+
/// Payload identifying the MCP server associated with a list change.
/// Represents the mcp.tools.list_changed event.
public sealed partial class McpToolsListChangedEvent : SessionEvent
@@ -2103,6 +2189,11 @@ public sealed partial class SessionErrorData
[JsonPropertyName("providerCallId")]
public string? ProviderCallId { get; set; }
+ /// What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("remediation")]
+ public RemediationAction? Remediation { get; set; }
+
/// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("serviceRequestId")]
@@ -2266,6 +2357,11 @@ public sealed partial class SessionWarningData
[JsonPropertyName("message")]
public required string Message { get; set; }
+ /// What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("remediation")]
+ public RemediationAction? Remediation { get; set; }
+
/// Optional URL associated with this warning that the user can open in a browser.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("url")]
@@ -2279,6 +2375,11 @@ public sealed partial class SessionWarningData
/// Model change details including previous and new model identifiers.
public sealed partial class SessionModelChangeData
{
+ /// Committed Auto preference after the model configuration change, when applicable.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("autoTier")]
+ public AutoTier? AutoTier { get; set; }
+
/// Reason the change happened, when not user-initiated. `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path, or `"refusal_fallback"` when the active model declined a request (content refusal) and the runtime switched to the configured refusal-fallback model. UI clients can use this to render contextual copy.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("cause")]
@@ -2293,6 +2394,11 @@ public sealed partial class SessionModelChangeData
[JsonPropertyName("newModel")]
public required string NewModel { get; set; }
+ /// Previously committed Auto preference, when one was explicitly selected.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("previousAutoTier")]
+ public AutoTier? PreviousAutoTier { get; set; }
+
/// Model that was previously selected, if any.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("previousModel")]
@@ -2334,6 +2440,23 @@ public sealed partial class SessionModelChangeData
public Verbosity? Verbosity { get; set; }
}
+/// A transient Auto preference failure emitted when the runtime cannot mint or accept a usable model and token pair. The previously effective preference remains active, so SDK clients can surface a non-blocking failure without changing their committed-tier state. This event is ephemeral and is not persisted or replayed on resume.
+public sealed partial class SessionAutoTierSwitchFailedData
+{
+ /// Auto preference that remains effective after the failed request.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("effectiveAutoTier")]
+ public AutoTier? EffectiveAutoTier { get; set; }
+
+ /// Low-cardinality failure outcome reported by Auto resolution.
+ [JsonPropertyName("reason")]
+ public required AutoTierSwitchFailureReason Reason { get; set; }
+
+ /// Auto preference that failed to activate, or null when returning to provider-default routing failed.
+ [JsonPropertyName("requestedAutoTier")]
+ public AutoTier? RequestedAutoTier { get; set; }
+}
+
/// Agent mode change details including previous and new modes.
public sealed partial class SessionModeChangedData
{
@@ -2346,6 +2469,19 @@ public sealed partial class SessionModeChangedData
public required SessionMode PreviousMode { get; set; }
}
+/// Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume.
+public sealed partial class SessionModeNoticeDeliveredData
+{
+ /// Model-visible transition notice persisted for a mid-turn delivery.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("content")]
+ public string? Content { get; set; }
+
+ /// Mode established by the delivered transition notice.
+ [JsonPropertyName("mode")]
+ public required SessionMode Mode { get; set; }
+}
+
/// Session limits update details. Null clears the limits.
public sealed partial class SessionSessionLimitsChangedData
{
@@ -2866,6 +3002,44 @@ public sealed partial class SessionTaskCompleteData
public string? Summary { get; set; }
}
+/// Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted.
+[Experimental(Diagnostics.Experimental)]
+public sealed partial class SessionCompletionReceiptData
+{
+ /// One-based accepted completion receipt ordinal in the durable session history.
+ [JsonPropertyName("attempt")]
+ public required long Attempt { get; set; }
+
+ /// Inclusive durable event range summarized by this receipt.
+ [JsonPropertyName("eventRange")]
+ public required CompletionReceiptEventRange EventRange { get; set; }
+
+ /// Number of failed structured tool completions in the covered range.
+ [JsonPropertyName("failedToolCount")]
+ public required long FailedToolCount { get; set; }
+
+ /// Final structured tool completion in the covered range, when one exists.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("finalTool")]
+ public CompletionReceiptFinalTool? FinalTool { get; set; }
+
+ /// Version of the completion receipt payload.
+ [JsonPropertyName("schemaVersion")]
+ public required long SchemaVersion { get; set; }
+
+ /// Identifier of the assistant turn-end event that supplied the accepted completion boundary. This is the receipt's idempotency key, and always equals eventRange.endEventId.
+ [JsonPropertyName("sourceEventId")]
+ public required string SourceEventId { get; set; }
+
+ /// Runtime reason the completion decision was accepted.
+ [JsonPropertyName("stopReason")]
+ public required CompletionReceiptStopReason StopReason { get; set; }
+
+ /// Number of successful structured tool completions in the covered range.
+ [JsonPropertyName("successfulToolCount")]
+ public required long SuccessfulToolCount { get; set; }
+}
+
/// Experimental transient signal that HydraFusion routing has started for an eligible turn.
[Experimental(Diagnostics.Experimental)]
public sealed partial class SessionFusionRouteStartedData
@@ -2958,6 +3132,12 @@ public sealed partial class SessionFusionResolvedData
[JsonPropertyName("pattern")]
public required FusionPattern Pattern { get; set; }
+ /// Presentation-neutral phase plan for clients that render workflow progress.
+ [Experimental(Diagnostics.Experimental)]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("phasePlan")]
+ public FusionPhasePlanStep[]? PhasePlan { get; set; }
+
/// Version of the validated execution-plan format.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("planVersion")]
@@ -3129,6 +3309,11 @@ public sealed partial class UserMessageData
[JsonPropertyName("isAutopilotContinuation")]
public bool? IsAutopilotContinuation { get; set; }
+ /// Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("messageId")]
+ public string? MessageId { get; set; }
+
/// Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("nativeDocumentPathFallbackPaths")]
@@ -3310,6 +3495,49 @@ public sealed partial class AssistantFusionPhaseStartedData
public required string Role { get; set; }
}
+/// Experimental content-safe activity signal for a running HydraFusion phase.
+[Experimental(Diagnostics.Experimental)]
+public sealed partial class AssistantFusionPhaseActivityData
+{
+ /// Kind of real activity observed.
+ [JsonPropertyName("activity")]
+ public required FusionPhaseActivityKind Activity { get; set; }
+
+ /// Conversation scope in which the phase executes.
+ [JsonPropertyName("conversationScope")]
+ public required FusionConversationScope ConversationScope { get; set; }
+
+ /// Identifier of the HydraFusion turn containing the phase.
+ [JsonPropertyName("fusionId")]
+ public required string FusionId { get; set; }
+
+ /// HydraFusion orchestration pattern containing the phase.
+ [JsonPropertyName("pattern")]
+ public required FusionPattern Pattern { get; set; }
+
+ /// Stable identifier for the concrete phase.
+ [JsonPropertyName("phaseId")]
+ public required string PhaseId { get; set; }
+
+ /// Kind of phase currently executing.
+ [JsonPropertyName("phaseKind")]
+ public required FusionPhaseKind PhaseKind { get; set; }
+
+ /// Semantic role assigned to the phase.
+ [JsonPropertyName("role")]
+ public required string Role { get; set; }
+
+ /// Opaque hashed correlation token for matching tool-started and tool-completed activity within this Fusion activity stream. It is not the tool call identifier exposed by tool lifecycle events.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("toolCallId")]
+ public string? ToolCallId { get; set; }
+
+ /// Cumulative private response bytes observed for this model call. The event never includes response text.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("totalResponseSizeBytes")]
+ public long? TotalResponseSizeBytes { get; set; }
+}
+
/// Experimental durable HydraFusion phase output and lossless replay checkpoint.
[Experimental(Diagnostics.Experimental)]
public sealed partial class AssistantFusionPhaseCompletedData
@@ -4467,6 +4695,11 @@ public sealed partial class SkillInvokedData
[JsonPropertyName("description")]
public string? Description { get; set; }
+ /// Whether model invocation is disabled for this skill.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("disableModelInvocation")]
+ public bool? DisableModelInvocation { get; set; }
+
/// Model identifier active when the skill was invoked, when known.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("model")]
@@ -4476,7 +4709,7 @@ public sealed partial class SkillInvokedData
[JsonPropertyName("name")]
public required string Name { get; set; }
- /// File path to the SKILL.md definition.
+ /// File path to the SKILL.md definition, or an empty string for an SDK-provided skill without a filesystem identity.
[JsonPropertyName("path")]
public required string Path { get; set; }
@@ -4490,7 +4723,7 @@ public sealed partial class SkillInvokedData
[JsonPropertyName("pluginVersion")]
public string? PluginVersion { get; set; }
- /// Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill).
+ /// Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), remote (org/enterprise skill), and sdk (SDK-provided skill).
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("source")]
public string? Source { get; set; }
@@ -4628,6 +4861,11 @@ public sealed partial class SubagentCompletedData
[JsonPropertyName("model")]
public string? Model { get; set; }
+ /// Why an explicit task-call model did not become the effective model.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("modelOverrideReason")]
+ public string? ModelOverrideReason { get; set; }
+
/// Tool call ID of the parent tool invocation that spawned this sub-agent.
[JsonPropertyName("toolCallId")]
public required string ToolCallId { get; set; }
@@ -4694,6 +4932,11 @@ public sealed partial class SubagentFailedData
[JsonPropertyName("model")]
public string? Model { get; set; }
+ /// Why an explicit task-call model did not become the effective model.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("modelOverrideReason")]
+ public string? ModelOverrideReason { get; set; }
+
/// Tool call ID of the parent tool invocation that spawned this sub-agent.
[JsonPropertyName("toolCallId")]
public required string ToolCallId { get; set; }
@@ -4873,6 +5116,11 @@ public sealed partial class SystemNotificationData
/// Permission request notification requiring client approval with request details.
public sealed partial class PermissionRequestedData
{
+ /// Agent mode captured from the owning turn when permission evaluation began.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("agentMode")]
+ public SessionMode? AgentMode { get; set; }
+
/// Details of the permission being requested.
[JsonPropertyName("permissionRequest")]
public required PermissionRequest PermissionRequest { get; set; }
@@ -5410,7 +5658,7 @@ public sealed partial class SessionAutoModeResolvedData
public bool? StickyOverride { get; set; }
}
-/// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes.
+/// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes.
[Experimental(Diagnostics.Experimental)]
public sealed partial class SessionManagedSettingsResolvedData
{
@@ -5440,6 +5688,11 @@ public sealed partial class SessionManagedSettingsResolvedData
[JsonPropertyName("permissionsAllowIntersected")]
public bool? PermissionsAllowIntersected { get; set; }
+ /// Whether the policy-helper managed-settings layer was present. The policy helper is the weakest channel: it fills keys no enterprise source set and can never replace one.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("policyHelperManaged")]
+ public bool? PolicyHelperManaged { get; set; }
+
/// Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("sandboxEnabledByUndeterminedPolicy")]
@@ -5454,7 +5707,7 @@ public sealed partial class SessionManagedSettingsResolvedData
[JsonPropertyName("settings")]
public JsonElement? Settings { get; set; }
- /// Channel summary: `server`, `device`, or `client` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance.
+ /// Channel summary: `server`, `device`, `client`, or `policyHelper` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance.
[JsonPropertyName("source")]
public required ManagedSettingsResolvedSource Source { get; set; }
}
@@ -5681,6 +5934,22 @@ public sealed partial class SessionMcpServerStatusChangedData
public required McpServerStatus Status { get; set; }
}
+/// Payload of `session.mcp_server_removed` identifying an MCP server the graph no longer runs.
+public sealed partial class SessionMcpServerRemovedData
+{
+ /// Name of the MCP server that was removed from the graph.
+ [JsonPropertyName("serverName")]
+ public required string ServerName { get; set; }
+}
+
+/// Payload of `session.mcp_server_needs_reconnect` identifying an MCP server whose connection must be re-established.
+public sealed partial class SessionMcpServerNeedsReconnectData
+{
+ /// Name of the MCP server that needs to reconnect.
+ [JsonPropertyName("serverName")]
+ public required string ServerName { get; set; }
+}
+
/// Payload identifying the MCP server associated with a list change.
public sealed partial class McpToolsListChangedData
{
@@ -6206,6 +6475,42 @@ public sealed partial class CompactionCompleteCompactionTokensUsed
public long? OutputTokens { get; set; }
}
+/// Inclusive durable event range summarized by a completion receipt.
+/// Nested data type for CompletionReceiptEventRange.
+public sealed partial class CompletionReceiptEventRange
+{
+ /// Identifier of the assistant turn-end event that ends the covered exchange. Always equals the receipt's sourceEventId, so either field is a valid join key.
+ [JsonPropertyName("endEventId")]
+ public required string EndEventId { get; set; }
+
+ /// Identifier of the user message that starts the covered exchange.
+ [JsonPropertyName("startEventId")]
+ public required string StartEventId { get; set; }
+}
+
+/// Final structured tool completion in the covered event range.
+/// Nested data type for CompletionReceiptFinalTool.
+public sealed partial class CompletionReceiptFinalTool
+{
+ /// Process exit code from a structured shell result, when available.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("exitCode")]
+ public long? ExitCode { get; set; }
+
+ /// Structured success or failure status from the tool completion event.
+ [JsonPropertyName("status")]
+ public required CompletionReceiptToolStatus Status { get; set; }
+
+ /// Unique identifier of the completed tool call.
+ [JsonPropertyName("toolCallId")]
+ public required string ToolCallId { get; set; }
+
+ /// Tool name from the matching tool execution start event, when available.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("toolName")]
+ public string? ToolName { get; set; }
+}
+
/// Durable server recommendation for subsequent HydraFusion turns.
/// Nested data type for FusionFollowUpRecommendation.
[Experimental(Diagnostics.Experimental)]
@@ -6220,6 +6525,28 @@ public sealed partial class FusionFollowUpRecommendation
public required FusionFollowUpAction UserTurn { get; set; }
}
+/// Presentation-neutral phase planned for a HydraFusion turn.
+/// Nested data type for FusionPhasePlanStep.
+[Experimental(Diagnostics.Experimental)]
+public sealed partial class FusionPhasePlanStep
+{
+ /// Whether the phase executes only when an earlier phase requests it.
+ [JsonPropertyName("conditional")]
+ public required bool Conditional { get; set; }
+
+ /// Kind of phase that may execute.
+ [JsonPropertyName("kind")]
+ public required FusionPhaseKind Kind { get; set; }
+
+ /// Semantic role assigned to the phase.
+ [JsonPropertyName("role")]
+ public required string Role { get; set; }
+
+ /// Conversation scope in which the phase executes.
+ [JsonPropertyName("scope")]
+ public required FusionConversationScope Scope { get; set; }
+}
+
/// Validated HydraFusion routing capability scores.
/// Nested data type for FusionScores.
[Experimental(Diagnostics.Experimental)]
@@ -7068,7 +7395,7 @@ public sealed partial class FusionAttribution
[Experimental(Diagnostics.Experimental)]
public sealed partial class AssistantMessageReasoningBlocks
{
- /// Provider-native reasoning content blocks (e.g. Anthropic `thinking` / `redacted_thinking`) preserved verbatim, in order. A single response can carry several, each signed over the content preceding it, so dropping or reordering any of them invalidates the rest.
+ /// Provider-native reasoning items or content blocks preserved verbatim, in order. A single response can carry several, and provider signatures or identifiers may depend on their exact content and ordering.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("blocks")]
public JsonElement[]? Blocks { get; set; }
@@ -7378,6 +7705,11 @@ public sealed partial class ToolExecutionCompleteError
/// Human-readable error message.
[JsonPropertyName("message")]
public required string Message { get; set; }
+
+ /// What the user must do to recover, when the runtime knows of an action. Set on sandbox policy denials, where `message` names the rule that blocked the call but never the client affordance that relaxes it.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("remediation")]
+ public RemediationAction? Remediation { get; set; }
}
/// Binary result returned by a tool for the model.
@@ -8520,12 +8852,12 @@ public override bool? ManagedApprovalRequired
[JsonPropertyName("possibleUrls")]
public required PermissionRequestShellPossibleUrl[] PossibleUrls { get; set; }
- /// True when the model has requested to run this command outside the sandbox (it set requestSandboxBypass: true and the host opted in via sandbox.allowBypass). This is a request, not a grant: the command runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
+ /// True when the tool is asking to run this command outside the sandbox, either because the command detaches and cannot be sandboxed at all, or because a sandboxed run looked blocked (host opted in via sandbox.allowBypass). The model cannot ask for this; only the tool raises it. This is a request, not a grant: the command runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("requestSandboxBypass")]
public bool? RequestSandboxBypass { get; set; }
- /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true.
+ /// What the tool tells the user about the bypass on offer: which policy rule blocked the call, or why it cannot be sandboxed. Only meaningful when requestSandboxBypass is true.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("requestSandboxBypassReason")]
public string? RequestSandboxBypassReason { get; set; }
@@ -8620,12 +8952,12 @@ public override bool? ManagedApprovalRequired
[JsonPropertyName("path")]
public required string Path { get; set; }
- /// True when the model has requested to run this search outside the sandbox (it set requestSandboxBypass: true and the host opted in via sandbox.allowBypass). This is a request, not a grant: the search runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
+ /// True when the tool is asking to re-run this search outside the sandbox, after a sandboxed run looked blocked (host opted in via sandbox.allowBypass). The model cannot ask for this; only the tool raises it. This is a request, not a grant: the search runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("requestSandboxBypass")]
public bool? RequestSandboxBypass { get; set; }
- /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true.
+ /// What the tool tells the user about the bypass on offer: which policy rule blocked the call, or why it cannot be sandboxed. Only meaningful when requestSandboxBypass is true.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("requestSandboxBypassReason")]
public string? RequestSandboxBypassReason { get; set; }
@@ -8703,12 +9035,12 @@ public override bool? ManagedApprovalRequired
[JsonPropertyName("redirectedFrom")]
public string? RedirectedFrom { get; set; }
- /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
+ /// True when the tool is asking to run this URL fetch outside the sandbox, after the network policy denied the approved URL or the sandbox proxy could not reach it (host opted in via sandbox.allowBypass). The model cannot ask for this; only the tool raises it. This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("requestSandboxBypass")]
public bool? RequestSandboxBypass { get; set; }
- /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true.
+ /// What the tool tells the user about the bypass on offer: which policy rule blocked the call, or why it cannot be sandboxed. Only meaningful when requestSandboxBypass is true.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("requestSandboxBypassReason")]
public string? RequestSandboxBypassReason { get; set; }
@@ -9257,12 +9589,12 @@ public sealed partial class PermissionPromptRequestUrl : PermissionPromptRequest
[JsonPropertyName("redirectedFrom")]
public string? RedirectedFrom { get; set; }
- /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
+ /// True when the tool is asking to run this URL fetch outside the sandbox, after the network policy denied the approved URL or the sandbox proxy could not reach it (host opted in via sandbox.allowBypass). The model cannot ask for this; only the tool raises it. This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("requestSandboxBypass")]
public bool? RequestSandboxBypass { get; set; }
- /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true.
+ /// What the tool tells the user about the bypass on offer: which policy rule blocked the call, or why it cannot be sandboxed. Only meaningful when requestSandboxBypass is true.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("requestSandboxBypassReason")]
public string? RequestSandboxBypassReason { get; set; }
@@ -10129,7 +10461,7 @@ public sealed partial class SkillsLoadedSkill
[JsonPropertyName("path")]
public string? Path { get; set; }
- /// Source location type (e.g., project, personal-copilot, plugin, builtin).
+ /// Source location type (e.g., project, personal-copilot, plugin, builtin, remote, sdk).
[JsonPropertyName("source")]
public required SkillSource Source { get; set; }
@@ -10138,7 +10470,7 @@ public sealed partial class SkillsLoadedSkill
public required bool UserInvocable { get; set; }
}
-/// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override.
+/// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration.
/// Nested data type for CustomAgentsUpdatedAgent.
public sealed partial class CustomAgentsUpdatedAgent
{
@@ -10159,6 +10491,16 @@ public sealed partial class CustomAgentsUpdatedAgent
[JsonPropertyName("model")]
public string? Model { get; set; }
+ /// Whether authored models are preferences or required constraints.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("modelPolicy")]
+ public AgentModelPolicy? ModelPolicy { get; set; }
+
+ /// Authored model ids in priority order, if configured.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("models")]
+ public string[]? Models { get; set; }
+
/// Internal name of the agent.
[JsonPropertyName("name")]
public required string Name { get; set; }
@@ -10176,6 +10518,15 @@ public sealed partial class CustomAgentsUpdatedAgent
public required bool UserInvocable { get; set; }
}
+/// Server-advertised metadata learned through modern discovery or legacy initialization.
+/// Nested data type for McpServerMetadata.
+public sealed partial class McpServerMetadata
+{
+ /// Non-empty natural-language guidance for using the server, or null when the server omitted instructions or advertised an empty string.
+ [JsonPropertyName("instructions")]
+ public string? Instructions { get; set; }
+}
+
/// A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata.
/// Nested data type for McpServersLoadedServer.
public sealed partial class McpServersLoadedServer
@@ -10199,6 +10550,11 @@ public sealed partial class McpServersLoadedServer
[JsonPropertyName("pluginVersion")]
public string? PluginVersion { get; set; }
+ /// Server-advertised metadata for a connected server. Omitted when no live connection metadata is available, including while pending or when failed, disabled, stopped, or not configured.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("serverMetadata")]
+ public McpServerMetadata? ServerMetadata { get; set; }
+
/// Configuration source: user, workspace, plugin, or builtin.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("source")]
@@ -10647,6 +11003,76 @@ public override void Write(Utf8JsonWriter writer, Verbosity value, JsonSerialize
}
}
+/// What the user must do to recover from a failure, named as an action rather than as one client's affordance. The runtime cannot know which affordance a client offers — a slash command, a settings pane, a link — so the accompanying message stays host-agnostic and each client renders its own copy from this value. Absent when the runtime knows of no action the user can take.
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct RemediationAction : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public RemediationAction(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Authenticate again with the Copilot backend. The current credential is absent, expired, or rejected.
+ public static RemediationAction SignIn { get; } = new("sign_in");
+
+ /// Authenticate as a different account. The current account exists but lacks access to the requested resource.
+ public static RemediationAction SwitchAccount { get; } = new("switch_account");
+
+ /// Inspect which account is currently authenticated before deciding what to change.
+ public static RemediationAction ShowAccount { get; } = new("show_account");
+
+ /// Review or widen the sandbox policy. The blocked path or host is named by the accompanying message or by the tool result the action arrived with.
+ public static RemediationAction ReviewSandboxPolicy { get; } = new("review_sandbox_policy");
+
+ /// Permit outbound network access in the sandbox policy.
+ public static RemediationAction AllowSandboxOutbound { get; } = new("allow_sandbox_outbound");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(RemediationAction left, RemediationAction right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(RemediationAction left, RemediationAction right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is RemediationAction other && Equals(other);
+
+ ///
+ public bool Equals(RemediationAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override RemediationAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, RemediationAction value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemediationAction));
+ }
+ }
+}
+
/// The session mode the agent is operating in.
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
@@ -10991,46 +11417,48 @@ public override void Write(Utf8JsonWriter writer, ModelChangeSource value, JsonS
}
}
-/// Permission mode for the session.
-[Experimental(Diagnostics.Experimental)]
+/// Terminal reason an Auto preference activation failed.
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
-public readonly struct PermissionMode : IEquatable
+public readonly struct AutoTierSwitchFailureReason : IEquatable
{
private readonly string? _value;
- /// Initializes a new instance of the struct.
- /// The value to associate with this .
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
[JsonConstructor]
- public PermissionMode(string value)
+ public AutoTierSwitchFailureReason(string value)
{
ArgumentException.ThrowIfNullOrWhiteSpace(value);
_value = value;
}
- /// Gets the value associated with this .
+ /// Gets the value associated with this .
public string Value => _value ?? string.Empty;
- /// Permission requests follow the normal approval flow.
- public static PermissionMode Manual { get; } = new("manual");
+ /// The candidate model was rejected by model policy.
+ public static AutoTierSwitchFailureReason PolicyRejected { get; } = new("policy_rejected");
- /// Permission requests include an LLM safety recommendation; clients may automatically approve requests judged acceptable.
- public static PermissionMode Assisted { get; } = new("assisted");
+ /// The Auto routing request failed or returned an unusable response.
+ public static AutoTierSwitchFailureReason RequestFailed { get; } = new("request_failed");
- /// Tool, path, and URL permission requests are automatically approved.
- public static PermissionMode AllowAll { get; } = new("allow-all");
+ /// The runtime could not prepare the Auto routing request.
+ public static AutoTierSwitchFailureReason SetupFailed { get; } = new("setup_failed");
- /// Returns a value indicating whether two instances are equivalent.
- public static bool operator ==(PermissionMode left, PermissionMode right) => left.Equals(right);
+ /// The provider does not support Auto routing.
+ public static AutoTierSwitchFailureReason Unsupported { get; } = new("unsupported");
- /// Returns a value indicating whether two instances are not equivalent.
- public static bool operator !=(PermissionMode left, PermissionMode right) => !(left == right);
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(AutoTierSwitchFailureReason left, AutoTierSwitchFailureReason right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(AutoTierSwitchFailureReason left, AutoTierSwitchFailureReason right) => !(left == right);
///
- public override bool Equals(object? obj) => obj is PermissionMode other && Equals(other);
+ public override bool Equals(object? obj) => obj is AutoTierSwitchFailureReason other && Equals(other);
///
- public bool Equals(PermissionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+ public bool Equals(AutoTierSwitchFailureReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
///
public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
@@ -11038,25 +11466,90 @@ public PermissionMode(string value)
///
public override string ToString() => Value;
- /// Provides a for serializing instances.
+ /// Provides a for serializing instances.
[EditorBrowsable(EditorBrowsableState.Never)]
- public sealed class Converter : JsonConverter
+ public sealed class Converter : JsonConverter
{
///
- public override PermissionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ public override AutoTierSwitchFailureReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
}
///
- public override void Write(Utf8JsonWriter writer, PermissionMode value, JsonSerializerOptions options)
+ public override void Write(Utf8JsonWriter writer, AutoTierSwitchFailureReason value, JsonSerializerOptions options)
{
- GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionMode));
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoTierSwitchFailureReason));
}
}
}
-/// The type of operation performed on the plan file.
+/// Permission mode for the session.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct PermissionMode : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public PermissionMode(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Permission requests follow the normal approval flow.
+ public static PermissionMode Manual { get; } = new("manual");
+
+ /// Permission requests include an LLM safety recommendation; clients may automatically approve requests judged acceptable.
+ public static PermissionMode Assisted { get; } = new("assisted");
+
+ /// Tool, path, and URL permission requests are automatically approved.
+ public static PermissionMode AllowAll { get; } = new("allow-all");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(PermissionMode left, PermissionMode right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(PermissionMode left, PermissionMode right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is PermissionMode other && Equals(other);
+
+ ///
+ public bool Equals(PermissionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override PermissionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, PermissionMode value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionMode));
+ }
+ }
+}
+
+/// The type of operation performed on the plan file.
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
public readonly struct PlanChangedOperation : IEquatable
@@ -11437,6 +11930,140 @@ public override void Write(Utf8JsonWriter writer, TaskCompletionOutcome value, J
}
}
+/// Structured terminal status from a tool completion event.
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct CompletionReceiptToolStatus : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public CompletionReceiptToolStatus(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// The tool completed successfully.
+ public static CompletionReceiptToolStatus Success { get; } = new("success");
+
+ /// The tool failed without a more specific structured status.
+ public static CompletionReceiptToolStatus Failure { get; } = new("failure");
+
+ /// The tool exceeded its time budget.
+ public static CompletionReceiptToolStatus Timeout { get; } = new("timeout");
+
+ /// The user rejected the tool call.
+ public static CompletionReceiptToolStatus Rejected { get; } = new("rejected");
+
+ /// The permissions service denied the tool call.
+ public static CompletionReceiptToolStatus Denied { get; } = new("denied");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(CompletionReceiptToolStatus left, CompletionReceiptToolStatus right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(CompletionReceiptToolStatus left, CompletionReceiptToolStatus right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is CompletionReceiptToolStatus other && Equals(other);
+
+ ///
+ public bool Equals(CompletionReceiptToolStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override CompletionReceiptToolStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, CompletionReceiptToolStatus value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CompletionReceiptToolStatus));
+ }
+ }
+}
+
+/// Runtime reason the completion decision was accepted.
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct CompletionReceiptStopReason : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public CompletionReceiptStopReason(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// The model reached a natural terminal response.
+ public static CompletionReceiptStopReason Natural { get; } = new("natural");
+
+ /// A terminal tool ended the interaction.
+ public static CompletionReceiptStopReason TerminalTool { get; } = new("terminal_tool");
+
+ /// The configured agentStop continuation limit was reached.
+ public static CompletionReceiptStopReason AgentStopBlockLimit { get; } = new("agent_stop_block_limit");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(CompletionReceiptStopReason left, CompletionReceiptStopReason right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(CompletionReceiptStopReason left, CompletionReceiptStopReason right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is CompletionReceiptStopReason other && Equals(other);
+
+ ///
+ public bool Equals(CompletionReceiptStopReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override CompletionReceiptStopReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, CompletionReceiptStopReason value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CompletionReceiptStopReason));
+ }
+ }
+}
+
/// Kind of turn for which HydraFusion routing is running.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
@@ -11626,6 +12253,145 @@ public override void Write(Utf8JsonWriter writer, FusionPattern value, JsonSeria
}
}
+/// HydraFusion phase kind.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct FusionPhaseKind : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public FusionPhaseKind(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Primary solver phase.
+ public static FusionPhaseKind Primary { get; } = new("primary");
+
+ /// Read-only cascade judge phase.
+ public static FusionPhaseKind Judge { get; } = new("judge");
+
+ /// Cascade repair phase.
+ public static FusionPhaseKind Repair { get; } = new("repair");
+
+ /// Initial critique-pattern draft phase.
+ public static FusionPhaseKind Draft { get; } = new("draft");
+
+ /// Read-only critique phase.
+ public static FusionPhaseKind Critic { get; } = new("critic");
+
+ /// Critique-pattern revision phase.
+ public static FusionPhaseKind Revision { get; } = new("revision");
+
+ /// Follow-up phase continuing from the resolved model.
+ public static FusionPhaseKind FollowUp { get; } = new("follow_up");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(FusionPhaseKind left, FusionPhaseKind right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(FusionPhaseKind left, FusionPhaseKind right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is FusionPhaseKind other && Equals(other);
+
+ ///
+ public bool Equals(FusionPhaseKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override FusionPhaseKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, FusionPhaseKind value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPhaseKind));
+ }
+ }
+}
+
+/// Conversation scope in which a HydraFusion phase executes.
+[Experimental(Diagnostics.Experimental)]
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct FusionConversationScope : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public FusionConversationScope(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Canonical root conversation history.
+ public static FusionConversationScope Root { get; } = new("root");
+
+ /// Isolated read-only review history that does not enter the root conversation.
+ public static FusionConversationScope Review { get; } = new("review");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(FusionConversationScope left, FusionConversationScope right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(FusionConversationScope left, FusionConversationScope right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is FusionConversationScope other && Equals(other);
+
+ ///
+ public bool Equals(FusionConversationScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override FusionConversationScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, FusionConversationScope value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionConversationScope));
+ }
+ }
+}
+
/// The agent mode that was active when this message was sent.
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
@@ -12071,120 +12837,46 @@ public override void Write(Utf8JsonWriter writer, ModelCallFailureTransport valu
}
}
-/// Conversation scope in which a HydraFusion phase executes.
-[Experimental(Diagnostics.Experimental)]
-[JsonConverter(typeof(Converter))]
-[DebuggerDisplay("{Value,nq}")]
-public readonly struct FusionConversationScope : IEquatable
-{
- private readonly string? _value;
-
- /// Initializes a new instance of the struct.
- /// The value to associate with this .
- [JsonConstructor]
- public FusionConversationScope(string value)
- {
- ArgumentException.ThrowIfNullOrWhiteSpace(value);
- _value = value;
- }
-
- /// Gets the value associated with this .
- public string Value => _value ?? string.Empty;
-
- /// Canonical root conversation history.
- public static FusionConversationScope Root { get; } = new("root");
-
- /// Isolated read-only review history that does not enter the root conversation.
- public static FusionConversationScope Review { get; } = new("review");
-
- /// Returns a value indicating whether two instances are equivalent.
- public static bool operator ==(FusionConversationScope left, FusionConversationScope right) => left.Equals(right);
-
- /// Returns a value indicating whether two instances are not equivalent.
- public static bool operator !=(FusionConversationScope left, FusionConversationScope right) => !(left == right);
-
- ///
- public override bool Equals(object? obj) => obj is FusionConversationScope other && Equals(other);
-
- ///
- public bool Equals(FusionConversationScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
-
- ///
- public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
-
- ///
- public override string ToString() => Value;
-
- /// Provides a for serializing instances.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public sealed class Converter : JsonConverter
- {
- ///
- public override FusionConversationScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
- }
-
- ///
- public override void Write(Utf8JsonWriter writer, FusionConversationScope value, JsonSerializerOptions options)
- {
- GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionConversationScope));
- }
- }
-}
-
-/// HydraFusion phase kind.
+/// Content-safe activity observed while a HydraFusion phase is running.
[Experimental(Diagnostics.Experimental)]
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
-public readonly struct FusionPhaseKind : IEquatable
+public readonly struct FusionPhaseActivityKind : IEquatable
{
private readonly string? _value;
- /// Initializes a new instance of the struct.
- /// The value to associate with this .
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
[JsonConstructor]
- public FusionPhaseKind(string value)
+ public FusionPhaseActivityKind(string value)
{
ArgumentException.ThrowIfNullOrWhiteSpace(value);
_value = value;
}
- /// Gets the value associated with this .
+ /// Gets the value associated with this .
public string Value => _value ?? string.Empty;
- /// Primary solver phase.
- public static FusionPhaseKind Primary { get; } = new("primary");
+ /// The provider produced additional private output bytes.
+ public static FusionPhaseActivityKind ModelOutput { get; } = new("model_output");
- /// Read-only cascade judge phase.
- public static FusionPhaseKind Judge { get; } = new("judge");
+ /// A tool began executing inside the phase.
+ public static FusionPhaseActivityKind ToolStarted { get; } = new("tool_started");
- /// Cascade repair phase.
- public static FusionPhaseKind Repair { get; } = new("repair");
+ /// A tool finished executing inside the phase.
+ public static FusionPhaseActivityKind ToolCompleted { get; } = new("tool_completed");
- /// Initial critique-pattern draft phase.
- public static FusionPhaseKind Draft { get; } = new("draft");
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(FusionPhaseActivityKind left, FusionPhaseActivityKind right) => left.Equals(right);
- /// Read-only critique phase.
- public static FusionPhaseKind Critic { get; } = new("critic");
-
- /// Critique-pattern revision phase.
- public static FusionPhaseKind Revision { get; } = new("revision");
-
- /// Follow-up phase continuing from the resolved model.
- public static FusionPhaseKind FollowUp { get; } = new("follow_up");
-
- /// Returns a value indicating whether two instances are equivalent.
- public static bool operator ==(FusionPhaseKind left, FusionPhaseKind right) => left.Equals(right);
-
- /// Returns a value indicating whether two instances are not equivalent.
- public static bool operator !=(FusionPhaseKind left, FusionPhaseKind right) => !(left == right);
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(FusionPhaseActivityKind left, FusionPhaseActivityKind right) => !(left == right);
///
- public override bool Equals(object? obj) => obj is FusionPhaseKind other && Equals(other);
+ public override bool Equals(object? obj) => obj is FusionPhaseActivityKind other && Equals(other);
///
- public bool Equals(FusionPhaseKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+ public bool Equals(FusionPhaseActivityKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
///
public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
@@ -12192,20 +12884,20 @@ public FusionPhaseKind(string value)
///
public override string ToString() => Value;
- /// Provides a for serializing instances.
+ /// Provides a for serializing instances.
[EditorBrowsable(EditorBrowsableState.Never)]
- public sealed class Converter : JsonConverter
+ public sealed class Converter : JsonConverter
{
///
- public override FusionPhaseKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ public override FusionPhaseActivityKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
}
///
- public override void Write(Utf8JsonWriter writer, FusionPhaseKind value, JsonSerializerOptions options)
+ public override void Write(Utf8JsonWriter writer, FusionPhaseActivityKind value, JsonSerializerOptions options)
{
- GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPhaseKind));
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPhaseActivityKind));
}
}
}
@@ -14833,7 +15525,10 @@ public ManagedSettingsResolvedSource(string value)
/// Only session-local SDK-host injection contributed.
public static ManagedSettingsResolvedSource Client { get; } = new("client");
- /// More than one channel contributed. Ordinary keys resolve device over server per key, while permissions compose restrictively across all present layers.
+ /// A policy helper registered by device or server policy contributed. Device registration takes priority when present.
+ public static ManagedSettingsResolvedSource PolicyHelper { get; } = new("policyHelper");
+
+ /// More than one channel contributed. Ordinary keys resolve device over server over policy helper per key, while permissions compose restrictively across all present layers.
public static ManagedSettingsResolvedSource Mixed { get; } = new("mixed");
/// No managed policy is in force (no channel contributed).
@@ -15140,7 +15835,7 @@ public override void Write(Utf8JsonWriter writer, FactoryRunSettledStatus value,
}
}
-/// Source location type (e.g., project, personal-copilot, plugin, builtin).
+/// Source location type (e.g., project, personal-copilot, plugin, builtin, sdk).
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
public readonly struct SkillSource : IEquatable
@@ -15180,6 +15875,9 @@ public SkillSource(string value)
/// Skill bundled with the runtime.
public static SkillSource Builtin { get; } = new("builtin");
+ /// Pathless skill supplied lazily by an SDK skill provider.
+ public static SkillSource Sdk { get; } = new("sdk");
+
/// Returns a value indicating whether two instances are equivalent.
public static bool operator ==(SkillSource left, SkillSource right) => left.Equals(right);
@@ -15216,6 +15914,67 @@ public override void Write(Utf8JsonWriter writer, SkillSource value, JsonSeriali
}
}
+/// Whether configured models are advisory preferences or required constraints.
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct AgentModelPolicy : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public AgentModelPolicy(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Treat the authored models as advisory preferences that callers may override.
+ public static AgentModelPolicy Preferred { get; } = new("preferred");
+
+ /// Require subagent execution to use one of the authored models.
+ public static AgentModelPolicy Required { get; } = new("required");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(AgentModelPolicy left, AgentModelPolicy right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(AgentModelPolicy left, AgentModelPolicy right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is AgentModelPolicy other && Equals(other);
+
+ ///
+ public bool Equals(AgentModelPolicy other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override AgentModelPolicy Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, AgentModelPolicy value, JsonSerializerOptions options)
+ {
+ GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentModelPolicy));
+ }
+ }
+}
+
/// Configuration source: user, workspace, plugin, or builtin.
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
@@ -15569,6 +16328,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu
[JsonSerializable(typeof(AbortEvent))]
[JsonSerializable(typeof(AgentInterruptedData))]
[JsonSerializable(typeof(AgentInterruptedEvent))]
+[JsonSerializable(typeof(AssistantFusionPhaseActivityData))]
+[JsonSerializable(typeof(AssistantFusionPhaseActivityEvent))]
[JsonSerializable(typeof(AssistantFusionPhaseCompletedData))]
[JsonSerializable(typeof(AssistantFusionPhaseCompletedEvent))]
[JsonSerializable(typeof(AssistantFusionPhaseFailedData))]
@@ -15663,6 +16424,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu
[JsonSerializable(typeof(CompactionCompleteCompactionTokensUsed))]
[JsonSerializable(typeof(CompactionCompleteCompactionTokensUsedCopilotUsage))]
[JsonSerializable(typeof(CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail))]
+[JsonSerializable(typeof(CompletionReceiptEventRange))]
+[JsonSerializable(typeof(CompletionReceiptFinalTool))]
[JsonSerializable(typeof(CustomAgentsUpdatedAgent))]
[JsonSerializable(typeof(ElicitationCompletedData))]
[JsonSerializable(typeof(ElicitationCompletedEvent))]
@@ -15689,6 +16452,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu
[JsonSerializable(typeof(FactoryRunUpdatedEvent))]
[JsonSerializable(typeof(FusionAttribution))]
[JsonSerializable(typeof(FusionFollowUpRecommendation))]
+[JsonSerializable(typeof(FusionPhasePlanStep))]
[JsonSerializable(typeof(FusionPhaseUsage))]
[JsonSerializable(typeof(FusionScores))]
[JsonSerializable(typeof(FusionStagedTerminal))]
@@ -15723,6 +16487,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu
[JsonSerializable(typeof(McpPromptsListChangedEvent))]
[JsonSerializable(typeof(McpResourcesListChangedData))]
[JsonSerializable(typeof(McpResourcesListChangedEvent))]
+[JsonSerializable(typeof(McpServerMetadata))]
[JsonSerializable(typeof(McpServersLoadedServer))]
[JsonSerializable(typeof(McpToolsListChangedData))]
[JsonSerializable(typeof(McpToolsListChangedEvent))]
@@ -15794,6 +16559,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu
[JsonSerializable(typeof(SandboxDecisionEvent))]
[JsonSerializable(typeof(SessionAutoModeResolvedData))]
[JsonSerializable(typeof(SessionAutoModeResolvedEvent))]
+[JsonSerializable(typeof(SessionAutoTierSwitchFailedData))]
+[JsonSerializable(typeof(SessionAutoTierSwitchFailedEvent))]
[JsonSerializable(typeof(SessionAutopilotObjectiveChangedData))]
[JsonSerializable(typeof(SessionAutopilotObjectiveChangedEvent))]
[JsonSerializable(typeof(SessionBackgroundTasksChangedData))]
@@ -15816,6 +16583,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu
[JsonSerializable(typeof(SessionCompactionCompleteEvent))]
[JsonSerializable(typeof(SessionCompactionStartData))]
[JsonSerializable(typeof(SessionCompactionStartEvent))]
+[JsonSerializable(typeof(SessionCompletionReceiptData))]
+[JsonSerializable(typeof(SessionCompletionReceiptEvent))]
[JsonSerializable(typeof(SessionContextChangedData))]
[JsonSerializable(typeof(SessionContextChangedEvent))]
[JsonSerializable(typeof(SessionContextClearedData))]
@@ -15855,12 +16624,18 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu
[JsonSerializable(typeof(SessionManagedSettingsEnforcedEvent))]
[JsonSerializable(typeof(SessionManagedSettingsResolvedData))]
[JsonSerializable(typeof(SessionManagedSettingsResolvedEvent))]
+[JsonSerializable(typeof(SessionMcpServerNeedsReconnectData))]
+[JsonSerializable(typeof(SessionMcpServerNeedsReconnectEvent))]
+[JsonSerializable(typeof(SessionMcpServerRemovedData))]
+[JsonSerializable(typeof(SessionMcpServerRemovedEvent))]
[JsonSerializable(typeof(SessionMcpServerStatusChangedData))]
[JsonSerializable(typeof(SessionMcpServerStatusChangedEvent))]
[JsonSerializable(typeof(SessionMcpServersLoadedData))]
[JsonSerializable(typeof(SessionMcpServersLoadedEvent))]
[JsonSerializable(typeof(SessionModeChangedData))]
[JsonSerializable(typeof(SessionModeChangedEvent))]
+[JsonSerializable(typeof(SessionModeNoticeDeliveredData))]
+[JsonSerializable(typeof(SessionModeNoticeDeliveredEvent))]
[JsonSerializable(typeof(SessionModelChangeData))]
[JsonSerializable(typeof(SessionModelChangeEvent))]
[JsonSerializable(typeof(SessionPermissionsChangedData))]
diff --git a/dotnet/src/GitHub.Copilot.SDK.csproj b/dotnet/src/GitHub.Copilot.SDK.csproj
index f48fb802d7..e5a2d9fb98 100644
--- a/dotnet/src/GitHub.Copilot.SDK.csproj
+++ b/dotnet/src/GitHub.Copilot.SDK.csproj
@@ -63,10 +63,10 @@
-
+
-
+
diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs
index 5995abaaff..8637da948c 100644
--- a/dotnet/src/Session.cs
+++ b/dotnet/src/Session.cs
@@ -5,12 +5,14 @@
using GitHub.Copilot.Rpc;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
+using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
using System.Threading.Channels;
namespace GitHub.Copilot;
@@ -59,6 +61,8 @@ public sealed partial class CopilotSession : IAsyncDisposable
private readonly Dictionary _toolHandlers = [];
private readonly Dictionary> _commandHandlers = [];
private readonly Dictionary>> _bearerTokenProviders = new(StringComparer.Ordinal);
+ private readonly ConcurrentDictionary _pendingExternalTools = new(StringComparer.Ordinal);
+ private readonly CancellationTokenSource _externalToolLifetime = new();
private readonly ILogger _logger;
private readonly CopilotClient _parentClient;
@@ -204,6 +208,32 @@ internal void RemoveFromClient()
((ICollection>)_parentClient._sessions).Remove(new(SessionId, this));
}
+ ///
+ /// Stops the session's event consumer () without
+ /// making an RPC. and
+ /// use this on error paths where a
+ /// locally registered session fails before it can be returned to the caller:
+ /// starts the consumer eagerly, and no caller
+ /// ever receives the failed session to dispose it. Safe to call more than once —
+ /// is idempotent.
+ ///
+ internal void CloseEventChannel()
+ {
+ _eventChannel.Writer.TryComplete();
+ }
+
+ ///
+ /// Removes the session from its parent client and stops its event consumer.
+ /// Used on session-creation/resume error paths where the session was registered
+ /// (and its event loop started) but never returned to the caller.
+ ///
+ internal void Unregister()
+ {
+ CancelPendingExternalTools();
+ CloseEventChannel();
+ RemoveFromClient();
+ }
+
internal void SetGitHubTokenProviderRegistration(string registrationId)
{
_gitHubTokenProviderRegistrationId = registrationId;
@@ -649,6 +679,10 @@ private async Task HandleBroadcastEventAsync(SessionEvent sessionEvent)
break;
}
+ case ExternalToolCompletedEvent completedEvent:
+ CancelExternalTool(completedEvent.Data.RequestId);
+ break;
+
case PermissionRequestedEvent permEvent:
{
var data = permEvent.Data;
@@ -858,8 +892,24 @@ private async Task TryCancelMcpAuthRequestAsync(string requestId)
///
private async Task ExecuteToolAndRespondAsync(string requestId, string toolName, string toolCallId, JsonElement? arguments, AIFunction tool)
{
+ if (_externalToolLifetime.IsCancellationRequested)
+ {
+ return;
+ }
+
+ using var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_externalToolLifetime.Token);
+ if (!_pendingExternalTools.TryAdd(requestId, cancellationSource))
+ {
+ return;
+ }
+
try
{
+ if (cancellationSource.IsCancellationRequested)
+ {
+ return;
+ }
+
var invocation = new ToolInvocation
{
SessionId = SessionId,
@@ -877,7 +927,7 @@ private async Task ExecuteToolAndRespondAsync(string requestId, string toolName,
{
try
{
- var metadata = await Rpc.Tools.GetCurrentMetadataAsync();
+ var metadata = await Rpc.Tools.GetCurrentMetadataAsync(cancellationSource.Token);
invocation.AvailableTools = metadata.Tools;
}
catch (Exception ex) when (ex is RemoteRpcException or IOException or ObjectDisposedException or JsonException)
@@ -898,14 +948,21 @@ private async Task ExecuteToolAndRespondAsync(string requestId, string toolName,
if (arguments is JsonElement incomingJsonArgs)
{
- foreach (var prop in incomingJsonArgs.EnumerateObject())
+ if (incomingJsonArgs.ValueKind == JsonValueKind.Object)
{
- aiFunctionArgs[prop.Name] = prop.Value;
+ foreach (var prop in incomingJsonArgs.EnumerateObject())
+ {
+ aiFunctionArgs[prop.Name] = prop.Value;
+ }
+ }
+ else
+ {
+ aiFunctionArgs[GetSingleParameterName(tool)] = incomingJsonArgs;
}
}
var toolTimestamp = Stopwatch.GetTimestamp();
- var result = await tool.InvokeAsync(aiFunctionArgs);
+ var result = await tool.InvokeAsync(aiFunctionArgs, cancellationSource.Token);
LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null,
"CopilotSession.ExecuteToolAndRespondAsync tool dispatch. Elapsed={Elapsed}, SessionId={SessionId}, RequestId={RequestId}, ToolCallId={ToolCallId}, Tool={ToolName}",
toolTimestamp,
@@ -915,9 +972,14 @@ private async Task ExecuteToolAndRespondAsync(string requestId, string toolName,
toolName);
var toolResultObject = ToolResultObject.ConvertFromInvocationResult(result, tool.JsonSerializerOptions);
+ if (!TryClaimExternalTool(requestId, cancellationSource))
+ {
+ return;
+ }
var responseRpcTimestamp = Stopwatch.GetTimestamp();
- await Rpc.Tools.HandlePendingToolCallAsync(requestId, toolResultObject, error: null);
+ await Rpc.Tools.HandlePendingToolCallAsync(
+ requestId, toolResultObject, error: null, cancellationSource.Token);
LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null,
"CopilotSession.ExecuteToolAndRespondAsync response sent successfully. Elapsed={Elapsed}, SessionId={SessionId}, RequestId={RequestId}, ToolCallId={ToolCallId}, Tool={ToolName}",
responseRpcTimestamp,
@@ -926,11 +988,33 @@ private async Task ExecuteToolAndRespondAsync(string requestId, string toolName,
toolCallId,
toolName);
}
- catch (Exception ex)
+ catch (OperationCanceledException) when (cancellationSource.IsCancellationRequested)
+ {
+ // The runtime has already completed the request or the session is shutting down.
+ }
+ catch (RemoteRpcException) when (cancellationSource.IsCancellationRequested)
+ {
+ // Another client answered after this invocation completed locally.
+ }
+ catch (Exception) when (cancellationSource.IsCancellationRequested)
{
+ // Cancellation won the request; no response or error should escape.
+ }
+ catch (Exception ex) when (!cancellationSource.IsCancellationRequested)
+ {
+ if (!TryClaimExternalTool(requestId, cancellationSource))
+ {
+ return;
+ }
+
try
{
- await Rpc.Tools.HandlePendingToolCallAsync(requestId, result: null, error: ex.Message);
+ await Rpc.Tools.HandlePendingToolCallAsync(
+ requestId, result: null, error: ex.Message, cancellationSource.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ // Teardown canceled the in-flight error response.
}
catch (IOException)
{
@@ -940,7 +1024,108 @@ private async Task ExecuteToolAndRespondAsync(string requestId, string toolName,
{
// Connection already disposed — nothing we can do
}
+ catch (RemoteRpcException)
+ {
+ // Another client may have answered the broadcast request first.
+ }
+ }
+ finally
+ {
+ ((ICollection>)_pendingExternalTools)
+ .Remove(new(requestId, cancellationSource));
+ }
+
+ static string GetSingleParameterName(AIFunction tool)
+ {
+ if (tool.JsonSchema.TryGetProperty("properties", out var properties) &&
+ properties.ValueKind == JsonValueKind.Object)
+ {
+ string? parameterName = null;
+ foreach (var property in properties.EnumerateObject())
+ {
+ if (parameterName is not null)
+ {
+ parameterName = null;
+ break;
+ }
+
+ parameterName = property.Name;
+ }
+
+ if (parameterName is not null)
+ {
+ return parameterName;
+ }
+
+ if (tool.JsonSchema.TryGetProperty("required", out var required) &&
+ required.ValueKind == JsonValueKind.Array)
+ {
+ string? requiredParameterName = null;
+ foreach (var requiredParameter in required.EnumerateArray())
+ {
+ if (requiredParameterName is not null)
+ {
+ requiredParameterName = null;
+ break;
+ }
+
+ requiredParameterName = requiredParameter.GetString();
+ }
+
+ if (requiredParameterName is not null &&
+ properties.TryGetProperty(requiredParameterName, out _))
+ {
+ return requiredParameterName;
+ }
+ }
+ }
+
+ throw new ArgumentException(
+ $"Tool '{tool.Name}' received non-object arguments, but its schema does not define exactly one parameter or one required parameter.");
+ }
+ }
+
+ private bool TryClaimExternalTool(string requestId, CancellationTokenSource cancellationSource)
+ => ((ICollection>)_pendingExternalTools)
+ .Remove(new(requestId, cancellationSource));
+
+ private void CancelExternalTool(string requestId)
+ {
+ if (!string.IsNullOrEmpty(requestId) && _pendingExternalTools.TryRemove(requestId, out var cancellationSource))
+ {
+ _ = Task.Run(() =>
+ {
+ try
+ {
+ cancellationSource.Cancel();
+ }
+ catch (AggregateException)
+ {
+ // Cancellation callbacks are consumer code and must not disrupt event dispatch.
+ }
+ catch (ObjectDisposedException)
+ {
+ // The invocation completed while cancellation was being delivered.
+ }
+ });
+ }
+ }
+
+ internal void CancelPendingExternalTools()
+ {
+ try
+ {
+ _externalToolLifetime.Cancel();
+ }
+ catch (AggregateException)
+ {
+ // User cancellation callbacks must not prevent session teardown.
}
+ catch (ObjectDisposedException)
+ {
+ // Session teardown already completed.
+ }
+ _pendingExternalTools.Clear();
}
///
@@ -1846,8 +2031,35 @@ public async Task SetModelAsync(string model, SetModelOptions options, Cancellat
ArgumentNullException.ThrowIfNull(model);
ThrowIfDisposed();
+ if (options.AutoTier is not null && options.ResetAutoTier)
+ {
+ throw new ArgumentException(
+ $"{nameof(SetModelOptions.AutoTier)} and {nameof(SetModelOptions.ResetAutoTier)} are mutually exclusive.",
+ nameof(options));
+ }
+
+ if (options.ResetAutoTier)
+ {
+ var request = new ModelSwitchToRequest
+ {
+ SessionId = SessionId,
+ ModelId = model,
+ ReasoningEffort = options.ReasoningEffort,
+ ReasoningSummary = options.ReasoningSummary,
+ ModelCapabilities = options.ModelCapabilities,
+ ContextTier = options.ContextTier,
+ };
+ await CopilotClient.InvokeRpcAsync(
+ Rpc,
+ "session.model.switchTo",
+ [WithExplicitNullAutoTier(request, RpcJsonContext.Default.ModelSwitchToRequest)],
+ cancellationToken);
+ return;
+ }
+
await Rpc.Model.SwitchToAsync(
modelId: model,
+ autoTier: options.AutoTier,
reasoningEffort: options.ReasoningEffort,
reasoningSummary: options.ReasoningSummary,
verbosity: null,
@@ -1857,6 +2069,69 @@ await Rpc.Model.SwitchToAsync(
cancellationToken: cancellationToken);
}
+ ///
+ /// Changes the Auto routing preference without changing the selected model.
+ ///
+ ///
+ ///
+ /// The runtime does not apply the preference immediately. It records the request and
+ /// commits it only when a later user turn using the auto model successfully
+ /// obtains a usable model from the provider. A pending status therefore confirms
+ /// that the request was accepted, not that it took effect.
+ ///
+ ///
+ /// Watch for the outcome through the session.model_change event on success, or the
+ /// ephemeral session.auto_tier_switch_failed event on failure. You can also read
+ /// the current committed and in-flight state at any time with
+ /// session.Rpc.Model.GetCurrentAsync.
+ ///
+ ///
+ /// Only the most recent request survives: issuing a new request replaces any earlier one
+ /// that has not yet been claimed by a turn.
+ ///
+ ///
+ /// Routing preference to activate, or to return to the provider's default Auto routing.
+ /// Optional cancellation token.
+ /// The runtime's immediate acknowledgement and Auto preference snapshot.
+ ///
+ ///
+ /// var result = await session.SetAutoTierAsync(AutoTier.Intelligence);
+ ///
+ ///
+ [Experimental(Diagnostics.Experimental)]
+ public async Task SetAutoTierAsync(AutoTier? autoTier, CancellationToken cancellationToken = default)
+ {
+ ThrowIfDisposed();
+
+ if (autoTier is not null)
+ {
+ return await Rpc.Model.SwitchAutoTierAsync(autoTier, cancellationToken: cancellationToken);
+ }
+
+ var request = new ModelSwitchAutoTierRequest { SessionId = SessionId };
+ return await CopilotClient.InvokeRpcAsync(
+ Rpc,
+ "session.model.switchAutoTier",
+ [WithExplicitNullAutoTier(request, RpcJsonContext.Default.ModelSwitchAutoTierRequest)],
+ cancellationToken);
+ }
+
+ ///
+ /// Serializes a generated request and restores the autoTier property as an explicit null.
+ ///
+ ///
+ /// The generated request types omit autoTier when it is null. The runtime reads an
+ /// omitted tier as "leave the current preference alone" and an explicit null as "return to
+ /// provider-default Auto routing", so the null has to survive serialization. Serializing the
+ /// generated type keeps every other field on the request in sync with the schema.
+ ///
+ private static JsonObject WithExplicitNullAutoTier(T request, JsonTypeInfo typeInfo)
+ {
+ var payload = JsonSerializer.SerializeToNode(request, typeInfo)!.AsObject();
+ payload["autoTier"] = null;
+ return payload;
+ }
+
///
/// Changes the model for this session.
///
@@ -1933,12 +2208,17 @@ public async ValueTask DisposeAsync()
return;
}
- _eventChannel.Writer.TryComplete();
+ CancelPendingExternalTools();
+ CloseEventChannel();
try
{
- await InvokeRpcAsync
public bool EnableRemoteSessions { get; set; }
+ ///
+ /// Declares the integrating application's identity, forwarded to the runtime on the
+ /// server.connect handshake. Declaring it lets the telemetry the
+ /// runtime emits on this connection be attributed to a consistent surface
+ /// (the application and its Copilot integration) instead of the runtime's own
+ /// build. All fields are optional; leave it to keep
+ /// the runtime's default attribution.
+ ///
+ public CopilotClientInfo? ClientInfo { get; set; }
+
///
/// Creates a shallow clone of this instance.
///
@@ -531,6 +542,38 @@ public sealed class TelemetryConfig
public bool? CaptureContent { get; set; }
}
+///
+/// Identifies the integrating application on the server.connect handshake.
+///
+///
+/// Declaring it lets the telemetry the runtime emits on the connection be
+/// attributed to a single, consistent surface instead of the runtime's own
+/// build. All properties are optional; an unset property is omitted from the
+/// handshake.
+///
+public sealed class CopilotClientInfo
+{
+ ///
+ /// Name of the application using the SDK.
+ ///
+ public string? ApplicationName { get; set; }
+
+ ///
+ /// Version of the application using the SDK.
+ ///
+ public string? ApplicationVersion { get; set; }
+
+ ///
+ /// Optionally specifies a named integration within the application, such as an extension or plugin.
+ ///
+ public string? IntegrationName { get; set; }
+
+ ///
+ /// Optionally specifies the version of that integration.
+ ///
+ public string? IntegrationVersion { get; set; }
+}
+
///
/// Configuration for a custom session filesystem provider.
///
@@ -2398,6 +2441,20 @@ public sealed class CapiSessionOptions
///
[JsonPropertyName("enableWebSocketResponses")]
public bool? EnableWebSocketResponses { get; set; }
+
+ ///
+ /// Routing tier for model auto with V2 Auto.
+ ///
+ ///
+ /// Requires a runtime that supports Auto tiers; it has no effect outside V2 Auto.
+ /// When omitted, the runtime uses its default on create and restores the last committed
+ /// tier on cold resume. On resident resume, a different tier requests a safe switch that
+ /// takes effect after resume succeeds and never disturbs a turn that is already running.
+ /// To change the preference on a live session, use
+ /// .
+ ///
+ [JsonPropertyName("autoTier")]
+ public AutoTier? AutoTier { get; set; }
}
///
@@ -2962,6 +3019,26 @@ public struct SetModelOptions
/// Per-property overrides for model capabilities, deep-merged over runtime defaults.
public ModelCapabilitiesOverride? ModelCapabilities { get; set; }
+
+ ///
+ /// Routing preference to stage atomically with selecting the auto model.
+ ///
+ ///
+ /// Leave unset to leave the current preference alone. Set
+ /// instead to return to the provider's default Auto
+ /// routing. The runtime rejects this option when the model is anything other than
+ /// auto; use to change the
+ /// preference without changing the selected model.
+ ///
+ [Experimental(Diagnostics.Experimental)]
+ public AutoTier? AutoTier { get; set; }
+
+ ///
+ /// Returns to the provider's default Auto routing as part of this switch.
+ /// Mutually exclusive with .
+ ///
+ [Experimental(Diagnostics.Experimental)]
+ public bool ResetAutoTier { get; set; }
}
///
diff --git a/dotnet/src/build/GitHub.Copilot.SDK.targets b/dotnet/src/build/GitHub.Copilot.SDK.targets
index 95770dba8e..a5aba612ab 100644
--- a/dotnet/src/build/GitHub.Copilot.SDK.targets
+++ b/dotnet/src/build/GitHub.Copilot.SDK.targets
@@ -26,7 +26,7 @@
-
+
<_CopilotPlatform Condition="'$(_CopilotRid)' == 'win-x64'">win32-x64
<_CopilotPlatform Condition="'$(_CopilotRid)' == 'win-arm64'">win32-arm64
@@ -49,15 +49,16 @@
<_CopilotRuntimeLib Condition="'$(_CopilotRuntimeLib)' == ''">libcopilot_runtime.so
-
+ COPILOT_CLI_DOWNLOAD_BASE_URL is also honored. -->
- https://registry.npmjs.org
+ $(COPILOT_CLI_DOWNLOAD_BASE_URL)
+ https://github.com/github/copilot-cli/releases/download
@@ -93,23 +94,58 @@
<_CopilotCacheDir>$(IntermediateOutputPath)copilot-cli\$(CopilotCliVersion)\$(_CopilotPlatform)
- <_CopilotCliBinaryPath Condition="'$(_CopilotCliBinaryPath)' == ''">$(_CopilotCacheDir)\$(_CopilotBinary)
+
+ <_CopilotCliBinaryPath Condition="'$(_CopilotCliBinaryPath)' == ''">$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\$(_CopilotRuntimeWrapper)
+ <_CopilotRuntimeNodePath>$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\runtime.node
+ <_CopilotRuntimeBundleCompleteMarker>$(_CopilotCacheDir)\.copilot-runtime-complete
<_CopilotArchivePath>$(_CopilotCacheDir)\copilot.tgz
- <_CopilotNormalizedRegistryUrl>$([System.String]::Copy('$(CopilotNpmRegistryUrl)').TrimEnd('/'))
- <_CopilotDownloadUrl>$(_CopilotNormalizedRegistryUrl)/@github/copilot-$(_CopilotPlatform)/-/copilot-$(_CopilotPlatform)-$(CopilotCliVersion).tgz
+ <_CopilotChecksumPath>$(_CopilotCacheDir)\SHA256SUMS.txt
+ <_CopilotAssetName>github-copilot-$(CopilotCliVersion)-$(_CopilotPlatform).tgz
+ <_CopilotAssetNameRegex>$([System.Text.RegularExpressions.Regex]::Escape('$(_CopilotAssetName)'))
+ <_CopilotNormalizedReleaseBaseUrl>$([System.String]::Copy('$(CopilotCliReleaseBaseUrl)').TrimEnd('/'))
+ <_CopilotReleaseUrl>$(_CopilotNormalizedReleaseBaseUrl)/v$(CopilotCliVersion)
+ <_CopilotDownloadUrl>$(_CopilotReleaseUrl)/$(_CopilotAssetName)
+ <_CopilotChecksumsUrl>$(_CopilotReleaseUrl)/SHA256SUMS.txt
+ <_CopilotRuntimeBundleMissing Condition="!Exists('$(_CopilotCliBinaryPath)') Or !Exists('$(_CopilotRuntimeNodePath)') Or !Exists('$(_CopilotRuntimeBundleCompleteMarker)')">true
<_CopilotCliDownloadTimeoutMs>$([System.Convert]::ToInt32($([MSBuild]::Multiply($(CopilotCliDownloadTimeout), 1000))))
-
-
+
+
-
-
+
+
+
+
+
+
+
+ <_CopilotChecksumMatch Include="@(_CopilotChecksumLine)"
+ Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Identity)', '^[0-9a-fA-F]{64}[\t ]+\*?$(_CopilotAssetNameRegex)[\t ]*$'))" />
+
+
+ Condition="'$(_CopilotRuntimeBundleMissing)' == 'true'" />
+
+
+
+
+ <_CopilotChecksumLineValue>@(_CopilotChecksumMatch)
+ <_CopilotArchiveHashValue>@(_CopilotArchiveHash->'%(FileHash)')
+ <_CopilotExpectedChecksum>$([System.String]::Copy('$(_CopilotChecksumLineValue)').Substring(0, 64).ToUpperInvariant())
+ <_CopilotActualChecksum>$([System.String]::Copy('$(_CopilotArchiveHashValue)').ToUpperInvariant())
+ <_CopilotChecksumMismatch Condition="'$(_CopilotExpectedChecksum)' != '$(_CopilotActualChecksum)'">true
+
+
+
@@ -117,23 +153,26 @@
<_TarCommand Condition="'$(_TarCommand)' == ''">tar
+ Condition="'$(_CopilotRuntimeBundleMissing)' == 'true'" />
-
+
+
+
-
+
<_CopilotCacheDir Condition="'$(_CopilotCacheDir)' == ''">$(IntermediateOutputPath)copilot-cli\$(CopilotCliVersion)\$(_CopilotPlatform)
- <_CopilotCliBinaryPath Condition="'$(_CopilotCliBinaryPath)' == ''">$(_CopilotCacheDir)\$(_CopilotBinary)
+ <_CopilotCliBinaryPath Condition="'$(_CopilotCliBinaryPath)' == ''">$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\$(_CopilotRuntimeWrapper)
<_CopilotOutputDir>$(OutDir)runtimes\$(_CopilotRid)\native
<_CopilotRuntimeNodePath>$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\runtime.node
<_CopilotRuntimeWrapperPath Condition="'$(_CopilotRuntimeWrapperPath)' == ''">$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\$(_CopilotRuntimeWrapper)
@@ -153,7 +192,7 @@
<_CopilotRuntimeRootAsset Include="$(_CopilotCacheDir)\**\*"
- Exclude="$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot-sdk\**\*;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\preloads\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sdk\**\*;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*" />
+ Exclude="$(_CopilotCacheDir)\.copilot-runtime-complete;$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\SHA256SUMS.txt;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*" />
<_CopilotRuntimePrebuildAsset Include="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\**\*"
Exclude="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\cli-native.node;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\mediaremote-adapter\**\*;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\copilot-runtime-bin*" />
@@ -176,12 +215,12 @@
-
<_CopilotCacheDir Condition="'$(_CopilotCacheDir)' == ''">$(IntermediateOutputPath)copilot-cli\$(CopilotCliVersion)\$(_CopilotPlatform)
- <_CopilotCliBinaryPath Condition="'$(_CopilotCliBinaryPath)' == ''">$(_CopilotCacheDir)\$(_CopilotBinary)
+ <_CopilotCliBinaryPath Condition="'$(_CopilotCliBinaryPath)' == ''">$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\$(_CopilotRuntimeWrapper)
<_CopilotRuntimeNodePath>$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\runtime.node
<_CopilotRuntimeWrapperPath Condition="'$(_CopilotRuntimeWrapperPath)' == ''">$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\$(_CopilotRuntimeWrapper)
<_CopilotExplicitCliMarker>$(_CopilotCacheDir)\.copilot-explicit-cli
@@ -193,7 +232,7 @@
Condition="'$(CopilotCliBinaryPath)' != ''" />
<_CopilotRuntimeRootAsset Include="$(_CopilotCacheDir)\**\*"
- Exclude="$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot-sdk\**\*;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\preloads\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sdk\**\*;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*"
+ Exclude="$(_CopilotCacheDir)\.copilot-runtime-complete;$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\SHA256SUMS.txt;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*"
Condition="Exists('$(_CopilotRuntimeWrapperPath)') And Exists('$(_CopilotRuntimeNodePath)')" />
<_CopilotRuntimePrebuildAsset Include="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\**\*"
Exclude="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\cli-native.node;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\mediaremote-adapter\**\*;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\copilot-runtime-bin*"
diff --git a/dotnet/test/E2E/AutoTierE2ETests.cs b/dotnet/test/E2E/AutoTierE2ETests.cs
new file mode 100644
index 0000000000..3758eee0f5
--- /dev/null
+++ b/dotnet/test/E2E/AutoTierE2ETests.cs
@@ -0,0 +1,88 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+using GitHub.Copilot.Rpc;
+using GitHub.Copilot.Test.Harness;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace GitHub.Copilot.Test.E2E;
+
+///
+/// Mirrors nodejs/test/e2e/auto_tier.e2e.test.ts (snapshot category "auto_tier").
+///
+///
+/// The runtime stages an Auto routing preference instead of applying it immediately: a
+/// request stays unclaimed until a later turn using the auto model mints a usable
+/// model and token pair. These tests observe that staged state through
+/// Model.GetCurrentAsync, so they assert what the runtime actually recorded rather
+/// than what the SDK serialized.
+///
+public class AutoTierE2ETests(E2ETestFixture fixture, ITestOutputHelper output)
+ : E2ETestBase(fixture, "auto_tier", output)
+{
+ private static async Task AssertPendingAutoTierAsync(CopilotSession session, AutoTier? expected)
+ {
+ var current = await session.Rpc.Model.GetCurrentAsync();
+ Assert.Equal(expected, current.PendingAutoTier);
+ }
+
+ [Fact]
+ public async Task Should_Stage_And_Reset_Auto_Tier_Preference()
+ {
+ await using var session = await CreateSessionAsync(new SessionConfig
+ {
+ Model = "auto",
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ });
+
+ await AssertPendingAutoTierAsync(session, null);
+
+ var staged = await session.SetAutoTierAsync(AutoTier.Efficiency);
+ Assert.Equal(ModelSwitchAutoTierStatus.Pending, staged.Status);
+ Assert.Equal(AutoTier.Efficiency, staged.PendingAutoTier);
+ await AssertPendingAutoTierAsync(session, AutoTier.Efficiency);
+
+ // A second request replaces the first and reports the one it displaced.
+ var superseded = await session.SetAutoTierAsync(AutoTier.Intelligence);
+ Assert.Equal(ModelSwitchAutoTierStatus.Pending, superseded.Status);
+ Assert.Equal(AutoTier.Intelligence, superseded.PendingAutoTier);
+ Assert.Equal(AutoTier.Efficiency, superseded.SupersededAutoTier);
+ await AssertPendingAutoTierAsync(session, AutoTier.Intelligence);
+
+ // A null tier returns the session to provider-default routing. The status is
+ // Unchanged because provider-default was already the committed preference; the
+ // request's effect is cancelling the staged one.
+ var reset = await session.SetAutoTierAsync(null);
+ Assert.Equal(ModelSwitchAutoTierStatus.Unchanged, reset.Status);
+ Assert.Equal(AutoTier.Intelligence, reset.SupersededAutoTier);
+ await AssertPendingAutoTierAsync(session, null);
+ }
+
+ [Fact]
+ public async Task Should_Preserve_Auto_Tier_When_Set_Model_Omits_It()
+ {
+ await using var session = await CreateSessionAsync(new SessionConfig
+ {
+ Model = "auto",
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ });
+
+ await session.SetAutoTierAsync(AutoTier.Balance);
+ await AssertPendingAutoTierAsync(session, AutoTier.Balance);
+
+ // Leaving AutoTier unset without asking for a reset leaves the staged preference alone.
+ await session.SetModelAsync("auto", new SetModelOptions());
+ await AssertPendingAutoTierAsync(session, AutoTier.Balance);
+
+ // Supplying a tier replaces it.
+ await session.SetModelAsync("auto", new SetModelOptions { AutoTier = AutoTier.Intelligence });
+ await AssertPendingAutoTierAsync(session, AutoTier.Intelligence);
+
+ // ResetAutoTier clears it. Omission, a value, and a reset are three distinct
+ // outcomes, which is why a single nullable property cannot express the request.
+ await session.SetModelAsync("auto", new SetModelOptions { ResetAutoTier = true });
+ await AssertPendingAutoTierAsync(session, null);
+ }
+}
diff --git a/dotnet/test/E2E/ClientLifecycleE2ETests.cs b/dotnet/test/E2E/ClientLifecycleE2ETests.cs
index 4b09c695d4..82b2d2badf 100644
--- a/dotnet/test/E2E/ClientLifecycleE2ETests.cs
+++ b/dotnet/test/E2E/ClientLifecycleE2ETests.cs
@@ -121,7 +121,7 @@ public async Task Should_Receive_Session_Deleted_Lifecycle_Event_When_Deleted()
}
});
- // Do NOT DisposeAsync the session before deleting: dispose sends session.destroy
+ // Do NOT DisposeAsync the session before deleting: dispose sends session.detach
// which closes in-memory state but does not remove the disk file; calling
// delete afterwards still succeeds, but skipping dispose keeps the test minimal.
await Client.DeleteSessionAsync(sessionId);
diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs
index 5391e4bdbc..1ff473bd01 100644
--- a/dotnet/test/E2E/ClientOptionsE2ETests.cs
+++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs
@@ -296,7 +296,7 @@ public async Task Should_Forward_Advanced_Session_Options_In_Create_Wire_Request
var session = await Ctx.CreateSessionAsync(client, new SessionConfig
{
ClientName = "advanced-create-client",
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
ReasoningEffort = "medium",
ReasoningSummary = ReasoningSummary.Detailed,
ContextTier = ContextTier.LongContext,
@@ -379,7 +379,7 @@ public async Task Should_Forward_Advanced_Session_Options_In_Create_Wire_Request
Provider = "create-provider",
Id = "create-model",
Name = "Create Model",
- ModelId = "claude-sonnet-4.5",
+ ModelId = "claude-sonnet-5",
WireModel = "create-wire-model",
MaxContextWindowTokens = 12_000,
MaxPromptTokens = 10_000,
@@ -392,7 +392,7 @@ public async Task Should_Forward_Advanced_Session_Options_In_Create_Wire_Request
using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath));
var createRequest = GetCapturedRequestParams(capture.RootElement, "session.create");
Assert.Equal("advanced-create-client", createRequest.GetProperty("clientName").GetString());
- Assert.Equal("claude-sonnet-4.5", createRequest.GetProperty("model").GetString());
+ Assert.Equal("claude-sonnet-5", createRequest.GetProperty("model").GetString());
Assert.Equal("medium", createRequest.GetProperty("reasoningEffort").GetString());
Assert.Equal("detailed", createRequest.GetProperty("reasoningSummary").GetString());
Assert.Equal("long_context", createRequest.GetProperty("contextTier").GetString());
@@ -442,7 +442,7 @@ public async Task Should_Forward_Singular_Provider_Options_In_Create_Wire_Reques
var session = await Ctx.CreateSessionAsync(client, new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
Provider = new ProviderConfig
{
Type = "azure",
@@ -453,7 +453,7 @@ public async Task Should_Forward_Singular_Provider_Options_In_Create_Wire_Reques
BearerToken = "provider-bearer-token",
Azure = new AzureOptions { ApiVersion = "2024-02-15-preview" },
Headers = new Dictionary { ["X-Provider-Wire"] = "yes" },
- ModelId = "claude-sonnet-4.5",
+ ModelId = "claude-sonnet-5",
WireModel = "azure-deployment",
MaxPromptTokens = 8192,
MaxOutputTokens = 1024,
@@ -471,7 +471,7 @@ public async Task Should_Forward_Singular_Provider_Options_In_Create_Wire_Reques
Assert.Equal("provider-bearer-token", provider.GetProperty("bearerToken").GetString());
Assert.Equal("2024-02-15-preview", provider.GetProperty("azure").GetProperty("apiVersion").GetString());
Assert.Equal("yes", provider.GetProperty("headers").GetProperty("X-Provider-Wire").GetString());
- Assert.Equal("claude-sonnet-4.5", provider.GetProperty("modelId").GetString());
+ Assert.Equal("claude-sonnet-5", provider.GetProperty("modelId").GetString());
Assert.Equal("azure-deployment", provider.GetProperty("wireModel").GetString());
Assert.Equal(8192, provider.GetProperty("maxPromptTokens").GetInt32());
Assert.Equal(1024, provider.GetProperty("maxOutputTokens").GetInt32());
diff --git a/dotnet/test/E2E/CopilotRequestE2EProvider.cs b/dotnet/test/E2E/CopilotRequestE2EProvider.cs
index 89826b4f84..f4c250752a 100644
--- a/dotnet/test/E2E/CopilotRequestE2EProvider.cs
+++ b/dotnet/test/E2E/CopilotRequestE2EProvider.cs
@@ -160,9 +160,9 @@ internal static HttpResponseMessage BuildNonInferenceResponse(string url)
private static readonly string[] ChatCompletionStreamEvents =
[
- "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4.5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"},\"finish_reason\":null}]}\n\n",
- "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4.5\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"" + SyntheticText + "\"},\"finish_reason\":null}]}\n\n",
- "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4.5\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":7,\"total_tokens\":12}}\n\n",
+ "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"},\"finish_reason\":null}]}\n\n",
+ "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-5\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"" + SyntheticText + "\"},\"finish_reason\":null}]}\n\n",
+ "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-5\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":7,\"total_tokens\":12}}\n\n",
"data: [DONE]\n\n",
];
@@ -172,7 +172,7 @@ internal static HttpResponseMessage BuildNonInferenceResponse(string url)
// runtime's Anthropic client fail with "stream ended without producing a Message".
private static readonly string[] AnthropicStreamEvents =
[
- "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-4.5\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":1}}}\n\n",
+ "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-5\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":1}}}\n\n",
"event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n",
"event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"" + SyntheticText + "\"}}\n\n",
"event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\n",
@@ -184,13 +184,13 @@ internal static HttpResponseMessage BuildNonInferenceResponse(string url)
"{\"id\":\"resp_stub_1\",\"object\":\"response\",\"status\":\"completed\",\"output\":[{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"" + SyntheticText + "\"}]}],\"usage\":{\"input_tokens\":5,\"output_tokens\":7,\"total_tokens\":12}}";
private static readonly string BufferedChatCompletionJson =
- "{\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion\",\"created\":1,\"model\":\"claude-sonnet-4.5\",\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"" + SyntheticText + "\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":7,\"total_tokens\":12}}";
+ "{\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion\",\"created\":1,\"model\":\"claude-sonnet-5\",\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"" + SyntheticText + "\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":7,\"total_tokens\":12}}";
private static readonly string BufferedAnthropicMessageJson =
- "{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-4.5\",\"content\":[{\"type\":\"text\",\"text\":\"" + SyntheticText + "\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":7}}";
+ "{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-5\",\"content\":[{\"type\":\"text\",\"text\":\"" + SyntheticText + "\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":7}}";
private const string ModelCatalogJson =
- "{\"data\":[{\"id\":\"claude-sonnet-4.5\",\"name\":\"Claude Sonnet 4.5\",\"object\":\"model\",\"vendor\":\"Anthropic\",\"version\":\"1\",\"preview\":false,\"model_picker_enabled\":true,\"capabilities\":{\"type\":\"chat\",\"family\":\"claude-sonnet-4.5\",\"tokenizer\":\"o200k_base\",\"limits\":{\"max_context_window_tokens\":200000,\"max_output_tokens\":8192},\"supports\":{\"streaming\":true,\"tool_calls\":true,\"parallel_tool_calls\":true,\"vision\":true}}}]}";
+ "{\"data\":[{\"id\":\"claude-sonnet-5\",\"name\":\"Claude Sonnet 5\",\"object\":\"model\",\"vendor\":\"Anthropic\",\"version\":\"1\",\"preview\":false,\"model_picker_enabled\":true,\"capabilities\":{\"type\":\"chat\",\"family\":\"claude-sonnet-5\",\"tokenizer\":\"o200k_base\",\"limits\":{\"max_context_window_tokens\":200000,\"max_output_tokens\":8192},\"supports\":{\"streaming\":true,\"tool_calls\":true,\"parallel_tool_calls\":true,\"vision\":true}}}]}";
}
/// A single request the callback intercepted.
diff --git a/dotnet/test/E2E/CopilotRequestSessionIdE2ETests.cs b/dotnet/test/E2E/CopilotRequestSessionIdE2ETests.cs
index fd00cc9b99..2fca912592 100644
--- a/dotnet/test/E2E/CopilotRequestSessionIdE2ETests.cs
+++ b/dotnet/test/E2E/CopilotRequestSessionIdE2ETests.cs
@@ -76,15 +76,15 @@ public async Task Threads_The_Session_Id_Into_A_Byok_Session_Inference_Request()
{
OnPermissionRequest = PermissionHandler.ApproveAll,
// BYOK providers require an explicit model id.
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
Provider = new ProviderConfig
{
Type = "openai",
WireApi = "responses",
BaseUrl = "https://byok.invalid/v1",
ApiKey = "byok-secret",
- ModelId = "claude-sonnet-4.5",
- WireModel = "claude-sonnet-4.5",
+ ModelId = "claude-sonnet-5",
+ WireModel = "claude-sonnet-5",
},
});
var byokSessionId = session.SessionId;
diff --git a/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs b/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs
index 80ccdb8c90..e6890d6992 100644
--- a/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs
+++ b/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs
@@ -349,7 +349,7 @@ private static (string Type, string Json)[] ResponseEvents(string text, string i
];
private const string ModelCatalogJson =
- "{\"data\":[{\"id\":\"claude-sonnet-4.5\",\"name\":\"Claude Sonnet 4.5\",\"object\":\"model\",\"vendor\":\"Anthropic\",\"version\":\"1\",\"preview\":false,\"model_picker_enabled\":true,\"supported_endpoints\":[\"/responses\",\"ws:/responses\"],\"capabilities\":{\"type\":\"chat\",\"family\":\"claude-sonnet-4.5\",\"tokenizer\":\"o200k_base\",\"limits\":{\"max_context_window_tokens\":200000,\"max_output_tokens\":8192},\"supports\":{\"streaming\":true,\"tool_calls\":true,\"parallel_tool_calls\":true,\"vision\":true}}}]}";
+ "{\"data\":[{\"id\":\"claude-sonnet-5\",\"name\":\"Claude Sonnet 5\",\"object\":\"model\",\"vendor\":\"Anthropic\",\"version\":\"1\",\"preview\":false,\"model_picker_enabled\":true,\"supported_endpoints\":[\"/responses\",\"ws:/responses\"],\"capabilities\":{\"type\":\"chat\",\"family\":\"claude-sonnet-5\",\"tokenizer\":\"o200k_base\",\"limits\":{\"max_context_window_tokens\":200000,\"max_output_tokens\":8192},\"supports\":{\"streaming\":true,\"tool_calls\":true,\"parallel_tool_calls\":true,\"vision\":true}}}]}";
private static int GetFreePort()
{
diff --git a/dotnet/test/E2E/EventFidelityE2ETests.cs b/dotnet/test/E2E/EventFidelityE2ETests.cs
index 8882f972d5..da9869757f 100644
--- a/dotnet/test/E2E/EventFidelityE2ETests.cs
+++ b/dotnet/test/E2E/EventFidelityE2ETests.cs
@@ -12,7 +12,7 @@ namespace GitHub.Copilot.Test.E2E;
/// Verifies the shape and ordering of s emitted from the
/// runtime: every event has an id and timestamp, user/assistant messages carry
/// content, tool execution events carry a toolCallId, and
-/// session.idle is the last event of a turn. Mirrors
+/// session.idle follows the final assistant message of a turn. Mirrors
/// nodejs/test/e2e/event_fidelity.e2e.test.ts.
///
public class EventFidelityE2ETests(E2ETestFixture fixture, ITestOutputHelper output)
@@ -43,9 +43,12 @@ await session.SendAndWaitAsync(new MessageOptions
var assistantIdx = types.LastIndexOf("assistant.message");
Assert.True(userIdx < assistantIdx, $"Expected user.message ({userIdx}) before last assistant.message ({assistantIdx})");
- // session.idle should be the last event we observed
+ // session.idle should complete the conversational turn. Post-turn
+ // metadata events may arrive after it on slower target frameworks.
var idleIdx = types.LastIndexOf("session.idle");
- Assert.Equal(types.Count - 1, idleIdx);
+ Assert.True(
+ assistantIdx < idleIdx,
+ $"Expected last assistant.message ({assistantIdx}) before session.idle ({idleIdx}): {string.Join(", ", types)}");
await session.DisposeAsync();
}
diff --git a/dotnet/test/E2E/ExternalToolCancellationE2ETests.cs b/dotnet/test/E2E/ExternalToolCancellationE2ETests.cs
new file mode 100644
index 0000000000..b7f34fd037
--- /dev/null
+++ b/dotnet/test/E2E/ExternalToolCancellationE2ETests.cs
@@ -0,0 +1,62 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+using Microsoft.Extensions.AI;
+using System.ComponentModel;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace GitHub.Copilot.Test.E2E;
+
+public class ExternalToolCancellationE2ETests(E2ETestFixture fixture, ITestOutputHelper output)
+ : E2ETestBase(fixture, "external_tool_cancellation", output)
+{
+ [Fact]
+ public async Task Should_Cancel_Tool_Handler_When_Session_Disposes()
+ {
+ var toolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var toolCancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var releaseTool = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ var session = await CreateSessionAsync(new SessionConfig
+ {
+ Tools = [AIFunctionFactory.Create(SlowTool, "slow_analysis")],
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ });
+
+ _ = session.SendAsync(new MessageOptions
+ {
+ Prompt = "Use slow_analysis with value 'test_abort'. Wait for the result.",
+ });
+
+ var startedValue = await toolStarted.Task.WaitAsync(TimeSpan.FromSeconds(60));
+ Assert.Equal("test_abort", startedValue);
+
+ await session.DisposeAsync();
+ await toolCancelled.Task.WaitAsync(TimeSpan.FromSeconds(60));
+
+ releaseTool.TrySetResult("RELEASED");
+
+ [Description("A slow analysis tool that blocks until released")]
+ async Task SlowTool([Description("Value to analyze")] string value, CancellationToken cancellationToken)
+ {
+ toolStarted.TrySetResult(value);
+ try
+ {
+ var completed = await Task.WhenAny(releaseTool.Task, Task.Delay(Timeout.Infinite, cancellationToken));
+ if (completed == releaseTool.Task)
+ {
+ return await releaseTool.Task;
+ }
+
+ throw new OperationCanceledException(cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ toolCancelled.TrySetResult(true);
+ throw;
+ }
+ }
+ }
+}
diff --git a/dotnet/test/E2E/McpOAuthE2ETests.cs b/dotnet/test/E2E/McpOAuthE2ETests.cs
index 1085aba040..e947ea7640 100644
--- a/dotnet/test/E2E/McpOAuthE2ETests.cs
+++ b/dotnet/test/E2E/McpOAuthE2ETests.cs
@@ -7,6 +7,7 @@
using System.Diagnostics;
using System.Net.Http;
using System.Text.Json;
+using System.Threading.Channels;
using Xunit;
using Xunit.Abstractions;
@@ -48,6 +49,7 @@ public async Task Should_Satisfy_MCP_OAuth_Using_Host_Provided_Token()
}
});
+ await session.Rpc.Mcp.ReloadAsync();
await WaitForMcpServerStatusAsync(session, serverName, McpServerStatus.Connected);
var tools = await session.Rpc.Mcp.ListToolsAsync(serverName);
Assert.Contains(tools.Tools, tool => tool.Name == "whoami");
@@ -75,14 +77,14 @@ public async Task Should_Resolve_Pending_MCP_OAuth_Request_With_Direct_Rpc()
{
await using var oauthServer = await OAuthMcpServer.StartAsync(ExpectedToken);
var serverName = "oauth-direct-rpc-mcp";
- var authRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var authRequests = Channel.CreateUnbounded();
var releaseHandler = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
await using var session = await CreateSessionAsync(new SessionConfig
{
OnMcpAuthRequest = request =>
{
- authRequest.TrySetResult(request);
+ authRequests.Writer.TryWrite(request);
return releaseHandler.Task;
},
McpServers = new Dictionary
@@ -95,8 +97,27 @@ public async Task Should_Resolve_Pending_MCP_OAuth_Request_With_Direct_Rpc()
},
});
+ var reload = session.Rpc.Mcp.ReloadAsync();
var connected = WaitForMcpServerStatusAsync(session, serverName, McpServerStatus.Connected);
- var request = await authRequest.Task.WaitAsync(TimeSpan.FromSeconds(30));
+ var request = await authRequests.Reader.ReadAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(30));
+
+ while (true)
+ {
+ var handled = await session.Rpc.Mcp.Oauth.HandlePendingRequestAsync(
+ request.RequestId,
+ new McpOauthPendingRequestResponseToken
+ {
+ AccessToken = ExpectedToken,
+ TokenType = "Bearer",
+ ExpiresIn = 3600,
+ });
+ if (handled.Success)
+ {
+ break;
+ }
+ request = await authRequests.Reader.ReadAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(30));
+ }
+
Assert.NotEmpty(request.RequestId);
Assert.Equal(serverName, request.ServerName);
Assert.Equal($"{oauthServer.Url}/mcp", request.ServerUrl);
@@ -104,21 +125,11 @@ public async Task Should_Resolve_Pending_MCP_OAuth_Request_With_Direct_Rpc()
Assert.NotNull(request.WwwAuthenticateParams);
Assert.Equal("mcp.read", request.WwwAuthenticateParams!.Scope);
- var handled = await session.Rpc.Mcp.Oauth.HandlePendingRequestAsync(
- request.RequestId,
- new McpOauthPendingRequestResponseToken
- {
- AccessToken = ExpectedToken,
- TokenType = "Bearer",
- ExpiresIn = 3600,
- });
- Assert.True(handled.Success);
-
+ releaseHandler.SetResult(McpAuthResult.FromToken(new McpAuthToken { AccessToken = ExpectedToken }));
+ await reload;
await connected;
var tools = await session.Rpc.Mcp.ListToolsAsync(serverName);
Assert.Contains(tools.Tools, tool => tool.Name == "whoami");
-
- releaseHandler.SetResult(McpAuthResult.FromToken(new McpAuthToken { AccessToken = ExpectedToken }));
}
[Fact]
@@ -178,14 +189,16 @@ public async Task Should_Request_Replacement_Tokens_Across_MCP_OAuth_Lifecycle()
}
});
+ await session.Rpc.Mcp.ReloadAsync();
await WaitForMcpServerStatusAsync(session, serverName, McpServerStatus.Connected);
+ refreshCount = 0;
await CallWhoamiAsync(session, serverName, "refresh");
await CallWhoamiAsync(session, serverName, "upscope");
await CallWhoamiAsync(session, serverName, "reauth");
+ observedReasons.RemoveAll(reason => reason == McpOauthRequestReason.Initial);
Assert.Equal(
[
- McpOauthRequestReason.Initial,
McpOauthRequestReason.Refresh,
McpOauthRequestReason.Upscope,
McpOauthRequestReason.Refresh,
@@ -223,6 +236,7 @@ public async Task Should_Cancel_Pending_MCP_OAuth_Request()
}
});
+ await session.Rpc.Mcp.ReloadAsync();
await WaitForMcpServerStatusAsync(session, serverName, McpServerStatus.NeedsAuth);
// The MCP connection is kicked off by session.create, but the SDK only registers its
diff --git a/dotnet/test/E2E/MultiClientE2ETests.cs b/dotnet/test/E2E/MultiClientE2ETests.cs
index 4dbe7190ad..7597aa580b 100644
--- a/dotnet/test/E2E/MultiClientE2ETests.cs
+++ b/dotnet/test/E2E/MultiClientE2ETests.cs
@@ -324,6 +324,9 @@ public async Task Disconnecting_Client_Removes_Its_Tools()
// Disconnect client 2
await Client2.ForceStopAsync();
+ // Give the server time to process the connection close and remove tools.
+ await Task.Delay(500);
+
// Recreate client2 for cleanup
var port = Client1.RuntimePort!.Value;
_client2 = Ctx.CreateClient(options: new CopilotClientOptions
diff --git a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs
index bf1ed687c1..bc3f4bd672 100644
--- a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs
+++ b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs
@@ -140,10 +140,12 @@ await session1.SendAsync(new MessageOptions
}
[Description("Looks up a value after resumption")]
- async Task BlockingExternalTool([Description("Value to look up")] string value)
+ async Task BlockingExternalTool(
+ [Description("Value to look up")] string value,
+ CancellationToken cancellationToken)
{
originalToolStarted.TrySetResult(value);
- return await releaseOriginalTool.Task;
+ return await releaseOriginalTool.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken);
}
}
@@ -274,11 +276,13 @@ await TestHelper.WaitForConditionAsync(
}
[Description("Looks up a value after resumption")]
- async Task BlockingExternalTool([Description("Value to look up")] string value)
+ async Task BlockingExternalTool(
+ [Description("Value to look up")] string value,
+ CancellationToken cancellationToken)
{
Interlocked.Increment(ref invocationCount);
originalToolStarted.TrySetResult(value);
- return await releaseOriginalTool.Task;
+ return await releaseOriginalTool.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken);
}
[Description("Looks up a value after resumption")]
@@ -327,6 +331,8 @@ await Task.WhenAll(
Assert.Equal("beta", await originalToolBStarted.Task);
await suspendedClient.ForceStopAsync();
+ releaseOriginalToolA.TrySetResult("ORIGINAL_A_SHOULD_NOT_WIN");
+ releaseOriginalToolB.TrySetResult("ORIGINAL_B_SHOULD_NOT_WIN");
await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) });
var session2 = await Ctx.ResumeSessionAsync(resumedClient, sessionId, new ResumeSessionConfig
@@ -356,17 +362,21 @@ await Task.WhenAll(
}
[Description("Looks up the first value after resumption")]
- async Task BlockingToolA([Description("Value to look up")] string value)
+ async Task BlockingToolA(
+ [Description("Value to look up")] string value,
+ CancellationToken cancellationToken)
{
originalToolAStarted.TrySetResult(value);
- return await releaseOriginalToolA.Task;
+ return await releaseOriginalToolA.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken);
}
[Description("Looks up the second value after resumption")]
- async Task BlockingToolB([Description("Value to look up")] string value)
+ async Task BlockingToolB(
+ [Description("Value to look up")] string value,
+ CancellationToken cancellationToken)
{
originalToolBStarted.TrySetResult(value);
- return await releaseOriginalToolB.Task;
+ return await releaseOriginalToolB.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken);
}
}
diff --git a/dotnet/test/E2E/RewindE2ETests.cs b/dotnet/test/E2E/RewindE2ETests.cs
index 3152b2e908..e8379f988d 100644
--- a/dotnet/test/E2E/RewindE2ETests.cs
+++ b/dotnet/test/E2E/RewindE2ETests.cs
@@ -24,7 +24,7 @@ public async Task Should_Restore_Tracked_File_And_Conversation()
await File.WriteAllTextAsync(filePath, OriginalFileContent);
await using var session = await CreateSessionAsync(new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
EnableFileChangeTracking = true,
});
diff --git a/dotnet/test/E2E/RpcAdditionalEdgeCasesE2ETests.cs b/dotnet/test/E2E/RpcAdditionalEdgeCasesE2ETests.cs
index 241a978a99..5157eb6336 100644
--- a/dotnet/test/E2E/RpcAdditionalEdgeCasesE2ETests.cs
+++ b/dotnet/test/E2E/RpcAdditionalEdgeCasesE2ETests.cs
@@ -26,7 +26,7 @@ public async Task Shell_Exec_With_Zero_Timeout_Does_Not_Kill_Long_Running_Comman
var session = await CreateSessionAsync();
var markerPath = Path.Join(Ctx.WorkDir, $"shell-zero-timeout-{Guid.NewGuid():N}.txt");
var command = OperatingSystem.IsWindows()
- ? $"powershell -NoLogo -NoProfile -Command \"Start-Sleep -Milliseconds 500; Set-Content -LiteralPath '{markerPath}' -Value 'alive'; Start-Sleep -Seconds 60\""
+ ? $"ping 127.0.0.1 -n 2 >nul & echo alive>\"{markerPath}\" & ping 127.0.0.1 -n 61 >nul"
: $"sh -c \"sleep 0.5; printf alive > '{markerPath}'; sleep 60\"";
var execResult = await session.Rpc.Shell.ExecAsync(command, cwd: Path.GetTempPath(), timeout: TimeSpan.Zero);
diff --git a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs
index 0a3513e4b5..3d017d8165 100644
--- a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs
+++ b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs
@@ -57,10 +57,22 @@ private CopilotClient CreateExtensionsClient()
{
return Ctx.CreateClient(options: new CopilotClientOptions
{
- Connection = RuntimeConnection.ForStdio(args: ["--yolo"]),
+ Connection = RuntimeConnection.ForStdio(
+ path: Ctx.GetLegacyCliPath(),
+ args: ["--yolo"]),
}, environment: ExtensionsEnabledEnvironment());
}
+ private static SessionConfig CreateExtensionsSessionConfig(string? workingDirectory = null)
+ {
+ return new SessionConfig
+ {
+ EnableConfigDiscovery = true,
+ WorkingDirectory = workingDirectory,
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ };
+ }
+
///
/// Writes a minimal user extension into {HomeDir}/extensions/{name}/extension.mjs.
/// The body imports @github/copilot-sdk/extension, calls joinSession
@@ -189,12 +201,9 @@ public async Task Discovers_Loads_And_Reports_Running_Extension(string sourceVal
await using var client = CreateExtensionsClient();
- await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig
- {
- EnableConfigDiscovery = true,
- WorkingDirectory = workingDirectory,
- OnPermissionRequest = PermissionHandler.ApproveAll,
- });
+ await using var session = await Ctx.CreateSessionAsync(
+ client,
+ CreateExtensionsSessionConfig(workingDirectory));
var ext = await WaitForExtensionAsync(session, extId, ExtensionStatus.Running);
@@ -214,11 +223,9 @@ public async Task Disable_Then_Enable_Cycles_Extension_Status()
await using var client = CreateExtensionsClient();
- await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig
- {
- EnableConfigDiscovery = true,
- OnPermissionRequest = PermissionHandler.ApproveAll,
- });
+ await using var session = await Ctx.CreateSessionAsync(
+ client,
+ CreateExtensionsSessionConfig());
// Wait until the initial running state is observed before mutating.
await WaitForExtensionAsync(session, extId, ExtensionStatus.Running);
@@ -240,11 +247,9 @@ public async Task Reload_Picks_Up_Extension_Added_After_Session_Create()
// Start the session BEFORE writing the extension so the initial discovery sees nothing.
await using var client = CreateExtensionsClient();
- await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig
- {
- EnableConfigDiscovery = true,
- OnPermissionRequest = PermissionHandler.ApproveAll,
- });
+ await using var session = await Ctx.CreateSessionAsync(
+ client,
+ CreateExtensionsSessionConfig());
// setupExtensionsForSession runs asynchronously; until it completes the
// controller isn't installed and ReloadAsync throws "Extensions not
@@ -285,11 +290,9 @@ public async Task Failed_Extension_Reports_Failed_Status()
await using var client = CreateExtensionsClient();
- await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig
- {
- EnableConfigDiscovery = true,
- OnPermissionRequest = PermissionHandler.ApproveAll,
- });
+ await using var session = await Ctx.CreateSessionAsync(
+ client,
+ CreateExtensionsSessionConfig());
var ext = await WaitForExtensionAsync(session, extId, ExtensionStatus.Failed);
Assert.Equal(extId, ext.Id);
@@ -306,11 +309,9 @@ public async Task Multiple_Extensions_Are_Discovered_Independently()
await using var client = CreateExtensionsClient();
- await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig
- {
- EnableConfigDiscovery = true,
- OnPermissionRequest = PermissionHandler.ApproveAll,
- });
+ await using var session = await Ctx.CreateSessionAsync(
+ client,
+ CreateExtensionsSessionConfig());
await WaitForExtensionAsync(session, ext1Id, ExtensionStatus.Running);
await WaitForExtensionAsync(session, ext2Id, ExtensionStatus.Running);
@@ -328,11 +329,9 @@ public async Task Reload_Preserves_Disabled_State_Across_Calls()
await using var client = CreateExtensionsClient();
- await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig
- {
- EnableConfigDiscovery = true,
- OnPermissionRequest = PermissionHandler.ApproveAll,
- });
+ await using var session = await Ctx.CreateSessionAsync(
+ client,
+ CreateExtensionsSessionConfig());
await WaitForExtensionAsync(session, extId, ExtensionStatus.Running);
diff --git a/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs b/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs
index 0d2942d4bf..ec923b31a1 100644
--- a/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs
+++ b/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs
@@ -33,7 +33,7 @@ public async Task Should_List_And_Toggle_Session_Skills()
{
var skillName = $"session-rpc-skill-{Guid.NewGuid():N}";
var skillsDir = CreateSkillDirectory(skillName, "Session skill controlled by RPC.");
- var session = await CreateSessionAsync(new SessionConfig
+ await using var session = await CreateSessionAsync(new SessionConfig
{
SkillDirectories = [skillsDir],
DisabledSkills = [skillName],
@@ -56,7 +56,7 @@ public async Task Should_Ensure_Skills_Are_Loaded_And_List_Invoked_Skills()
{
var skillName = $"ensure-rpc-skill-{Guid.NewGuid():N}";
var skillsDir = CreateSkillDirectory(skillName, "Skill loaded explicitly by RPC.");
- var session = await CreateSessionAsync(new SessionConfig
+ await using var session = await CreateSessionAsync(new SessionConfig
{
SkillDirectories = [skillsDir],
});
@@ -79,7 +79,7 @@ public async Task Should_Reload_Session_Skills()
Directory.CreateDirectory(skillsDir);
var skillName = $"reload-rpc-skill-{Guid.NewGuid():N}";
- var session = await CreateSessionAsync(new SessionConfig { SkillDirectories = [skillsDir] });
+ await using var session = await CreateSessionAsync(new SessionConfig { SkillDirectories = [skillsDir] });
var before = await session.Rpc.Skills.ListAsync();
Assert.DoesNotContain(before.Skills, skill => string.Equals(skill.Name, skillName, StringComparison.Ordinal));
@@ -95,7 +95,7 @@ public async Task Should_Reload_Session_Skills()
public async Task Should_List_Mcp_Servers_With_Configured_Server()
{
const string serverName = "rpc-list-mcp-server";
- var session = await CreateSessionAsync(new SessionConfig
+ await using var session = await CreateSessionAsync(new SessionConfig
{
McpServers = CreateTestMcpServers(serverName),
});
@@ -111,7 +111,7 @@ public async Task Should_List_Mcp_Servers_With_Configured_Server()
public async Task Should_Set_Mcp_Env_Value_Mode_And_Remove_GitHub_Server()
{
const string serverName = "github";
- var session = await CreateSessionAsync(new SessionConfig
+ await using var session = await CreateSessionAsync(new SessionConfig
{
McpServers = CreateTestMcpServers(serverName),
});
@@ -137,7 +137,7 @@ public async Task Should_Set_Mcp_Env_Value_Mode_And_Remove_GitHub_Server()
public async Task Should_Report_Mcp_Sampling_Failure_And_Cancel_Missing_Sampling()
{
const string serverName = "rpc-sampling-server";
- var session = await CreateSessionAsync(new SessionConfig
+ await using var session = await CreateSessionAsync(new SessionConfig
{
McpServers = CreateTestMcpServers(serverName),
});
@@ -172,7 +172,7 @@ public async Task Should_Report_Mcp_Sampling_Failure_And_Cancel_Missing_Sampling
[Fact]
public async Task Should_List_Plugins()
{
- var session = await CreateSessionAsync();
+ await using var session = await CreateSessionAsync();
var result = await session.Rpc.Plugins.ListAsync();
@@ -314,7 +314,7 @@ public async Task Should_Report_Error_When_Mcp_App_Resource_Is_Not_Available()
[Fact]
public async Task Should_Report_Error_When_Mcp_Host_Is_Not_Initialized()
{
- var session = await CreateSessionAsync();
+ await using var session = await CreateSessionAsync();
await AssertFailureAsync(
() => session.Rpc.Mcp.EnableAsync("missing-server"),
@@ -333,7 +333,7 @@ await AssertFailureAsync(
[Fact]
public async Task Should_Report_Error_When_Mcp_Oauth_Server_Is_Not_Configured()
{
- var session = await CreateSessionAsync(new SessionConfig
+ await using var session = await CreateSessionAsync(new SessionConfig
{
McpServers = CreateTestMcpServers("configured-stdio-server"),
});
@@ -348,7 +348,7 @@ await AssertFailureAsync(
public async Task Should_Report_Error_When_Mcp_Oauth_Server_Is_Not_Remote()
{
const string serverName = "configured-stdio-server";
- var session = await CreateSessionAsync(new SessionConfig
+ await using var session = await CreateSessionAsync(new SessionConfig
{
McpServers = CreateTestMcpServers(serverName),
});
diff --git a/dotnet/test/E2E/RpcServerE2ETests.cs b/dotnet/test/E2E/RpcServerE2ETests.cs
index 2df8593cc4..5e39c5ca5a 100644
--- a/dotnet/test/E2E/RpcServerE2ETests.cs
+++ b/dotnet/test/E2E/RpcServerE2ETests.cs
@@ -171,7 +171,7 @@ public async Task Should_Call_Rpc_Models_List_With_Typed_Result()
var result = await client.Rpc.Models.ListAsync();
Assert.NotNull(result.Models);
- Assert.Contains(result.Models, model => model.Id == "claude-sonnet-4.5");
+ Assert.Contains(result.Models, model => model.Id == "claude-sonnet-5");
Assert.All(result.Models, model => Assert.False(string.IsNullOrWhiteSpace(model.Name)));
}
diff --git a/dotnet/test/E2E/RpcSessionStateE2ETests.cs b/dotnet/test/E2E/RpcSessionStateE2ETests.cs
index 803c1c602b..196861cd9b 100644
--- a/dotnet/test/E2E/RpcSessionStateE2ETests.cs
+++ b/dotnet/test/E2E/RpcSessionStateE2ETests.cs
@@ -22,14 +22,14 @@ private static async Task AssertImplementedFailureAsync(Func ac
[Fact]
public async Task Should_Call_Session_Rpc_Model_GetCurrent()
{
- await using var session = await CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" });
+ await using var session = await CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-5" });
var result = await session.Rpc.Model.GetCurrentAsync();
Assert.NotNull(result.ModelId);
Assert.NotEmpty(result.ModelId);
// Strengthen: verify the configured model is actually in effect, not just any model
- Assert.Equal("claude-sonnet-4.5", result.ModelId);
+ Assert.Equal("claude-sonnet-5", result.ModelId);
}
[Fact]
@@ -48,12 +48,12 @@ public async Task Should_Call_Session_Rpc_Model_SwitchTo()
await using var session = await isolatedCtx.CreateSessionAsync(isolatedClient, new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
OnPermissionRequest = PermissionHandler.ApproveAll,
});
var before = await session.Rpc.Model.GetCurrentAsync();
- Assert.Equal("claude-sonnet-4.5", before.ModelId);
+ Assert.Equal("claude-sonnet-5", before.ModelId);
var result = await session.Rpc.Model.SwitchToAsync(modelId: "gpt-5.4", reasoningEffort: "high");
Assert.Equal("gpt-5.4", result.ModelId);
@@ -281,14 +281,14 @@ public async Task Should_Call_Metadata_Snapshot_SetWorkingDirectory_And_RecordCo
var branch = $"rpc-context-{Guid.NewGuid():N}";
await using var session = await CreateSessionAsync(new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
WorkingDirectory = firstDirectory,
});
var initialSnapshot = await session.Rpc.Metadata.SnapshotAsync();
Assert.Equal(session.SessionId, initialSnapshot.SessionId);
Assert.Equal(MetadataSnapshotCurrentMode.Interactive, initialSnapshot.CurrentMode);
- Assert.Equal("claude-sonnet-4.5", initialSnapshot.SelectedModel);
+ Assert.Equal("claude-sonnet-5", initialSnapshot.SelectedModel);
Assert.False(initialSnapshot.IsRemote);
Assert.False(initialSnapshot.AlreadyInUse);
Assert.NotEqual(default, initialSnapshot.StartTime);
@@ -405,14 +405,14 @@ public async Task Should_Set_ReasoningEffort_And_Auto_Name()
{
await using var session = await CreateSessionAsync(new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
});
var reasoning = await session.Rpc.Model.SetReasoningEffortAsync("high");
Assert.Equal("high", reasoning.ReasoningEffort);
var currentModel = await session.Rpc.Model.GetCurrentAsync();
- Assert.Equal("claude-sonnet-4.5", currentModel.ModelId);
+ Assert.Equal("claude-sonnet-5", currentModel.ModelId);
Assert.Equal("high", currentModel.ReasoningEffort);
var autoName = $"Auto Session {Guid.NewGuid():N}";
@@ -653,9 +653,9 @@ public async Task Should_Compact_Session_History_After_Messages()
var contextInfo = await session.Rpc.Metadata.ContextInfoAsync(
promptTokenLimit: 128_000,
outputTokenLimit: 4_096,
- selectedModel: "claude-sonnet-4.5");
+ selectedModel: "claude-sonnet-5");
var context = Assert.IsType(contextInfo.ContextInfo);
- Assert.Equal("claude-sonnet-4.5", context.ModelName);
+ Assert.Equal("claude-sonnet-5", context.ModelName);
Assert.Equal(128_000, context.PromptTokenLimit);
Assert.True(context.Limit >= context.PromptTokenLimit);
Assert.True(context.TotalTokens > 0);
@@ -666,7 +666,7 @@ public async Task Should_Compact_Session_History_After_Messages()
context.SystemTokens + context.ConversationTokens + context.ToolDefinitionsTokens,
context.TotalTokens);
- var recomputed = await session.Rpc.Metadata.RecomputeContextTokensAsync("claude-sonnet-4.5");
+ var recomputed = await session.Rpc.Metadata.RecomputeContextTokensAsync("claude-sonnet-5");
Assert.True(recomputed.SystemTokenCount > 0);
Assert.True(recomputed.MessagesTokenCount > 0);
Assert.Equal(recomputed.SystemTokenCount + recomputed.MessagesTokenCount, recomputed.TotalTokens);
diff --git a/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs b/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs
index 72663c35a4..234a5d8694 100644
--- a/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs
+++ b/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs
@@ -32,7 +32,7 @@ public async Task Should_List_Models_For_Session()
await using var client = CreateAuthenticatedClient(token);
await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
OnPermissionRequest = PermissionHandler.ApproveAll,
});
@@ -41,7 +41,7 @@ public async Task Should_List_Models_For_Session()
Assert.NotNull(result.List);
Assert.NotEmpty(result.List);
// The configured model must be present in the returned catalog.
- Assert.Contains(result.List, model => model.GetRawText().Contains("claude-sonnet-4.5", StringComparison.Ordinal));
+ Assert.Contains(result.List, model => model.GetRawText().Contains("claude-sonnet-5", StringComparison.Ordinal));
}
[Fact]
@@ -73,7 +73,7 @@ public async Task Should_Add_Byok_Provider_And_Model_At_Runtime()
Provider = providerName,
Id = modelId,
Name = "SDK Runtime Model",
- ModelId = "claude-sonnet-4.5",
+ ModelId = "claude-sonnet-5",
WireModel = "wire-sdk-runtime-model",
MaxContextWindowTokens = 4_096,
MaxPromptTokens = 3_072,
@@ -277,7 +277,7 @@ public async Task Should_Update_And_Clear_Live_Subagent_Settings()
{
["general-purpose"] = new()
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
EffortLevel = "high",
ContextTier = SubagentSettingsEntryContextTier.Default,
},
diff --git a/dotnet/test/E2E/RpcShellEdgeCaseE2ETests.cs b/dotnet/test/E2E/RpcShellEdgeCaseE2ETests.cs
index b6036b7b8d..25055a671a 100644
--- a/dotnet/test/E2E/RpcShellEdgeCaseE2ETests.cs
+++ b/dotnet/test/E2E/RpcShellEdgeCaseE2ETests.cs
@@ -28,16 +28,21 @@ public async Task Shell_Exec_With_Timeout_Kills_Long_Running_Command()
var markerPath = Path.Join(Ctx.WorkDir, $"shell-timeout-{Guid.NewGuid():N}.txt");
var startedPath = Path.Join(Ctx.WorkDir, $"shell-timeout-started-{Guid.NewGuid():N}.txt");
- // Sleep 30s but timeout at 200ms — runtime should SIGTERM the child before the
+ // Sleep 30s but use a much shorter timeout — runtime should SIGTERM the child before the
// sleep completes, which means the marker file must NEVER appear within a wait
// window comfortably greater than the timeout but well under the sleep duration.
+ // Process startup on Windows can exceed 200ms on loaded runners, so match the
+ // platform-specific allowance used by the Rust coverage for this RPC.
+ var timeout = OperatingSystem.IsWindows()
+ ? TimeSpan.FromSeconds(2)
+ : TimeSpan.FromMilliseconds(200);
var command = OperatingSystem.IsWindows()
- ? $"echo started>\"{startedPath}\" & for /L %i in (1,1,2147483647) do @rem & echo should-not-exist>\"{markerPath}\""
+ ? $"powershell -NoLogo -NoProfile -Command \"Set-Content -LiteralPath '{startedPath}' -Value started; Start-Sleep -Seconds 30; Set-Content -LiteralPath '{markerPath}' -Value should-not-exist\""
: $"printf 'started' > '{startedPath}'; sleep 30; printf 'should-not-exist' > '{markerPath}'";
// On Windows, terminating the shell wrapper can briefly leave children alive.
// Keep this long-running command outside the fixture workspace so cleanup is not blocked by cwd handles.
- var result = await session.Rpc.Shell.ExecAsync(command, cwd: Path.GetTempPath(), timeout: TimeSpan.FromMilliseconds(200));
+ var result = await session.Rpc.Shell.ExecAsync(command, cwd: Path.GetTempPath(), timeout: timeout);
Assert.False(string.IsNullOrWhiteSpace(result.ProcessId));
await TestHelper.WaitForConditionAsync(
@@ -164,12 +169,10 @@ public async Task Shell_Exec_With_Large_Stdout_Cleans_Up()
var session = await CreateSessionAsync();
var markerPath = Path.Join(Ctx.WorkDir, $"shell-stdout-{Guid.NewGuid():N}.txt");
- // Print a payload large enough to exceed the runtime's 64KB chunk threshold so the
- // chunked-output path is executed. We use a single 200KB write so the runtime has to
- // emit at least 3 chunks (200KB / 64KB ≈ 4).
+ // Exceed the runtime's 64KB chunk threshold without flooding slower CI runners.
var command = OperatingSystem.IsWindows()
- ? $"powershell -NoLogo -NoProfile -Command \"Write-Host ('x' * 204800); Set-Content -LiteralPath '{markerPath}' -Value 'done'\""
- : $"printf '%0.s=' $(seq 1 204800); printf 'done' > '{markerPath}'";
+ ? $"powershell -NoLogo -NoProfile -Command \"Write-Host ('x' * 71680); Set-Content -LiteralPath '{markerPath}' -Value 'done'\""
+ : $"printf '%0.s=' $(seq 1 71680); printf 'done' > '{markerPath}'";
var result = await session.Rpc.Shell.ExecAsync(command);
Assert.False(string.IsNullOrWhiteSpace(result.ProcessId));
@@ -185,10 +188,9 @@ await TestHelper.WaitForConditionAsync(
private static async Task AssertProcessMapCleanedUpAsync(CopilotSession session, string processId, string scenario)
{
// The shell RPC surface exposes kill but not a non-mutating status API.
- // Give the runtime's close/exit handler a bounded grace period, then
- // probe exactly once; if this returns true, the assertion fails instead
- // of letting a polling kill make the test pass by cleaning up itself.
- await Task.Delay(TimeSpan.FromSeconds(1));
+ // Give slower CI runners enough time to flush output and process the exit
+ // before the single mutating cleanup probe.
+ await Task.Delay(TimeSpan.FromSeconds(5));
var killResult = await session.Rpc.Shell.KillAsync(processId);
Assert.False(killResult.Killed, $"{scenario} should have already exited and been removed from the runtime's process map.");
}
diff --git a/dotnet/test/E2E/SessionConfigE2ETests.cs b/dotnet/test/E2E/SessionConfigE2ETests.cs
index 1bc4c52eb9..314ba2f0cf 100644
--- a/dotnet/test/E2E/SessionConfigE2ETests.cs
+++ b/dotnet/test/E2E/SessionConfigE2ETests.cs
@@ -31,7 +31,7 @@ public async Task Vision_Disabled_Then_Enabled_Via_SetModel()
var session = await CreateSessionAsync(new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
ModelCapabilities = new ModelCapabilitiesOverride
{
Supports = new ModelCapabilitiesOverrideSupports { Vision = false },
@@ -46,7 +46,7 @@ public async Task Vision_Disabled_Then_Enabled_Via_SetModel()
// Switch vision on
await session.SetModelAsync(
- "claude-sonnet-4.5",
+ "claude-sonnet-5",
reasoningEffort: null,
modelCapabilities: new ModelCapabilitiesOverride
{
@@ -74,7 +74,7 @@ public async Task Vision_Enabled_Then_Disabled_Via_SetModel()
var session = await CreateSessionAsync(new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
ModelCapabilities = new ModelCapabilitiesOverride
{
Supports = new ModelCapabilitiesOverrideSupports { Vision = true },
@@ -89,7 +89,7 @@ public async Task Vision_Enabled_Then_Disabled_Via_SetModel()
// Switch vision off
await session.SetModelAsync(
- "claude-sonnet-4.5",
+ "claude-sonnet-5",
reasoningEffort: null,
modelCapabilities: new ModelCapabilitiesOverride
{
@@ -216,7 +216,7 @@ public async Task Should_Forward_Custom_Provider_Headers_On_Create()
{
var session = await CreateSessionAsync(new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
Provider = CreateProxyProvider("create-provider-header"),
});
@@ -240,7 +240,7 @@ public async Task Should_Forward_Custom_Provider_Headers_On_Resume()
var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
Provider = CreateProxyProvider("resume-provider-header"),
});
@@ -267,7 +267,7 @@ public async Task Should_Forward_Provider_Wire_Model()
// tests for serialization coverage).
var session = await CreateSessionAsync(new SessionConfig
{
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
Provider = new ProviderConfig
{
Type = "openai",
@@ -300,14 +300,14 @@ public async Task Should_Use_Provider_Model_Id_As_Wire_Model()
Type = "openai",
BaseUrl = Ctx.ProxyUrl,
ApiKey = "test-provider-key",
- ModelId = "claude-sonnet-4.5",
+ ModelId = "claude-sonnet-5",
},
});
await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 1+1?" });
var exchange = Assert.Single(await Ctx.GetExchangesAsync());
- Assert.Equal("claude-sonnet-4.5", exchange.Request.Model);
+ Assert.Equal("claude-sonnet-5", exchange.Request.Model);
await session.DisposeAsync();
}
@@ -598,7 +598,7 @@ public async Task Should_Enable_Citations_For_Anthropic_File_Attachments_On_Crea
var session = await Ctx.CreateSessionAsync(client, new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
EnableCitations = true,
Provider = CreateAnthropicProvider(),
});
@@ -645,7 +645,7 @@ public async Task Should_Enable_Citations_For_Anthropic_File_Attachments_On_Resu
var session2 = await Ctx.ResumeSessionAsync(resumeClient, sessionId, new ResumeSessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
- Model = "claude-sonnet-4.5",
+ Model = "claude-sonnet-5",
EnableCitations = true,
Provider = CreateAnthropicProvider(),
});
@@ -834,8 +834,8 @@ private static ProviderConfig CreateAnthropicProvider()
Type = "anthropic",
BaseUrl = "https://anthropic-citations.invalid/v1",
ApiKey = "test-provider-key",
- ModelId = "claude-sonnet-4.5",
- WireModel = "claude-sonnet-4.5",
+ ModelId = "claude-sonnet-5",
+ WireModel = "claude-sonnet-5",
};
}
diff --git a/dotnet/test/E2E/SessionE2ETests.cs b/dotnet/test/E2E/SessionE2ETests.cs
index 27ef7437f7..fab84bc439 100644
--- a/dotnet/test/E2E/SessionE2ETests.cs
+++ b/dotnet/test/E2E/SessionE2ETests.cs
@@ -17,7 +17,7 @@ public class SessionE2ETests(E2ETestFixture fixture, ITestOutputHelper output) :
[Fact]
public async Task ShouldCreateAndDisconnectSessions()
{
- var session = await CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" });
+ var session = await CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-5" });
Assert.Matches(@"^[a-f0-9-]+$", session.SessionId);
@@ -295,6 +295,50 @@ public async Task Resumes_A_Persisted_Session_From_A_New_Client_When_An_Mcp_OAut
Assert.Equal(sessionId, session2.SessionId);
}
+ [Fact]
+ public async Task Should_Recover_Marker_After_Cold_Resume_With_Explicit_Session_Id()
+ {
+ await using var isolatedCtx = await E2ETestContext.CreateAsync();
+ await isolatedCtx.ConfigureForTestAsync("session", nameof(Should_Recover_Marker_After_Cold_Resume_With_Explicit_Session_Id));
+
+ var sessionId = $"e2e-cold-resume-{Guid.NewGuid()}";
+
+ var client1 = isolatedCtx.CreateClient();
+ var session1 = await isolatedCtx.CreateSessionAsync(client1, new SessionConfig
+ {
+ SessionId = sessionId,
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ });
+ Assert.Equal(sessionId, session1.SessionId);
+
+ var answer = await session1.SendAndWaitAsync(new MessageOptions
+ {
+ Prompt = "Please remember this exact secret marker for later - MARKER-7f3ac21e. Reply with only the single word \"Acknowledged\".",
+ });
+ Assert.NotNull(answer);
+ Assert.Contains("Acknowledged", answer!.Data.Content ?? string.Empty);
+
+ await session1.DisposeAsync();
+ await client1.ForceStopAsync();
+
+ var client2 = isolatedCtx.CreateClient();
+ var session2 = await isolatedCtx.ResumeSessionAsync(client2, sessionId, new ResumeSessionConfig
+ {
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ });
+ Assert.Equal(sessionId, session2.SessionId);
+
+ var answer2 = await session2.SendAndWaitAsync(new MessageOptions
+ {
+ Prompt = "What was the exact secret marker I asked you to remember earlier? Reply with only that marker value and nothing else.",
+ });
+ Assert.NotNull(answer2);
+ Assert.Contains("MARKER-7f3ac21e", answer2!.Data.Content ?? string.Empty);
+
+ await session2.DisposeAsync();
+ await client2.ForceStopAsync();
+ }
+
[Fact]
public async Task Should_Throw_Error_When_Resuming_Non_Existent_Session()
{
diff --git a/dotnet/test/E2E/SessionEventLoopLeakE2ETests.cs b/dotnet/test/E2E/SessionEventLoopLeakE2ETests.cs
new file mode 100644
index 0000000000..40470109c5
--- /dev/null
+++ b/dotnet/test/E2E/SessionEventLoopLeakE2ETests.cs
@@ -0,0 +1,198 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+using GitHub.Copilot.Test.Harness;
+using System.Collections;
+using System.Reflection;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace GitHub.Copilot.Test.E2E;
+
+///
+/// Regression coverage for the goroutine/task leak fixed alongside
+/// github/copilot-sdk#2360: and
+/// construct a
+/// and start its event-dispatch consumer (ProcessEventsAsync) eagerly, before the
+/// CLI confirms the session, so the CLI can route session-scoped requests to it while
+/// session.create (or session.resume) is still being processed. Every failure path must
+/// stop that consumer — otherwise each failed call leaks a background task forever, since
+/// no caller ever receives the failed session to dispose it. Mirrors
+/// go/internal/e2e/session_event_loop_leak_e2e_test.go.
+///
+[Trait(E2ETestTraits.Backend, E2ETestTraits.CapiOnly)]
+public class SessionEventLoopLeakE2ETests(E2ETestFixture fixture, ITestOutputHelper output)
+ : E2ETestBase(fixture, "session-event-loop-leak", output)
+{
+ private static readonly FieldInfo SessionsField =
+ typeof(CopilotClient).GetField("_sessions", BindingFlags.Instance | BindingFlags.NonPublic)
+ ?? throw new InvalidOperationException("CopilotClient._sessions was not found.");
+
+ private static readonly FieldInfo EventChannelField =
+ typeof(CopilotSession).GetField("_eventChannel", BindingFlags.Instance | BindingFlags.NonPublic)
+ ?? throw new InvalidOperationException("CopilotSession._eventChannel was not found.");
+
+ private static IDictionary GetSessionsMap(CopilotClient client) =>
+ (IDictionary)SessionsField.GetValue(client)!;
+
+ private static bool IsEventChannelClosed(CopilotSession session)
+ {
+ var channel = EventChannelField.GetValue(session)!;
+ var readerProperty = channel.GetType().GetProperty("Reader")
+ ?? throw new InvalidOperationException("Channel.Reader was not found.");
+ var reader = readerProperty.GetValue(channel)!;
+ var completionProperty = reader.GetType().GetProperty("Completion")
+ ?? throw new InvalidOperationException("ChannelReader.Completion was not found.");
+ var completion = (Task)completionProperty.GetValue(reader)!;
+ return completion.IsCompleted;
+ }
+
+ ///
+ /// Continuously scans 's session dictionary on a dedicated,
+ /// tightly-spinning thread (not the thread pool, and no await-based yielding) so
+ /// that even a sub-millisecond in-flight registration window — as seen with a fast local
+ /// RPC failure like a nonexistent-session resume — is reliably observed.
+ ///
+ private sealed class SessionSniffer : IDisposable
+ {
+ private readonly List _seen = [];
+ private readonly Thread _thread;
+ private volatile bool _stop;
+
+ public SessionSniffer(IDictionary sessions)
+ {
+ _thread = new Thread(() =>
+ {
+ var iterations = 0L;
+ while (!_stop)
+ {
+ iterations++;
+ foreach (CopilotSession s in sessions.Values)
+ {
+ lock (_seen)
+ {
+ if (!_seen.Contains(s)) _seen.Add(s);
+ }
+ }
+ }
+ Iterations = iterations;
+ })
+ { IsBackground = true };
+ _thread.Start();
+ }
+
+ public long Iterations { get; private set; }
+
+ public IReadOnlyList Stop()
+ {
+ _stop = true;
+ _thread.Join();
+ lock (_seen) return [.. _seen];
+ }
+
+ public void Dispose() => _stop = true;
+ }
+
+ [Fact]
+ public async Task CreateSessionAsync_Failure_Does_Not_Leak_The_Session_Or_Its_Event_Loop()
+ {
+ // An invalid per-session GitHub token, redirected at the replaying proxy, makes
+ // the real CLI reject session.create with a genuine RPC error (401 Unauthorized)
+ // — the same failure path a real user would hit, not a mocked transport.
+ var env = new Dictionary(Ctx.GetEnvironment())
+ {
+ ["COPILOT_DEBUG_GITHUB_API_URL"] = Ctx.ProxyUrl,
+ };
+ var client = Ctx.CreateClient(environment: env, autoInjectGitHubToken: false);
+
+ async Task CreateFailingAsync()
+ {
+ var ex = await Assert.ThrowsAnyAsync(() => Ctx.CreateSessionAsync(client, new SessionConfig
+ {
+ GitHubToken = "invalid-token",
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ }));
+ Assert.Contains("401", ex.ToString(), StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Warm up: the first call establishes the CLI connection.
+ await CreateFailingAsync();
+
+ var sessions = GetSessionsMap(client);
+
+ // The session is registered (and its event-loop consumer started) before the RPC
+ // completes, and is only ever removed inside CreateSessionAsync's own catch block —
+ // by the time a failed call *returns* to us, RemoveFromClient() has already run, so
+ // polling the dictionary after each await observes nothing. We must instead observe
+ // it concurrently, while each call is still in flight, to capture the real session
+ // object and verify its event channel actually got closed.
+ using var sniffer = new SessionSniffer(sessions);
+
+ for (var i = 0; i < 20; i++)
+ {
+ await CreateFailingAsync();
+ }
+
+ var seen = sniffer.Stop();
+
+ Assert.True(seen.Count > 0, "Test did not observe any in-flight session registrations; cannot validate the fix.");
+ Assert.Empty(sessions);
+
+ foreach (var s in seen)
+ {
+ Assert.True(IsEventChannelClosed(s), "A failed CreateSessionAsync's session had its event channel left open, leaking its background event-processing task.");
+ }
+ }
+
+ [Fact]
+ public async Task ResumeSessionAsync_Failure_Does_Not_Leak_The_Session_Or_Its_Event_Loop()
+ {
+ // Use our own dedicated client rather than the shared fixture's ResumeSessionAsync
+ // helper: that helper spins up a brand-new CopilotClient for every call it makes
+ // (to support the multi-client resume scenarios it's designed for), so each call's
+ // pre-registered session would land in a different, short-lived client's dictionary
+ // that we'd never get to observe. A single, reused client lets us watch one
+ // dictionary across all 20 failed calls.
+ var client = Ctx.CreateClient();
+ var sessions = GetSessionsMap(client);
+
+ async Task ResumeNonExistentAsync()
+ {
+ await Assert.ThrowsAnyAsync(() =>
+ Ctx.ResumeSessionAsync(client, "non-existent-leak-check-session", new ResumeSessionConfig
+ {
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ }));
+ }
+
+ // Warm up: the first call establishes the CLI connection.
+ await ResumeNonExistentAsync();
+
+ // Same rationale as the CreateSessionAsync test above: the pre-registered session is
+ // already removed from the dictionary by the time a failed call returns, so we must
+ // observe it concurrently, while the call is still in flight, to actually validate
+ // that its event channel got closed rather than just that it got unregistered.
+ using var sniffer = new SessionSniffer(sessions);
+
+ var baseline = sessions.Count;
+ for (var i = 0; i < 20; i++)
+ {
+ await ResumeNonExistentAsync();
+ }
+
+ var seen = sniffer.Stop();
+
+ Assert.True(seen.Count > 0, $"Test did not observe any in-flight session registrations (sniffer ran {sniffer.Iterations} iterations); cannot validate the fix.");
+
+ Assert.True(
+ sessions.Count == baseline,
+ $"Expected no sessions left registered after 20 failed ResumeSessionAsync calls (baseline={baseline}), " +
+ $"but found {sessions.Count}. Failed ResumeSessionAsync calls must not leak the local session registration.");
+
+ foreach (var s in seen)
+ {
+ Assert.True(IsEventChannelClosed(s), "A failed ResumeSessionAsync's session had its event channel left open, leaking its background event-processing task.");
+ }
+ }
+}
diff --git a/dotnet/test/E2E/ToolsE2ETests.cs b/dotnet/test/E2E/ToolsE2ETests.cs
index ea615fbc4a..8a786f3927 100644
--- a/dotnet/test/E2E/ToolsE2ETests.cs
+++ b/dotnet/test/E2E/ToolsE2ETests.cs
@@ -7,7 +7,11 @@
using Microsoft.Extensions.AI;
using System.Collections.ObjectModel;
using System.ComponentModel;
+using System.Net;
+using System.Net.Http;
+using System.Text;
using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Xunit;
using Xunit.Abstractions;
@@ -16,6 +20,9 @@ namespace GitHub.Copilot.Test.E2E;
public partial class ToolsE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "tools", output)
{
+ private const string ApplyPatchInput = "*** Begin Patch\n*** End Patch";
+ private const string ApplyPatchResult = "patched by the host";
+
[Fact]
public async Task Invokes_Built_In_Tools()
{
@@ -234,6 +241,91 @@ static string CustomGrep([Description("Search query")] string query)
=> $"CUSTOM_GREP_RESULT: {query}";
}
+ [Theory]
+ [InlineData("string")]
+ [InlineData("object")]
+ [InlineData("JsonElement")]
+ [InlineData("JsonNode")]
+ [Trait(E2ETestTraits.Backend, E2ETestTraits.SelfConfiguredBackend)]
+ public async Task ApplyPatch_Override_Receives_Freeform_Input_Shapes(string parameterType)
+ {
+ foreach (var useCustomToolCall in new[] { true, false })
+ {
+ object? receivedInput = null;
+ var invocationCount = 0;
+ var handler = new ApplyPatchOverrideRequestHandler(useCustomToolCall);
+ await using var client = Ctx.CreateClient(options: new CopilotClientOptions
+ {
+ Connection = RuntimeConnection.ForStdio(),
+ RequestHandler = handler,
+ });
+ await client.StartAsync();
+
+ string CaptureInput(object input)
+ {
+ receivedInput = input;
+ invocationCount++;
+ return ApplyPatchResult;
+ }
+
+ Delegate applyPatch = parameterType switch
+ {
+ "string" => (string input) => CaptureInput(input),
+ "object" => (object input) => CaptureInput(input),
+ "JsonElement" => (JsonElement input) => CaptureInput(input),
+ "JsonNode" => (JsonNode input) => CaptureInput(input),
+ _ => throw new ArgumentOutOfRangeException(nameof(parameterType)),
+ };
+ var tool = CopilotTool.DefineTool(
+ applyPatch,
+ new CopilotToolOptions
+ {
+ OverridesBuiltInTool = true,
+ SkipPermission = true,
+ },
+ new AIFunctionFactoryOptions
+ {
+ Name = "apply_patch",
+ Description = "Host-implemented apply_patch",
+ });
+
+ await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig
+ {
+ Model = "gpt-4o-mini",
+ Provider = new ProviderConfig
+ {
+ Type = "openai",
+ WireApi = "completions",
+ BaseUrl = "https://apply-patch.invalid/v1",
+ ApiKey = "test-key",
+ ModelId = "gpt-4o-mini",
+ WireModel = "gpt-4o-mini",
+ },
+ Streaming = true,
+ Tools = [tool],
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ });
+
+ var message = await session.SendAndWaitAsync(new MessageOptions { Prompt = "Use apply_patch" });
+
+ var input = receivedInput switch
+ {
+ string value => value,
+ JsonElement value => value.GetString(),
+ JsonNode value => value.GetValue(),
+ _ => null,
+ };
+ Assert.Equal(ApplyPatchInput, input);
+ Assert.Equal(1, invocationCount);
+ Assert.Equal("override complete", message?.Data.Content);
+
+ var requests = handler.InferenceRequests;
+ Assert.Equal(2, requests.Count);
+ AssertApplyPatchOverrideAdvertised(requests[0], parameterType);
+ AssertApplyPatchResultReachedModel(requests[1]);
+ }
+ }
+
[Fact]
public async Task SkipPermission_Sent_In_Tool_Definition()
{
@@ -448,4 +540,114 @@ string ExcludedTool([Description("Input value")] string input)
return $"EXCLUDED_{input.ToUpperInvariant()}";
}
}
+
+ private static void AssertApplyPatchOverrideAdvertised(string requestBody, string parameterType)
+ {
+ using var request = JsonDocument.Parse(requestBody);
+ var applyPatchTools = request.RootElement.GetProperty("tools")
+ .EnumerateArray()
+ .Where(tool =>
+ tool.TryGetProperty("function", out var function)
+ && function.TryGetProperty("name", out var functionName)
+ && functionName.GetString() == "apply_patch"
+ || tool.TryGetProperty("custom", out var custom)
+ && custom.TryGetProperty("name", out var customName)
+ && customName.GetString() == "apply_patch")
+ .ToArray();
+
+ var applyPatch = Assert.Single(applyPatchTools);
+ Assert.Equal("function", applyPatch.GetProperty("type").GetString());
+ var definition = applyPatch.GetProperty("function");
+ Assert.Equal("apply_patch", definition.GetProperty("name").GetString());
+
+ var parameters = definition.GetProperty("parameters");
+ Assert.Equal("object", parameters.GetProperty("type").GetString());
+ var inputSchema = parameters.GetProperty("properties").GetProperty("input");
+ if (parameterType == "string")
+ {
+ Assert.Equal("string", inputSchema.GetProperty("type").GetString());
+ }
+ else
+ {
+ Assert.Equal(JsonValueKind.True, inputSchema.ValueKind);
+ }
+ Assert.Contains(parameters.GetProperty("required").EnumerateArray(), item => item.GetString() == "input");
+ }
+
+ private static void AssertApplyPatchResultReachedModel(string requestBody)
+ {
+ using var request = JsonDocument.Parse(requestBody);
+ var toolResult = Assert.Single(
+ request.RootElement.GetProperty("messages").EnumerateArray(),
+ message =>
+ message.GetProperty("role").GetString() == "tool"
+ && message.GetProperty("tool_call_id").GetString() == "call-1");
+ Assert.Equal(ApplyPatchResult, toolResult.GetProperty("content").GetString());
+ }
+
+ private sealed class ApplyPatchOverrideRequestHandler(bool useCustomToolCall) : CopilotRequestHandler
+ {
+ private const string CustomToolCallResponse =
+ "data: {\"id\":\"chatcmpl-tool\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gpt-4o-mini\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"id\":\"call-1\",\"type\":\"custom\",\"custom\":{\"name\":\"apply_patch\",\"input\":\"*** Begin Patch\\n*** End Patch\"}}]},\"finish_reason\":\"tool_calls\"}]}\n\n" +
+ "data: [DONE]\n\n";
+
+ private const string FunctionToolCallResponse =
+ "data: {\"id\":\"chatcmpl-tool\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gpt-4o-mini\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"id\":\"call-1\",\"type\":\"function\",\"function\":{\"name\":\"apply_patch\",\"arguments\":\"{\\\"input\\\":\\\"*** Begin Patch\\\\n*** End Patch\\\"}\"}}]},\"finish_reason\":\"tool_calls\"}]}\n\n" +
+ "data: [DONE]\n\n";
+
+ private const string FinalResponse =
+ "data: {\"id\":\"chatcmpl-final\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gpt-4o-mini\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"override complete\"},\"finish_reason\":null}]}\n\n" +
+ "data: {\"id\":\"chatcmpl-final\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"gpt-4o-mini\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\n" +
+ "data: [DONE]\n\n";
+
+ private readonly object _lock = new();
+ private readonly List _inferenceRequests = [];
+
+ internal IReadOnlyList InferenceRequests
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return [.. _inferenceRequests];
+ }
+ }
+ }
+
+ protected override async Task SendRequestAsync(HttpRequestMessage request, CopilotRequestContext ctx)
+ {
+ var url = request.RequestUri!.ToString();
+ if (!RecordingRequestHandler.IsInferenceUrl(url))
+ {
+ return RecordingRequestHandler.BuildNonInferenceResponse(url);
+ }
+
+ var requestBody = request.Content is null
+ ? string.Empty
+#if NET8_0_OR_GREATER
+ : await request.Content.ReadAsStringAsync(ctx.CancellationToken).ConfigureAwait(false);
+#else
+ : await request.Content.ReadAsStringAsync().ConfigureAwait(false);
+#endif
+
+ int requestNumber;
+ lock (_lock)
+ {
+ _inferenceRequests.Add(requestBody);
+ requestNumber = _inferenceRequests.Count;
+ }
+
+ return requestNumber switch
+ {
+ 1 => Sse(useCustomToolCall ? CustomToolCallResponse : FunctionToolCallResponse),
+ 2 => Sse(FinalResponse),
+ _ => throw new InvalidOperationException($"Unexpected inference request #{requestNumber}."),
+ };
+ }
+
+ private static HttpResponseMessage Sse(string body) => new(HttpStatusCode.OK)
+ {
+ Content = new StringContent(body, Encoding.UTF8, "text/event-stream"),
+ };
+ }
}
diff --git a/dotnet/test/Harness/E2ETestBackend.cs b/dotnet/test/Harness/E2ETestBackend.cs
index 04808ed7a9..71c8f85761 100644
--- a/dotnet/test/Harness/E2ETestBackend.cs
+++ b/dotnet/test/Harness/E2ETestBackend.cs
@@ -15,7 +15,7 @@ internal enum E2ETestBackend
internal static class E2ETestBackendConfiguration
{
internal const string EnvironmentVariable = "COPILOT_SDK_E2E_BACKEND";
- private const string AnthropicDefaultModel = "claude-sonnet-4.5";
+ private const string AnthropicDefaultModel = "claude-sonnet-5";
private const string OpenAIDefaultModel = "gpt-4.1";
private const string FakeCredential = "fake-byok-credential-for-e2e-tests";
diff --git a/dotnet/test/Harness/E2ETestBase.cs b/dotnet/test/Harness/E2ETestBase.cs
index 3eb0f0e97a..f664812d58 100644
--- a/dotnet/test/Harness/E2ETestBase.cs
+++ b/dotnet/test/Harness/E2ETestBase.cs
@@ -113,7 +113,7 @@ protected static async Task SuspendAndUntrackSessionForResumeAsync(CopilotSessio
{
await session.Rpc.SuspendAsync();
- // In-process clients host separate runtimes, while session.destroy removes the
+ // In-process clients host separate runtimes, while session.detach releases the
// session from the current runtime. Untrack locally to exercise resume without
// either replacing an active wrapper or destroying the session first.
var removeFromClient = typeof(CopilotSession).GetMethod(
diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs
index 0080cbc609..8d474bfe7e 100644
--- a/dotnet/test/Harness/E2ETestContext.cs
+++ b/dotnet/test/Harness/E2ETestContext.cs
@@ -3,9 +3,9 @@
*--------------------------------------------------------------------------------------------*/
using Microsoft.Extensions.Logging;
+using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace GitHub.Copilot.Test.Harness;
@@ -14,6 +14,7 @@ public sealed class E2ETestContext : IAsyncDisposable
{
private const string DefaultGitHubToken = "fake-token-for-e2e-tests";
private static readonly TimeSpan s_gracefulClientStopTimeout = TimeSpan.FromSeconds(30);
+ private static readonly ConcurrentDictionary> s_preparedCliPaths = new(StringComparer.Ordinal);
public string HomeDir { get; }
public string WorkDir { get; }
@@ -25,6 +26,8 @@ public sealed class E2ETestContext : IAsyncDisposable
private readonly ReplayProxy _proxy;
private readonly string _repoRoot;
+ private readonly Lazy _cliPath;
+ private readonly Lazy _legacyCliPath;
private readonly object _clientsLock = new();
private readonly List _persistentClients = [];
private readonly List _transientClients = [];
@@ -36,6 +39,8 @@ private E2ETestContext(string homeDir, string workDir, string proxyUrl, ReplayPr
ProxyUrl = proxyUrl;
_proxy = proxy;
_repoRoot = repoRoot;
+ _cliPath = GetCachedCliPath(repoRoot, "--print-path");
+ _legacyCliPath = GetCachedCliPath(repoRoot, "--print-legacy-path");
}
public static async Task CreateAsync()
@@ -144,46 +149,54 @@ private static string FindRepoRoot()
throw new InvalidOperationException("Could not find repository root");
}
- private static string GetCliPath(string repoRoot)
+ private string GetCliPath()
+ => _cliPath.Value;
+
+ public string GetLegacyCliPath()
+ => _legacyCliPath.Value;
+
+ private static Lazy GetCachedCliPath(string repoRoot, string option)
+ {
+ var envPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH");
+ var cacheKey = $"{repoRoot}\0{option}\0{envPath}";
+ return s_preparedCliPaths.GetOrAdd(
+ cacheKey,
+ _ => new Lazy(
+ () => PrepareCliPath(repoRoot, option),
+ LazyThreadSafetyMode.ExecutionAndPublication));
+ }
+
+ private static string PrepareCliPath(string repoRoot, string option)
{
var envPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH");
if (!string.IsNullOrEmpty(envPath)) return envPath;
- // As of CLI 1.0.64-1 the @github/copilot package is a thin loader; the
- // runnable index.js ships in the installed platform package.
- var githubModules = Path.Join(repoRoot, "nodejs", "node_modules", "@github");
- var packagePrefix = GetCliPackagePrefix();
- var candidates = Directory.Exists(githubModules)
- ? Directory.EnumerateDirectories(githubModules, $"{packagePrefix}-*", SearchOption.TopDirectoryOnly)
- .Select(directory => Path.Join(directory, "index.js"))
- .Where(File.Exists)
- .ToArray()
- : [];
-
- return candidates.Length switch
+ var startInfo = new ProcessStartInfo
{
- 1 => candidates[0],
- 0 => throw new InvalidOperationException(
- $"CLI package matching '{packagePrefix}-*' not found under {githubModules}. " +
- "Run 'npm install' in the nodejs directory first."),
- _ => throw new InvalidOperationException(
- $"Multiple CLI packages matching '{packagePrefix}-*' found under {githubModules}: " +
- string.Join(", ", candidates.Select(Path.GetDirectoryName))),
+ FileName = "node",
+ WorkingDirectory = Path.Join(repoRoot, "nodejs"),
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ Arguments = $"node_modules/tsx/dist/cli.mjs scripts/prepare-runtime.ts {option}",
};
- }
- private static string GetCliPackagePrefix()
- {
- var platform = OperatingSystem.IsWindows()
- ? "win32"
- : OperatingSystem.IsMacOS()
- ? "darwin"
- : OperatingSystem.IsLinux()
- ? RuntimeInformation.RuntimeIdentifier.StartsWith("linux-musl-", StringComparison.Ordinal)
- ? "linuxmusl"
- : "linux"
- : throw new PlatformNotSupportedException("Unsupported operating system for Copilot CLI E2E tests.");
- return $"copilot-{platform}";
+ using var process = Process.Start(startInfo)
+ ?? throw new InvalidOperationException("Failed to start Node.js runtime preparation.");
+ var stdout = process.StandardOutput.ReadToEndAsync();
+ var stderr = process.StandardError.ReadToEndAsync();
+ process.WaitForExit();
+ var output = stdout.GetAwaiter().GetResult();
+ var lines = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
+ var cliPath = lines.Length == 0 ? string.Empty : lines[^1].Trim();
+ var error = stderr.GetAwaiter().GetResult().Trim();
+ if (process.ExitCode != 0 || string.IsNullOrEmpty(cliPath))
+ throw new InvalidOperationException(
+ $"Failed to prepare the pinned Copilot CLI: {error}");
+ if (!File.Exists(cliPath))
+ throw new InvalidOperationException(
+ $"Pinned Copilot CLI was not created at {cliPath}.");
+ return cliPath;
}
public async Task ConfigureForTestAsync(string testFile, [CallerMemberName] string? testName = null)
@@ -310,21 +323,20 @@ public CopilotClient CreateClient(
// CopilotClient honors COPILOT_SDK_DEFAULT_CONNECTION (stdio by default,
// or in-process); the CI matrix uses this to run the suite under both.
// Tests that need a specific transport set options.Connection directly.
- var cliPath = GetCliPath(_repoRoot);
switch (options.Connection)
{
case null when !IsInProcess(null):
// No explicit connection and not the in-process default: the
// default resolves to stdio, so materialize it here so the
// environment can be attached to the connection below.
- options.Connection = RuntimeConnection.ForStdio(path: cliPath);
+ options.Connection = RuntimeConnection.ForStdio(path: GetCliPath());
break;
case null:
// In-process default: leave Connection unset so CopilotClient's
// ResolveDefaultConnection honors COPILOT_SDK_DEFAULT_CONNECTION.
break;
case ChildProcessRuntimeConnection child when child.Path is null:
- child.Path = cliPath;
+ child.Path = GetCliPath();
break;
}
@@ -583,7 +595,7 @@ private static async Task StopClientForCleanupAsync(CopilotClient client)
$"Graceful in-process client cleanup exceeded {s_gracefulClientStopTimeout}; forcing shutdown.");
await client.ForceStopAsync();
- // Disposing the connection completes any session.destroy RPC that
+ // Disposing the connection completes any session.detach RPC that
// blocked graceful cleanup. Observe that task before continuing.
await gracefulStop.WaitAsync(s_gracefulClientStopTimeout);
}
diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs
index bb0042efdb..04ef6e6405 100644
--- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs
+++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs
@@ -12,6 +12,7 @@
using System.Text;
using System.Text.Json;
using GitHub.Copilot.Rpc;
+using Microsoft.Extensions.AI;
using Xunit;
namespace GitHub.Copilot.Test.Unit;
@@ -464,6 +465,199 @@ public async Task CreateSessionAsync_Omits_CustomAgent_ReasoningEffort_When_Unse
Assert.False(agent.TryGetProperty("reasoningEffort", out _));
}
+ public static TheoryData CapiAutoTiers => new()
+ {
+ { AutoTier.Efficiency, "efficiency", null },
+ { AutoTier.Balance, "balance", null },
+ { AutoTier.Intelligence, "intelligence", null },
+ { AutoTier.Efficiency, "efficiency", false },
+ { AutoTier.Balance, "balance", false },
+ { AutoTier.Intelligence, "intelligence", false },
+ };
+
+ [Theory]
+ [MemberData(nameof(CapiAutoTiers))]
+ public async Task SessionRequests_Serialize_CapiAutoTier(AutoTier tier, string expectedTier, bool? enableWebSocketResponses)
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var capi = new CapiSessionOptions { AutoTier = tier, EnableWebSocketResponses = enableWebSocketResponses };
+
+ await using var created = await client.CreateSessionAsync(new SessionConfig
+ {
+ Model = "auto",
+ Capi = capi,
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+ await using var resumed = await client.ResumeSessionAsync("resume-with-auto-tier", new ResumeSessionConfig
+ {
+ Model = "auto",
+ Capi = capi,
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ foreach (var method in new[] { "session.create", "session.resume" })
+ {
+ var request = Assert.Single(server.Requests, request => request.Method == method);
+ var serializedCapi = request.Params.GetProperty("capi");
+ Assert.Equal(expectedTier, serializedCapi.GetProperty("autoTier").GetString());
+ if (enableWebSocketResponses.HasValue)
+ {
+ Assert.Equal(enableWebSocketResponses.Value, serializedCapi.GetProperty("enableWebSocketResponses").GetBoolean());
+ }
+ else
+ {
+ Assert.False(serializedCapi.TryGetProperty("enableWebSocketResponses", out _));
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData("efficiency")]
+ [InlineData("balance")]
+ [InlineData("intelligence")]
+ public async Task SetModelAsync_Serializes_AutoTier(string expectedTier)
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ await session.SetModelAsync("auto", new SetModelOptions { AutoTier = new AutoTier(expectedTier) });
+
+ var request = Assert.Single(server.Requests, request => request.Method == "session.model.switchTo");
+ Assert.Equal("auto", request.Params.GetProperty("modelId").GetString());
+ Assert.Equal(expectedTier, request.Params.GetProperty("autoTier").GetString());
+ }
+
+ [Fact]
+ public async Task SetModelAsync_Omits_AutoTier_WhenUnset()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ await session.SetModelAsync("gpt-5.4");
+
+ var request = Assert.Single(server.Requests, request => request.Method == "session.model.switchTo");
+ Assert.False(request.Params.TryGetProperty("autoTier", out _));
+ }
+
+ [Fact]
+ public async Task SetModelAsync_Writes_Null_AutoTier_WhenCleared()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ await session.SetModelAsync("auto", new SetModelOptions { ResetAutoTier = true });
+
+ // An explicit null must survive to the wire. Omitting it would mean "leave the
+ // preference alone" rather than "use provider-default routing".
+ var request = Assert.Single(server.Requests, request => request.Method == "session.model.switchTo");
+ Assert.True(request.Params.TryGetProperty("autoTier", out var autoTier));
+ Assert.Equal(JsonValueKind.Null, autoTier.ValueKind);
+ }
+
+ [Fact]
+ public async Task SetModelAsync_Rejects_Conflicting_AutoTier_Options()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ await Assert.ThrowsAsync(() => session.SetModelAsync(
+ "auto",
+ new SetModelOptions { AutoTier = AutoTier.Balance, ResetAutoTier = true }));
+ }
+
+ [Fact]
+ public async Task SetAutoTierAsync_Serializes_Tier_And_Returns_Snapshot()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ var result = await session.SetAutoTierAsync(AutoTier.Intelligence);
+
+ var request = Assert.Single(server.Requests, request => request.Method == "session.model.switchAutoTier");
+ Assert.Equal("intelligence", request.Params.GetProperty("autoTier").GetString());
+ Assert.Equal(ModelSwitchAutoTierStatus.Pending, result.Status);
+ Assert.Equal(AutoTier.Balance, result.EffectiveAutoTier);
+ }
+
+ [Fact]
+ public async Task SetAutoTierAsync_Writes_Null_Tier_ForDefaultRouting()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ await session.SetAutoTierAsync(null);
+
+ var request = Assert.Single(server.Requests, request => request.Method == "session.model.switchAutoTier");
+ Assert.True(request.Params.TryGetProperty("autoTier", out var autoTier));
+ Assert.Equal(JsonValueKind.Null, autoTier.ValueKind);
+ }
+
+ [Theory]
+ [InlineData(false, null)]
+ [InlineData(true, null)]
+ [InlineData(true, false)]
+ public async Task SessionRequests_Omit_CapiAutoTier_WhenUnset(bool includeCapi, bool? enableWebSocketResponses)
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var capi = includeCapi ? new CapiSessionOptions { EnableWebSocketResponses = enableWebSocketResponses } : null;
+
+ await using var created = await client.CreateSessionAsync(new SessionConfig
+ {
+ Model = "auto",
+ Capi = capi,
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+ await using var resumed = await client.ResumeSessionAsync("resume-without-auto-tier", new ResumeSessionConfig
+ {
+ Capi = capi,
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ foreach (var method in new[] { "session.create", "session.resume" })
+ {
+ var request = Assert.Single(server.Requests, request => request.Method == method);
+ Assert.Equal(includeCapi, request.Params.TryGetProperty("capi", out var serializedCapi));
+ if (includeCapi)
+ {
+ Assert.False(serializedCapi.TryGetProperty("autoTier", out _));
+ if (enableWebSocketResponses.HasValue)
+ {
+ Assert.Equal(enableWebSocketResponses.Value, serializedCapi.GetProperty("enableWebSocketResponses").GetBoolean());
+ }
+ else
+ {
+ Assert.Empty(serializedCapi.EnumerateObject());
+ }
+ }
+ }
+ }
+
[Fact]
public async Task CreateSessionAsync_Forwards_AskUserVariant()
{
@@ -577,6 +771,166 @@ public async Task SessionRequests_Serialize_Terminal_Tools()
Assert.True(resumeRequest.Params.GetProperty("tools")[0].GetProperty("isTerminal").GetBoolean());
}
+ [Fact]
+ public async Task ExternalTool_String_Arguments_Bind_To_Single_Function_Parameter()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ string? receivedPatch = null;
+ var tool = CopilotTool.DefineTool(
+ (string patch) =>
+ {
+ receivedPatch = patch;
+ return "applied";
+ },
+ new CopilotToolOptions { OverridesBuiltInTool = true },
+ new AIFunctionFactoryOptions { Name = "apply_patch" });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [tool],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+ server.ClearRequests();
+ using var arguments = JsonDocument.Parse("\"*** Begin Patch\\n*** End Patch\"");
+
+ DispatchEvent(session, new ExternalToolRequestedEvent
+ {
+ Data = new ExternalToolRequestedData
+ {
+ Arguments = arguments.RootElement.Clone(),
+ RequestId = "apply-patch-request",
+ SessionId = session.SessionId,
+ ToolCallId = "apply-patch-call",
+ ToolName = "apply_patch"
+ }
+ });
+
+ var request = await WaitForRequestAsync(server, "session.tools.handlePendingToolCall");
+ Assert.Equal("*** Begin Patch\n*** End Patch", receivedPatch);
+ Assert.False(request.Params.TryGetProperty("error", out _));
+ Assert.Equal("applied", request.Params.GetProperty("result").GetProperty("textResultForLlm").GetString());
+ }
+
+ [Fact]
+ public async Task ExternalTool_String_Arguments_Reject_Ambiguous_Function_Parameters()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var invoked = false;
+ var tool = CopilotTool.DefineTool(
+ (string patch, string explanation) =>
+ {
+ invoked = true;
+ return "applied";
+ },
+ new CopilotToolOptions { OverridesBuiltInTool = true },
+ new AIFunctionFactoryOptions { Name = "apply_patch" });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [tool],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+ server.ClearRequests();
+ using var arguments = JsonDocument.Parse("\"*** Begin Patch\\n*** End Patch\"");
+
+ DispatchEvent(session, new ExternalToolRequestedEvent
+ {
+ Data = new ExternalToolRequestedData
+ {
+ Arguments = arguments.RootElement.Clone(),
+ RequestId = "ambiguous-apply-patch-request",
+ SessionId = session.SessionId,
+ ToolCallId = "ambiguous-apply-patch-call",
+ ToolName = "apply_patch"
+ }
+ });
+
+ var request = await WaitForRequestAsync(server, "session.tools.handlePendingToolCall");
+ Assert.False(invoked);
+ Assert.Contains("received non-object arguments", request.Params.GetProperty("error").GetString());
+ Assert.False(request.Params.TryGetProperty("result", out _));
+ }
+
+ [Fact]
+ public async Task ExternalTool_Number_Arguments_Bind_To_Single_Function_Parameter()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ int? receivedLine = null;
+ var tool = CopilotTool.DefineTool(
+ (int line) =>
+ {
+ receivedLine = line;
+ return "selected";
+ },
+ factoryOptions: new AIFunctionFactoryOptions { Name = "select_line" });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [tool],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+ server.ClearRequests();
+ using var arguments = JsonDocument.Parse("42");
+
+ DispatchEvent(session, new ExternalToolRequestedEvent
+ {
+ Data = new ExternalToolRequestedData
+ {
+ Arguments = arguments.RootElement.Clone(),
+ RequestId = "select-line-request",
+ SessionId = session.SessionId,
+ ToolCallId = "select-line-call",
+ ToolName = "select_line"
+ }
+ });
+
+ var request = await WaitForRequestAsync(server, "session.tools.handlePendingToolCall");
+ Assert.Equal(42, receivedLine);
+ Assert.False(request.Params.TryGetProperty("error", out _));
+ }
+
+ [Fact]
+ public async Task ExternalTool_String_Arguments_Bind_To_Sole_Required_Function_Parameter()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ string? receivedPatch = null;
+ string? receivedExplanation = null;
+ var tool = CopilotTool.DefineTool(
+ (string patch, string? explanation = null) =>
+ {
+ receivedPatch = patch;
+ receivedExplanation = explanation;
+ return "applied";
+ },
+ new CopilotToolOptions { OverridesBuiltInTool = true },
+ new AIFunctionFactoryOptions { Name = "apply_patch" });
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [tool],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+ server.ClearRequests();
+ using var arguments = JsonDocument.Parse("\"*** Begin Patch\\n*** End Patch\"");
+
+ DispatchEvent(session, new ExternalToolRequestedEvent
+ {
+ Data = new ExternalToolRequestedData
+ {
+ Arguments = arguments.RootElement.Clone(),
+ RequestId = "optional-apply-patch-request",
+ SessionId = session.SessionId,
+ ToolCallId = "optional-apply-patch-call",
+ ToolName = "apply_patch"
+ }
+ });
+
+ var request = await WaitForRequestAsync(server, "session.tools.handlePendingToolCall");
+ Assert.Equal("*** Begin Patch\n*** End Patch", receivedPatch);
+ Assert.Null(receivedExplanation);
+ Assert.False(request.Params.TryGetProperty("error", out _));
+ }
+
[Fact]
public async Task EmptyMode_Create_Sends_Empty_IncludedBuiltinSkills()
{
@@ -862,6 +1216,252 @@ public async Task McpAuth_Handler_Exception_Cancels_Pending_Request()
Assert.Equal("cancelled", request.Params.GetProperty("result").GetProperty("kind").GetString());
}
+ [Fact]
+ public async Task ExternalToolCompleted_Cancels_Blocked_Tool_When_Cancellation_Callback_Throws()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var toolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var toolCancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [AIFunctionFactory.Create(BlockedTool, "blocked_tool")],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ DispatchEvent(session, ExternalToolRequested("request-1"));
+ await toolStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ DispatchEvent(session, new ExternalToolCompletedEvent
+ {
+ Data = new ExternalToolCompletedData { RequestId = "request-1" }
+ });
+
+ await toolCancelled.Task.WaitAsync(TimeSpan.FromSeconds(5));
+ await Task.Delay(50);
+ Assert.DoesNotContain(server.Requests,
+ request => request.Method == "session.tools.handlePendingToolCall"
+ && request.Params.GetProperty("requestId").GetString() == "request-1");
+
+ async Task BlockedTool(CancellationToken cancellationToken)
+ {
+ toolStarted.TrySetResult();
+ using var registration = cancellationToken.Register(
+ () => throw new InvalidOperationException("cancellation callback failed"));
+ try
+ {
+ await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
+ return "unreachable";
+ }
+ catch (OperationCanceledException)
+ {
+ toolCancelled.TrySetResult();
+ throw;
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ExternalToolCompleted_Does_Not_Block_Event_Dispatch_On_Cancellation_Callback()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var toolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var callbackStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var releaseCallback = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var toolCancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ await using var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [AIFunctionFactory.Create(BlockedTool, "blocked_tool")],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ DispatchEvent(session, ExternalToolRequested("request-blocking-callback"));
+ await toolStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ var dispatchTask = Task.Run(() => DispatchEvent(session, new ExternalToolCompletedEvent
+ {
+ Data = new ExternalToolCompletedData { RequestId = "request-blocking-callback" }
+ }));
+ await callbackStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
+ try
+ {
+ await dispatchTask.WaitAsync(TimeSpan.FromSeconds(5));
+ }
+ finally
+ {
+ releaseCallback.TrySetResult();
+ }
+ await toolCancelled.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ async Task BlockedTool(CancellationToken cancellationToken)
+ {
+ toolStarted.TrySetResult();
+ using var registration = cancellationToken.Register(() =>
+ {
+ callbackStarted.TrySetResult();
+ releaseCallback.Task.GetAwaiter().GetResult();
+ });
+ try
+ {
+ await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
+ return "unreachable";
+ }
+ catch (OperationCanceledException)
+ {
+ toolCancelled.TrySetResult();
+ throw;
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ForceStopAsync_Cancels_Blocked_Tool_When_Cancellation_Callback_Throws()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var toolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var toolCancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [AIFunctionFactory.Create(BlockedTool, "blocked_tool")],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ DispatchEvent(session, ExternalToolRequested("request-force-stop"));
+ await toolStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ await client.ForceStopAsync();
+
+ await toolCancelled.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ async Task BlockedTool(CancellationToken cancellationToken)
+ {
+ toolStarted.TrySetResult();
+ using var registration = cancellationToken.Register(
+ () => throw new InvalidOperationException("cancellation callback failed"));
+ try
+ {
+ await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
+ return "unreachable";
+ }
+ catch (OperationCanceledException)
+ {
+ toolCancelled.TrySetResult();
+ throw;
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ForceStopAsync_Does_Not_Start_Late_External_Tool()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var toolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [AIFunctionFactory.Create(Tool, "late_tool")],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ await client.ForceStopAsync();
+ DispatchEvent(session, ExternalToolRequested("request-after-force-stop", "late_tool"));
+
+ Assert.False(toolStarted.Task.IsCompleted);
+
+ string Tool()
+ {
+ toolStarted.TrySetResult();
+ return "unexpected";
+ }
+ }
+
+ [Fact]
+ public async Task ConnectionClose_Cancels_Blocked_Tool_Delegate()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var toolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var toolCancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [AIFunctionFactory.Create(BlockedTool, "blocked_tool")],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ DispatchEvent(session, ExternalToolRequested("request-connection-close"));
+ await toolStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ server.CloseConnection();
+
+ await toolCancelled.Task.WaitAsync(TimeSpan.FromSeconds(5));
+ await client.ForceStopAsync();
+
+ async Task BlockedTool(CancellationToken cancellationToken)
+ {
+ toolStarted.TrySetResult();
+ try
+ {
+ await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
+ return "unreachable";
+ }
+ catch (OperationCanceledException)
+ {
+ toolCancelled.TrySetResult();
+ throw;
+ }
+ }
+ }
+
+ [Fact]
+ public async Task DisposeAsync_Cancels_Blocked_Tool_Delegate()
+ {
+ await using var server = await FakeCopilotServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
+ var toolStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var toolCancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var session = await client.CreateSessionAsync(new SessionConfig
+ {
+ Tools = [AIFunctionFactory.Create(BlockedTool, "blocked_tool")],
+ OnPermissionRequest = PermissionHandler.ApproveAll
+ });
+
+ DispatchEvent(session, ExternalToolRequested("request-2"));
+ await toolStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ await session.DisposeAsync();
+
+ await toolCancelled.Task.WaitAsync(TimeSpan.FromSeconds(5));
+
+ async Task BlockedTool(CancellationToken cancellationToken)
+ {
+ toolStarted.TrySetResult();
+ try
+ {
+ await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
+ return "unreachable";
+ }
+ catch (OperationCanceledException)
+ {
+ toolCancelled.TrySetResult();
+ throw;
+ }
+ }
+ }
+
+ private static ExternalToolRequestedEvent ExternalToolRequested(string requestId, string toolName = "blocked_tool") =>
+ new()
+ {
+ Data = new ExternalToolRequestedData
+ {
+ RequestId = requestId,
+ SessionId = "session-1",
+ ToolCallId = "tool-call-1",
+ ToolName = toolName
+ }
+ };
+
[Fact]
public async Task Generated_Session_Rpc_Throws_When_Session_Disposed()
{
@@ -1426,6 +2026,11 @@ public void FailSessionCreate()
_failSessionCreate = true;
}
+ public void CloseConnection()
+ {
+ _stream?.Dispose();
+ }
+
public async Task SendRequestAsync(string method, Dictionary parameters)
{
var stream = _stream ?? throw new InvalidOperationException("Client is not connected.");
@@ -1579,11 +2184,24 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel
{
["success"] = true
},
+ "session.tools.handlePendingToolCall" => new Dictionary
+ {
+ ["success"] = true
+ },
+ "session.model.switchTo" => new Dictionary
+ {
+ ["modelId"] = "auto"
+ },
+ "session.model.switchAutoTier" => new Dictionary
+ {
+ ["status"] = "pending",
+ ["effectiveAutoTier"] = "balance"
+ },
"session.delete" => new Dictionary
{
["success"] = true
},
- "session.destroy" => await DestroySessionAsync(cancellationToken),
+ "session.detach" => await DetachSessionAsync(cancellationToken),
"runtime.shutdown" => HandleRuntimeShutdown(),
_ => throw new InvalidOperationException($"Unexpected RPC method '{method}'.")
};
@@ -1620,7 +2238,7 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel
};
}
- private async Task> DestroySessionAsync(CancellationToken cancellationToken)
+ private async Task> DetachSessionAsync(CancellationToken cancellationToken)
{
if (_delayDestroy)
{
@@ -1628,7 +2246,7 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel
await _allowDestroy.Task.WaitAsync(cancellationToken);
}
- return [];
+ return new Dictionary { ["success"] = true };
}
private Dictionary HandleRuntimeShutdown()
diff --git a/dotnet/test/Unit/CloneTests.cs b/dotnet/test/Unit/CloneTests.cs
index 2948ac2129..2f213525f6 100644
--- a/dotnet/test/Unit/CloneTests.cs
+++ b/dotnet/test/Unit/CloneTests.cs
@@ -23,6 +23,13 @@ public void CopilotClientOptions_Clone_CopiesAllProperties()
BuiltinPluginDirectories = ["/plugins/core", "/plugins/github"],
EnableRemoteSessions = true,
SessionIdleTimeoutSeconds = 600,
+ ClientInfo = new CopilotClientInfo
+ {
+ ApplicationName = "example-app",
+ ApplicationVersion = "1.0.0",
+ IntegrationName = "example-integration",
+ IntegrationVersion = "2.0.0",
+ },
};
var clone = original.Clone();
@@ -38,6 +45,7 @@ public void CopilotClientOptions_Clone_CopiesAllProperties()
Assert.NotSame(original.BuiltinPluginDirectories, clone.BuiltinPluginDirectories);
Assert.Equal(original.EnableRemoteSessions, clone.EnableRemoteSessions);
Assert.Equal(original.SessionIdleTimeoutSeconds, clone.SessionIdleTimeoutSeconds);
+ Assert.Same(original.ClientInfo, clone.ClientInfo);
}
[Fact]
diff --git a/dotnet/test/Unit/E2ETestBackendTests.cs b/dotnet/test/Unit/E2ETestBackendTests.cs
index f7c39a5083..43b23c1a48 100644
--- a/dotnet/test/Unit/E2ETestBackendTests.cs
+++ b/dotnet/test/Unit/E2ETestBackendTests.cs
@@ -25,7 +25,7 @@ public void RejectsUnknownBackend()
() => E2ETestBackendConfiguration.Parse("unknown"));
[Theory]
- [InlineData("anthropic-messages", "anthropic", null, "claude-sonnet-4.5")]
+ [InlineData("anthropic-messages", "anthropic", null, "claude-sonnet-5")]
[InlineData("openai-responses", "openai", "responses", "gpt-4.1")]
[InlineData("openai-completions", "openai", "completions", "gpt-4.1")]
public void AppliesProvider(
diff --git a/dotnet/test/Unit/GitHubTelemetryTests.cs b/dotnet/test/Unit/GitHubTelemetryTests.cs
index a4a241e38d..6213e0b1d0 100644
--- a/dotnet/test/Unit/GitHubTelemetryTests.cs
+++ b/dotnet/test/Unit/GitHubTelemetryTests.cs
@@ -138,6 +138,92 @@ public async Task Connect_Does_Not_Opt_In_Without_Handler()
Assert.True(
!present || flag.ValueKind == JsonValueKind.Null,
"connect request should omit enableGitHubTelemetryForwarding (or send null) when no handler is registered");
+ Assert.Equal(
+ ["agent", "client", "shell"],
+ connectParams.GetProperty("supportedTaskKinds").EnumerateArray().Select(kind => kind.GetString()));
+ }
+
+ [Fact]
+ public async Task Connect_Forwards_Declared_ClientInfo()
+ {
+ await using var server = await FakeTelemetryServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions
+ {
+ Connection = RuntimeConnection.ForUri(server.Url),
+ ClientInfo = new CopilotClientInfo
+ {
+ ApplicationName = "acme-developer-portal",
+ ApplicationVersion = "2.4.0",
+ IntegrationName = "copilot-assistant",
+ IntegrationVersion = "1.5.0",
+ },
+ });
+ await client.StartAsync();
+
+ var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured.");
+ Assert.True(connectParams.TryGetProperty("clientInfo", out var clientInfo));
+ Assert.Equal("acme-developer-portal", clientInfo.GetProperty("editorName").GetString());
+ Assert.Equal("2.4.0", clientInfo.GetProperty("editorVersion").GetString());
+ Assert.Equal("copilot-assistant", clientInfo.GetProperty("extensionName").GetString());
+ Assert.Equal("1.5.0", clientInfo.GetProperty("extensionVersion").GetString());
+ }
+
+ [Fact]
+ public async Task Connect_Omits_ClientInfo_When_Unset()
+ {
+ await using var server = await FakeTelemetryServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions
+ {
+ Connection = RuntimeConnection.ForUri(server.Url),
+ });
+ await client.StartAsync();
+
+ var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured.");
+ Assert.False(
+ connectParams.TryGetProperty("clientInfo", out _),
+ "connect request should omit clientInfo when none was declared");
+ }
+
+ [Fact]
+ public async Task Connect_Omits_Empty_ClientInfo_Fields()
+ {
+ await using var server = await FakeTelemetryServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions
+ {
+ Connection = RuntimeConnection.ForUri(server.Url),
+ ClientInfo = new CopilotClientInfo { ApplicationName = "example-app", ApplicationVersion = "" },
+ });
+ await client.StartAsync();
+
+ var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured.");
+ Assert.True(connectParams.TryGetProperty("clientInfo", out var clientInfo));
+ Assert.Equal("example-app", clientInfo.GetProperty("editorName").GetString());
+ Assert.False(clientInfo.TryGetProperty("editorVersion", out _));
+ Assert.False(clientInfo.TryGetProperty("extensionName", out _));
+ Assert.False(clientInfo.TryGetProperty("extensionVersion", out _));
+ }
+
+ [Fact]
+ public async Task Connect_Omits_All_Empty_ClientInfo()
+ {
+ await using var server = await FakeTelemetryServer.StartAsync();
+ await using var client = new CopilotClient(new CopilotClientOptions
+ {
+ Connection = RuntimeConnection.ForUri(server.Url),
+ ClientInfo = new CopilotClientInfo
+ {
+ ApplicationName = "",
+ ApplicationVersion = "",
+ IntegrationName = "",
+ IntegrationVersion = "",
+ },
+ });
+ await client.StartAsync();
+
+ var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured.");
+ Assert.False(
+ connectParams.TryGetProperty("clientInfo", out _),
+ "connect request should omit an all-empty clientInfo");
}
[Fact]
@@ -409,7 +495,7 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel
"session.create" => CaptureCreate(request),
"session.resume" => CaptureResume(request),
"session.send" => new Dictionary { ["messageId"] = "message-1" },
- "session.destroy" => new Dictionary(),
+ "session.detach" => new Dictionary { ["success"] = true },
"session.options.update" => new Dictionary { ["success"] = true },
"runtime.shutdown" => new Dictionary(),
_ => throw new InvalidOperationException($"Unexpected RPC method '{method}'."),
diff --git a/dotnet/test/Unit/MSBuildTargetsTests.cs b/dotnet/test/Unit/MSBuildTargetsTests.cs
index cab91e568f..a6e3bb5660 100644
--- a/dotnet/test/Unit/MSBuildTargetsTests.cs
+++ b/dotnet/test/Unit/MSBuildTargetsTests.cs
@@ -2,8 +2,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
+using System.Collections.Concurrent;
using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
using System.Text;
using Xunit;
@@ -16,11 +20,7 @@ namespace GitHub.Copilot.Test.Unit;
/// a subprocess so we exercise real MSBuild evaluation.
///
///
-/// These tests deliberately do not exercise the network-bound default download path; they
-/// pin a fake CopilotCliVersion and supply a fake CLI binary via
-/// CopilotCliBinaryPath. That is sufficient to cover the regression in issue
-/// #921 ("preinstalled CLI is ignored and copy/register are skipped when
-/// CopilotSkipCliDownload=true").
+/// Download tests use a loopback release server; they never access the default GitHub URL.
///
public class MSBuildTargetsTests
{
@@ -28,6 +28,9 @@ public class MSBuildTargetsTests
private static readonly string BinaryName = OperatingSystem.IsWindows() ? "copilot.exe" : "copilot";
+ private static readonly string RuntimeWrapperName =
+ OperatingSystem.IsWindows() ? "copilot-runtime.exe" : "copilot-runtime";
+
[Fact]
public async Task PreinstalledCliBinaryPath_IsHonored_DownloadSkipped_AndCopiedToOutput()
{
@@ -106,16 +109,111 @@ public async Task PreinstalledCliBinaryPath_WithSkipCliDownload_StillCopiesToOut
Assert.True(File.Exists(sandbox.ExpectedOutputBinary()), result.FailureMessage());
}
+ [Fact]
+ public async Task ReleaseAsset_IsDownloadedVerifiedExtractedAndCached()
+ {
+ using var sandbox = MSBuildSandbox.Create();
+ var archive = sandbox.CreateReleaseArchive("release-runtime-wrapper");
+ var assetName = $"github-copilot-0.0.0-test-{GetReleasePlatform()}.tgz";
+ var assetPath = $"/v0.0.0-test/{assetName}";
+ var checksumsPath = "/v0.0.0-test/SHA256SUMS.txt";
+ var checksum = ComputeSha256(archive);
+ using var server = new ReleaseServer(new Dictionary
+ {
+ [checksumsPath] = Encoding.UTF8.GetBytes($"{checksum} {assetName}\n"),
+ [assetPath] = archive,
+ });
+
+ var properties = new Dictionary
+ {
+ ["CopilotCliReleaseBaseUrl"] = server.BaseUrl,
+ };
+ var firstBuild = await sandbox.BuildAsync(properties);
+
+ Assert.True(firstBuild.Succeeded, firstBuild.FailureMessage());
+ Assert.Equal("release-runtime-wrapper", File.ReadAllText(sandbox.ExpectedOutputBinary()));
+ Assert.Equal("release-runtime-wrapper", File.ReadAllText(sandbox.ExpectedRuntimeAsset(RuntimeWrapperName)));
+ Assert.Equal("runtime", File.ReadAllText(sandbox.ExpectedRuntimeAsset("runtime.node")));
+ Assert.True(File.Exists(sandbox.ExpectedCacheAsset(".copilot-runtime-complete")));
+ Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset(".copilot-runtime-complete")));
+ Assert.Equal(1, server.RequestPaths.Count(path => path == checksumsPath));
+ Assert.Equal(1, server.RequestPaths.Count(path => path == assetPath));
+ Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("SHA256SUMS.txt")));
+
+ var secondBuild = await sandbox.BuildAsync(properties);
+
+ Assert.True(secondBuild.Succeeded, secondBuild.FailureMessage());
+ Assert.Equal(2, server.RequestPaths.Count);
+ }
+
+ [Fact]
+ public async Task IncompleteCache_WithRuntimePairButNoMarker_IsReacquired()
+ {
+ using var sandbox = MSBuildSandbox.Create();
+ sandbox.WriteRuntimeCacheAsset("prebuilds", GetReleasePlatform(), RuntimeWrapperName, "partial-wrapper");
+ sandbox.WriteRuntimeCacheAsset("prebuilds", GetReleasePlatform(), "runtime.node", "partial-runtime");
+ sandbox.WriteRuntimeCacheAsset("definitions", "stale.json", "stale");
+ var archive = sandbox.CreateReleaseArchive("complete-wrapper");
+ var assetName = $"github-copilot-0.0.0-test-{GetReleasePlatform()}.tgz";
+ var assetPath = $"/v0.0.0-test/{assetName}";
+ var checksumsPath = "/v0.0.0-test/SHA256SUMS.txt";
+ using var server = new ReleaseServer(new Dictionary
+ {
+ [checksumsPath] = Encoding.UTF8.GetBytes($"{ComputeSha256(archive)} {assetName}\n"),
+ [assetPath] = archive,
+ });
+
+ var result = await sandbox.BuildAsync(new Dictionary
+ {
+ ["CopilotCliReleaseBaseUrl"] = server.BaseUrl,
+ });
+
+ Assert.True(result.Succeeded, result.FailureMessage());
+ Assert.Equal(1, server.RequestPaths.Count(path => path == checksumsPath));
+ Assert.Equal(1, server.RequestPaths.Count(path => path == assetPath));
+ Assert.Equal("complete-wrapper", File.ReadAllText(sandbox.ExpectedRuntimeAsset(RuntimeWrapperName)));
+ Assert.Equal("runtime", File.ReadAllText(sandbox.ExpectedRuntimeAsset("runtime.node")));
+ Assert.True(File.Exists(sandbox.ExpectedCacheAsset(".copilot-runtime-complete")));
+ Assert.False(File.Exists(sandbox.ExpectedCacheAsset("definitions", "stale.json")));
+ Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("definitions", "stale.json")));
+ }
+
+ [Fact]
+ public async Task ReleaseAsset_WithChecksumMismatch_FailsBeforeExtraction()
+ {
+ using var sandbox = MSBuildSandbox.Create();
+ var archive = Encoding.UTF8.GetBytes("not the expected archive");
+ var assetName = $"github-copilot-0.0.0-test-{GetReleasePlatform()}.tgz";
+ using var server = new ReleaseServer(new Dictionary
+ {
+ ["/v0.0.0-test/SHA256SUMS.txt"] =
+ Encoding.UTF8.GetBytes($"{new string('0', 64)} *{assetName}\n"),
+ [$"/v0.0.0-test/{assetName}"] = archive,
+ });
+
+ var result = await sandbox.BuildAsync(new Dictionary
+ {
+ ["CopilotCliReleaseBaseUrl"] = server.BaseUrl,
+ });
+
+ Assert.False(result.Succeeded, "Build should fail when the release checksum does not match.");
+ Assert.Contains($"Checksum mismatch for {assetName}", result.StandardOutput, StringComparison.Ordinal);
+ Assert.False(File.Exists(sandbox.ExpectedOutputBinary()));
+ }
+
[Fact]
public async Task RuntimePackageAssets_AreFilteredAndCopiedToOutput()
{
using var sandbox = MSBuildSandbox.Create();
var preinstalled = sandbox.WritePreinstalledBinary("fake-cli-contents");
- sandbox.WriteRuntimeCacheAsset("prebuilds", GetNpmPlatform(), "runtime.node", "runtime");
- sandbox.WriteRuntimeCacheAsset("prebuilds", GetNpmPlatform(),
- OperatingSystem.IsWindows() ? "copilot-runtime.exe" : "copilot-runtime", "wrapper");
- sandbox.WriteRuntimeCacheAsset("ripgrep", "bin", GetNpmPlatform(), "rg", "ripgrep");
+ sandbox.WriteRuntimeCacheAsset("prebuilds", GetReleasePlatform(), "runtime.node", "runtime");
+ sandbox.WriteRuntimeCacheAsset("prebuilds", GetReleasePlatform(),
+ RuntimeWrapperName, "wrapper");
+ sandbox.WriteRuntimeCacheAsset("ripgrep", "bin", GetReleasePlatform(), "rg", "ripgrep");
sandbox.WriteRuntimeCacheAsset("definitions", "future.json", "{}");
+ sandbox.WriteRuntimeCacheAsset("copilot-sdk", "extension.js", "extension");
+ sandbox.WriteRuntimeCacheAsset("preloads", "extension_bootstrap.mjs", "preload");
+ sandbox.WriteRuntimeCacheAsset("sdk", "factory.js", "factory");
sandbox.WriteRuntimeCacheAsset("app.js", "excluded");
sandbox.WriteRuntimeCacheAsset("LICENSE.md", "excluded");
sandbox.WriteRuntimeCacheAsset("README.md", "excluded");
@@ -127,8 +225,11 @@ public async Task RuntimePackageAssets_AreFilteredAndCopiedToOutput()
});
Assert.True(result.Succeeded, result.FailureMessage());
- Assert.Equal("ripgrep", File.ReadAllText(sandbox.ExpectedRuntimeAsset("ripgrep", "bin", GetNpmPlatform(), "rg")));
+ Assert.Equal("ripgrep", File.ReadAllText(sandbox.ExpectedRuntimeAsset("ripgrep", "bin", GetReleasePlatform(), "rg")));
Assert.Equal("{}", File.ReadAllText(sandbox.ExpectedRuntimeAsset("definitions", "future.json")));
+ Assert.Equal("extension", File.ReadAllText(sandbox.ExpectedRuntimeAsset("copilot-sdk", "extension.js")));
+ Assert.Equal("preload", File.ReadAllText(sandbox.ExpectedRuntimeAsset("preloads", "extension_bootstrap.mjs")));
+ Assert.Equal("factory", File.ReadAllText(sandbox.ExpectedRuntimeAsset("sdk", "factory.js")));
Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("app.js")));
Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("LICENSE.md")));
Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("README.md")));
@@ -180,7 +281,7 @@ private static string FindTargetsFile([CallerFilePath] string? thisFile = null)
"Could not locate GitHub.Copilot.SDK.targets relative to test assembly or source file.");
}
- private static string GetNpmPlatform()
+ private static string GetReleasePlatform()
{
var arch = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture
== System.Runtime.InteropServices.Architecture.Arm64
@@ -191,6 +292,16 @@ private static string GetNpmPlatform()
return $"linux-{arch}";
}
+ private static string ComputeSha256(byte[] contents)
+ {
+#if NETFRAMEWORK
+ using var sha256 = SHA256.Create();
+ return BitConverter.ToString(sha256.ComputeHash(contents)).Replace("-", "").ToLowerInvariant();
+#else
+ return Convert.ToHexString(SHA256.HashData(contents)).ToLowerInvariant();
+#endif
+ }
+
///
/// A throwaway directory containing a minimal csproj that imports the SDK targets
/// file. Disposing removes the directory tree.
@@ -238,6 +349,36 @@ public string WritePreinstalledBinary(string contents, string? fileName = null)
return path;
}
+ public byte[] CreateReleaseArchive(string runtimeWrapperContents)
+ {
+ var sourceDir = Path.Combine(ProjectDir, "release-source");
+ var packageDir = Path.Combine(sourceDir, "package");
+ var prebuildDir = Path.Combine(packageDir, "prebuilds", GetReleasePlatform());
+ Directory.CreateDirectory(prebuildDir);
+ File.WriteAllText(Path.Combine(prebuildDir, "runtime.node"), "runtime");
+ File.WriteAllText(Path.Combine(prebuildDir, RuntimeWrapperName), runtimeWrapperContents);
+
+ var archivePath = Path.Combine(ProjectDir, "release-asset.tgz");
+ var tarPath = OperatingSystem.IsWindows()
+ ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "tar.exe")
+ : "tar";
+ var startInfo = new ProcessStartInfo(tarPath)
+ {
+ Arguments = $"-czf \"{archivePath}\" -C \"{sourceDir}\" package",
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ };
+ using var process = Process.Start(startInfo) ??
+ throw new InvalidOperationException("Failed to start tar while creating a release test asset.");
+ var standardError = process.StandardError.ReadToEnd();
+ process.WaitForExit();
+ if (process.ExitCode != 0)
+ {
+ throw new InvalidOperationException($"tar failed while creating a release test asset: {standardError}");
+ }
+ return File.ReadAllBytes(archivePath);
+ }
+
public string ExpectedOutputBinary()
{
var rid = GetPortableRid();
@@ -247,14 +388,20 @@ public string ExpectedOutputBinary()
public void WriteRuntimeCacheAsset(params string[] pathAndContents)
{
var pathParts = pathAndContents.Take(pathAndContents.Length - 1).ToArray();
+ var path = ExpectedCacheAsset(pathParts);
+ Directory.CreateDirectory(Path.GetDirectoryName(path)!);
+ File.WriteAllText(path, pathAndContents[^1]);
+ }
+
+ public string ExpectedCacheAsset(params string[] pathParts)
+ {
var path = Path.Combine(ProjectDir, "obj", "Debug", "net8.0", "copilot-cli", "0.0.0-test",
- GetNpmPlatform());
+ GetReleasePlatform());
foreach (var part in pathParts)
{
path = Path.Combine(path, part);
}
- Directory.CreateDirectory(Path.GetDirectoryName(path)!);
- File.WriteAllText(path, pathAndContents[^1]);
+ return path;
}
public string ExpectedRuntimeAsset(params string[] pathParts)
@@ -366,6 +513,92 @@ private static string GetPortableRid()
}
}
+ private sealed class ReleaseServer : IDisposable
+ {
+ private readonly IReadOnlyDictionary _responses;
+ private readonly TcpListener _listener = new(IPAddress.Loopback, 0);
+ private readonly CancellationTokenSource _cancellation = new();
+ private readonly Task _serverTask;
+
+ public ReleaseServer(IReadOnlyDictionary responses)
+ {
+ _responses = responses;
+ _listener.Start();
+ var endpoint = (IPEndPoint)_listener.LocalEndpoint;
+ BaseUrl = $"http://127.0.0.1:{endpoint.Port}";
+ _serverTask = ServeAsync();
+ }
+
+ public string BaseUrl { get; }
+
+ public ConcurrentQueue RequestPaths { get; } = new();
+
+ public void Dispose()
+ {
+ _cancellation.Cancel();
+ _listener.Stop();
+ try { _serverTask.GetAwaiter().GetResult(); }
+ catch (OperationCanceledException) { }
+ _cancellation.Dispose();
+ }
+
+ private async Task ServeAsync()
+ {
+ while (!_cancellation.IsCancellationRequested)
+ {
+ TcpClient client;
+ try
+ {
+ client = await _listener.AcceptTcpClientAsync();
+ }
+ catch (ObjectDisposedException) when (_cancellation.IsCancellationRequested)
+ {
+ break;
+ }
+ catch (SocketException) when (_cancellation.IsCancellationRequested)
+ {
+ break;
+ }
+ await RespondAsync(client);
+ }
+ }
+
+ private async Task RespondAsync(TcpClient client)
+ {
+ using (client)
+ {
+ var stream = client.GetStream();
+ using var reader = new StreamReader(stream, Encoding.ASCII, false, 1024, leaveOpen: true);
+ var requestLine = await reader.ReadLineAsync();
+ string? header;
+ do
+ {
+ header = await reader.ReadLineAsync();
+ }
+ while (!string.IsNullOrEmpty(header));
+
+ var path = requestLine?.Split(' ', StringSplitOptions.RemoveEmptyEntries).ElementAtOrDefault(1) ?? "";
+ RequestPaths.Enqueue(path);
+ var found = _responses.TryGetValue(path, out var body);
+ body ??= Encoding.UTF8.GetBytes("Not found");
+ var status = found ? "200 OK" : "404 Not Found";
+ var responseHeaders = Encoding.ASCII.GetBytes(
+ $"HTTP/1.1 {status}\r\nContent-Length: {body.Length}\r\nConnection: close\r\n\r\n");
+ await WriteBytesAsync(stream, responseHeaders);
+ await WriteBytesAsync(stream, body);
+ }
+ }
+
+ private static Task WriteBytesAsync(Stream stream, byte[] contents)
+ {
+#if NETFRAMEWORK
+ return stream.WriteAsync(contents, 0, contents.Length);
+#else
+ return stream.WriteAsync(contents).AsTask();
+#endif
+ }
+ }
+
private sealed record BuildResult(int ExitCode, string StandardOutput, string StandardError, string CommandLine)
{
public bool Succeeded => ExitCode == 0;
diff --git a/dotnet/test/Unit/RuntimeConnectionUrlParsingTests.cs b/dotnet/test/Unit/RuntimeConnectionUrlParsingTests.cs
new file mode 100644
index 0000000000..1b16456b0d
--- /dev/null
+++ b/dotnet/test/Unit/RuntimeConnectionUrlParsingTests.cs
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+using Xunit;
+using System.Reflection;
+
+namespace GitHub.Copilot.Test.Unit;
+
+public class RuntimeConnectionUrlParsingTests
+{
+ [Fact]
+ public void ForUri_ParsesBracketedIpv6HostPort()
+ {
+ var client = new CopilotClient(new CopilotClientOptions
+ {
+ Connection = RuntimeConnection.ForUri("[::1]:9000")
+ });
+
+ Assert.Equal("::1", GetPrivateField(client, "_optionsHost"));
+ Assert.Equal(9000, GetPrivateField(client, "_optionsPort"));
+ }
+
+ [Fact]
+ public void ForUri_ParsesHttpIpv6HostPort()
+ {
+ var client = new CopilotClient(new CopilotClientOptions
+ {
+ Connection = RuntimeConnection.ForUri("http://[::1]:7000")
+ });
+
+ Assert.Equal("::1", GetPrivateField(client, "_optionsHost"));
+ Assert.Equal(7000, GetPrivateField(client, "_optionsPort"));
+ }
+
+ private static T? GetPrivateField(object instance, string name)
+ {
+ var field = instance.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.NotNull(field);
+ return (T?)field.GetValue(instance);
+ }
+}
diff --git a/dotnet/test/Unit/SerializationTests.cs b/dotnet/test/Unit/SerializationTests.cs
index 2414093797..7d0f535160 100644
--- a/dotnet/test/Unit/SerializationTests.cs
+++ b/dotnet/test/Unit/SerializationTests.cs
@@ -17,6 +17,25 @@ namespace GitHub.Copilot.Test.Unit;
///
public class SerializationTests
{
+ [Fact]
+ public void SandboxConfig_RoundtripsAllowBypass_AndOmitsWhenAbsent()
+ {
+ var options = GetSerializerOptions();
+ var configured = new SandboxConfig { Enabled = true, AllowBypass = true };
+
+ var json = JsonSerializer.Serialize(configured, options);
+ using var document = JsonDocument.Parse(json);
+ Assert.True(document.RootElement.GetProperty("allowBypass").GetBoolean());
+
+ var roundTripped = JsonSerializer.Deserialize(json, options);
+ Assert.NotNull(roundTripped);
+ Assert.True(roundTripped.AllowBypass);
+
+ var omitted = JsonSerializer.Serialize(new SandboxConfig { Enabled = true }, options);
+ using var omittedDocument = JsonDocument.Parse(omitted);
+ Assert.False(omittedDocument.RootElement.TryGetProperty("allowBypass", out _));
+ }
+
[Fact]
public void ProviderConfig_CanSerializeHeaders_WithSdkOptions()
{
@@ -1122,6 +1141,40 @@ public void ToolResultObject_OmitsToolReferences_WhenNull_WithSdkOptions()
Assert.False(document.RootElement.TryGetProperty("toolReferences", out _));
}
+#pragma warning disable GHCP001 // The queue management surface is intentionally experimental.
+ [Theory]
+ [InlineData("message-1")]
+ [InlineData(null)]
+ public void QueuePendingItems_MessageId_UsesCamelCaseAndIsOptional(string? messageId)
+ {
+ var options = GetSerializerOptions();
+ var messageIdProperty = messageId is null ? "" : $""","messageId":"{messageId}" """;
+ var json = $$"""
+ {
+ "id": "queue-1",
+ "kind": "message",
+ "displayText": "hello",
+ "agentMode": "interactive"
+ {{messageIdProperty}}
+ }
+ """;
+
+ var item = JsonSerializer.Deserialize(json, options);
+ Assert.NotNull(item);
+ Assert.Equal(messageId, item.MessageId);
+
+ using var document = JsonDocument.Parse(JsonSerializer.Serialize(item, options));
+ if (messageId is null)
+ {
+ Assert.False(document.RootElement.TryGetProperty("messageId", out _));
+ }
+ else
+ {
+ Assert.Equal(messageId, document.RootElement.GetProperty("messageId").GetString());
+ }
+ }
+#pragma warning restore GHCP001
+
private static JsonSerializerOptions GetSerializerOptions()
{
var prop = typeof(CopilotClient)
diff --git a/dotnet/test/Unit/SessionEventSerializationTests.cs b/dotnet/test/Unit/SessionEventSerializationTests.cs
index 326ac3f3c7..405ffb379f 100644
--- a/dotnet/test/Unit/SessionEventSerializationTests.cs
+++ b/dotnet/test/Unit/SessionEventSerializationTests.cs
@@ -9,6 +9,140 @@ namespace GitHub.Copilot.Test.Unit;
public class SessionEventSerializationTests
{
+ public static TheoryData AutoTiers => new()
+ {
+ { AutoTier.Efficiency, "efficiency" },
+ { AutoTier.Balance, "balance" },
+ { AutoTier.Intelligence, "intelligence" },
+ { null, null },
+ };
+
+ [Theory]
+ [MemberData(nameof(AutoTiers))]
+ public void SessionEvent_Deserializes_AutoTier(AutoTier? expectedTier, string? wireTier)
+ {
+ foreach (var eventType in new[] { "session.start", "session.resume" })
+ {
+ var autoTierProperty = wireTier is null ? "" : $""", "autoTier": "{wireTier}" """;
+ var json = $$"""
+ {
+ "id": "11111111-1111-1111-1111-111111111111",
+ "timestamp": "2026-08-28T00:00:00Z",
+ "parentId": null,
+ "type": "{{eventType}}",
+ "data": {
+ "sessionId": "test-session", "version": 1,
+ "producer": "copilot", "copilotVersion": "1.0.82-1",
+ "startTime": "2026-08-28T00:00:00Z",
+ "resumeTime": "2026-08-28T00:00:00Z", "eventCount": 1
+ {{autoTierProperty}}
+ }
+ }
+ """;
+
+ var sessionEvent = SessionEvent.FromJson(json);
+ var actualTier = eventType == "session.start"
+ ? Assert.IsType(sessionEvent).Data.AutoTier
+ : Assert.IsType(sessionEvent).Data.AutoTier;
+ Assert.Equal(expectedTier, actualTier);
+ }
+ }
+
+ [Theory]
+ [InlineData("message-1")]
+ [InlineData(null)]
+ public void UserMessageEvent_MessageId_UsesCamelCaseAndIsOptional(string? messageId)
+ {
+ var messageIdProperty = messageId is null ? "" : $""", "messageId": "{messageId}" """;
+ var json = $$"""
+ {
+ "id": "11111111-1111-1111-1111-111111111111",
+ "timestamp": "2026-08-28T00:00:00Z",
+ "parentId": null,
+ "type": "user.message",
+ "data": {
+ "content": "hello"
+ {{messageIdProperty}}
+ }
+ }
+ """;
+
+ var sessionEvent = Assert.IsType(SessionEvent.FromJson(json));
+ Assert.Equal(messageId, sessionEvent.Data.MessageId);
+
+ using var document = JsonDocument.Parse(sessionEvent.ToJson());
+ var data = document.RootElement.GetProperty("data");
+ if (messageId is null)
+ {
+ Assert.False(data.TryGetProperty("messageId", out _));
+ }
+ else
+ {
+ Assert.Equal(messageId, data.GetProperty("messageId").GetString());
+ }
+ }
+
+ public static TheoryData AutoTierSwitchFailureReasons =>
+ [
+ "policy_rejected",
+ "request_failed",
+ "setup_failed",
+ "unsupported",
+ ];
+
+ [Theory]
+ [MemberData(nameof(AutoTierSwitchFailureReasons))]
+ public void SessionEvent_Deserializes_AutoTierSwitchFailed(string wireReason)
+ {
+ var json = $$"""
+ {
+ "id": "11111111-1111-1111-1111-111111111111",
+ "timestamp": "2026-08-28T00:00:00Z",
+ "parentId": null,
+ "type": "session.auto_tier_switch_failed",
+ "data": {
+ "effectiveAutoTier": "balance",
+ "requestedAutoTier": "intelligence",
+ "reason": "{{wireReason}}"
+ }
+ }
+ """;
+
+ var sessionEvent = SessionEvent.FromJson(json);
+
+ var data = Assert.IsType(sessionEvent).Data;
+ Assert.Equal(new AutoTierSwitchFailureReason(wireReason), data.Reason);
+ Assert.Equal(AutoTier.Balance, data.EffectiveAutoTier);
+ Assert.Equal(AutoTier.Intelligence, data.RequestedAutoTier);
+ }
+
+ [Fact]
+ public void SessionEvent_Deserializes_AutoTierSwitchFailed_WithNullRequestedTier()
+ {
+ // A null requested tier means the attempt to return to provider-default
+ // Auto routing is what failed.
+ var json = """
+ {
+ "id": "11111111-1111-1111-1111-111111111111",
+ "timestamp": "2026-08-28T00:00:00Z",
+ "parentId": null,
+ "type": "session.auto_tier_switch_failed",
+ "data": {
+ "effectiveAutoTier": "efficiency",
+ "requestedAutoTier": null,
+ "reason": "unsupported"
+ }
+ }
+ """;
+
+ var sessionEvent = SessionEvent.FromJson(json);
+
+ var data = Assert.IsType(sessionEvent).Data;
+ Assert.Null(data.RequestedAutoTier);
+ Assert.Equal(AutoTier.Efficiency, data.EffectiveAutoTier);
+ Assert.Equal(AutoTierSwitchFailureReason.Unsupported, data.Reason);
+ }
+
public static TheoryData JsonElementBackedEvents => new()
{
{
diff --git a/go/README.md b/go/README.md
index 6c85f93ae1..1fa8202419 100644
--- a/go/README.md
+++ b/go/README.md
@@ -20,7 +20,10 @@ go get github.com/github/copilot-sdk/go
Try the interactive chat sample (from the repo root):
```bash
-cd go/samples
+cd nodejs
+npm ci
+export COPILOT_CLI_PATH="$(npm run --silent prepare:runtime -- --print-path)"
+cd ../go/samples
go run chat.go
```
@@ -98,6 +101,9 @@ tool name is `-`. For `AvailableTools` and
The SDK supports bundling, using Go's `embed` package, the Copilot CLI binary within your application's distribution.
This allows you to bundle a specific CLI version and avoid external dependencies on the user's system.
+The bundler downloads the matching `github-copilot--.tgz`
+asset from the `github/copilot-cli` release and verifies it against that
+release's `SHA256SUMS.txt`.
Follow these steps to embed the CLI:
@@ -329,6 +335,30 @@ Each section override supports five actions:
Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored.
+## Auto routing tiers
+
+Change the Auto routing preference without changing the selected model. The runtime does not apply the preference immediately: it records the request and commits it only when a later user turn using the `auto` model successfully obtains a usable model from the provider, so a `pending` status confirms acceptance rather than effect. Only the most recent request survives.
+
+Watch for the outcome through the `session.model_change` event on success or the ephemeral `session.auto_tier_switch_failed` event on failure. Read the authoritative committed, pending, and activating preferences at any time through the session's `model.getCurrent` RPC method.
+
+```go
+tier := copilot.AutoTierIntelligence
+result, err := session.SetAutoTier(ctx, &tier)
+if err != nil {
+ return err
+}
+if result.Status == rpc.ModelSwitchAutoTierStatusPending {
+ // Accepted, but not yet in effect.
+}
+
+// Return to the provider's default Auto routing.
+_, err = session.SetAutoTier(ctx, nil)
+```
+
+`SetModel` accepts the same preference through `SetModelOptions.AutoTier`, which stages the tier atomically with selecting `auto`. Set `ResetAutoTier` instead to return to provider-default routing; the two options are mutually exclusive.
+
+See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) for the full lifecycle rules.
+
## Image Support
The SDK supports image attachments via the `Attachments` field in `MessageOptions`. You can attach images by providing their file path, or by passing base64-encoded data directly using a blob attachment:
diff --git a/go/client.go b/go/client.go
index 8f6b41a983..6cac724f0d 100644
--- a/go/client.go
+++ b/go/client.go
@@ -36,6 +36,7 @@ import (
"fmt"
"log"
"net"
+ "net/netip"
"os"
"os/exec"
"path/filepath"
@@ -401,13 +402,31 @@ func setEnvValue(env []string, key string, value string) []string {
// parseCLIURL parses a CLI URL into host and port components.
//
-// Supports formats: "host:port", "http://host:port", "https://host:port", or just "port".
+// Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port".
// Panics if the URL format is invalid or the port is out of range.
func parseCLIURL(url string) (string, int) {
// Remove protocol if present
cleanURL, _ := strings.CutPrefix(url, "https://")
cleanURL, _ = strings.CutPrefix(cleanURL, "http://")
+ // Use the standard parser only for the bracketed IPv6 form. Keep the
+ // existing host:port parsing behavior for all other inputs.
+ if strings.HasPrefix(cleanURL, "[") {
+ host, portStr, err := net.SplitHostPort(cleanURL)
+ if err != nil {
+ panic(fmt.Sprintf("Invalid port in URIConnection: %s", url))
+ }
+ addr, err := netip.ParseAddr(host)
+ if err != nil || !addr.Is6() {
+ panic(fmt.Sprintf("Invalid URIConnection format: %s", url))
+ }
+ port, err := strconv.Atoi(portStr)
+ if err != nil || port <= 0 || port > 65535 {
+ panic(fmt.Sprintf("Invalid port in URIConnection: %s", url))
+ }
+ return host, port
+ }
+
// Parse host:port or port format
var host string
var portStr string
@@ -415,15 +434,13 @@ func parseCLIURL(url string) (string, int) {
host = before
portStr = after
} else {
- // Only port provided
- portStr = before
+ portStr = cleanURL
}
if host == "" {
host = "localhost"
}
- // Validate port
port, err := strconv.Atoi(portStr)
if err != nil || port <= 0 || port > 65535 {
panic(fmt.Sprintf("Invalid port in URIConnection: %s", url))
@@ -684,8 +701,15 @@ func (c *Client) ForceStop() {
// Clear sessions immediately without trying to destroy them
c.sessionsMux.Lock()
+ sessions := make([]*Session, 0, len(c.sessions))
+ for _, session := range c.sessions {
+ sessions = append(sessions, session)
+ }
c.sessions = make(map[string]*Session)
c.sessionsMux.Unlock()
+ for _, session := range sessions {
+ session.cancelPendingExternalTools()
+ }
c.clearGitHubTokenProviders()
c.startStopMux.Lock()
@@ -985,6 +1009,20 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
}
req.SessionID = localSessionID
+ // unregisterSession removes only the session created by this call and stops
+ // its event consumer. The latter is essential on CreateSession error paths:
+ // newSession starts processEvents eagerly, and no caller receives the failed
+ // session to disconnect it.
+ unregisterSession := func(sessionID string, s *Session) {
+ c.sessionsMux.Lock()
+ if c.sessions[sessionID] == s {
+ delete(c.sessions, sessionID)
+ }
+ c.sessionsMux.Unlock()
+ s.cancelPendingExternalTools()
+ s.stopEventProcessing()
+ }
+
// initializeSession creates the session, wires up handlers, and registers
// it in the sessions map. Invoked from the read loop the instant the
// session.create response arrives (synchronously, before the next
@@ -1038,17 +1076,13 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
if c.options.SessionFS != nil {
if config.CreateSessionFSProvider == nil {
- c.sessionsMux.Lock()
- delete(c.sessions, sessionID)
- c.sessionsMux.Unlock()
+ unregisterSession(sessionID, s)
return nil, fmt.Errorf("CreateSessionFSProvider is required in session config when SessionFS is enabled in client options")
}
provider := config.CreateSessionFSProvider(s)
if c.options.SessionFS.Capabilities != nil && c.options.SessionFS.Capabilities.Sqlite {
if _, ok := provider.(SessionFSSqliteProvider); !ok {
- c.sessionsMux.Lock()
- delete(c.sessions, sessionID)
- c.sessionsMux.Unlock()
+ unregisterSession(sessionID, s)
return nil, fmt.Errorf("SessionFS capabilities declare SQLite support but the provider does not implement SessionFSSqliteProvider")
}
}
@@ -1105,9 +1139,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
result, err := c.client.RequestWithInlineResponse(ctx, "session.create", req, inlineCb)
if err != nil {
if registeredSessionID != "" {
- c.sessionsMux.Lock()
- delete(c.sessions, registeredSessionID)
- c.sessionsMux.Unlock()
+ unregisterSession(registeredSessionID, session)
}
return nil, fmt.Errorf("failed to create session: %w", err)
}
@@ -1115,9 +1147,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
var response createSessionResponse
if err := json.Unmarshal(result, &response); err != nil {
if registeredSessionID != "" {
- c.sessionsMux.Lock()
- delete(c.sessions, registeredSessionID)
- c.sessionsMux.Unlock()
+ unregisterSession(registeredSessionID, session)
}
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
@@ -1127,9 +1157,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
}
if localSessionID != "" && response.SessionID != "" && response.SessionID != localSessionID {
- c.sessionsMux.Lock()
- delete(c.sessions, registeredSessionID)
- c.sessionsMux.Unlock()
+ unregisterSession(registeredSessionID, session)
return nil, fmt.Errorf("session.create returned sessionId %s but the caller requested %s", response.SessionID, localSessionID)
}
if config.OnMCPAuthRequest != nil {
@@ -1137,6 +1165,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
"sessionId": session.SessionID,
"eventType": "mcp.oauth_required",
}); err != nil {
+ unregisterSession(registeredSessionID, session)
return nil, err
}
}
@@ -1151,6 +1180,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
ManageScheduleEnabled: config.ManageScheduleEnabled,
IncludedBuiltinSkills: config.IncludedBuiltinSkills,
}); err != nil {
+ unregisterSession(registeredSessionID, session)
return nil, err
}
@@ -1411,6 +1441,12 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
}
}
c.sessionsMux.Unlock()
+ session.cancelPendingExternalTools()
+ // newSession starts processEvents eagerly, before the RPC confirms the
+ // resume; every failure path here restores the previously-registered
+ // session (if any) but never returns this failed one to the caller, so
+ // its event consumer must be stopped here or it leaks forever.
+ session.stopEventProcessing()
}
if c.options.SessionFS != nil {
@@ -1943,7 +1979,14 @@ func (c *Client) verifyProtocolVersion(ctx context.Context) error {
t := c.effectiveConnectionToken
tokenPtr = &t
}
- connectReq := &connectHandshakeRequest{Token: tokenPtr}
+ connectReq := &connectHandshakeRequest{
+ Token: tokenPtr,
+ SupportedTaskKinds: []rpc.TaskKind{
+ rpc.TaskKindAgent,
+ rpc.TaskKindClient,
+ rpc.TaskKindShell,
+ },
+ }
// Opt in to GitHub telemetry forwarding at the connection level when a handler is
// registered (mirrors the runtime, which reads this flag on the `connect` handshake
// so the first session's un-replayable `session.start` event is forwarded). Also
@@ -1951,6 +1994,10 @@ func (c *Client) verifyProtocolVersion(ctx context.Context) error {
if c.options.OnGitHubTelemetry != nil {
connectReq.EnableGitHubTelemetryForwarding = Bool(true)
}
+ // Declare the integrating host's identity so the runtime attributes the
+ // telemetry it emits on this connection to a consistent surface instead of
+ // its own build. Nil when the app didn't supply it.
+ connectReq.ClientInfo = c.options.ClientInfo.toWire()
rawConnectResult, err := c.client.Request(ctx, "connect", connectReq)
if err != nil {
var rpcErr *jsonrpc2.Error
@@ -1987,8 +2034,10 @@ func (c *Client) verifyProtocolVersion(ctx context.Context) error {
}
type connectHandshakeRequest struct {
- Token *string `json:"token,omitempty"`
- EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"`
+ Token *string `json:"token,omitempty"`
+ EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"`
+ ClientInfo *rpc.ConnectClientInfo `json:"clientInfo,omitempty"`
+ SupportedTaskKinds []rpc.TaskKind `json:"supportedTaskKinds,omitempty"`
}
// stderrBufferSize is the maximum number of bytes kept from the CLI process's
@@ -2440,11 +2489,9 @@ func (c *Client) setupNotificationHandler() {
}
if c.options.RequestHandler != nil {
+ llmInference := c.RPC.LlmInference
handlers.LlmInference = newCopilotRequestAdapter(c.options.RequestHandler, func() *rpc.ServerLlmInferenceAPI {
- if c.RPC == nil {
- return nil
- }
- return c.RPC.LlmInference
+ return llmInference
})
}
if c.options.OnGitHubTelemetry != nil {
@@ -2484,6 +2531,15 @@ func (c *Client) clearGitHubTokenProviders() {
func (c *Client) handleConnectionClose() {
c.clearGitHubTokenProviders()
+ c.sessionsMux.Lock()
+ sessions := make([]*Session, 0, len(c.sessions))
+ for _, session := range c.sessions {
+ sessions = append(sessions, session)
+ }
+ c.sessionsMux.Unlock()
+ for _, session := range sessions {
+ session.cancelPendingExternalTools()
+ }
// Avoid deadlocking with Stop/ForceStop, which hold startStopMux while
// waiting for the JSON-RPC read loop to finish.
go func() {
diff --git a/go/client_test.go b/go/client_test.go
index ec3924f0a9..52587c8462 100644
--- a/go/client_test.go
+++ b/go/client_test.go
@@ -78,6 +78,15 @@ func TestClient_URLParsing(t *testing.T) {
}
})
+ t.Run("should parse bracketed IPv6 host:port URL format", func(t *testing.T) {
+ client := NewClient(&ClientOptions{
+ Connection: URIConnection{URL: "[::1]:9000"},
+ })
+ if client.actualPort != 9000 || client.actualHost != "::1" {
+ t.Errorf("Expected [::1]:9000, got %s:%d", client.actualHost, client.actualPort)
+ }
+ })
+
t.Run("should parse http://host:port URL format", func(t *testing.T) {
client := NewClient(&ClientOptions{
Connection: URIConnection{URL: "http://localhost:7000"},
@@ -87,6 +96,24 @@ func TestClient_URLParsing(t *testing.T) {
}
})
+ t.Run("should parse http://[ipv6]:port URL format", func(t *testing.T) {
+ client := NewClient(&ClientOptions{
+ Connection: URIConnection{URL: "http://[::1]:7000"},
+ })
+ if client.actualPort != 7000 || client.actualHost != "::1" {
+ t.Errorf("Expected [::1]:7000, got %s:%d", client.actualHost, client.actualPort)
+ }
+ })
+
+ t.Run("should panic for bracketed non-IPv6 host", func(t *testing.T) {
+ defer func() {
+ if r := recover(); r == nil {
+ t.Error("Expected panic for invalid bracketed host")
+ }
+ }()
+ NewClient(&ClientOptions{Connection: URIConnection{URL: "[not-ipv6]:1234"}})
+ })
+
t.Run("should parse https://host:port URL format", func(t *testing.T) {
client := NewClient(&ClientOptions{
Connection: URIConnection{URL: "https://example.com:443"},
@@ -276,6 +303,106 @@ func TestClient_BuiltinPluginDirectories(t *testing.T) {
})
}
+func TestClient_ClientInfo(t *testing.T) {
+ findConnect := func(requests []startupRPCRequest) map[string]any {
+ t.Helper()
+ for _, request := range requests {
+ if request.Method != "connect" {
+ continue
+ }
+ var params map[string]any
+ if err := json.Unmarshal(request.Params, ¶ms); err != nil {
+ t.Fatalf("decode connect params: %v", err)
+ }
+ return params
+ }
+ t.Fatal("connect was not called")
+ return nil
+ }
+
+ t.Run("forwards a declared identity on the connect handshake", func(t *testing.T) {
+ url, requests, cleanup := newStartupRPCServer(t)
+ defer cleanup()
+
+ client := NewClient(&ClientOptions{
+ Connection: URIConnection{URL: url},
+ ClientInfo: &ClientInfo{
+ ApplicationName: "acme-developer-portal",
+ ApplicationVersion: "2.4.0",
+ IntegrationName: "copilot-assistant",
+ IntegrationVersion: "1.5.0",
+ },
+ })
+ if err := client.Start(t.Context()); err != nil {
+ t.Fatalf("Start failed: %v", err)
+ }
+ defer client.ForceStop()
+
+ params := findConnect(requests())
+ want := map[string]any{
+ "editorName": "acme-developer-portal",
+ "editorVersion": "2.4.0",
+ "extensionName": "copilot-assistant",
+ "extensionVersion": "1.5.0",
+ }
+ if !reflect.DeepEqual(params["clientInfo"], want) {
+ t.Fatalf("clientInfo = %v, want %v", params["clientInfo"], want)
+ }
+ })
+
+ t.Run("omits clientInfo when unset", func(t *testing.T) {
+ url, requests, cleanup := newStartupRPCServer(t)
+ defer cleanup()
+
+ client := NewClient(&ClientOptions{Connection: URIConnection{URL: url}})
+ if err := client.Start(t.Context()); err != nil {
+ t.Fatalf("Start failed: %v", err)
+ }
+ defer client.ForceStop()
+
+ if _, ok := findConnect(requests())["clientInfo"]; ok {
+ t.Fatal("clientInfo should be omitted when unset")
+ }
+ })
+
+ t.Run("omits empty fields", func(t *testing.T) {
+ url, requests, cleanup := newStartupRPCServer(t)
+ defer cleanup()
+
+ client := NewClient(&ClientOptions{
+ Connection: URIConnection{URL: url},
+ ClientInfo: &ClientInfo{ApplicationName: "example-app"},
+ })
+ if err := client.Start(t.Context()); err != nil {
+ t.Fatalf("Start failed: %v", err)
+ }
+ defer client.ForceStop()
+
+ want := map[string]any{"editorName": "example-app"}
+ if got := findConnect(requests())["clientInfo"]; !reflect.DeepEqual(got, want) {
+ t.Fatalf("clientInfo = %v, want %v", got, want)
+ }
+ })
+
+ t.Run("omits clientInfo when all fields empty", func(t *testing.T) {
+ url, requests, cleanup := newStartupRPCServer(t)
+ defer cleanup()
+
+ client := NewClient(&ClientOptions{
+ Connection: URIConnection{URL: url},
+ ClientInfo: &ClientInfo{},
+ })
+ if err := client.Start(t.Context()); err != nil {
+ t.Fatalf("Start failed: %v", err)
+ }
+ defer client.ForceStop()
+
+ if _, ok := findConnect(requests())["clientInfo"]; ok {
+ t.Fatal("clientInfo should be omitted when all fields are empty")
+ }
+ })
+}
+
type startupRPCRequest struct {
Method string
Params json.RawMessage
@@ -414,6 +541,67 @@ func TestClient_ForceStopAndExternalStopDoNotRequestRuntimeShutdown(t *testing.T
externalServer.Stop()
}
+func TestClient_ForceStopCancelsPendingExternalTools(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ session := &Session{
+ pendingExternalTools: map[string]*pendingExternalTool{
+ "request-1": {ctx: ctx, cancel: cancel},
+ },
+ }
+
+ client := &Client{sessions: map[string]*Session{"session-1": session}}
+
+ client.ForceStop()
+
+ if len(client.sessions) != 0 {
+ t.Fatal("ForceStop did not clear sessions")
+ }
+ select {
+ case <-ctx.Done():
+ case <-time.After(time.Second):
+ t.Fatal("ForceStop did not cancel the pending external tool")
+ }
+}
+
+func TestClient_ConnectionCloseCancelsPendingExternalTools(t *testing.T) {
+ rpcClient, server, _ := newRuntimeShutdownRpcPair(t)
+ server.SetRequestHandler("session.detach", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ return []byte(`{"success":true}`), nil
+ })
+ ctx, cancel := context.WithCancel(context.Background())
+ session := newSession("session-1", rpcClient, "", false)
+ session.pendingExternalTools = map[string]*pendingExternalTool{
+ "request-1": {ctx: ctx, cancel: cancel},
+ }
+ client := &Client{
+ client: rpcClient,
+ RPC: rpc.NewServerRPC(rpcClient),
+ sessions: map[string]*Session{"session-1": session},
+ isExternalServer: true,
+ }
+
+ client.handleConnectionClose()
+
+ if len(client.sessions) != 1 {
+ t.Fatal("connection close removed sessions before Stop could clean them up")
+ }
+ select {
+ case <-ctx.Done():
+ case <-time.After(time.Second):
+ t.Fatal("connection close did not cancel the pending external tool")
+ }
+
+ if err := client.Stop(); err != nil {
+ t.Fatalf("Stop failed after connection close: %v", err)
+ }
+ session.toolHandlersM.RLock()
+ defer session.toolHandlersM.RUnlock()
+ if session.toolHandlers != nil {
+ t.Fatal("Stop did not clean up the retained session")
+ }
+ server.Stop()
+}
+
func newRuntimeShutdownRpcPair(t *testing.T) (*jsonrpc2.Client, *jsonrpc2.Client, chan struct{}) {
t.Helper()
@@ -436,42 +624,63 @@ func newRuntimeShutdownRpcPair(t *testing.T) (*jsonrpc2.Client, *jsonrpc2.Client
}
func TestClient_ForwardsCapiOptionsToSessionRequests(t *testing.T) {
- rpcClient, server, _ := newRuntimeShutdownRpcPair(t)
- t.Cleanup(server.Stop)
- client := &Client{
- client: rpcClient,
- RPC: rpc.NewServerRPC(rpcClient),
- sessions: make(map[string]*Session),
- }
+ tests := []struct {
+ name string
+ capi *CapiSessionOptions
+ want map[string]any
+ }{
+ {"omitted", nil, nil},
+ {"empty", &CapiSessionOptions{}, map[string]any{}},
+ {"websocket only", &CapiSessionOptions{EnableWebSocketResponses: Bool(false)}, map[string]any{"enableWebSocketResponses": false}},
+ {"efficiency", &CapiSessionOptions{AutoTier: AutoTierEfficiency}, map[string]any{"autoTier": "efficiency"}},
+ {"balance", &CapiSessionOptions{AutoTier: AutoTierBalance}, map[string]any{"autoTier": "balance"}},
+ {"intelligence", &CapiSessionOptions{AutoTier: AutoTierIntelligence}, map[string]any{"autoTier": "intelligence"}},
+ {"efficiency with websocket", &CapiSessionOptions{AutoTier: AutoTierEfficiency, EnableWebSocketResponses: Bool(false)}, map[string]any{"autoTier": "efficiency", "enableWebSocketResponses": false}},
+ {"balance with websocket", &CapiSessionOptions{AutoTier: AutoTierBalance, EnableWebSocketResponses: Bool(false)}, map[string]any{"autoTier": "balance", "enableWebSocketResponses": false}},
+ {"intelligence with websocket", &CapiSessionOptions{AutoTier: AutoTierIntelligence, EnableWebSocketResponses: Bool(false)}, map[string]any{"autoTier": "intelligence", "enableWebSocketResponses": false}},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rpcClient, server, _ := newRuntimeShutdownRpcPair(t)
+ t.Cleanup(server.Stop)
+ client := &Client{
+ client: rpcClient,
+ RPC: rpc.NewServerRPC(rpcClient),
+ sessions: make(map[string]*Session),
+ }
- createParams := make(chan json.RawMessage, 1)
- server.SetRequestHandler("session.create", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
- createParams <- append(json.RawMessage(nil), params...)
- sessionID := sessionIDFromParams(t, params)
- return []byte(`{"sessionId":"` + sessionID + `","workspacePath":"/workspace"}`), nil
- })
+ createParams := make(chan json.RawMessage, 1)
+ server.SetRequestHandler("session.create", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ createParams <- append(json.RawMessage(nil), params...)
+ sessionID := sessionIDFromParams(t, params)
+ return []byte(`{"sessionId":"` + sessionID + `","workspacePath":"/workspace"}`), nil
+ })
- _, err := client.CreateSession(t.Context(), &SessionConfig{
- Capi: &CapiSessionOptions{EnableWebSocketResponses: Bool(false)},
- })
- if err != nil {
- t.Fatalf("CreateSession failed: %v", err)
- }
- assertCapiEnableWebSocketResponses(t, <-createParams)
+ _, err := client.CreateSession(t.Context(), &SessionConfig{
+ Model: "auto",
+ Capi: tt.capi,
+ })
+ if err != nil {
+ t.Fatalf("CreateSession failed: %v", err)
+ }
+ assertCapiOptions(t, <-createParams, tt.want)
- resumeParams := make(chan json.RawMessage, 1)
- server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
- resumeParams <- append(json.RawMessage(nil), params...)
- return []byte(`{"sessionId":"resumed-capi","workspacePath":"/workspace"}`), nil
- })
+ resumeParams := make(chan json.RawMessage, 1)
+ server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ resumeParams <- append(json.RawMessage(nil), params...)
+ return []byte(`{"sessionId":"resumed-capi","workspacePath":"/workspace"}`), nil
+ })
- _, err = client.ResumeSessionWithOptions(t.Context(), "resumed-capi", &ResumeSessionConfig{
- Capi: &CapiSessionOptions{EnableWebSocketResponses: Bool(false)},
- })
- if err != nil {
- t.Fatalf("ResumeSessionWithOptions failed: %v", err)
+ _, err = client.ResumeSessionWithOptions(t.Context(), "resumed-capi", &ResumeSessionConfig{
+ Model: "auto",
+ Capi: tt.capi,
+ })
+ if err != nil {
+ t.Fatalf("ResumeSessionWithOptions failed: %v", err)
+ }
+ assertCapiOptions(t, <-resumeParams, tt.want)
+ })
}
- assertCapiEnableWebSocketResponses(t, <-resumeParams)
}
func TestClient_ForwardsAskUserVariantToSessionRequests(t *testing.T) {
@@ -728,7 +937,7 @@ func TestClient_ForwardsNewSessionOptionsToSessionRequests(t *testing.T) {
assertNewSessionOptions(t, <-resumeParams, false, false, "task", 15)
}
-func assertCapiEnableWebSocketResponses(t *testing.T, params json.RawMessage) {
+func assertCapiOptions(t *testing.T, params json.RawMessage, want map[string]any) {
t.Helper()
var decoded map[string]any
@@ -736,12 +945,18 @@ func assertCapiEnableWebSocketResponses(t *testing.T, params json.RawMessage) {
t.Fatalf("failed to unmarshal request params: %v", err)
}
+ if want == nil {
+ if _, present := decoded["capi"]; present {
+ t.Fatalf("expected capi to be omitted, got %v", decoded["capi"])
+ }
+ return
+ }
capi, ok := decoded["capi"].(map[string]any)
if !ok {
t.Fatalf("expected capi object in request params, got %T", decoded["capi"])
}
- if capi["enableWebSocketResponses"] != false {
- t.Fatalf("expected capi.enableWebSocketResponses=false, got %v", capi["enableWebSocketResponses"])
+ if !reflect.DeepEqual(capi, want) {
+ t.Fatalf("expected capi %v, got %v", want, capi)
}
}
@@ -797,6 +1012,225 @@ func sessionIDFromParams(t *testing.T, params json.RawMessage) string {
return decoded.SessionID
}
+func TestClient_CreateSessionFailureClosesRegisteredSession(t *testing.T) {
+ tests := []struct {
+ name string
+ response func(string) (json.RawMessage, *jsonrpc2.Error)
+ wantErrSub string
+ }{
+ {
+ name: "RPC failure",
+ response: func(string) (json.RawMessage, *jsonrpc2.Error) {
+ return nil, &jsonrpc2.Error{Code: -32000, Message: "session creation failed"}
+ },
+ wantErrSub: "failed to create session",
+ },
+ {
+ name: "invalid response",
+ response: func(string) (json.RawMessage, *jsonrpc2.Error) {
+ return json.RawMessage(`"invalid"`), nil
+ },
+ wantErrSub: "failed to unmarshal response",
+ },
+ {
+ name: "session ID mismatch",
+ response: func(string) (json.RawMessage, *jsonrpc2.Error) {
+ return json.RawMessage(`{"sessionId":"different-session"}`), nil
+ },
+ wantErrSub: "but the caller requested failed-session",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rpcClient, server, _ := newRuntimeShutdownRpcPair(t)
+ t.Cleanup(server.Stop)
+ client := &Client{
+ client: rpcClient,
+ RPC: rpc.NewServerRPC(rpcClient),
+ sessions: make(map[string]*Session),
+ }
+
+ captured := make(chan *Session, 1)
+ server.SetRequestHandler("session.create", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ sessionID := sessionIDFromParams(t, params)
+ client.sessionsMux.Lock()
+ session := client.sessions[sessionID]
+ client.sessionsMux.Unlock()
+ captured <- session
+ return tt.response(sessionID)
+ })
+
+ _, err := client.CreateSession(t.Context(), &SessionConfig{SessionID: "failed-session"})
+ if err == nil || !strings.Contains(err.Error(), tt.wantErrSub) {
+ t.Fatalf("CreateSession error = %v, want substring %q", err, tt.wantErrSub)
+ }
+
+ session := <-captured
+ if session == nil {
+ t.Fatal("session was not registered before session.create")
+ }
+ assertSessionEventChannelClosed(t, session)
+ assertSessionNotRegistered(t, client, "failed-session")
+ })
+ }
+}
+
+func TestClient_CreateSessionInitializationFailureClosesRegisteredSession(t *testing.T) {
+ rpcClient, server, _ := newRuntimeShutdownRpcPair(t)
+ t.Cleanup(server.Stop)
+ client := &Client{
+ client: rpcClient,
+ RPC: rpc.NewServerRPC(rpcClient),
+ sessions: make(map[string]*Session),
+ options: ClientOptions{SessionFS: &SessionFSConfig{
+ InitialWorkingDirectory: "/",
+ SessionStatePath: "/session-state",
+ Conventions: rpc.SessionFSSetProviderConventionsPosix,
+ Capabilities: &SessionFSCapabilities{Sqlite: true},
+ }},
+ }
+
+ var captured *Session
+ _, err := client.CreateSession(t.Context(), &SessionConfig{
+ SessionID: "failed-session-fs",
+ CreateSessionFSProvider: func(session *Session) SessionFSProvider {
+ captured = session
+ return noSQLiteSessionFSProvider{}
+ },
+ })
+ if err == nil || !strings.Contains(err.Error(), "does not implement SessionFSSqliteProvider") {
+ t.Fatalf("CreateSession error = %v, want SQLite provider validation error", err)
+ }
+ if captured == nil {
+ t.Fatal("CreateSessionFSProvider did not receive the registered session")
+ }
+ assertSessionEventChannelClosed(t, captured)
+ assertSessionNotRegistered(t, client, "failed-session-fs")
+}
+
+func TestClient_ResumeSessionFailureClosesRegisteredSession(t *testing.T) {
+ tests := []struct {
+ name string
+ response func(string) (json.RawMessage, *jsonrpc2.Error)
+ wantErrSub string
+ }{
+ {
+ name: "RPC failure",
+ response: func(string) (json.RawMessage, *jsonrpc2.Error) {
+ return nil, &jsonrpc2.Error{Code: -32000, Message: "session resume failed"}
+ },
+ wantErrSub: "failed to resume session",
+ },
+ {
+ name: "invalid response",
+ response: func(string) (json.RawMessage, *jsonrpc2.Error) {
+ return json.RawMessage(`"invalid"`), nil
+ },
+ wantErrSub: "failed to unmarshal response",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rpcClient, server, _ := newRuntimeShutdownRpcPair(t)
+ t.Cleanup(server.Stop)
+ client := &Client{
+ client: rpcClient,
+ RPC: rpc.NewServerRPC(rpcClient),
+ sessions: make(map[string]*Session),
+ }
+
+ captured := make(chan *Session, 1)
+ server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ sessionID := sessionIDFromParams(t, params)
+ client.sessionsMux.Lock()
+ session := client.sessions[sessionID]
+ client.sessionsMux.Unlock()
+ captured <- session
+ return tt.response(sessionID)
+ })
+
+ _, err := client.ResumeSession(t.Context(), "resumed-session", &ResumeSessionConfig{})
+ if err == nil || !strings.Contains(err.Error(), tt.wantErrSub) {
+ t.Fatalf("ResumeSession error = %v, want substring %q", err, tt.wantErrSub)
+ }
+
+ session := <-captured
+ if session == nil {
+ t.Fatal("session was not registered before session.resume")
+ }
+ assertSessionEventChannelClosed(t, session)
+ assertSessionNotRegistered(t, client, "resumed-session")
+ })
+ }
+}
+
+func TestClient_ResumeSessionInitializationFailureClosesRegisteredSession(t *testing.T) {
+ rpcClient, server, _ := newRuntimeShutdownRpcPair(t)
+ t.Cleanup(server.Stop)
+ client := &Client{
+ client: rpcClient,
+ RPC: rpc.NewServerRPC(rpcClient),
+ sessions: make(map[string]*Session),
+ options: ClientOptions{SessionFS: &SessionFSConfig{
+ InitialWorkingDirectory: "/",
+ SessionStatePath: "/session-state",
+ Conventions: rpc.SessionFSSetProviderConventionsPosix,
+ Capabilities: &SessionFSCapabilities{Sqlite: true},
+ }},
+ }
+
+ var captured *Session
+ _, err := client.ResumeSession(t.Context(), "resumed-session-fs", &ResumeSessionConfig{
+ CreateSessionFSProvider: func(session *Session) SessionFSProvider {
+ captured = session
+ return noSQLiteSessionFSProvider{}
+ },
+ })
+ if err == nil || !strings.Contains(err.Error(), "does not implement SessionFSSqliteProvider") {
+ t.Fatalf("ResumeSession error = %v, want SQLite provider validation error", err)
+ }
+ if captured == nil {
+ t.Fatal("CreateSessionFSProvider did not receive the registered session")
+ }
+ assertSessionEventChannelClosed(t, captured)
+ assertSessionNotRegistered(t, client, "resumed-session-fs")
+}
+
+func assertSessionEventChannelClosed(t *testing.T, session *Session) {
+ t.Helper()
+ select {
+ case <-session.eventDone:
+ case <-time.After(time.Second):
+ t.Fatal("timed out waiting for session event processing to stop")
+ }
+}
+
+func assertSessionNotRegistered(t *testing.T, client *Client, sessionID string) {
+ t.Helper()
+ client.sessionsMux.Lock()
+ defer client.sessionsMux.Unlock()
+ if _, ok := client.sessions[sessionID]; ok {
+ t.Fatalf("session %q is still registered", sessionID)
+ }
+}
+
+type noSQLiteSessionFSProvider struct{}
+
+func (noSQLiteSessionFSProvider) ReadFile(string) (string, error) { return "", nil }
+func (noSQLiteSessionFSProvider) WriteFile(string, string, *int) error { return nil }
+func (noSQLiteSessionFSProvider) AppendFile(string, string, *int) error { return nil }
+func (noSQLiteSessionFSProvider) Exists(string) (bool, error) { return false, nil }
+func (noSQLiteSessionFSProvider) Stat(string) (*SessionFSFileInfo, error) { return nil, nil }
+func (noSQLiteSessionFSProvider) MakeDirectory(string, bool, *int) error { return nil }
+func (noSQLiteSessionFSProvider) ReadDirectory(string) ([]string, error) { return nil, nil }
+func (noSQLiteSessionFSProvider) ReadDirectoryWithTypes(string) ([]rpc.SessionFSReaddirWithTypesEntry, error) {
+ return nil, nil
+}
+func (noSQLiteSessionFSProvider) Remove(string, bool, bool) error { return nil }
+func (noSQLiteSessionFSProvider) Rename(string, string) error { return nil }
+
func assertRuntimeShutdownNotCalled(t *testing.T, shutdownCalled <-chan struct{}) {
t.Helper()
select {
@@ -1199,15 +1633,36 @@ func TestClient_SessionIdleTimeoutSeconds(t *testing.T) {
})
}
-func findCLIPathForTest() string {
- base, err := filepath.Abs("../nodejs/node_modules/@github")
- if err == nil {
- matches, _ := filepath.Glob(filepath.Join(base, "copilot-*", "index.js"))
- if len(matches) > 0 {
- return matches[0]
+func findCLIPathForTest(t *testing.T) string {
+ t.Helper()
+
+ if cliPath := os.Getenv("COPILOT_CLI_PATH"); cliPath != "" {
+ if info, err := os.Stat(cliPath); err == nil && !info.IsDir() {
+ return cliPath
}
+ t.Fatalf("COPILOT_CLI_PATH does not point to a file: %s", cliPath)
+ }
+
+ nodeDir, err := filepath.Abs("../nodejs")
+ if err != nil {
+ t.Fatal(err)
+ }
+ command := exec.Command(
+ "node",
+ "node_modules/tsx/dist/cli.mjs",
+ "scripts/prepare-runtime.ts",
+ "--print-path",
+ )
+ command.Dir = nodeDir
+ output, err := command.CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to prepare pinned Copilot CLI: %v\n%s", err, output)
+ }
+ cliPath := strings.TrimSpace(string(output))
+ if info, err := os.Stat(cliPath); err != nil || info.IsDir() {
+ t.Fatalf("prepared Copilot CLI path is not a file: %q", cliPath)
}
- return ""
+ return cliPath
}
func TestCreateSessionRequest_ClientName(t *testing.T) {
@@ -1922,6 +2377,21 @@ func TestClient_ResumeSession_AllowsMissingPermissionHandler(t *testing.T) {
})
}
+func TestModelInfoPreservesProviderMetadata(t *testing.T) {
+ var model ModelInfo
+ if err := json.Unmarshal([]byte(`{
+ "id":"provider/model",
+ "name":"Provider Model",
+ "capabilities":{"supports":{},"limits":{}},
+ "metadata":{"provider":"acme","contextWindow":200000}
+ }`), &model); err != nil {
+ t.Fatalf("unmarshal model info: %v", err)
+ }
+ if model.Metadata["provider"] != "acme" || model.Metadata["contextWindow"] != float64(200000) {
+ t.Fatalf("metadata = %#v", model.Metadata)
+ }
+}
+
func TestListModelsWithCustomHandler(t *testing.T) {
customModels := []ModelInfo{
{
@@ -2054,10 +2524,7 @@ func TestListModelsHandlerCachesResults(t *testing.T) {
}
func TestClient_StartContextCancellationDoesNotKillProcess(t *testing.T) {
- cliPath := findCLIPathForTest()
- if cliPath == "" {
- t.Skip("CLI not found")
- }
+ cliPath := findCLIPathForTest(t)
client := NewClient(&ClientOptions{Connection: StdioConnection{Path: cliPath}})
t.Cleanup(func() { client.ForceStop() })
@@ -2080,10 +2547,7 @@ func TestClient_StartContextCancellationDoesNotKillProcess(t *testing.T) {
}
func TestClient_StartStopRace(t *testing.T) {
- cliPath := findCLIPathForTest()
- if cliPath == "" {
- t.Skip("CLI not found")
- }
+ cliPath := findCLIPathForTest(t)
client := NewClient(&ClientOptions{Connection: StdioConnection{Path: cliPath}})
defer client.ForceStop()
errChan := make(chan error)
@@ -2514,8 +2978,10 @@ func serveInMemoryRuntime(t *testing.T, stdinR *io.PipeReader, stdoutW *io.PipeW
result = map[string]any{"id": "interest-1"}
case "session.options.update":
result = map[string]any{"success": true}
- case "session.skills.reload", "session.destroy":
+ case "session.skills.reload":
result = map[string]any{}
+ case "session.detach":
+ result = map[string]any{"success": true}
default:
t.Errorf("unexpected JSON-RPC method %s", request.Method)
return
@@ -3528,7 +3994,9 @@ func TestClient_ForwardsGitHubTelemetryForwardingOnConnect(t *testing.T) {
if err := client.verifyProtocolVersion(t.Context()); err != nil {
t.Fatalf("verifyProtocolVersion failed: %v", err)
}
- assertForwardingFlagTrue(t, <-connectParams)
+ params := <-connectParams
+ assertForwardingFlagTrue(t, params)
+ assertSupportedTaskKinds(t, params)
}
func TestClient_OmitsGitHubTelemetryForwardingOnConnectWhenNoHandler(t *testing.T) {
@@ -3554,6 +4022,20 @@ func TestClient_OmitsGitHubTelemetryForwardingOnConnectWhenNoHandler(t *testing.
assertForwardingFlagAbsent(t, <-connectParams)
}
+func assertSupportedTaskKinds(t *testing.T, params json.RawMessage) {
+ t.Helper()
+ var decoded struct {
+ SupportedTaskKinds []rpc.TaskKind `json:"supportedTaskKinds"`
+ }
+ if err := json.Unmarshal(params, &decoded); err != nil {
+ t.Fatalf("unmarshal connect params: %v", err)
+ }
+ expected := []rpc.TaskKind{rpc.TaskKindAgent, rpc.TaskKindClient, rpc.TaskKindShell}
+ if !reflect.DeepEqual(decoded.SupportedTaskKinds, expected) {
+ t.Fatalf("supportedTaskKinds = %v, want %v", decoded.SupportedTaskKinds, expected)
+ }
+}
+
func TestGitHubTelemetryNotificationRoutesToCallback(t *testing.T) {
// The runtime forwards telemetry via a JSON-RPC *notification* (no id).
// Drive a real Content-Length-framed notification through the transport and
diff --git a/go/cmd/bundler/main.go b/go/cmd/bundler/main.go
index 89f99daf1b..41b5863347 100644
--- a/go/cmd/bundler/main.go
+++ b/go/cmd/bundler/main.go
@@ -8,7 +8,7 @@
// --platform: Target platform using Go conventions (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, windows/arm64). Defaults to current platform.
// --output: Output directory for embedded artifacts. Defaults to the current directory.
// --cli-version: CLI version to download. If not specified, automatically detects from the copilot-sdk version in go.mod.
-// --check-only: Check that embedded CLI version matches the detected version from package-lock.json without downloading. Exits with error if versions don't match.
+// --check-only: Check that embedded CLI version matches the detected version from package.json without downloading. Exits with error if versions don't match.
package main
import (
@@ -30,33 +30,35 @@ import (
"regexp"
"runtime"
"strings"
+ "time"
"github.com/klauspost/compress/zstd"
)
const (
// Keep these URLs centralized so reviewers can verify all outbound calls in one place.
- sdkModule = "github.com/github/copilot-sdk/go"
- packageLockURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package-lock.json"
- tarballURLFmt = "https://registry.npmjs.org/@github/copilot-%s/-/copilot-%s-%s.tgz"
- licenseTarballFmt = "https://registry.npmjs.org/@github/copilot/-/copilot-%s.tgz"
- defaultPackageName = "main"
+ sdkModule = "github.com/github/copilot-sdk/go"
+ packageJSONURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package.json"
+ packageLockURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package-lock.json"
+ defaultCLIDownloadBaseURL = "https://github.com/github/copilot-cli/releases/download"
+ cliDownloadBaseURLEnvironment = "COPILOT_CLI_DOWNLOAD_BASE_URL"
+ defaultPackageName = "main"
)
-// Platform info: npm package suffix, binary name
+// Platform info: release asset platform suffix, binary name
type platformInfo struct {
- npmPlatform string
- binaryName string
+ runtimePlatform string
+ binaryName string
}
-// Map from GOOS/GOARCH to npm platform info
+// Map from GOOS/GOARCH to release asset platform info.
var platforms = map[string]platformInfo{
- "linux/amd64": {npmPlatform: "linux-x64", binaryName: "copilot"},
- "linux/arm64": {npmPlatform: "linux-arm64", binaryName: "copilot"},
- "darwin/amd64": {npmPlatform: "darwin-x64", binaryName: "copilot"},
- "darwin/arm64": {npmPlatform: "darwin-arm64", binaryName: "copilot"},
- "windows/amd64": {npmPlatform: "win32-x64", binaryName: "copilot.exe"},
- "windows/arm64": {npmPlatform: "win32-arm64", binaryName: "copilot.exe"},
+ "linux/amd64": {runtimePlatform: "linux-x64", binaryName: "copilot"},
+ "linux/arm64": {runtimePlatform: "linux-arm64", binaryName: "copilot"},
+ "darwin/amd64": {runtimePlatform: "darwin-x64", binaryName: "copilot"},
+ "darwin/arm64": {runtimePlatform: "darwin-arm64", binaryName: "copilot"},
+ "windows/amd64": {runtimePlatform: "win32-x64", binaryName: "copilot.exe"},
+ "windows/arm64": {runtimePlatform: "win32-arm64", binaryName: "copilot.exe"},
}
// main is the CLI entry point.
@@ -69,7 +71,7 @@ func main() {
// Resolve version first so the default output name can include it.
version := resolveCLIVersion(*cliVersion)
- // Resolve platform once to validate input and get the npm package mapping.
+ // Resolve platform once to validate input and get the release asset mapping.
goos, goarch, info, err := resolvePlatform(*platform)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -100,7 +102,7 @@ func main() {
fmt.Printf("Building bundle for %s (CLI version %s)\n", *platform, version)
- bundle, err := buildBundle(info, version, outputPath, goos)
+ bundle, err := buildBundle(info, version, outputPath, goos, true)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
@@ -109,8 +111,8 @@ func main() {
var muslBundle bundleArtifacts
if goos == "linux" {
muslInfo := platformInfo{
- npmPlatform: strings.Replace(info.npmPlatform, "linux-", "linuxmusl-", 1),
- binaryName: info.binaryName,
+ runtimePlatform: strings.Replace(info.runtimePlatform, "linux-", "linuxmusl-", 1),
+ binaryName: info.binaryName,
}
muslOutputPath := filepath.Join(*output, defaultOutputFileName(version, "linuxmusl", goarch, info.binaryName))
muslBundle, err = buildBundle(
@@ -118,17 +120,13 @@ func main() {
version,
muslOutputPath,
goos,
+ false,
)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
- if err := downloadCLILicense(version, outputPath); err != nil {
- fmt.Fprintf(os.Stderr, "Error: failed to download CLI license: %v\n", err)
- os.Exit(1)
- }
-
// Generate the Go file with embed directive
if err := generateGoFile(
goos,
@@ -262,8 +260,8 @@ func detectPackageName(dir, goos, goarch string) (string, error) {
// detectCLIVersion detects the CLI version by:
// 1. Running "go list -m" to get the copilot-sdk version from the user's go.mod
-// 2. Fetching the package-lock.json from the SDK repo at that version
-// 3. Extracting the @github/copilot CLI version from it
+// 2. Fetching package.json from the SDK repo at that version
+// 3. Extracting the pinned Copilot CLI version from it
func detectCLIVersion() (string, error) {
// Get the SDK version from the user's go.mod
sdkVersion, err := getSDKVersion()
@@ -273,7 +271,7 @@ func detectCLIVersion() (string, error) {
fmt.Printf("Found copilot-sdk %s in go.mod\n", sdkVersion)
- // Fetch package-lock.json from the SDK repo at that version
+ // Fetch package.json from the SDK repo at that version
cliVersion, err := fetchCLIVersionFromRepo(sdkVersion)
if err != nil {
return "", fmt.Errorf("failed to fetch CLI version: %w", err)
@@ -301,7 +299,7 @@ func getSDKVersion() (string, error) {
return version, nil
}
-// fetchCLIVersionFromRepo fetches package-lock.json from GitHub and extracts the CLI version.
+// fetchCLIVersionFromRepo fetches package.json from GitHub and extracts the CLI version.
func fetchCLIVersionFromRepo(sdkVersion string) (string, error) {
// Convert Go module version to Git ref
// v0.1.0 -> v0.1.0
@@ -319,7 +317,7 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) {
}
}
- url := fmt.Sprintf(packageLockURLFmt, gitRef)
+ url := fmt.Sprintf(packageJSONURLFmt, gitRef)
fmt.Printf("Fetching %s...\n", url)
resp, err := http.Get(url)
@@ -329,7 +327,35 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) {
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
- return "", fmt.Errorf("failed to fetch package-lock.json: %s", resp.Status)
+ return "", fmt.Errorf("failed to fetch package.json: %s", resp.Status)
+ }
+
+ var packageJSON struct {
+ CopilotCLIVersion string `json:"copilotCliVersion"`
+ }
+
+ if err := json.NewDecoder(resp.Body).Decode(&packageJSON); err != nil {
+ return "", fmt.Errorf("failed to parse package.json: %w", err)
+ }
+
+ if packageJSON.CopilotCLIVersion == "" {
+ return fetchLegacyCLIVersionFromRepo(gitRef)
+ }
+
+ return packageJSON.CopilotCLIVersion, nil
+}
+
+func fetchLegacyCLIVersionFromRepo(gitRef string) (string, error) {
+ url := fmt.Sprintf(packageLockURLFmt, gitRef)
+ fmt.Printf("Falling back to %s...\n", url)
+
+ resp, err := http.Get(url)
+ if err != nil {
+ return "", fmt.Errorf("failed to fetch legacy package-lock.json: %w", err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return "", fmt.Errorf("failed to fetch legacy package-lock.json: %s", resp.Status)
}
var packageLock struct {
@@ -337,16 +363,13 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) {
Version string `json:"version"`
} `json:"packages"`
}
-
if err := json.NewDecoder(resp.Body).Decode(&packageLock); err != nil {
- return "", fmt.Errorf("failed to parse package-lock.json: %w", err)
+ return "", fmt.Errorf("failed to parse legacy package-lock.json: %w", err)
}
-
pkg, ok := packageLock.Packages["node_modules/@github/copilot"]
if !ok || pkg.Version == "" {
- return "", fmt.Errorf("could not find @github/copilot version in package-lock.json")
+ return "", fmt.Errorf("could not find copilotCliVersion in package.json or @github/copilot in package-lock.json")
}
-
return pkg.Version, nil
}
@@ -371,19 +394,23 @@ type bundleArtifacts struct {
assetsHash []byte
}
-// buildBundle downloads the CLI and native runtime artifacts from one platform package.
-func buildBundle(info platformInfo, cliVersion, outputPath, goos string) (bundleArtifacts, error) {
+// buildBundle downloads the CLI and native runtime artifacts from one release package.
+func buildBundle(info platformInfo, cliVersion, outputPath, goos string, includeLicense bool) (bundleArtifacts, error) {
outputDir := filepath.Dir(outputPath)
if outputDir == "" {
outputDir = "."
}
- runtimeArtifactPath := filepath.Join(outputDir, runtimeLibArtifactName(cliVersion, info.npmPlatform, goos))
- wrapperArtifactPath := filepath.Join(outputDir, runtimeWrapperArtifactName(cliVersion, info.npmPlatform, info.binaryName))
- assetsArtifactPath := filepath.Join(outputDir, runtimeAssetsArtifactName(cliVersion, info.npmPlatform))
+ runtimeArtifactPath := filepath.Join(outputDir, runtimeLibArtifactName(cliVersion, info.runtimePlatform, goos))
+ wrapperArtifactPath := filepath.Join(outputDir, runtimeWrapperArtifactName(cliVersion, info.runtimePlatform, info.binaryName))
+ assetsArtifactPath := filepath.Join(outputDir, runtimeAssetsArtifactName(cliVersion, info.runtimePlatform))
+ requiredPaths := []string{outputPath, runtimeArtifactPath, wrapperArtifactPath, assetsArtifactPath}
+ if includeLicense {
+ requiredPaths = append(requiredPaths, licensePathForOutput(outputPath))
+ }
- if filesExist(outputPath, runtimeArtifactPath, wrapperArtifactPath, assetsArtifactPath) {
+ if filesExist(requiredPaths...) {
// Idempotent output avoids re-downloading in CI or local rebuilds.
- fmt.Printf("Output runtime bundle for %s already exists, skipping download\n", info.npmPlatform)
+ fmt.Printf("Output runtime bundle for %s already exists, skipping download\n", info.runtimePlatform)
binaryHash, err := sha256FileFromCompressed(outputPath)
if err != nil {
return bundleArtifacts{}, fmt.Errorf("failed to hash existing output: %w", err)
@@ -410,7 +437,7 @@ func buildBundle(info platformInfo, cliVersion, outputPath, goos string) (bundle
}
defer os.RemoveAll(tempDir)
- binaryPath, tarballPath, err := downloadCLIBinary(info.npmPlatform, info.binaryName, cliVersion, tempDir)
+ binaryPath, tarballPath, err := downloadCLIBinary(info.runtimePlatform, info.binaryName, cliVersion, tempDir)
if err != nil {
return bundleArtifacts{}, fmt.Errorf("failed to download CLI binary: %w", err)
}
@@ -420,6 +447,11 @@ func buildBundle(info platformInfo, cliVersion, outputPath, goos string) (bundle
return bundleArtifacts{}, fmt.Errorf("failed to create output directory: %w", err)
}
}
+ if includeLicense {
+ if err := extractCLILicense(tarballPath, outputPath); err != nil {
+ return bundleArtifacts{}, fmt.Errorf("failed to extract CLI license: %w", err)
+ }
+ }
binaryHash, err := sha256File(binaryPath)
if err != nil {
@@ -433,10 +465,10 @@ func buildBundle(info platformInfo, cliVersion, outputPath, goos string) (bundle
if err := extractFileFromTarball(
tarballPath,
tempDir,
- "package/prebuilds/"+info.npmPlatform+"/runtime.node",
+ "package/prebuilds/"+info.runtimePlatform+"/runtime.node",
"runtime.node",
); err != nil {
- return bundleArtifacts{}, fmt.Errorf("runtime package is missing prebuilds/%s/runtime.node: %w", info.npmPlatform, err)
+ return bundleArtifacts{}, fmt.Errorf("runtime package is missing prebuilds/%s/runtime.node: %w", info.runtimePlatform, err)
}
runtimeHash, err := sha256File(rawLibPath)
if err != nil {
@@ -451,10 +483,10 @@ func buildBundle(info platformInfo, cliVersion, outputPath, goos string) (bundle
if err := extractFileFromTarball(
tarballPath,
tempDir,
- "package/prebuilds/"+info.npmPlatform+"/"+wrapperName,
+ "package/prebuilds/"+info.runtimePlatform+"/"+wrapperName,
wrapperName,
); err != nil {
- return bundleArtifacts{}, fmt.Errorf("runtime package is missing prebuilds/%s/%s: %w", info.npmPlatform, wrapperName, err)
+ return bundleArtifacts{}, fmt.Errorf("runtime package is missing prebuilds/%s/%s: %w", info.runtimePlatform, wrapperName, err)
}
wrapperHash, err := sha256File(rawWrapperPath)
if err != nil {
@@ -488,16 +520,16 @@ func filesExist(paths ...string) bool {
}
// runtimeLibArtifactName builds the compressed runtime-library artifact filename.
-func runtimeLibArtifactName(version, npmPlatform, goos string) string {
- return fmt.Sprintf("zcopilotruntime_%s_%s.%s.zst", version, npmPlatform, runtimeLibExt(goos))
+func runtimeLibArtifactName(version, runtimePlatform, goos string) string {
+ return fmt.Sprintf("zcopilotruntime_%s_%s.%s.zst", version, runtimePlatform, runtimeLibExt(goos))
}
-func runtimeWrapperArtifactName(version, npmPlatform, binaryName string) string {
- return fmt.Sprintf("zcopilotruntimewrapper_%s_%s_%s.zst", version, npmPlatform, runtimeWrapperName(binaryName))
+func runtimeWrapperArtifactName(version, runtimePlatform, binaryName string) string {
+ return fmt.Sprintf("zcopilotruntimewrapper_%s_%s_%s.zst", version, runtimePlatform, runtimeWrapperName(binaryName))
}
-func runtimeAssetsArtifactName(version, npmPlatform string) string {
- return fmt.Sprintf("zcopilotruntimeassets_%s_%s.tgz", version, npmPlatform)
+func runtimeAssetsArtifactName(version, runtimePlatform string) string {
+ return fmt.Sprintf("zcopilotruntimeassets_%s_%s.tgz", version, runtimePlatform)
}
func runtimeWrapperName(binaryName string) string {
@@ -509,12 +541,12 @@ func runtimeWrapperName(binaryName string) string {
var hostlessExcludedTopLevel = map[string]bool{
"app.js": true, "assets": true, "changelog.json": true, "copilot": true, "copilot.exe": true,
- "copilot-sdk": true, "foundry-local-sdk": true, "index.js": true, "napi-oop-runtime": true,
- "LICENSE.md": true, "npm-loader.js": true, "package.json": true, "preloads": true, "pvrecorder": true,
- "queries": true, "README.md": true, "sdk": true, "sea-loader.js": true, "webview": true,
+ "foundry-local-sdk": true, "index.js": true, "napi-oop-runtime": true, "LICENSE.md": true,
+ "npm-loader.js": true, "package.json": true, "pvrecorder": true, "queries": true, "README.md": true,
+ "sea-loader.js": true, "webview": true,
}
-func hostlessRuntimePath(name, npmPlatform, wrapperName string) (string, bool) {
+func hostlessRuntimePath(name, runtimePlatform, wrapperName string) (string, bool) {
relative, ok := strings.CutPrefix(name, "package/")
if !ok {
return "", false
@@ -535,7 +567,7 @@ func hostlessRuntimePath(name, npmPlatform, wrapperName string) (string, bool) {
}
}
if topLevel == "prebuilds" {
- if len(parts) < 3 || parts[1] != npmPlatform {
+ if len(parts) < 3 || parts[1] != runtimePlatform {
return "", false
}
return strings.Join(parts[2:], "/"), true
@@ -576,7 +608,7 @@ func createRuntimeAssetsArchive(tarballPath, outputPath string, info platformInf
}
destination, include := hostlessRuntimePath(
header.Name,
- info.npmPlatform,
+ info.runtimePlatform,
runtimeWrapperName(info.binaryName),
)
if !include {
@@ -892,15 +924,82 @@ func mustDecodeBase64(s string) []byte {
`, buildConstraint, pkgName, binaryName, licenseName, runtimeEmbed, muslEmbed, cliVersion, hashBase64, runtimeConfig, muslConfig, runtimeReader, muslReaders)
}
-// downloadCLIBinary downloads the npm tarball and extracts the CLI binary. It
+var (
+ releaseChecksumCache = map[string]map[string]string{}
+ releaseHTTPClient = &http.Client{Timeout: 10 * time.Minute}
+)
+
+func cliDownloadBaseURL() string {
+ if override := strings.TrimRight(os.Getenv(cliDownloadBaseURLEnvironment), "/"); override != "" {
+ return override
+ }
+ return defaultCLIDownloadBaseURL
+}
+
+func releaseAssetName(version, runtimePlatform string) string {
+ return fmt.Sprintf("github-copilot-%s-%s.tgz", version, runtimePlatform)
+}
+
+func releaseDownloadURL(version, assetName string) string {
+ return fmt.Sprintf("%s/v%s/%s", cliDownloadBaseURL(), version, assetName)
+}
+
+func parseReleaseChecksums(contents string) map[string]string {
+ checksums := make(map[string]string)
+ hashPattern := regexp.MustCompile(`^[0-9a-fA-F]{64}$`)
+ for _, line := range strings.Split(contents, "\n") {
+ fields := strings.Fields(line)
+ if len(fields) != 2 || !hashPattern.MatchString(fields[0]) {
+ continue
+ }
+ checksums[strings.TrimPrefix(fields[1], "*")] = strings.ToLower(fields[0])
+ }
+ return checksums
+}
+
+func getReleaseChecksum(version, assetName string) (string, error) {
+ baseURL := cliDownloadBaseURL()
+ cacheKey := baseURL + "\x00" + version
+ checksums, ok := releaseChecksumCache[cacheKey]
+ if !ok {
+ checksumsURL := fmt.Sprintf("%s/v%s/SHA256SUMS.txt", baseURL, version)
+ fmt.Printf("Downloading checksums from %s...\n", checksumsURL)
+ resp, err := releaseHTTPClient.Get(checksumsURL)
+ if err != nil {
+ return "", fmt.Errorf("failed to download checksums: %w", err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return "", fmt.Errorf("failed to download checksums: %s", resp.Status)
+ }
+ contents, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return "", fmt.Errorf("failed to read checksums: %w", err)
+ }
+ checksums = parseReleaseChecksums(string(contents))
+ releaseChecksumCache[cacheKey] = checksums
+ }
+ checksum, ok := checksums[assetName]
+ if !ok {
+ return "", fmt.Errorf("SHA256SUMS.txt does not contain %s", assetName)
+ }
+ return checksum, nil
+}
+
+// downloadCLIBinary downloads the verified release package and extracts the CLI binary. It
// returns the extracted binary path and the downloaded tarball path (retained so
// callers can extract additional files, such as the runtime library).
-func downloadCLIBinary(npmPlatform, binaryName, cliVersion, destDir string) (string, string, error) {
- tarballURL := fmt.Sprintf(tarballURLFmt, npmPlatform, npmPlatform, cliVersion)
+func downloadCLIBinary(runtimePlatform, binaryName, cliVersion, destDir string) (string, string, error) {
+ assetName := releaseAssetName(cliVersion, runtimePlatform)
+ expectedChecksum, err := getReleaseChecksum(cliVersion, assetName)
+ if err != nil {
+ return "", "", err
+ }
+ tarballURL := releaseDownloadURL(cliVersion, assetName)
fmt.Printf("Downloading from %s...\n", tarballURL)
- resp, err := http.Get(tarballURL)
+ resp, err := releaseHTTPClient.Get(tarballURL)
if err != nil {
return "", "", fmt.Errorf("failed to download: %w", err)
}
@@ -911,24 +1010,43 @@ func downloadCLIBinary(npmPlatform, binaryName, cliVersion, destDir string) (str
}
// Save tarball to temp file
- tarballPath := filepath.Join(destDir, fmt.Sprintf("copilot-%s-%s.tgz", npmPlatform, cliVersion))
+ tarballPath := filepath.Join(destDir, assetName)
tarballFile, err := os.Create(tarballPath)
if err != nil {
return "", "", fmt.Errorf("failed to create tarball file: %w", err)
}
- if _, err := io.Copy(tarballFile, resp.Body); err != nil {
+ hasher := sha256.New()
+ if _, err := io.Copy(io.MultiWriter(tarballFile, hasher), resp.Body); err != nil {
tarballFile.Close()
return "", "", fmt.Errorf("failed to save tarball: %w", err)
}
if err := tarballFile.Close(); err != nil {
return "", "", fmt.Errorf("failed to close tarball file: %w", err)
}
+ actualChecksum := fmt.Sprintf("%x", hasher.Sum(nil))
+ if actualChecksum != expectedChecksum {
+ return "", "", fmt.Errorf(
+ "checksum mismatch for %s: expected %s, got %s",
+ assetName,
+ expectedChecksum,
+ actualChecksum,
+ )
+ }
- // Extract only the CLI binary to avoid unpacking the full package tree.
+ // The SDK release package intentionally omits the legacy SEA binary. Preserve
+ // embeddedcli.Path compatibility by installing the runtime wrapper under the
+ // historical copilot[.exe] name; the normal client path uses the adjacent
+ // wrapper/runtime.node pair directly.
binaryPath := filepath.Join(destDir, binaryName)
- if err := extractFileFromTarball(tarballPath, destDir, "package/"+binaryName, binaryName); err != nil {
- return "", "", fmt.Errorf("failed to extract binary: %w", err)
+ wrapperName := runtimeWrapperName(binaryName)
+ if err := extractFileFromTarball(
+ tarballPath,
+ destDir,
+ "package/prebuilds/"+runtimePlatform+"/"+wrapperName,
+ binaryName,
+ ); err != nil {
+ return "", "", fmt.Errorf("failed to extract runtime wrapper compatibility entrypoint: %w", err)
}
// Verify binary exists
@@ -953,8 +1071,8 @@ func downloadCLIBinary(npmPlatform, binaryName, cliVersion, destDir string) (str
return binaryPath, tarballPath, nil
}
-// downloadCLILicense downloads the @github/copilot package and writes its license next to outputPath.
-func downloadCLILicense(cliVersion, outputPath string) error {
+// extractCLILicense writes the license from the verified release package next to outputPath.
+func extractCLILicense(tarballPath, outputPath string) error {
outputDir := filepath.Dir(outputPath)
if outputDir == "" {
outputDir = "."
@@ -964,18 +1082,13 @@ func downloadCLILicense(cliVersion, outputPath string) error {
return nil
}
- licenseURL := fmt.Sprintf(licenseTarballFmt, cliVersion)
- resp, err := http.Get(licenseURL)
+ source, err := os.Open(tarballPath)
if err != nil {
- return fmt.Errorf("failed to download license tarball: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("failed to download license tarball: %s", resp.Status)
+ return fmt.Errorf("failed to open release package: %w", err)
}
+ defer source.Close()
- gzReader, err := gzip.NewReader(resp.Body)
+ gzReader, err := gzip.NewReader(source)
if err != nil {
return fmt.Errorf("failed to create gzip reader: %w", err)
}
diff --git a/go/cmd/bundler/main_test.go b/go/cmd/bundler/main_test.go
index f41afe43f5..7dd376d40b 100644
--- a/go/cmd/bundler/main_test.go
+++ b/go/cmd/bundler/main_test.go
@@ -4,9 +4,13 @@ import (
"archive/tar"
"bytes"
"compress/gzip"
+ "crypto/sha256"
+ "fmt"
"go/parser"
"go/token"
"io"
+ "net/http"
+ "net/http/httptest"
"os"
"path/filepath"
"strings"
@@ -22,20 +26,27 @@ func TestCreateRuntimeAssetsArchiveRetainsUnknownAssetsAndFiltersCLIContent(t *t
"package/prebuilds/linux-x64/copilot-runtime": "wrapper",
"package/ripgrep/bin/linux-x64/rg": "ripgrep",
"package/definitions/future.json": "{}",
+ "package/copilot-sdk/extension.js": "extension",
+ "package/preloads/extension_bootstrap.mjs": "preload",
+ "package/sdk/factory.js": "factory",
"package/app.js": "excluded",
"package/LICENSE.md": "excluded",
"package/README.md": "excluded",
})
if err := createRuntimeAssetsArchive(source, output, platformInfo{
- npmPlatform: "linux-x64",
- binaryName: "copilot",
+ runtimePlatform: "linux-x64",
+ binaryName: "copilot",
}); err != nil {
t.Fatal(err)
}
files := readTarGz(t, output)
- if files["ripgrep/bin/linux-x64/rg"] != "ripgrep" || files["definitions/future.json"] != "{}" {
+ if files["ripgrep/bin/linux-x64/rg"] != "ripgrep" ||
+ files["definitions/future.json"] != "{}" ||
+ files["copilot-sdk/extension.js"] != "extension" ||
+ files["preloads/extension_bootstrap.mjs"] != "preload" ||
+ files["sdk/factory.js"] != "factory" {
t.Fatalf("retained assets = %#v", files)
}
for _, excluded := range []string{
@@ -47,6 +58,93 @@ func TestCreateRuntimeAssetsArchiveRetainsUnknownAssetsAndFiltersCLIContent(t *t
}
}
+func TestDownloadCLIBinaryUsesVerifiedReleasePackage(t *testing.T) {
+ dir := t.TempDir()
+ archivePath := filepath.Join(dir, "source.tgz")
+ writeTarGz(t, archivePath, map[string]string{
+ "package/prebuilds/linux-x64/copilot-runtime": "runtime wrapper",
+ })
+ archive, err := os.ReadFile(archivePath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ checksum := fmt.Sprintf("%x", sha256.Sum256(archive))
+ version := "1.2.3"
+ assetName := releaseAssetName(version, "linux-x64")
+ server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+ switch request.URL.Path {
+ case "/v1.2.3/SHA256SUMS.txt":
+ fmt.Fprintf(writer, "%s %s\n", checksum, assetName)
+ case "/v1.2.3/" + assetName:
+ writer.Write(archive)
+ default:
+ http.NotFound(writer, request)
+ }
+ }))
+ defer server.Close()
+ t.Setenv(cliDownloadBaseURLEnvironment, server.URL)
+ releaseChecksumCache = map[string]map[string]string{}
+
+ binaryPath, downloadedArchive, err := downloadCLIBinary(
+ "linux-x64",
+ "copilot",
+ version,
+ t.TempDir(),
+ )
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, err := os.ReadFile(binaryPath); err != nil || string(got) != "runtime wrapper" {
+ t.Fatalf("downloaded CLI = %q, %v", got, err)
+ }
+ if filepath.Base(downloadedArchive) != assetName {
+ t.Fatalf("downloaded archive = %q, want basename %q", downloadedArchive, assetName)
+ }
+}
+
+func TestDownloadCLIBinaryRejectsChecksumMismatch(t *testing.T) {
+ dir := t.TempDir()
+ archivePath := filepath.Join(dir, "source.tgz")
+ writeTarGz(t, archivePath, map[string]string{
+ "package/prebuilds/linux-x64/copilot-runtime": "runtime wrapper",
+ })
+ archive, err := os.ReadFile(archivePath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ version := "1.2.3"
+ assetName := releaseAssetName(version, "linux-x64")
+ server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+ switch request.URL.Path {
+ case "/v1.2.3/SHA256SUMS.txt":
+ fmt.Fprintf(writer, "%s %s\n", strings.Repeat("0", 64), assetName)
+ case "/v1.2.3/" + assetName:
+ _, _ = writer.Write(archive)
+ default:
+ http.NotFound(writer, request)
+ }
+ }))
+ defer server.Close()
+ t.Setenv(cliDownloadBaseURLEnvironment, server.URL)
+ releaseChecksumCache = map[string]map[string]string{}
+
+ _, _, err = downloadCLIBinary("linux-x64", "copilot", version, t.TempDir())
+ if err == nil || !strings.Contains(err.Error(), "checksum mismatch") {
+ t.Fatalf("downloadCLIBinary() error = %v, want checksum mismatch", err)
+ }
+}
+
+func TestParseReleaseChecksums(t *testing.T) {
+ hash := strings.Repeat("a", 64)
+ checksums := parseReleaseChecksums(
+ "invalid\n" +
+ strings.ToUpper(hash) + " *github-copilot-1.2.3-linux-x64.tgz\n",
+ )
+ if got := checksums["github-copilot-1.2.3-linux-x64.tgz"]; got != hash {
+ t.Fatalf("checksum = %q, want %q", got, hash)
+ }
+}
+
func writeTarGz(t *testing.T, path string, files map[string]string) {
t.Helper()
var buffer bytes.Buffer
diff --git a/go/github_token_provider_test.go b/go/github_token_provider_test.go
index f00837379f..51e8f3f7f0 100644
--- a/go/github_token_provider_test.go
+++ b/go/github_token_provider_test.go
@@ -48,8 +48,8 @@ func TestGitHubTokenProviderCreateRequestAndCallback(t *testing.T) {
sessionID := sessionIDFromParams(t, params)
return []byte(`{"sessionId":"` + sessionID + `","workspacePath":"/workspace"}`), nil
})
- server.SetRequestHandler("session.destroy", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
- return []byte(`{}`), nil
+ server.SetRequestHandler("session.detach", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ return []byte(`{"success":true}`), nil
})
var gotArgs GitHubTokenProviderArgs
@@ -201,8 +201,8 @@ func TestGitHubTokenStringRedactsAccessToken(t *testing.T) {
func TestGitHubTokenProviderCleanupOnDisconnectError(t *testing.T) {
rpcClient, server, _ := newRuntimeShutdownRpcPair(t)
t.Cleanup(server.Stop)
- server.SetRequestHandler("session.destroy", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
- return nil, &jsonrpc2.Error{Code: -32000, Message: "destroy failed"}
+ server.SetRequestHandler("session.detach", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ return nil, &jsonrpc2.Error{Code: -32000, Message: "detach failed"}
})
client := &Client{}
registrationID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) {
@@ -213,7 +213,7 @@ func TestGitHubTokenProviderCleanupOnDisconnectError(t *testing.T) {
client.unregisterGitHubTokenProvider(registrationID)
})
- if err := session.Disconnect(); err == nil || !strings.Contains(err.Error(), "destroy failed") {
+ if err := session.Disconnect(); err == nil || !strings.Contains(err.Error(), "detach failed") {
t.Fatalf("Disconnect error = %v", err)
}
if len(client.gitHubTokenProviders) != 0 {
diff --git a/go/internal/e2e/auto_tier_e2e_test.go b/go/internal/e2e/auto_tier_e2e_test.go
new file mode 100644
index 0000000000..e974f95927
--- /dev/null
+++ b/go/internal/e2e/auto_tier_e2e_test.go
@@ -0,0 +1,140 @@
+package e2e
+
+import (
+ "testing"
+
+ copilot "github.com/github/copilot-sdk/go"
+ "github.com/github/copilot-sdk/go/internal/e2e/testharness"
+ "github.com/github/copilot-sdk/go/rpc"
+)
+
+// Mirrors nodejs/test/e2e/auto_tier.e2e.test.ts (snapshot category "auto_tier").
+//
+// The runtime stages an Auto routing preference instead of applying it immediately: a
+// request stays unclaimed until a later turn using the "auto" model mints a usable
+// model and token pair. These tests observe that staged state through Model.GetCurrent,
+// so they assert what the runtime actually recorded rather than what the SDK serialized.
+func TestAutoTierE2E(t *testing.T) {
+ autoTier := func(tier copilot.AutoTier) *copilot.AutoTier { return &tier }
+
+ pendingTier := func(t *testing.T, session *copilot.Session) *rpc.AutoTier {
+ t.Helper()
+ current, err := session.RPC.Model.GetCurrent(t.Context())
+ if err != nil {
+ t.Fatalf("Model.GetCurrent failed: %v", err)
+ }
+ return current.PendingAutoTier
+ }
+
+ assertPending := func(t *testing.T, session *copilot.Session, want rpc.AutoTier) {
+ t.Helper()
+ got := pendingTier(t, session)
+ if got == nil || *got != want {
+ t.Fatalf("Expected pending auto tier %q, got %v", want, got)
+ }
+ }
+
+ assertNoPending := func(t *testing.T, session *copilot.Session) {
+ t.Helper()
+ if got := pendingTier(t, session); got != nil {
+ t.Fatalf("Expected no pending auto tier, got %q", *got)
+ }
+ }
+
+ newAutoSession := func(t *testing.T) *copilot.Session {
+ t.Helper()
+ ctx := testharness.NewTestContext(t)
+ client := ctx.NewClient()
+ t.Cleanup(func() { client.ForceStop() })
+ if err := client.Start(t.Context()); err != nil {
+ t.Fatalf("Failed to start client: %v", err)
+ }
+ ctx.ConfigureForTest(t)
+
+ session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
+ Model: "auto",
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ })
+ if err != nil {
+ t.Fatalf("Failed to create session: %v", err)
+ }
+ return session
+ }
+
+ t.Run("should stage and reset auto tier preference", func(t *testing.T) {
+ session := newAutoSession(t)
+ assertNoPending(t, session)
+
+ staged, err := session.SetAutoTier(t.Context(), autoTier(copilot.AutoTierEfficiency))
+ if err != nil {
+ t.Fatalf("SetAutoTier(efficiency) failed: %v", err)
+ }
+ if staged.Status != rpc.ModelSwitchAutoTierStatusPending {
+ t.Fatalf("Expected status pending, got %q", staged.Status)
+ }
+ if staged.PendingAutoTier == nil || *staged.PendingAutoTier != rpc.AutoTierEfficiency {
+ t.Fatalf("Expected pending efficiency in result, got %+v", staged)
+ }
+ assertPending(t, session, rpc.AutoTierEfficiency)
+
+ // A second request replaces the first and reports the one it displaced.
+ superseded, err := session.SetAutoTier(t.Context(), autoTier(copilot.AutoTierIntelligence))
+ if err != nil {
+ t.Fatalf("SetAutoTier(intelligence) failed: %v", err)
+ }
+ if superseded.Status != rpc.ModelSwitchAutoTierStatusPending {
+ t.Fatalf("Expected status pending, got %q", superseded.Status)
+ }
+ if superseded.SupersededAutoTier == nil || *superseded.SupersededAutoTier != rpc.AutoTierEfficiency {
+ t.Fatalf("Expected superseded efficiency, got %+v", superseded)
+ }
+ assertPending(t, session, rpc.AutoTierIntelligence)
+
+ // A nil tier returns the session to provider-default routing. The status is
+ // unchanged because provider-default was already the committed preference;
+ // the request's effect is cancelling the staged one.
+ reset, err := session.SetAutoTier(t.Context(), nil)
+ if err != nil {
+ t.Fatalf("SetAutoTier(nil) failed: %v", err)
+ }
+ if reset.Status != rpc.ModelSwitchAutoTierStatusUnchanged {
+ t.Fatalf("Expected status unchanged, got %q", reset.Status)
+ }
+ if reset.SupersededAutoTier == nil || *reset.SupersededAutoTier != rpc.AutoTierIntelligence {
+ t.Fatalf("Expected superseded intelligence, got %+v", reset)
+ }
+ assertNoPending(t, session)
+ })
+
+ t.Run("should preserve auto tier when set model omits it", func(t *testing.T) {
+ session := newAutoSession(t)
+
+ if _, err := session.SetAutoTier(t.Context(), autoTier(copilot.AutoTierBalance)); err != nil {
+ t.Fatalf("SetAutoTier(balance) failed: %v", err)
+ }
+ assertPending(t, session, rpc.AutoTierBalance)
+
+ // Leaving AutoTier nil without asking for a reset leaves the staged preference alone.
+ if err := session.SetModel(t.Context(), "auto", nil); err != nil {
+ t.Fatalf("SetModel without options failed: %v", err)
+ }
+ assertPending(t, session, rpc.AutoTierBalance)
+
+ // Supplying a tier replaces it.
+ if err := session.SetModel(t.Context(), "auto", &copilot.SetModelOptions{
+ AutoTier: autoTier(copilot.AutoTierIntelligence),
+ }); err != nil {
+ t.Fatalf("SetModel with AutoTier failed: %v", err)
+ }
+ assertPending(t, session, rpc.AutoTierIntelligence)
+
+ // ResetAutoTier clears it. Omission, a value, and a reset are three distinct
+ // outcomes, which is why a single nillable field cannot express the request.
+ if err := session.SetModel(t.Context(), "auto", &copilot.SetModelOptions{
+ ResetAutoTier: true,
+ }); err != nil {
+ t.Fatalf("SetModel with ResetAutoTier failed: %v", err)
+ }
+ assertNoPending(t, session)
+ })
+}
diff --git a/go/internal/e2e/client_options_e2e_test.go b/go/internal/e2e/client_options_e2e_test.go
index 86332eb6f1..54461443ca 100644
--- a/go/internal/e2e/client_options_e2e_test.go
+++ b/go/internal/e2e/client_options_e2e_test.go
@@ -329,7 +329,7 @@ func TestClientOptionsE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
SessionID: sessionID,
ClientName: "go-sdk-e2e-client",
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
ReasoningEffort: "low",
ReasoningSummary: copilot.ReasoningSummaryNone,
ContextTier: copilot.ContextTierLongContext,
@@ -388,7 +388,7 @@ func TestClientOptionsE2E(t *testing.T) {
expectedValues := map[string]any{
"sessionId": sessionID,
"clientName": "go-sdk-e2e-client",
- "model": "claude-sonnet-4.5",
+ "model": "claude-sonnet-5",
"reasoningEffort": "low",
"reasoningSummary": "none",
"contextTier": "long_context",
@@ -869,6 +869,10 @@ function handleMessage(message) {
writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null });
return;
}
+ if (message.method === "session.detach") {
+ writeResponse(message.id, { success: true });
+ return;
+ }
if (message.method === "session.resume") {
const sessionId = (message.params && message.params.sessionId) || "fake-session";
writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null });
diff --git a/go/internal/e2e/copilot_request_helpers_test.go b/go/internal/e2e/copilot_request_helpers_test.go
index 81d14f4d94..cd1ac63cf7 100644
--- a/go/internal/e2e/copilot_request_helpers_test.go
+++ b/go/internal/e2e/copilot_request_helpers_test.go
@@ -47,8 +47,8 @@ func sseFrame(eventType string, data map[string]any) string {
func modelCatalogJSON(supportedEndpoints []string) string {
model := map[string]any{
- "id": "claude-sonnet-4.5",
- "name": "Claude Sonnet 4.5",
+ "id": "claude-sonnet-5",
+ "name": "Claude Sonnet 5",
"object": "model",
"vendor": "Anthropic",
"version": "1",
@@ -56,7 +56,7 @@ func modelCatalogJSON(supportedEndpoints []string) string {
"model_picker_enabled": true,
"capabilities": map[string]any{
"type": "chat",
- "family": "claude-sonnet-4.5",
+ "family": "claude-sonnet-5",
"tokenizer": "o200k_base",
"limits": map[string]any{
"max_context_window_tokens": 200000,
@@ -141,7 +141,7 @@ func buildAnthropicMessageSSEBody(text string) string {
"type": "message_start",
"message": map[string]any{
"id": "msg_stub_1", "type": "message", "role": "assistant",
- "model": "claude-sonnet-4.5", "content": []any{},
+ "model": "claude-sonnet-5", "content": []any{},
"stop_reason": nil, "stop_sequence": nil,
"usage": map[string]any{"input_tokens": 5, "output_tokens": 1},
},
@@ -188,7 +188,7 @@ func buildInferenceResponse(url string, bodyText string) *http.Response {
base := func() map[string]any {
return map[string]any{
"id": "chatcmpl-stub-1", "object": "chat.completion.chunk",
- "created": 1, "model": "claude-sonnet-4.5",
+ "created": 1, "model": "claude-sonnet-5",
}
}
c1 := base()
@@ -215,7 +215,7 @@ func buildInferenceResponse(url string, bodyText string) *http.Response {
"id": "msg_stub_1",
"type": "message",
"role": "assistant",
- "model": "claude-sonnet-4.5",
+ "model": "claude-sonnet-5",
"content": []any{map[string]any{"type": "text", "text": syntheticResponseText}},
"stop_reason": "end_turn",
"stop_sequence": nil,
@@ -225,7 +225,7 @@ func buildInferenceResponse(url string, bodyText string) *http.Response {
}
raw, _ := json.Marshal(map[string]any{
- "id": "chatcmpl-stub-1", "object": "chat.completion", "created": 1, "model": "claude-sonnet-4.5",
+ "id": "chatcmpl-stub-1", "object": "chat.completion", "created": 1, "model": "claude-sonnet-5",
"choices": []any{map[string]any{"index": 0, "message": map[string]any{"role": "assistant", "content": syntheticResponseText}, "finish_reason": "stop"}},
"usage": map[string]any{"prompt_tokens": 5, "completion_tokens": 7, "total_tokens": 12},
})
diff --git a/go/internal/e2e/copilot_request_session_id_e2e_test.go b/go/internal/e2e/copilot_request_session_id_e2e_test.go
index f7673bd457..46a62db1ab 100644
--- a/go/internal/e2e/copilot_request_session_id_e2e_test.go
+++ b/go/internal/e2e/copilot_request_session_id_e2e_test.go
@@ -139,14 +139,14 @@ func TestCopilotRequestSessionID(t *testing.T) {
before := len(transport.inferenceRecords())
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
Provider: &copilot.ProviderConfig{
Type: "openai",
WireAPI: "responses",
BaseURL: "https://byok.invalid/v1",
APIKey: "byok-secret",
- ModelID: "claude-sonnet-4.5",
- WireModel: "claude-sonnet-4.5",
+ ModelID: "claude-sonnet-5",
+ WireModel: "claude-sonnet-5",
},
})
if err != nil {
diff --git a/go/internal/e2e/event_fidelity_e2e_test.go b/go/internal/e2e/event_fidelity_e2e_test.go
index e7cc4bfb37..8606da22d9 100644
--- a/go/internal/e2e/event_fidelity_e2e_test.go
+++ b/go/internal/e2e/event_fidelity_e2e_test.go
@@ -333,8 +333,8 @@ func TestEventFidelityE2E(t *testing.T) {
}
idleIdx := lastEventFidelityTypeIndex(types, copilot.SessionEventTypeSessionIdle)
- if idleIdx != len(types)-1 {
- t.Fatalf("Expected session.idle to be last event; idleIdx=%d len=%d types=%v", idleIdx, len(types), types)
+ if idleIdx < 0 || assistantIdx >= idleIdx {
+ t.Fatalf("Expected last assistant.message before session.idle; assistantIdx=%d idleIdx=%d types=%v", assistantIdx, idleIdx, types)
}
})
diff --git a/go/internal/e2e/external_tool_cancellation_e2e_test.go b/go/internal/e2e/external_tool_cancellation_e2e_test.go
new file mode 100644
index 0000000000..1c865ca278
--- /dev/null
+++ b/go/internal/e2e/external_tool_cancellation_e2e_test.go
@@ -0,0 +1,77 @@
+package e2e
+
+import (
+ "testing"
+ "time"
+
+ copilot "github.com/github/copilot-sdk/go"
+ "github.com/github/copilot-sdk/go/internal/e2e/testharness"
+)
+
+func TestExternalToolCancellationE2E(t *testing.T) {
+ ctx := testharness.NewTestContext(t)
+ client := ctx.NewClient()
+ t.Cleanup(func() { client.ForceStop() })
+
+ t.Run("should_cancel_tool_handler_when_session_disconnects", func(t *testing.T) {
+ ctx.ConfigureForTest(t)
+
+ type ValueParams struct {
+ Value string `json:"value" jsonschema:"Value to analyze"`
+ }
+ toolStarted := make(chan struct{}, 1)
+ toolCancelled := make(chan struct{}, 1)
+ releaseTool := make(chan string, 1)
+
+ slowTool := copilot.DefineTool("slow_analysis", "A slow analysis tool that blocks until released",
+ func(_ ValueParams, inv copilot.ToolInvocation) (string, error) {
+ select {
+ case toolStarted <- struct{}{}:
+ default:
+ }
+ select {
+ case value := <-releaseTool:
+ return value, nil
+ case <-inv.TraceContext.Done():
+ select {
+ case toolCancelled <- struct{}{}:
+ default:
+ }
+ return "", inv.TraceContext.Err()
+ }
+ })
+ slowTool.SkipPermission = true
+
+ session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ Tools: []copilot.Tool{slowTool},
+ })
+ if err != nil {
+ t.Fatalf("Failed to create session: %v", err)
+ }
+ t.Cleanup(func() { _ = session.Disconnect() })
+
+ go func() {
+ _, _ = session.Send(t.Context(), copilot.MessageOptions{
+ Prompt: "Use slow_analysis with value 'test_abort'. Wait for the result.",
+ })
+ }()
+
+ select {
+ case <-toolStarted:
+ case <-time.After(60 * time.Second):
+ t.Fatal("Timed out waiting for tool handler to start")
+ }
+
+ if err := session.Disconnect(); err != nil {
+ t.Fatalf("Disconnect failed: %v", err)
+ }
+
+ select {
+ case <-toolCancelled:
+ case <-time.After(60 * time.Second):
+ t.Fatal("Timed out waiting for tool handler cancellation")
+ }
+
+ })
+}
diff --git a/go/internal/e2e/mcp_oauth_e2e_test.go b/go/internal/e2e/mcp_oauth_e2e_test.go
index 95de73eddd..356805371b 100644
--- a/go/internal/e2e/mcp_oauth_e2e_test.go
+++ b/go/internal/e2e/mcp_oauth_e2e_test.go
@@ -163,16 +163,21 @@ func TestMCPOAuthE2E(t *testing.T) {
}
t.Cleanup(func() { session.Disconnect() })
+ if _, err := session.RPC.MCP.Reload(t.Context()); err != nil {
+ t.Fatalf("Failed to reload MCP servers: %v", err)
+ }
waitForMCPServerStatus(t, session, serverName, rpc.MCPServerStatusConnected)
callWhoami(t, session, serverName, "refresh")
callWhoami(t, session, serverName, "upscope")
callWhoami(t, session, serverName, "reauth")
mu.Lock()
+ observedReasons = slices.DeleteFunc(observedReasons, func(reason copilot.MCPOauthRequestReason) bool {
+ return reason == copilot.MCPOauthRequestReasonInitial
+ })
reasons := append([]copilot.MCPOauthRequestReason(nil), observedReasons...)
mu.Unlock()
expectedReasons := []copilot.MCPOauthRequestReason{
- copilot.MCPOauthRequestReasonInitial,
copilot.MCPOauthRequestReasonRefresh,
copilot.MCPOauthRequestReasonUpscope,
copilot.MCPOauthRequestReasonRefresh,
@@ -256,6 +261,7 @@ func TestMCPOAuthE2E(t *testing.T) {
})
t.Run("resolve pending MCP OAuth request through RPC", func(t *testing.T) {
+ testharness.SkipIfInProcess(t, "blocked on github/copilot-agent-runtime#18961 MCP OAuth connection stall")
ctx := testharness.NewTestContext(t)
ctx.ConfigureWithoutSnapshot(t)
client := ctx.NewClient()
diff --git a/go/internal/e2e/rewind_e2e_test.go b/go/internal/e2e/rewind_e2e_test.go
index ab73feadda..701e610775 100644
--- a/go/internal/e2e/rewind_e2e_test.go
+++ b/go/internal/e2e/rewind_e2e_test.go
@@ -32,7 +32,7 @@ func TestRewindE2E(t *testing.T) {
t.Fatalf("Failed to create original file: %v", err)
}
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
EnableFileChangeTracking: copilot.Bool(true),
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
diff --git a/go/internal/e2e/rpc_e2e_test.go b/go/internal/e2e/rpc_e2e_test.go
index fcf843814e..4380415701 100644
--- a/go/internal/e2e/rpc_e2e_test.go
+++ b/go/internal/e2e/rpc_e2e_test.go
@@ -128,7 +128,7 @@ func TestSessionRPCE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -150,7 +150,7 @@ func TestSessionRPCE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -194,7 +194,7 @@ func TestSessionRPCE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
diff --git a/go/internal/e2e/rpc_server_e2e_test.go b/go/internal/e2e/rpc_server_e2e_test.go
index 6ea9ad6851..f1aa5a19c7 100644
--- a/go/internal/e2e/rpc_server_e2e_test.go
+++ b/go/internal/e2e/rpc_server_e2e_test.go
@@ -64,12 +64,12 @@ func TestRPCServerE2E(t *testing.T) {
if strings.TrimSpace(model.Name) == "" {
t.Errorf("Model %q has empty Name", model.ID)
}
- if model.ID == "claude-sonnet-4.5" {
+ if model.ID == "claude-sonnet-5" {
hasClaude = true
}
}
if !hasClaude {
- t.Errorf("Expected models list to contain 'claude-sonnet-4.5'")
+ t.Errorf("Expected models list to contain 'claude-sonnet-5'")
}
})
@@ -532,6 +532,7 @@ func TestRPCServerE2E(t *testing.T) {
t.Run("should report implemented error when connecting unknown remote session", func(t *testing.T) {
ctx := testharness.NewTestContext(t)
+ ctx.ConfigureWithoutSnapshot(t)
client := ctx.NewClient()
t.Cleanup(func() { client.ForceStop() })
if err := client.Start(t.Context()); err != nil {
diff --git a/go/internal/e2e/rpc_session_state_e2e_test.go b/go/internal/e2e/rpc_session_state_e2e_test.go
index 88673a9ba9..f4870dbac6 100644
--- a/go/internal/e2e/rpc_session_state_e2e_test.go
+++ b/go/internal/e2e/rpc_session_state_e2e_test.go
@@ -26,7 +26,7 @@ func TestRPCSessionStateE2E(t *testing.T) {
t.Run("should call session rpc model getCurrent", func(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err != nil {
@@ -37,8 +37,8 @@ func TestRPCSessionStateE2E(t *testing.T) {
if err != nil {
t.Fatalf("Model.GetCurrent failed: %v", err)
}
- if result.ModelID == nil || *result.ModelID != "claude-sonnet-4.5" {
- t.Fatalf("Expected current model claude-sonnet-4.5, got %+v", result)
+ if result.ModelID == nil || *result.ModelID != "claude-sonnet-5" {
+ t.Fatalf("Expected current model claude-sonnet-5, got %+v", result)
}
})
@@ -58,7 +58,7 @@ func TestRPCSessionStateE2E(t *testing.T) {
switchCtx.ConfigureForTest(t)
session, err := switchClient.CreateSession(t.Context(), &copilot.SessionConfig{
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err != nil {
@@ -485,7 +485,7 @@ func TestRPCSessionStateE2E(t *testing.T) {
branch := "rpc-context-" + randomHex(t)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
WorkingDirectory: firstDirectory,
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
@@ -498,7 +498,7 @@ func TestRPCSessionStateE2E(t *testing.T) {
t.Fatalf("Metadata.Snapshot failed: %v", err)
}
if initial.SessionID != session.SessionID || initial.CurrentMode != rpc.MetadataSnapshotCurrentModeInteractive ||
- initial.SelectedModel == nil || *initial.SelectedModel != "claude-sonnet-4.5" ||
+ initial.SelectedModel == nil || *initial.SelectedModel != "claude-sonnet-5" ||
initial.IsRemote || initial.AlreadyInUse || initial.StartTime.IsZero() || initial.ModifiedTime.IsZero() ||
initial.Workspace == nil || initial.WorkspacePath == nil || *initial.WorkspacePath == "" {
t.Fatalf("Unexpected initial metadata snapshot: %+v", initial)
@@ -630,7 +630,7 @@ func TestRPCSessionStateE2E(t *testing.T) {
t.Run("should set reasoning effort and auto name", func(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err != nil {
@@ -648,9 +648,9 @@ func TestRPCSessionStateE2E(t *testing.T) {
if err != nil {
t.Fatalf("Model.GetCurrent failed: %v", err)
}
- if current.ModelID == nil || *current.ModelID != "claude-sonnet-4.5" ||
+ if current.ModelID == nil || *current.ModelID != "claude-sonnet-5" ||
current.ReasoningEffort == nil || *current.ReasoningEffort != "high" {
- t.Fatalf("Expected current model claude-sonnet-4.5/high, got %+v", current)
+ t.Fatalf("Expected current model claude-sonnet-5/high, got %+v", current)
}
autoName := "Auto Session " + randomHex(t)
@@ -760,7 +760,7 @@ func TestRPCSessionStateE2E(t *testing.T) {
t.Fatal("Expected fresh session to be idle")
}
- model := "claude-sonnet-4.5"
+ model := "claude-sonnet-5"
contextInfo, err := session.RPC.Metadata.ContextInfo(t.Context(), &rpc.MetadataContextInfoRequest{
PromptTokenLimit: 128000,
OutputTokenLimit: 4096,
diff --git a/go/internal/e2e/rpc_session_state_extras_e2e_test.go b/go/internal/e2e/rpc_session_state_extras_e2e_test.go
index 99dc0b7572..c74ed45cc8 100644
--- a/go/internal/e2e/rpc_session_state_extras_e2e_test.go
+++ b/go/internal/e2e/rpc_session_state_extras_e2e_test.go
@@ -22,7 +22,7 @@ func TestRpcSessionStateExtras(t *testing.T) {
authClient := newAuthenticatedClient(ctx, token)
defer authClient.ForceStop()
- session := createPortedSession(t, authClient, &copilot.SessionConfig{Model: "claude-sonnet-4.5"})
+ session := createPortedSession(t, authClient, &copilot.SessionConfig{Model: "claude-sonnet-5"})
defer session.Disconnect()
result, err := session.RPC.Model.List(t.Context())
@@ -38,13 +38,13 @@ func TestRpcSessionStateExtras(t *testing.T) {
found := false
for _, model := range result.List {
data, err := json.Marshal(model)
- if err == nil && strings.Contains(string(data), "claude-sonnet-4.5") {
+ if err == nil && strings.Contains(string(data), "claude-sonnet-5") {
found = true
break
}
}
if !found {
- t.Fatalf("Expected model list to include claude-sonnet-4.5, got %+v", result.List)
+ t.Fatalf("Expected model list to include claude-sonnet-5, got %+v", result.List)
}
})
diff --git a/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go b/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go
index 81b8471dac..a426f6bdc9 100644
--- a/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go
+++ b/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go
@@ -30,12 +30,16 @@ func TestRPCShellAndFleetE2E(t *testing.T) {
t.Fatalf("Failed to create session: %v", err)
}
- markerPath := filepath.Join(ctx.WorkDir, "shell-rpc-"+randomHex(t)+".txt")
+ commandDir := filepath.Join(ctx.WorkDir, "shell-rpc-"+randomHex(t))
+ if err := os.Mkdir(commandDir, 0755); err != nil {
+ t.Fatalf("Failed to create shell command directory: %v", err)
+ }
+ markerPath := filepath.Join(commandDir, "marker.txt")
const marker = "copilot-sdk-shell-rpc"
- cwd := ctx.WorkDir
+ cwd := commandDir
result, err := session.RPC.Shell.Exec(t.Context(), &rpc.ShellExecRequest{
- Command: writeFileCommand(markerPath, marker),
+ Command: writeFileCommand(filepath.Base(markerPath), marker),
Cwd: &cwd,
})
if err != nil {
@@ -174,11 +178,11 @@ func randomHex(t *testing.T) string {
return hex.EncodeToString(buf[:])
}
-func writeFileCommand(markerPath, marker string) string {
+func writeFileCommand(markerName, marker string) string {
if runtime.GOOS == "windows" {
- return fmt.Sprintf("powershell -NoLogo -NoProfile -Command \"Set-Content -LiteralPath '%s' -Value '%s'\"", markerPath, marker)
+ return fmt.Sprintf("echo %s>\"%s\"", marker, markerName)
}
- return fmt.Sprintf("sh -c \"printf '%%s' '%s' > '%s'\"", marker, markerPath)
+ return fmt.Sprintf("sh -c \"printf '%%s' '%s' > '%s'\"", marker, markerName)
}
func waitForFileText(t *testing.T, path, expected string) {
diff --git a/go/internal/e2e/session_config_e2e_test.go b/go/internal/e2e/session_config_e2e_test.go
index 2ce48e3b33..f1c267e230 100644
--- a/go/internal/e2e/session_config_e2e_test.go
+++ b/go/internal/e2e/session_config_e2e_test.go
@@ -119,8 +119,8 @@ func createAnthropicProvider() *copilot.ProviderConfig {
Type: "anthropic",
BaseURL: "https://anthropic-citations.invalid/v1",
APIKey: "test-provider-key",
- ModelID: "claude-sonnet-4.5",
- WireModel: "claude-sonnet-4.5",
+ ModelID: "claude-sonnet-5",
+ WireModel: "claude-sonnet-5",
}
}
@@ -184,6 +184,7 @@ func TestSessionConfigE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ Model: "claude-sonnet-5",
ModelCapabilities: &copilot.ModelCapabilitiesOverride{
Supports: &copilot.ModelCapabilitiesOverrideSupports{
Vision: copilot.Bool(false),
@@ -208,7 +209,7 @@ func TestSessionConfigE2E(t *testing.T) {
}
// Switch vision on
- if err := session.SetModel(t.Context(), "claude-sonnet-4.5", &copilot.SetModelOptions{
+ if err := session.SetModel(t.Context(), "claude-sonnet-5", &copilot.SetModelOptions{
ModelCapabilities: &copilot.ModelCapabilitiesOverride{
Supports: &copilot.ModelCapabilitiesOverrideSupports{
Vision: copilot.Bool(true),
@@ -238,6 +239,7 @@ func TestSessionConfigE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ Model: "claude-sonnet-5",
ModelCapabilities: &copilot.ModelCapabilitiesOverride{
Supports: &copilot.ModelCapabilitiesOverrideSupports{
Vision: copilot.Bool(true),
@@ -262,7 +264,7 @@ func TestSessionConfigE2E(t *testing.T) {
}
// Switch vision off
- if err := session.SetModel(t.Context(), "claude-sonnet-4.5", &copilot.SetModelOptions{
+ if err := session.SetModel(t.Context(), "claude-sonnet-5", &copilot.SetModelOptions{
ModelCapabilities: &copilot.ModelCapabilitiesOverride{
Supports: &copilot.ModelCapabilitiesOverrideSupports{
Vision: copilot.Bool(false),
@@ -420,7 +422,7 @@ func TestSessionConfigNewOptionsCopilotRequestE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
EnableCitations: copilot.Bool(true),
Provider: createAnthropicProvider(),
})
@@ -481,7 +483,7 @@ func TestSessionConfigNewOptionsCopilotRequestE2E(t *testing.T) {
session2, err := resumeClient.ResumeSessionWithOptions(t.Context(), session1.SessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
EnableCitations: copilot.Bool(true),
Provider: createAnthropicProvider(),
})
@@ -589,7 +591,7 @@ func TestSessionConfigExtrasE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
Provider: createProxyProvider(ctx, providerHeaderName, "create-provider-header"),
})
if err != nil {
@@ -633,7 +635,7 @@ func TestSessionConfigExtrasE2E(t *testing.T) {
session2, err := client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
Provider: createProxyProvider(ctx, providerHeaderName, "resume-provider-header"),
})
if err != nil {
@@ -677,7 +679,7 @@ func TestSessionConfigExtrasE2E(t *testing.T) {
maxOutputTokens := 1024
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Model: "claude-sonnet-4.5",
+ Model: "claude-sonnet-5",
Provider: &copilot.ProviderConfig{
Type: "openai",
BaseURL: ctx.ProxyURL,
@@ -719,7 +721,7 @@ func TestSessionConfigExtrasE2E(t *testing.T) {
Type: "openai",
BaseURL: ctx.ProxyURL,
APIKey: "test-provider-key",
- ModelID: "claude-sonnet-4.5",
+ ModelID: "claude-sonnet-5",
},
})
if err != nil {
@@ -738,8 +740,8 @@ func TestSessionConfigExtrasE2E(t *testing.T) {
if len(exchanges) != 1 {
t.Fatalf("Expected exactly 1 exchange, got %d", len(exchanges))
}
- if exchanges[0].Request.Model != "claude-sonnet-4.5" {
- t.Errorf("Expected request model to be 'claude-sonnet-4.5', got %q", exchanges[0].Request.Model)
+ if exchanges[0].Request.Model != "claude-sonnet-5" {
+ t.Errorf("Expected request model to be 'claude-sonnet-5', got %q", exchanges[0].Request.Model)
}
})
diff --git a/go/internal/e2e/session_e2e_test.go b/go/internal/e2e/session_e2e_test.go
index cf39b6784c..12550e6e2d 100644
--- a/go/internal/e2e/session_e2e_test.go
+++ b/go/internal/e2e/session_e2e_test.go
@@ -10,6 +10,8 @@ import (
"testing"
"time"
+ "github.com/google/uuid"
+
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/internal/e2e/testharness"
"github.com/github/copilot-sdk/go/rpc"
@@ -23,7 +25,7 @@ func TestSessionE2E(t *testing.T) {
t.Run("should create and disconnect sessions", func(t *testing.T) {
ctx.ConfigureForTest(t)
- session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll, Model: "claude-sonnet-4.5"})
+ session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll, Model: "claude-sonnet-5"})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
}
@@ -47,8 +49,8 @@ func TestSessionE2E(t *testing.T) {
t.Errorf("Expected session.start sessionId to match")
}
- if !startOk || startData.SelectedModel == nil || *startData.SelectedModel != "claude-sonnet-4.5" {
- t.Errorf("Expected selectedModel to be 'claude-sonnet-4.5', got %v", startData)
+ if !startOk || startData.SelectedModel == nil || *startData.SelectedModel != "claude-sonnet-5" {
+ t.Errorf("Expected selectedModel to be 'claude-sonnet-5', got %v", startData)
}
if err := session.Disconnect(); err != nil {
@@ -526,6 +528,62 @@ func TestSessionE2E(t *testing.T) {
}
})
+ t.Run("should recover marker after cold resume with explicit session id", func(t *testing.T) {
+ ctx.ConfigureForTest(t)
+
+ sessionID := "e2e-cold-resume-" + uuid.NewString()
+
+ client1 := ctx.NewClient()
+ session1, err := client1.CreateSession(t.Context(), &copilot.SessionConfig{
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ SessionID: sessionID,
+ })
+ if err != nil {
+ t.Fatalf("Failed to create session: %v", err)
+ }
+ if session1.SessionID != sessionID {
+ t.Fatalf("Expected explicit session ID %q, got %q", sessionID, session1.SessionID)
+ }
+
+ answer, err := session1.SendAndWait(t.Context(), copilot.MessageOptions{
+ Prompt: `Please remember this exact secret marker for later - MARKER-7f3ac21e. Reply with only the single word "Acknowledged".`,
+ })
+ if err != nil {
+ t.Fatalf("Failed to send message: %v", err)
+ }
+ if ad, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "Acknowledged") {
+ t.Errorf("Expected answer to contain 'Acknowledged', got %v", answer.Data)
+ }
+
+ if err := session1.Disconnect(); err != nil {
+ t.Fatalf("Failed to disconnect session: %v", err)
+ }
+ client1.ForceStop()
+
+ client2 := ctx.NewClient()
+ defer client2.ForceStop()
+
+ session2, err := client2.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ })
+ if err != nil {
+ t.Fatalf("Failed to resume session: %v", err)
+ }
+ if session2.SessionID != sessionID {
+ t.Errorf("Expected resumed session ID to match, got %q vs %q", session2.SessionID, sessionID)
+ }
+
+ answer2, err := session2.SendAndWait(t.Context(), copilot.MessageOptions{
+ Prompt: "What was the exact secret marker I asked you to remember earlier? Reply with only that marker value and nothing else.",
+ })
+ if err != nil {
+ t.Fatalf("Failed to send message after resume: %v", err)
+ }
+ if ad, ok := answer2.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "MARKER-7f3ac21e") {
+ t.Errorf("Expected resumed answer to contain marker, got %v", answer2.Data)
+ }
+ })
+
t.Run("should throw error when resuming non-existent session", func(t *testing.T) {
ctx.ConfigureForTest(t)
diff --git a/go/internal/e2e/session_event_loop_leak_e2e_test.go b/go/internal/e2e/session_event_loop_leak_e2e_test.go
new file mode 100644
index 0000000000..c6974f74d8
--- /dev/null
+++ b/go/internal/e2e/session_event_loop_leak_e2e_test.go
@@ -0,0 +1,102 @@
+package e2e
+
+import (
+ "runtime"
+ "testing"
+ "time"
+
+ copilot "github.com/github/copilot-sdk/go"
+ "github.com/github/copilot-sdk/go/internal/e2e/testharness"
+)
+
+// TestSessionEventLoopLeakE2E is a regression test for the goroutine leak fixed
+// alongside PR #2360: CreateSession/ResumeSession construct a *Session and start
+// its event-dispatch goroutine eagerly, before the server confirms the session,
+// so the CLI can route session-scoped requests to it while session.create (or
+// session.resume) is still being processed. Every failure path must stop that
+// goroutine — otherwise each failed call leaks one goroutine forever, since no
+// caller ever receives the failed session to Disconnect() it. This test fails
+// without the fix and passes with it.
+func TestSessionEventLoopLeakE2E(t *testing.T) {
+ ctx := testharness.NewTestContext(t)
+
+ t.Run("CreateSession failure does not leak the event loop goroutine", func(t *testing.T) {
+ ctx.ConfigureForTest(t)
+
+ // Redirect the CLI's GitHub API calls at the replaying proxy so an
+ // invalid per-session token makes the real CLI reject session.create
+ // with a genuine RPC error (401 Unauthorized), exercising the same
+ // failure path a real user would hit — not a mocked transport.
+ client := ctx.NewClient(func(opts *copilot.ClientOptions) {
+ opts.Env = append(opts.Env, "COPILOT_DEBUG_GITHUB_API_URL="+ctx.ProxyURL)
+ })
+ t.Cleanup(func() { client.ForceStop() })
+
+ createFailing := func() {
+ if _, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ GitHubToken: "invalid-token",
+ }); err == nil {
+ t.Fatal("expected CreateSession to fail with an invalid token")
+ }
+ }
+
+ // Warm up: the first call spawns the CLI subprocess and its steady-state
+ // goroutines (read loop, etc.), which must not be counted as leaks.
+ createFailing()
+
+ assertNoGoroutineLeak(t, 20, createFailing)
+ })
+
+ t.Run("ResumeSession failure does not leak the event loop goroutine", func(t *testing.T) {
+ ctx.ConfigureForTest(t)
+
+ client := ctx.NewClient()
+ t.Cleanup(func() { client.ForceStop() })
+
+ resumeNonExistent := func() {
+ if _, err := client.ResumeSession(t.Context(), "non-existent-leak-check-session", &copilot.ResumeSessionConfig{
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ }); err == nil {
+ t.Fatal("expected ResumeSession for a non-existent session to fail")
+ }
+ }
+
+ // Warm up: the first call spawns the CLI subprocess and its steady-state
+ // goroutines (read loop, etc.), which must not be counted as leaks.
+ resumeNonExistent()
+
+ assertNoGoroutineLeak(t, 20, resumeNonExistent)
+ })
+}
+
+// assertNoGoroutineLeak runs fn n times and fails if the goroutine count grows
+// roughly in proportion to n afterward, which would indicate one goroutine
+// leaked per call rather than transient goroutines that already exited.
+func assertNoGoroutineLeak(t *testing.T, n int, fn func()) {
+ t.Helper()
+ runtime.GC()
+ before := runtime.NumGoroutine()
+
+ for i := 0; i < n; i++ {
+ fn()
+ }
+
+ // Give any goroutines that exit promptly (but not synchronously with the
+ // call returning) a brief window to actually terminate before measuring.
+ deadline := time.Now().Add(2 * time.Second)
+ var after int
+ for {
+ runtime.GC()
+ after = runtime.NumGoroutine()
+ if after <= before+3 || time.Now().After(deadline) {
+ break
+ }
+ time.Sleep(50 * time.Millisecond)
+ }
+
+ t.Logf("goroutines before=%d after=%d (n=%d)", before, after, n)
+ if after > before+3 {
+ t.Fatalf("goroutine count grew from %d to %d after %d failed calls; suspected event-loop leak", before, after, n)
+ }
+}
diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go
index 037265f9de..060997d264 100644
--- a/go/internal/e2e/testharness/context.go
+++ b/go/internal/e2e/testharness/context.go
@@ -1,7 +1,9 @@
package testharness
import (
+ "fmt"
"os"
+ "os/exec"
"path/filepath"
"regexp"
"runtime"
@@ -29,15 +31,18 @@ func CLIPath() string {
return
}
- // Look for CLI in sibling nodejs directory's node_modules. As of CLI
- // 1.0.64-1 the @github/copilot package is a thin loader; the runnable
- // index.js ships in the installed platform package
- // (e.g. @github/copilot-linux-x64).
- base := RepoPath("nodejs", "node_modules", "@github")
- matches, _ := filepath.Glob(filepath.Join(base, "copilot-*", "index.js"))
- if len(matches) > 0 {
- cliPath = matches[0]
- return
+ npm := "npm"
+ if runtime.GOOS == "windows" {
+ npm = "npm.cmd"
+ }
+ command := exec.Command(npm, "run", "--silent", "prepare:runtime", "--", "--print-path")
+ command.Dir = RepoPath("nodejs")
+ output, err := command.Output()
+ if err == nil {
+ candidate := strings.TrimSpace(string(output))
+ if info, statErr := os.Stat(candidate); statErr == nil && !info.IsDir() {
+ cliPath = candidate
+ }
}
})
return cliPath
@@ -159,6 +164,17 @@ func NewTestContext(t *testing.T) *TestContext {
os.RemoveAll(workDir)
t.Fatalf("Failed to start proxy: %v", err)
}
+ // Initialize the proxy before any client can start runtime requests. Tests that
+ // use a snapshot replace this empty configuration before model traffic begins.
+ dummySnapshotPath := filepath.Join(workDir, "__no_snapshot__.yaml")
+ if err := proxy.Configure(dummySnapshotPath, workDir); err != nil {
+ if stopErr := proxy.StopWithOptions(true); stopErr != nil {
+ t.Logf("Failed to stop proxy after initialization error: %v", stopErr)
+ }
+ os.RemoveAll(homeDir)
+ os.RemoveAll(workDir)
+ t.Fatalf("Failed to initialize proxy: %v", err)
+ }
if err := proxy.SetCopilotUserByToken(defaultGitHubToken, map[string]interface{}{
"login": "e2e-test-user",
"copilot_plan": "individual_pro",
@@ -168,7 +184,9 @@ func NewTestContext(t *testing.T) *TestContext {
},
"analytics_tracking_id": "e2e-test-tracking-id",
}); err != nil {
- proxy.StopWithOptions(true)
+ if stopErr := proxy.StopWithOptions(true); stopErr != nil {
+ t.Logf("Failed to stop proxy after configuration error: %v", stopErr)
+ }
os.RemoveAll(homeDir)
os.RemoveAll(workDir)
t.Fatalf("Failed to configure default Copilot user: %v", err)
@@ -251,7 +269,9 @@ func (c *TestContext) ConfigureWithoutSnapshot(t *testing.T) {
func (c *TestContext) Close(testFailed bool) {
c.restoreInProcessEnvironment()
if c.proxy != nil {
- c.proxy.StopWithOptions(testFailed)
+ if err := c.proxy.StopWithOptions(testFailed); err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to stop E2E proxy: %v\n", err)
+ }
}
if c.HomeDir != "" {
os.RemoveAll(c.HomeDir)
diff --git a/go/internal/e2e/testharness/proxy.go b/go/internal/e2e/testharness/proxy.go
index 2545882bce..990c066eea 100644
--- a/go/internal/e2e/testharness/proxy.go
+++ b/go/internal/e2e/testharness/proxy.go
@@ -4,16 +4,22 @@ import (
"bufio"
"bytes"
"encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"regexp"
+ "runtime"
+ "strconv"
"strings"
"sync"
+ "time"
)
+const proxyShutdownTimeout = 5 * time.Second
+
// CapiProxy manages a child process that acts as a replaying proxy to AI endpoints.
// It spawns the shared test harness server from test/harness/server.ts.
type CapiProxy struct {
@@ -118,6 +124,11 @@ func (p *CapiProxy) StopWithOptions(skipWritingCache bool) error {
if p.cmd == nil || p.cmd.Process == nil {
return nil
}
+ cmd := p.cmd
+ defer func() {
+ p.cmd = nil
+ p.proxyURL = ""
+ }()
// Send stop request to the server
if p.proxyURL != "" {
@@ -126,20 +137,61 @@ func (p *CapiProxy) StopWithOptions(skipWritingCache bool) error {
stopURL += "?skipWritingCache=true"
}
// Best effort - ignore errors
- resp, err := http.Post(stopURL, "application/json", nil)
+ client := http.Client{Timeout: proxyShutdownTimeout}
+ resp, err := client.Post(stopURL, "application/json", nil)
if err == nil {
resp.Body.Close()
}
}
- // Wait for process to exit
- p.cmd.Wait()
- p.cmd = nil
- p.proxyURL = ""
+ exited := make(chan struct{}, 1)
+ go func() {
+ _ = cmd.Wait()
+ exited <- struct{}{}
+ }()
+ if !waitForProcessExit(exited, proxyShutdownTimeout) {
+ if err := killProcessTree(cmd); err != nil {
+ return fmt.Errorf("failed to kill proxy process: %w", err)
+ }
+ if !waitForProcessExit(exited, proxyShutdownTimeout) {
+ return fmt.Errorf("proxy process did not exit after being killed")
+ }
+ }
+ return nil
+}
+func killProcessTree(cmd *exec.Cmd) error {
+ if runtime.GOOS == "windows" {
+ taskkill := exec.Command(
+ "taskkill",
+ "/PID",
+ strconv.Itoa(cmd.Process.Pid),
+ "/T",
+ "/F",
+ )
+ if err := taskkill.Run(); err == nil {
+ return nil
+ }
+ }
+
+ if err := cmd.Process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) {
+ return err
+ }
return nil
}
+func waitForProcessExit(exited <-chan struct{}, timeout time.Duration) bool {
+ timer := time.NewTimer(timeout)
+ defer timer.Stop()
+
+ select {
+ case <-exited:
+ return true
+ case <-timer.C:
+ return false
+ }
+}
+
// Configure sends configuration to the proxy.
func (p *CapiProxy) Configure(filePath, workDir string) error {
p.mu.Lock()
diff --git a/go/internal/embeddedcli/embeddedcli.go b/go/internal/embeddedcli/embeddedcli.go
index 2535cf5f20..2e3b8add16 100644
--- a/go/internal/embeddedcli/embeddedcli.go
+++ b/go/internal/embeddedcli/embeddedcli.go
@@ -26,7 +26,7 @@ import (
// when provided, is written next to the installed binary.
//
// RuntimeExecutable and RuntimeNode form the adjacent out-of-process runtime
-// pair. RuntimeAssets is a filtered npm package archive containing auxiliary
+// pair. RuntimeAssets is a filtered release package archive containing auxiliary
// binaries and resources. RuntimeLib is the same cdylib bytes installed under
// the natural platform name for the optional in-process transport.
type Config struct {
@@ -264,6 +264,13 @@ func installAt(installDir string) (string, error) {
if !bytes.Equal(existingHash, config.CliHash) {
return "", fmt.Errorf("existing binary hash mismatch")
}
+ if config.RuntimeExecutable != nil {
+ path, err := installRuntimePair(installDir)
+ if err != nil {
+ return "", err
+ }
+ runtimePath = path
+ }
if config.RuntimeLib != nil {
libPath, err := installRuntimeLib(installDir)
if err != nil {
@@ -298,6 +305,14 @@ func installAt(installDir string) (string, error) {
}
}
+ if config.RuntimeExecutable != nil {
+ path, err := installRuntimePair(installDir)
+ if err != nil {
+ return "", err
+ }
+ runtimePath = path
+ }
+
// Install the native in-process runtime library (if bundled) next to the CLI.
// Fail closed on any hash mismatch; never place unverified native code.
if config.RuntimeLib != nil {
@@ -305,11 +320,11 @@ func installAt(installDir string) (string, error) {
if err != nil {
return "", err
}
- if err := installRuntimeAssets(installDir); err != nil {
- return "", err
- }
runtimeLibPath = libPath
}
+ if err := installRuntimeAssets(installDir); err != nil {
+ return "", err
+ }
return finalPath, nil
}
diff --git a/go/internal/embeddedcli/embeddedcli_test.go b/go/internal/embeddedcli/embeddedcli_test.go
index 159b6e1505..a56dd8106e 100644
--- a/go/internal/embeddedcli/embeddedcli_test.go
+++ b/go/internal/embeddedcli/embeddedcli_test.go
@@ -259,6 +259,46 @@ func TestInstallAtWritesBinaryAndLicense(t *testing.T) {
}
}
+func TestInstallAtInstallsRuntimePairAndAssetsWithoutRuntimeLib(t *testing.T) {
+ resetGlobals()
+ tempDir := t.TempDir()
+ wrapper := []byte("wrapper")
+ node := []byte("runtime")
+ assets := runtimeAssetsArchive(t, map[string]assetFixture{
+ "definitions/future.json": {content: []byte("{}"), mode: 0644},
+ })
+ wrapperHash := sha256.Sum256(wrapper)
+ nodeHash := sha256.Sum256(node)
+ assetsHash := sha256.Sum256(assets)
+ Setup(Config{
+ Cli: bytes.NewReader(wrapper),
+ CliHash: wrapperHash[:],
+ RuntimeExecutable: bytes.NewReader(wrapper),
+ RuntimeExecutableHash: wrapperHash[:],
+ RuntimeNode: bytes.NewReader(node),
+ RuntimeNodeHash: nodeHash[:],
+ RuntimeAssets: bytes.NewReader(assets),
+ RuntimeAssetsHash: assetsHash[:],
+ Version: "1.2.3",
+ Dir: tempDir,
+ })
+
+ path, err := installAt(tempDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ installDir := filepath.Dir(path)
+ if got, err := os.ReadFile(filepath.Join(installDir, runtimeExecutableName())); err != nil || !bytes.Equal(got, wrapper) {
+ t.Fatalf("runtime wrapper content=%q err=%v", got, err)
+ }
+ if got, err := os.ReadFile(filepath.Join(installDir, "runtime.node")); err != nil || !bytes.Equal(got, node) {
+ t.Fatalf("runtime.node content=%q err=%v", got, err)
+ }
+ if got, err := os.ReadFile(filepath.Join(installDir, "definitions", "future.json")); err != nil || string(got) != "{}" {
+ t.Fatalf("definition content=%q err=%v", got, err)
+ }
+}
+
func TestInstallAtExistingBinaryHashMismatch(t *testing.T) {
resetGlobals()
tempDir := t.TempDir()
diff --git a/go/rpc/generated_rpc_api_shape_test.go b/go/rpc/generated_rpc_api_shape_test.go
index 05774f4847..dcfe0feb8d 100644
--- a/go/rpc/generated_rpc_api_shape_test.go
+++ b/go/rpc/generated_rpc_api_shape_test.go
@@ -40,6 +40,9 @@ func TestGeneratedRPCAPIShape(t *testing.T) {
assertStructFieldType(t, file, fileSet, "MCPConfigUpdateRequest", "Config", "MCPSerializableServerConfig")
assertStructFieldType(t, file, fileSet, "MCPServerConfigHTTP", "FilterMapping", "FilterMapping")
assertStructFieldType(t, file, fileSet, "MCPServerConfigStdio", "FilterMapping", "FilterMapping")
+ assertStructFieldType(t, file, fileSet, "ModelSwitchToRequest", "AutoTier", "**AutoTier")
+ assertStructFieldType(t, file, fileSet, "TaskClientUpdateProgress", "Percentage", "**float64")
+ assertStructFieldType(t, file, fileSet, "TaskClientUpdateProgress", "Phase", "**string")
assertInterfaceType(t, file, "UIElicitationFieldValue")
assertTypeExpr(t, fileSet, findTypeSpec(t, file, "UIElicitationStringArrayValue").Type, "[]string")
diff --git a/go/rpc/generated_rpc_union_test.go b/go/rpc/generated_rpc_union_test.go
index 92bcb4c077..64d723f21a 100644
--- a/go/rpc/generated_rpc_union_test.go
+++ b/go/rpc/generated_rpc_union_test.go
@@ -14,6 +14,7 @@ func TestExternalToolResultJSONUnion(t *testing.T) {
if err != nil {
t.Fatalf("marshal string result: %v", err)
}
+
if string(raw) != `"tool result"` {
t.Fatalf("marshal string result = %s", raw)
}
@@ -46,6 +47,29 @@ func TestExternalToolResultJSONUnion(t *testing.T) {
}
}
+func TestOptionalNullableFieldsPreserveOmittedAndNull(t *testing.T) {
+ unset, err := json.Marshal(TaskClientUpdateProgress{})
+ if err != nil {
+ t.Fatalf("marshal unset progress: %v", err)
+ }
+ if string(unset) != `{"kind":"progress"}` {
+ t.Fatalf("marshal unset progress = %s", unset)
+ }
+
+ var clearedPercentage *float64
+ var clearedPhase *string
+ cleared, err := json.Marshal(TaskClientUpdateProgress{
+ Percentage: &clearedPercentage,
+ Phase: &clearedPhase,
+ })
+ if err != nil {
+ t.Fatalf("marshal cleared progress: %v", err)
+ }
+ if string(cleared) != `{"kind":"progress","percentage":null,"phase":null}` {
+ t.Fatalf("marshal cleared progress = %s", cleared)
+ }
+}
+
func TestFilterMappingJSONUnion(t *testing.T) {
var mapping FilterMapping = FilterMappingEnumMap{"secret": ContentFilterModeHiddenCharacters}
raw, err := json.Marshal(mapping)
diff --git a/go/rpc/message_identity_test.go b/go/rpc/message_identity_test.go
new file mode 100644
index 0000000000..6124d7084a
--- /dev/null
+++ b/go/rpc/message_identity_test.go
@@ -0,0 +1,101 @@
+package rpc
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+func TestQueuePendingItemsMessageIDJSONCompatibility(t *testing.T) {
+ var item QueuePendingItems
+ if err := json.Unmarshal([]byte(`{
+ "id": "queue-1",
+ "messageId": "message-1",
+ "kind": "message",
+ "displayText": "hello",
+ "agentMode": "interactive"
+ }`), &item); err != nil {
+ t.Fatal(err)
+ }
+ if item.MessageID == nil || *item.MessageID != "message-1" {
+ t.Fatalf("MessageID = %v, want message-1", item.MessageID)
+ }
+
+ encoded, err := json.Marshal(item)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var wire map[string]any
+ if err := json.Unmarshal(encoded, &wire); err != nil {
+ t.Fatal(err)
+ }
+ if got := wire["messageId"]; got != "message-1" {
+ t.Fatalf("messageId = %v, want message-1", got)
+ }
+
+ var olderItem QueuePendingItems
+ if err := json.Unmarshal([]byte(`{
+ "id": "queue-2",
+ "kind": "command",
+ "displayText": "/help",
+ "agentMode": "interactive"
+ }`), &olderItem); err != nil {
+ t.Fatal(err)
+ }
+ if olderItem.MessageID != nil {
+ t.Fatalf("MessageID = %v, want nil", olderItem.MessageID)
+ }
+
+ encoded, err = json.Marshal(olderItem)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wire = nil
+ if err := json.Unmarshal(encoded, &wire); err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := wire["messageId"]; ok {
+ t.Fatal("messageId should be omitted when absent")
+ }
+}
+
+func TestUserMessageDataMessageIDJSONCompatibility(t *testing.T) {
+ var message UserMessageData
+ if err := json.Unmarshal([]byte(`{"content":"hello","messageId":"message-1"}`), &message); err != nil {
+ t.Fatal(err)
+ }
+ if message.MessageID == nil || *message.MessageID != "message-1" {
+ t.Fatalf("MessageID = %v, want message-1", message.MessageID)
+ }
+
+ encoded, err := json.Marshal(message)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var wire map[string]any
+ if err := json.Unmarshal(encoded, &wire); err != nil {
+ t.Fatal(err)
+ }
+ if got := wire["messageId"]; got != "message-1" {
+ t.Fatalf("messageId = %v, want message-1", got)
+ }
+
+ var olderMessage UserMessageData
+ if err := json.Unmarshal([]byte(`{"content":"hello"}`), &olderMessage); err != nil {
+ t.Fatal(err)
+ }
+ if olderMessage.MessageID != nil {
+ t.Fatalf("MessageID = %v, want nil", olderMessage.MessageID)
+ }
+
+ encoded, err = json.Marshal(olderMessage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wire = nil
+ if err := json.Unmarshal(encoded, &wire); err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := wire["messageId"]; ok {
+ t.Fatal("messageId should be omitted when absent")
+ }
+}
diff --git a/go/rpc/sandbox_config_test.go b/go/rpc/sandbox_config_test.go
new file mode 100644
index 0000000000..58be07e1d6
--- /dev/null
+++ b/go/rpc/sandbox_config_test.go
@@ -0,0 +1,43 @@
+package rpc
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+func TestSandboxConfigAllowBypassJSON(t *testing.T) {
+ allowBypass := true
+ configured := SandboxConfig{Enabled: true, AllowBypass: &allowBypass}
+
+ data, err := json.Marshal(configured)
+ if err != nil {
+ t.Fatalf("marshal configured sandbox: %v", err)
+ }
+ var wire map[string]any
+ if err := json.Unmarshal(data, &wire); err != nil {
+ t.Fatalf("unmarshal configured sandbox: %v", err)
+ }
+ if got := wire["allowBypass"]; got != true {
+ t.Fatalf("allowBypass = %v, want true", got)
+ }
+
+ var roundTripped SandboxConfig
+ if err := json.Unmarshal(data, &roundTripped); err != nil {
+ t.Fatalf("round-trip configured sandbox: %v", err)
+ }
+ if roundTripped.AllowBypass == nil || !*roundTripped.AllowBypass {
+ t.Fatal("round-tripped allowBypass = nil or false, want true")
+ }
+
+ data, err = json.Marshal(SandboxConfig{Enabled: true})
+ if err != nil {
+ t.Fatalf("marshal sandbox without bypass: %v", err)
+ }
+ wire = make(map[string]any)
+ if err := json.Unmarshal(data, &wire); err != nil {
+ t.Fatalf("unmarshal sandbox without bypass: %v", err)
+ }
+ if _, ok := wire["allowBypass"]; ok {
+ t.Fatal("allowBypass was serialized when absent")
+ }
+}
diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go
index 67121c30ed..3c9d2d46de 100644
--- a/go/rpc/zrpc.go
+++ b/go/rpc/zrpc.go
@@ -170,7 +170,7 @@ type AgentGetCurrentResult struct {
Agent *AgentInfo `json:"agent,omitempty"`
}
-// Agent metadata, including identifiers, display details, source, tools, model, MCP
+// Agent metadata, including identifiers, display details, source, tools, model, models, MCP
// servers, skills, and file path.
// Experimental: AgentInfo is part of an experimental API and may change or be removed.
type AgentInfo struct {
@@ -189,6 +189,11 @@ type AgentInfo struct {
// Authored preferred model id for this agent. Runtime model selection may choose a
// different model; omitted means no authored preference.
Model *string `json:"model,omitempty"`
+ // Whether authored models are preferences or required constraints.
+ ModelPolicy *AgentModelPolicy `json:"modelPolicy,omitempty"`
+ // Authored preferred model ids for this agent, in priority order. Runtime model selection
+ // chooses the first available model; omitted means no authored preference.
+ Models []string `json:"models,omitzero"`
// Name of the agent. Use `id` as the stable selection identifier.
Name string `json:"name"`
// Absolute local file path of the agent definition. Only set for file-based agents loaded
@@ -1065,6 +1070,49 @@ type AuthValidationError struct {
// removed.
type AuthValidationErrors []AuthValidationError
+// Current per-window credit limit and consumption for an autopilot objective.
+// Experimental: AutopilotObjectiveCreditLimit is part of an experimental API and may change
+// or be removed.
+type AutopilotObjectiveCreditLimit struct {
+ // Configured AI-credit cap, when one is set.
+ Credits *float64 `json:"credits,omitempty"`
+ // Window consumption in fractional AI credits, for display.
+ CreditsUsed float64 `json:"creditsUsed"`
+ // Exact window consumption in non-negative integer nano-AIU, encoded as a decimal string.
+ CreditsUsedNanoAiu string `json:"creditsUsedNanoAiu"`
+}
+
+// Canonical runtime state for the session's current autopilot objective.
+// Experimental: AutopilotObjectiveGetStateResult is part of an experimental API and may
+// change or be removed.
+type AutopilotObjectiveGetStateResult struct {
+ // Current objective state, or `null` when the session has no objective.
+ State *AutopilotObjectiveState `json:"state"`
+}
+
+// Public, persistence-independent projection of an autopilot objective.
+// Experimental: AutopilotObjectiveState is part of an experimental API and may change or be
+// removed.
+type AutopilotObjectiveState struct {
+ // Optional summary recorded when the objective completed.
+ CompletionSummary *string `json:"completionSummary,omitempty"`
+ // Exact lifetime AI-credit consumption in non-negative integer nano-AIU, encoded as a
+ // decimal string.
+ CreditCountNanoAiu string `json:"creditCountNanoAiu"`
+ // Current per-window consumption and optional cap, when a credit-tracking window is present.
+ CreditLimit *AutopilotObjectiveCreditLimit `json:"creditLimit,omitempty"`
+ // Session-local objective identifier.
+ ID int64 `json:"id"`
+ // User-provided objective text.
+ Objective string `json:"objective"`
+ // Optional reason the objective is paused.
+ PauseReason *string `json:"pauseReason,omitempty"`
+ // Current normalized lifecycle status.
+ Status AutopilotObjectiveStatus `json:"status"`
+ // Number of objective turns started.
+ TurnCount int64 `json:"turnCount"`
+}
+
// The running runtime's complete catalog of well-known built-in model IDs, including
// supported models and additional IDs with built-in metadata.
// Experimental: BuiltInModelCatalog is part of an experimental API and may change or be
@@ -1361,9 +1409,12 @@ type CanvasSessionContext struct {
// Experimental: CapiSessionOptions is part of an experimental API and may change or be
// removed.
type CapiSessionOptions struct {
- // Routing preference used when the session model is `auto`. The runtime persists the
- // preference across cold resume. When omitted, the default routing behavior is used.
- // Resuming an already-resident session cannot change its preference.
+ // Routing preference for sessions whose model is `auto`. On create or cold resume, this
+ // establishes the preference sent as `tier` on CAPI `/auto` requests; when omitted on cold
+ // resume, the runtime restores the last committed preference. On resident resume, a
+ // different value requests a safe switch after resume succeeds and cannot change an
+ // in-flight turn. Successful switches are persisted for later cold resume. When no
+ // preference is supplied or restored, CAPI default routing is used.
AutoTier *AutoTier `json:"autoTier,omitempty"`
// Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when
// the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses
@@ -1604,7 +1655,8 @@ type CatalogSearchRequest struct {
Kinds []CatalogCandidateKind `json:"kinds,omitzero"`
// Maximum number of candidates to return. Defaults to 10 when omitted.
Limit *int32 `json:"limit,omitempty"`
- // Free-text search query. Never written to logs or telemetry.
+ // Free-text search query. Persisted as tool input for session continuity, but omitted from
+ // telemetry.
Query string `json:"query"`
}
@@ -1737,6 +1789,10 @@ type CatalogNetworkFailureError struct {
Message string `json:"message"`
// Categorised failure, low cardinality so it can be aggregated without carrying a URL.
Reason CatalogNetworkFailureReason `json:"reason"`
+ // Bounded cooldown in seconds before another catalog request should be attempted, when the
+ // authority supplied a numeric Retry-After value or the runtime applied its documented
+ // fallback.
+ RetryAfterSeconds *int32 `json:"retryAfterSeconds,omitempty"`
// HTTP status code, when the failure was a rejected response.
StatusCode *int32 `json:"statusCode,omitempty"`
}
@@ -1840,6 +1896,30 @@ func (CatalogUnsupportedKindError) Kind() CatalogSearchResultKind {
return CatalogSearchResultKindUnsupportedKind
}
+// Runtime-to-owner cancellation request for a client-owned task.
+// Experimental: ClientTaskCancelRequest is part of an experimental API and may change or be
+// removed.
+type ClientTaskCancelRequest struct {
+ // Opaque identifier shared by coalesced cancellation callers
+ CancellationID string `json:"cancellationId"`
+ // Owner-scoped task key included for correlation
+ ClientTaskID string `json:"clientTaskId"`
+ // Canonical runtime-generated task identifier
+ ID string `json:"id"`
+ // Reason the runtime requests cancellation
+ Reason ClientTaskCancelReason `json:"reason"`
+ // Session that owns the client task
+ SessionID string `json:"sessionId"`
+}
+
+// Whether the client authoritatively confirmed its external work stopped.
+// Experimental: ClientTaskCancelResult is part of an experimental API and may change or be
+// removed.
+type ClientTaskCancelResult struct {
+ // True only when the owner confirms that external work stopped before responding
+ Cancelled bool `json:"cancelled"`
+}
+
// Slash commands available in the session, after applying any include/exclude filters.
// Experimental: CommandList is part of an experimental API and may change or be removed.
type CommandList struct {
@@ -2063,6 +2143,9 @@ type ConnectRequest struct {
// using the process-global gate for ordinary events and an explicit session-scoped decision
// for host-only events.
EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"`
+ // Task kinds this connection can decode when observing session tasks. Omit to retain agent
+ // and shell compatibility.
+ SupportedTaskKinds []TaskKind `json:"supportedTaskKinds,omitzero"`
// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN
Token *string `json:"token,omitempty"`
}
@@ -2075,6 +2158,8 @@ type ConnectResult struct {
Ok bool `json:"ok"`
// Server protocol version number
ProtocolVersion int64 `json:"protocolVersion"`
+ // Task kinds the server may return to this connection.
+ TaskKinds []TaskKind `json:"taskKinds,omitzero"`
// Server package version
Version string `json:"version"`
}
@@ -2331,15 +2416,24 @@ type CopilotUserResponseQuotaSnapshotsPremiumInteractions struct {
Unlimited *bool `json:"unlimited,omitempty"`
}
-// The currently selected model, reasoning effort, and context tier for the session. The
-// context tier reflects `Session.getContextTier()`, restored from the session journal on
-// resume.
+// The session's authoritative model snapshot. Auto preference fields are configuration for
+// the virtual `auto` model and do not change the selected model identifier. The context
+// tier reflects `Session.getContextTier()`, restored from the session journal on resume.
// Experimental: CurrentModel is part of an experimental API and may change or be removed.
type CurrentModel struct {
+ // Auto preference currently claimed by an in-progress activation. Null means the activation
+ // is returning to provider-default routing.
+ ActivatingAutoTier *AutoTier `json:"activatingAutoTier,omitempty"`
+ // Auto preference currently committed for the session. This can remain available while
+ // another model is selected so a later switch to `auto` can reuse it.
+ AutoTier *AutoTier `json:"autoTier,omitempty"`
// Context tier for models that support multiple context-window sizes.
ContextTier *ContextTier `json:"contextTier,omitempty"`
// Currently active model identifier
ModelID *string `json:"modelId,omitempty"`
+ // Latest unclaimed Auto preference waiting for a future user turn. Null means the pending
+ // request is returning to provider-default routing.
+ PendingAutoTier *AutoTier `json:"pendingAutoTier,omitempty"`
// Reasoning effort level currently applied to the active model, when one is set. Reads
// `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the
// two values are reported as a snapshot.
@@ -2577,6 +2671,36 @@ type DiscoveredExtensionsEnableRequest struct {
IDs []string `json:"ids"`
}
+// One server-discovered hook action from user, repository, plugin, or managed-policy
+// configuration.
+// Experimental: DiscoveredHook is part of an experimental API and may change or be removed.
+type DiscoveredHook struct {
+ // Durable content hash used by hook enablement. Identical actions may intentionally share
+ // this key. Omitted when changing the user's disabled-hooks setting cannot change the
+ // action's current server-discovered state, including managed-policy hooks, session-start
+ // prompt actions, actions suppressed by disable-all settings, and projectless plugin
+ // actions that require project-directory expansion.
+ DisableKey *string `json:"disableKey,omitempty"`
+ // Whether this action is enabled under the server-side discovery settings. Concrete
+ // sessions may differ because they can add session-specific directories, plugins, or trust.
+ // False when its disable key is present in the user's disabled-hooks setting or disable-all
+ // settings suppress the action.
+ Enabled bool `json:"enabled"`
+ // Hook event that invokes this action.
+ HookType HookType `json:"hookType"`
+ // Deterministic identifier for this server-discovered action row. It remains stable while
+ // the project, origin, source, event, action content, and duplicate ordinal are unchanged.
+ // This is row identity, not the key persisted in disabledHooks.
+ ID string `json:"id"`
+ // Configuration tier that contributed this hook action.
+ Origin HookOrigin `json:"origin"`
+ // Input project path for which this server-side action was resolved. Set on every row
+ // returned for project-scoped discovery, including repeated user and policy actions.
+ ProjectPath *string `json:"projectPath,omitempty"`
+ // Human-readable source label, such as a hook file path, settings source, or plugin name.
+ Source *string `json:"source,omitempty"`
+}
+
// MCP server discovered by `mcp.discover`, with config source, optional plugin source,
// transport type, and enabled state.
// Experimental: DiscoveredMCPServer is part of an experimental API and may change or be
@@ -3564,6 +3688,18 @@ func (FactoryRunFailureFactoryLimitReached) Type() FactoryRunFailureType {
return FactoryRunFailureTypeFactoryLimitReached
}
+// The extension that owns the factory disconnected while the run was executing, so the host
+// halted it. The run's journaled subagent results are preserved so a resume can reuse them.
+type FactoryRunFailureFactoryProviderDisconnected struct {
+ // Factory run identifier.
+ RunID string `json:"runId"`
+}
+
+func (FactoryRunFailureFactoryProviderDisconnected) factoryRunFailure() {}
+func (FactoryRunFailureFactoryProviderDisconnected) Type() FactoryRunFailureType {
+ return FactoryRunFailureTypeFactoryProviderDisconnected
+}
+
type FactoryRunFailureFactoryResumeDeclined struct {
// Human-readable reason the resume did not proceed.
Reason string `json:"reason"`
@@ -3609,9 +3745,12 @@ type FactoryRunRequest struct {
// Experimental: FactoryRunResult is part of an experimental API and may change or be
// removed.
type FactoryRunResult struct {
+ // One-based execution attempt represented by this envelope. Absent before the first attempt
+ // starts or when returned by an older runtime.
+ Attempt *int64 `json:"attempt,omitempty"`
// Error message for an errored run.
Error *string `json:"error,omitempty"`
- // Machine-readable failure details for an errored run.
+ // Machine-readable failure details for a halted or errored run.
Failure FactoryRunFailure `json:"failure,omitempty"`
// Reason for a halted or cancelled run.
Reason *string `json:"reason,omitempty"`
@@ -4209,8 +4348,6 @@ type HistoryTruncateResult struct {
// removed.
// Internal: HookInvokeRequest is an internal SDK API and is not part of the public surface.
type HookInvokeRequest struct {
- // Internal: HookType is part of the SDK's internal API surface and is not intended for
- // external use.
HookType HookType `json:"hookType"`
Input any `json:"input"`
SessionID string `json:"sessionId"`
@@ -4224,6 +4361,40 @@ type HookInvokeResponse struct {
Output any `json:"output,omitempty"`
}
+// Optional project paths and host-exclusion behavior for server-scoped hook discovery.
+// Experimental: HooksDiscoverRequest is part of an experimental API and may change or be
+// removed.
+type HooksDiscoverRequest struct {
+ // When true, omit host-owned user and plugin hook rows and their diagnostics.
+ // Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks
+ // still contribute to each remaining row's effective enabled state. This filters sources
+ // rather than simulating a host with no settings.
+ ExcludeHostHooks *bool `json:"excludeHostHooks,omitempty"`
+ // Optional project directory paths whose trusted repository and project-expanded plugin
+ // hooks should be discovered. When omitted or empty, user, managed-policy, and globally
+ // enabled installed or explicit plugin hooks are returned without project expansion.
+ ProjectPaths []string `json:"projectPaths,omitzero"`
+}
+
+// Server-discovered hook actions and partial-load diagnostics from user, repository,
+// plugin, and managed-policy sources. Concrete sessions may include additional
+// session-specific hook sources.
+// Experimental: HooksDiscoverResult is part of an experimental API and may change or be
+// removed.
+type HooksDiscoverResult struct {
+ // Errors for hook sources or actions that could not be loaded, making the result partially
+ // incomplete. Other valid actions are still returned. Project-resolution and
+ // repository-settings errors are prefixed with their project path.
+ Errors []string `json:"errors"`
+ // All discovered hook actions. Byte-identical actions remain separate rows even when they
+ // share a disable key.
+ Hooks []DiscoveredHook `json:"hooks"`
+ // Non-fatal source-loading warnings. Discovery remains complete for the affected source,
+ // although the source had a recoverable issue. Repository-settings warnings are prefixed
+ // with their project path when attribution is available.
+ Warnings []string `json:"warnings"`
+}
+
// Installed plugin record from global state, with marketplace, version, install time,
// enabled state, cache path, and source.
// Experimental: InstalledPlugin is part of an experimental API and may change or be removed.
@@ -4685,6 +4856,11 @@ type LspInitializeRequest struct {
WorkingDirectory *string `json:"workingDirectory,omitempty"`
}
+// Experimental: ManagedSettingsClearCacheResult is part of an experimental API and may
+// change or be removed.
+type ManagedSettingsClearCacheResult struct {
+}
+
// Validated device-managed settings discovered before a session exists.
// Experimental: ManagedSettingsReadResult is part of an experimental API and may change or
// be removed.
@@ -5035,6 +5211,8 @@ type MCPConfigReloadResult struct {
// Experimental: MCPConfigRemoveRequest is part of an experimental API and may change or be
// removed.
type MCPConfigRemoveRequest struct {
+ // OAuth Client ID Metadata Document URL whose persisted credentials should also be removed.
+ AuthClientIDMetadataURL *string `json:"authClientIdMetadataUrl,omitempty"`
// Name of the MCP server to remove
Name string `json:"name"`
}
@@ -6387,6 +6565,10 @@ type MCPServer struct {
Error *string `json:"error,omitempty"`
// Server name (config key)
Name string `json:"name"`
+ // Server-advertised metadata for a connected server. Omitted when no live connection
+ // metadata is available, including while pending or when failed, disabled, stopped, or not
+ // configured.
+ ServerMetadata *MCPServerMetadata `json:"serverMetadata,omitempty"`
// Configuration source: user, workspace, plugin, or builtin
Source *MCPServerSource `json:"source,omitempty"`
// Plugin name that provided this server, when source is plugin.
@@ -6559,6 +6741,15 @@ type MCPServerList struct {
Servers []MCPServer `json:"servers"`
}
+// Server-advertised metadata learned through modern discovery or legacy initialization.
+// Experimental: MCPServerMetadata is part of an experimental API and may change or be
+// removed.
+type MCPServerMetadata struct {
+ // Non-empty natural-language guidance for using the server, or null when the server omitted
+ // instructions or advertised an empty string.
+ Instructions *string `json:"instructions"`
+}
+
// Recorded MCP server pending-auth state.
// Experimental: MCPServerNeedsAuthInfo is part of an experimental API and may change or be
// removed.
@@ -6846,6 +7037,10 @@ type Model struct {
// a recommended alternative. Present only when the service published at least one notice.
// Hosts should surface these without implying anything is wrong with the model.
InfoMessages []ModelMessage `json:"infoMessages,omitzero"`
+ // Provider-supplied model metadata. Keys and JSON-compatible values are preserved
+ // unchanged. This is factual metadata published by the model provider; it carries no picker
+ // or UX semantics.
+ Metadata map[string]any `json:"metadata,omitzero"`
// Model capability category for grouping in the model picker
ModelPickerCategory *ModelPickerCategory `json:"modelPickerCategory,omitempty"`
// Relative cost tier for token-based billing users
@@ -6880,6 +7075,10 @@ type ModelApplyStartupOverlayRequest struct {
DeferredResume *bool `json:"deferredResume,omitempty"`
// Model required by device-managed policy, when configured.
DeviceManagedModel *string `json:"deviceManagedModel,omitempty"`
+ // Startup default model from the enterprise policy helper, when configured. Weakest of the
+ // managed sources: it applies only when neither device nor server policy names a model, and
+ // an explicit user selection still wins.
+ PolicyHelperModel *string `json:"policyHelperModel,omitempty"`
// Context tier selected by repository settings, when configured.
RepoContextTier *string `json:"repoContextTier,omitempty"`
// Model selected by repository settings, when configured.
@@ -6921,6 +7120,12 @@ type ModelBillingPromo struct {
// Human-readable promotion message. Does not include the expiry timestamp; consumers may
// format endsAt and append it when present.
Message *string `json:"message,omitempty"`
+ // Whether the service asked hosts to give this promotion a prominent surface, such as a
+ // dedicated banner, in addition to listing it with the model. `true` requests that surface
+ // and `false` asks for the model list only. Absent means the service expressed no
+ // preference — for example a response that predates the field — so hosts should apply their
+ // own default rather than read it as `false`.
+ ShowBanner *bool `json:"showBanner,omitempty"`
}
// Token-level pricing information for this model
@@ -7164,6 +7369,38 @@ type ModelsListRequest struct {
SelectionID *string `json:"selectionId,omitempty"`
}
+// An Auto preference request for the session. This updates Auto configuration only; it does
+// not change the selected model to `auto`.
+// Experimental: ModelSwitchAutoTierRequest is part of an experimental API and may change or
+// be removed.
+type ModelSwitchAutoTierRequest struct {
+ // Auto preference to activate when a future user turn using the `auto` model safely mints a
+ // replacement model and token pair. Pass null to return to provider-default Auto routing.
+ AutoTier *AutoTier `json:"autoTier"`
+ // Origin to record on the effective `session.model_change` event. Defaults to `sdk` when
+ // omitted.
+ Source *ModelChangeSource `json:"source,omitempty"`
+}
+
+// Immediate acknowledgement and Auto preference snapshot after a switch request. This
+// result never implies that a pending preference committed.
+// Experimental: ModelSwitchAutoTierResult is part of an experimental API and may change or
+// be removed.
+type ModelSwitchAutoTierResult struct {
+ // Auto preference currently claimed by an in-progress activation. Null means the activation
+ // is returning to provider-default routing.
+ ActivatingAutoTier *AutoTier `json:"activatingAutoTier,omitempty"`
+ // Auto preference currently committed for the session.
+ EffectiveAutoTier *AutoTier `json:"effectiveAutoTier,omitempty"`
+ // Latest unclaimed Auto preference waiting for a future user turn.
+ PendingAutoTier *AutoTier `json:"pendingAutoTier,omitempty"`
+ // Immediate request status. `pending` means accepted but not committed.
+ Status ModelSwitchAutoTierStatus `json:"status"`
+ // Earlier unclaimed preference replaced by this request. This can be present with either
+ // status, including when selecting the effective preference cancels pending work.
+ SupersededAutoTier *AutoTier `json:"supersededAutoTier,omitempty"`
+}
+
// Experimental: ModelSwitchConfirmation is part of an experimental API and may change or be
// removed.
type ModelSwitchConfirmation struct {
@@ -7180,6 +7417,10 @@ type ModelSwitchConfirmation struct {
// Experimental: ModelSwitchToRequest is part of an experimental API and may change or be
// removed.
type ModelSwitchToRequest struct {
+ // Optional Auto routing preference to stage atomically with selecting `auto`. Pass null to
+ // return to provider-default Auto routing. This field is rejected when `modelId` is not
+ // `auto`.
+ AutoTier **AutoTier `json:"autoTier,omitempty"`
// Explicit response to a model-switch compaction preflight. Omit to request a confirmation
// projection when compaction is necessary.
CompactionDecision *string `json:"compactionDecision,omitempty"`
@@ -7214,8 +7455,8 @@ type ModelSwitchToRequest struct {
RequireAvailable *bool `json:"requireAvailable,omitempty"`
// When true, evaluate context-window compaction policy before applying the switch.
RunCompactionPreflight *bool `json:"runCompactionPreflight,omitempty"`
- // Origin to record on the effective `session.model_change` event. Defaults to `sdk` when
- // omitted.
+ // Origin to record on the effective `session.model_change` event for trusted in-process
+ // calls. Transport SDK calls are always recorded as `sdk`, regardless of this value.
Source *ModelChangeSource `json:"source,omitempty"`
// Output verbosity level to request for supported models
Verbosity *Verbosity `json:"verbosity,omitempty"`
@@ -7238,6 +7479,9 @@ type ModelSwitchToResult struct {
Message *string `json:"message,omitempty"`
// Currently active model identifier after the switch
ModelID *string `json:"modelId,omitempty"`
+ // Authoritative model and Auto preference state after an immediate switch. For deferred
+ // switches this remains the current state until the queued change drains.
+ ModelState *CurrentModel `json:"modelState,omitempty"`
// Persistence failure encountered after applying the model switch.
PersistenceError *string `json:"persistenceError,omitempty"`
// Lifecycle result for the requested switch
@@ -8823,6 +9067,8 @@ type PluginInstallResult struct {
PostInstallMessage *string `json:"postInstallMessage,omitempty"`
// Number of skills discovered and installed from the plugin
SkillsInstalled int64 `json:"skillsInstalled"`
+ // Where the completed plugin tree was staged before atomic promotion
+ StagingMode *PluginInstallStagingMode `json:"stagingMode,omitempty"`
}
// Plugins installed for the session, with their enabled state and version metadata.
@@ -9795,6 +10041,9 @@ type QueuePendingItems struct {
ID string `json:"id"`
// Whether this item is a queued user message or a queued slash command / model change
Kind QueuePendingItemsKind `json:"kind"`
+ // Stable identity of the queued user message. Present for message rows and absent for slash
+ // commands and model changes.
+ MessageID *string `json:"messageId,omitempty"`
}
// Snapshot of the session's pending queued items and immediate-steering messages.
@@ -10216,6 +10465,12 @@ type RuntimeShutdownResult struct {
type SandboxConfig struct {
// Whether to auto-add the current working directory to readwritePaths. Default: true.
AddCurrentWorkingDirectory *bool `json:"addCurrentWorkingDirectory,omitempty"`
+ // Whether the agent may request that an individual command run outside the sandbox, which
+ // the host then approves or denies through the usual permission flow. A host capability
+ // flag rather than part of the policy: it is stripped from the effective spawn policy and
+ // only has an effect while `enabled` is true. Fail-closed, unlike the opt-out flags on this
+ // object: omitting it offers no bypass. Default: false (opt-in).
+ AllowBypass *bool `json:"allowBypass,omitempty"`
// Whether to auto-grant read access to tool directories discovered on PATH and in toolchain
// environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common
// developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the
@@ -10232,6 +10487,27 @@ type SandboxConfig struct {
Auth *SandboxConfigAuth `json:"auth,omitempty"`
// Whether sandboxing is enabled for the session.
Enabled bool `json:"enabled"`
+ // The `sandboxLspServers` counterpart of `managedMcpRoutingLocked`.
+ // Internal: ManagedLspRoutingLocked is part of the SDK's internal API surface and is not
+ // intended for external use.
+ ManagedLspRoutingLocked *bool `json:"managedLspRoutingLocked,omitempty"`
+ // Set by the runtime when a managed policy forced `sandboxMcpServers` on and took the local
+ // opt-out away. Provenance rather than policy: it lets a sandbox startup failure point at
+ // the administrator instead of a setting the next managed merge would override, and it is
+ // ignored when comparing two configs for change. Only the managed merge may set it; a
+ // caller-supplied value is stripped.
+ // Internal: ManagedMCPRoutingLocked is part of the SDK's internal API surface and is not
+ // intended for external use.
+ ManagedMCPRoutingLocked *bool `json:"managedMcpRoutingLocked,omitempty"`
+ // Whether language servers the session launches are confined by the sandbox. Only an
+ // explicit `false` opts out. Ignored while `enabled` is false. Default: true (enabled by
+ // default; set to false to opt out).
+ SandboxLspServers *bool `json:"sandboxLspServers,omitempty"`
+ // Whether MCP servers the session launches are confined by the sandbox. Only an explicit
+ // `false` opts out; doing so also lets remote-MCP egress leave the sandbox, so the flag and
+ // `enabled` are always read together. Ignored while `enabled` is false. Default: true
+ // (enabled by default; set to false to opt out).
+ SandboxMCPServers *bool `json:"sandboxMcpServers,omitempty"`
// User-managed sandbox policy fragment merged into the auto-discovered base policy.
UserPolicy *SandboxConfigUserPolicy `json:"userPolicy,omitempty"`
}
@@ -10306,11 +10582,14 @@ type SandboxConfigUserPolicyNetwork struct {
AllowLocalNetwork *bool `json:"allowLocalNetwork,omitempty"`
// Whether outbound network traffic is allowed at all.
AllowOutbound *bool `json:"allowOutbound,omitempty"`
- // HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and
- // cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS.
- // Credentials go in the separate `username`/`password` fields. A credential-free http://
- // loopback proxy URL is routed through the localhost proxy automatically; an https:// or
- // authenticated loopback URL is used as-is.
+ // HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint,
+ // requires that endpoint to be reachable over IPv4 (the [::] dual-stack wildcard is
+ // accepted and routed through the IPv4 gateway), and does not support proxy credentials.
+ // macOS relies on applications honoring proxy environment variables. Windows also
+ // configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's
+ // networking stack. Configure supported credentials in the separate `username` and
+ // `password` fields. A credential-free http:// loopback URL uses the localhost proxy form,
+ // while an https:// or authenticated loopback URL uses the URL form.
Proxy *SandboxConfigUserPolicyNetworkProxy `json:"proxy,omitempty"`
}
@@ -10326,12 +10605,12 @@ type SandboxConfigUserPolicyNetworkProxy struct {
// settings.json); the field is masked in the dialog and redacted by /settings show.
Password *string `json:"password,omitempty"`
// Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the
- // scheme's standard port when omitted. Credentials must not be embedded here — a
- // `user:pass@` authority is rejected; put them in the separate `username`/`password`
- // fields. A credential-free http:// loopback URL is routed through the localhost proxy
- // automatically; loopback covers localhost and any *.localhost subdomain, the whole
- // 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or
- // one with a username/password set, is used as-is.
+ // scheme's standard port when omitted; an explicit port must be between 1 and 65535.
+ // Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in
+ // the separate `username`/`password` fields. A credential-free http:// loopback proxy URL
+ // is routed through the localhost proxy automatically; loopback covers localhost and any
+ // *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback
+ // (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is.
URL string `json:"url"`
// Optional username for proxy authentication. Combined with the URL (and `password`) into
// `user:pass@host` when the sandboxed process routes through the proxy.
@@ -11891,6 +12170,8 @@ type SessionOpenOptions struct {
AllowAllMCPServerInstructions *bool `json:"allowAllMcpServerInstructions,omitempty"`
// Whether ask_user is explicitly disabled.
AskUserDisabled *bool `json:"askUserDisabled,omitempty"`
+ // OAuth Client ID Metadata Document URL used by this host for MCP authorization.
+ AuthClientIDMetadataURL *string `json:"authClientIdMetadataUrl,omitempty"`
// Initial authentication info for the session.
AuthInfo AuthInfo `json:"authInfo,omitempty"`
// Allowlist of available tool names.
@@ -11954,6 +12235,9 @@ type SessionOpenOptions struct {
EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"`
// Whether shell-script safety heuristics are enabled.
EnableScriptSafety *bool `json:"enableScriptSafety,omitempty"`
+ // Whether skill loading is enabled. When omitted, an SDK skill provider enables skills by
+ // default.
+ EnableSkills *bool `json:"enableSkills,omitempty"`
// Whether model responses stream as delta events.
EnableStreaming *bool `json:"enableStreaming,omitempty"`
// How MCP server environment values are interpreted.
@@ -11977,6 +12261,16 @@ type SessionOpenOptions struct {
ExpAssignments any `json:"expAssignments,omitempty"`
// Feature-flag values resolved by the host.
FeatureFlags map[string]bool `json:"featureFlags,omitzero"`
+ // Whether the requesting SDK session has a skill provider. The provider remains ephemeral
+ // and is never persisted in session options or history. When enableSkills is false, it
+ // remains bound but dormant and receives no callbacks. Cloud, relay, handoff, and raw
+ // sessions.open flows reject it because they cannot safely pre-register the callback
+ // handler.
+ // Experimental: HasSkillProvider is part of an experimental API and may change or be
+ // removed.
+ // Internal: HasSkillProvider is part of the SDK's internal API surface and is not intended
+ // for external use.
+ HasSkillProvider *bool `json:"hasSkillProvider,omitempty"`
// Built-in subagent names to include in this session. When specified, only these built-ins
// are available, subject to runtime availability and exclusions. Custom agents with the
// same name remain available.
@@ -12872,6 +13166,22 @@ type SessionsPruneOldRequest struct {
OlderThanDays int64 `json:"olderThanDays"`
}
+// Pagination options for reading an inactive or active local session's persisted event
+// journal.
+// Experimental: SessionsReadPersistedEventsRequest is part of an experimental API and may
+// change or be removed.
+type SessionsReadPersistedEventsRequest struct {
+ // Opaque cursor returned by a previous persisted-event read. Omit on the first call.
+ Cursor *string `json:"cursor,omitempty"`
+ // Direction to page through persisted history. Forward starts at the beginning; backward
+ // starts with the newest events. Events in each page remain chronological.
+ Direction *EventsReadDirection `json:"direction,omitempty"`
+ // Maximum number of events to return in this batch (1–1000, default 200).
+ Max *int64 `json:"max,omitempty"`
+ // Session ID whose persisted event journal should be read.
+ SessionID string `json:"sessionId"`
+}
+
// Optional registration options.
// Experimental: SessionsRegisterExtensionToolsOnSessionOptions is part of an experimental
// API and may change or be removed.
@@ -13060,8 +13370,9 @@ type SessionUpdateOptionsParams struct {
EnableScriptSafety *bool `json:"enableScriptSafety,omitempty"`
// Whether to enable cross-session store writes and reads.
EnableSessionStore *bool `json:"enableSessionStore,omitempty"`
- // Whether to enable skill directory scanning and loading. Falls back to
- // enableConfigDiscovery when unset.
+ // Whether skill loading is enabled. Explicit false disables every source, including a bound
+ // SDK provider; changing the value invalidates the loaded skill snapshot. When omitted,
+ // creation falls back to enableConfigDiscovery unless an SDK skill provider is registered.
EnableSkills *bool `json:"enableSkills,omitempty"`
// Whether to stream model responses.
EnableStreaming *bool `json:"enableStreaming,omitempty"`
@@ -13479,6 +13790,66 @@ type SkillList struct {
Skills []Skill `json:"skills"`
}
+// Catalog-only metadata for one SDK-provided skill. The complete SKILL.md is fetched
+// separately and lazily.
+// Experimental: SkillProviderDescriptor is part of an experimental API and may change or be
+// removed.
+type SkillProviderDescriptor struct {
+ // Optional freeform argument hint used by slash-command catalogs.
+ ArgumentHint *string `json:"argumentHint,omitempty"`
+ // Description used in skill catalogs without fetching content.
+ Description string `json:"description"`
+ // Whether model invocation is disabled. Defaults to false.
+ DisableModelInvocation *bool `json:"disableModelInvocation,omitempty"`
+ // Invocation and display name.
+ Name string `json:"name"`
+ // Whether users may invoke the skill directly. Defaults to true.
+ UserInvocable *bool `json:"userInvocable,omitempty"`
+}
+
+// Identifies the target session.
+// Experimental: SkillProviderListRequest is part of an experimental API and may change or
+// be removed.
+type SkillProviderListRequest struct {
+ // Target session identifier
+ SessionID string `json:"sessionId"`
+}
+
+// Catalog metadata returned by an SDK session's skill provider. Catalogs are limited to
+// 1024 descriptors and 1 MiB of aggregate metadata.
+// Experimental: SkillProviderListResult is part of an experimental API and may change or be
+// removed.
+// Internal: SkillProviderListResult is an internal SDK API and is not part of the public
+// surface.
+type SkillProviderListResult struct {
+ // Skill descriptors in provider order. Invocation names must be unique under
+ // case-insensitive comparison.
+ Skills []SkillProviderDescriptor `json:"skills"`
+}
+
+// Identifies one SDK-provided skill by invocation name.
+// Experimental: SkillProviderReadRequest is part of an experimental API and may change or
+// be removed.
+// Internal: SkillProviderReadRequest is an internal SDK API and is not part of the public
+// surface.
+type SkillProviderReadRequest struct {
+ // Invocation name of the skill to read.
+ Name string `json:"name"`
+ // Target session identifier
+ SessionID string `json:"sessionId"`
+}
+
+// Complete text-only SKILL.md content returned by an SDK session's skill provider. Related
+// files and assets are not supported.
+// Experimental: SkillProviderReadResult is part of an experimental API and may change or be
+// removed.
+// Internal: SkillProviderReadResult is an internal SDK API and is not part of the public
+// surface.
+type SkillProviderReadResult struct {
+ // Complete SKILL.md text. The runtime enforces a 1 MiB UTF-8 byte limit.
+ Markdown string `json:"markdown"`
+}
+
// Skill names to mark as disabled in global configuration, replacing any previous list.
// Experimental: SkillsConfigSetDisabledSkillsRequest is part of an experimental API and may
// change or be removed.
@@ -13565,11 +13936,14 @@ type SkillsInvokedSkill struct {
AllowedTools []string `json:"allowedTools,omitzero"`
// Full content of the skill file
Content string `json:"content"`
+ // Whether model invocation was disabled when this skill was invoked
+ DisableModelInvocation *bool `json:"disableModelInvocation,omitempty"`
// Turn number when the skill was invoked
InvokedAtTurn int64 `json:"invokedAtTurn"`
// Unique identifier for the skill
Name string `json:"name"`
- // Path to the SKILL.md file
+ // Path to the SKILL.md file, or an empty string for an SDK-provided skill without a
+ // filesystem identity
Path string `json:"path"`
}
@@ -13703,6 +14077,8 @@ func (SlashCommandAgentPromptResult) Kind() SlashCommandInvocationResultKind {
type SlashCommandCompletedResult struct {
// Optional user-facing message describing the completed command
Message *string `json:"message,omitempty"`
+ // Optional target session mode applied without submitting an agent prompt
+ Mode *SessionMode `json:"mode,omitempty"`
// True when the invocation mutated user runtime settings; consumers caching settings should
// refresh
RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"`
@@ -13837,6 +14213,10 @@ type SlashCommandSelectSubcommandOption struct {
// Experimental: SlashCommandTimelineEntry is part of an experimental API and may change or
// be removed.
type SlashCommandTimelineEntry struct {
+ // What the user must do to recover, when the entry reports a failure the runtime knows an
+ // action for. The `text` never names a client affordance, so a client that offers one
+ // renders it from this value.
+ Remediation *RemediationAction `json:"remediation,omitempty"`
// Text displayed for the timeline entry.
Text string `json:"text"`
// Timeline entry presentation type.
@@ -13870,6 +14250,104 @@ type SubagentSettingsEntry struct {
EffortLevel *string `json:"effortLevel,omitempty"`
// Model override for matching subagents
Model *string `json:"model,omitempty"`
+ // Whether the configured model strategy is preferred or required
+ ModelPolicy *AgentModelPolicy `json:"modelPolicy,omitempty"`
+}
+
+// Public owner attribution for a client-owned task. Identifiers are opaque and never
+// authorize requests.
+// Experimental: TaskClientOwner is part of an experimental API and may change or be removed.
+type TaskClientOwner struct {
+ // ISO 8601 timestamp when the bound join disconnected
+ DisconnectedAt *time.Time `json:"disconnectedAt,omitempty"`
+ // Display-only owner name
+ DisplayName *string `json:"displayName,omitempty"`
+ // Opaque identity of the currently or most recently bound session join
+ JoinID string `json:"joinId"`
+ // Class of the task owner
+ Kind TaskClientOwnerKind `json:"kind"`
+ // Opaque session-scoped participant identity
+ ParticipantID string `json:"participantId"`
+ // Whether this task's bound join is currently connected
+ Presence TaskClientOwnerPresence `json:"presence"`
+ // Display-only owner source
+ Source *string `json:"source,omitempty"`
+}
+
+// Progress or terminal update for a client-owned task.
+// Experimental: TaskClientUpdate is part of an experimental API and may change or be
+// removed.
+type TaskClientUpdate interface {
+ taskClientUpdate()
+ Kind() TaskClientUpdateKind
+}
+
+type RawTaskClientUpdateData struct {
+ Discriminator TaskClientUpdateKind
+ Raw json.RawMessage
+}
+
+func (RawTaskClientUpdateData) taskClientUpdate() {}
+func (r RawTaskClientUpdateData) Kind() TaskClientUpdateKind {
+ return r.Discriminator
+}
+
+// Reports terminal cancellation after external work stopped.
+type TaskClientUpdateCancelled struct {
+ // Optional final progress message
+ Message *string `json:"message,omitempty"`
+ // Optional human-readable cancellation reason
+ Reason *string `json:"reason,omitempty"`
+}
+
+func (TaskClientUpdateCancelled) taskClientUpdate() {}
+func (TaskClientUpdateCancelled) Kind() TaskClientUpdateKind {
+ return TaskClientUpdateKindCancelled
+}
+
+// Reports successful terminal completion.
+type TaskClientUpdateCompleted struct {
+ // Optional final progress message
+ Message *string `json:"message,omitempty"`
+ // Optional opaque successful terminal result
+ Result any `json:"result,omitempty"`
+}
+
+func (TaskClientUpdateCompleted) taskClientUpdate() {}
+func (TaskClientUpdateCompleted) Kind() TaskClientUpdateKind {
+ return TaskClientUpdateKindCompleted
+}
+
+// Reports terminal failure.
+type TaskClientUpdateFailed struct {
+ // Optional owner-supplied terminal failure code
+ Code *string `json:"code,omitempty"`
+ // Human-readable terminal failure message
+ Error string `json:"error"`
+ // Optional final progress message
+ Message *string `json:"message,omitempty"`
+}
+
+func (TaskClientUpdateFailed) taskClientUpdate() {}
+func (TaskClientUpdateFailed) Kind() TaskClientUpdateKind {
+ return TaskClientUpdateKindFailed
+}
+
+// Publishes nonterminal progress for a running or idle client task.
+type TaskClientUpdateProgress struct {
+ // Optional progress message appended to recent activity when nonempty
+ Message *string `json:"message,omitempty"`
+ // Optional completion percentage; null clears the current percentage
+ Percentage **float64 `json:"percentage,omitempty"`
+ // Optional progress phase; null clears the current phase
+ Phase **string `json:"phase,omitempty"`
+ // Optional active status transition
+ Status *TaskClientActiveStatus `json:"status,omitempty"`
+}
+
+func (TaskClientUpdateProgress) taskClientUpdate() {}
+func (TaskClientUpdateProgress) Kind() TaskClientUpdateKind {
+ return TaskClientUpdateKindProgress
}
// Task completion notification with summary from the agent
@@ -13911,7 +14389,7 @@ type TaskCompletionDecision struct {
ReviewerResultMeta any `json:"reviewerResultMeta,omitempty"`
}
-// Tracked task union returned by task APIs, containing either an agent task or a shell task.
+// Tracked task union returned by task APIs, containing an agent, client, or shell task.
// Experimental: TaskInfo is part of an experimental API and may change or be removed.
type TaskInfo interface {
taskInfo()
@@ -13980,6 +14458,58 @@ func (TaskAgentInfo) Type() TaskInfoType {
return TaskInfoTypeAgent
}
+// Tracked client-owned task metadata.
+// Experimental: TaskClientInfo is part of an experimental API and may change or be removed.
+type TaskClientInfo struct {
+ // ISO 8601 timestamp when the current active segment started
+ ActiveStartedAt *time.Time `json:"activeStartedAt,omitempty"`
+ // Accumulated active execution time in milliseconds
+ ActiveTimeMs int64 `json:"activeTimeMs"`
+ // Whether the currently bound owner can receive a cancellation request
+ CanCancel bool `json:"canCancel"`
+ // Human-readable reason for terminal cancellation
+ CancellationReason *string `json:"cancellationReason,omitempty"`
+ // Owner-scoped registration and reclaim key
+ ClientTaskID string `json:"clientTaskId"`
+ // ISO 8601 timestamp when the task reached a terminal status
+ CompletedAt *time.Time `json:"completedAt,omitempty"`
+ // Task description
+ Description string `json:"description"`
+ // Optional task display name
+ DisplayName *string `json:"displayName,omitempty"`
+ // Human-readable terminal failure message
+ Error *string `json:"error,omitempty"`
+ // Optional owner-supplied terminal failure code
+ ErrorCode *string `json:"errorCode,omitempty"`
+ // Execution mode, which is always background for client-owned tasks
+ ExecutionMode TaskClientExecutionMode `json:"executionMode"`
+ // Canonical runtime-generated task identifier
+ ID string `json:"id"`
+ // ISO 8601 timestamp when the connected owner entered idle status
+ IdleSince *time.Time `json:"idleSince,omitempty"`
+ // ISO 8601 timestamp of the most recent orphan transition
+ OrphanedAt *time.Time `json:"orphanedAt,omitempty"`
+ // Public attribution and presence for the task owner
+ Owner TaskClientOwner `json:"owner"`
+ // ISO 8601 timestamp of the most recent successful reclaim
+ ReclaimedAt *time.Time `json:"reclaimedAt,omitempty"`
+ // Opaque successful terminal result supplied by the task owner
+ Result any `json:"result,omitempty"`
+ // Sequence number of the latest accepted owner update
+ Sequence int64 `json:"sequence"`
+ // ISO 8601 timestamp when the task started
+ StartedAt time.Time `json:"startedAt"`
+ // Client task lifecycle status
+ Status TaskClientStatus `json:"status"`
+ // ISO 8601 timestamp of the latest accepted lifecycle change
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+func (TaskClientInfo) taskInfo() {}
+func (TaskClientInfo) Type() TaskInfoType {
+ return TaskInfoTypeClient
+}
+
// Tracked shell task metadata, including ID, command, status, timing, attachment/execution
// mode, log path, and PID.
// Experimental: TaskShellInfo is part of an experimental API and may change or be removed.
@@ -14021,6 +14551,8 @@ type TaskList struct {
Tasks []TaskInfo `json:"tasks"`
}
+// Progress information for the task, discriminated by type. Returns null when no task with
+// this ID is currently tracked.
// Experimental: TaskProgress is part of an experimental API and may change or be removed.
type TaskProgress interface {
taskProgress()
@@ -14053,6 +14585,31 @@ func (TaskAgentProgress) Type() TaskProgressType {
return TaskProgressTypeAgent
}
+// Generic progress for a client-owned task.
+// Experimental: TaskClientProgress is part of an experimental API and may change or be
+// removed.
+type TaskClientProgress struct {
+ // Most recent nonempty progress message
+ LastMessage *string `json:"lastMessage,omitempty"`
+ // Current completion percentage from zero through one hundred
+ Percentage *float64 `json:"percentage,omitempty"`
+ // Current owner-defined progress phase
+ Phase *string `json:"phase,omitempty"`
+ // Recent server-timestamped progress messages
+ RecentActivity []TaskProgressLine `json:"recentActivity"`
+ // Sequence number of the latest accepted owner update
+ Sequence int64 `json:"sequence"`
+ // Current client task lifecycle status
+ Status TaskClientStatus `json:"status"`
+ // ISO 8601 timestamp of the latest accepted lifecycle change
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+func (TaskClientProgress) taskProgress() {}
+func (TaskClientProgress) Type() TaskProgressType {
+ return TaskProgressTypeClient
+}
+
// Progress snapshot for a shell task, with recent stdout/stderr output and optional process
// ID.
// Experimental: TaskShellProgress is part of an experimental API and may change or be
@@ -14156,6 +14713,36 @@ type TasksPromoteToBackgroundResult struct {
type TasksRefreshResult struct {
}
+// Registers or reclaims a client-owned task.
+// Experimental: TasksRegisterRequest is part of an experimental API and may change or be
+// removed.
+type TasksRegisterRequest struct {
+ // Whether the owner supports runtime cancellation requests
+ Cancellable bool `json:"cancellable"`
+ // Owner-scoped idempotency key used for registration and reclaim
+ ClientTaskID string `json:"clientTaskId"`
+ // Human-readable description of the external work
+ Description string `json:"description"`
+ // Optional short display name for the external work
+ DisplayName *string `json:"displayName,omitempty"`
+ // Expected current sequence for idempotent registration or orphan reclaim
+ ExpectedSequence *int64 `json:"expectedSequence,omitempty"`
+ // Task kind
+ Type TaskClientType `json:"type"`
+}
+
+// Result of registering or reclaiming a client-owned task.
+// Experimental: TasksRegisterResult is part of an experimental API and may change or be
+// removed.
+type TasksRegisterResult struct {
+ // True only when this invocation created a new task
+ Created bool `json:"created"`
+ // True only when this invocation reclaimed an orphaned task
+ Reclaimed bool `json:"reclaimed"`
+ // Authoritative registered or reclaimed task
+ Task TaskClientInfo `json:"task"`
+}
+
// Identifier of the completed or cancelled task to remove from tracking.
// Experimental: TasksRemoveRequest is part of an experimental API and may change or be
// removed.
@@ -14220,6 +14807,30 @@ type TasksStartAgentResult struct {
AgentID string `json:"agentId"`
}
+// Updates a client-owned task.
+// Experimental: TasksUpdateRequest is part of an experimental API and may change or be
+// removed.
+type TasksUpdateRequest struct {
+ // Canonical runtime-generated task identifier
+ ID string `json:"id"`
+ // Owner update sequence to apply
+ Sequence int64 `json:"sequence"`
+ // Progress or terminal update payload
+ Update TaskClientUpdate `json:"update"`
+}
+
+// Result of publishing a client-owned task update.
+// Experimental: TasksUpdateResult is part of an experimental API and may change or be
+// removed.
+type TasksUpdateResult struct {
+ // Whether this invocation changed task state
+ Applied bool `json:"applied"`
+ // Whether this invocation repeated the latest accepted update
+ Duplicate bool `json:"duplicate"`
+ // Authoritative task after processing the update
+ Task TaskClientInfo `json:"task"`
+}
+
// Wait until all in-flight background tasks (agents + shells) and any follow-up turns
// scheduled by their completions have settled. Returns when the runtime is fully drained or
// after an internal timeout (default 10 minutes; configurable via
@@ -15681,6 +16292,18 @@ const (
AgentInfoSourceUser AgentInfoSource = "user"
)
+// Whether configured models are advisory preferences or required constraints
+// Experimental: AgentModelPolicy is part of an experimental API and may change or be
+// removed.
+type AgentModelPolicy string
+
+const (
+ // Treat the authored models as advisory preferences that callers may override.
+ AgentModelPolicyPreferred AgentModelPolicy = "preferred"
+ // Require subagent execution to use one of the authored models.
+ AgentModelPolicyRequired AgentModelPolicy = "required"
+)
+
// Kind of attention required when status === "attention". Meaningful only when status ===
// "attention".
// Experimental: AgentRegistryLiveTargetEntryAttentionKind is part of an experimental API
@@ -15868,6 +16491,20 @@ const (
AuthInfoTypeUser AuthInfoType = "user"
)
+// Current normalized autopilot objective lifecycle status.
+// Experimental: AutopilotObjectiveStatus is part of an experimental API and may change or
+// be removed.
+type AutopilotObjectiveStatus string
+
+const (
+ // The objective is actively running.
+ AutopilotObjectiveStatusActive AutopilotObjectiveStatus = "active"
+ // The objective completed.
+ AutopilotObjectiveStatusCompleted AutopilotObjectiveStatus = "completed"
+ // The objective is paused and may be resumed.
+ AutopilotObjectiveStatusPaused AutopilotObjectiveStatus = "paused"
+)
+
// Routing preference used when the session model is `auto`.
// Experimental: AutoTier is part of an experimental API and may change or be removed.
type AutoTier string
@@ -16122,14 +16759,20 @@ const (
CatalogNetworkFailureReasonConnectionRefused CatalogNetworkFailureReason = "connection-refused"
// The authority's name could not be resolved.
CatalogNetworkFailureReasonDns CatalogNetworkFailureReason = "dns"
- // The authority returned a status the runtime treats as a failure.
+ // The authority returned another status the runtime treats as a failure.
CatalogNetworkFailureReasonHTTPStatus CatalogNetworkFailureReason = "http-status"
// No network is available, so nothing was attempted.
CatalogNetworkFailureReasonOffline CatalogNetworkFailureReason = "offline"
+ // The configured proxy returned 407 and requires authentication.
+ CatalogNetworkFailureReasonProxyAuthenticationRequired CatalogNetworkFailureReason = "proxy-authentication-required"
+ // The authority rate-limited requests and supplied or implied a bounded cooldown.
+ CatalogNetworkFailureReasonRateLimited CatalogNetworkFailureReason = "rate-limited"
// A redirect was refused by the runtime's redirect policy.
CatalogNetworkFailureReasonRedirectRejected CatalogNetworkFailureReason = "redirect-rejected"
// The response exceeded the permitted size.
CatalogNetworkFailureReasonResponseTooLarge CatalogNetworkFailureReason = "response-too-large"
+ // The authority returned a transient 5xx response.
+ CatalogNetworkFailureReasonServiceUnavailable CatalogNetworkFailureReason = "service-unavailable"
// The request exceeded its time budget.
CatalogNetworkFailureReasonTimeout CatalogNetworkFailureReason = "timeout"
// The TLS handshake or certificate validation failed.
@@ -16217,6 +16860,18 @@ const (
CatalogUnsafeRetrievalReasonRedirectToBlockedAddress CatalogUnsafeRetrievalReason = "redirect-to-blocked-address"
)
+// Why the runtime requests client-task cancellation.
+// Experimental: ClientTaskCancelReason is part of an experimental API and may change or be
+// removed.
+type ClientTaskCancelReason string
+
+const (
+ // A caller requested task cancellation.
+ ClientTaskCancelReasonCancelRequested ClientTaskCancelReason = "cancel_requested"
+ // The session is shutting down.
+ ClientTaskCancelReasonSessionShutdown ClientTaskCancelReason = "session_shutdown"
+)
+
// Whether a pending slash-command invocation effect was applied or cancelled by the host.
// Experimental: CommandsInvocationEffectOutcome is part of an experimental API and may
// change or be removed.
@@ -16592,6 +17247,7 @@ const (
FactoryRunFailureTypeFactoryAccountingIncomplete FactoryRunFailureType = "factory_accounting_incomplete"
FactoryRunFailureTypeFactoryDurableFailure FactoryRunFailureType = "factory_durable_failure"
FactoryRunFailureTypeFactoryLimitReached FactoryRunFailureType = "factory_limit_reached"
+ FactoryRunFailureTypeFactoryProviderDisconnected FactoryRunFailureType = "factory_provider_disconnected"
FactoryRunFailureTypeFactoryResumeDeclined FactoryRunFailureType = "factory_resume_declined"
)
@@ -16745,7 +17401,24 @@ const (
HMACAuthInfoHostHTTPSGitHubCom HMACAuthInfoHost = "https://github.com"
)
-// Hook event name dispatched through the SDK callback transport.
+// Configuration tier that contributed a discovered hook action.
+// Experimental: HookOrigin is part of an experimental API and may change or be removed.
+type HookOrigin string
+
+const (
+ // Hook provided by an enabled installed or explicit plugin. Projectless rows omit
+ // projectPath and do not expand a project directory.
+ HookOriginPlugin HookOrigin = "plugin"
+ // Hook enforced by centrally managed policy.
+ HookOriginPolicy HookOrigin = "policy"
+ // Hook loaded from repository settings or the repository hook directory.
+ HookOriginRepository HookOrigin = "repository"
+ // Hook loaded from user settings or the user's hook directory.
+ HookOriginUser HookOrigin = "user"
+)
+
+// Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally
+// support callback-only events.
// Experimental: HookType is part of an experimental API and may change or be removed.
type HookType string
@@ -17575,6 +18248,22 @@ const (
ModelPolicyStateUnconfigured ModelPolicyState = "unconfigured"
)
+// Whether the requested preference was already effective or was accepted for later
+// transactional activation.
+// Experimental: ModelSwitchAutoTierStatus is part of an experimental API and may change or
+// be removed.
+type ModelSwitchAutoTierStatus string
+
+const (
+ // The request was accepted but has not committed. A later user turn using the `auto` model
+ // must mint and validate the replacement before it becomes effective.
+ ModelSwitchAutoTierStatusPending ModelSwitchAutoTierStatus = "pending"
+ // The requested preference is already effective. No activation is pending for it, although
+ // this request may have cancelled an earlier unclaimed preference reported in
+ // `supersededAutoTier`.
+ ModelSwitchAutoTierStatusUnchanged ModelSwitchAutoTierStatus = "unchanged"
+)
+
// Why the binary data is absent: it exceeded the inline size limit, or its asset was
// unavailable
// Experimental: OmittedBinaryOmittedReason is part of an experimental API and may change or
@@ -17882,6 +18571,18 @@ const (
PermissionsSetApproveAllSourceUserSetting PermissionsSetApproveAllSource = "user_setting"
)
+// Where completed plugin content was staged before atomic promotion.
+// Experimental: PluginInstallStagingMode is part of an experimental API and may change or
+// be removed.
+type PluginInstallStagingMode string
+
+const (
+ // A sibling of the destination plugin directory, used when external staging is unavailable.
+ PluginInstallStagingModeDestinationSibling PluginInstallStagingMode = "destination_sibling"
+ // A sibling of the installed-plugins root, outside the recursively watched tree.
+ PluginInstallStagingModeExternal PluginInstallStagingMode = "external"
+)
+
// Controls whether the runtime may defer loading an external tool definition.
// Experimental: ProtocolExternalToolDefer is part of an experimental API and may change or
// be removed.
@@ -18031,6 +18732,31 @@ const (
ReasoningSummaryNone ReasoningSummary = "none"
)
+// What the user must do to recover from a failure, named as an action rather than as one
+// client's affordance. The runtime cannot know which affordance a client offers — a slash
+// command, a settings pane, a link — so the accompanying message stays host-agnostic and
+// each client renders its own copy from this value. Absent when the runtime knows of no
+// action the user can take.
+// Experimental: RemediationAction is part of an experimental API and may change or be
+// removed.
+type RemediationAction string
+
+const (
+ // Permit outbound network access in the sandbox policy.
+ RemediationActionAllowSandboxOutbound RemediationAction = "allow_sandbox_outbound"
+ // Review or widen the sandbox policy. The blocked path or host is named by the accompanying
+ // message or by the tool result the action arrived with.
+ RemediationActionReviewSandboxPolicy RemediationAction = "review_sandbox_policy"
+ // Inspect which account is currently authenticated before deciding what to change.
+ RemediationActionShowAccount RemediationAction = "show_account"
+ // Authenticate again with the Copilot backend. The current credential is absent, expired,
+ // or rejected.
+ RemediationActionSignIn RemediationAction = "sign_in"
+ // Authenticate as a different account. The current account exists but lacks access to the
+ // requested resource.
+ RemediationActionSwitchAccount RemediationAction = "switch_account"
+)
+
// State discriminator for RemoteControlStatus.
type RemoteControlStatusState string
@@ -18650,7 +19376,7 @@ const (
SkillDiscoveryScopeProject SkillDiscoveryScope = "project"
)
-// Source location type (e.g., project, personal-copilot, plugin, builtin)
+// Source location type (e.g., project, personal-copilot, plugin, builtin, sdk)
// Experimental: SkillSource is part of an experimental API and may change or be removed.
type SkillSource string
@@ -18669,6 +19395,8 @@ const (
SkillSourcePlugin SkillSource = "plugin"
// Skill defined in the current project's skill directories.
SkillSourceProject SkillSource = "project"
+ // Pathless skill supplied lazily by an SDK skill provider.
+ SkillSourceSDK SkillSource = "sdk"
)
// Optional completion hint for the input (e.g. 'directory' for filesystem path completion)
@@ -18731,6 +19459,89 @@ const (
SubagentSettingsEntryContextTierLongContext SubagentSettingsEntryContextTier = "long_context"
)
+// Active status a client owner may publish with a progress update.
+// Experimental: TaskClientActiveStatus is part of an experimental API and may change or be
+// removed.
+type TaskClientActiveStatus string
+
+const (
+ // The external owner is connected but waiting.
+ TaskClientActiveStatusIdle TaskClientActiveStatus = "idle"
+ // The external owner is actively working.
+ TaskClientActiveStatusRunning TaskClientActiveStatus = "running"
+)
+
+// Client-owned tasks always execute outside the runtime in background mode.
+// Experimental: TaskClientExecutionMode is part of an experimental API and may change or be
+// removed.
+type TaskClientExecutionMode string
+
+const (
+ TaskClientExecutionModeBackground TaskClientExecutionMode = "background"
+)
+
+// Connection class owning a client task.
+// Experimental: TaskClientOwnerKind is part of an experimental API and may change or be
+// removed.
+type TaskClientOwnerKind string
+
+const (
+ // A discovered extension connection owns the task.
+ TaskClientOwnerKindExtension TaskClientOwnerKind = "extension"
+ // A generic SDK connection owns the task.
+ TaskClientOwnerKindSDK TaskClientOwnerKind = "sdk"
+)
+
+// Presence of the task's bound join.
+// Experimental: TaskClientOwnerPresence is part of an experimental API and may change or be
+// removed.
+type TaskClientOwnerPresence string
+
+const (
+ // The bound session join is connected.
+ TaskClientOwnerPresenceConnected TaskClientOwnerPresence = "connected"
+ // The bound session join is disconnected.
+ TaskClientOwnerPresenceDisconnected TaskClientOwnerPresence = "disconnected"
+)
+
+// Lifecycle status of a client-owned task.
+// Experimental: TaskClientStatus is part of an experimental API and may change or be
+// removed.
+type TaskClientStatus string
+
+const (
+ // The owner reported or confirmed cancellation.
+ TaskClientStatusCancelled TaskClientStatus = "cancelled"
+ // The owner reported successful completion.
+ TaskClientStatusCompleted TaskClientStatus = "completed"
+ // The owner reported failure.
+ TaskClientStatusFailed TaskClientStatus = "failed"
+ // The external owner is connected but waiting.
+ TaskClientStatusIdle TaskClientStatus = "idle"
+ // The bound owner join disappeared; external executor state is unknown.
+ TaskClientStatusOrphaned TaskClientStatus = "orphaned"
+ // The external owner is actively working.
+ TaskClientStatusRunning TaskClientStatus = "running"
+)
+
+// Discriminator for a client-owned task.
+// Experimental: TaskClientType is part of an experimental API and may change or be removed.
+type TaskClientType string
+
+const (
+ TaskClientTypeClient TaskClientType = "client"
+)
+
+// Kind discriminator for TaskClientUpdate.
+type TaskClientUpdateKind string
+
+const (
+ TaskClientUpdateKindCancelled TaskClientUpdateKind = "cancelled"
+ TaskClientUpdateKindCompleted TaskClientUpdateKind = "completed"
+ TaskClientUpdateKindFailed TaskClientUpdateKind = "failed"
+ TaskClientUpdateKindProgress TaskClientUpdateKind = "progress"
+)
+
// Semantic result of evaluating a task completion request
// Experimental: TaskCompletionOutcome is part of an experimental API and may change or be
// removed.
@@ -18762,16 +19573,31 @@ const (
type TaskInfoType string
const (
- TaskInfoTypeAgent TaskInfoType = "agent"
- TaskInfoTypeShell TaskInfoType = "shell"
+ TaskInfoTypeAgent TaskInfoType = "agent"
+ TaskInfoTypeClient TaskInfoType = "client"
+ TaskInfoTypeShell TaskInfoType = "shell"
+)
+
+// Closed set of public task kinds a connection can negotiate.
+// Experimental: TaskKind is part of an experimental API and may change or be removed.
+type TaskKind string
+
+const (
+ // Runtime-owned background agent task.
+ TaskKindAgent TaskKind = "agent"
+ // Client-owned externally executed task.
+ TaskKindClient TaskKind = "client"
+ // Runtime-owned shell task.
+ TaskKindShell TaskKind = "shell"
)
// Type discriminator for TaskProgress.
type TaskProgressType string
const (
- TaskProgressTypeAgent TaskProgressType = "agent"
- TaskProgressTypeShell TaskProgressType = "shell"
+ TaskProgressTypeAgent TaskProgressType = "agent"
+ TaskProgressTypeClient TaskProgressType = "client"
+ TaskProgressTypeShell TaskProgressType = "shell"
)
// Whether the shell runs inside a managed PTY session or as an independent background
@@ -19309,6 +20135,32 @@ func (a *ServerExtensionsAPI) Enable(ctx context.Context, params *DiscoveredExte
return &result, nil
}
+// Experimental: ServerHooksAPI contains experimental APIs that may change or be removed.
+type ServerHooksAPI serverAPI
+
+// Discovers hook actions enabled under server-side discovery settings from user,
+// repository, plugin, and managed-policy sources.
+//
+// RPC method: hooks.discover.
+//
+// Parameters: Optional project paths and host-exclusion behavior for server-scoped hook
+// discovery.
+//
+// Returns: Server-discovered hook actions and partial-load diagnostics from user,
+// repository, plugin, and managed-policy sources. Concrete sessions may include additional
+// session-specific hook sources.
+func (a *ServerHooksAPI) Discover(ctx context.Context, params *HooksDiscoverRequest) (*HooksDiscoverResult, error) {
+ raw, err := a.client.Request(ctx, "hooks.discover", params)
+ if err != nil {
+ return nil, err
+ }
+ var result HooksDiscoverResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
// Experimental: ServerInstructionsAPI contains experimental APIs that may change or be
// removed.
type ServerInstructionsAPI serverAPI
@@ -19424,6 +20276,32 @@ func (a *ServerLlmInferenceAPI) SetProvider(ctx context.Context) (*LlmInferenceS
// removed.
type ServerManagedSettingsAPI serverAPI
+// ClearCache force-refreshes enterprise managed settings for every account: wipes the
+// persistent server-policy cache (the whole `/managed-settings` directory) and
+// drops this runtime process's in-memory retained server policy. It does not itself fetch
+// policy — the effect is that the next time a session resolves managed settings for an
+// account, that resolution re-fetches the account's org policy from the network instead of
+// serving a cached response. Note that `managedSettings.read` returns only device/MDM
+// settings and never triggers the account server-policy fetch, so a host implementing "sync
+// account policy" should start a fresh session resolution rather than treat a subsequent
+// `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out
+// performs, broadened from the one signing-out account to all of them; device/MDM layers
+// describe the machine, not the account, and are left untouched. Rejects if the on-disk
+// cache cannot be removed.
+//
+// RPC method: managedSettings.clearCache.
+func (a *ServerManagedSettingsAPI) ClearCache(ctx context.Context) (*ManagedSettingsClearCacheResult, error) {
+ raw, err := a.client.Request(ctx, "managedSettings.clearCache", nil)
+ if err != nil {
+ return nil, err
+ }
+ var result ManagedSettingsClearCacheResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
// Read discovers device-managed settings from production MDM and managed-file sources,
// validates them against the runtime-owned managed-settings schema, and returns the
// canonical JSON without requiring a session.
@@ -20293,6 +21171,31 @@ func (a *ServerSessionsAPI) PruneOld(ctx context.Context, params *SessionsPruneO
return &result, nil
}
+// ReadPersistedEvents reads a page of durable events directly from a local session's
+// persisted journal without creating, resuming, or activating the session. The initial
+// backward read uses a bounded tail scan for fast first paint; cursor continuations
+// preserve the session event-log paging semantics. Persisted events may omit payloads that
+// are reconstructed only for an active session.
+//
+// RPC method: sessions.readPersistedEvents.
+//
+// Parameters: Pagination options for reading an inactive or active local session's
+// persisted event journal.
+//
+// Returns: Batch of session events returned by a read, with cursor and continuation
+// metadata.
+func (a *ServerSessionsAPI) ReadPersistedEvents(ctx context.Context, params *SessionsReadPersistedEventsRequest) (*EventsReadResult, error) {
+ raw, err := a.client.Request(ctx, "sessions.readPersistedEvents", params)
+ if err != nil {
+ return nil, err
+ }
+ var result EventsReadResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
// ReleaseLock releases the in-use lock held by this process for a session.
//
// RPC method: sessions.releaseLock.
@@ -20666,6 +21569,7 @@ type ServerRPC struct {
Catalog *ServerCatalogAPI
Commands *ServerCommandsAPI
Extensions *ServerExtensionsAPI
+ Hooks *ServerHooksAPI
Instructions *ServerInstructionsAPI
LlmInference *ServerLlmInferenceAPI
ManagedSettings *ServerManagedSettingsAPI
@@ -20730,6 +21634,7 @@ func NewServerRPC(client *jsonrpc2.Client) *ServerRPC {
r.Catalog = (*ServerCatalogAPI)(&r.common)
r.Commands = (*ServerCommandsAPI)(&r.common)
r.Extensions = (*ServerExtensionsAPI)(&r.common)
+ r.Hooks = (*ServerHooksAPI)(&r.common)
r.Instructions = (*ServerInstructionsAPI)(&r.common)
r.LlmInference = (*ServerLlmInferenceAPI)(&r.common)
r.ManagedSettings = (*ServerManagedSettingsAPI)(&r.common)
@@ -21134,6 +22039,28 @@ func (a *AgentAPI) SetPrompt(ctx context.Context, params *AgentSetPromptRequest)
return &result, nil
}
+// Experimental: AutopilotObjectiveAPI contains experimental APIs that may change or be
+// removed.
+type AutopilotObjectiveAPI sessionAPI
+
+// GetState reads the current canonical autopilot objective state for this session.
+//
+// RPC method: session.autopilotObjective.getState.
+//
+// Returns: Canonical runtime state for the session's current autopilot objective.
+func (a *AutopilotObjectiveAPI) GetState(ctx context.Context) (*AutopilotObjectiveGetStateResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ raw, err := a.client.Request(ctx, "session.autopilotObjective.getState", req)
+ if err != nil {
+ return nil, err
+ }
+ var result AutopilotObjectiveGetStateResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
// Experimental: CanvasAPI contains experimental APIs that may change or be removed.
type CanvasAPI sessionAPI
@@ -23552,13 +24479,15 @@ func (a *ModeAPI) Set(ctx context.Context, params *ModeSetRequest) (*ModeSetResu
// Experimental: ModelAPI contains experimental APIs that may change or be removed.
type ModelAPI sessionAPI
-// GetCurrent gets the currently selected model for the session.
+// GetCurrent gets the session's authoritative model snapshot, including the committed Auto
+// preference and any newer unclaimed Auto preference waiting for a future user turn.
//
// RPC method: session.model.getCurrent.
//
-// Returns: The currently selected model, reasoning effort, and context tier for the
-// session. The context tier reflects `Session.getContextTier()`, restored from the session
-// journal on resume.
+// Returns: The session's authoritative model snapshot. Auto preference fields are
+// configuration for the virtual `auto` model and do not change the selected model
+// identifier. The context tier reflects `Session.getContextTier()`, restored from the
+// session journal on resume.
func (a *ModelAPI) GetCurrent(ctx context.Context) (*CurrentModel, error) {
req := map[string]any{"sessionId": a.sessionID}
raw, err := a.client.Request(ctx, "session.model.getCurrent", req)
@@ -23629,6 +24558,40 @@ func (a *ModelAPI) SetReasoningEffort(ctx context.Context, params *ModelSetReaso
return &result, nil
}
+// SwitchAutoTier requests an Auto preference change without changing the session's selected
+// model. The latest unclaimed request wins; the runtime commits it only after a later
+// prompt using the `auto` model mints a usable model and token pair. A `pending` response
+// confirms that the request was accepted, not that it committed. Observe eventual success
+// through `session.model_change`, failure through the ephemeral
+// `session.auto_tier_switch_failed` event, or current unclaimed state through
+// `session.model.getCurrent`.
+//
+// RPC method: session.model.switchAutoTier.
+//
+// Parameters: An Auto preference request for the session. This updates Auto configuration
+// only; it does not change the selected model to `auto`.
+//
+// Returns: Immediate acknowledgement and Auto preference snapshot after a switch request.
+// This result never implies that a pending preference committed.
+func (a *ModelAPI) SwitchAutoTier(ctx context.Context, params *ModelSwitchAutoTierRequest) (*ModelSwitchAutoTierResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ req["autoTier"] = params.AutoTier
+ if params.Source != nil {
+ req["source"] = *params.Source
+ }
+ }
+ raw, err := a.client.Request(ctx, "session.model.switchAutoTier", req)
+ if err != nil {
+ return nil, err
+ }
+ var result ModelSwitchAutoTierResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
// SwitchTo switches the session to a model and optional reasoning configuration.
//
// RPC method: session.model.switchTo.
@@ -23640,6 +24603,9 @@ func (a *ModelAPI) SetReasoningEffort(ctx context.Context, params *ModelSetReaso
func (a *ModelAPI) SwitchTo(ctx context.Context, params *ModelSwitchToRequest) (*ModelSwitchToResult, error) {
req := map[string]any{"sessionId": a.sessionID}
if params != nil {
+ if params.AutoTier != nil {
+ req["autoTier"] = *params.AutoTier
+ }
if params.CompactionDecision != nil {
req["compactionDecision"] = *params.CompactionDecision
}
@@ -25516,6 +26482,39 @@ func (a *TasksAPI) Refresh(ctx context.Context) (*TasksRefreshResult, error) {
return &result, nil
}
+// Registers a client-owned task, or reclaims an orphaned task belonging to the same
+// extension principal.
+//
+// RPC method: session.tasks.register.
+//
+// Parameters: Registers or reclaims a client-owned task.
+//
+// Returns: Result of registering or reclaiming a client-owned task.
+func (a *TasksAPI) Register(ctx context.Context, params *TasksRegisterRequest) (*TasksRegisterResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ req["cancellable"] = params.Cancellable
+ req["clientTaskId"] = params.ClientTaskID
+ req["description"] = params.Description
+ if params.DisplayName != nil {
+ req["displayName"] = *params.DisplayName
+ }
+ if params.ExpectedSequence != nil {
+ req["expectedSequence"] = *params.ExpectedSequence
+ }
+ req["type"] = params.Type
+ }
+ raw, err := a.client.Request(ctx, "session.tasks.register", req)
+ if err != nil {
+ return nil, err
+ }
+ var result TasksRegisterResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
// Removes a completed or cancelled background task from tracking.
//
// RPC method: session.tasks.remove.
@@ -25601,6 +26600,31 @@ func (a *TasksAPI) StartAgent(ctx context.Context, params *TasksStartAgentReques
return &result, nil
}
+// Update publishes generic progress or a terminal outcome for a client-owned task.
+//
+// RPC method: session.tasks.update.
+//
+// Parameters: Updates a client-owned task.
+//
+// Returns: Result of publishing a client-owned task update.
+func (a *TasksAPI) Update(ctx context.Context, params *TasksUpdateRequest) (*TasksUpdateResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ req["id"] = params.ID
+ req["sequence"] = params.Sequence
+ req["update"] = params.Update
+ }
+ raw, err := a.client.Request(ctx, "session.tasks.update", req)
+ if err != nil {
+ return nil, err
+ }
+ var result TasksUpdateResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
// WaitForPending waits for all in-flight background tasks and any follow-up turns to settle.
//
// RPC method: session.tasks.waitForPending.
@@ -26607,44 +27631,45 @@ type SessionRPC struct {
// Reuse a single struct instead of allocating one for each service on the heap.
common sessionAPI
- Agent *AgentAPI
- Canvas *CanvasAPI
- Commands *CommandsAPI
- Completions *CompletionsAPI
- ContentExclusion *ContentExclusionAPI
- Debug *DebugAPI
- EventLog *EventLogAPI
- Extensions *ExtensionsAPI
- Factory *FactoryAPI
- Fleet *FleetAPI
- GitHubAuth *GitHubAuthAPI
- History *HistoryAPI
- Instructions *InstructionsAPI
- LimitPrediction *LimitPredictionAPI
- Lsp *LspAPI
- MCP *MCPAPI
- Metadata *MetadataAPI
- Mode *ModeAPI
- Model *ModelAPI
- Name *NameAPI
- Options *OptionsAPI
- Permissions *PermissionsAPI
- Plan *PlanAPI
- Plugins *PluginsAPI
- Provider *ProviderAPI
- Queue *QueueAPI
- Remote *RemoteAPI
- Sandbox *SandboxAPI
- Schedule *ScheduleAPI
- Shell *ShellAPI
- Skills *SkillsAPI
- Tasks *TasksAPI
- Telemetry *TelemetryAPI
- Tools *ToolsAPI
- UI *UIAPI
- Usage *UsageAPI
- Visibility *VisibilityAPI
- Workspaces *WorkspacesAPI
+ Agent *AgentAPI
+ AutopilotObjective *AutopilotObjectiveAPI
+ Canvas *CanvasAPI
+ Commands *CommandsAPI
+ Completions *CompletionsAPI
+ ContentExclusion *ContentExclusionAPI
+ Debug *DebugAPI
+ EventLog *EventLogAPI
+ Extensions *ExtensionsAPI
+ Factory *FactoryAPI
+ Fleet *FleetAPI
+ GitHubAuth *GitHubAuthAPI
+ History *HistoryAPI
+ Instructions *InstructionsAPI
+ LimitPrediction *LimitPredictionAPI
+ Lsp *LspAPI
+ MCP *MCPAPI
+ Metadata *MetadataAPI
+ Mode *ModeAPI
+ Model *ModelAPI
+ Name *NameAPI
+ Options *OptionsAPI
+ Permissions *PermissionsAPI
+ Plan *PlanAPI
+ Plugins *PluginsAPI
+ Provider *ProviderAPI
+ Queue *QueueAPI
+ Remote *RemoteAPI
+ Sandbox *SandboxAPI
+ Schedule *ScheduleAPI
+ Shell *ShellAPI
+ Skills *SkillsAPI
+ Tasks *TasksAPI
+ Telemetry *TelemetryAPI
+ Tools *ToolsAPI
+ UI *UIAPI
+ Usage *UsageAPI
+ Visibility *VisibilityAPI
+ Workspaces *WorkspacesAPI
}
// Aborts the current agent turn.
@@ -26930,6 +27955,7 @@ func NewSessionRPC(client *jsonrpc2.Client, sessionID string) *SessionRPC {
r := &SessionRPC{}
r.common = sessionAPI{client: client, sessionID: sessionID}
r.Agent = (*AgentAPI)(&r.common)
+ r.AutopilotObjective = (*AutopilotObjectiveAPI)(&r.common)
r.Canvas = (*CanvasAPI)(&r.common)
r.Commands = (*CommandsAPI)(&r.common)
r.Completions = (*CompletionsAPI)(&r.common)
@@ -27461,6 +28487,9 @@ func (a *InternalModelAPI) ApplyStartupOverlay(ctx context.Context, params *Mode
if params.DeviceManagedModel != nil {
req["deviceManagedModel"] = *params.DeviceManagedModel
}
+ if params.PolicyHelperModel != nil {
+ req["policyHelperModel"] = *params.PolicyHelperModel
+ }
if params.RepoContextTier != nil {
req["repoContextTier"] = *params.RepoContextTier
}
@@ -28185,12 +29214,26 @@ type SessionFSHandler interface {
WriteFile(request *SessionFSWriteFileRequest) (*SessionFSError, error)
}
+// Experimental: TasksHandler contains experimental APIs that may change or be removed.
+type TasksHandler interface {
+ // Cancel asks the client currently bound to a client-owned session task to confirm that its
+ // external work stopped.
+ //
+ // RPC method: tasks.cancel.
+ //
+ // Parameters: Runtime-to-owner cancellation request for a client-owned task.
+ //
+ // Returns: Whether the client authoritatively confirmed its external work stopped.
+ Cancel(request *ClientTaskCancelRequest) (*ClientTaskCancelResult, error)
+}
+
// ClientSessionAPIHandlers provides all client session API handler groups for a session.
type ClientSessionAPIHandlers struct {
Canvas CanvasHandler
Factory FactoryHandler
ProviderToken ProviderTokenHandler
SessionFS SessionFSHandler
+ Tasks TasksHandler
}
func clientSessionHandlerError(err error) *jsonrpc2.Error {
@@ -28568,6 +29611,25 @@ func RegisterClientSessionAPIHandlers(client *jsonrpc2.Client, getHandlers func(
}
return raw, nil
})
+ client.SetRequestHandler("tasks.cancel", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ var request ClientTaskCancelRequest
+ if err := json.Unmarshal(params, &request); err != nil {
+ return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)}
+ }
+ handlers := getHandlers(request.SessionID)
+ if handlers == nil || handlers.Tasks == nil {
+ return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No tasks handler registered for session: %s", request.SessionID)}
+ }
+ result, err := handlers.Tasks.Cancel(&request)
+ if err != nil {
+ return nil, clientSessionHandlerError(err)
+ }
+ raw, err := json.Marshal(result)
+ if err != nil {
+ return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)}
+ }
+ return raw, nil
+ })
}
// Experimental: ExtensionLaunchProviderHandler contains experimental APIs that may change
diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go
index 78788660f5..13d190be22 100644
--- a/go/rpc/zrpc_encoding.go
+++ b/go/rpc/zrpc_encoding.go
@@ -1606,6 +1606,12 @@ func unmarshalFactoryRunFailure(data []byte) (FactoryRunFailure, error) {
return nil, err
}
return &d, nil
+ case FactoryRunFailureTypeFactoryProviderDisconnected:
+ var d FactoryRunFailureFactoryProviderDisconnected
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
case FactoryRunFailureTypeFactoryResumeDeclined:
var d FactoryRunFailureFactoryResumeDeclined
if err := json.Unmarshal(data, &d); err != nil {
@@ -1661,6 +1667,17 @@ func (r FactoryRunFailureFactoryLimitReached) MarshalJSON() ([]byte, error) {
})
}
+func (r FactoryRunFailureFactoryProviderDisconnected) MarshalJSON() ([]byte, error) {
+ type alias FactoryRunFailureFactoryProviderDisconnected
+ return json.Marshal(struct {
+ Type FactoryRunFailureType `json:"type"`
+ alias
+ }{
+ Type: r.Type(),
+ alias: alias(r),
+ })
+}
+
func (r FactoryRunFailureFactoryResumeDeclined) MarshalJSON() ([]byte, error) {
type alias FactoryRunFailureFactoryResumeDeclined
return json.Marshal(struct {
@@ -1698,6 +1715,7 @@ func (r *FactoryRunTerminal) UnmarshalJSON(data []byte) error {
func (r *FactoryRunResult) UnmarshalJSON(data []byte) error {
type rawFactoryRunResult struct {
+ Attempt *int64 `json:"attempt,omitempty"`
Error *string `json:"error,omitempty"`
Failure json.RawMessage `json:"failure,omitempty"`
Reason *string `json:"reason,omitempty"`
@@ -1710,6 +1728,7 @@ func (r *FactoryRunResult) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
+ r.Attempt = raw.Attempt
r.Error = raw.Error
if raw.Failure != nil {
value, err := unmarshalFactoryRunFailure(raw.Failure)
@@ -5362,6 +5381,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error {
AgentContext *string `json:"agentContext,omitempty"`
AllowAllMCPServerInstructions *bool `json:"allowAllMcpServerInstructions,omitempty"`
AskUserDisabled *bool `json:"askUserDisabled,omitempty"`
+ AuthClientIDMetadataURL *string `json:"authClientIdMetadataUrl,omitempty"`
AuthInfo json.RawMessage `json:"authInfo,omitempty"`
AvailableTools []string `json:"availableTools,omitzero"`
Capi *CapiSessionOptions `json:"capi,omitempty"`
@@ -5382,6 +5402,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error {
EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"`
EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"`
EnableScriptSafety *bool `json:"enableScriptSafety,omitempty"`
+ EnableSkills *bool `json:"enableSkills,omitempty"`
EnableStreaming *bool `json:"enableStreaming,omitempty"`
EnvValueMode *SessionOpenOptionsEnvValueMode `json:"envValueMode,omitempty"`
EventsLogDirectory *string `json:"eventsLogDirectory,omitempty"`
@@ -5390,6 +5411,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error {
ExcludedTools []string `json:"excludedTools,omitzero"`
ExpAssignments any `json:"expAssignments,omitempty"`
FeatureFlags map[string]bool `json:"featureFlags,omitzero"`
+ HasSkillProvider *bool `json:"hasSkillProvider,omitempty"`
IncludedBuiltinAgents []string `json:"includedBuiltinAgents,omitzero"`
IncludedBuiltinSkills []string `json:"includedBuiltinSkills,omitzero"`
InstalledPlugins []InstalledPlugin `json:"installedPlugins,omitzero"`
@@ -5436,6 +5458,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error {
r.AgentContext = raw.AgentContext
r.AllowAllMCPServerInstructions = raw.AllowAllMCPServerInstructions
r.AskUserDisabled = raw.AskUserDisabled
+ r.AuthClientIDMetadataURL = raw.AuthClientIDMetadataURL
if raw.AuthInfo != nil {
value, err := unmarshalAuthInfo(raw.AuthInfo)
if err != nil {
@@ -5462,6 +5485,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error {
r.EnableManagedSettings = raw.EnableManagedSettings
r.EnableOnDemandInstructionDiscovery = raw.EnableOnDemandInstructionDiscovery
r.EnableScriptSafety = raw.EnableScriptSafety
+ r.EnableSkills = raw.EnableSkills
r.EnableStreaming = raw.EnableStreaming
r.EnvValueMode = raw.EnvValueMode
r.EventsLogDirectory = raw.EventsLogDirectory
@@ -5470,6 +5494,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error {
r.ExcludedTools = raw.ExcludedTools
r.ExpAssignments = raw.ExpAssignments
r.FeatureFlags = raw.FeatureFlags
+ r.HasSkillProvider = raw.HasSkillProvider
r.IncludedBuiltinAgents = raw.IncludedBuiltinAgents
r.IncludedBuiltinSkills = raw.IncludedBuiltinSkills
r.InstalledPlugins = raw.InstalledPlugins
@@ -5922,6 +5947,103 @@ func (r SlashCommandTextResult) MarshalJSON() ([]byte, error) {
})
}
+func unmarshalTaskClientUpdate(data []byte) (TaskClientUpdate, error) {
+ if string(data) == "null" {
+ return nil, nil
+ }
+ type rawUnion struct {
+ Kind TaskClientUpdateKind `json:"kind"`
+ }
+ var raw rawUnion
+ if err := json.Unmarshal(data, &raw); err != nil {
+ return nil, err
+ }
+
+ switch raw.Kind {
+ case TaskClientUpdateKindCancelled:
+ var d TaskClientUpdateCancelled
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
+ case TaskClientUpdateKindCompleted:
+ var d TaskClientUpdateCompleted
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
+ case TaskClientUpdateKindFailed:
+ var d TaskClientUpdateFailed
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
+ case TaskClientUpdateKindProgress:
+ var d TaskClientUpdateProgress
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
+ default:
+ return &RawTaskClientUpdateData{Discriminator: raw.Kind, Raw: data}, nil
+ }
+}
+
+func (r RawTaskClientUpdateData) MarshalJSON() ([]byte, error) {
+ if r.Raw != nil {
+ return r.Raw, nil
+ }
+ return json.Marshal(struct {
+ Kind TaskClientUpdateKind `json:"kind"`
+ }{
+ Kind: r.Discriminator,
+ })
+}
+
+func (r TaskClientUpdateCancelled) MarshalJSON() ([]byte, error) {
+ type alias TaskClientUpdateCancelled
+ return json.Marshal(struct {
+ Kind TaskClientUpdateKind `json:"kind"`
+ alias
+ }{
+ Kind: r.Kind(),
+ alias: alias(r),
+ })
+}
+
+func (r TaskClientUpdateCompleted) MarshalJSON() ([]byte, error) {
+ type alias TaskClientUpdateCompleted
+ return json.Marshal(struct {
+ Kind TaskClientUpdateKind `json:"kind"`
+ alias
+ }{
+ Kind: r.Kind(),
+ alias: alias(r),
+ })
+}
+
+func (r TaskClientUpdateFailed) MarshalJSON() ([]byte, error) {
+ type alias TaskClientUpdateFailed
+ return json.Marshal(struct {
+ Kind TaskClientUpdateKind `json:"kind"`
+ alias
+ }{
+ Kind: r.Kind(),
+ alias: alias(r),
+ })
+}
+
+func (r TaskClientUpdateProgress) MarshalJSON() ([]byte, error) {
+ type alias TaskClientUpdateProgress
+ return json.Marshal(struct {
+ Kind TaskClientUpdateKind `json:"kind"`
+ alias
+ }{
+ Kind: r.Kind(),
+ alias: alias(r),
+ })
+}
+
func unmarshalTaskInfo(data []byte) (TaskInfo, error) {
if string(data) == "null" {
return nil, nil
@@ -5941,6 +6063,12 @@ func unmarshalTaskInfo(data []byte) (TaskInfo, error) {
return nil, err
}
return &d, nil
+ case TaskInfoTypeClient:
+ var d TaskClientInfo
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
case TaskInfoTypeShell:
var d TaskShellInfo
if err := json.Unmarshal(data, &d); err != nil {
@@ -5974,6 +6102,17 @@ func (r TaskAgentInfo) MarshalJSON() ([]byte, error) {
})
}
+func (r TaskClientInfo) MarshalJSON() ([]byte, error) {
+ type alias TaskClientInfo
+ return json.Marshal(struct {
+ Type TaskInfoType `json:"type"`
+ alias
+ }{
+ Type: r.Type(),
+ alias: alias(r),
+ })
+}
+
func (r TaskShellInfo) MarshalJSON() ([]byte, error) {
type alias TaskShellInfo
return json.Marshal(struct {
@@ -6025,6 +6164,12 @@ func unmarshalTaskProgress(data []byte) (TaskProgress, error) {
return nil, err
}
return &d, nil
+ case TaskProgressTypeClient:
+ var d TaskClientProgress
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
case TaskProgressTypeShell:
var d TaskShellProgress
if err := json.Unmarshal(data, &d); err != nil {
@@ -6058,6 +6203,17 @@ func (r TaskAgentProgress) MarshalJSON() ([]byte, error) {
})
}
+func (r TaskClientProgress) MarshalJSON() ([]byte, error) {
+ type alias TaskClientProgress
+ return json.Marshal(struct {
+ Type TaskProgressType `json:"type"`
+ alias
+ }{
+ Type: r.Type(),
+ alias: alias(r),
+ })
+}
+
func (r TaskShellProgress) MarshalJSON() ([]byte, error) {
type alias TaskShellProgress
return json.Marshal(struct {
@@ -6123,6 +6279,28 @@ func (r *TasksPromoteCurrentToBackgroundResult) UnmarshalJSON(data []byte) error
return nil
}
+func (r *TasksUpdateRequest) UnmarshalJSON(data []byte) error {
+ type rawTasksUpdateRequest struct {
+ ID string `json:"id"`
+ Sequence int64 `json:"sequence"`
+ Update json.RawMessage `json:"update"`
+ }
+ var raw rawTasksUpdateRequest
+ if err := json.Unmarshal(data, &raw); err != nil {
+ return err
+ }
+ r.ID = raw.ID
+ r.Sequence = raw.Sequence
+ if raw.Update != nil {
+ value, err := unmarshalTaskClientUpdate(raw.Update)
+ if err != nil {
+ return err
+ }
+ r.Update = value
+ }
+ return nil
+}
+
func (r *ToolResultExpanded) UnmarshalJSON(data []byte) error {
type rawToolResultExpanded struct {
BinaryResultsForLlm []ExternalToolTextResultForLlmBinaryResultsForLlm `json:"binaryResultsForLlm,omitzero"`
diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go
index 4c03a42c00..a220b79ad5 100644
--- a/go/rpc/zsession_encoding.go
+++ b/go/rpc/zsession_encoding.go
@@ -47,6 +47,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error {
return err
}
e.Data = &d
+ case SessionEventTypeAssistantFusionPhaseActivity:
+ var d AssistantFusionPhaseActivityData
+ if err := json.Unmarshal(raw.Data, &d); err != nil {
+ return err
+ }
+ e.Data = &d
case SessionEventTypeAssistantFusionPhaseCompleted:
var d AssistantFusionPhaseCompletedData
if err := json.Unmarshal(raw.Data, &d); err != nil {
@@ -383,6 +389,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error {
return err
}
e.Data = &d
+ case SessionEventTypeSessionAutoTierSwitchFailed:
+ var d SessionAutoTierSwitchFailedData
+ if err := json.Unmarshal(raw.Data, &d); err != nil {
+ return err
+ }
+ e.Data = &d
case SessionEventTypeSessionBackgroundTasksChanged:
var d SessionBackgroundTasksChangedData
if err := json.Unmarshal(raw.Data, &d); err != nil {
@@ -443,6 +455,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error {
return err
}
e.Data = &d
+ case SessionEventTypeSessionCompletionReceipt:
+ var d SessionCompletionReceiptData
+ if err := json.Unmarshal(raw.Data, &d); err != nil {
+ return err
+ }
+ e.Data = &d
case SessionEventTypeSessionContextChanged:
var d SessionContextChangedData
if err := json.Unmarshal(raw.Data, &d); err != nil {
@@ -551,6 +569,18 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error {
return err
}
e.Data = &d
+ case SessionEventTypeSessionMCPServerNeedsReconnect:
+ var d SessionMCPServerNeedsReconnectData
+ if err := json.Unmarshal(raw.Data, &d); err != nil {
+ return err
+ }
+ e.Data = &d
+ case SessionEventTypeSessionMCPServerRemoved:
+ var d SessionMCPServerRemovedData
+ if err := json.Unmarshal(raw.Data, &d); err != nil {
+ return err
+ }
+ e.Data = &d
case SessionEventTypeSessionMCPServersLoaded:
var d SessionMCPServersLoadedData
if err := json.Unmarshal(raw.Data, &d); err != nil {
@@ -575,6 +605,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error {
return err
}
e.Data = &d
+ case SessionEventTypeSessionModeNoticeDelivered:
+ var d SessionModeNoticeDeliveredData
+ if err := json.Unmarshal(raw.Data, &d); err != nil {
+ return err
+ }
+ e.Data = &d
case SessionEventTypeSessionPermissionsChanged:
var d SessionPermissionsChangedData
if err := json.Unmarshal(raw.Data, &d); err != nil {
@@ -858,6 +894,7 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error {
Delivery *UserMessageDelivery `json:"delivery,omitempty"`
InteractionID *string `json:"interactionId,omitempty"`
IsAutopilotContinuation *bool `json:"isAutopilotContinuation,omitempty"`
+ MessageID *string `json:"messageId,omitempty"`
NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"`
ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"`
Source *string `json:"source,omitempty"`
@@ -884,6 +921,7 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error {
r.Delivery = raw.Delivery
r.InteractionID = raw.InteractionID
r.IsAutopilotContinuation = raw.IsAutopilotContinuation
+ r.MessageID = raw.MessageID
r.NativeDocumentPathFallbackPaths = raw.NativeDocumentPathFallbackPaths
r.ParentAgentTaskID = raw.ParentAgentTaskID
r.Source = raw.Source
@@ -2095,6 +2133,7 @@ func (r PermissionPromptRequestWrite) MarshalJSON() ([]byte, error) {
func (r *PermissionRequestedData) UnmarshalJSON(data []byte) error {
type rawPermissionRequestedData struct {
+ AgentMode *SessionMode `json:"agentMode,omitempty"`
PermissionRequest json.RawMessage `json:"permissionRequest"`
PromptRequest json.RawMessage `json:"promptRequest,omitempty"`
RequestID string `json:"requestId"`
@@ -2105,6 +2144,7 @@ func (r *PermissionRequestedData) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
+ r.AgentMode = raw.AgentMode
if raw.PermissionRequest != nil {
value, err := unmarshalPermissionRequest(raw.PermissionRequest)
if err != nil {
diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go
index acea96f5b9..2874e1f59b 100644
--- a/go/rpc/zsession_events.go
+++ b/go/rpc/zsession_events.go
@@ -55,6 +55,9 @@ type SessionEventType string
const (
SessionEventTypeAbort SessionEventType = "abort"
SessionEventTypeAgentInterrupted SessionEventType = "agent.interrupted"
+ // Experimental: SessionEventTypeAssistantFusionPhaseActivity identifies an experimental
+ // event that may change or be removed.
+ SessionEventTypeAssistantFusionPhaseActivity SessionEventType = "assistant.fusion_phase_activity"
// Experimental: SessionEventTypeAssistantFusionPhaseCompleted identifies an experimental
// event that may change or be removed.
SessionEventTypeAssistantFusionPhaseCompleted SessionEventType = "assistant.fusion_phase_completed"
@@ -125,6 +128,7 @@ const (
// that may change or be removed.
SessionEventTypeSessionAutoModeResolved SessionEventType = "session.auto_mode_resolved"
SessionEventTypeSessionAutopilotObjectiveChanged SessionEventType = "session.autopilot_objective_changed"
+ SessionEventTypeSessionAutoTierSwitchFailed SessionEventType = "session.auto_tier_switch_failed"
SessionEventTypeSessionBackgroundTasksChanged SessionEventType = "session.background_tasks_changed"
// Experimental: SessionEventTypeSessionBinaryAsset identifies an experimental event that
// may change or be removed.
@@ -146,9 +150,12 @@ const (
SessionEventTypeSessionCanvasRemoved SessionEventType = "session.canvas.removed"
// Experimental: SessionEventTypeSessionCanvasUnavailable identifies an experimental event
// that may change or be removed.
- SessionEventTypeSessionCanvasUnavailable SessionEventType = "session.canvas.unavailable"
- SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete"
- SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start"
+ SessionEventTypeSessionCanvasUnavailable SessionEventType = "session.canvas.unavailable"
+ SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete"
+ SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start"
+ // Experimental: SessionEventTypeSessionCompletionReceipt identifies an experimental event
+ // that may change or be removed.
+ SessionEventTypeSessionCompletionReceipt SessionEventType = "session.completion_receipt"
SessionEventTypeSessionContextChanged SessionEventType = "session.context_changed"
SessionEventTypeSessionContextCleared SessionEventType = "session.context_cleared"
SessionEventTypeSessionCustomAgentsUpdated SessionEventType = "session.custom_agents_updated"
@@ -179,10 +186,13 @@ const (
// Experimental: SessionEventTypeSessionManagedSettingsResolved identifies an experimental
// event that may change or be removed.
SessionEventTypeSessionManagedSettingsResolved SessionEventType = "session.managed_settings_resolved"
+ SessionEventTypeSessionMCPServerNeedsReconnect SessionEventType = "session.mcp_server_needs_reconnect"
+ SessionEventTypeSessionMCPServerRemoved SessionEventType = "session.mcp_server_removed"
SessionEventTypeSessionMCPServersLoaded SessionEventType = "session.mcp_servers_loaded"
SessionEventTypeSessionMCPServerStatusChanged SessionEventType = "session.mcp_server_status_changed"
SessionEventTypeSessionModeChanged SessionEventType = "session.mode_changed"
SessionEventTypeSessionModelChange SessionEventType = "session.model_change"
+ SessionEventTypeSessionModeNoticeDelivered SessionEventType = "session.mode_notice_delivered"
// Experimental: SessionEventTypeSessionPermissionsChanged identifies an experimental event
// that may change or be removed.
SessionEventTypeSessionPermissionsChanged SessionEventType = "session.permissions_changed"
@@ -299,6 +309,21 @@ type PromptCacheBreakData struct {
func (*PromptCacheBreakData) sessionEventData() {}
func (*PromptCacheBreakData) Type() SessionEventType { return SessionEventTypePromptCacheBreak }
+// A transient Auto preference failure emitted when the runtime cannot mint or accept a usable model and token pair. The previously effective preference remains active, so SDK clients can surface a non-blocking failure without changing their committed-tier state. This event is ephemeral and is not persisted or replayed on resume.
+type SessionAutoTierSwitchFailedData struct {
+ // Auto preference that remains effective after the failed request.
+ EffectiveAutoTier *AutoTier `json:"effectiveAutoTier,omitempty"`
+ // Low-cardinality failure outcome reported by Auto resolution.
+ Reason AutoTierSwitchFailureReason `json:"reason"`
+ // Auto preference that failed to activate, or null when returning to provider-default routing failed.
+ RequestedAutoTier *AutoTier `json:"requestedAutoTier"`
+}
+
+func (*SessionAutoTierSwitchFailedData) sessionEventData() {}
+func (*SessionAutoTierSwitchFailedData) Type() SessionEventType {
+ return SessionEventTypeSessionAutoTierSwitchFailed
+}
+
// Agent intent description for current activity or plan
type AssistantIntentData struct {
// Short description of what the agent is currently doing or planning to do
@@ -473,6 +498,32 @@ func (*SessionAutopilotObjectiveChangedData) Type() SessionEventType {
return SessionEventTypeSessionAutopilotObjectiveChanged
}
+// Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted.
+// Experimental: SessionCompletionReceiptData is part of an experimental API and may change or be removed.
+type SessionCompletionReceiptData struct {
+ // One-based accepted completion receipt ordinal in the durable session history.
+ Attempt int64 `json:"attempt"`
+ // Inclusive durable event range summarized by this receipt.
+ EventRange CompletionReceiptEventRange `json:"eventRange"`
+ // Number of failed structured tool completions in the covered range.
+ FailedToolCount int64 `json:"failedToolCount"`
+ // Final structured tool completion in the covered range, when one exists.
+ FinalTool *CompletionReceiptFinalTool `json:"finalTool,omitempty"`
+ // Version of the completion receipt payload.
+ SchemaVersion int64 `json:"schemaVersion"`
+ // Identifier of the assistant turn-end event that supplied the accepted completion boundary. This is the receipt's idempotency key, and always equals eventRange.endEventId.
+ SourceEventID string `json:"sourceEventId"`
+ // Runtime reason the completion decision was accepted.
+ StopReason CompletionReceiptStopReason `json:"stopReason"`
+ // Number of successful structured tool completions in the covered range.
+ SuccessfulToolCount int64 `json:"successfulToolCount"`
+}
+
+func (*SessionCompletionReceiptData) sessionEventData() {}
+func (*SessionCompletionReceiptData) Type() SessionEventType {
+ return SessionEventTypeSessionCompletionReceipt
+}
+
// Canonical bytes for a content-addressed binary asset shared by reference across events
type SessionBinaryAssetData struct {
// Content-addressed id for this binary asset (e.g. "sha256:...").
@@ -768,7 +819,7 @@ func (*PendingMessagesModifiedData) Type() SessionEventType {
return SessionEventTypePendingMessagesModified
}
-// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes.
+// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes.
// Experimental: SessionManagedSettingsResolvedData is part of an experimental API and may change or be removed.
type SessionManagedSettingsResolvedData struct {
// Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true.
@@ -783,13 +834,15 @@ type SessionManagedSettingsResolvedData struct {
ManagedKeys []string `json:"managedKeys"`
// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`.
PermissionsAllowIntersected *bool `json:"permissionsAllowIntersected,omitempty"`
+ // Whether the policy-helper managed-settings layer was present. The policy helper is the weakest channel: it fills keys no enterprise source set and can never replace one.
+ PolicyHelperManaged *bool `json:"policyHelperManaged,omitempty"`
// Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy.
SandboxEnabledByUndeterminedPolicy *bool `json:"sandboxEnabledByUndeterminedPolicy,omitempty"`
// Whether the server (account/org) managed-settings layer was present
ServerManaged bool `json:"serverManaged"`
// The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force.
Settings any `json:"settings,omitempty"`
- // Channel summary: `server`, `device`, or `client` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance.
+ // Channel summary: `server`, `device`, `client`, or `policyHelper` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance.
Source ManagedSettingsResolvedSource `json:"source"`
}
@@ -867,6 +920,8 @@ type SessionErrorData struct {
Message string `json:"message"`
// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs
ProviderCallID *string `json:"providerCallId,omitempty"`
+ // What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value.
+ Remediation *RemediationAction `json:"remediation,omitempty"`
// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation
ServiceRequestID *string `json:"serviceRequestId,omitempty"`
// Error stack trace, when available
@@ -880,6 +935,34 @@ type SessionErrorData struct {
func (*SessionErrorData) sessionEventData() {}
func (*SessionErrorData) Type() SessionEventType { return SessionEventTypeSessionError }
+// Experimental content-safe activity signal for a running HydraFusion phase.
+// Experimental: AssistantFusionPhaseActivityData is part of an experimental API and may change or be removed.
+type AssistantFusionPhaseActivityData struct {
+ // Kind of real activity observed.
+ Activity FusionPhaseActivityKind `json:"activity"`
+ // Conversation scope in which the phase executes.
+ ConversationScope FusionConversationScope `json:"conversationScope"`
+ // Identifier of the HydraFusion turn containing the phase.
+ FusionID string `json:"fusionId"`
+ // HydraFusion orchestration pattern containing the phase.
+ Pattern FusionPattern `json:"pattern"`
+ // Stable identifier for the concrete phase.
+ PhaseID string `json:"phaseId"`
+ // Kind of phase currently executing.
+ PhaseKind FusionPhaseKind `json:"phaseKind"`
+ // Semantic role assigned to the phase.
+ Role string `json:"role"`
+ // Opaque hashed correlation token for matching tool-started and tool-completed activity within this Fusion activity stream. It is not the tool call identifier exposed by tool lifecycle events.
+ ToolCallID *string `json:"toolCallId,omitempty"`
+ // Cumulative private response bytes observed for this model call. The event never includes response text.
+ TotalResponseSizeBytes *int64 `json:"totalResponseSizeBytes,omitempty"`
+}
+
+func (*AssistantFusionPhaseActivityData) sessionEventData() {}
+func (*AssistantFusionPhaseActivityData) Type() SessionEventType {
+ return SessionEventTypeAssistantFusionPhaseActivity
+}
+
// Experimental durable HydraFusion phase output and lossless replay checkpoint.
// Experimental: AssistantFusionPhaseCompletedData is part of an experimental API and may change or be removed.
type AssistantFusionPhaseCompletedData struct {
@@ -1042,6 +1125,9 @@ type SessionFusionResolvedData struct {
ModelUniverseVersion *string `json:"modelUniverseVersion,omitempty"`
// Validated orchestration pattern selected for the turn.
Pattern FusionPattern `json:"pattern"`
+ // Presentation-neutral phase plan for clients that render workflow progress.
+ // Experimental: PhasePlan is part of an experimental API and may change or be removed.
+ PhasePlan []FusionPhasePlanStep `json:"phasePlan,omitzero"`
// Version of the validated execution-plan format.
PlanVersion *string `json:"planVersion,omitempty"`
// HydraFusion routing policy used to resolve the plan.
@@ -1511,12 +1597,16 @@ func (*ModelCallStartData) Type() SessionEventType { return SessionEventTypeMode
// Model change details including previous and new model identifiers
type SessionModelChangeData struct {
+ // Committed Auto preference after the model configuration change, when applicable.
+ AutoTier *AutoTier `json:"autoTier,omitempty"`
// Reason the change happened, when not user-initiated. `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path, or `"refusal_fallback"` when the active model declined a request (content refusal) and the runtime switched to the configured refusal-fallback model. UI clients can use this to render contextual copy.
Cause *string `json:"cause,omitempty"`
// Context tier after the model change; null explicitly clears a previously selected tier
ContextTier *ContextTier `json:"contextTier,omitempty"`
// Newly selected model identifier
NewModel string `json:"newModel"`
+ // Previously committed Auto preference, when one was explicitly selected.
+ PreviousAutoTier *AutoTier `json:"previousAutoTier,omitempty"`
// Model that was previously selected, if any
PreviousModel *string `json:"previousModel,omitempty"`
// Reasoning effort level before the model change, if applicable
@@ -1756,6 +1846,28 @@ func (*SessionExtensionsLoadedData) Type() SessionEventType {
return SessionEventTypeSessionExtensionsLoaded
}
+// Payload of `session.mcp_server_needs_reconnect` identifying an MCP server whose connection must be re-established.
+type SessionMCPServerNeedsReconnectData struct {
+ // Name of the MCP server that needs to reconnect
+ ServerName string `json:"serverName"`
+}
+
+func (*SessionMCPServerNeedsReconnectData) sessionEventData() {}
+func (*SessionMCPServerNeedsReconnectData) Type() SessionEventType {
+ return SessionEventTypeSessionMCPServerNeedsReconnect
+}
+
+// Payload of `session.mcp_server_removed` identifying an MCP server the graph no longer runs.
+type SessionMCPServerRemovedData struct {
+ // Name of the MCP server that was removed from the graph
+ ServerName string `json:"serverName"`
+}
+
+func (*SessionMCPServerRemovedData) sessionEventData() {}
+func (*SessionMCPServerRemovedData) Type() SessionEventType {
+ return SessionEventTypeSessionMCPServerRemoved
+}
+
// Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error.
type SessionMCPServerStatusChangedData struct {
// Error message if the server entered a failed state
@@ -1814,6 +1926,8 @@ type UserMessageData struct {
InteractionID *string `json:"interactionId,omitempty"`
// True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry.
IsAutopilotContinuation *bool `json:"isAutopilotContinuation,omitempty"`
+ // Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots
+ MessageID *string `json:"messageId,omitempty"`
// Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit
NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"`
// Parent agent task ID for background telemetry correlated to this user turn
@@ -1846,6 +1960,8 @@ func (*PermissionCompletedData) Type() SessionEventType { return SessionEventTyp
// Permission request notification requiring client approval with request details
type PermissionRequestedData struct {
+ // Agent mode captured from the owning turn when permission evaluation began.
+ AgentMode *SessionMode `json:"agentMode,omitempty"`
// Details of the permission being requested
PermissionRequest PermissionRequest `json:"permissionRequest"`
// Derived user-facing permission prompt details for UI consumers
@@ -1960,6 +2076,19 @@ type CommandQueuedData struct {
func (*CommandQueuedData) sessionEventData() {}
func (*CommandQueuedData) Type() SessionEventType { return SessionEventTypeCommandQueued }
+// Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume.
+type SessionModeNoticeDeliveredData struct {
+ // Model-visible transition notice persisted for a mid-turn delivery
+ Content *string `json:"content,omitempty"`
+ // Mode established by the delivered transition notice
+ Mode SessionMode `json:"mode"`
+}
+
+func (*SessionModeNoticeDeliveredData) sessionEventData() {}
+func (*SessionModeNoticeDeliveredData) Type() SessionEventType {
+ return SessionEventTypeSessionModeNoticeDelivered
+}
+
// Registered command dispatch request routed to the owning client
type CommandExecuteData struct {
// Raw argument string after the command name
@@ -2319,17 +2448,19 @@ type SkillInvokedData struct {
Content string `json:"content"`
// Description of the skill from its SKILL.md frontmatter
Description *string `json:"description,omitempty"`
+ // Whether model invocation is disabled for this skill
+ DisableModelInvocation *bool `json:"disableModelInvocation,omitempty"`
// Model identifier active when the skill was invoked, when known
Model *string `json:"model,omitempty"`
// Name of the invoked skill
Name string `json:"name"`
- // File path to the SKILL.md definition
+ // File path to the SKILL.md definition, or an empty string for an SDK-provided skill without a filesystem identity
Path string `json:"path"`
// Name of the plugin this skill originated from, when applicable
PluginName *string `json:"pluginName,omitempty"`
// Version of the plugin this skill originated from, when applicable
PluginVersion *string `json:"pluginVersion,omitempty"`
- // Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill)
+ // Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), remote (org/enterprise skill), and sdk (SDK-provided skill)
Source *string `json:"source,omitempty"`
// What triggered the skill invocation: `user-invoked` (explicit user action, such as via a slash command or UI affordance), `agent-invoked` (agent requested the skill), or `context-load` (loaded as part of another context, such as preloading skills configured on a custom agent or subagent)
Trigger *SkillInvokedTrigger `json:"trigger,omitempty"`
@@ -2443,6 +2574,8 @@ type SubagentCompletedData struct {
FirstDispatchedModel *string `json:"firstDispatchedModel,omitempty"`
// Model used by the sub-agent
Model *string `json:"model,omitempty"`
+ // Why an explicit task-call model did not become the effective model
+ ModelOverrideReason *string `json:"modelOverrideReason,omitempty"`
// Tool call ID of the parent tool invocation that spawned this sub-agent
ToolCallID string `json:"toolCallId"`
// Total tokens (input + output) consumed by the sub-agent
@@ -2476,6 +2609,8 @@ type SubagentFailedData struct {
FirstDispatchedModel *string `json:"firstDispatchedModel,omitempty"`
// Model selected for the sub-agent, when known
Model *string `json:"model,omitempty"`
+ // Why an explicit task-call model did not become the effective model
+ ModelOverrideReason *string `json:"modelOverrideReason,omitempty"`
// Tool call ID of the parent tool invocation that spawned this sub-agent
ToolCallID string `json:"toolCallId"`
// Total tokens (input + output) consumed before the sub-agent failed
@@ -2735,6 +2870,8 @@ func (*ToolUserRequestedData) Type() SessionEventType { return SessionEventTypeT
type SessionWarningData struct {
// Human-readable warning message for display in the timeline
Message string `json:"message"`
+ // What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value.
+ Remediation *RemediationAction `json:"remediation,omitempty"`
// Optional URL associated with this warning that the user can open in a browser
URL *string `json:"url,omitempty"`
// Category of warning (e.g., "subscription", "policy", "mcp")
@@ -2787,7 +2924,7 @@ func (*SessionWorkspaceFileChangedData) Type() SessionEventType {
// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping
// Experimental: AssistantMessageReasoningBlocks is part of an experimental API and may change or be removed.
type AssistantMessageReasoningBlocks struct {
- // Provider-native reasoning content blocks (e.g. Anthropic `thinking` / `redacted_thinking`) preserved verbatim, in order. A single response can carry several, each signed over the content preceding it, so dropping or reordering any of them invalidates the rest.
+ // Provider-native reasoning items or content blocks preserved verbatim, in order. A single response can carry several, and provider signatures or identifiers may depend on their exact content and ordering.
Blocks []any `json:"blocks,omitzero"`
// Model provider that produced these reasoning blocks.
Provider string `json:"provider"`
@@ -3107,7 +3244,27 @@ type CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail struct {
TokenType string `json:"tokenType"`
}
-// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override.
+// Inclusive durable event range summarized by a completion receipt.
+type CompletionReceiptEventRange struct {
+ // Identifier of the assistant turn-end event that ends the covered exchange. Always equals the receipt's sourceEventId, so either field is a valid join key.
+ EndEventID string `json:"endEventId"`
+ // Identifier of the user message that starts the covered exchange.
+ StartEventID string `json:"startEventId"`
+}
+
+// Final structured tool completion in the covered event range.
+type CompletionReceiptFinalTool struct {
+ // Process exit code from a structured shell result, when available.
+ ExitCode *int64 `json:"exitCode,omitempty"`
+ // Structured success or failure status from the tool completion event.
+ Status CompletionReceiptToolStatus `json:"status"`
+ // Unique identifier of the completed tool call.
+ ToolCallID string `json:"toolCallId"`
+ // Tool name from the matching tool execution start event, when available.
+ ToolName *string `json:"toolName,omitempty"`
+}
+
+// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration.
type CustomAgentsUpdatedAgent struct {
// Description of what the agent does
Description string `json:"description"`
@@ -3117,6 +3274,10 @@ type CustomAgentsUpdatedAgent struct {
ID string `json:"id"`
// Model override for this agent, if set
Model *string `json:"model,omitempty"`
+ // Whether authored models are preferences or required constraints
+ ModelPolicy *AgentModelPolicy `json:"modelPolicy,omitempty"`
+ // Authored model ids in priority order, if configured
+ Models []string `json:"models,omitzero"`
// Internal name of the agent
Name string `json:"name"`
// Source location: user, project, inherited, remote, or plugin
@@ -3193,6 +3354,19 @@ type FusionFollowUpRecommendation struct {
UserTurn FusionFollowUpAction `json:"userTurn"`
}
+// Presentation-neutral phase planned for a HydraFusion turn.
+// Experimental: FusionPhasePlanStep is part of an experimental API and may change or be removed.
+type FusionPhasePlanStep struct {
+ // Whether the phase executes only when an earlier phase requests it.
+ Conditional bool `json:"conditional"`
+ // Kind of phase that may execute.
+ Kind FusionPhaseKind `json:"kind"`
+ // Semantic role assigned to the phase.
+ Role string `json:"role"`
+ // Conversation scope in which the phase executes.
+ Scope FusionConversationScope `json:"scope"`
+}
+
// Aggregate concrete-model usage for one HydraFusion phase.
// Experimental: FusionPhaseUsage is part of an experimental API and may change or be removed.
type FusionPhaseUsage struct {
@@ -3336,6 +3510,8 @@ type MCPServersLoadedServer struct {
PluginName *string `json:"pluginName,omitempty"`
// Version of the plugin that supplied the effective MCP server config, only when source is plugin
PluginVersion *string `json:"pluginVersion,omitempty"`
+ // Server-advertised metadata for a connected server. Omitted when no live connection metadata is available, including while pending or when failed, disabled, stopped, or not configured.
+ ServerMetadata *MCPServerMetadata `json:"serverMetadata,omitempty"`
// Configuration source: user, workspace, plugin, or builtin
Source *MCPServerSource `json:"source,omitempty"`
// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured
@@ -3657,9 +3833,9 @@ type PermissionPromptRequestURL struct {
ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"`
// Immediately preceding URL when this prompt is for a redirect target
RedirectedFrom *string `json:"redirectedFrom,omitempty"`
- // True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
+ // True when the tool is asking to run this URL fetch outside the sandbox, after the network policy denied the approved URL or the sandbox proxy could not reach it (host opted in via sandbox.allowBypass). The model cannot ask for this; only the tool raises it. This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"`
- // Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true.
+ // What the tool tells the user about the bypass on offer: which policy rule blocked the call, or why it cannot be sandboxed. Only meaningful when requestSandboxBypass is true.
RequestSandboxBypassReason *string `json:"requestSandboxBypassReason,omitempty"`
// Tool call ID that triggered this permission request
ToolCallID *string `json:"toolCallId,omitempty"`
@@ -3913,9 +4089,9 @@ type PermissionRequestRead struct {
ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"`
// Path of the file or directory being read
Path string `json:"path"`
- // True when the model has requested to run this search outside the sandbox (it set requestSandboxBypass: true and the host opted in via sandbox.allowBypass). This is a request, not a grant: the search runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
+ // True when the tool is asking to re-run this search outside the sandbox, after a sandboxed run looked blocked (host opted in via sandbox.allowBypass). The model cannot ask for this; only the tool raises it. This is a request, not a grant: the search runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"`
- // Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true.
+ // What the tool tells the user about the bypass on offer: which policy rule blocked the call, or why it cannot be sandboxed. Only meaningful when requestSandboxBypass is true.
RequestSandboxBypassReason *string `json:"requestSandboxBypassReason,omitempty"`
// Tool call ID that triggered this permission request
ToolCallID *string `json:"toolCallId,omitempty"`
@@ -3946,9 +4122,9 @@ type PermissionRequestShell struct {
PossiblePaths []string `json:"possiblePaths"`
// URLs that may be accessed by the command
PossibleURLs []PermissionRequestShellPossibleURL `json:"possibleUrls"`
- // True when the model has requested to run this command outside the sandbox (it set requestSandboxBypass: true and the host opted in via sandbox.allowBypass). This is a request, not a grant: the command runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
+ // True when the tool is asking to run this command outside the sandbox, either because the command detaches and cannot be sandboxed at all, or because a sandboxed run looked blocked (host opted in via sandbox.allowBypass). The model cannot ask for this; only the tool raises it. This is a request, not a grant: the command runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"`
- // Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true.
+ // What the tool tells the user about the bypass on offer: which policy rule blocked the call, or why it cannot be sandboxed. Only meaningful when requestSandboxBypass is true.
RequestSandboxBypassReason *string `json:"requestSandboxBypassReason,omitempty"`
// Tool call ID that triggered this permission request
ToolCallID *string `json:"toolCallId,omitempty"`
@@ -3969,9 +4145,9 @@ type PermissionRequestURL struct {
ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"`
// Immediately preceding URL when this request is for a redirect target
RedirectedFrom *string `json:"redirectedFrom,omitempty"`
- // True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
+ // True when the tool is asking to run this URL fetch outside the sandbox, after the network policy denied the approved URL or the sandbox proxy could not reach it (host opted in via sandbox.allowBypass). The model cannot ask for this; only the tool raises it. This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI.
RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"`
- // Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true.
+ // What the tool tells the user about the bypass on offer: which policy rule blocked the call, or why it cannot be sandboxed. Only meaningful when requestSandboxBypass is true.
RequestSandboxBypassReason *string `json:"requestSandboxBypassReason,omitempty"`
// Tool call ID that triggered this permission request
ToolCallID *string `json:"toolCallId,omitempty"`
@@ -4339,7 +4515,7 @@ type SkillsLoadedSkill struct {
Name string `json:"name"`
// Absolute path to the skill file, if available
Path *string `json:"path,omitempty"`
- // Source location type (e.g., project, personal-copilot, plugin, builtin)
+ // Source location type (e.g., project, personal-copilot, plugin, builtin, remote, sdk)
Source SkillSource `json:"source"`
// Whether the skill can be invoked by the user as a slash command
UserInvocable bool `json:"userInvocable"`
@@ -4656,6 +4832,8 @@ type ToolExecutionCompleteError struct {
Code *string `json:"code,omitempty"`
// Human-readable error message
Message string `json:"message"`
+ // What the user must do to recover, when the runtime knows of an action. Set on sandbox policy denials, where `message` names the rule that blocked the call but never the client affordance that relaxes it.
+ Remediation *RemediationAction `json:"remediation,omitempty"`
}
// Tool execution result on success
@@ -4993,6 +5171,20 @@ const (
AutopilotObjectiveChangedStatusPaused AutopilotObjectiveChangedStatus = "paused"
)
+// Terminal reason an Auto preference activation failed.
+type AutoTierSwitchFailureReason string
+
+const (
+ // The candidate model was rejected by model policy.
+ AutoTierSwitchFailureReasonPolicyRejected AutoTierSwitchFailureReason = "policy_rejected"
+ // The Auto routing request failed or returned an unusable response.
+ AutoTierSwitchFailureReasonRequestFailed AutoTierSwitchFailureReason = "request_failed"
+ // The runtime could not prepare the Auto routing request.
+ AutoTierSwitchFailureReasonSetupFailed AutoTierSwitchFailureReason = "setup_failed"
+ // The provider does not support Auto routing.
+ AutoTierSwitchFailureReasonUnsupported AutoTierSwitchFailureReason = "unsupported"
+)
+
// Binary result type discriminator. Use "image" for images and "resource" for other binary data.
type BinaryAssetReferenceType string
@@ -5052,6 +5244,34 @@ const (
CompactionTriggerThreshold CompactionTrigger = "threshold"
)
+// Runtime reason the completion decision was accepted.
+type CompletionReceiptStopReason string
+
+const (
+ // The configured agentStop continuation limit was reached.
+ CompletionReceiptStopReasonAgentStopBlockLimit CompletionReceiptStopReason = "agent_stop_block_limit"
+ // The model reached a natural terminal response.
+ CompletionReceiptStopReasonNatural CompletionReceiptStopReason = "natural"
+ // A terminal tool ended the interaction.
+ CompletionReceiptStopReasonTerminalTool CompletionReceiptStopReason = "terminal_tool"
+)
+
+// Structured terminal status from a tool completion event.
+type CompletionReceiptToolStatus string
+
+const (
+ // The permissions service denied the tool call.
+ CompletionReceiptToolStatusDenied CompletionReceiptToolStatus = "denied"
+ // The tool failed without a more specific structured status.
+ CompletionReceiptToolStatusFailure CompletionReceiptToolStatus = "failure"
+ // The user rejected the tool call.
+ CompletionReceiptToolStatusRejected CompletionReceiptToolStatus = "rejected"
+ // The tool completed successfully.
+ CompletionReceiptToolStatusSuccess CompletionReceiptToolStatus = "success"
+ // The tool exceeded its time budget.
+ CompletionReceiptToolStatusTimeout CompletionReceiptToolStatus = "timeout"
+)
+
// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed)
type ElicitationCompletedAction string
@@ -5182,6 +5402,19 @@ const (
FusionPatternSingle FusionPattern = "single"
)
+// Content-safe activity observed while a HydraFusion phase is running.
+// Experimental: FusionPhaseActivityKind is part of an experimental API and may change or be removed.
+type FusionPhaseActivityKind string
+
+const (
+ // The provider produced additional private output bytes.
+ FusionPhaseActivityKindModelOutput FusionPhaseActivityKind = "model_output"
+ // A tool finished executing inside the phase.
+ FusionPhaseActivityKindToolCompleted FusionPhaseActivityKind = "tool_completed"
+ // A tool began executing inside the phase.
+ FusionPhaseActivityKindToolStarted FusionPhaseActivityKind = "tool_started"
+)
+
// HydraFusion phase kind.
// Experimental: FusionPhaseKind is part of an experimental API and may change or be removed.
type FusionPhaseKind string
@@ -5284,10 +5517,12 @@ const (
ManagedSettingsResolvedSourceClient ManagedSettingsResolvedSource = "client"
// Only the device MDM/plist/registry/file channel contributed.
ManagedSettingsResolvedSourceDevice ManagedSettingsResolvedSource = "device"
- // More than one channel contributed. Ordinary keys resolve device over server per key, while permissions compose restrictively across all present layers.
+ // More than one channel contributed. Ordinary keys resolve device over server over policy helper per key, while permissions compose restrictively across all present layers.
ManagedSettingsResolvedSourceMixed ManagedSettingsResolvedSource = "mixed"
// No managed policy is in force (no channel contributed).
ManagedSettingsResolvedSourceNone ManagedSettingsResolvedSource = "none"
+ // A policy helper registered by device or server policy contributed. Device registration takes priority when present.
+ ManagedSettingsResolvedSourcePolicyHelper ManagedSettingsResolvedSource = "policyHelper"
// Only the server/account channel contributed.
ManagedSettingsResolvedSourceServer ManagedSettingsResolvedSource = "server"
)
diff --git a/go/samples/chat.go b/go/samples/chat.go
index 2f34a243c9..85128a1b56 100644
--- a/go/samples/chat.go
+++ b/go/samples/chat.go
@@ -5,10 +5,10 @@ import (
"context"
"fmt"
"os"
- "path/filepath"
"strings"
copilot "github.com/github/copilot-sdk/go"
+ "github.com/github/copilot-sdk/go/samples/internal/sampleutil"
)
const blue = "\033[34m"
@@ -16,7 +16,10 @@ const reset = "\033[0m"
func main() {
ctx := context.Background()
- cliPath := filepath.Join("..", "..", "nodejs", "node_modules", "@github", "copilot", "index.js")
+ cliPath, err := sampleutil.CLIPath()
+ if err != nil {
+ panic(err)
+ }
client := copilot.NewClient(&copilot.ClientOptions{Connection: copilot.StdioConnection{Path: cliPath}})
if err := client.Start(ctx); err != nil {
panic(err)
diff --git a/go/samples/internal/sampleutil/runtime.go b/go/samples/internal/sampleutil/runtime.go
new file mode 100644
index 0000000000..5c51f0d8db
--- /dev/null
+++ b/go/samples/internal/sampleutil/runtime.go
@@ -0,0 +1,48 @@
+package sampleutil
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+// CLIPath resolves the pinned source-checkout runtime used by SDK samples.
+func CLIPath() (string, error) {
+ if cliPath := os.Getenv("COPILOT_CLI_PATH"); cliPath != "" {
+ return cliPath, nil
+ }
+
+ current, err := os.Getwd()
+ if err != nil {
+ return "", err
+ }
+ for {
+ nodeDir := filepath.Join(current, "nodejs")
+ if _, err := os.Stat(filepath.Join(nodeDir, "package.json")); err == nil {
+ command := exec.Command(
+ "node",
+ "node_modules/tsx/dist/cli.mjs",
+ "scripts/prepare-runtime.ts",
+ "--print-path",
+ )
+ command.Dir = nodeDir
+ output, err := command.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("prepare pinned Copilot CLI: %w: %s", err, output)
+ }
+ cliPath := strings.TrimSpace(string(output))
+ if info, err := os.Stat(cliPath); err != nil || info.IsDir() {
+ return "", fmt.Errorf("prepared Copilot CLI path is not a file: %q", cliPath)
+ }
+ return cliPath, nil
+ }
+
+ parent := filepath.Dir(current)
+ if parent == current {
+ return "", fmt.Errorf("could not find nodejs/package.json; set COPILOT_CLI_PATH")
+ }
+ current = parent
+ }
+}
diff --git a/go/samples/manual_tool_resume/main.go b/go/samples/manual_tool_resume/main.go
index a7391ff405..62806b7a78 100644
--- a/go/samples/manual_tool_resume/main.go
+++ b/go/samples/manual_tool_resume/main.go
@@ -4,11 +4,11 @@ import (
"context"
"fmt"
"os"
- "path/filepath"
"time"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
+ "github.com/github/copilot-sdk/go/samples/internal/sampleutil"
)
const timeout = 2 * time.Minute
@@ -32,7 +32,10 @@ func manualTool() copilot.Tool {
}
func newClient() *copilot.Client {
- cliPath := filepath.Join("..", "..", "nodejs", "node_modules", "@github", "copilot", "index.js")
+ cliPath, err := sampleutil.CLIPath()
+ if err != nil {
+ panic(err)
+ }
return copilot.NewClient(&copilot.ClientOptions{Connection: copilot.StdioConnection{Path: cliPath}})
}
diff --git a/go/session.go b/go/session.go
index 5d45d19ef2..f4bb0d38c7 100644
--- a/go/session.go
+++ b/go/session.go
@@ -4,6 +4,7 @@ package copilot
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"log"
"sync"
@@ -65,6 +66,9 @@ type Session struct {
handlerMutex sync.RWMutex
toolHandlers map[string]ToolHandler
toolHandlersM sync.RWMutex
+ pendingExternalTools map[string]*pendingExternalTool
+ pendingExternalToolsM sync.Mutex
+ externalToolsClosed bool
permissionHandler PermissionHandlerFunc
permissionMux sync.RWMutex
managedSettings bool
@@ -98,13 +102,20 @@ type Session struct {
// eventCh serializes user event handler dispatch. dispatchEvent enqueues;
// a single goroutine (processEvents) dequeues and invokes handlers in FIFO order.
+ // eventDone stops both sides without closing eventCh while a sender may be active.
eventCh chan SessionEvent
- closeOnce sync.Once // guards eventCh close so Disconnect is safe to call more than once
+ eventDone chan struct{}
+ closeOnce sync.Once
// RPC provides typed session-scoped RPC methods.
RPC *rpc.SessionRPC
}
+type pendingExternalTool struct {
+ ctx context.Context
+ cancel context.CancelFunc
+}
+
// WorkspacePath returns the path to the session workspace directory when infinite
// sessions are enabled. Contains checkpoints/, plan.md, and files/ subdirectories.
// Returns empty string if infinite sessions are disabled.
@@ -385,6 +396,7 @@ func newSession(
toolHandlers: make(map[string]ToolHandler),
commandHandlers: make(map[string]CommandHandler),
eventCh: make(chan SessionEvent, 128),
+ eventDone: make(chan struct{}),
RPC: rpc.NewSessionRPC(client, sessionID),
}
s.clientSessionAPIs.Canvas = newCanvasClientSessionAdapter(s)
@@ -1389,16 +1401,24 @@ func fromRPCElicitationRequestedSchema(schema *rpc.ElicitationRequestedSchema) *
// serial, FIFO dispatch without blocking the read loop.
func (s *Session) dispatchEvent(event SessionEvent) {
s.updateOpenCanvasesFromEvent(event)
- go s.handleBroadcastEvent(event)
-
- // Send to the event channel in a closure with a recover guard.
- // Disconnect closes eventCh, and in Go sending on a closed channel
- // panics — there is no non-panicking send primitive. We only want
- // to suppress that specific panic; other panics are not expected here.
- func() {
- defer func() { recover() }()
- s.eventCh <- event
- }()
+
+ broadcastHandled := false
+ switch data := event.Data.(type) {
+ case *ExternalToolRequestedData:
+ s.startExternalTool(data)
+ broadcastHandled = true
+ case *ExternalToolCompletedData:
+ s.cancelExternalTool(data.RequestID)
+ broadcastHandled = true
+ }
+ if !broadcastHandled {
+ go s.handleBroadcastEvent(event)
+ }
+
+ select {
+ case s.eventCh <- event:
+ case <-s.eventDone:
+ }
}
// processEvents is the single consumer goroutine for the event channel.
@@ -1406,7 +1426,14 @@ func (s *Session) dispatchEvent(event SessionEvent) {
// handlers are recovered so that one misbehaving handler does not prevent
// others from receiving the event.
func (s *Session) processEvents() {
- for event := range s.eventCh {
+ for {
+ var event SessionEvent
+ select {
+ case event = <-s.eventCh:
+ case <-s.eventDone:
+ return
+ }
+
s.handlerMutex.RLock()
handlers := make([]SessionEventHandler, 0, len(s.handlers))
for _, h := range s.handlers {
@@ -1427,6 +1454,13 @@ func (s *Session) processEvents() {
}
}
+// stopEventProcessing stops the session event consumer without making an RPC.
+// CreateSession/ResumeSession use this when a locally registered session fails
+// before it can be returned to the caller.
+func (s *Session) stopEventProcessing() {
+ s.closeOnce.Do(func() { close(s.eventDone) })
+}
+
// handleBroadcastEvent handles broadcast request events by executing local handlers
// and responding via RPC. This implements the protocol v3 broadcast model where tool
// calls and permission requests are broadcast as session events to all clients.
@@ -1436,20 +1470,6 @@ func (s *Session) processEvents() {
// cause RPC deadlocks.
func (s *Session) handleBroadcastEvent(event SessionEvent) {
switch d := event.Data.(type) {
- case *ExternalToolRequestedData:
- handler, ok := s.getToolHandler(d.ToolName)
- if !ok {
- return
- }
- var tp, ts string
- if d.Traceparent != nil {
- tp = *d.Traceparent
- }
- if d.Tracestate != nil {
- ts = *d.Tracestate
- }
- s.executeToolAndRespond(d.RequestID, d.ToolName, d.ToolCallID, d.Arguments, handler, tp, ts)
-
case *PermissionRequestedData:
if d.ResolvedByHook != nil && *d.ResolvedByHook {
return // Already resolved by a permissionRequest hook; no client action needed.
@@ -1532,11 +1552,90 @@ func (s *Session) handleBroadcastEvent(event SessionEvent) {
}
}
+func (s *Session) startExternalTool(data *ExternalToolRequestedData) {
+ handler, ok := s.getToolHandler(data.ToolName)
+ if !ok {
+ return
+ }
+
+ var traceparent, tracestate string
+ if data.Traceparent != nil {
+ traceparent = *data.Traceparent
+ }
+ if data.Tracestate != nil {
+ tracestate = *data.Tracestate
+ }
+ traceCtx := contextWithTraceParent(context.Background(), traceparent, tracestate)
+ ctx, cancel := context.WithCancel(traceCtx)
+ pending := &pendingExternalTool{ctx: ctx, cancel: cancel}
+
+ s.pendingExternalToolsM.Lock()
+ if s.externalToolsClosed {
+ s.pendingExternalToolsM.Unlock()
+ cancel()
+ return
+ }
+ if s.pendingExternalTools == nil {
+ s.pendingExternalTools = make(map[string]*pendingExternalTool)
+ }
+ if _, exists := s.pendingExternalTools[data.RequestID]; exists {
+ s.pendingExternalToolsM.Unlock()
+ cancel()
+ return
+ }
+ s.pendingExternalTools[data.RequestID] = pending
+ s.pendingExternalToolsM.Unlock()
+
+ go s.executeToolAndRespond(data.RequestID, data.ToolName, data.ToolCallID, data.Arguments, handler, pending)
+}
+
+func (s *Session) cancelExternalTool(requestID string) {
+ s.pendingExternalToolsM.Lock()
+ pending := s.pendingExternalTools[requestID]
+ delete(s.pendingExternalTools, requestID)
+ s.pendingExternalToolsM.Unlock()
+ if pending != nil {
+ pending.cancel()
+ }
+}
+
+func (s *Session) cancelPendingExternalTools() {
+ s.pendingExternalToolsM.Lock()
+ s.externalToolsClosed = true
+ pendingTools := s.pendingExternalTools
+ s.pendingExternalTools = nil
+ s.pendingExternalToolsM.Unlock()
+ for _, pending := range pendingTools {
+ pending.cancel()
+ }
+}
+
+func (s *Session) claimExternalTool(requestID string, pending *pendingExternalTool) bool {
+ s.pendingExternalToolsM.Lock()
+ defer s.pendingExternalToolsM.Unlock()
+ if s.pendingExternalTools[requestID] != pending {
+ return false
+ }
+ delete(s.pendingExternalTools, requestID)
+ return true
+}
+
// executeToolAndRespond executes a tool handler and sends the result back via RPC.
-func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, arguments any, handler ToolHandler, traceparent, tracestate string) {
- ctx := contextWithTraceParent(context.Background(), traceparent, tracestate)
+func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, arguments any, handler ToolHandler, pending *pendingExternalTool) {
+ ctx := pending.ctx
+ defer func() {
+ s.pendingExternalToolsM.Lock()
+ if s.pendingExternalTools[requestID] == pending {
+ delete(s.pendingExternalTools, requestID)
+ }
+ s.pendingExternalToolsM.Unlock()
+ pending.cancel()
+ }()
defer func() {
if r := recover(); r != nil {
+ if !s.claimExternalTool(requestID, pending) {
+ return
+ }
errMsg := fmt.Sprintf("tool panic: %v", r)
s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.HandlePendingToolCallRequest{
RequestID: requestID,
@@ -1563,8 +1662,14 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string,
invocation.AvailableTools = metadata.Tools
}
}
+ if ctx.Err() != nil {
+ return
+ }
result, err := handler(invocation)
+ if !s.claimExternalTool(requestID, pending) {
+ return
+ }
if err != nil {
errMsg := err.Error()
s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.HandlePendingToolCallRequest{
@@ -1727,9 +1832,24 @@ func (s *Session) GetEvents(ctx context.Context) ([]SessionEvent, error) {
// log.Printf("Failed to disconnect session: %v", err)
// }
func (s *Session) Disconnect() error {
- _, err := s.client.Request(context.Background(), "session.destroy", sessionDestroyRequest{SessionID: s.SessionID})
+ s.cancelPendingExternalTools()
+ result, err := s.client.Request(context.Background(), "session.detach", sessionDetachRequest{SessionID: s.SessionID})
+ if err == nil {
+ var response sessionDetachResponse
+ if decodeErr := json.Unmarshal(result, &response); decodeErr != nil {
+ err = fmt.Errorf("failed to decode session detach response: %w", decodeErr)
+ } else if !response.Success {
+ if response.Error == "" {
+ response.Error = "unknown error"
+ }
+ err = errors.New(response.Error)
+ }
+ }
- s.closeOnce.Do(func() { close(s.eventCh) })
+ // Local cleanup always runs, even if the detach RPC failed, so callers
+ // don't leak in-memory resources (event goroutines, registered
+ // providers/handlers) just because the runtime couldn't be reached.
+ s.stopEventProcessing()
s.releaseGitHubTokenProviderRegistration()
// Clear handlers
@@ -1828,6 +1948,22 @@ type SetModelOptions struct {
// ModelCapabilities overrides individual model capabilities resolved by the runtime.
// Only non-nil fields are applied over the runtime-resolved capabilities.
ModelCapabilities *rpc.ModelCapabilitiesOverride
+ // AutoTier stages an Auto routing preference atomically with selecting the
+ // "auto" model. Leave nil to leave the current preference alone.
+ //
+ // The runtime rejects this option when the model is anything other than
+ // "auto". Use [Session.SetAutoTier] to change the preference without
+ // changing the selected model.
+ //
+ // Experimental: AutoTier is part of an experimental Auto routing surface and
+ // may change or be removed.
+ AutoTier *AutoTier
+ // ResetAutoTier returns to the provider's default Auto routing as part of
+ // this switch. It is mutually exclusive with AutoTier.
+ //
+ // Experimental: ResetAutoTier is part of an experimental Auto routing surface
+ // and may change or be removed.
+ ResetAutoTier bool
}
// SetModel changes the model for this session.
@@ -1844,10 +1980,25 @@ type SetModelOptions struct {
func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOptions) error {
params := &rpc.ModelSwitchToRequest{ModelID: model}
if opts != nil {
+ if opts.AutoTier != nil && opts.ResetAutoTier {
+ return errors.New("failed to set model: AutoTier and ResetAutoTier are mutually exclusive")
+ }
params.ReasoningEffort = opts.ReasoningEffort
params.ReasoningSummary = opts.ReasoningSummary
params.ContextTier = opts.ContextTier
params.ModelCapabilities = opts.ModelCapabilities
+
+ // The generated field is a double pointer so the three cases stay
+ // distinct on the wire: a nil outer pointer omits the field and leaves
+ // any staged preference alone, while a non-nil outer pointer sends the
+ // inner value, including an explicit null.
+ switch {
+ case opts.AutoTier != nil:
+ params.AutoTier = &opts.AutoTier
+ case opts.ResetAutoTier:
+ var providerDefault *AutoTier
+ params.AutoTier = &providerDefault
+ }
}
_, err := s.RPC.Model.SwitchTo(ctx, params)
if err != nil {
@@ -1857,6 +2008,41 @@ func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOpti
return nil
}
+// SetAutoTier changes the Auto routing preference without changing the selected model.
+//
+// The runtime does not apply the preference immediately. It records the request and
+// commits it only when a later user turn using the "auto" model successfully obtains a
+// usable model from the provider. A [rpc.ModelSwitchAutoTierStatusPending] status
+// therefore confirms that the request was accepted, not that it took effect.
+//
+// Watch for the outcome through the session.model_change event on success, or the
+// ephemeral session.auto_tier_switch_failed event on failure. You can also read the
+// current committed and in-flight state at any time with session.RPC.Model.GetCurrent.
+//
+// Only the most recent request survives: issuing a new request replaces any earlier one
+// that has not yet been claimed by a turn.
+//
+// Pass nil to return to the provider's default Auto routing.
+//
+// Experimental: SetAutoTier is part of an experimental Auto routing surface and
+// may change or be removed.
+//
+// Example:
+//
+// tier := copilot.AutoTierIntelligence
+// result, err := session.SetAutoTier(context.Background(), &tier)
+// if err != nil {
+// log.Printf("Failed to set auto tier: %v", err)
+// }
+func (s *Session) SetAutoTier(ctx context.Context, autoTier *AutoTier) (*rpc.ModelSwitchAutoTierResult, error) {
+ result, err := s.RPC.Model.SwitchAutoTier(ctx, &rpc.ModelSwitchAutoTierRequest{AutoTier: autoTier})
+ if err != nil {
+ return nil, fmt.Errorf("failed to set auto tier: %w", err)
+ }
+
+ return result, nil
+}
+
type LogOptions struct {
// Level sets the log severity. Valid values are [rpc.SessionLogLevelInfo] (default),
// [rpc.SessionLogLevelWarning], and [rpc.SessionLogLevelError].
diff --git a/go/session_event_serialization_test.go b/go/session_event_serialization_test.go
index ee9258b225..c64b6ce598 100644
--- a/go/session_event_serialization_test.go
+++ b/go/session_event_serialization_test.go
@@ -14,6 +14,50 @@ var _ SessionEventData = (*rpc.UserMessageData)(nil)
var _ rpc.EmbeddedTextResourceContents = EmbeddedTextResourceContents{}
var _ EmbeddedTextResourceContents = rpc.EmbeddedTextResourceContents{}
+func TestSessionEventAutoTier(t *testing.T) {
+ for _, eventType := range []string{"session.start", "session.resume"} {
+ for _, tier := range []AutoTier{"", AutoTierEfficiency, AutoTierBalance, AutoTierIntelligence} {
+ t.Run(eventType+"/"+string(tier), func(t *testing.T) {
+ data := map[string]any{
+ "sessionId": "test-session", "version": 1,
+ "producer": "copilot", "copilotVersion": "1.0.82-1",
+ "startTime": "2026-08-28T00:00:00Z",
+ "resumeTime": "2026-08-28T00:00:00Z", "eventCount": 1,
+ }
+ if tier != "" {
+ data["autoTier"] = tier
+ }
+ wire, err := json.Marshal(map[string]any{
+ "id": "00000000-0000-0000-0000-000000000001",
+ "timestamp": "2026-08-28T00:00:00Z", "parentId": nil,
+ "type": eventType, "data": data,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ var event SessionEvent
+ if err := json.Unmarshal(wire, &event); err != nil {
+ t.Fatal(err)
+ }
+ var actual *AutoTier
+ switch eventType {
+ case "session.start":
+ actual = event.Data.(*SessionStartData).AutoTier
+ case "session.resume":
+ actual = event.Data.(*SessionResumeData).AutoTier
+ }
+ if tier == "" {
+ if actual != nil {
+ t.Fatalf("expected omitted autoTier, got %v", *actual)
+ }
+ } else if actual == nil || *actual != tier {
+ t.Fatalf("expected autoTier %q, got %v", tier, actual)
+ }
+ })
+ }
+ }
+}
+
func TestSessionEventAgentIDRoundTripsKnownEvent(t *testing.T) {
var event SessionEvent
if err := json.Unmarshal([]byte(`{
@@ -255,3 +299,70 @@ func TestManagedSettingsResolvedProvenanceRoundTrips(t *testing.T) {
t.Fatalf("expected absent clientManaged to be omitted, got %v", serialized)
}
}
+
+// The failure event is ephemeral: the runtime emits it when an Auto preference
+// switch cannot mint a usable model, and never persists or replays it.
+func TestSessionAutoTierSwitchFailedEvent(t *testing.T) {
+ reasons := []AutoTierSwitchFailureReason{
+ AutoTierSwitchFailureReasonPolicyRejected,
+ AutoTierSwitchFailureReasonRequestFailed,
+ AutoTierSwitchFailureReasonSetupFailed,
+ AutoTierSwitchFailureReasonUnsupported,
+ }
+ for _, reason := range reasons {
+ t.Run(string(reason), func(t *testing.T) {
+ wire, err := json.Marshal(map[string]any{
+ "id": "00000000-0000-0000-0000-000000000001",
+ "timestamp": "2026-08-28T00:00:00Z", "parentId": nil,
+ "type": "session.auto_tier_switch_failed",
+ "data": map[string]any{
+ "effectiveAutoTier": AutoTierBalance,
+ "requestedAutoTier": AutoTierIntelligence,
+ "reason": reason,
+ },
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ var event SessionEvent
+ if err := json.Unmarshal(wire, &event); err != nil {
+ t.Fatal(err)
+ }
+ data, ok := event.Data.(*SessionAutoTierSwitchFailedData)
+ if !ok {
+ t.Fatalf("expected *SessionAutoTierSwitchFailedData, got %T", event.Data)
+ }
+ if data.Reason != reason {
+ t.Fatalf("expected reason %q, got %q", reason, data.Reason)
+ }
+ if data.EffectiveAutoTier == nil || *data.EffectiveAutoTier != AutoTierBalance {
+ t.Fatalf("expected effective tier %q, got %v", AutoTierBalance, data.EffectiveAutoTier)
+ }
+ if data.RequestedAutoTier == nil || *data.RequestedAutoTier != AutoTierIntelligence {
+ t.Fatalf("expected requested tier %q, got %v", AutoTierIntelligence, data.RequestedAutoTier)
+ }
+ })
+ }
+}
+
+// A null requested tier means the attempt to return to provider-default Auto
+// routing is what failed.
+func TestSessionAutoTierSwitchFailedEventNullRequestedTier(t *testing.T) {
+ wire := []byte(`{"id":"00000000-0000-0000-0000-000000000001","timestamp":"2026-08-28T00:00:00Z",` +
+ `"parentId":null,"type":"session.auto_tier_switch_failed","data":{"effectiveAutoTier":"efficiency",` +
+ `"requestedAutoTier":null,"reason":"unsupported"}}`)
+ var event SessionEvent
+ if err := json.Unmarshal(wire, &event); err != nil {
+ t.Fatal(err)
+ }
+ data, ok := event.Data.(*SessionAutoTierSwitchFailedData)
+ if !ok {
+ t.Fatalf("expected *SessionAutoTierSwitchFailedData, got %T", event.Data)
+ }
+ if data.RequestedAutoTier != nil {
+ t.Fatalf("expected nil requested tier, got %v", *data.RequestedAutoTier)
+ }
+ if data.EffectiveAutoTier == nil || *data.EffectiveAutoTier != AutoTierEfficiency {
+ t.Fatalf("expected effective tier %q, got %v", AutoTierEfficiency, data.EffectiveAutoTier)
+ }
+}
diff --git a/go/session_test.go b/go/session_test.go
index 74e212c418..bdf14887a3 100644
--- a/go/session_test.go
+++ b/go/session_test.go
@@ -18,21 +18,79 @@ import (
)
// newTestSession creates a session with an event channel and starts the consumer goroutine.
-// Returns a cleanup function that closes the channel (stopping the consumer).
+// Returns a cleanup function that stops the consumer.
func newTestSession() (*Session, func()) {
s := &Session{
handlers: make([]sessionHandler, 0),
commandHandlers: make(map[string]CommandHandler),
eventCh: make(chan SessionEvent, 128),
+ eventDone: make(chan struct{}),
}
go s.processEvents()
- return s, func() { close(s.eventCh) }
+ return s, s.stopEventProcessing
}
func newTestEvent() SessionEvent {
return SessionEvent{Data: &SessionIdleData{}}
}
+func TestExternalToolCompletedCancelsBlockedHandler(t *testing.T) {
+ session, cleanup := newTestSession()
+ defer cleanup()
+
+ started := make(chan struct{})
+ cancelled := make(chan struct{})
+ session.registerTools([]Tool{{
+ Name: "blocked_tool",
+ Handler: func(invocation ToolInvocation) (ToolResult, error) {
+ close(started)
+ <-invocation.TraceContext.Done()
+ close(cancelled)
+ return ToolResult{}, invocation.TraceContext.Err()
+ },
+ }})
+
+ session.dispatchEvent(SessionEvent{Data: &ExternalToolRequestedData{
+ RequestID: "request-1",
+ SessionID: "session-1",
+ ToolCallID: "tool-call-1",
+ ToolName: "blocked_tool",
+ }})
+ select {
+ case <-started:
+ case <-time.After(time.Second):
+ t.Fatal("tool handler did not start")
+ }
+
+ session.dispatchEvent(SessionEvent{Data: &ExternalToolCompletedData{RequestID: "request-1"}})
+ select {
+ case <-cancelled:
+ case <-time.After(time.Second):
+ t.Fatal("tool handler was not cancelled")
+ }
+}
+
+func TestDispatchEventReturnsAfterEventProcessingStops(t *testing.T) {
+ session := &Session{
+ eventCh: make(chan SessionEvent),
+ eventDone: make(chan struct{}),
+ }
+
+ dispatched := make(chan struct{})
+ go func() {
+ session.dispatchEvent(newTestEvent())
+ close(dispatched)
+ }()
+
+ session.stopEventProcessing()
+
+ select {
+ case <-dispatched:
+ case <-time.After(time.Second):
+ t.Fatal("dispatchEvent remained blocked after event processing stopped")
+ }
+}
+
func ptr[T any](value T) *T {
return &value
}
@@ -58,6 +116,72 @@ func TestSession_SetModelOmitsContextTierWhenUnset(t *testing.T) {
if _, ok := params["contextTier"]; ok {
t.Fatalf("expected contextTier to be omitted, got %v", params["contextTier"])
}
+ if _, ok := params["autoTier"]; ok {
+ t.Fatalf("expected autoTier to be omitted, got %v", params["autoTier"])
+ }
+}
+
+func TestSession_SetModelForwardsAutoTier(t *testing.T) {
+ tier := AutoTierIntelligence
+ params := captureSetModelRequestForModel(t, "auto", &SetModelOptions{AutoTier: &tier})
+
+ if params["modelId"] != "auto" {
+ t.Fatalf("expected modelId auto, got %v", params["modelId"])
+ }
+ if params["autoTier"] != "intelligence" {
+ t.Fatalf("expected autoTier intelligence, got %v", params["autoTier"])
+ }
+}
+
+func TestSession_SetModelSendsExplicitNullAutoTierWhenCleared(t *testing.T) {
+ params := captureSetModelRequestForModel(t, "auto", &SetModelOptions{ResetAutoTier: true})
+
+ // An explicit null must survive to the wire. Omitting it would mean "leave
+ // the preference alone" rather than "use provider-default routing".
+ value, ok := params["autoTier"]
+ if !ok {
+ t.Fatal("expected autoTier to be present")
+ }
+ if value != nil {
+ t.Fatalf("expected autoTier to be null, got %v", value)
+ }
+}
+
+func TestSession_SetModelRejectsConflictingAutoTierOptions(t *testing.T) {
+ tier := AutoTierBalance
+ session := &Session{SessionID: "session-1"}
+
+ err := session.SetModel(context.Background(), "auto", &SetModelOptions{
+ AutoTier: &tier,
+ ResetAutoTier: true,
+ })
+ if err == nil {
+ t.Fatal("expected an error when AutoTier and ResetAutoTier are both set")
+ }
+}
+
+func TestSession_SetAutoTierForwardsTier(t *testing.T) {
+ tier := AutoTierEfficiency
+ params := captureSetAutoTierRequest(t, &tier)
+
+ if params["sessionId"] != "session-1" {
+ t.Fatalf("expected sessionId session-1, got %v", params["sessionId"])
+ }
+ if params["autoTier"] != "efficiency" {
+ t.Fatalf("expected autoTier efficiency, got %v", params["autoTier"])
+ }
+}
+
+func TestSession_SetAutoTierSendsExplicitNull(t *testing.T) {
+ params := captureSetAutoTierRequest(t, nil)
+
+ value, ok := params["autoTier"]
+ if !ok {
+ t.Fatal("expected autoTier to be present")
+ }
+ if value != nil {
+ t.Fatalf("expected autoTier to be null, got %v", value)
+ }
}
func TestSession_MCPAuthRequestSendsHostToken(t *testing.T) {
@@ -261,6 +385,28 @@ func TestMCPOauthRequiredDataAllowsOptionalMetadata(t *testing.T) {
func captureSetModelRequest(t *testing.T, opts *SetModelOptions) map[string]any {
t.Helper()
+ return captureModelRequest(t, "session.model.switchTo", func(session *Session) error {
+ return session.SetModel(context.Background(), "gpt-4.1", opts)
+ })
+}
+
+func captureSetModelRequestForModel(t *testing.T, model string, opts *SetModelOptions) map[string]any {
+ t.Helper()
+ return captureModelRequest(t, "session.model.switchTo", func(session *Session) error {
+ return session.SetModel(context.Background(), model, opts)
+ })
+}
+
+func captureSetAutoTierRequest(t *testing.T, autoTier *AutoTier) map[string]any {
+ t.Helper()
+ return captureModelRequest(t, "session.model.switchAutoTier", func(session *Session) error {
+ _, err := session.SetAutoTier(context.Background(), autoTier)
+ return err
+ })
+}
+
+func captureModelRequest(t *testing.T, method string, invoke func(*Session) error) map[string]any {
+ t.Helper()
stdinR, stdinW := io.Pipe()
stdoutR, stdoutW := io.Pipe()
@@ -292,8 +438,8 @@ func captureSetModelRequest(t *testing.T, opts *SetModelOptions) map[string]any
errCh <- err
return
}
- if request.Method != "session.model.switchTo" {
- errCh <- fmt.Errorf("expected session.model.switchTo, got %s", request.Method)
+ if request.Method != method {
+ errCh <- fmt.Errorf("expected %s, got %s", method, request.Method)
return
}
@@ -302,7 +448,7 @@ func captureSetModelRequest(t *testing.T, opts *SetModelOptions) map[string]any
response := map[string]any{
"jsonrpc": "2.0",
"id": json.RawMessage(request.ID),
- "result": map[string]any{},
+ "result": map[string]any{"status": "pending"},
}
data, err := json.Marshal(response)
if err != nil {
@@ -320,8 +466,8 @@ func captureSetModelRequest(t *testing.T, opts *SetModelOptions) map[string]any
client: client,
RPC: rpc.NewSessionRPC(client, "session-1"),
}
- if err := session.SetModel(context.Background(), "gpt-4.1", opts); err != nil {
- t.Fatalf("SetModel failed: %v", err)
+ if err := invoke(session); err != nil {
+ t.Fatalf("model request failed: %v", err)
}
select {
@@ -330,7 +476,7 @@ func captureSetModelRequest(t *testing.T, opts *SetModelOptions) map[string]any
case err := <-errCh:
t.Fatal(err)
case <-time.After(2 * time.Second):
- t.Fatal("timed out waiting for session.model.switchTo request")
+ t.Fatalf("timed out waiting for %s request", method)
}
return nil
}
@@ -423,9 +569,10 @@ func TestSession_SendAndWaitSkipsAutopilotContinuationIdle(t *testing.T) {
RPC: rpc.NewSessionRPC(client, "session-1"),
handlers: make([]sessionHandler, 0),
eventCh: make(chan SessionEvent, 8),
+ eventDone: make(chan struct{}),
}
go session.processEvents()
- defer close(session.eventCh)
+ defer session.stopEventProcessing()
resultCh := make(chan *SessionEvent, 1)
go func() {
diff --git a/go/test.sh b/go/test.sh
index dfb7bac1dd..f5924b3e8d 100755
--- a/go/test.sh
+++ b/go/test.sh
@@ -15,17 +15,14 @@ fi
# Determine COPILOT_CLI_PATH
if [ -z "$COPILOT_CLI_PATH" ]; then
- # Try to find it relative to the SDK. As of CLI 1.0.64-1 the @github/copilot
- # package is a thin loader; the runnable index.js ships in the installed
- # platform package (e.g. @github/copilot-linux-x64). Exactly one is installed.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
- POTENTIAL_PATH="$(ls "$SCRIPT_DIR"/../nodejs/node_modules/@github/copilot-*/index.js 2>/dev/null | head -n1)"
- if [ -n "$POTENTIAL_PATH" ] && [ -f "$POTENTIAL_PATH" ]; then
+ if POTENTIAL_PATH="$(cd "$SCRIPT_DIR/../nodejs" && npm run --silent prepare:runtime -- --print-path)" &&
+ [ -n "$POTENTIAL_PATH" ] && [ -f "$POTENTIAL_PATH" ]; then
export COPILOT_CLI_PATH="$POTENTIAL_PATH"
echo "📍 Auto-detected CLI path: $COPILOT_CLI_PATH"
else
echo "❌ COPILOT_CLI_PATH environment variable not set"
- echo " Run: export COPILOT_CLI_PATH=/path/to/dist-cli/index.js"
+ echo " Run: export COPILOT_CLI_PATH=/path/to/copilot"
exit 1
fi
fi
diff --git a/go/types.go b/go/types.go
index 3577731187..4a45a4019c 100644
--- a/go/types.go
+++ b/go/types.go
@@ -193,6 +193,13 @@ type ClientOptions struct {
// directory are accessible from GitHub web and mobile.
// Ignored when connecting to an existing runtime via [URIConnection].
EnableRemoteSessions bool
+ // ClientInfo declares the integrating application's identity, forwarded to the
+ // runtime on the `server.connect` handshake. Declaring it lets the
+ // telemetry the runtime emits on this connection be attributed to a
+ // consistent surface (the application and its Copilot integration) instead of
+ // the runtime's own build. All fields are optional; leave it nil to keep the
+ // runtime's default attribution.
+ ClientInfo *ClientInfo
// Mode controls the default tool surface and feature flags presented to
// sessions created by this client. The zero value ([ModeCopilotCli])
// matches legacy CLI defaults. Set to [ModeEmpty] to opt in to
@@ -204,6 +211,54 @@ type ClientOptions struct {
Mode ClientMode
}
+// ClientInfo identifies the integrating application on the `server.connect` handshake.
+//
+// Declaring it lets the telemetry the runtime emits on the connection be
+// attributed to a single, consistent surface instead of the runtime's own
+// build. All fields are optional; an empty field is omitted from the handshake.
+type ClientInfo struct {
+ // ApplicationName is the name of the application using the SDK.
+ ApplicationName string
+ // ApplicationVersion is the version of the application using the SDK.
+ ApplicationVersion string
+ // IntegrationName optionally identifies a specific integration within the
+ // application, such as an extension or plugin.
+ IntegrationName string
+ // IntegrationVersion is the optional version of the named integration.
+ IntegrationVersion string
+}
+
+// toWire maps the public [ClientInfo] onto the generated connect wire shape,
+// omitting empty fields. It returns nil when no identity was supplied so the
+// caller drops the clientInfo field and keeps the runtime's default attribution.
+func (ci *ClientInfo) toWire() *rpc.ConnectClientInfo {
+ if ci == nil {
+ return nil
+ }
+ wire := &rpc.ConnectClientInfo{}
+ populated := false
+ if ci.ApplicationName != "" {
+ wire.EditorName = &ci.ApplicationName
+ populated = true
+ }
+ if ci.ApplicationVersion != "" {
+ wire.EditorVersion = &ci.ApplicationVersion
+ populated = true
+ }
+ if ci.IntegrationName != "" {
+ wire.ExtensionName = &ci.IntegrationName
+ populated = true
+ }
+ if ci.IntegrationVersion != "" {
+ wire.ExtensionVersion = &ci.IntegrationVersion
+ populated = true
+ }
+ if !populated {
+ return nil
+ }
+ return wire
+}
+
// CloudSessionRepository is GitHub repository metadata associated with a cloud session.
type CloudSessionRepository struct {
Owner string `json:"owner"`
@@ -1688,7 +1743,9 @@ type ToolInvocation struct {
// TraceContext carries the W3C Trace Context propagated from the CLI's
// execute_tool span. Pass this to OpenTelemetry-aware code so that
// child spans created inside the handler are parented to the CLI span.
- // When no trace context is available this will be context.Background().
+ // It is cancelled when the external tool request completes or the session
+ // disconnects, so background work must derive its own lifetime if it should
+ // outlive the invocation.
TraceContext context.Context
}
@@ -2231,6 +2288,18 @@ func (p ProviderConfig) MarshalJSON() ([]byte, error) {
return json.Marshal(aux)
}
+// AutoTier selects the routing tier for model "auto" with V2 Auto.
+type AutoTier = rpc.AutoTier
+
+const (
+ // AutoTierEfficiency selects the efficiency routing tier.
+ AutoTierEfficiency = rpc.AutoTierEfficiency
+ // AutoTierBalance selects the balance routing tier.
+ AutoTierBalance = rpc.AutoTierBalance
+ // AutoTierIntelligence selects the intelligence routing tier.
+ AutoTierIntelligence = rpc.AutoTierIntelligence
+)
+
// CapiSessionOptions configures provider-scoped Copilot API (CAPI) session behavior.
//
// WebSocket transport is the default for the CAPI Responses API whenever the
@@ -2245,6 +2314,16 @@ type CapiSessionOptions struct {
// WebSocket transport. Enabled by default when the model advertises
// ws:/responses support; set to Bool(false) to force HTTP Responses transport.
EnableWebSocketResponses *bool `json:"enableWebSocketResponses,omitempty"`
+
+ // AutoTier selects the routing tier for model "auto" with V2 Auto.
+ // Requires a runtime that supports Auto tiers; it has no effect outside V2 Auto.
+ // When unset, the runtime uses its default on create and restores the last
+ // committed tier on cold resume. On resident resume, a different tier
+ // requests a safe switch that takes effect after resume succeeds and never
+ // disturbs a turn that is already running.
+ //
+ // To change the preference on a live session, use [Session.SetAutoTier].
+ AutoTier AutoTier `json:"autoTier,omitempty"`
}
// AzureProviderOptions contains Azure-specific provider configuration
@@ -2439,6 +2518,7 @@ type ModelInfo struct {
Capabilities ModelCapabilities `json:"capabilities"`
Policy *ModelPolicy `json:"policy,omitempty"`
Billing *ModelBilling `json:"billing,omitempty"`
+ Metadata map[string]any `json:"metadata,omitempty"`
SupportedReasoningEfforts []string `json:"supportedReasoningEfforts,omitempty"`
DefaultReasoningEffort string `json:"defaultReasoningEffort,omitempty"`
}
@@ -2815,11 +2895,16 @@ type sessionGetMessagesResponse struct {
Events []SessionEvent `json:"events"`
}
-// sessionDestroyRequest is the request for session.destroy
-type sessionDestroyRequest struct {
+// sessionDetachRequest is the request for session.detach.
+type sessionDetachRequest struct {
SessionID string `json:"sessionId"`
}
+type sessionDetachResponse struct {
+ Success bool `json:"success"`
+ Error string `json:"error,omitempty"`
+}
+
// sessionAbortRequest is the request for session.abort
type sessionAbortRequest struct {
SessionID string `json:"sessionId"`
diff --git a/go/zsession_events.go b/go/zsession_events.go
index 8731009064..9540968e52 100644
--- a/go/zsession_events.go
+++ b/go/zsession_events.go
@@ -12,6 +12,8 @@ type (
AgentInterruptedActivity = rpc.AgentInterruptedActivity
AgentInterruptedCancelPhase = rpc.AgentInterruptedCancelPhase
AgentInterruptedData = rpc.AgentInterruptedData
+ AgentModelPolicy = rpc.AgentModelPolicy
+ AssistantFusionPhaseActivityData = rpc.AssistantFusionPhaseActivityData
AssistantFusionPhaseCompletedData = rpc.AssistantFusionPhaseCompletedData
AssistantFusionPhaseFailedData = rpc.AssistantFusionPhaseFailedData
AssistantFusionPhaseStartedData = rpc.AssistantFusionPhaseStartedData
@@ -71,7 +73,7 @@ type (
AutoModeSwitchResponse = rpc.AutoModeSwitchResponse
AutopilotObjectiveChangedOperation = rpc.AutopilotObjectiveChangedOperation
AutopilotObjectiveChangedStatus = rpc.AutopilotObjectiveChangedStatus
- AutoTier = rpc.AutoTier
+ AutoTierSwitchFailureReason = rpc.AutoTierSwitchFailureReason
BinaryAssetReference = rpc.BinaryAssetReference
BinaryAssetReferenceType = rpc.BinaryAssetReferenceType
BinaryAssetType = rpc.BinaryAssetType
@@ -98,6 +100,10 @@ type (
CompactionCompleteCompactionTokensUsed = rpc.CompactionCompleteCompactionTokensUsed
CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail = rpc.CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail
CompactionTrigger = rpc.CompactionTrigger
+ CompletionReceiptEventRange = rpc.CompletionReceiptEventRange
+ CompletionReceiptFinalTool = rpc.CompletionReceiptFinalTool
+ CompletionReceiptStopReason = rpc.CompletionReceiptStopReason
+ CompletionReceiptToolStatus = rpc.CompletionReceiptToolStatus
ContextTier = rpc.ContextTier
CustomAgentsUpdatedAgent = rpc.CustomAgentsUpdatedAgent
ElicitationCompletedAction = rpc.ElicitationCompletedAction
@@ -127,7 +133,9 @@ type (
FusionFollowUpAction = rpc.FusionFollowUpAction
FusionFollowUpRecommendation = rpc.FusionFollowUpRecommendation
FusionPattern = rpc.FusionPattern
+ FusionPhaseActivityKind = rpc.FusionPhaseActivityKind
FusionPhaseKind = rpc.FusionPhaseKind
+ FusionPhasePlanStep = rpc.FusionPhasePlanStep
FusionPhaseStatus = rpc.FusionPhaseStatus
FusionPhaseUsage = rpc.FusionPhaseUsage
FusionScores = rpc.FusionScores
@@ -161,6 +169,7 @@ type (
MCPOauthWwwAuthenticateParams = rpc.MCPOauthWwwAuthenticateParams
MCPPromptsListChangedData = rpc.MCPPromptsListChangedData
MCPResourcesListChangedData = rpc.MCPResourcesListChangedData
+ MCPServerMetadata = rpc.MCPServerMetadata
MCPServersLoadedServer = rpc.MCPServersLoadedServer
MCPServerSource = rpc.MCPServerSource
MCPServerStatus = rpc.MCPServerStatus
@@ -250,12 +259,14 @@ type (
RawSystemNotification = rpc.RawSystemNotification
RawToolExecutionCompleteContent = rpc.RawToolExecutionCompleteContent
ReasoningSummary = rpc.ReasoningSummary
+ RemediationAction = rpc.RemediationAction
SamplingCompletedData = rpc.SamplingCompletedData
SamplingRequestedData = rpc.SamplingRequestedData
SandboxDecisionData = rpc.SandboxDecisionData
ScheduleOrigin = rpc.ScheduleOrigin
SessionAutoModeResolvedData = rpc.SessionAutoModeResolvedData
SessionAutopilotObjectiveChangedData = rpc.SessionAutopilotObjectiveChangedData
+ SessionAutoTierSwitchFailedData = rpc.SessionAutoTierSwitchFailedData
SessionBackgroundTasksChangedData = rpc.SessionBackgroundTasksChangedData
SessionBinaryAssetData = rpc.SessionBinaryAssetData
SessionCanvasClosedData = rpc.SessionCanvasClosedData
@@ -266,6 +277,7 @@ type (
SessionCanvasUnavailableData = rpc.SessionCanvasUnavailableData
SessionCompactionCompleteData = rpc.SessionCompactionCompleteData
SessionCompactionStartData = rpc.SessionCompactionStartData
+ SessionCompletionReceiptData = rpc.SessionCompletionReceiptData
SessionContextChangedData = rpc.SessionContextChangedData
SessionContextClearedData = rpc.SessionContextClearedData
SessionCustomAgentsUpdatedData = rpc.SessionCustomAgentsUpdatedData
@@ -290,11 +302,14 @@ type (
SessionLimitsExhaustedResponseAction = rpc.SessionLimitsExhaustedResponseAction
SessionManagedSettingsEnforcedData = rpc.SessionManagedSettingsEnforcedData
SessionManagedSettingsResolvedData = rpc.SessionManagedSettingsResolvedData
+ SessionMCPServerNeedsReconnectData = rpc.SessionMCPServerNeedsReconnectData
+ SessionMCPServerRemovedData = rpc.SessionMCPServerRemovedData
SessionMCPServersLoadedData = rpc.SessionMCPServersLoadedData
SessionMCPServerStatusChangedData = rpc.SessionMCPServerStatusChangedData
SessionMode = rpc.SessionMode
SessionModeChangedData = rpc.SessionModeChangedData
SessionModelChangeData = rpc.SessionModelChangeData
+ SessionModeNoticeDeliveredData = rpc.SessionModeNoticeDeliveredData
SessionPermissionsChangedData = rpc.SessionPermissionsChangedData
SessionPlanChangedData = rpc.SessionPlanChangedData
SessionRemoteSteerableChangedData = rpc.SessionRemoteSteerableChangedData
@@ -427,6 +442,8 @@ const (
AgentInterruptedActivityToolCall = rpc.AgentInterruptedActivityToolCall
AgentInterruptedCancelPhaseMidStream = rpc.AgentInterruptedCancelPhaseMidStream
AgentInterruptedCancelPhasePreFirstToken = rpc.AgentInterruptedCancelPhasePreFirstToken
+ AgentModelPolicyPreferred = rpc.AgentModelPolicyPreferred
+ AgentModelPolicyRequired = rpc.AgentModelPolicyRequired
AssistantMessageToolRequestCallerTypeProgram = rpc.AssistantMessageToolRequestCallerTypeProgram
AssistantMessageToolRequestTypeCustom = rpc.AssistantMessageToolRequestTypeCustom
AssistantMessageToolRequestTypeFunction = rpc.AssistantMessageToolRequestTypeFunction
@@ -476,9 +493,10 @@ const (
AutopilotObjectiveChangedStatusCapReached = rpc.AutopilotObjectiveChangedStatusCapReached
AutopilotObjectiveChangedStatusCompleted = rpc.AutopilotObjectiveChangedStatusCompleted
AutopilotObjectiveChangedStatusPaused = rpc.AutopilotObjectiveChangedStatusPaused
- AutoTierBalance = rpc.AutoTierBalance
- AutoTierEfficiency = rpc.AutoTierEfficiency
- AutoTierIntelligence = rpc.AutoTierIntelligence
+ AutoTierSwitchFailureReasonPolicyRejected = rpc.AutoTierSwitchFailureReasonPolicyRejected
+ AutoTierSwitchFailureReasonRequestFailed = rpc.AutoTierSwitchFailureReasonRequestFailed
+ AutoTierSwitchFailureReasonSetupFailed = rpc.AutoTierSwitchFailureReasonSetupFailed
+ AutoTierSwitchFailureReasonUnsupported = rpc.AutoTierSwitchFailureReasonUnsupported
BinaryAssetReferenceTypeImage = rpc.BinaryAssetReferenceTypeImage
BinaryAssetReferenceTypeResource = rpc.BinaryAssetReferenceTypeResource
BinaryAssetTypeImage = rpc.BinaryAssetTypeImage
@@ -494,6 +512,14 @@ const (
CompactionTriggerMemoryPressure = rpc.CompactionTriggerMemoryPressure
CompactionTriggerModelSwitch = rpc.CompactionTriggerModelSwitch
CompactionTriggerThreshold = rpc.CompactionTriggerThreshold
+ CompletionReceiptStopReasonAgentStopBlockLimit = rpc.CompletionReceiptStopReasonAgentStopBlockLimit
+ CompletionReceiptStopReasonNatural = rpc.CompletionReceiptStopReasonNatural
+ CompletionReceiptStopReasonTerminalTool = rpc.CompletionReceiptStopReasonTerminalTool
+ CompletionReceiptToolStatusDenied = rpc.CompletionReceiptToolStatusDenied
+ CompletionReceiptToolStatusFailure = rpc.CompletionReceiptToolStatusFailure
+ CompletionReceiptToolStatusRejected = rpc.CompletionReceiptToolStatusRejected
+ CompletionReceiptToolStatusSuccess = rpc.CompletionReceiptToolStatusSuccess
+ CompletionReceiptToolStatusTimeout = rpc.CompletionReceiptToolStatusTimeout
ContextTierDefault = rpc.ContextTierDefault
ContextTierLongContext = rpc.ContextTierLongContext
ElicitationCompletedActionAccept = rpc.ElicitationCompletedActionAccept
@@ -527,6 +553,9 @@ const (
FusionPatternCascade = rpc.FusionPatternCascade
FusionPatternCritique = rpc.FusionPatternCritique
FusionPatternSingle = rpc.FusionPatternSingle
+ FusionPhaseActivityKindModelOutput = rpc.FusionPhaseActivityKindModelOutput
+ FusionPhaseActivityKindToolCompleted = rpc.FusionPhaseActivityKindToolCompleted
+ FusionPhaseActivityKindToolStarted = rpc.FusionPhaseActivityKindToolStarted
FusionPhaseKindCritic = rpc.FusionPhaseKindCritic
FusionPhaseKindDraft = rpc.FusionPhaseKindDraft
FusionPhaseKindFollowUp = rpc.FusionPhaseKindFollowUp
@@ -555,6 +584,7 @@ const (
ManagedSettingsResolvedSourceDevice = rpc.ManagedSettingsResolvedSourceDevice
ManagedSettingsResolvedSourceMixed = rpc.ManagedSettingsResolvedSourceMixed
ManagedSettingsResolvedSourceNone = rpc.ManagedSettingsResolvedSourceNone
+ ManagedSettingsResolvedSourcePolicyHelper = rpc.ManagedSettingsResolvedSourcePolicyHelper
ManagedSettingsResolvedSourceServer = rpc.ManagedSettingsResolvedSourceServer
MCPHeadersRefreshCompletedOutcomeHeaders = rpc.MCPHeadersRefreshCompletedOutcomeHeaders
MCPHeadersRefreshCompletedOutcomeNone = rpc.MCPHeadersRefreshCompletedOutcomeNone
@@ -669,10 +699,16 @@ const (
ReasoningSummaryConcise = rpc.ReasoningSummaryConcise
ReasoningSummaryDetailed = rpc.ReasoningSummaryDetailed
ReasoningSummaryNone = rpc.ReasoningSummaryNone
+ RemediationActionAllowSandboxOutbound = rpc.RemediationActionAllowSandboxOutbound
+ RemediationActionReviewSandboxPolicy = rpc.RemediationActionReviewSandboxPolicy
+ RemediationActionShowAccount = rpc.RemediationActionShowAccount
+ RemediationActionSignIn = rpc.RemediationActionSignIn
+ RemediationActionSwitchAccount = rpc.RemediationActionSwitchAccount
ScheduleOriginModel = rpc.ScheduleOriginModel
ScheduleOriginUser = rpc.ScheduleOriginUser
SessionEventTypeAbort = rpc.SessionEventTypeAbort
SessionEventTypeAgentInterrupted = rpc.SessionEventTypeAgentInterrupted
+ SessionEventTypeAssistantFusionPhaseActivity = rpc.SessionEventTypeAssistantFusionPhaseActivity
SessionEventTypeAssistantFusionPhaseCompleted = rpc.SessionEventTypeAssistantFusionPhaseCompleted
SessionEventTypeAssistantFusionPhaseFailed = rpc.SessionEventTypeAssistantFusionPhaseFailed
SessionEventTypeAssistantFusionPhaseStarted = rpc.SessionEventTypeAssistantFusionPhaseStarted
@@ -729,6 +765,7 @@ const (
SessionEventTypeSandboxDecision = rpc.SessionEventTypeSandboxDecision
SessionEventTypeSessionAutoModeResolved = rpc.SessionEventTypeSessionAutoModeResolved
SessionEventTypeSessionAutopilotObjectiveChanged = rpc.SessionEventTypeSessionAutopilotObjectiveChanged
+ SessionEventTypeSessionAutoTierSwitchFailed = rpc.SessionEventTypeSessionAutoTierSwitchFailed
SessionEventTypeSessionBackgroundTasksChanged = rpc.SessionEventTypeSessionBackgroundTasksChanged
SessionEventTypeSessionBinaryAsset = rpc.SessionEventTypeSessionBinaryAsset
SessionEventTypeSessionCanvasClosed = rpc.SessionEventTypeSessionCanvasClosed
@@ -739,6 +776,7 @@ const (
SessionEventTypeSessionCanvasUnavailable = rpc.SessionEventTypeSessionCanvasUnavailable
SessionEventTypeSessionCompactionComplete = rpc.SessionEventTypeSessionCompactionComplete
SessionEventTypeSessionCompactionStart = rpc.SessionEventTypeSessionCompactionStart
+ SessionEventTypeSessionCompletionReceipt = rpc.SessionEventTypeSessionCompletionReceipt
SessionEventTypeSessionContextChanged = rpc.SessionEventTypeSessionContextChanged
SessionEventTypeSessionContextCleared = rpc.SessionEventTypeSessionContextCleared
SessionEventTypeSessionCustomAgentsUpdated = rpc.SessionEventTypeSessionCustomAgentsUpdated
@@ -757,10 +795,13 @@ const (
SessionEventTypeSessionLimitsExhaustedRequested = rpc.SessionEventTypeSessionLimitsExhaustedRequested
SessionEventTypeSessionManagedSettingsEnforced = rpc.SessionEventTypeSessionManagedSettingsEnforced
SessionEventTypeSessionManagedSettingsResolved = rpc.SessionEventTypeSessionManagedSettingsResolved
+ SessionEventTypeSessionMCPServerNeedsReconnect = rpc.SessionEventTypeSessionMCPServerNeedsReconnect
+ SessionEventTypeSessionMCPServerRemoved = rpc.SessionEventTypeSessionMCPServerRemoved
SessionEventTypeSessionMCPServersLoaded = rpc.SessionEventTypeSessionMCPServersLoaded
SessionEventTypeSessionMCPServerStatusChanged = rpc.SessionEventTypeSessionMCPServerStatusChanged
SessionEventTypeSessionModeChanged = rpc.SessionEventTypeSessionModeChanged
SessionEventTypeSessionModelChange = rpc.SessionEventTypeSessionModelChange
+ SessionEventTypeSessionModeNoticeDelivered = rpc.SessionEventTypeSessionModeNoticeDelivered
SessionEventTypeSessionPermissionsChanged = rpc.SessionEventTypeSessionPermissionsChanged
SessionEventTypeSessionPlanChanged = rpc.SessionEventTypeSessionPlanChanged
SessionEventTypeSessionRemoteSteerableChanged = rpc.SessionEventTypeSessionRemoteSteerableChanged
@@ -820,6 +861,7 @@ const (
SkillSourcePersonalCopilot = rpc.SkillSourcePersonalCopilot
SkillSourcePlugin = rpc.SkillSourcePlugin
SkillSourceProject = rpc.SkillSourceProject
+ SkillSourceSDK = rpc.SkillSourceSDK
SystemMessageRoleDeveloper = rpc.SystemMessageRoleDeveloper
SystemMessageRoleSystem = rpc.SystemMessageRoleSystem
SystemNotificationAgentCompletedStatusCompleted = rpc.SystemNotificationAgentCompletedStatusCompleted
diff --git a/java/README.md b/java/README.md
index f3ba3f63a0..69bf8b050c 100644
--- a/java/README.md
+++ b/java/README.md
@@ -30,25 +30,25 @@ runtime.
### Maven
-Replace `${copilot.sdk.version}` with the latest release from Maven Central.
-
```xml
com.githubcopilot-sdk-java
- 1.0.13-preview.4
+ 1.0.13
```
### Gradle
```groovy
-implementation 'com.github:copilot-sdk-java:1.0.13-preview.4'
+implementation 'com.github:copilot-sdk-java:1.0.13'
```
-#### Snapshot Builds
+### Snapshot builds
+
+Snapshot builds of the next development version are published to Maven Central Snapshots. To use them, add the snapshot repository and depend on the development version:
-Snapshot builds of the next development version are published to Maven Central Snapshots. To use them, add the repository and update the dependency version in your `pom.xml`:
+#### Maven
```xml
@@ -62,16 +62,14 @@ Snapshot builds of the next development version are published to Maven Central S
com.githubcopilot-sdk-java
- 1.0.14-preview.4-SNAPSHOT
+ 1.0.14-SNAPSHOT
```
-### Gradle
-
-Replace `${copilot.sdk.version}` with the latest release from Maven Central.
+#### Gradle
```groovy
-implementation 'com.github:copilot-sdk-java:1.0.14-preview.4-SNAPSHOT'
+implementation 'com.github:copilot-sdk-java:1.0.14-SNAPSHOT'
```
## In-process mode (experimental)
@@ -341,6 +339,52 @@ Chain fluent modifiers to set tool options:
For design context and decision rationale, see [ADR-006](docs/adr/adr-006-tool-definition-inline.md).
+## Auto routing tiers
+
+Use `CapiSessionOptions.setAutoTier(...)` to select `AutoTier.EFFICIENCY`,
+`AutoTier.BALANCE`, or `AutoTier.INTELLIGENCE`. This option is meaningful only
+with model `auto` (Auto mode V2).
+It requires a runtime version that supports `capi.autoTier`.
+
+```java
+import com.github.copilot.rpc.AutoTier;
+import com.github.copilot.rpc.CapiSessionOptions;
+import com.github.copilot.rpc.PermissionHandler;
+import com.github.copilot.rpc.SessionConfig;
+
+var config = new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ .setModel("auto")
+ .setCapi(new CapiSessionOptions().setAutoTier(AutoTier.BALANCE));
+```
+
+The same options work with `ResumeSessionConfig.setCapi(...)` and can be combined
+with `setEnableWebSocketResponses(false)`. The SDK omits an unset (`null`) tier:
+the runtime chooses its default on create and preserves the persisted/current
+tier on resume. An explicit tier overrides the persisted tier on cold resume. On
+resident resume, a different tier requests a safe switch applied after the
+resume succeeds; it cannot change a turn that is already in flight. The SDK does not choose a default or manage tier persistence.
+See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence)
+for the lifecycle rules.
+
+### Changing the Auto tier during a session
+
+Change the Auto routing preference without changing the selected model. The runtime does not apply the preference immediately: it records the request and commits it only when a later user turn using the `auto` model successfully obtains a usable model from the provider, so a `pending` status confirms acceptance rather than effect. Only the most recent request survives.
+
+Watch for the outcome through the `session.model_change` event on success or the ephemeral `session.auto_tier_switch_failed` event on failure. Read the authoritative committed, pending, and activating preferences at any time through the session's `model.getCurrent` RPC method.
+
+```java
+var result = session.setAutoTier(AutoTier.INTELLIGENCE).get();
+if (result.status() == ModelSwitchAutoTierStatus.PENDING) {
+ // Accepted, but not yet in effect.
+}
+
+// Return to the provider's default Auto routing.
+session.setAutoTier(null).get();
+```
+
+`setModel(SetModelOptions)` accepts the same preference through `SetModelOptions.setAutoTier(...)`, which stages the tier atomically with selecting `auto`. Call `setResetAutoTier(true)` instead to return to provider-default routing; the two options are mutually exclusive.
+
## Session Store
`enableSessionStore` on `SessionConfig` enables the cross-session store for search and retrieval across sessions. When unset in the default `CopilotClientMode.COPILOT_CLI` mode, the runtime default applies (enabled). In `CopilotClientMode.EMPTY` mode, defaults to disabled.
@@ -520,9 +564,9 @@ mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test-
#### Development Setup for native embedding
-Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js and npm in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned npm runtime package.
+Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned runtime package from the corresponding GitHub release.
-On a native Linux glibc host, Maven activates `native-linux-x64` or `native-linux-arm64` for the matching architecture when `copilot.native.libc=glibc` is set. On Windows x64, Windows ARM64, and Apple Silicon macOS, Maven activates `native-win32-x64`, `native-win32-arm64`, or `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build.
+On a native Linux glibc host, Maven activates `native-linux-x64` or `native-linux-arm64` for the matching architecture when `copilot.native.libc=glibc` is set. On Windows x64, Windows ARM64, and Apple Silicon macOS, Maven activates `native-win32-x64`, `native-win32-arm64`, or `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned platform package from the corresponding `github/copilot-cli` release during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents.
Before opting in, validate that Node.js reports glibc for the build host:
diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml
index c03be9909e..7624deb4fe 100644
--- a/java/copilot-native/pom.xml
+++ b/java/copilot-native/pom.xml
@@ -8,7 +8,7 @@
com.githubcopilot-sdk-java-parent
- 1.0.14-preview.4-SNAPSHOT
+ 1.0.14-SNAPSHOT../pom.xml
@@ -31,14 +31,13 @@
${project.basedir}/../..${project.build.directory}/native-stagingfalse
@@ -60,11 +59,10 @@
org.codehaus.mojo
diff --git a/java/copilot-native/scripts/fetch-native.mjs b/java/copilot-native/scripts/fetch-native.mjs
index 4ee91b6aa6..29800708b1 100644
--- a/java/copilot-native/scripts/fetch-native.mjs
+++ b/java/copilot-native/scripts/fetch-native.mjs
@@ -6,10 +6,9 @@
* Downloads the native runtime artifacts for one platform classifier.
*
* Steps:
- * 1. Read the pinned version and the SHA-512 `integrity` value for
- * `@github/copilot-` from `nodejs/package-lock.json`.
- * 2. `npm pack` that exact version into the staging directory.
- * 3. Verify the downloaded tarball against the `integrity` value.
+ * 1. Read the pinned version from `nodejs/package.json`.
+ * 2. Download the platform tarball and `SHA256SUMS.txt` from the matching release.
+ * 3. Verify the downloaded tarball against the release checksum.
* 4. Stage the hostless runtime tree, flattening the selected prebuild directory
* beside the package's retained top-level runtime assets.
* 5. Write an inventory consumed by the SDK's generic classpath extractor.
@@ -29,18 +28,15 @@ const excludedTopLevel = new Set([
'changelog.json',
'copilot',
'copilot.exe',
- 'copilot-sdk',
'foundry-local-sdk',
'index.js',
'LICENSE.md',
'napi-oop-runtime',
'npm-loader.js',
'package.json',
- 'preloads',
'pvrecorder',
'queries',
'README.md',
- 'sdk',
'sea-loader.js',
'webview',
]);
@@ -52,21 +48,14 @@ if (!repoRoot || !stagingDir || !classifier) {
process.exit(1);
}
-const lockPath = path.join(repoRoot, 'nodejs', 'package-lock.json');
-const packageName = `@github/copilot-${classifier}`;
-const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
-const entry = lock.packages?.[`node_modules/${packageName}`];
-
-if (!entry?.version || !entry?.integrity) {
- console.error(`Could not find version/integrity for ${packageName} in ${lockPath}`);
- process.exit(1);
-}
-
-const { version, integrity } = entry;
-if (!integrity.startsWith('sha512-')) {
- console.error(`Unsupported integrity algorithm for ${packageName}: ${integrity}`);
+const packagePath = path.join(repoRoot, 'nodejs', 'package.json');
+const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
+const version = packageJson.copilotCliVersion;
+if (!version) {
+ console.error(`Could not find copilotCliVersion in ${packagePath}`);
process.exit(1);
}
+const assetName = `github-copilot-${version}-${classifier}.tgz`;
const outDir = path.join(stagingDir, classifier);
const resourceDir = path.join(outDir, 'native', classifier);
@@ -77,7 +66,7 @@ const wrapperPath = path.join(resourceDir, wrapperFilename);
const inventoryPath = path.join(resourceDir, 'runtime-assets.list');
const platformPropertiesPath = path.join(resourceDir, 'platform.properties');
const expectedPlatformProperties = `classifier=${classifier}\nversion=${version}\n`;
-const stagingSchema = 'hostless-runtime-v2';
+const stagingSchema = 'hostless-runtime-v3';
const stampPath = path.join(outDir, '.version');
// Idempotence: skip the download only when every required staged artifact
@@ -92,18 +81,16 @@ if (
const stampLines = fs.readFileSync(stampPath, 'utf8').trim().split('\n');
const stampSchema = stampLines[0] || '';
const stampVersion = stampLines[1] || '';
- const stampIntegrity = stampLines[2] || '';
const stampTreeDigest = stampLines[3] || '';
const currentTreeDigest = digestTree(resourceDir);
const currentPlatformProperties = fs.readFileSync(platformPropertiesPath, 'utf8');
if (
stampSchema === stagingSchema &&
stampVersion === version &&
- stampIntegrity === integrity &&
stampTreeDigest === currentTreeDigest &&
currentPlatformProperties === expectedPlatformProperties
) {
- console.log(`${packageName}@${version} already staged at ${runtimePath}`);
+ console.log(`${assetName} already staged at ${runtimePath}`);
process.exit(0);
}
}
@@ -111,25 +98,36 @@ if (
fs.rmSync(outDir, { recursive: true, force: true });
fs.mkdirSync(resourceDir, { recursive: true });
-console.log(`Downloading ${packageName}@${version} ...`);
-const packOutput = execFileSync('npm', ['pack', `${packageName}@${version}`, '--pack-destination', outDir], {
- encoding: 'utf8',
- shell: process.platform === 'win32',
-});
-const tarballName = packOutput.trim().split('\n').pop().trim();
-const tarballPath = path.join(outDir, tarballName);
-
-const actual = `sha512-${createHash('sha512').update(fs.readFileSync(tarballPath)).digest('base64')}`;
-if (actual !== integrity) {
- console.error(`Integrity verification failed for ${tarballPath}`);
- console.error(` expected: ${integrity}`);
+console.log(`Downloading ${assetName} ...`);
+const releaseBase = (
+ process.env.COPILOT_CLI_DOWNLOAD_BASE_URL ??
+ 'https://github.com/github/copilot-cli/releases/download'
+).replace(/\/+$/, '');
+let archive;
+let expectedHash;
+if (process.env.COPILOT_CLI_RELEASE_TARBALL) {
+ archive = fs.readFileSync(process.env.COPILOT_CLI_RELEASE_TARBALL);
+ expectedHash = process.env.COPILOT_CLI_RELEASE_SHA256;
+} else {
+ const releaseUrl = `${releaseBase}/v${version}`;
+ const checksums = (await download(`${releaseUrl}/SHA256SUMS.txt`)).toString('utf8');
+ expectedHash = findChecksum(checksums, assetName);
+ archive = await download(`${releaseUrl}/${assetName}`);
+}
+if (!expectedHash || !/^[a-fA-F0-9]{64}$/.test(expectedHash)) {
+ throw new Error(`Missing or invalid SHA-256 for ${assetName}`);
+}
+const actual = createHash('sha256').update(archive).digest('hex');
+if (actual !== expectedHash.toLowerCase()) {
+ console.error(`Integrity verification failed for ${assetName}`);
+ console.error(` expected: ${expectedHash}`);
console.error(` actual: ${actual}`);
process.exit(1);
}
-console.log(`Integrity verified (${integrity.slice(0, 20)}...).`);
+console.log(`Integrity verified (${expectedHash.slice(0, 20)}...).`);
const inventory = [];
-const members = execFileSync('tar', ['-tzf', tarballPath], { encoding: 'utf8' })
+const members = execFileSync('tar', ['-tzf', '-'], { encoding: 'utf8', input: archive })
.split(/\r?\n/)
.filter(Boolean);
for (const member of members) {
@@ -137,15 +135,19 @@ for (const member of members) {
if (destinationRelative === null) {
continue;
}
- const listing = execFileSync('tar', ['-tvzf', tarballPath, member], { encoding: 'utf8' }).trim();
+ const listing = execFileSync('tar', ['-tvzf', '-', member], {
+ encoding: 'utf8',
+ input: archive,
+ }).trim();
if (listing.startsWith('d')) {
continue;
}
if (!listing.startsWith('-')) {
throw new Error(`Unsupported runtime package entry: ${member}`);
}
- const content = execFileSync('tar', ['-xOzf', tarballPath, member], {
+ const content = execFileSync('tar', ['-xOzf', '-', member], {
encoding: null,
+ input: archive,
maxBuffer: 512 * 1024 * 1024,
});
const destination = path.resolve(resourceDir, destinationRelative);
@@ -162,14 +164,12 @@ for (const member of members) {
inventory.sort();
fs.writeFileSync(inventoryPath, `${inventory.join('\n')}\n`);
-fs.rmSync(tarballPath, { force: true });
-
if (!fs.existsSync(runtimePath) || !fs.existsSync(wrapperPath)) {
- throw new Error(`Package ${packageName}@${version} is missing the runtime wrapper pair`);
+ throw new Error(`${assetName} is missing the runtime wrapper pair`);
}
fs.writeFileSync(platformPropertiesPath, expectedPlatformProperties);
const treeDigest = digestTree(resourceDir);
-fs.writeFileSync(stampPath, `${stagingSchema}\n${version}\n${integrity}\n${treeDigest}\n`);
+fs.writeFileSync(stampPath, `${stagingSchema}\n${version}\n${expectedHash}\n${treeDigest}\n`);
console.log(`Staged ${runtimePath}`);
@@ -224,3 +224,37 @@ function digestTree(directory) {
}
return `sha512-${hash.digest('base64')}`;
}
+
+async function download(url) {
+ let lastError;
+ for (let attempt = 0; attempt < 3; attempt++) {
+ try {
+ // lgtm[js/file-access-to-http] The repository-pinned CLI version intentionally selects the release asset.
+ const response = await fetch(url);
+ if (response.ok) {
+ return Buffer.from(await response.arrayBuffer());
+ }
+ await response.body?.cancel();
+ lastError = new Error(`${response.status} ${response.statusText}`);
+ if (response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429) {
+ break;
+ }
+ } catch (error) {
+ lastError = error;
+ }
+ if (attempt < 2) {
+ await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000));
+ }
+ }
+ throw new Error(`Failed to download ${url}: ${lastError}`);
+}
+
+function findChecksum(checksums, assetName) {
+ for (const line of checksums.split(/\r?\n/)) {
+ const [hash, name] = line.trim().split(/\s+/, 2);
+ if (name?.replace(/^\*/, '') === assetName && /^[a-fA-F0-9]{64}$/.test(hash)) {
+ return hash.toLowerCase();
+ }
+ }
+ throw new Error(`SHA256SUMS.txt does not contain ${assetName}`);
+}
diff --git a/java/copilot-native/scripts/fetch-native.test.mjs b/java/copilot-native/scripts/fetch-native.test.mjs
index 582ffa397f..3213d416f0 100644
--- a/java/copilot-native/scripts/fetch-native.test.mjs
+++ b/java/copilot-native/scripts/fetch-native.test.mjs
@@ -12,10 +12,10 @@ import { fileURLToPath } from 'node:url';
import test from 'node:test';
const version = '1.0.79';
-const integrity = 'sha512-test-integrity';
+const checksum = '0'.repeat(64);
const runtimeContent = 'runtime content';
const wrapperContent = 'wrapper content';
-const stagingSchema = 'hostless-runtime-v2';
+const stagingSchema = 'hostless-runtime-v3';
const scriptPath = fileURLToPath(new URL('./fetch-native.mjs', import.meta.url));
for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64', 'darwin-arm64']) {
@@ -26,7 +26,6 @@ for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64'
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /already staged/);
- assert.equal(fs.existsSync(fixture.npmMarkerPath), false);
});
test(`${classifier}: missing runtime wrapper does not use incremental fast path`, (t) => {
@@ -38,10 +37,10 @@ for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64'
assertRestagingAttempted(fixture, result);
});
- test(`${classifier}: legacy staging schema does not use incremental fast path`, (t) => {
+ test(`${classifier}: v2 staging schema does not use incremental fast path`, (t) => {
const fixture = createFixture(t, classifier);
const stampPath = path.join(fixture.stagingDir, classifier, '.version');
- fs.writeFileSync(stampPath, fs.readFileSync(stampPath, 'utf8').replace(stagingSchema, 'hostless-runtime-v1'));
+ fs.writeFileSync(stampPath, fs.readFileSync(stampPath, 'utf8').replace(stagingSchema, 'hostless-runtime-v2'));
const result = runScript(fixture);
@@ -73,7 +72,6 @@ for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64'
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /already staged/);
- assert.equal(fs.existsSync(fixture.npmMarkerPath), false);
});
}
@@ -95,25 +93,17 @@ test('stages retained package assets and excludes CLI-only content', (t) => {
fs.writeFileSync(path.join(packageRoot, 'README.md'), 'excluded');
const tarball = path.join(fixture.repoRoot, 'fixture.tgz');
execFileSync('tar', ['-czf', tarball, '-C', path.dirname(packageRoot), 'package']);
- const packageIntegrity = digest(fs.readFileSync(tarball));
+ const packageChecksum = createHash('sha256').update(fs.readFileSync(tarball)).digest('hex');
fs.writeFileSync(
- path.join(fixture.repoRoot, 'nodejs', 'package-lock.json'),
- JSON.stringify({
- packages: {
- [`node_modules/@github/copilot-${classifier}`]: { version, integrity: packageIntegrity },
- },
- }),
+ path.join(fixture.repoRoot, 'nodejs', 'package.json'),
+ JSON.stringify({ copilotCliVersion: version }),
);
- const fakeNpmPath = path.join(fixture.fakeBinDir, process.platform === 'win32' ? 'npm.cmd' : 'npm');
- const fakeNpm =
- process.platform === 'win32'
- ? '@copy "%FETCH_NATIVE_TARBALL%" "%4\\fixture.tgz" >nul\r\n@echo fixture.tgz\r\n'
- : '#!/bin/sh\ncp "$FETCH_NATIVE_TARBALL" "$4/fixture.tgz"\nprintf "fixture.tgz\\n"\n';
- fs.writeFileSync(fakeNpmPath, fakeNpm);
- fs.chmodSync(fakeNpmPath, 0o755);
fs.rmSync(path.join(fixture.stagingDir, classifier), { recursive: true, force: true });
- const result = runScript(fixture, { FETCH_NATIVE_TARBALL: tarball });
+ const result = runScript(fixture, {
+ COPILOT_CLI_RELEASE_TARBALL: tarball,
+ COPILOT_CLI_RELEASE_SHA256: packageChecksum,
+ });
assert.equal(result.status, 0, result.stderr);
const resourceDir = path.join(fixture.stagingDir, classifier, 'native', classifier);
@@ -133,19 +123,12 @@ function createFixture(t, classifier) {
const repoRoot = path.join(root, 'repo');
const stagingDir = path.join(root, 'staging');
const resourceDir = path.join(stagingDir, classifier, 'native', classifier);
- const fakeBinDir = path.join(root, 'bin');
- const npmMarkerPath = path.join(root, 'npm-invoked');
fs.mkdirSync(path.join(repoRoot, 'nodejs'), { recursive: true });
fs.mkdirSync(resourceDir, { recursive: true });
- fs.mkdirSync(fakeBinDir);
fs.writeFileSync(
- path.join(repoRoot, 'nodejs', 'package-lock.json'),
- JSON.stringify({
- packages: {
- [`node_modules/@github/copilot-${classifier}`]: { version, integrity },
- },
- }),
+ path.join(repoRoot, 'nodejs', 'package.json'),
+ JSON.stringify({ copilotCliVersion: version }),
);
const runtimePath = path.join(resourceDir, 'runtime.node');
@@ -167,23 +150,14 @@ function createFixture(t, classifier) {
fs.writeFileSync(platformPropertiesPath, `classifier=${classifier}\nversion=${version}\n`);
fs.writeFileSync(
path.join(stagingDir, classifier, '.version'),
- `${stagingSchema}\n${version}\n${integrity}\n${digestTree(resourceDir)}\n`,
+ `${stagingSchema}\n${version}\n${checksum}\n${digestTree(resourceDir)}\n`,
);
- const fakeNpmPath = path.join(fakeBinDir, process.platform === 'win32' ? 'npm.cmd' : 'npm');
- if (process.platform === 'win32') {
- fs.writeFileSync(fakeNpmPath, '@echo off\r\n> "%FETCH_NATIVE_NPM_MARKER%" echo invoked\r\nexit /b 42\r\n');
- } else {
- fs.writeFileSync(fakeNpmPath, '#!/bin/sh\nprintf invoked > "$FETCH_NATIVE_NPM_MARKER"\nexit 42\n');
- fs.chmodSync(fakeNpmPath, 0o755);
- }
-
return {
+ root,
classifier,
repoRoot,
stagingDir,
- fakeBinDir,
- npmMarkerPath,
runtimePath,
wrapperPath,
ripgrepPath,
@@ -196,16 +170,15 @@ function runScript(fixture, extraEnv = {}) {
encoding: 'utf8',
env: {
...process.env,
- PATH: `${fixture.fakeBinDir}${path.delimiter}${process.env.PATH}`,
- FETCH_NATIVE_NPM_MARKER: fixture.npmMarkerPath,
+ COPILOT_CLI_RELEASE_TARBALL: path.join(fixture.root, 'missing.tgz'),
+ COPILOT_CLI_RELEASE_SHA256: checksum,
...extraEnv,
},
});
}
function assertRestagingAttempted(fixture, result) {
- assert.notEqual(result.status, 0, 'The fake npm command should make restaging fail');
- assert.equal(fs.readFileSync(fixture.npmMarkerPath, 'utf8').trim(), 'invoked');
+ assert.notEqual(result.status, 0, 'The unavailable release should make restaging fail');
}
function digestTree(directory) {
diff --git a/java/copilot-native/scripts/validate-native-artifact.mjs b/java/copilot-native/scripts/validate-native-artifact.mjs
index 9af4ffd772..466fa260a4 100644
--- a/java/copilot-native/scripts/validate-native-artifact.mjs
+++ b/java/copilot-native/scripts/validate-native-artifact.mjs
@@ -119,22 +119,19 @@ export function validateSha256Manifest({
}
export function readPinnedNativeVersion(repoRoot, classifier) {
- const packageName = `@github/copilot-${classifier}`;
- const lockPath = path.join(repoRoot, "nodejs", "package-lock.json");
- let lock;
+ const packagePath = path.join(repoRoot, "nodejs", "package.json");
+ let packageJson;
try {
- lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
+ packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
} catch (error) {
throw new Error(
- `Could not read pinned ${packageName} version from ${lockPath}: ${error.message}`,
+ `Could not read pinned Copilot CLI version from ${packagePath}: ${error.message}`,
);
}
- const version = lock.packages?.[`node_modules/${packageName}`]?.version;
+ const version = packageJson.copilotCliVersion;
if (!version) {
- throw new Error(
- `Could not find pinned ${packageName} version in ${lockPath}`,
- );
+ throw new Error(`Could not find copilotCliVersion in ${packagePath}`);
}
return version;
}
diff --git a/java/copilot-native/scripts/validate-native-artifact.test.mjs b/java/copilot-native/scripts/validate-native-artifact.test.mjs
index 25851bb575..c07695a484 100644
--- a/java/copilot-native/scripts/validate-native-artifact.test.mjs
+++ b/java/copilot-native/scripts/validate-native-artifact.test.mjs
@@ -67,7 +67,7 @@ test("accepts a matching, complete Windows ARM64 classifier", (t) => {
}),
{
classifier: windowsArm64Classifier,
- nativeVersion: "9.8.10",
+ nativeVersion: "9.8.7",
sha256: undefined,
},
);
@@ -94,7 +94,7 @@ test("accepts a matching, complete Darwin classifier", (t) => {
}),
{
classifier: darwinClassifier,
- nativeVersion: "9.8.8",
+ nativeVersion: "9.8.7",
sha256: undefined,
},
);
@@ -121,7 +121,7 @@ test("accepts a matching, complete Linux ARM64 classifier", (t) => {
}),
{
classifier: linuxArm64Classifier,
- nativeVersion: "9.8.9",
+ nativeVersion: "9.8.7",
sha256: undefined,
},
);
@@ -248,7 +248,7 @@ test("rejects Windows resources in a Linux classifier", (t) => {
["native/linux-x64/copilot-runtime", "runtime wrapper"],
[
"native/linux-x64/platform.properties",
- "classifier=linux-x64\nversion=9.8.6\n",
+ "classifier=linux-x64\nversion=9.8.7\n",
],
["native/win32-x64/runtime.node", "wrong platform"],
]);
@@ -462,7 +462,7 @@ test("local publication validation rejects cross-classifier contamination", (t)
["native/linux-x64/copilot-runtime", "runtime wrapper"],
[
"native/linux-x64/platform.properties",
- "classifier=linux-x64\nversion=9.8.6\n",
+ "classifier=linux-x64\nversion=9.8.7\n",
],
["native/win32-x64/runtime.node", "wrong platform"],
],
@@ -537,16 +537,8 @@ function createFixture(t) {
const repoRoot = path.join(root, "repo");
fs.mkdirSync(path.join(repoRoot, "nodejs"), { recursive: true });
fs.writeFileSync(
- path.join(repoRoot, "nodejs", "package-lock.json"),
- JSON.stringify({
- packages: {
- "node_modules/@github/copilot-win32-x64": { version: "9.8.7" },
- "node_modules/@github/copilot-win32-arm64": { version: "9.8.10" },
- "node_modules/@github/copilot-linux-x64": { version: "9.8.6" },
- "node_modules/@github/copilot-linux-arm64": { version: "9.8.9" },
- "node_modules/@github/copilot-darwin-arm64": { version: "9.8.8" },
- },
- }),
+ path.join(repoRoot, "nodejs", "package.json"),
+ JSON.stringify({ copilotCliVersion: "9.8.7" }),
);
return {
diff --git a/java/docs/adr/adr-007-native-bundling-strategy.md b/java/docs/adr/adr-007-native-bundling-strategy.md
index 1540829366..3c13d451cf 100644
--- a/java/docs/adr/adr-007-native-bundling-strategy.md
+++ b/java/docs/adr/adr-007-native-bundling-strategy.md
@@ -361,7 +361,7 @@ The pattern follows DJL's `LibUtils.loadLibrary()` approach: detect the platform
2. Locates the matching `runtime.node` binary on the classpath (via `getResourceAsStream` from the classifier JAR).
3. Extracts `runtime.node` and the transitional CLI entrypoint into `~/.copilot/runtime-cache/` if valid cached files are not already present.
4. Loads it via [JNA](#references) using the C ABI entry points, per the [binding technology decision](#binding-technology-jna-over-panama-ffm) above. The JNA-specific code is confined behind an internal binding interface to preserve a future FFM migration path.
-* A validated supported-host profile fetches the pinned matching `@github/copilot-` npm package, verifies its SHA-512 integrity from `nodejs/package-lock.json`, and packages the version-matched runtime and CLI files.
+* A validated supported-host profile fetches the matching platform tarball from the pinned `github/copilot-cli` release, verifies its release SHA-256, and packages the version-matched runtime files.
* The current release work publishes the `linux-x64`, `linux-arm64`, `win32-x64`, `win32-arm64`, and `darwin-arm64` classifiers. The planned classifier set expands to the other detected platforms.
* Adding an implemented platform requires validated host activation, a profile that supplies the classifier and platform CLI filename, and lifecycle bindings for the shared host validation, fetch, script test, package, and verification executions.
* `cli-native.node` is not bundled. It provides terminal UI features that are irrelevant to the Java SDK's programmatic API surface.
diff --git a/java/pom.xml b/java/pom.xml
index 91a97aadff..b17cdbde7d 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -7,7 +7,7 @@
com.githubcopilot-sdk-java-parent
- 1.0.14-preview.4-SNAPSHOT
+ 1.0.14-SNAPSHOTpomGitHub Copilot SDK :: Java :: Parent
@@ -55,15 +55,6 @@
adjust for their directory depth (e.g. sdk/ overrides with ../../).
-->
${project.basedir}/..
-
- ^1.0.83-0true
@@ -139,7 +130,7 @@
com.github.spotbugsspotbugs-maven-plugin
- 4.10.3.0
+ 4.10.4.0com.diffplug.spotless
diff --git a/java/scripts/codegen/fetch-schemas.mjs b/java/scripts/codegen/fetch-schemas.mjs
new file mode 100644
index 0000000000..d1bcb4c0b7
--- /dev/null
+++ b/java/scripts/codegen/fetch-schemas.mjs
@@ -0,0 +1,127 @@
+#!/usr/bin/env node
+
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+import { createHash } from 'node:crypto';
+import { execFileSync } from 'node:child_process';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const scriptDir = path.dirname(fileURLToPath(import.meta.url));
+const repoRoot = path.resolve(scriptDir, '../../..');
+const packagePath = path.join(repoRoot, 'nodejs', 'package.json');
+const outputDir = path.resolve(
+ process.env.COPILOT_CLI_SCHEMA_OUTPUT ?? path.join(scriptDir, 'target', 'schemas'),
+);
+// Schemas are platform-independent; use one asset consistently on every codegen host.
+const platform = process.env.COPILOT_CLI_SCHEMA_PLATFORM ?? 'linux-x64';
+const version =
+ process.env.COPILOT_CLI_VERSION ??
+ JSON.parse(fs.readFileSync(packagePath, 'utf8')).copilotCliVersion;
+
+if (!version) {
+ throw new Error(`Could not find copilotCliVersion in ${packagePath}`);
+}
+
+const assetName = `github-copilot-${version}-${platform}.tgz`;
+const releaseBase = (
+ process.env.COPILOT_CLI_DOWNLOAD_BASE_URL ??
+ 'https://github.com/github/copilot-cli/releases/download'
+).replace(/\/+$/, '');
+
+let archive;
+let expectedHash;
+if (process.env.COPILOT_CLI_RELEASE_TARBALL) {
+ archive = fs.readFileSync(process.env.COPILOT_CLI_RELEASE_TARBALL);
+ expectedHash = process.env.COPILOT_CLI_RELEASE_SHA256;
+} else {
+ const releaseUrl = `${releaseBase}/v${version}`;
+ const checksums = (await download(`${releaseUrl}/SHA256SUMS.txt`)).toString('utf8');
+ expectedHash = findChecksum(checksums, assetName);
+ archive = await download(`${releaseUrl}/${assetName}`);
+}
+
+if (!expectedHash || !/^[a-fA-F0-9]{64}$/.test(expectedHash)) {
+ throw new Error(`Missing or invalid SHA-256 for ${assetName}`);
+}
+const actualHash = createHash('sha256').update(archive).digest('hex');
+if (actualHash !== expectedHash.toLowerCase()) {
+ throw new Error(
+ `Integrity verification failed for ${assetName}: expected ${expectedHash}, got ${actualHash}`,
+ );
+}
+
+const schemaNames = ['api.schema.json', 'session-events.schema.json'];
+const members = execFileSync('tar', ['-tzf', '-'], {
+ encoding: 'utf8',
+ input: archive,
+ maxBuffer: 512 * 1024 * 1024,
+})
+ .split(/\r?\n/)
+ .filter(Boolean);
+const outputParent = path.dirname(outputDir);
+fs.mkdirSync(outputParent, { recursive: true });
+const stagingDir = fs.mkdtempSync(path.join(outputParent, '.schemas-'));
+
+try {
+ for (const schemaName of schemaNames) {
+ const member = `package/schemas/${schemaName}`;
+ if (members.filter((candidate) => candidate === member).length !== 1) {
+ throw new Error(`${assetName} must contain exactly one ${member}`);
+ }
+ const contents = execFileSync('tar', ['-xOzf', '-', member], {
+ encoding: null,
+ input: archive,
+ maxBuffer: 512 * 1024 * 1024,
+ });
+ JSON.parse(contents.toString('utf8'));
+ fs.writeFileSync(path.join(stagingDir, schemaName), contents);
+ }
+
+ fs.rmSync(outputDir, { recursive: true, force: true });
+ fs.renameSync(stagingDir, outputDir);
+} finally {
+ fs.rmSync(stagingDir, { recursive: true, force: true });
+}
+
+console.log(`Staged Copilot CLI ${version} schemas at ${outputDir}`);
+
+async function download(url) {
+ let lastError;
+ for (let attempt = 0; attempt < 3; attempt++) {
+ try {
+ // lgtm[js/file-access-to-http] The repository-pinned CLI version selects the release asset.
+ const response = await fetch(url, { signal: AbortSignal.timeout(600_000) });
+ if (response.ok) {
+ return Buffer.from(await response.arrayBuffer());
+ }
+ await response.body?.cancel();
+ lastError = new Error(`${response.status} ${response.statusText}`);
+ if (response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429) {
+ break;
+ }
+ } catch (error) {
+ lastError = error;
+ }
+ if (attempt < 2) {
+ await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000));
+ }
+ }
+ throw new Error(`Failed to download ${url}: ${lastError}`);
+}
+
+function findChecksum(checksums, expectedAssetName) {
+ for (const line of checksums.split(/\r?\n/)) {
+ const [hash, name] = line.trim().split(/\s+/, 2);
+ if (
+ name?.replace(/^\*/, '') === expectedAssetName &&
+ /^[a-fA-F0-9]{64}$/.test(hash)
+ ) {
+ return hash.toLowerCase();
+ }
+ }
+ throw new Error(`SHA256SUMS.txt does not contain ${expectedAssetName}`);
+}
diff --git a/java/scripts/codegen/fetch-schemas.test.mjs b/java/scripts/codegen/fetch-schemas.test.mjs
new file mode 100644
index 0000000000..19d7ba6715
--- /dev/null
+++ b/java/scripts/codegen/fetch-schemas.test.mjs
@@ -0,0 +1,77 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+import assert from 'node:assert/strict';
+import { createHash } from 'node:crypto';
+import { execFileSync, spawnSync } from 'node:child_process';
+import fs from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+import test from 'node:test';
+import { fileURLToPath } from 'node:url';
+
+const scriptPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'fetch-schemas.mjs');
+
+test('extracts schemas from a verified release archive', (t) => {
+ const fixture = createFixture(t);
+ const outputDir = path.join(fixture.root, 'output');
+ const result = runFetch(fixture, outputDir);
+
+ assert.equal(result.status, 0, result.stderr);
+ assert.deepEqual(
+ JSON.parse(fs.readFileSync(path.join(outputDir, 'api.schema.json'), 'utf8')),
+ { title: 'API' },
+ );
+ assert.deepEqual(
+ JSON.parse(fs.readFileSync(path.join(outputDir, 'session-events.schema.json'), 'utf8')),
+ { title: 'Events' },
+ );
+});
+
+test('rejects an archive with the wrong checksum', (t) => {
+ const fixture = createFixture(t);
+ const result = runFetch(
+ { ...fixture, hash: '0'.repeat(64) },
+ path.join(fixture.root, 'output'),
+ );
+
+ assert.notEqual(result.status, 0);
+ assert.match(result.stderr, /Integrity verification failed/);
+});
+
+test('requires both schema files', (t) => {
+ const fixture = createFixture(t, { includeEvents: false });
+ const result = runFetch(fixture, path.join(fixture.root, 'output'));
+
+ assert.notEqual(result.status, 0);
+ assert.match(result.stderr, /must contain exactly one package\/schemas\/session-events\.schema\.json/);
+});
+
+function createFixture(t, { includeEvents = true } = {}) {
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'copilot-java-schemas-'));
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }));
+ const packageDir = path.join(root, 'package');
+ const schemasDir = path.join(packageDir, 'schemas');
+ fs.mkdirSync(schemasDir, { recursive: true });
+ fs.writeFileSync(path.join(schemasDir, 'api.schema.json'), '{"title":"API"}\n');
+ if (includeEvents) {
+ fs.writeFileSync(path.join(schemasDir, 'session-events.schema.json'), '{"title":"Events"}\n');
+ }
+ const archivePath = path.join(root, 'release.tgz');
+ execFileSync('tar', ['-czf', archivePath, '-C', root, 'package']);
+ const hash = createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
+ return { root, archivePath, hash };
+}
+
+function runFetch(fixture, outputDir) {
+ return spawnSync(process.execPath, [scriptPath], {
+ encoding: 'utf8',
+ env: {
+ ...process.env,
+ COPILOT_CLI_RELEASE_TARBALL: fixture.archivePath,
+ COPILOT_CLI_RELEASE_SHA256: fixture.hash,
+ COPILOT_CLI_SCHEMA_OUTPUT: outputDir,
+ },
+ });
+}
diff --git a/java/scripts/codegen/java.test.ts b/java/scripts/codegen/java.test.ts
new file mode 100644
index 0000000000..8db96ee3d9
--- /dev/null
+++ b/java/scripts/codegen/java.test.ts
@@ -0,0 +1,118 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+import type { JSONSchema7 } from "json-schema";
+
+import {
+ collectNestedDiscriminatedUnionTypeNames,
+ schemaTypeToJava,
+} from "./java.js";
+
+test("nested discriminated array items use their named Java type", () => {
+ const definitions: Record = {
+ MessageResult: {
+ anyOf: [
+ { $ref: "#/definitions/MessageDelivered" },
+ { $ref: "#/definitions/MessageRejected" },
+ ],
+ },
+ MessageDelivered: {
+ type: "object",
+ additionalProperties: false,
+ properties: {
+ status: { const: "delivered" },
+ actions: {
+ type: "array",
+ items: { $ref: "#/definitions/ActionChoice" },
+ },
+ },
+ },
+ MessageRejected: {
+ type: "object",
+ additionalProperties: false,
+ properties: {
+ status: { const: "rejected" },
+ reason: { type: "string" },
+ },
+ },
+ ActionChoice: {
+ anyOf: [
+ { $ref: "#/definitions/PhoneAction" },
+ { $ref: "#/definitions/EmailAction" },
+ ],
+ },
+ PhoneAction: {
+ type: "object",
+ title: "PhoneAction",
+ additionalProperties: false,
+ properties: {
+ kind: { const: "phone" },
+ number: { type: "string" },
+ sources: {
+ type: "object",
+ additionalProperties: { $ref: "#/definitions/ActionSource" },
+ },
+ },
+ },
+ EmailAction: {
+ type: "object",
+ title: "EmailAction",
+ additionalProperties: false,
+ properties: {
+ kind: { const: "email" },
+ address: { type: "string" },
+ sources: {
+ type: "object",
+ additionalProperties: { $ref: "#/definitions/ActionSource" },
+ },
+ },
+ },
+ ActionSource: {
+ anyOf: [
+ { $ref: "#/definitions/LocalActionSource" },
+ { $ref: "#/definitions/RemoteActionSource" },
+ ],
+ },
+ LocalActionSource: {
+ type: "object",
+ title: "LocalActionSource",
+ additionalProperties: false,
+ properties: {
+ location: { const: "local" },
+ },
+ },
+ RemoteActionSource: {
+ type: "object",
+ title: "RemoteActionSource",
+ additionalProperties: false,
+ properties: {
+ location: { const: "remote" },
+ url: { type: "string" },
+ },
+ },
+ };
+ const standaloneTypes = new Map();
+ const promotedUnionTypes = collectNestedDiscriminatedUnionTypeNames(
+ { $ref: "#/definitions/MessageResult" },
+ definitions
+ );
+
+ const result = schemaTypeToJava(
+ {
+ type: "array",
+ items: { $ref: "#/definitions/ActionChoice" },
+ },
+ false,
+ "MessageEnvelope",
+ "actions",
+ new Map(),
+ {
+ definitions,
+ standaloneTypes,
+ promotedUnionTypes,
+ }
+ );
+
+ assert.equal(result.javaType, "List");
+ assert.deepEqual([...standaloneTypes.keys()], ["ActionChoice"]);
+ assert.deepEqual([...promotedUnionTypes], ["ActionChoice", "ActionSource"]);
+});
diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts
index 785049afa1..71e556eda4 100644
--- a/java/scripts/codegen/java.ts
+++ b/java/scripts/codegen/java.ts
@@ -174,53 +174,15 @@ function toEnumConstant(value: string): string {
// ── Schema path resolution ───────────────────────────────────────────────────
-/**
- * Resolve a JSON schema shipped by the `@github/copilot` CLI package.
- *
- * The CLI package layout changed in 1.0.64-1: the umbrella `@github/copilot`
- * package became a thin loader and its bundled assets (including the JSON
- * schemas) moved into the platform-specific packages installed as optional
- * dependencies, e.g. `@github/copilot-linux-x64` or `@github/copilot-win32-x64`.
- *
- * We search both the Java codegen install (`scripts/codegen/node_modules`) and
- * the Node SDK install (`nodejs/node_modules`), checking the umbrella package
- * first (older versions) and then whichever platform package is present.
- */
+/** Resolve a JSON schema staged from the pinned GitHub Release artifact. */
async function resolveCopilotSchemaPath(fileName: string): Promise {
- const nodeModulesDirs = [
- path.join(REPO_ROOT, "scripts/codegen/node_modules"),
- path.join(REPO_ROOT, "nodejs/node_modules"),
- ];
-
- const candidates: string[] = [];
- for (const nodeModulesDir of nodeModulesDirs) {
- candidates.push(path.join(nodeModulesDir, "@github/copilot/schemas", fileName));
- const githubScopeDir = path.join(nodeModulesDir, "@github");
- try {
- for (const entry of await fs.readdir(githubScopeDir)) {
- if (entry.startsWith("copilot-")) {
- candidates.push(path.join(githubScopeDir, entry, "schemas", fileName));
- }
- }
- } catch (err) {
- const code = (err as NodeJS.ErrnoException).code;
- if (code !== "ENOENT" && code !== "ENOTDIR") {
- throw err;
- }
- // @github scope directory may not exist; try the next location.
- }
- }
-
- for (const candidate of candidates) {
- try {
- await fs.access(candidate);
- return candidate;
- } catch {
- // Try the next candidate.
- }
+ const schemaPath = path.join(REPO_ROOT, "scripts/codegen/target/schemas", fileName);
+ try {
+ await fs.access(schemaPath);
+ return schemaPath;
+ } catch {
+ throw new Error(`${fileName} not found. Run 'npm run fetch:schemas' in java/scripts/codegen.`);
}
-
- throw new Error(`${fileName} not found. Run 'npm ci' in java/scripts/codegen or java/nodejs first.`);
}
async function getSessionEventsSchemaPath(): Promise {
@@ -252,6 +214,7 @@ interface JavaTypeResult {
// Set before each schema generation pass; used by schemaTypeToJava and helpers.
let currentDefinitions: Record = {};
const pendingStandaloneTypes = new Map();
+const promotedNestedUnionTypes = new Set();
const generatedSessionEventTypeNames = new Set();
// Cross-schema definitions: keyed by schema filename (e.g. "session-events.schema.json"),
@@ -365,18 +328,101 @@ function findDiscriminator(variants: JSONSchema7[]): DiscriminatorInfo | null {
/**
* Resolve anyOf variants, handling $ref to definitions.
*/
-function resolveAnyOfVariants(anyOf: JSONSchema7[]): JSONSchema7[] {
+function resolveAnyOfVariants(
+ anyOf: JSONSchema7[],
+ definitions: Record = currentDefinitions
+): JSONSchema7[] {
return anyOf
.map((v) => {
if (v.$ref) {
const name = v.$ref.replace(/^#\/definitions\//, "");
- return currentDefinitions[name] ?? v;
+ return definitions[name] ?? v;
}
return v;
})
.filter((v) => v.type !== "null");
}
+export function collectNestedDiscriminatedUnionTypeNames(
+ root: unknown,
+ definitions: Record
+): Set {
+ const promotedTypes = new Set();
+ const definitionName = (schema: JSONSchema7): string | null => {
+ return schema.$ref?.match(/^#\/definitions\/([^/]+)$/)?.[1] ?? null;
+ };
+ const resolveLocal = (schema: JSONSchema7): JSONSchema7 | null => {
+ const name = definitionName(schema);
+ return name ? definitions[name] ?? null : schema;
+ };
+ const closedDiscriminatedUnionVariants = (schema: JSONSchema7): JSONSchema7[] | null => {
+ const resolved = resolveLocal(schema);
+ if (!resolved?.anyOf || !Array.isArray(resolved.anyOf)) return null;
+ const variants = resolveAnyOfVariants(resolved.anyOf as JSONSchema7[], definitions);
+ return variants.length > 1
+ && findDiscriminator(variants)
+ && variants.every((variant) => variant.additionalProperties === false)
+ ? variants
+ : null;
+ };
+
+ const rootSchema = typeof root === "object" && root !== null ? root as JSONSchema7 : null;
+ const rootVariants = rootSchema ? closedDiscriminatedUnionVariants(rootSchema) : null;
+ if (!rootVariants) return promotedTypes;
+
+ const nestedUnionItems: JSONSchema7[] = [];
+ for (const variant of rootVariants) {
+ for (const property of Object.values(variant.properties ?? {})) {
+ if (!property || typeof property !== "object") continue;
+ const propertySchema = resolveLocal(property as JSONSchema7);
+ if (
+ propertySchema?.type === "array"
+ && propertySchema.items
+ && !Array.isArray(propertySchema.items)
+ && closedDiscriminatedUnionVariants(propertySchema.items as JSONSchema7)
+ ) {
+ nestedUnionItems.push(propertySchema.items as JSONSchema7);
+ }
+ }
+ }
+
+ const visitedDefinitions = new Set();
+ const visit = (schema: JSONSchema7): void => {
+ const name = definitionName(schema);
+ if (name) {
+ if (visitedDefinitions.has(name)) return;
+ visitedDefinitions.add(name);
+ const resolved = definitions[name];
+ if (!resolved) return;
+ if (closedDiscriminatedUnionVariants(schema)) {
+ promotedTypes.add(name);
+ }
+ visit(resolved);
+ return;
+ }
+
+ for (const property of Object.values(schema.properties ?? {})) {
+ if (property && typeof property === "object") {
+ visit(property as JSONSchema7);
+ }
+ }
+ if (schema.items && !Array.isArray(schema.items)) {
+ visit(schema.items as JSONSchema7);
+ }
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
+ visit(schema.additionalProperties as JSONSchema7);
+ }
+ for (const branch of [...(schema.anyOf ?? []), ...(schema.allOf ?? [])]) {
+ if (branch && typeof branch === "object") {
+ visit(branch as JSONSchema7);
+ }
+ }
+ };
+
+ for (const items of nestedUnionItems) visit(items);
+ return promotedTypes;
+}
+
/**
* Generate a polymorphic base class and variant subclasses for a discriminated union result type.
*/
@@ -429,7 +475,10 @@ async function generatePolymorphicResultClass(
baseLines.push(` * @since 1.0.0`);
baseLines.push(` */`);
}
- baseLines.push(`@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "${discriminator.property}", visible = true)`);
+ const typeInfoInclude = promotedNestedUnionTypes.has(className)
+ ? `, include = JsonTypeInfo.As.EXISTING_PROPERTY`
+ : "";
+ baseLines.push(`@JsonTypeInfo(use = JsonTypeInfo.Id.NAME${typeInfoInclude}, property = "${discriminator.property}", visible = true)`);
baseLines.push(`@JsonSubTypes({`);
for (let i = 0; i < variantInfos.length; i++) {
const v = variantInfos[i];
@@ -578,12 +627,23 @@ async function generatePolymorphicVariantClass(
await writeGeneratedFile(`${packageDir}/${className}.java`, lines.join("\n"));
}
-function schemaTypeToJava(
+interface JavaTypeResolution {
+ definitions: Record;
+ standaloneTypes: Map;
+ promotedUnionTypes: Set;
+}
+
+export function schemaTypeToJava(
schema: JSONSchema7,
required: boolean,
context: string,
propName: string,
- nestedTypes: Map
+ nestedTypes: Map,
+ resolution: JavaTypeResolution = {
+ definitions: currentDefinitions,
+ standaloneTypes: pendingStandaloneTypes,
+ promotedUnionTypes: promotedNestedUnionTypes,
+ }
): JavaTypeResult {
const imports = new Set();
@@ -606,16 +666,27 @@ function schemaTypeToJava(
}
const name = schema.$ref.replace(/^#\/definitions\//, "");
- const resolved = currentDefinitions[name];
+ const resolved = resolution.definitions[name];
if (resolved) {
+ if (
+ resolution.promotedUnionTypes.has(name)
+ && resolved.anyOf
+ && Array.isArray(resolved.anyOf)
+ ) {
+ const variants = resolveAnyOfVariants(resolved.anyOf as JSONSchema7[], resolution.definitions);
+ if (variants.length > 1 && findDiscriminator(variants)) {
+ resolution.standaloneTypes.set(name, resolved);
+ return { javaType: name, imports };
+ }
+ }
// Enum or object types → register for standalone generation, return ref name
if ((resolved.type === "string" && resolved.enum) ||
(resolved.type === "object" && resolved.properties)) {
- pendingStandaloneTypes.set(name, resolved);
+ resolution.standaloneTypes.set(name, resolved);
return { javaType: name, imports };
}
// Other types (primitives, arrays, maps, anyOf unions) → resolve and recurse
- return schemaTypeToJava(resolved, required, context, propName, nestedTypes);
+ return schemaTypeToJava(resolved, required, context, propName, nestedTypes, resolution);
}
// Unresolved $ref — return name as-is
console.warn(`[codegen] Unresolved $ref: ${schema.$ref}`);
@@ -626,7 +697,8 @@ function schemaTypeToJava(
const hasNull = schema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null");
const nonNull = schema.anyOf.filter((s) => typeof s === "object" && (s as JSONSchema7).type !== "null");
if (nonNull.length === 1) {
- const result = schemaTypeToJava(nonNull[0] as JSONSchema7, required && !hasNull, context, propName, nestedTypes);
+ const result = schemaTypeToJava(nonNull[0] as JSONSchema7, required && !hasNull,
+ context, propName, nestedTypes, resolution);
return result;
}
// Multi-branch anyOf: fall through to Object, matching the C# generator's
@@ -662,7 +734,8 @@ function schemaTypeToJava(
const nonNullTypes = schema.type.filter((t) => t !== "null");
if (nonNullTypes.length === 1) {
const baseSchema = { ...schema, type: nonNullTypes[0] };
- return schemaTypeToJava(baseSchema as JSONSchema7, required, context, propName, nestedTypes);
+ return schemaTypeToJava(baseSchema as JSONSchema7, required, context, propName,
+ nestedTypes, resolution);
}
}
@@ -684,7 +757,8 @@ function schemaTypeToJava(
const items = schema.items as JSONSchema7 | undefined;
if (items) {
// Always pass required=false so primitives are boxed (List, not List)
- const itemResult = schemaTypeToJava(items, false, context, propName + "Item", nestedTypes);
+ const itemResult = schemaTypeToJava(items, false, context, propName + "Item",
+ nestedTypes, resolution);
imports.add("java.util.List");
for (const imp of itemResult.imports) imports.add(imp);
return { javaType: `List<${itemResult.javaType}>`, imports };
@@ -712,7 +786,8 @@ function schemaTypeToJava(
? schema.additionalProperties as JSONSchema7
: { type: "object" } as JSONSchema7;
// Always pass required=false so primitives are boxed (Map, not Map)
- const valueResult = schemaTypeToJava(valueSchema, false, context, propName + "Value", nestedTypes);
+ const valueResult = schemaTypeToJava(valueSchema, false, context,
+ propName + "Value", nestedTypes, resolution);
imports.add("java.util.Map");
for (const imp of valueResult.imports) imports.add(imp);
return { javaType: `Map`, imports };
@@ -1391,6 +1466,7 @@ async function generateRpcTypes(schemaPath: string): Promise {
// Set module-level definitions for $ref resolution
currentDefinitions = (schema.definitions ?? {}) as Record;
pendingStandaloneTypes.clear();
+ promotedNestedUnionTypes.clear();
crossSchemaDefinitions.clear();
// Load cross-schema definitions (session-events) so that cross-schema $ref values
@@ -1415,6 +1491,14 @@ async function generateRpcTypes(schemaPath: string): Promise {
if (schema.clientSession) sections.push(["clientSession", schema.clientSession]);
if (schema.clientGlobal) sections.push(["clientGlobal", schema.clientGlobal]);
+ for (const [, sectionNode] of sections) {
+ for (const [, method] of collectRpcMethods(sectionNode)) {
+ for (const typeName of collectNestedDiscriminatedUnionTypeNames(method.result, currentDefinitions)) {
+ promotedNestedUnionTypes.add(typeName);
+ }
+ }
+ }
+
const generatedClasses = new Map();
const allFiles: string[] = [];
@@ -2392,7 +2476,9 @@ async function main(): Promise {
console.log("\n✅ Java code generation complete!");
}
-main().catch((err) => {
- console.error("❌ Code generation failed:", err);
- process.exit(1);
-});
+if (process.argv[1] && path.resolve(process.argv[1]) === __filename) {
+ main().catch((err) => {
+ console.error("❌ Code generation failed:", err);
+ process.exit(1);
+ });
+}
diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json
index fd177ca1e8..a92322d340 100644
--- a/java/scripts/codegen/package-lock.json
+++ b/java/scripts/codegen/package-lock.json
@@ -6,9 +6,8 @@
"": {
"name": "copilot-sdk-java-codegen",
"dependencies": {
- "@github/copilot": "^1.0.83-0",
"json-schema": "^0.4.0",
- "tsx": "^4.23.12"
+ "tsx": "^4.23.13"
}
},
"node_modules/@esbuild/aix-ppc64": {
@@ -427,165 +426,6 @@
"node": ">=18"
}
},
- "node_modules/@github/copilot": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.83-0.tgz",
- "integrity": "sha512-Nv4IsqsveMgghwaBhgvSBZyIyvsqNBZTqnbVnv69+9+Suyq20vJcv6aB74UcJ7VPCMxIGJJUaJkugEtkMNv6wA==",
- "license": "SEE LICENSE IN LICENSE.md",
- "dependencies": {
- "detect-libc": "^2.1.2"
- },
- "bin": {
- "copilot": "npm-loader.js"
- },
- "optionalDependencies": {
- "@github/copilot-darwin-arm64": "1.0.83-0",
- "@github/copilot-darwin-x64": "1.0.83-0",
- "@github/copilot-linux-arm64": "1.0.83-0",
- "@github/copilot-linux-x64": "1.0.83-0",
- "@github/copilot-linuxmusl-arm64": "1.0.83-0",
- "@github/copilot-linuxmusl-x64": "1.0.83-0",
- "@github/copilot-win32-arm64": "1.0.83-0",
- "@github/copilot-win32-x64": "1.0.83-0"
- }
- },
- "node_modules/@github/copilot-darwin-arm64": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.83-0.tgz",
- "integrity": "sha512-0KQjKS9vd4QGxLAbFJcvyv/zsC5kivrtDe0UZhHt/43nUGqoS61DFcsM596/kg75vNE6c9J4gmZ5fUPYef+0hw==",
- "cpu": [
- "arm64"
- ],
- "license": "SEE LICENSE IN LICENSE.md",
- "optional": true,
- "os": [
- "darwin"
- ],
- "bin": {
- "copilot-darwin-arm64": "copilot"
- }
- },
- "node_modules/@github/copilot-darwin-x64": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.83-0.tgz",
- "integrity": "sha512-fiyW+hy4c8AI7ONxN623f9cmJGRpbqTztc0jSVXc9z9WwzcWi39X0nxUprRM2l2Dq6YQ3guPCqGl/g1T5bQfQg==",
- "cpu": [
- "x64"
- ],
- "license": "SEE LICENSE IN LICENSE.md",
- "optional": true,
- "os": [
- "darwin"
- ],
- "bin": {
- "copilot-darwin-x64": "copilot"
- }
- },
- "node_modules/@github/copilot-linux-arm64": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.83-0.tgz",
- "integrity": "sha512-RWbRU+KgEmtAdKp1GQVTqfdwg4Ti/OVmgZGkXq4lMYj3wnBBQcayFpSLHg5ShzDSS0RglD4b8Z27NjPrm7bXxA==",
- "cpu": [
- "arm64"
- ],
- "license": "SEE LICENSE IN LICENSE.md",
- "optional": true,
- "os": [
- "linux"
- ],
- "bin": {
- "copilot-linux-arm64": "copilot"
- }
- },
- "node_modules/@github/copilot-linux-x64": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.83-0.tgz",
- "integrity": "sha512-5COXUNT+jDfkeyqrymZMvhTogkBYUXt+wuRwKrK6ol5vaw5SoDP1DYbI2hIEfoUj4g7XTHLUCD1s3lw8eicqUA==",
- "cpu": [
- "x64"
- ],
- "license": "SEE LICENSE IN LICENSE.md",
- "optional": true,
- "os": [
- "linux"
- ],
- "bin": {
- "copilot-linux-x64": "copilot"
- }
- },
- "node_modules/@github/copilot-linuxmusl-arm64": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.83-0.tgz",
- "integrity": "sha512-7sYf364iz6s97ClviBRQusTKz3S3TgoKniyYv8+aRi5f5w6TL8NTPnGX1bXMeU0VZmk5VKQTlxVRO2yA4uFwpg==",
- "cpu": [
- "arm64"
- ],
- "license": "SEE LICENSE IN LICENSE.md",
- "optional": true,
- "os": [
- "linux"
- ],
- "bin": {
- "copilot-linuxmusl-arm64": "copilot"
- }
- },
- "node_modules/@github/copilot-linuxmusl-x64": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.83-0.tgz",
- "integrity": "sha512-jze/f6Yd3Y83kxUa88kXUiwHlZmHDwAqudswdHT6f6q+K1ZEELFGEzbB6Ku4i0L8M6wHXO1EF/zaiSFWQaM4Tw==",
- "cpu": [
- "x64"
- ],
- "license": "SEE LICENSE IN LICENSE.md",
- "optional": true,
- "os": [
- "linux"
- ],
- "bin": {
- "copilot-linuxmusl-x64": "copilot"
- }
- },
- "node_modules/@github/copilot-win32-arm64": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.83-0.tgz",
- "integrity": "sha512-93jln98UAJpslMQ7n+wAmCpoOWGEV5lXxV/DaEajySvYrCU33D2yj7d9kl8X2CgaUVBas6sNWSWKtqy+rKJxXQ==",
- "cpu": [
- "arm64"
- ],
- "license": "SEE LICENSE IN LICENSE.md",
- "optional": true,
- "os": [
- "win32"
- ],
- "bin": {
- "copilot-win32-arm64": "copilot.exe"
- }
- },
- "node_modules/@github/copilot-win32-x64": {
- "version": "1.0.83-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.83-0.tgz",
- "integrity": "sha512-+4Htk3CixO1qcOtYegjn33/8bSDdx8QXDpgVBak2D4Y5hzBWPO5IuQoICwvjaW5VOIW+I7Q62RK2pupSjxB38Q==",
- "cpu": [
- "x64"
- ],
- "license": "SEE LICENSE IN LICENSE.md",
- "optional": true,
- "os": [
- "win32"
- ],
- "bin": {
- "copilot-win32-x64": "copilot.exe"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/esbuild": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
@@ -648,9 +488,9 @@
"license": "(AFL-2.1 OR BSD-3-Clause)"
},
"node_modules/tsx": {
- "version": "4.23.12",
- "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz",
- "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==",
+ "version": "4.23.13",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz",
+ "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==",
"license": "MIT",
"dependencies": {
"esbuild": "~0.28.0"
diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json
index 8089a79133..3e7761fc20 100644
--- a/java/scripts/codegen/package.json
+++ b/java/scripts/codegen/package.json
@@ -3,12 +3,13 @@
"private": true,
"type": "module",
"scripts": {
- "generate": "tsx java.ts",
- "generate:java": "tsx java.ts"
+ "fetch:schemas": "node fetch-schemas.mjs",
+ "generate": "npm run fetch:schemas && tsx java.ts",
+ "generate:java": "npm run generate",
+ "test": "node --test fetch-schemas.test.mjs && tsx --test java.test.ts"
},
"dependencies": {
- "@github/copilot": "^1.0.83-0",
"json-schema": "^0.4.0",
- "tsx": "^4.23.12"
+ "tsx": "^4.23.13"
}
}
diff --git a/java/sdk/jbang-example.java b/java/sdk/jbang-example.java
index 0a1505f9c2..4df2583e8c 100644
--- a/java/sdk/jbang-example.java
+++ b/java/sdk/jbang-example.java
@@ -1,5 +1,5 @@
///usr/bin/env jbang "$0" "$@" ; exit $?
-//DEPS com.github:copilot-sdk-java:1.0.13-preview.4
+//DEPS com.github:copilot-sdk-java:1.0.13
import com.github.copilot.CopilotClient;
import com.github.copilot.generated.AssistantMessageEvent;
import com.github.copilot.generated.SessionUsageInfoEvent;
diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml
index bb7cd2fa36..895c464911 100644
--- a/java/sdk/pom.xml
+++ b/java/sdk/pom.xml
@@ -8,7 +8,7 @@
com.githubcopilot-sdk-java-parent
- 1.0.14-preview.4-SNAPSHOT
+ 1.0.14-SNAPSHOT../pom.xml
@@ -42,20 +42,18 @@
${project.basedir}/../..${copilot.sdk.root}/test
- ${copilot.sdk.root}/nodejs/node_modules/@github/copilot/npm-loader.js
+ falsecom.github.spotbugsspotbugs-annotations
- 4.10.3
+ 4.10.4provided
@@ -196,12 +194,7 @@
-
+
install-nodejs-cli-dependenciesgenerate-test-resources
@@ -249,9 +242,8 @@
${copilot.cli.path}
@@ -281,10 +273,9 @@
${copilot.cli.path}
@@ -632,6 +623,8 @@ did not produce the multi-release output. Re-build on JDK 25+ and verify the
**/AskUserTest.java**/CompactionTest.java
+
+ **/CopilotClientTest.java**/CopilotSessionTest.java**/ErrorHandlingTest.java**/EventFidelityTest.java
@@ -782,57 +775,6 @@ did not produce the multi-release output. Re-build on JDK 25+ and verify the
-
-
- update-schemas-from-npm-artifact
-
-
-
- org.codehaus.mojo
- exec-maven-plugin
-
-
- update-copilot-schema-version
- generate-sources
-
- exec
-
-
- npm
- ${project.parent.basedir}/scripts/codegen
-
- install
- @github/copilot@${copilot.schema.version}
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
-
-
- require-schema-version
- validate
-
- enforce
-
-
-
-
- copilot.schema.version
- You must specify -Dcopilot.schema.version=VERSION (e.g. 1.0.25)
-
-
-
-
-
-
-
-
-
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AgentModelPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/AgentModelPolicy.java
new file mode 100644
index 0000000000..06c80ba22e
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/AgentModelPolicy.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Whether configured models are advisory preferences or required constraints
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum AgentModelPolicy {
+ /** The {@code preferred} variant. */
+ PREFERRED("preferred"),
+ /** The {@code required} variant. */
+ REQUIRED("required");
+
+ private final String value;
+ AgentModelPolicy(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static AgentModelPolicy fromValue(String value) {
+ for (AgentModelPolicy v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown AgentModelPolicy value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseActivityEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseActivityEvent.java
new file mode 100644
index 0000000000..64d8d58283
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseActivityEvent.java
@@ -0,0 +1,57 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Session event "assistant.fusion_phase_activity". Experimental content-safe activity signal for a running HydraFusion phase.
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class AssistantFusionPhaseActivityEvent extends SessionEvent {
+
+ @Override
+ public String getType() { return "assistant.fusion_phase_activity"; }
+
+ @JsonProperty("data")
+ private AssistantFusionPhaseActivityEventData data;
+
+ public AssistantFusionPhaseActivityEventData getData() { return data; }
+ public void setData(AssistantFusionPhaseActivityEventData data) { this.data = data; }
+
+ /** Data payload for {@link AssistantFusionPhaseActivityEvent}. */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record AssistantFusionPhaseActivityEventData(
+ /** Identifier of the HydraFusion turn containing the phase. */
+ @JsonProperty("fusionId") String fusionId,
+ /** Stable identifier for the concrete phase. */
+ @JsonProperty("phaseId") String phaseId,
+ /** Kind of phase currently executing. */
+ @JsonProperty("phaseKind") FusionPhaseKind phaseKind,
+ /** HydraFusion orchestration pattern containing the phase. */
+ @JsonProperty("pattern") FusionPattern pattern,
+ /** Semantic role assigned to the phase. */
+ @JsonProperty("role") String role,
+ /** Conversation scope in which the phase executes. */
+ @JsonProperty("conversationScope") FusionConversationScope conversationScope,
+ /** Kind of real activity observed. */
+ @JsonProperty("activity") FusionPhaseActivityKind activity,
+ /** Cumulative private response bytes observed for this model call. The event never includes response text. */
+ @JsonProperty("totalResponseSizeBytes") Long totalResponseSizeBytes,
+ /** Opaque hashed correlation token for matching tool-started and tool-completed activity within this Fusion activity stream. It is not the tool call identifier exposed by tool lifecycle events. */
+ @JsonProperty("toolCallId") String toolCallId
+ ) {
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java
index d2ad87f7c4..ee9c5fd485 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java
@@ -24,7 +24,7 @@
public record AssistantMessageReasoningBlocks(
/** Model provider that produced these reasoning blocks. */
@JsonProperty("provider") String provider,
- /** Provider-native reasoning content blocks (e.g. Anthropic `thinking` / `redacted_thinking`) preserved verbatim, in order. A single response can carry several, each signed over the content preceding it, so dropping or reordering any of them invalidates the rest. */
+ /** Provider-native reasoning items or content blocks preserved verbatim, in order. A single response can carry several, and provider signatures or identifiers may depend on their exact content and ordering. */
@JsonProperty("blocks") List blocks
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AutoTierSwitchFailureReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutoTierSwitchFailureReason.java
new file mode 100644
index 0000000000..4180045ace
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/AutoTierSwitchFailureReason.java
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Terminal reason an Auto preference activation failed.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum AutoTierSwitchFailureReason {
+ /** The {@code policy_rejected} variant. */
+ POLICY_REJECTED("policy_rejected"),
+ /** The {@code request_failed} variant. */
+ REQUEST_FAILED("request_failed"),
+ /** The {@code setup_failed} variant. */
+ SETUP_FAILED("setup_failed"),
+ /** The {@code unsupported} variant. */
+ UNSUPPORTED("unsupported");
+
+ private final String value;
+ AutoTierSwitchFailureReason(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static AutoTierSwitchFailureReason fromValue(String value) {
+ for (AutoTierSwitchFailureReason v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown AutoTierSwitchFailureReason value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptEventRange.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptEventRange.java
new file mode 100644
index 0000000000..7b05d1c9cd
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptEventRange.java
@@ -0,0 +1,29 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Inclusive durable event range summarized by a completion receipt.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record CompletionReceiptEventRange(
+ /** Identifier of the user message that starts the covered exchange. */
+ @JsonProperty("startEventId") String startEventId,
+ /** Identifier of the assistant turn-end event that ends the covered exchange. Always equals the receipt's sourceEventId, so either field is a valid join key. */
+ @JsonProperty("endEventId") String endEventId
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptFinalTool.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptFinalTool.java
new file mode 100644
index 0000000000..74d78d7ee1
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptFinalTool.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Final structured tool completion in the covered event range.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record CompletionReceiptFinalTool(
+ /** Unique identifier of the completed tool call. */
+ @JsonProperty("toolCallId") String toolCallId,
+ /** Tool name from the matching tool execution start event, when available. */
+ @JsonProperty("toolName") String toolName,
+ /** Structured success or failure status from the tool completion event. */
+ @JsonProperty("status") CompletionReceiptToolStatus status,
+ /** Process exit code from a structured shell result, when available. */
+ @JsonProperty("exitCode") Long exitCode
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptStopReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptStopReason.java
new file mode 100644
index 0000000000..49874b0275
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptStopReason.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Runtime reason the completion decision was accepted.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum CompletionReceiptStopReason {
+ /** The {@code natural} variant. */
+ NATURAL("natural"),
+ /** The {@code terminal_tool} variant. */
+ TERMINAL_TOOL("terminal_tool"),
+ /** The {@code agent_stop_block_limit} variant. */
+ AGENT_STOP_BLOCK_LIMIT("agent_stop_block_limit");
+
+ private final String value;
+ CompletionReceiptStopReason(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static CompletionReceiptStopReason fromValue(String value) {
+ for (CompletionReceiptStopReason v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown CompletionReceiptStopReason value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptToolStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptToolStatus.java
new file mode 100644
index 0000000000..e82a1e6986
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptToolStatus.java
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Structured terminal status from a tool completion event.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum CompletionReceiptToolStatus {
+ /** The {@code success} variant. */
+ SUCCESS("success"),
+ /** The {@code failure} variant. */
+ FAILURE("failure"),
+ /** The {@code timeout} variant. */
+ TIMEOUT("timeout"),
+ /** The {@code rejected} variant. */
+ REJECTED("rejected"),
+ /** The {@code denied} variant. */
+ DENIED("denied");
+
+ private final String value;
+ CompletionReceiptToolStatus(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static CompletionReceiptToolStatus fromValue(String value) {
+ for (CompletionReceiptToolStatus v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown CompletionReceiptToolStatus value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java b/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java
index c2f195e486..762f0b1ac8 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java
@@ -14,7 +14,7 @@
import javax.annotation.processing.Generated;
/**
- * A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override.
+ * A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration.
*
* @since 1.0.0
*/
@@ -37,6 +37,10 @@ public record CustomAgentsUpdatedAgent(
/** Whether the agent can be selected by the user */
@JsonProperty("userInvocable") Boolean userInvocable,
/** Model override for this agent, if set */
- @JsonProperty("model") String model
+ @JsonProperty("model") String model,
+ /** Authored model ids in priority order, if configured */
+ @JsonProperty("models") List models,
+ /** Whether authored models are preferences or required constraints */
+ @JsonProperty("modelPolicy") AgentModelPolicy modelPolicy
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseActivityKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseActivityKind.java
new file mode 100644
index 0000000000..a95a08cf91
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseActivityKind.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Content-safe activity observed while a HydraFusion phase is running.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum FusionPhaseActivityKind {
+ /** The {@code model_output} variant. */
+ MODEL_OUTPUT("model_output"),
+ /** The {@code tool_started} variant. */
+ TOOL_STARTED("tool_started"),
+ /** The {@code tool_completed} variant. */
+ TOOL_COMPLETED("tool_completed");
+
+ private final String value;
+ FusionPhaseActivityKind(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static FusionPhaseActivityKind fromValue(String value) {
+ for (FusionPhaseActivityKind v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown FusionPhaseActivityKind value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhasePlanStep.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhasePlanStep.java
new file mode 100644
index 0000000000..cfb07a789a
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhasePlanStep.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Presentation-neutral phase planned for a HydraFusion turn.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record FusionPhasePlanStep(
+ /** Kind of phase that may execute. */
+ @JsonProperty("kind") FusionPhaseKind kind,
+ /** Semantic role assigned to the phase. */
+ @JsonProperty("role") String role,
+ /** Conversation scope in which the phase executes. */
+ @JsonProperty("scope") FusionConversationScope scope,
+ /** Whether the phase executes only when an earlier phase requests it. */
+ @JsonProperty("conditional") Boolean conditional
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java
index 32386f898a..90ce9de668 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java
@@ -22,6 +22,8 @@ public enum ManagedSettingsResolvedSource {
DEVICE("device"),
/** The {@code client} variant. */
CLIENT("client"),
+ /** The {@code policyHelper} variant. */
+ POLICYHELPER("policyHelper"),
/** The {@code mixed} variant. */
MIXED("mixed"),
/** The {@code none} variant. */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/McpServerMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpServerMetadata.java
new file mode 100644
index 0000000000..6657eb5d87
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/McpServerMetadata.java
@@ -0,0 +1,27 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Server-advertised metadata learned through modern discovery or legacy initialization.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record McpServerMetadata(
+ /** Non-empty natural-language guidance for using the server, or null when the server omitted instructions or advertised an empty string. */
+ @JsonProperty("instructions") String instructions
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java
index c4567f30fa..ca16f83ece 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java
@@ -29,6 +29,8 @@ public record McpServersLoadedServer(
@JsonProperty("source") McpServerSource source,
/** Error message if the server failed to connect */
@JsonProperty("error") String error,
+ /** Server-advertised metadata for a connected server. Omitted when no live connection metadata is available, including while pending or when failed, disabled, stopped, or not configured. */
+ @JsonProperty("serverMetadata") McpServerMetadata serverMetadata,
/** Transport mechanism: stdio, http, sse (deprecated), or memory (in-process MCP server) */
@JsonProperty("transport") McpServerTransport transport,
/** Name of the plugin that supplied the effective MCP server config, only when source is plugin */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java
index b7aae9ec80..a854d5ffa4 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java
@@ -40,6 +40,8 @@ public record PermissionRequestedEventData(
@JsonProperty("permissionRequest") Object permissionRequest,
/** Derived user-facing permission prompt details for UI consumers */
@JsonProperty("promptRequest") Object promptRequest,
+ /** Agent mode captured from the owning turn when permission evaluation began. */
+ @JsonProperty("agentMode") SessionMode agentMode,
/** Neutral risk metadata supplied by the tool host. Consumers may display this value but must not use it to bypass the permission decision. */
@JsonProperty("riskAssessment") Object riskAssessment,
/** When true, this permission was already resolved by a permissionRequest hook and requires no client action */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/RemediationAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/RemediationAction.java
new file mode 100644
index 0000000000..5fba9dfc33
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/RemediationAction.java
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * What the user must do to recover from a failure, named as an action rather than as one client's affordance. The runtime cannot know which affordance a client offers — a slash command, a settings pane, a link — so the accompanying message stays host-agnostic and each client renders its own copy from this value. Absent when the runtime knows of no action the user can take.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum RemediationAction {
+ /** The {@code sign_in} variant. */
+ SIGN_IN("sign_in"),
+ /** The {@code switch_account} variant. */
+ SWITCH_ACCOUNT("switch_account"),
+ /** The {@code show_account} variant. */
+ SHOW_ACCOUNT("show_account"),
+ /** The {@code review_sandbox_policy} variant. */
+ REVIEW_SANDBOX_POLICY("review_sandbox_policy"),
+ /** The {@code allow_sandbox_outbound} variant. */
+ ALLOW_SANDBOX_OUTBOUND("allow_sandbox_outbound");
+
+ private final String value;
+ RemediationAction(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static RemediationAction fromValue(String value) {
+ for (RemediationAction v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown RemediationAction value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionAutoTierSwitchFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionAutoTierSwitchFailedEvent.java
new file mode 100644
index 0000000000..7509bb678f
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionAutoTierSwitchFailedEvent.java
@@ -0,0 +1,45 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Session event "session.auto_tier_switch_failed". A transient Auto preference failure emitted when the runtime cannot mint or accept a usable model and token pair. The previously effective preference remains active, so SDK clients can surface a non-blocking failure without changing their committed-tier state. This event is ephemeral and is not persisted or replayed on resume.
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class SessionAutoTierSwitchFailedEvent extends SessionEvent {
+
+ @Override
+ public String getType() { return "session.auto_tier_switch_failed"; }
+
+ @JsonProperty("data")
+ private SessionAutoTierSwitchFailedEventData data;
+
+ public SessionAutoTierSwitchFailedEventData getData() { return data; }
+ public void setData(SessionAutoTierSwitchFailedEventData data) { this.data = data; }
+
+ /** Data payload for {@link SessionAutoTierSwitchFailedEvent}. */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record SessionAutoTierSwitchFailedEventData(
+ /** Auto preference that remains effective after the failed request. */
+ @JsonProperty("effectiveAutoTier") AutoTier effectiveAutoTier,
+ /** Auto preference that failed to activate, or null when returning to provider-default routing failed. */
+ @JsonProperty("requestedAutoTier") AutoTier requestedAutoTier,
+ /** Low-cardinality failure outcome reported by Auto resolution. */
+ @JsonProperty("reason") AutoTierSwitchFailureReason reason
+ ) {
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompletionReceiptEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompletionReceiptEvent.java
new file mode 100644
index 0000000000..15670c992a
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompletionReceiptEvent.java
@@ -0,0 +1,55 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Session event "session.completion_receipt". Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted.
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class SessionCompletionReceiptEvent extends SessionEvent {
+
+ @Override
+ public String getType() { return "session.completion_receipt"; }
+
+ @JsonProperty("data")
+ private SessionCompletionReceiptEventData data;
+
+ public SessionCompletionReceiptEventData getData() { return data; }
+ public void setData(SessionCompletionReceiptEventData data) { this.data = data; }
+
+ /** Data payload for {@link SessionCompletionReceiptEvent}. */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record SessionCompletionReceiptEventData(
+ /** Version of the completion receipt payload. */
+ @JsonProperty("schemaVersion") Long schemaVersion,
+ /** One-based accepted completion receipt ordinal in the durable session history. */
+ @JsonProperty("attempt") Long attempt,
+ /** Identifier of the assistant turn-end event that supplied the accepted completion boundary. This is the receipt's idempotency key, and always equals eventRange.endEventId. */
+ @JsonProperty("sourceEventId") String sourceEventId,
+ /** Inclusive durable event range summarized by this receipt. */
+ @JsonProperty("eventRange") CompletionReceiptEventRange eventRange,
+ /** Runtime reason the completion decision was accepted. */
+ @JsonProperty("stopReason") CompletionReceiptStopReason stopReason,
+ /** Final structured tool completion in the covered range, when one exists. */
+ @JsonProperty("finalTool") CompletionReceiptFinalTool finalTool,
+ /** Number of successful structured tool completions in the covered range. */
+ @JsonProperty("successfulToolCount") Long successfulToolCount,
+ /** Number of failed structured tool completions in the covered range. */
+ @JsonProperty("failedToolCount") Long failedToolCount
+ ) {
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java
index cd7f34365f..3ef32dfd13 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java
@@ -42,6 +42,8 @@ public record SessionErrorEventData(
@JsonProperty("eligibleForAutoSwitch") Boolean eligibleForAutoSwitch,
/** Human-readable error message */
@JsonProperty("message") String message,
+ /** What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value. */
+ @JsonProperty("remediation") RemediationAction remediation,
/** Error stack trace, when available */
@JsonProperty("stack") String stack,
/** HTTP status code from the upstream request, if applicable */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java
index e22561b914..367fa120b5 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java
@@ -38,7 +38,9 @@
@JsonSubTypes.Type(value = SessionInfoEvent.class, name = "session.info"),
@JsonSubTypes.Type(value = SessionWarningEvent.class, name = "session.warning"),
@JsonSubTypes.Type(value = SessionModelChangeEvent.class, name = "session.model_change"),
+ @JsonSubTypes.Type(value = SessionAutoTierSwitchFailedEvent.class, name = "session.auto_tier_switch_failed"),
@JsonSubTypes.Type(value = SessionModeChangedEvent.class, name = "session.mode_changed"),
+ @JsonSubTypes.Type(value = SessionModeNoticeDeliveredEvent.class, name = "session.mode_notice_delivered"),
@JsonSubTypes.Type(value = SessionSessionLimitsChangedEvent.class, name = "session.session_limits_changed"),
@JsonSubTypes.Type(value = SessionPermissionsChangedEvent.class, name = "session.permissions_changed"),
@JsonSubTypes.Type(value = SessionPlanChangedEvent.class, name = "session.plan_changed"),
@@ -55,6 +57,7 @@
@JsonSubTypes.Type(value = SessionCompactionStartEvent.class, name = "session.compaction_start"),
@JsonSubTypes.Type(value = SessionCompactionCompleteEvent.class, name = "session.compaction_complete"),
@JsonSubTypes.Type(value = SessionTaskCompleteEvent.class, name = "session.task_complete"),
+ @JsonSubTypes.Type(value = SessionCompletionReceiptEvent.class, name = "session.completion_receipt"),
@JsonSubTypes.Type(value = SessionFusionRouteStartedEvent.class, name = "session.fusion_route_started"),
@JsonSubTypes.Type(value = SessionFusionRouteFailedEvent.class, name = "session.fusion_route_failed"),
@JsonSubTypes.Type(value = SessionFusionResolvedEvent.class, name = "session.fusion_resolved"),
@@ -66,6 +69,7 @@
@JsonSubTypes.Type(value = AgentInterruptedEvent.class, name = "agent.interrupted"),
@JsonSubTypes.Type(value = AssistantIntentEvent.class, name = "assistant.intent"),
@JsonSubTypes.Type(value = AssistantFusionPhaseStartedEvent.class, name = "assistant.fusion_phase_started"),
+ @JsonSubTypes.Type(value = AssistantFusionPhaseActivityEvent.class, name = "assistant.fusion_phase_activity"),
@JsonSubTypes.Type(value = AssistantFusionPhaseCompletedEvent.class, name = "assistant.fusion_phase_completed"),
@JsonSubTypes.Type(value = AssistantFusionPhaseFailedEvent.class, name = "assistant.fusion_phase_failed"),
@JsonSubTypes.Type(value = AssistantServerToolProgressEvent.class, name = "assistant.server_tool_progress"),
@@ -143,6 +147,8 @@
@JsonSubTypes.Type(value = SessionCustomAgentsUpdatedEvent.class, name = "session.custom_agents_updated"),
@JsonSubTypes.Type(value = SessionMcpServersLoadedEvent.class, name = "session.mcp_servers_loaded"),
@JsonSubTypes.Type(value = SessionMcpServerStatusChangedEvent.class, name = "session.mcp_server_status_changed"),
+ @JsonSubTypes.Type(value = SessionMcpServerRemovedEvent.class, name = "session.mcp_server_removed"),
+ @JsonSubTypes.Type(value = SessionMcpServerNeedsReconnectEvent.class, name = "session.mcp_server_needs_reconnect"),
@JsonSubTypes.Type(value = McpToolsListChangedEvent.class, name = "mcp.tools.list_changed"),
@JsonSubTypes.Type(value = McpResourcesListChangedEvent.class, name = "mcp.resources.list_changed"),
@JsonSubTypes.Type(value = McpPromptsListChangedEvent.class, name = "mcp.prompts.list_changed"),
@@ -171,7 +177,9 @@ public abstract sealed class SessionEvent permits
SessionInfoEvent,
SessionWarningEvent,
SessionModelChangeEvent,
+ SessionAutoTierSwitchFailedEvent,
SessionModeChangedEvent,
+ SessionModeNoticeDeliveredEvent,
SessionSessionLimitsChangedEvent,
SessionPermissionsChangedEvent,
SessionPlanChangedEvent,
@@ -188,6 +196,7 @@ public abstract sealed class SessionEvent permits
SessionCompactionStartEvent,
SessionCompactionCompleteEvent,
SessionTaskCompleteEvent,
+ SessionCompletionReceiptEvent,
SessionFusionRouteStartedEvent,
SessionFusionRouteFailedEvent,
SessionFusionResolvedEvent,
@@ -199,6 +208,7 @@ public abstract sealed class SessionEvent permits
AgentInterruptedEvent,
AssistantIntentEvent,
AssistantFusionPhaseStartedEvent,
+ AssistantFusionPhaseActivityEvent,
AssistantFusionPhaseCompletedEvent,
AssistantFusionPhaseFailedEvent,
AssistantServerToolProgressEvent,
@@ -276,6 +286,8 @@ public abstract sealed class SessionEvent permits
SessionCustomAgentsUpdatedEvent,
SessionMcpServersLoadedEvent,
SessionMcpServerStatusChangedEvent,
+ SessionMcpServerRemovedEvent,
+ SessionMcpServerNeedsReconnectEvent,
McpToolsListChangedEvent,
McpResourcesListChangedEvent,
McpPromptsListChangedEvent,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java
index 7553c6eb43..d63bee33b3 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java
@@ -10,6 +10,7 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
import javax.annotation.processing.Generated;
/**
@@ -62,6 +63,8 @@ public record SessionFusionResolvedEventData(
@JsonProperty("scores") FusionScores scores,
/** Validated orchestration pattern selected for the turn. */
@JsonProperty("pattern") FusionPattern pattern,
+ /** Presentation-neutral phase plan for clients that render workflow progress. */
+ @JsonProperty("phasePlan") List phasePlan,
/** Concrete model selected for the primary solver phase. */
@JsonProperty("primaryModel") String primaryModel,
/** Concrete model selected for the review or judge phase, when required. */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java
index ac3763248a..e5428d26db 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java
@@ -14,7 +14,7 @@
import javax.annotation.processing.Generated;
/**
- * Session event "session.managed_settings_resolved". Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes.
+ * Session event "session.managed_settings_resolved". Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes.
* @since 1.0.0
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -35,7 +35,7 @@ public final class SessionManagedSettingsResolvedEvent extends SessionEvent {
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record SessionManagedSettingsResolvedEventData(
- /** Channel summary: `server`, `device`, or `client` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance. */
+ /** Channel summary: `server`, `device`, `client`, or `policyHelper` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance. */
@JsonProperty("source") ManagedSettingsResolvedSource source,
/** Whether the server (account/org) managed-settings layer was present */
@JsonProperty("serverManaged") Boolean serverManaged,
@@ -43,6 +43,8 @@ public record SessionManagedSettingsResolvedEventData(
@JsonProperty("deviceManaged") Boolean deviceManaged,
/** Whether a session-local permissions layer injected by the SDK host was present */
@JsonProperty("clientManaged") Boolean clientManaged,
+ /** Whether the policy-helper managed-settings layer was present. The policy helper is the weakest channel: it fills keys no enterprise source set and can never replace one. */
+ @JsonProperty("policyHelperManaged") Boolean policyHelperManaged,
/** Whether managed policy could not be determined (e.g. a failed server fetch) and the session fell back to the fail-closed restriction. When true, restrictions such as disabling bypass-permissions are enforced even though `settings` may be absent. */
@JsonProperty("failClosed") Boolean failClosed,
/** Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerNeedsReconnectEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerNeedsReconnectEvent.java
new file mode 100644
index 0000000000..9b03a66b94
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerNeedsReconnectEvent.java
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Session event "session.mcp_server_needs_reconnect". Payload of `session.mcp_server_needs_reconnect` identifying an MCP server whose connection must be re-established.
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class SessionMcpServerNeedsReconnectEvent extends SessionEvent {
+
+ @Override
+ public String getType() { return "session.mcp_server_needs_reconnect"; }
+
+ @JsonProperty("data")
+ private SessionMcpServerNeedsReconnectEventData data;
+
+ public SessionMcpServerNeedsReconnectEventData getData() { return data; }
+ public void setData(SessionMcpServerNeedsReconnectEventData data) { this.data = data; }
+
+ /** Data payload for {@link SessionMcpServerNeedsReconnectEvent}. */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record SessionMcpServerNeedsReconnectEventData(
+ /** Name of the MCP server that needs to reconnect */
+ @JsonProperty("serverName") String serverName
+ ) {
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerRemovedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerRemovedEvent.java
new file mode 100644
index 0000000000..68b5107118
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerRemovedEvent.java
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Session event "session.mcp_server_removed". Payload of `session.mcp_server_removed` identifying an MCP server the graph no longer runs.
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class SessionMcpServerRemovedEvent extends SessionEvent {
+
+ @Override
+ public String getType() { return "session.mcp_server_removed"; }
+
+ @JsonProperty("data")
+ private SessionMcpServerRemovedEventData data;
+
+ public SessionMcpServerRemovedEventData getData() { return data; }
+ public void setData(SessionMcpServerRemovedEventData data) { this.data = data; }
+
+ /** Data payload for {@link SessionMcpServerRemovedEvent}. */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record SessionMcpServerRemovedEventData(
+ /** Name of the MCP server that was removed from the graph */
+ @JsonProperty("serverName") String serverName
+ ) {
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionModeNoticeDeliveredEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModeNoticeDeliveredEvent.java
new file mode 100644
index 0000000000..9e24109f9a
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModeNoticeDeliveredEvent.java
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: session-events.schema.json
+
+package com.github.copilot.generated;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Session event "session.mode_notice_delivered". Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume.
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class SessionModeNoticeDeliveredEvent extends SessionEvent {
+
+ @Override
+ public String getType() { return "session.mode_notice_delivered"; }
+
+ @JsonProperty("data")
+ private SessionModeNoticeDeliveredEventData data;
+
+ public SessionModeNoticeDeliveredEventData getData() { return data; }
+ public void setData(SessionModeNoticeDeliveredEventData data) { this.data = data; }
+
+ /** Data payload for {@link SessionModeNoticeDeliveredEvent}. */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record SessionModeNoticeDeliveredEventData(
+ /** Mode established by the delivered transition notice */
+ @JsonProperty("mode") SessionMode mode,
+ /** Model-visible transition notice persisted for a mid-turn delivery */
+ @JsonProperty("content") String content
+ ) {
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java
index 868d78fe0d..4ea1dbd46d 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java
@@ -55,7 +55,11 @@ public record SessionModelChangeEventData(
/** Reason the change happened, when not user-initiated. `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path, or `"refusal_fallback"` when the active model declined a request (content refusal) and the runtime switched to the configured refusal-fallback model. UI clients can use this to render contextual copy. */
@JsonProperty("cause") String cause,
/** Origin of the effective model change, when known. */
- @JsonProperty("source") ModelChangeSource source
+ @JsonProperty("source") ModelChangeSource source,
+ /** Previously committed Auto preference, when one was explicitly selected. */
+ @JsonProperty("previousAutoTier") AutoTier previousAutoTier,
+ /** Committed Auto preference after the model configuration change, when applicable. */
+ @JsonProperty("autoTier") AutoTier autoTier
) {
}
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionWarningEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionWarningEvent.java
index a253f246ed..5a76d8fa9e 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionWarningEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionWarningEvent.java
@@ -39,7 +39,9 @@ public record SessionWarningEventData(
/** Human-readable warning message for display in the timeline */
@JsonProperty("message") String message,
/** Optional URL associated with this warning that the user can open in a browser */
- @JsonProperty("url") String url
+ @JsonProperty("url") String url,
+ /** What the user must do to recover, when the runtime knows of an action. The `message` never names a client affordance, so a client that offers one — a slash command, a settings pane, a link — renders it from this value. */
+ @JsonProperty("remediation") RemediationAction remediation
) {
}
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java
index 6ad04f9699..e8675a6bbc 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java
@@ -39,13 +39,15 @@ public record SkillInvokedEventData(
@JsonProperty("name") String name,
/** Model identifier active when the skill was invoked, when known */
@JsonProperty("model") String model,
- /** File path to the SKILL.md definition */
+ /** File path to the SKILL.md definition, or an empty string for an SDK-provided skill without a filesystem identity */
@JsonProperty("path") String path,
/** Full content of the skill file, injected into the conversation for the model */
@JsonProperty("content") String content,
/** Tool names that should be auto-approved when this skill is active */
@JsonProperty("allowedTools") List allowedTools,
- /** Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill) */
+ /** Whether model invocation is disabled for this skill */
+ @JsonProperty("disableModelInvocation") Boolean disableModelInvocation,
+ /** Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), remote (org/enterprise skill), and sdk (SDK-provided skill) */
@JsonProperty("source") String source,
/** Name of the plugin this skill originated from, when applicable */
@JsonProperty("pluginName") String pluginName,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SkillSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/SkillSource.java
index b681faaae8..622dc5d88a 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SkillSource.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SkillSource.java
@@ -10,7 +10,7 @@
import javax.annotation.processing.Generated;
/**
- * Source location type (e.g., project, personal-copilot, plugin, builtin)
+ * Source location type (e.g., project, personal-copilot, plugin, builtin, sdk)
*
* @since 1.0.0
*/
@@ -29,7 +29,9 @@ public enum SkillSource {
/** The {@code custom} variant. */
CUSTOM("custom"),
/** The {@code builtin} variant. */
- BUILTIN("builtin");
+ BUILTIN("builtin"),
+ /** The {@code sdk} variant. */
+ SDK("sdk");
private final String value;
SkillSource(String value) { this.value = value; }
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java b/java/sdk/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java
index 932d9affe5..2335a9d9ea 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java
@@ -27,7 +27,7 @@ public record SkillsLoadedSkill(
@JsonProperty("commandName") String commandName,
/** Description of what the skill does */
@JsonProperty("description") String description,
- /** Source location type (e.g., project, personal-copilot, plugin, builtin) */
+ /** Source location type (e.g., project, personal-copilot, plugin, builtin, remote, sdk) */
@JsonProperty("source") SkillSource source,
/** Whether the skill can be invoked by the user as a slash command */
@JsonProperty("userInvocable") Boolean userInvocable,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java
index 62f6803652..26459bb955 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java
@@ -50,6 +50,8 @@ public record SubagentCompletedEventData(
@JsonProperty("explicitModelOverride") String explicitModelOverride,
/** Whether the explicit task-call model matched the user's configured preference */
@JsonProperty("explicitModelMatchesPreference") Boolean explicitModelMatchesPreference,
+ /** Why an explicit task-call model did not become the effective model */
+ @JsonProperty("modelOverrideReason") String modelOverrideReason,
/** Whether the first model actually dispatched matched the user's configured preference */
@JsonProperty("configuredModelMatchesActual") Boolean configuredModelMatchesActual,
/** Total number of tool calls made by the sub-agent */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java
index 1d1413c64d..54464f8ddf 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java
@@ -52,6 +52,8 @@ public record SubagentFailedEventData(
@JsonProperty("explicitModelOverride") String explicitModelOverride,
/** Whether the explicit task-call model matched the user's configured preference */
@JsonProperty("explicitModelMatchesPreference") Boolean explicitModelMatchesPreference,
+ /** Why an explicit task-call model did not become the effective model */
+ @JsonProperty("modelOverrideReason") String modelOverrideReason,
/** Whether the first model actually dispatched matched the user's configured preference */
@JsonProperty("configuredModelMatchesActual") Boolean configuredModelMatchesActual,
/** Total number of tool calls made before the sub-agent failed */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteError.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteError.java
index ac4c7d843d..8e183b78d1 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteError.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteError.java
@@ -24,6 +24,8 @@ public record ToolExecutionCompleteError(
/** Human-readable error message */
@JsonProperty("message") String message,
/** Machine-readable error code */
- @JsonProperty("code") String code
+ @JsonProperty("code") String code,
+ /** What the user must do to recover, when the runtime knows of an action. Set on sandbox policy denials, where `message` names the rule that blocked the call but never the client affordance that relaxes it. */
+ @JsonProperty("remediation") RemediationAction remediation
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java
index 7d839087da..43a886ea01 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java
@@ -37,6 +37,8 @@ public final class UserMessageEvent extends SessionEvent {
public record UserMessageEventData(
/** The user's message text as displayed in the timeline */
@JsonProperty("content") String content,
+ /** Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots */
+ @JsonProperty("messageId") String messageId,
/** Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching */
@JsonProperty("transformedContent") String transformedContent,
/** Files, selections, or GitHub references attached to the message */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java
index f239c82e61..3d9f9c2d7e 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java
@@ -15,7 +15,7 @@
import javax.annotation.processing.Generated;
/**
- * Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path.
+ * Agent metadata, including identifiers, display details, source, tools, model, models, MCP servers, skills, and file path.
*
* @since 1.0.0
*/
@@ -41,6 +41,10 @@ public record AgentInfo(
@JsonProperty("tools") List tools,
/** Authored preferred model id for this agent. Runtime model selection may choose a different model; omitted means no authored preference. */
@JsonProperty("model") String model,
+ /** Authored preferred model ids for this agent, in priority order. Runtime model selection chooses the first available model; omitted means no authored preference. */
+ @JsonProperty("models") List models,
+ /** Whether authored models are preferences or required constraints. */
+ @JsonProperty("modelPolicy") AgentModelPolicy modelPolicy,
/** MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. */
@JsonProperty("mcpServers") Map mcpServers,
/** Skill names preloaded into this agent's context. Omitted means none. */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentModelPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentModelPolicy.java
new file mode 100644
index 0000000000..99158516f0
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentModelPolicy.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Whether configured models are advisory preferences or required constraints
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum AgentModelPolicy {
+ /** The {@code preferred} variant. */
+ PREFERRED("preferred"),
+ /** The {@code required} variant. */
+ REQUIRED("required");
+
+ private final String value;
+ AgentModelPolicy(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static AgentModelPolicy fromValue(String value) {
+ for (AgentModelPolicy v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown AgentModelPolicy value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveCreditLimit.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveCreditLimit.java
new file mode 100644
index 0000000000..d3f6a585b8
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveCreditLimit.java
@@ -0,0 +1,31 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Current per-window credit limit and consumption for an autopilot objective.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record AutopilotObjectiveCreditLimit(
+ /** Configured AI-credit cap, when one is set. */
+ @JsonProperty("credits") Double credits,
+ /** Window consumption in fractional AI credits, for display. */
+ @JsonProperty("creditsUsed") Double creditsUsed,
+ /** Exact window consumption in non-negative integer nano-AIU, encoded as a decimal string. */
+ @JsonProperty("creditsUsedNanoAiu") String creditsUsedNanoAiu
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveState.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveState.java
new file mode 100644
index 0000000000..657884c4f6
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveState.java
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Public, persistence-independent projection of an autopilot objective.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record AutopilotObjectiveState(
+ /** Session-local objective identifier. */
+ @JsonProperty("id") Long id,
+ /** User-provided objective text. */
+ @JsonProperty("objective") String objective,
+ /** Current normalized lifecycle status. */
+ @JsonProperty("status") AutopilotObjectiveStatus status,
+ /** Number of objective turns started. */
+ @JsonProperty("turnCount") Long turnCount,
+ /** Optional reason the objective is paused. */
+ @JsonProperty("pauseReason") String pauseReason,
+ /** Optional summary recorded when the objective completed. */
+ @JsonProperty("completionSummary") String completionSummary,
+ /** Exact lifetime AI-credit consumption in non-negative integer nano-AIU, encoded as a decimal string. */
+ @JsonProperty("creditCountNanoAiu") String creditCountNanoAiu,
+ /** Current per-window consumption and optional cap, when a credit-tracking window is present. */
+ @JsonProperty("creditLimit") AutopilotObjectiveCreditLimit creditLimit
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveStatus.java
new file mode 100644
index 0000000000..b50cf2072c
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutopilotObjectiveStatus.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Current normalized autopilot objective lifecycle status.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum AutopilotObjectiveStatus {
+ /** The {@code active} variant. */
+ ACTIVE("active"),
+ /** The {@code paused} variant. */
+ PAUSED("paused"),
+ /** The {@code completed} variant. */
+ COMPLETED("completed");
+
+ private final String value;
+ AutopilotObjectiveStatus(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static AutopilotObjectiveStatus fromValue(String value) {
+ for (AutopilotObjectiveStatus v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown AutopilotObjectiveStatus value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java
index e77117b2cb..4fd7a91ca2 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java
@@ -21,7 +21,7 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public record CapiSessionOptions(
- /** Routing preference used when the session model is `auto`. The runtime persists the preference across cold resume. When omitted, the default routing behavior is used. Resuming an already-resident session cannot change its preference. */
+ /** Routing preference for sessions whose model is `auto`. On create or cold resume, this establishes the preference sent as `tier` on CAPI `/auto` requests; when omitted on cold resume, the runtime restores the last committed preference. On resident resume, a different value requests a safe switch after resume succeeds and cannot change an in-flight turn. Successful switches are persisted for later cold resume. When no preference is supplied or restored, CAPI default routing is used. */
@JsonProperty("autoTier") AutoTier autoTier,
/** Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses transport in environments where WebSockets are blocked (e.g. behind a proxy). Setting this to `false` is equivalent to the `COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES` environment variable. */
@JsonProperty("enableWebSocketResponses") Boolean enableWebSocketResponses
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidate.java
new file mode 100644
index 0000000000..59e70f4935
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidate.java
@@ -0,0 +1,93 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * An inert AI skill catalog result. AI skills are discovery-only and cannot be represented as installable through this surface.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class CatalogAiSkillCandidate extends CatalogCandidate {
+
+ @JsonProperty("kind")
+ private final String kind = "ai-skill";
+
+ @Override
+ public String getKind() { return kind; }
+
+ /** Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries no readable information and is rejected when stale, replayed, or presented to a different runtime instance. Never logged. */
+ @JsonProperty("handle")
+ private String handle;
+
+ /** ISO 8601 timestamp after which the handle is stale and will be rejected. */
+ @JsonProperty("handleExpiresAt")
+ private String handleExpiresAt;
+
+ /** Media type of the underlying AI skill card */
+ @JsonProperty("mediaType")
+ private String mediaType;
+
+ /** AI skills are discovery-only and cannot be installed through this surface */
+ @JsonProperty("installability")
+ private String installability;
+
+ /** Display name taken verbatim from the card. Inert untrusted text. */
+ @JsonProperty("displayName")
+ private String displayName;
+
+ /** Description taken verbatim from the card. Inert untrusted text. */
+ @JsonProperty("description")
+ private String description;
+
+ /** Publisher taken verbatim from the card. Inert untrusted text. */
+ @JsonProperty("publisher")
+ private String publisher;
+
+ /** Where the card came from: exactly one of a URL or embedded data, encoded as a tagged union so neither both nor neither can be represented. */
+ @JsonProperty("source")
+ private CatalogCandidateSource source;
+
+ /** Where the catalog reference was observed, without the card itself or any content digest. */
+ @JsonProperty("provenance")
+ private CatalogAiSkillCandidateProvenance provenance;
+
+ public String getHandle() { return handle; }
+ public void setHandle(String handle) { this.handle = handle; }
+
+ public String getHandleExpiresAt() { return handleExpiresAt; }
+ public void setHandleExpiresAt(String handleExpiresAt) { this.handleExpiresAt = handleExpiresAt; }
+
+ public String getMediaType() { return mediaType; }
+ public void setMediaType(String mediaType) { this.mediaType = mediaType; }
+
+ public String getInstallability() { return installability; }
+ public void setInstallability(String installability) { this.installability = installability; }
+
+ public String getDisplayName() { return displayName; }
+ public void setDisplayName(String displayName) { this.displayName = displayName; }
+
+ public String getDescription() { return description; }
+ public void setDescription(String description) { this.description = description; }
+
+ public String getPublisher() { return publisher; }
+ public void setPublisher(String publisher) { this.publisher = publisher; }
+
+ public CatalogCandidateSource getSource() { return source; }
+ public void setSource(CatalogCandidateSource source) { this.source = source; }
+
+ public CatalogAiSkillCandidateProvenance getProvenance() { return provenance; }
+ public void setProvenance(CatalogAiSkillCandidateProvenance provenance) { this.provenance = provenance; }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidateProvenance.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidateProvenance.java
new file mode 100644
index 0000000000..0a2eff24e8
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidateProvenance.java
@@ -0,0 +1,31 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Where and when an AI skill catalog reference was observed. Discovery provenance deliberately carries no content digest because search does not establish the exact validated content a later plan will bind.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record CatalogAiSkillCandidateProvenance(
+ /** Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. */
+ @JsonProperty("authority") String authority,
+ /** ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. */
+ @JsonProperty("observedAt") String observedAt,
+ /** Media type advertised for the referenced AI skill card */
+ @JsonProperty("mediaType") String mediaType
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidate.java
new file mode 100644
index 0000000000..a712236ddb
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidate.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import javax.annotation.processing.Generated;
+
+/**
+ * One inert catalog result, represented as an MCP server or discovery-only AI skill variant so kind, media type, provenance, and installability cannot contradict each other.
+ *
+ * @since 1.0.0
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "kind", visible = true)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = CatalogMcpServerCandidate.class, name = "mcp-server"),
+ @JsonSubTypes.Type(value = CatalogAiSkillCandidate.class, name = "ai-skill")
+})
+@JsonIgnoreProperties(ignoreUnknown = true)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public abstract class CatalogCandidate {
+
+ /**
+ * Returns the discriminator value for this variant.
+ *
+ * @return the kind discriminator
+ */
+ public abstract String getKind();
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSource.java
new file mode 100644
index 0000000000..798768739d
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSource.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import javax.annotation.processing.Generated;
+
+/**
+ * Where a candidate's card came from. Exactly one of a URL or embedded data: the union has no variant carrying both, and no variant carrying neither, so the rule holds structurally rather than by validation.
+ *
+ * @since 1.0.0
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "kind", visible = true)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = CatalogCandidateSourceUrl.class, name = "url"),
+ @JsonSubTypes.Type(value = CatalogCandidateSourceEmbedded.class, name = "embedded")
+})
+@JsonIgnoreProperties(ignoreUnknown = true)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public abstract class CatalogCandidateSource {
+
+ /**
+ * Returns the discriminator value for this variant.
+ *
+ * @return the kind discriminator
+ */
+ public abstract String getKind();
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSourceEmbedded.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSourceEmbedded.java
new file mode 100644
index 0000000000..6814a0c4f2
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSourceEmbedded.java
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Candidate whose card reference arrived inline. The document and its content-derived properties stay behind the runtime boundary.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class CatalogCandidateSourceEmbedded extends CatalogCandidateSource {
+
+ @JsonProperty("kind")
+ private final String kind = "embedded";
+
+ @Override
+ public String getKind() { return kind; }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSourceUrl.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSourceUrl.java
new file mode 100644
index 0000000000..1c9b6d8905
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateSourceUrl.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Candidate whose card is retrieved from a URL through the runtime's hardened fetch boundary.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class CatalogCandidateSourceUrl extends CatalogCandidateSource {
+
+ @JsonProperty("kind")
+ private final String kind = "url";
+
+ @Override
+ public String getKind() { return kind; }
+
+ /** Card URL as advertised. Inert untrusted data: the runtime retrieves it only through its own hardened boundary, and it is never logged. */
+ @JsonProperty("url")
+ private String url;
+
+ public String getUrl() { return url; }
+ public void setUrl(String url) { this.url = url; }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerCandidate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerCandidate.java
new file mode 100644
index 0000000000..8183ca422a
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerCandidate.java
@@ -0,0 +1,93 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * An inert MCP server catalog result. Every free-text field is untrusted external data and must never be treated as an instruction, and the handle is the only way to refer to the candidate in a later operation.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class CatalogMcpServerCandidate extends CatalogCandidate {
+
+ @JsonProperty("kind")
+ private final String kind = "mcp-server";
+
+ @Override
+ public String getKind() { return kind; }
+
+ /** Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries no readable information and is rejected when stale, replayed, or presented to a different runtime instance. Never logged. */
+ @JsonProperty("handle")
+ private String handle;
+
+ /** ISO 8601 timestamp after which the handle is stale and will be rejected. */
+ @JsonProperty("handleExpiresAt")
+ private String handleExpiresAt;
+
+ /** JSON MCP media type of the underlying card. */
+ @JsonProperty("mediaType")
+ private McpServerCardMediaType mediaType;
+
+ /** Whether this MCP server can be planned for installation, and if policy prevents it. */
+ @JsonProperty("installability")
+ private CatalogMcpServerInstallability installability;
+
+ /** Display name taken verbatim from the card. Inert untrusted text. */
+ @JsonProperty("displayName")
+ private String displayName;
+
+ /** Description taken verbatim from the card. Inert untrusted text. */
+ @JsonProperty("description")
+ private String description;
+
+ /** Publisher taken verbatim from the card. Inert untrusted text. */
+ @JsonProperty("publisher")
+ private String publisher;
+
+ /** Where the card came from: exactly one of a URL or embedded data, encoded as a tagged union so neither both nor neither can be represented. */
+ @JsonProperty("source")
+ private CatalogCandidateSource source;
+
+ /** Where the catalog reference was observed, without the card itself or any content digest. */
+ @JsonProperty("provenance")
+ private CatalogMcpServerCandidateProvenance provenance;
+
+ public String getHandle() { return handle; }
+ public void setHandle(String handle) { this.handle = handle; }
+
+ public String getHandleExpiresAt() { return handleExpiresAt; }
+ public void setHandleExpiresAt(String handleExpiresAt) { this.handleExpiresAt = handleExpiresAt; }
+
+ public McpServerCardMediaType getMediaType() { return mediaType; }
+ public void setMediaType(McpServerCardMediaType mediaType) { this.mediaType = mediaType; }
+
+ public CatalogMcpServerInstallability getInstallability() { return installability; }
+ public void setInstallability(CatalogMcpServerInstallability installability) { this.installability = installability; }
+
+ public String getDisplayName() { return displayName; }
+ public void setDisplayName(String displayName) { this.displayName = displayName; }
+
+ public String getDescription() { return description; }
+ public void setDescription(String description) { this.description = description; }
+
+ public String getPublisher() { return publisher; }
+ public void setPublisher(String publisher) { this.publisher = publisher; }
+
+ public CatalogCandidateSource getSource() { return source; }
+ public void setSource(CatalogCandidateSource source) { this.source = source; }
+
+ public CatalogMcpServerCandidateProvenance getProvenance() { return provenance; }
+ public void setProvenance(CatalogMcpServerCandidateProvenance provenance) { this.provenance = provenance; }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerCandidateProvenance.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerCandidateProvenance.java
new file mode 100644
index 0000000000..3ef13704ff
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerCandidateProvenance.java
@@ -0,0 +1,31 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Where and when an MCP server catalog reference was observed. Discovery provenance deliberately carries no content digest because search does not establish the exact validated content a later plan will bind.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record CatalogMcpServerCandidateProvenance(
+ /** Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. */
+ @JsonProperty("authority") String authority,
+ /** ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. */
+ @JsonProperty("observedAt") String observedAt,
+ /** JSON MCP media type advertised for the referenced card. */
+ @JsonProperty("mediaType") McpServerCardMediaType mediaType
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerInstallability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerInstallability.java
new file mode 100644
index 0000000000..4478a81bd3
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMcpServerInstallability.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Whether an MCP server candidate can be planned for installation
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum CatalogMcpServerInstallability {
+ /** The {@code installable} variant. */
+ INSTALLABLE("installable"),
+ /** The {@code not-installable-policy} variant. */
+ NOT_INSTALLABLE_POLICY("not-installable-policy");
+
+ private final String value;
+ CatalogMcpServerInstallability(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static CatalogMcpServerInstallability fromValue(String value) {
+ for (CatalogMcpServerInstallability v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown CatalogMcpServerInstallability value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureError.java
index d4afb32c3d..01d92e908a 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureError.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureError.java
@@ -36,6 +36,10 @@ public final class CatalogNetworkFailureError extends CatalogSearchResult {
@JsonProperty("statusCode")
private Long statusCode;
+ /** Bounded cooldown in seconds before another catalog request should be attempted, when the authority supplied a numeric Retry-After value or the runtime applied its documented fallback. */
+ @JsonProperty("retryAfterSeconds")
+ private Long retryAfterSeconds;
+
/** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */
@JsonProperty("message")
private String message;
@@ -46,6 +50,9 @@ public final class CatalogNetworkFailureError extends CatalogSearchResult {
public Long getStatusCode() { return statusCode; }
public void setStatusCode(Long statusCode) { this.statusCode = statusCode; }
+ public Long getRetryAfterSeconds() { return retryAfterSeconds; }
+ public void setRetryAfterSeconds(Long retryAfterSeconds) { this.retryAfterSeconds = retryAfterSeconds; }
+
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureReason.java
index c28d1cc4a7..ac821b062d 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureReason.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureReason.java
@@ -26,6 +26,12 @@ public enum CatalogNetworkFailureReason {
TLS("tls"),
/** The {@code connection-refused} variant. */
CONNECTION_REFUSED("connection-refused"),
+ /** The {@code proxy-authentication-required} variant. */
+ PROXY_AUTHENTICATION_REQUIRED("proxy-authentication-required"),
+ /** The {@code rate-limited} variant. */
+ RATE_LIMITED("rate-limited"),
+ /** The {@code service-unavailable} variant. */
+ SERVICE_UNAVAILABLE("service-unavailable"),
/** The {@code http-status} variant. */
HTTP_STATUS("http-status"),
/** The {@code response-too-large} variant. */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchParams.java
index ee5e703621..68b90a19c9 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchParams.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchParams.java
@@ -27,7 +27,7 @@
public record CatalogSearchParams(
/** Protocol version and capabilities the caller requires. */
@JsonProperty("contract") CatalogClientContract contract,
- /** Free-text search query. Never written to logs or telemetry. */
+ /** Free-text search query. Persisted as tool input for session continuity, but omitted from telemetry. */
@JsonProperty("query") String query,
/** Maximum number of candidates to return. Defaults to 10 when omitted. */
@JsonProperty("limit") Long limit,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchSucceeded.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchSucceeded.java
index 8ecd11788a..b49f78faff 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchSucceeded.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchSucceeded.java
@@ -35,7 +35,7 @@ public final class CatalogSearchSucceeded extends CatalogSearchResult {
/** Matching candidates, never more than the requested limit. All text is inert untrusted data. */
@JsonProperty("candidates")
- private List candidates;
+ private List candidates;
/** Whether further matches existed beyond the requested limit. */
@JsonProperty("truncated")
@@ -48,8 +48,8 @@ public final class CatalogSearchSucceeded extends CatalogSearchResult {
public String getSearchId() { return searchId; }
public void setSearchId(String searchId) { this.searchId = searchId; }
- public List getCandidates() { return candidates; }
- public void setCandidates(List candidates) { this.candidates = candidates; }
+ public List getCandidates() { return candidates; }
+ public void setCandidates(List candidates) { this.candidates = candidates; }
public Boolean getTruncated() { return truncated; }
public void setTruncated(Boolean truncated) { this.truncated = truncated; }
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ClientTaskCancelReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ClientTaskCancelReason.java
new file mode 100644
index 0000000000..723e7e80b0
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ClientTaskCancelReason.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Why the runtime requests client-task cancellation.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum ClientTaskCancelReason {
+ /** The {@code cancel_requested} variant. */
+ CANCEL_REQUESTED("cancel_requested"),
+ /** The {@code session_shutdown} variant. */
+ SESSION_SHUTDOWN("session_shutdown");
+
+ private final String value;
+ ClientTaskCancelReason(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static ClientTaskCancelReason fromValue(String value) {
+ for (ClientTaskCancelReason v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown ClientTaskCancelReason value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java
index 05f2534970..0975b7bd87 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java
@@ -11,6 +11,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.copilot.CopilotExperimental;
+import java.util.List;
import javax.annotation.processing.Generated;
/**
@@ -28,6 +29,8 @@ public record ConnectParams(
@JsonProperty("enableGitHubTelemetryForwarding") Boolean enableGitHubTelemetryForwarding,
/** Identity of the integrating host. Optional; omit it to keep the default attribution. */
@JsonProperty("clientInfo") ConnectClientInfo clientInfo,
+ /** Task kinds this connection can decode when observing session tasks. Omit to retain agent and shell compatibility. */
+ @JsonProperty("supportedTaskKinds") List supportedTaskKinds,
/** Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN */
@JsonProperty("token") String token
) {
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectResult.java
index 8c12b57a80..41a200ff4b 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectResult.java
@@ -11,6 +11,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.copilot.CopilotExperimental;
+import java.util.List;
import javax.annotation.processing.Generated;
/**
@@ -29,6 +30,8 @@ public record ConnectResult(
/** Server protocol version number */
@JsonProperty("protocolVersion") Long protocolVersion,
/** Server package version */
- @JsonProperty("version") String version
+ @JsonProperty("version") String version,
+ /** Task kinds the server may return to this connection. */
+ @JsonProperty("taskKinds") List taskKinds
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CurrentModel.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CurrentModel.java
new file mode 100644
index 0000000000..bf5f9a8b97
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CurrentModel.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * The session's authoritative model snapshot. Auto preference fields are configuration for the virtual `auto` model and do not change the selected model identifier. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record CurrentModel(
+ /** Currently active model identifier */
+ @JsonProperty("modelId") String modelId,
+ /** Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. */
+ @JsonProperty("reasoningEffort") String reasoningEffort,
+ /** Context tier for models that support multiple context-window sizes. */
+ @JsonProperty("contextTier") ContextTier contextTier,
+ /** Auto preference currently committed for the session. This can remain available while another model is selected so a later switch to `auto` can reuse it. */
+ @JsonProperty("autoTier") AutoTier autoTier,
+ /** Latest unclaimed Auto preference waiting for a future user turn. Null means the pending request is returning to provider-default routing. */
+ @JsonProperty("pendingAutoTier") AutoTier pendingAutoTier,
+ /** Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing. */
+ @JsonProperty("activatingAutoTier") AutoTier activatingAutoTier
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredHook.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredHook.java
new file mode 100644
index 0000000000..fce707243d
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredHook.java
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * One server-discovered hook action from user, repository, plugin, or managed-policy configuration.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record DiscoveredHook(
+ /** Deterministic identifier for this server-discovered action row. It remains stable while the project, origin, source, event, action content, and duplicate ordinal are unchanged. This is row identity, not the key persisted in disabledHooks. */
+ @JsonProperty("id") String id,
+ /** Hook event that invokes this action. */
+ @JsonProperty("hookType") HookType hookType,
+ /** Configuration tier that contributed this hook action. */
+ @JsonProperty("origin") HookOrigin origin,
+ /** Human-readable source label, such as a hook file path, settings source, or plugin name. */
+ @JsonProperty("source") String source,
+ /** Input project path for which this server-side action was resolved. Set on every row returned for project-scoped discovery, including repeated user and policy actions. */
+ @JsonProperty("projectPath") String projectPath,
+ /** Whether this action is enabled under the server-side discovery settings. Concrete sessions may differ because they can add session-specific directories, plugins, or trust. False when its disable key is present in the user's disabled-hooks setting or disable-all settings suppress the action. */
+ @JsonProperty("enabled") Boolean enabled,
+ /** Durable content hash used by hook enablement. Identical actions may intentionally share this key. Omitted when changing the user's disabled-hooks setting cannot change the action's current server-discovered state, including managed-policy hooks, session-start prompt actions, actions suppressed by disable-all settings, and projectless plugin actions that require project-directory expansion. */
+ @JsonProperty("disableKey") String disableKey
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java
index bb28f40887..71ee3c49e6 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java
@@ -23,13 +23,15 @@
public record FactoryRunResult(
/** Factory run identifier. */
@JsonProperty("runId") String runId,
+ /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */
+ @JsonProperty("attempt") Long attempt,
/** Current or terminal factory run status. */
@JsonProperty("status") FactoryRunStatus status,
/** Completed factory result. */
@JsonProperty("result") Object result,
/** Error message for an errored run. */
@JsonProperty("error") String error,
- /** Machine-readable failure details for an errored run. */
+ /** Machine-readable failure details for a halted or errored run. */
@JsonProperty("failure") Object failure,
/** Reason for a halted or cancelled run. */
@JsonProperty("reason") String reason,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookOrigin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookOrigin.java
new file mode 100644
index 0000000000..bf3bfd14cd
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookOrigin.java
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Configuration tier that contributed a discovered hook action.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum HookOrigin {
+ /** The {@code user} variant. */
+ USER("user"),
+ /** The {@code repository} variant. */
+ REPOSITORY("repository"),
+ /** The {@code plugin} variant. */
+ PLUGIN("plugin"),
+ /** The {@code policy} variant. */
+ POLICY("policy");
+
+ private final String value;
+ HookOrigin(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static HookOrigin fromValue(String value) {
+ for (HookOrigin v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown HookOrigin value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookType.java
index 8d7cd913c3..1a185958de 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookType.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookType.java
@@ -10,7 +10,7 @@
import javax.annotation.processing.Generated;
/**
- * Hook event name dispatched through the SDK callback transport.
+ * Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally support callback-only events.
*
* @since 1.0.0
*/
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverParams.java
new file mode 100644
index 0000000000..a0e7610394
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverParams.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+/**
+ * Optional project paths and host-exclusion behavior for server-scoped hook discovery.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record HooksDiscoverParams(
+ /** Optional project directory paths whose trusted repository and project-expanded plugin hooks should be discovered. When omitted or empty, user, managed-policy, and globally enabled installed or explicit plugin hooks are returned without project expansion. */
+ @JsonProperty("projectPaths") List projectPaths,
+ /** When true, omit host-owned user and plugin hook rows and their diagnostics. Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings. */
+ @JsonProperty("excludeHostHooks") Boolean excludeHostHooks
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverResult.java
new file mode 100644
index 0000000000..f30d4837ae
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverResult.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+/**
+ * Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record HooksDiscoverResult(
+ /** All discovered hook actions. Byte-identical actions remain separate rows even when they share a disable key. */
+ @JsonProperty("hooks") List hooks,
+ /** Non-fatal source-loading warnings. Discovery remains complete for the affected source, although the source had a recoverable issue. Repository-settings warnings are prefixed with their project path when attribution is available. */
+ @JsonProperty("warnings") List warnings,
+ /** Errors for hook sources or actions that could not be loaded, making the result partially incomplete. Other valid actions are still returned. Project-resolution and repository-settings errors are prefixed with their project path. */
+ @JsonProperty("errors") List errors
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigRemoveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigRemoveParams.java
index 81a0aa0e21..74258dcfc5 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigRemoveParams.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigRemoveParams.java
@@ -25,6 +25,8 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public record McpConfigRemoveParams(
/** Name of the MCP server to remove */
- @JsonProperty("name") String name
+ @JsonProperty("name") String name,
+ /** OAuth Client ID Metadata Document URL whose persisted credentials should also be removed. */
+ @JsonProperty("authClientIdMetadataUrl") String authClientIdMetadataUrl
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServer.java
index 14a9118d0b..063385f6ff 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServer.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServer.java
@@ -32,6 +32,8 @@ public record McpServer(
/** Plugin version that provided this server, when source is plugin. */
@JsonProperty("sourcePluginVersion") String sourcePluginVersion,
/** Error message if the server failed to connect */
- @JsonProperty("error") String error
+ @JsonProperty("error") String error,
+ /** Server-advertised metadata for a connected server. Omitted when no live connection metadata is available, including while pending or when failed, disabled, stopped, or not configured. */
+ @JsonProperty("serverMetadata") McpServerMetadata serverMetadata
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerMetadata.java
new file mode 100644
index 0000000000..15ba7395d6
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerMetadata.java
@@ -0,0 +1,27 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Server-advertised metadata learned through modern discovery or legacy initialization.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record McpServerMetadata(
+ /** Non-empty natural-language guidance for using the server, or null when the server omitted instructions or advertised an empty string. */
+ @JsonProperty("instructions") String instructions
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java
index a652e8f4f6..3e9f0b6b87 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java
@@ -11,6 +11,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
+import java.util.Map;
import javax.annotation.processing.Generated;
/**
@@ -28,6 +29,8 @@ public record Model(
@JsonProperty("name") String name,
/** Model capabilities and limits */
@JsonProperty("capabilities") ModelCapabilities capabilities,
+ /** Provider-supplied model metadata. Keys and JSON-compatible values are preserved unchanged. This is factual metadata published by the model provider; it carries no picker or UX semantics. */
+ @JsonProperty("metadata") Map metadata,
/** Policy state (if applicable) */
@JsonProperty("policy") ModelPolicy policy,
/** Billing information */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java
index 087ca1c15a..d44087dcff 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java
@@ -28,6 +28,8 @@ public record ModelBillingPromo(
/** UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion omits this field. When present, the API only surfaces a promo whose expiry parses and is in the future, so consumers should treat a past value as expired. */
@JsonProperty("endsAt") String endsAt,
/** Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it when present. */
- @JsonProperty("message") String message
+ @JsonProperty("message") String message,
+ /** Whether the service asked hosts to give this promotion a prominent surface, such as a dedicated banner, in addition to listing it with the model. `true` requests that surface and `false` asks for the model list only. Absent means the service expressed no preference — for example a response that predates the field — so hosts should apply their own default rather than read it as `false`. */
+ @JsonProperty("showBanner") Boolean showBanner
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelSwitchAutoTierStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelSwitchAutoTierStatus.java
new file mode 100644
index 0000000000..e4a1baed5e
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelSwitchAutoTierStatus.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Whether the requested preference was already effective or was accepted for later transactional activation.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum ModelSwitchAutoTierStatus {
+ /** The {@code unchanged} variant. */
+ UNCHANGED("unchanged"),
+ /** The {@code pending} variant. */
+ PENDING("pending");
+
+ private final String value;
+ ModelSwitchAutoTierStatus(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static ModelSwitchAutoTierStatus fromValue(String value) {
+ for (ModelSwitchAutoTierStatus v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown ModelSwitchAutoTierStatus value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginInstallStagingMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginInstallStagingMode.java
new file mode 100644
index 0000000000..e754f07bc9
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginInstallStagingMode.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Where completed plugin content was staged before atomic promotion.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum PluginInstallStagingMode {
+ /** The {@code external} variant. */
+ EXTERNAL("external"),
+ /** The {@code destination_sibling} variant. */
+ DESTINATION_SIBLING("destination_sibling");
+
+ private final String value;
+ PluginInstallStagingMode(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static PluginInstallStagingMode fromValue(String value) {
+ for (PluginInstallStagingMode v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown PluginInstallStagingMode value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallResult.java
index 82152cfe4c..a1e04692c6 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallResult.java
@@ -28,6 +28,8 @@ public record PluginsInstallResult(
@JsonProperty("plugin") InstalledPluginInfo plugin,
/** Number of skills discovered and installed from the plugin */
@JsonProperty("skillsInstalled") Long skillsInstalled,
+ /** Where the completed plugin tree was staged before atomic promotion */
+ @JsonProperty("stagingMode") PluginInstallStagingMode stagingMode,
/** Optional post-install message provided by the plugin (e.g. setup instructions) */
@JsonProperty("postInstallMessage") String postInstallMessage,
/** Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users. */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java
index f3b2f99188..f815f565f5 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java
@@ -23,6 +23,8 @@
public record QueuePendingItems(
/** Stable opaque id for the canonical queued item. Batch rows share one id. */
@JsonProperty("id") String id,
+ /** Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes. */
+ @JsonProperty("messageId") String messageId,
/** Whether this item is a queued user message or a queued slash command / model change */
@JsonProperty("kind") QueuePendingItemsKind kind,
/** Human-readable text to display for this queue entry in the UI */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemediationAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemediationAction.java
new file mode 100644
index 0000000000..50f6256352
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemediationAction.java
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * What the user must do to recover from a failure, named as an action rather than as one client's affordance. The runtime cannot know which affordance a client offers — a slash command, a settings pane, a link — so the accompanying message stays host-agnostic and each client renders its own copy from this value. Absent when the runtime knows of no action the user can take.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum RemediationAction {
+ /** The {@code sign_in} variant. */
+ SIGN_IN("sign_in"),
+ /** The {@code switch_account} variant. */
+ SWITCH_ACCOUNT("switch_account"),
+ /** The {@code show_account} variant. */
+ SHOW_ACCOUNT("show_account"),
+ /** The {@code review_sandbox_policy} variant. */
+ REVIEW_SANDBOX_POLICY("review_sandbox_policy"),
+ /** The {@code allow_sandbox_outbound} variant. */
+ ALLOW_SANDBOX_OUTBOUND("allow_sandbox_outbound");
+
+ private final String value;
+ RemediationAction(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static RemediationAction fromValue(String value) {
+ for (RemediationAction v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown RemediationAction value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java
index beda6b20a2..dd522f7a34 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java
@@ -27,6 +27,16 @@ public record SandboxConfig(
@JsonProperty("userPolicy") SandboxConfigUserPolicy userPolicy,
/** Whether to auto-add the current working directory to readwritePaths. Default: true. */
@JsonProperty("addCurrentWorkingDirectory") Boolean addCurrentWorkingDirectory,
+ /** Whether MCP servers the session launches are confined by the sandbox. Only an explicit `false` opts out; doing so also lets remote-MCP egress leave the sandbox, so the flag and `enabled` are always read together. Ignored while `enabled` is false. Default: true (enabled by default; set to false to opt out). */
+ @JsonProperty("sandboxMcpServers") Boolean sandboxMcpServers,
+ /** Whether language servers the session launches are confined by the sandbox. Only an explicit `false` opts out. Ignored while `enabled` is false. Default: true (enabled by default; set to false to opt out). */
+ @JsonProperty("sandboxLspServers") Boolean sandboxLspServers,
+ /** Whether the agent may request that an individual command run outside the sandbox, which the host then approves or denies through the usual permission flow. A host capability flag rather than part of the policy: it is stripped from the effective spawn policy and only has an effect while `enabled` is true. Fail-closed, unlike the opt-out flags on this object: omitting it offers no bypass. Default: false (opt-in). */
+ @JsonProperty("allowBypass") Boolean allowBypass,
+ /** Set by the runtime when a managed policy forced `sandboxMcpServers` on and took the local opt-out away. Provenance rather than policy: it lets a sandbox startup failure point at the administrator instead of a setting the next managed merge would override, and it is ignored when comparing two configs for change. Only the managed merge may set it; a caller-supplied value is stripped. */
+ @JsonProperty("managedMcpRoutingLocked") Boolean managedMcpRoutingLocked,
+ /** The `sandboxLspServers` counterpart of `managedMcpRoutingLocked`. */
+ @JsonProperty("managedLspRoutingLocked") Boolean managedLspRoutingLocked,
/** Credential-injection capability flags. */
@JsonProperty("auth") SandboxConfigAuth auth,
/** Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java
index 1e56acb53f..9c5e3e475f 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java
@@ -25,7 +25,7 @@ public record SandboxConfigUserPolicyNetwork(
@JsonProperty("allowOutbound") Boolean allowOutbound,
/** Whether traffic to local/loopback addresses is allowed. */
@JsonProperty("allowLocalNetwork") Boolean allowLocalNetwork,
- /** HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS. Credentials go in the separate `username`/`password` fields. A credential-free http:// loopback proxy URL is routed through the localhost proxy automatically; an https:// or authenticated loopback URL is used as-is. */
+ /** HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint, requires that endpoint to be reachable over IPv4 (the [::] dual-stack wildcard is accepted and routed through the IPv4 gateway), and does not support proxy credentials. macOS relies on applications honoring proxy environment variables. Windows also configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's networking stack. Configure supported credentials in the separate `username` and `password` fields. A credential-free http:// loopback URL uses the localhost proxy form, while an https:// or authenticated loopback URL uses the URL form. */
@JsonProperty("proxy") SandboxConfigUserPolicyNetworkProxy proxy
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java
index 74ff86919e..2946269ec1 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java
@@ -21,7 +21,7 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public record SandboxConfigUserPolicyNetworkProxy(
- /** Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted. Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in the separate `username`/`password` fields. A credential-free http:// loopback URL is routed through the localhost proxy automatically; loopback covers localhost and any *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is. */
+ /** Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted; an explicit port must be between 1 and 65535. Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in the separate `username`/`password` fields. A credential-free http:// loopback proxy URL is routed through the localhost proxy automatically; loopback covers localhost and any *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is. */
@JsonProperty("url") String url,
/** Optional username for proxy authentication. Combined with the URL (and `password`) into `user:pass@host` when the sandboxed process routes through the proxy. */
@JsonProperty("username") String username,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerHooksApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerHooksApi.java
new file mode 100644
index 0000000000..b6f69bf7dc
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerHooksApi.java
@@ -0,0 +1,40 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.github.copilot.CopilotExperimental;
+import java.util.concurrent.CompletableFuture;
+import javax.annotation.processing.Generated;
+
+/**
+ * API methods for the {@code hooks} namespace.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class ServerHooksApi {
+
+ private final RpcCaller caller;
+
+ /** @param caller the RPC transport function */
+ ServerHooksApi(RpcCaller caller) {
+ this.caller = caller;
+ }
+
+ /**
+ * Optional project paths and host-exclusion behavior for server-scoped hook discovery.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+ @CopilotExperimental
+ public CompletableFuture discover(HooksDiscoverParams params) {
+ return caller.invoke("hooks.discover", params, HooksDiscoverResult.class);
+ }
+
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java
index e85b7b987a..e6013c870d 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java
@@ -37,4 +37,15 @@ public CompletableFuture read() {
return caller.invoke("managedSettings.read", java.util.Map.of(), ManagedSettingsReadResult.class);
}
+ /**
+ * Invokes {@code managedSettings.clearCache}.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+ @CopilotExperimental
+ public CompletableFuture clearCache() {
+ return caller.invoke("managedSettings.clearCache", java.util.Map.of(), Void.class);
+ }
+
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java
index 111cee2560..caa7f92d07 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java
@@ -25,6 +25,8 @@ public final class ServerRpc {
private final RpcCaller caller;
+ /** API methods for the {@code hooks} namespace. */
+ public final ServerHooksApi hooks;
/** API methods for the {@code models} namespace. */
public final ServerModelsApi models;
/** API methods for the {@code tools} namespace. */
@@ -71,6 +73,7 @@ public final class ServerRpc {
*/
public ServerRpc(RpcCaller caller) {
this.caller = caller;
+ this.hooks = new ServerHooksApi(caller);
this.models = new ServerModelsApi(caller);
this.tools = new ServerToolsApi(caller);
this.account = new ServerAccountApi(caller);
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java
index 52481a7d73..870f7ac3cf 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java
@@ -94,6 +94,17 @@ public CompletableFuture getMetadata(SessionsGetMetad
return caller.invoke("sessions.getMetadata", params, SessionsGetMetadataResult.class);
}
+ /**
+ * Pagination options for reading an inactive or active local session's persisted event journal.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+ @CopilotExperimental
+ public CompletableFuture readPersistedEvents(SessionsReadPersistedEventsParams params) {
+ return caller.invoke("sessions.readPersistedEvents", params, SessionsReadPersistedEventsResult.class);
+ }
+
/**
* Limit for non-empty local session IDs.
*
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveApi.java
new file mode 100644
index 0000000000..ab4058e6b6
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveApi.java
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.github.copilot.CopilotExperimental;
+import java.util.concurrent.CompletableFuture;
+import javax.annotation.processing.Generated;
+
+/**
+ * API methods for the {@code autopilotObjective} namespace.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public final class SessionAutopilotObjectiveApi {
+
+ private final RpcCaller caller;
+ private final String sessionId;
+
+ /** @param caller the RPC transport function */
+ SessionAutopilotObjectiveApi(RpcCaller caller, String sessionId) {
+ this.caller = caller;
+ this.sessionId = sessionId;
+ }
+
+ /**
+ * Identifies the target session.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+ @CopilotExperimental
+ public CompletableFuture getState() {
+ return caller.invoke("session.autopilotObjective.getState", java.util.Map.of("sessionId", this.sessionId), SessionAutopilotObjectiveGetStateResult.class);
+ }
+
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveGetStateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveGetStateParams.java
new file mode 100644
index 0000000000..94ca56d3b5
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveGetStateParams.java
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Identifies the target session.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionAutopilotObjectiveGetStateParams(
+ /** Target session identifier */
+ @JsonProperty("sessionId") String sessionId
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveGetStateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveGetStateResult.java
new file mode 100644
index 0000000000..d4d2185365
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAutopilotObjectiveGetStateResult.java
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Canonical runtime state for the session's current autopilot objective.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionAutopilotObjectiveGetStateResult(
+ /** Current objective state, or `null` when the session has no objective. */
+ @JsonProperty("state") AutopilotObjectiveState state
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelResult.java
index 0cb66280c0..c9f9de2dcc 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelResult.java
@@ -26,13 +26,15 @@
public record SessionFactoryCancelResult(
/** Factory run identifier. */
@JsonProperty("runId") String runId,
+ /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */
+ @JsonProperty("attempt") Long attempt,
/** Current or terminal factory run status. */
@JsonProperty("status") FactoryRunStatus status,
/** Completed factory result. */
@JsonProperty("result") Object result,
/** Error message for an errored run. */
@JsonProperty("error") String error,
- /** Machine-readable failure details for an errored run. */
+ /** Machine-readable failure details for a halted or errored run. */
@JsonProperty("failure") Object failure,
/** Reason for a halted or cancelled run. */
@JsonProperty("reason") String reason,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunResult.java
index 6742faf03e..2d6a5f52a9 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunResult.java
@@ -26,13 +26,15 @@
public record SessionFactoryGetRunResult(
/** Factory run identifier. */
@JsonProperty("runId") String runId,
+ /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */
+ @JsonProperty("attempt") Long attempt,
/** Current or terminal factory run status. */
@JsonProperty("status") FactoryRunStatus status,
/** Completed factory result. */
@JsonProperty("result") Object result,
/** Error message for an errored run. */
@JsonProperty("error") String error,
- /** Machine-readable failure details for an errored run. */
+ /** Machine-readable failure details for a halted or errored run. */
@JsonProperty("failure") Object failure,
/** Reason for a halted or cancelled run. */
@JsonProperty("reason") String reason,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java
index 71b12f94f1..1a9dee5926 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java
@@ -26,13 +26,15 @@
public record SessionFactoryRunFromToolResult(
/** Factory run identifier. */
@JsonProperty("runId") String runId,
+ /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */
+ @JsonProperty("attempt") Long attempt,
/** Current or terminal factory run status. */
@JsonProperty("status") FactoryRunStatus status,
/** Completed factory result. */
@JsonProperty("result") Object result,
/** Error message for an errored run. */
@JsonProperty("error") String error,
- /** Machine-readable failure details for an errored run. */
+ /** Machine-readable failure details for a halted or errored run. */
@JsonProperty("failure") Object failure,
/** Reason for a halted or cancelled run. */
@JsonProperty("reason") String reason,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunResult.java
index 46083f2284..d8ce481895 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunResult.java
@@ -26,13 +26,15 @@
public record SessionFactoryRunResult(
/** Factory run identifier. */
@JsonProperty("runId") String runId,
+ /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */
+ @JsonProperty("attempt") Long attempt,
/** Current or terminal factory run status. */
@JsonProperty("status") FactoryRunStatus status,
/** Completed factory result. */
@JsonProperty("result") Object result,
/** Error message for an errored run. */
@JsonProperty("error") String error,
- /** Machine-readable failure details for an errored run. */
+ /** Machine-readable failure details for a halted or errored run. */
@JsonProperty("failure") Object failure,
/** Reason for a halted or cancelled run. */
@JsonProperty("reason") String reason,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java
index 12777c9a1f..b9adc34484 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java
@@ -57,6 +57,22 @@ public CompletableFuture switchTo(SessionModelSwitch
return caller.invoke("session.model.switchTo", _p, SessionModelSwitchToResult.class);
}
+ /**
+ * An Auto preference request for the session. This updates Auto configuration only; it does not change the selected model to `auto`.
+ *
+ * Note: the {@code sessionId} field in the params record is overridden
+ * by the session-scoped wrapper; any value provided is ignored.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+ @CopilotExperimental
+ public CompletableFuture switchAutoTier(SessionModelSwitchAutoTierParams params) {
+ com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params);
+ _p.put("sessionId", this.sessionId);
+ return caller.invoke("session.model.switchAutoTier", _p, SessionModelSwitchAutoTierResult.class);
+ }
+
/**
* Managed, repository, and CLI model overrides to overlay onto the session at startup.
*
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayParams.java
index a1d25ae7ec..dfb593fd21 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayParams.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayParams.java
@@ -30,6 +30,8 @@ public record SessionModelApplyStartupOverlayParams(
@JsonProperty("deviceManagedModel") String deviceManagedModel,
/** Model required by server-managed policy, when configured. */
@JsonProperty("serverManagedModel") String serverManagedModel,
+ /** Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and an explicit user selection still wins. */
+ @JsonProperty("policyHelperModel") String policyHelperModel,
/** Model selected by repository settings, when configured. */
@JsonProperty("repoModel") String repoModel,
/** Reasoning effort selected by repository settings, when configured. */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayResult.java
index f29e249c32..53ce443e08 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayResult.java
@@ -40,6 +40,8 @@ public record SessionModelApplyStartupOverlayResult(
/** User-facing warning produced while applying the model switch. */
@JsonProperty("warning") String warning,
/** Deprecation warnings associated with the selected model or options. */
- @JsonProperty("deprecationWarnings") List deprecationWarnings
+ @JsonProperty("deprecationWarnings") List deprecationWarnings,
+ /** Authoritative model and Auto preference state after an immediate switch. For deferred switches this remains the current state until the queued change drains. */
+ @JsonProperty("modelState") CurrentModel modelState
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentResult.java
index 21afab2fa4..23a9788540 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentResult.java
@@ -14,7 +14,7 @@
import javax.annotation.processing.Generated;
/**
- * The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
+ * The session's authoritative model snapshot. Auto preference fields are configuration for the virtual `auto` model and do not change the selected model identifier. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume.
*
* @apiNote This method is experimental and may change in a future version.
* @since 1.0.0
@@ -29,6 +29,12 @@ public record SessionModelGetCurrentResult(
/** Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. */
@JsonProperty("reasoningEffort") String reasoningEffort,
/** Context tier for models that support multiple context-window sizes. */
- @JsonProperty("contextTier") ContextTier contextTier
+ @JsonProperty("contextTier") ContextTier contextTier,
+ /** Auto preference currently committed for the session. This can remain available while another model is selected so a later switch to `auto` can reuse it. */
+ @JsonProperty("autoTier") AutoTier autoTier,
+ /** Latest unclaimed Auto preference waiting for a future user turn. Null means the pending request is returning to provider-default routing. */
+ @JsonProperty("pendingAutoTier") AutoTier pendingAutoTier,
+ /** Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing. */
+ @JsonProperty("activatingAutoTier") AutoTier activatingAutoTier
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierParams.java
new file mode 100644
index 0000000000..576df55aa1
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierParams.java
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * An Auto preference request for the session. This updates Auto configuration only; it does not change the selected model to `auto`.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionModelSwitchAutoTierParams(
+ /** Target session identifier */
+ @JsonProperty("sessionId") String sessionId,
+ /** Auto preference to activate when a future user turn using the `auto` model safely mints a replacement model and token pair. Pass null to return to provider-default Auto routing. */
+ @JsonProperty("autoTier") AutoTier autoTier,
+ /** Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted. */
+ @JsonProperty("source") ModelChangeSource source
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierResult.java
new file mode 100644
index 0000000000..7e95695492
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchAutoTierResult.java
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Immediate acknowledgement and Auto preference snapshot after a switch request. This result never implies that a pending preference committed.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionModelSwitchAutoTierResult(
+ /** Immediate request status. `pending` means accepted but not committed. */
+ @JsonProperty("status") ModelSwitchAutoTierStatus status,
+ /** Auto preference currently committed for the session. */
+ @JsonProperty("effectiveAutoTier") AutoTier effectiveAutoTier,
+ /** Latest unclaimed Auto preference waiting for a future user turn. */
+ @JsonProperty("pendingAutoTier") AutoTier pendingAutoTier,
+ /** Auto preference currently claimed by an in-progress activation. Null means the activation is returning to provider-default routing. */
+ @JsonProperty("activatingAutoTier") AutoTier activatingAutoTier,
+ /** Earlier unclaimed preference replaced by this request. This can be present with either status, including when selecting the effective preference cancels pending work. */
+ @JsonProperty("supersededAutoTier") AutoTier supersededAutoTier
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java
index a7c60d28cd..cfd59bcf36 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java
@@ -28,6 +28,8 @@ public record SessionModelSwitchToParams(
@JsonProperty("sessionId") String sessionId,
/** Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. */
@JsonProperty("modelId") String modelId,
+ /** Optional Auto routing preference to stage atomically with selecting `auto`. Pass null to return to provider-default Auto routing. This field is rejected when `modelId` is not `auto`. */
+ @JsonProperty("autoTier") AutoTier autoTier,
/** Reasoning effort level to use for the model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. "none" disables reasoning. When omitted, no effort override is applied. */
@JsonProperty("reasoningEffort") String reasoningEffort,
/** Reasoning summary mode to request for supported model clients */
@@ -38,7 +40,7 @@ public record SessionModelSwitchToParams(
@JsonProperty("modelCapabilities") ModelCapabilitiesOverride modelCapabilities,
/** Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. */
@JsonProperty("contextTier") ContextTier contextTier,
- /** Origin to record on the effective `session.model_change` event. Defaults to `sdk` when omitted. */
+ /** Origin to record on the effective `session.model_change` event for trusted in-process calls. Transport SDK calls are always recorded as `sdk`, regardless of this value. */
@JsonProperty("source") ModelChangeSource source,
/** When true, defer this switch (enqueue it) if another model change is already queued, even when no turn is active — so it drains last (FIFO) and wins over the already-queued change. Intended for genuine user-initiated model selections; internal restore/reapply switches omit it and apply immediately when no turn is active. When no other model change is queued this has no effect (a switch still applies immediately unless a turn is active). */
@JsonProperty("deferIfModelChangeQueued") Boolean deferIfModelChangeQueued,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java
index fb143abc30..47099e42ea 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java
@@ -40,6 +40,8 @@ public record SessionModelSwitchToResult(
/** User-facing warning produced while applying the model switch. */
@JsonProperty("warning") String warning,
/** Deprecation warnings associated with the selected model or options. */
- @JsonProperty("deprecationWarnings") List deprecationWarnings
+ @JsonProperty("deprecationWarnings") List deprecationWarnings,
+ /** Authoritative model and Auto preference state after an immediate switch. For deferred switches this remains the current state until the queued change drains. */
+ @JsonProperty("modelState") CurrentModel modelState
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java
index 34706a68e1..a1c234903e 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java
@@ -37,6 +37,8 @@ public record SessionOpenOptions(
@JsonProperty("verbosity") Verbosity verbosity,
/** Identifier of the client driving the session. */
@JsonProperty("clientName") String clientName,
+ /** OAuth Client ID Metadata Document URL used by this host for MCP authorization. */
+ @JsonProperty("authClientIdMetadataUrl") String authClientIdMetadataUrl,
/** Structured client kind used for runtime behavior gates. */
@JsonProperty("clientKind") String clientKind,
/** Identifier sent to LSP-style integrations. */
@@ -111,6 +113,10 @@ public record SessionOpenOptions(
@JsonProperty("allowAllMcpServerInstructions") Boolean allowAllMcpServerInstructions,
/** Additional directories to search for skills. */
@JsonProperty("skillDirectories") List skillDirectories,
+ /** Whether skill loading is enabled. When omitted, an SDK skill provider enables skills by default. */
+ @JsonProperty("enableSkills") Boolean enableSkills,
+ /** Whether the requesting SDK session has a skill provider. The provider remains ephemeral and is never persisted in session options or history. When enableSkills is false, it remains bound but dormant and receives no callbacks. Cloud, relay, handoff, and raw sessions.open flows reject it because they cannot safely pre-register the callback handler. */
+ @JsonProperty("hasSkillProvider") Boolean hasSkillProvider,
/** Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. */
@JsonProperty("includedBuiltinSkills") List includedBuiltinSkills,
/** Skill IDs disabled for this session. */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java
index 2e8a069a4b..27a5c2db06 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java
@@ -140,7 +140,7 @@ public record SessionOptionsUpdateParams(
@JsonProperty("enableHostGitOperations") Boolean enableHostGitOperations,
/** Whether to enable cross-session store writes and reads. */
@JsonProperty("enableSessionStore") Boolean enableSessionStore,
- /** Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. */
+ /** Whether skill loading is enabled. Explicit false disables every source, including a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless an SDK skill provider is registered. */
@JsonProperty("enableSkills") Boolean enableSkills,
/** Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier. */
@JsonProperty("contextTier") OptionsUpdateContextTier contextTier,
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java
index b9bd6b8226..cf89d37208 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java
@@ -49,6 +49,8 @@ public final class SessionRpc {
public final SessionPlanApi plan;
/** API methods for the {@code workspaces} namespace. */
public final SessionWorkspacesApi workspaces;
+ /** API methods for the {@code autopilotObjective} namespace. */
+ public final SessionAutopilotObjectiveApi autopilotObjective;
/** API methods for the {@code completions} namespace. */
public final SessionCompletionsApi completions;
/** API methods for the {@code instructions} namespace. */
@@ -127,6 +129,7 @@ public SessionRpc(RpcCaller caller, String sessionId) {
this.name = new SessionNameApi(caller, sessionId);
this.plan = new SessionPlanApi(caller, sessionId);
this.workspaces = new SessionWorkspacesApi(caller, sessionId);
+ this.autopilotObjective = new SessionAutopilotObjectiveApi(caller, sessionId);
this.completions = new SessionCompletionsApi(caller, sessionId);
this.instructions = new SessionInstructionsApi(caller, sessionId);
this.fleet = new SessionFleetApi(caller, sessionId);
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksApi.java
index 68f038eb4b..c6ca1335de 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksApi.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksApi.java
@@ -57,6 +57,38 @@ public CompletableFuture list() {
return caller.invoke("session.tasks.list", java.util.Map.of("sessionId", this.sessionId), SessionTasksListResult.class);
}
+ /**
+ * Registers or reclaims a client-owned task.
+ *
+ * Note: the {@code sessionId} field in the params record is overridden
+ * by the session-scoped wrapper; any value provided is ignored.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+ @CopilotExperimental
+ public CompletableFuture register(SessionTasksRegisterParams params) {
+ com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params);
+ _p.put("sessionId", this.sessionId);
+ return caller.invoke("session.tasks.register", _p, SessionTasksRegisterResult.class);
+ }
+
+ /**
+ * Updates a client-owned task.
+ *
+ * Note: the {@code sessionId} field in the params record is overridden
+ * by the session-scoped wrapper; any value provided is ignored.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+ @CopilotExperimental
+ public CompletableFuture update(SessionTasksUpdateParams params) {
+ com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params);
+ _p.put("sessionId", this.sessionId);
+ return caller.invoke("session.tasks.update", _p, SessionTasksUpdateResult.class);
+ }
+
/**
* Identifies the target session.
*
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRegisterParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRegisterParams.java
new file mode 100644
index 0000000000..0b14af08fd
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRegisterParams.java
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Registers or reclaims a client-owned task.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionTasksRegisterParams(
+ /** Target session identifier */
+ @JsonProperty("sessionId") String sessionId,
+ /** Task kind */
+ @JsonProperty("type") TaskClientType type,
+ /** Owner-scoped idempotency key used for registration and reclaim */
+ @JsonProperty("clientTaskId") String clientTaskId,
+ /** Human-readable description of the external work */
+ @JsonProperty("description") String description,
+ /** Optional short display name for the external work */
+ @JsonProperty("displayName") String displayName,
+ /** Whether the owner supports runtime cancellation requests */
+ @JsonProperty("cancellable") Boolean cancellable,
+ /** Expected current sequence for idempotent registration or orphan reclaim */
+ @JsonProperty("expectedSequence") Long expectedSequence
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRegisterResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRegisterResult.java
new file mode 100644
index 0000000000..f7e25fe885
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRegisterResult.java
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Result of registering or reclaiming a client-owned task.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionTasksRegisterResult(
+ /** Authoritative registered or reclaimed task */
+ @JsonProperty("task") TaskClientInfo task,
+ /** True only when this invocation created a new task */
+ @JsonProperty("created") Boolean created,
+ /** True only when this invocation reclaimed an orphaned task */
+ @JsonProperty("reclaimed") Boolean reclaimed
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksUpdateParams.java
new file mode 100644
index 0000000000..ecd06a1b7e
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksUpdateParams.java
@@ -0,0 +1,36 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Updates a client-owned task.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionTasksUpdateParams(
+ /** Target session identifier */
+ @JsonProperty("sessionId") String sessionId,
+ /** Canonical runtime-generated task identifier */
+ @JsonProperty("id") String id,
+ /** Owner update sequence to apply */
+ @JsonProperty("sequence") Long sequence,
+ /** Progress or terminal update payload */
+ @JsonProperty("update") Object update
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksUpdateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksUpdateResult.java
new file mode 100644
index 0000000000..8a08e87859
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksUpdateResult.java
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Result of publishing a client-owned task update.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionTasksUpdateResult(
+ /** Authoritative task after processing the update */
+ @JsonProperty("task") TaskClientInfo task,
+ /** Whether this invocation changed task state */
+ @JsonProperty("applied") Boolean applied,
+ /** Whether this invocation repeated the latest accepted update */
+ @JsonProperty("duplicate") Boolean duplicate
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsParams.java
new file mode 100644
index 0000000000..4da93409be
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsParams.java
@@ -0,0 +1,36 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Pagination options for reading an inactive or active local session's persisted event journal.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionsReadPersistedEventsParams(
+ /** Session ID whose persisted event journal should be read. */
+ @JsonProperty("sessionId") String sessionId,
+ /** Opaque cursor returned by a previous persisted-event read. Omit on the first call. */
+ @JsonProperty("cursor") String cursor,
+ /** Maximum number of events to return in this batch (1–1000, default 200). */
+ @JsonProperty("max") Long max,
+ /** Direction to page through persisted history. Forward starts at the beginning; backward starts with the newest events. Events in each page remain chronological. */
+ @JsonProperty("direction") EventsReadDirection direction
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsResult.java
new file mode 100644
index 0000000000..f022df5ae2
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsResult.java
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import com.github.copilot.generated.SessionEvent;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+/**
+ * Batch of session events returned by a read, with cursor and continuation metadata.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SessionsReadPersistedEventsResult(
+ /** Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order. */
+ @JsonProperty("events") List events,
+ /** Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly). */
+ @JsonProperty("cursor") String cursor,
+ /** True when more events are available in the read's direction. For a forward read, true means the batch returned `max` events and more are available immediately. For a backward read, true means older persisted events remain before the returned window. */
+ @JsonProperty("hasMore") Boolean hasMore,
+ /** Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history. For a forward read the fallback starts from the beginning of the remaining history; for a backward read it falls back to the tail (the newest window). Because the fallback page is a fresh boundary snapshot rather than a continuation of the requested cursor, it may overlap events the consumer has already rendered — a backward fallback to the tail in particular can repeat the newest window. On 'expired', consumers should reset or rebase their local pagination state (or deduplicate by event id) before continuing from the returned cursor rather than blindly appending/prepending the fallback page. */
+ @JsonProperty("cursorStatus") EventsCursorStatus cursorStatus
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderDescriptor.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderDescriptor.java
new file mode 100644
index 0000000000..a678b95fa3
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderDescriptor.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.processing.Generated;
+
+/**
+ * Catalog-only metadata for one SDK-provided skill. The complete SKILL.md is fetched separately and lazily.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SkillProviderDescriptor(
+ /** Invocation and display name. */
+ @JsonProperty("name") String name,
+ /** Description used in skill catalogs without fetching content. */
+ @JsonProperty("description") String description,
+ /** Whether users may invoke the skill directly. Defaults to true. */
+ @JsonProperty("userInvocable") Boolean userInvocable,
+ /** Whether model invocation is disabled. Defaults to false. */
+ @JsonProperty("disableModelInvocation") Boolean disableModelInvocation,
+ /** Optional freeform argument hint used by slash-command catalogs. */
+ @JsonProperty("argumentHint") String argumentHint
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListParams.java
new file mode 100644
index 0000000000..e11fcf0fbd
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListParams.java
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Identifies the target session.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SkillProviderListParams(
+ /** Target session identifier */
+ @JsonProperty("sessionId") String sessionId
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListResult.java
new file mode 100644
index 0000000000..228ec9240b
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListResult.java
@@ -0,0 +1,31 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+/**
+ * Catalog metadata returned by an SDK session's skill provider. Catalogs are limited to 1024 descriptors and 1 MiB of aggregate metadata.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SkillProviderListResult(
+ /** Skill descriptors in provider order. Invocation names must be unique under case-insensitive comparison. */
+ @JsonProperty("skills") List skills
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadParams.java
new file mode 100644
index 0000000000..895078384b
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadParams.java
@@ -0,0 +1,32 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Identifies one SDK-provided skill by invocation name.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SkillProviderReadParams(
+ /** Target session identifier */
+ @JsonProperty("sessionId") String sessionId,
+ /** Invocation name of the skill to read. */
+ @JsonProperty("name") String name
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadResult.java
new file mode 100644
index 0000000000..0f17696565
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadResult.java
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Complete text-only SKILL.md content returned by an SDK session's skill provider. Related files and assets are not supported.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record SkillProviderReadResult(
+ /** Complete SKILL.md text. The runtime enforces a 1 MiB UTF-8 byte limit. */
+ @JsonProperty("markdown") String markdown
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillSource.java
index 8d723548be..bef1995e6c 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillSource.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillSource.java
@@ -10,7 +10,7 @@
import javax.annotation.processing.Generated;
/**
- * Source location type (e.g., project, personal-copilot, plugin, builtin)
+ * Source location type (e.g., project, personal-copilot, plugin, builtin, sdk)
*
* @since 1.0.0
*/
@@ -29,7 +29,9 @@ public enum SkillSource {
/** The {@code custom} variant. */
CUSTOM("custom"),
/** The {@code builtin} variant. */
- BUILTIN("builtin");
+ BUILTIN("builtin"),
+ /** The {@code sdk} variant. */
+ SDK("sdk");
private final String value;
SkillSource(String value) { this.value = value; }
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java
index a020c89ecf..5e1f6fd90e 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java
@@ -24,12 +24,14 @@
public record SkillsInvokedSkill(
/** Unique identifier for the skill */
@JsonProperty("name") String name,
- /** Path to the SKILL.md file */
+ /** Path to the SKILL.md file, or an empty string for an SDK-provided skill without a filesystem identity */
@JsonProperty("path") String path,
/** Full content of the skill file */
@JsonProperty("content") String content,
/** Tools that should be auto-approved when this skill is active, captured at invocation time */
@JsonProperty("allowedTools") List allowedTools,
+ /** Whether model invocation was disabled when this skill was invoked */
+ @JsonProperty("disableModelInvocation") Boolean disableModelInvocation,
/** Turn number when the skill was invoked */
@JsonProperty("invokedAtTurn") Long invokedAtTurn
) {
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java
index b2a1970a36..c457181435 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java
@@ -32,6 +32,10 @@ public final class SlashCommandCompletedResult extends SlashCommandInvocationRes
@JsonProperty("message")
private String message;
+ /** Optional target session mode applied without submitting an agent prompt */
+ @JsonProperty("mode")
+ private SessionMode mode;
+
/** True when the invocation mutated user runtime settings; consumers caching settings should refresh */
@JsonProperty("runtimeSettingsChanged")
private Boolean runtimeSettingsChanged;
@@ -39,6 +43,9 @@ public final class SlashCommandCompletedResult extends SlashCommandInvocationRes
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
+ public SessionMode getMode() { return mode; }
+ public void setMode(SessionMode mode) { this.mode = mode; }
+
public Boolean getRuntimeSettingsChanged() { return runtimeSettingsChanged; }
public void setRuntimeSettingsChanged(Boolean runtimeSettingsChanged) { this.runtimeSettingsChanged = runtimeSettingsChanged; }
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTimelineEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTimelineEntry.java
index a238d7fb67..f8c832e05d 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTimelineEntry.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTimelineEntry.java
@@ -21,6 +21,8 @@ public record SlashCommandTimelineEntry(
/** Text displayed for the timeline entry. */
@JsonProperty("text") String text,
/** Optional URL associated with the timeline entry. */
- @JsonProperty("url") String url
+ @JsonProperty("url") String url,
+ /** What the user must do to recover, when the entry reports a failure the runtime knows an action for. The `text` never names a client affordance, so a client that offers one renders it from this value. */
+ @JsonProperty("remediation") RemediationAction remediation
) {
}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntry.java
index 29426d931b..4a366864c3 100644
--- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntry.java
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntry.java
@@ -23,6 +23,8 @@
public record SubagentSettingsEntry(
/** Model override for matching subagents */
@JsonProperty("model") String model,
+ /** Whether the configured model strategy is preferred or required */
+ @JsonProperty("modelPolicy") AgentModelPolicy modelPolicy,
/** Reasoning effort override for matching subagents */
@JsonProperty("effortLevel") String effortLevel,
/** Context tier override for matching subagents */
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientExecutionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientExecutionMode.java
new file mode 100644
index 0000000000..6c948f86e0
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientExecutionMode.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Client-owned tasks always execute outside the runtime in background mode.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum TaskClientExecutionMode {
+ /** The {@code background} variant. */
+ BACKGROUND("background");
+
+ private final String value;
+ TaskClientExecutionMode(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static TaskClientExecutionMode fromValue(String value) {
+ for (TaskClientExecutionMode v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown TaskClientExecutionMode value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientInfo.java
new file mode 100644
index 0000000000..6a221303eb
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientInfo.java
@@ -0,0 +1,70 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.time.OffsetDateTime;
+import javax.annotation.processing.Generated;
+
+/**
+ * Tracked client-owned task metadata.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record TaskClientInfo(
+ /** Task kind */
+ @JsonProperty("type") TaskClientType type,
+ /** Canonical runtime-generated task identifier */
+ @JsonProperty("id") String id,
+ /** Owner-scoped registration and reclaim key */
+ @JsonProperty("clientTaskId") String clientTaskId,
+ /** Optional task display name */
+ @JsonProperty("displayName") String displayName,
+ /** Task description */
+ @JsonProperty("description") String description,
+ /** Client task lifecycle status */
+ @JsonProperty("status") TaskClientStatus status,
+ /** Public attribution and presence for the task owner */
+ @JsonProperty("owner") TaskClientOwner owner,
+ /** ISO 8601 timestamp when the task started */
+ @JsonProperty("startedAt") OffsetDateTime startedAt,
+ /** ISO 8601 timestamp of the latest accepted lifecycle change */
+ @JsonProperty("updatedAt") OffsetDateTime updatedAt,
+ /** ISO 8601 timestamp when the task reached a terminal status */
+ @JsonProperty("completedAt") OffsetDateTime completedAt,
+ /** Accumulated active execution time in milliseconds */
+ @JsonProperty("activeTimeMs") Long activeTimeMs,
+ /** ISO 8601 timestamp when the current active segment started */
+ @JsonProperty("activeStartedAt") OffsetDateTime activeStartedAt,
+ /** ISO 8601 timestamp when the connected owner entered idle status */
+ @JsonProperty("idleSince") OffsetDateTime idleSince,
+ /** ISO 8601 timestamp of the most recent orphan transition */
+ @JsonProperty("orphanedAt") OffsetDateTime orphanedAt,
+ /** ISO 8601 timestamp of the most recent successful reclaim */
+ @JsonProperty("reclaimedAt") OffsetDateTime reclaimedAt,
+ /** Execution mode, which is always background for client-owned tasks */
+ @JsonProperty("executionMode") TaskClientExecutionMode executionMode,
+ /** Whether the currently bound owner can receive a cancellation request */
+ @JsonProperty("canCancel") Boolean canCancel,
+ /** Sequence number of the latest accepted owner update */
+ @JsonProperty("sequence") Long sequence,
+ /** Opaque successful terminal result supplied by the task owner */
+ @JsonProperty("result") Object result,
+ /** Human-readable terminal failure message */
+ @JsonProperty("error") String error,
+ /** Optional owner-supplied terminal failure code */
+ @JsonProperty("errorCode") String errorCode,
+ /** Human-readable reason for terminal cancellation */
+ @JsonProperty("cancellationReason") String cancellationReason
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwner.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwner.java
new file mode 100644
index 0000000000..e2ce54019b
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwner.java
@@ -0,0 +1,40 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.time.OffsetDateTime;
+import javax.annotation.processing.Generated;
+
+/**
+ * Public owner attribution for a client-owned task. Identifiers are opaque and never authorize requests.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record TaskClientOwner(
+ /** Opaque session-scoped participant identity */
+ @JsonProperty("participantId") String participantId,
+ /** Opaque identity of the currently or most recently bound session join */
+ @JsonProperty("joinId") String joinId,
+ /** Class of the task owner */
+ @JsonProperty("kind") TaskClientOwnerKind kind,
+ /** Display-only owner name */
+ @JsonProperty("displayName") String displayName,
+ /** Display-only owner source */
+ @JsonProperty("source") String source,
+ /** Whether this task's bound join is currently connected */
+ @JsonProperty("presence") TaskClientOwnerPresence presence,
+ /** ISO 8601 timestamp when the bound join disconnected */
+ @JsonProperty("disconnectedAt") OffsetDateTime disconnectedAt
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwnerKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwnerKind.java
new file mode 100644
index 0000000000..92518264a5
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwnerKind.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Connection class owning a client task.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum TaskClientOwnerKind {
+ /** The {@code extension} variant. */
+ EXTENSION("extension"),
+ /** The {@code sdk} variant. */
+ SDK("sdk");
+
+ private final String value;
+ TaskClientOwnerKind(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static TaskClientOwnerKind fromValue(String value) {
+ for (TaskClientOwnerKind v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown TaskClientOwnerKind value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwnerPresence.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwnerPresence.java
new file mode 100644
index 0000000000..282cc69206
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientOwnerPresence.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Presence of the task's bound join.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum TaskClientOwnerPresence {
+ /** The {@code connected} variant. */
+ CONNECTED("connected"),
+ /** The {@code disconnected} variant. */
+ DISCONNECTED("disconnected");
+
+ private final String value;
+ TaskClientOwnerPresence(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static TaskClientOwnerPresence fromValue(String value) {
+ for (TaskClientOwnerPresence v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown TaskClientOwnerPresence value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientStatus.java
new file mode 100644
index 0000000000..a72daddc41
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientStatus.java
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Lifecycle status of a client-owned task.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum TaskClientStatus {
+ /** The {@code running} variant. */
+ RUNNING("running"),
+ /** The {@code idle} variant. */
+ IDLE("idle"),
+ /** The {@code completed} variant. */
+ COMPLETED("completed"),
+ /** The {@code failed} variant. */
+ FAILED("failed"),
+ /** The {@code cancelled} variant. */
+ CANCELLED("cancelled"),
+ /** The {@code orphaned} variant. */
+ ORPHANED("orphaned");
+
+ private final String value;
+ TaskClientStatus(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static TaskClientStatus fromValue(String value) {
+ for (TaskClientStatus v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown TaskClientStatus value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientType.java
new file mode 100644
index 0000000000..42f74cf3dd
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskClientType.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Discriminator for a client-owned task.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum TaskClientType {
+ /** The {@code client} variant. */
+ CLIENT("client");
+
+ private final String value;
+ TaskClientType(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static TaskClientType fromValue(String value) {
+ for (TaskClientType v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown TaskClientType value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskKind.java
new file mode 100644
index 0000000000..413859db20
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskKind.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import javax.annotation.processing.Generated;
+
+/**
+ * Closed set of public task kinds a connection can negotiate.
+ *
+ * @since 1.0.0
+ */
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+public enum TaskKind {
+ /** The {@code agent} variant. */
+ AGENT("agent"),
+ /** The {@code shell} variant. */
+ SHELL("shell"),
+ /** The {@code client} variant. */
+ CLIENT("client");
+
+ private final String value;
+ TaskKind(String value) { this.value = value; }
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() { return value; }
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static TaskKind fromValue(String value) {
+ for (TaskKind v : values()) {
+ if (v.value.equals(value)) return v;
+ }
+ throw new IllegalArgumentException("Unknown TaskKind value: " + value);
+ }
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TasksCancelParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TasksCancelParams.java
new file mode 100644
index 0000000000..57cbd5e6e0
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TasksCancelParams.java
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Runtime-to-owner cancellation request for a client-owned task.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record TasksCancelParams(
+ /** Session that owns the client task */
+ @JsonProperty("sessionId") String sessionId,
+ /** Canonical runtime-generated task identifier */
+ @JsonProperty("id") String id,
+ /** Owner-scoped task key included for correlation */
+ @JsonProperty("clientTaskId") String clientTaskId,
+ /** Opaque identifier shared by coalesced cancellation callers */
+ @JsonProperty("cancellationId") String cancellationId,
+ /** Reason the runtime requests cancellation */
+ @JsonProperty("reason") ClientTaskCancelReason reason
+) {
+}
diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TasksCancelResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TasksCancelResult.java
new file mode 100644
index 0000000000..bd549d1df4
--- /dev/null
+++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TasksCancelResult.java
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+// AUTO-GENERATED FILE - DO NOT EDIT
+// Generated from: api.schema.json
+
+package com.github.copilot.generated.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.CopilotExperimental;
+import javax.annotation.processing.Generated;
+
+/**
+ * Whether the client authoritatively confirmed its external work stopped.
+ *
+ * @apiNote This method is experimental and may change in a future version.
+ * @since 1.0.0
+ */
+@CopilotExperimental
+@javax.annotation.processing.Generated("copilot-sdk-codegen")
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record TasksCancelResult(
+ /** True only when the owner confirms that external work stopped before responding */
+ @JsonProperty("cancelled") Boolean cancelled
+) {
+}
diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java
index 661fe6dab4..9f7d8ebcf8 100644
--- a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java
+++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java
@@ -221,7 +221,10 @@ public CopilotClient(CopilotClientOptions options) {
// Parse CliUrl if provided
if (this.options.getCliUrl() != null && !this.options.getCliUrl().isEmpty()) {
URI uri = CliServerManager.parseCliUrl(this.options.getCliUrl());
- this.optionsHost = uri.getHost();
+ String host = uri.getHost();
+ this.optionsHost = host != null && host.startsWith("[") && host.endsWith("]")
+ ? host.substring(1, host.length() - 1)
+ : host;
this.optionsPort = uri.getPort();
} else {
this.optionsHost = null;
@@ -550,6 +553,7 @@ private Connection startCoreBody() {
JsonRpcClient connectedRpc = rpc;
Connection connection = new Connection(connectedRpc, process, new ServerRpc(connectedRpc::invoke),
inProcessTransport == null ? null : inProcessTransport.host());
+ connectedRpc.setCloseHandler(() -> sessions.values().forEach(CopilotSession::cancelPendingExternalTools));
// Register handlers for server-to-client calls
RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch, executor,
@@ -635,6 +639,7 @@ private void verifyProtocolVersion(Connection connection) throws Exception {
if (effectiveConnectionToken != null) {
connectParams.put("token", effectiveConnectionToken);
}
+ connectParams.put("supportedTaskKinds", List.of("agent", "client", "shell"));
// Opt into GitHub telemetry forwarding at the connection level when a handler
// is registered, so the runtime can forward the first session's un-replayable
// start event. Also sent on session create/resume for backward compatibility
@@ -642,6 +647,13 @@ private void verifyProtocolVersion(Connection connection) throws Exception {
if (this.options.getOnGitHubTelemetry() != null) {
connectParams.put("enableGitHubTelemetryForwarding", true);
}
+ // Declare the integrating application's identity so the runtime attributes the
+ // telemetry it emits on this connection to a consistent surface instead of
+ // its own build. Omitted when the app didn't supply it (or supplied no fields).
+ var clientInfo = this.options.getClientInfo();
+ if (clientInfo != null && !clientInfo.isEmpty()) {
+ connectParams.put("clientInfo", clientInfo);
+ }
var connectResponse = connection.rpc.invoke("connect", connectParams, ConnectResult.class).get(30,
TimeUnit.SECONDS);
serverVersion = connectResponse.protocolVersion() != null
@@ -735,7 +747,9 @@ public CompletableFuture stop() {
*/
public CompletableFuture forceStop() {
disposed = true;
+ var activeSessions = new ArrayList<>(sessions.values());
sessions.clear();
+ activeSessions.forEach(CopilotSession::cancelPendingExternalTools);
gitHubTokenProviders.clear();
// Dispatch the blocking shutdownOwnedExecutor() on a dedicated thread:
// cleanupConnection() is chained off async work running on the owned
@@ -1007,6 +1021,7 @@ public CompletableFuture createSession(SessionConfig config) {
CopilotSession session = preRegisteredSessionHolder[0] != null
? preRegisteredSessionHolder[0]
: initializeSession.apply(returnedId);
+ preRegisteredSessionHolder[0] = session;
if (tokenRegistration != null) {
session.setGitHubTokenProviderRegistration(tokenRegistration);
}
@@ -1038,6 +1053,9 @@ public CompletableFuture createSession(SessionConfig config) {
return session;
});
}).exceptionally(ex -> {
+ if (preRegisteredSessionHolder[0] != null) {
+ preRegisteredSessionHolder[0].cancelPendingExternalTools();
+ }
if (registeredIdHolder[0] != null) {
sessions.remove(registeredIdHolder[0]);
}
@@ -1218,6 +1236,7 @@ public CompletableFuture resumeSession(String sessionId, ResumeS
return session;
});
}).exceptionally(ex -> {
+ session.cancelPendingExternalTools();
sessions.remove(sessionId);
// Also remove the re-keyed entry if the server returned a different ID
String activeId = session.getSessionId();
diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotSession.java b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java
index f3a35967d3..c072e31ec1 100644
--- a/java/sdk/src/main/java/com/github/copilot/CopilotSession.java
+++ b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java
@@ -20,6 +20,7 @@
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.logging.Level;
@@ -29,7 +30,9 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.copilot.generated.AssistantMessageEvent;
+import com.github.copilot.generated.ExternalToolCompletedEvent;
import com.github.copilot.generated.rpc.SessionCommandsHandlePendingCommandParams;
import com.github.copilot.generated.rpc.SessionLogParams;
import com.github.copilot.generated.rpc.SessionLogLevel;
@@ -37,6 +40,8 @@
import com.github.copilot.generated.rpc.ModelCapabilitiesOverride;
import com.github.copilot.generated.rpc.ModelCapabilitiesOverrideLimits;
import com.github.copilot.generated.rpc.ModelCapabilitiesOverrideSupports;
+import com.github.copilot.generated.rpc.SessionModelSwitchAutoTierParams;
+import com.github.copilot.generated.rpc.SessionModelSwitchAutoTierResult;
import com.github.copilot.generated.rpc.SessionModelSwitchToParams;
import com.github.copilot.generated.rpc.SessionPermissionsHandlePendingPermissionRequestParams;
import com.github.copilot.generated.rpc.SessionRpc;
@@ -184,6 +189,8 @@ public final class CopilotSession implements AutoCloseable {
private volatile SessionRpc sessionRpc;
private final Set> eventHandlers = ConcurrentHashMap.newKeySet();
private final Map toolHandlers = new ConcurrentHashMap<>();
+ private final Map pendingExternalTools = new ConcurrentHashMap<>();
+ private boolean externalToolsClosed;
private final Map commandHandlers = new ConcurrentHashMap<>();
private final Map bearerTokenProviders = new ConcurrentHashMap<>();
private final AtomicReference permissionHandler = new AtomicReference<>();
@@ -204,6 +211,46 @@ public final class CopilotSession implements AutoCloseable {
/** Tracks whether this session instance has been terminated via close(). */
private volatile boolean isTerminated = false;
+ private static final class PendingExternalTool {
+ private static final int WAITING = 0;
+ private static final int STARTED = 1;
+ private static final int CANCELLED = 2;
+
+ private final AtomicInteger state = new AtomicInteger(WAITING);
+ private final AtomicReference> future = new AtomicReference<>();
+
+ T join(CompletableFuture operation) {
+ future.set(operation);
+ if (state.get() == CANCELLED) {
+ operation.cancel(true);
+ }
+ try {
+ return operation.join();
+ } finally {
+ future.compareAndSet(operation, null);
+ }
+ }
+
+ boolean tryStart() {
+ return state.compareAndSet(WAITING, STARTED);
+ }
+
+ void attach(CompletableFuture toolFuture) {
+ future.set(toolFuture);
+ if (state.get() == CANCELLED) {
+ toolFuture.cancel(true);
+ }
+ }
+
+ void cancel() {
+ state.set(CANCELLED);
+ CompletableFuture> activeFuture = future.get();
+ if (activeFuture != null) {
+ activeFuture.cancel(true);
+ }
+ }
+ }
+
/**
* Creates a new session with the given ID and RPC client.
*
@@ -857,6 +904,14 @@ private void handleBroadcastEventAsync(SessionEvent event) {
}
executeToolAndRespondAsync(data.requestId(), data.toolName(), data.toolCallId(), data.arguments(), tool);
+ } else if (event instanceof ExternalToolCompletedEvent completedEvent) {
+ var data = completedEvent.getData();
+ if (data != null && data.requestId() != null) {
+ PendingExternalTool pending = pendingExternalTools.remove(data.requestId());
+ if (pending != null) {
+ pending.cancel();
+ }
+ }
} else if (event instanceof PermissionRequestedEvent permEvent) {
var data = permEvent.getData();
if (data == null || data.requestId() == null || data.permissionRequest() == null) {
@@ -928,9 +983,9 @@ private void handleBroadcastEventAsync(SessionEvent event) {
* built-in tool-search tool, so an override can filter the live catalog without
* issuing its own RPC. The snapshot is fetched only for that tool to avoid a
* round-trip on every ordinary tool call; a failed fetch leaves the snapshot
- * {@code null} rather than failing the tool. Shared by both server-to-client
- * tool dispatch paths ({@link RpcHandlerDispatcher} and
- * {@link #executeToolAndRespondAsync}).
+ * {@code null} rather than failing the tool. Used by the direct RPC dispatch
+ * path; event-dispatched tools perform the same lookup with request-scoped
+ * cancellation.
*
* @param toolName
* the name of the tool being invoked
@@ -955,6 +1010,12 @@ void populateToolSearchMetadata(String toolName, com.github.copilot.rpc.ToolInvo
*/
private void executeToolAndRespondAsync(String requestId, String toolName, String toolCallId, Object arguments,
ToolDefinition tool) {
+ var pending = new PendingExternalTool();
+ synchronized (this) {
+ if (isTerminated || externalToolsClosed || pendingExternalTools.putIfAbsent(requestId, pending) != null) {
+ return;
+ }
+ }
Runnable task = () -> {
try {
JsonNode argumentsNode = arguments instanceof JsonNode jn
@@ -963,9 +1024,32 @@ private void executeToolAndRespondAsync(String requestId, String toolName, Strin
var invocation = new com.github.copilot.rpc.ToolInvocation().setSessionId(sessionId)
.setToolCallId(toolCallId).setToolName(toolName).setArguments(argumentsNode);
- populateToolSearchMetadata(toolName, invocation);
+ if (TOOL_SEARCH_TOOL_NAME.equals(toolName)) {
+ try {
+ var metadata = pending.join(getRpc().tools.getCurrentMetadata());
+ if (metadata != null) {
+ invocation.setAvailableTools(metadata.tools());
+ }
+ } catch (RuntimeException e) {
+ if (pendingExternalTools.get(requestId) != pending) {
+ return;
+ }
+ LOG.log(Level.FINE, "Failed to fetch tool metadata for tool search", e);
+ }
+ if (pendingExternalTools.get(requestId) != pending) {
+ return;
+ }
+ }
- tool.handler().invoke(invocation).thenAccept(result -> {
+ if (!pending.tryStart()) {
+ return;
+ }
+ CompletableFuture toolFuture = tool.handler().invoke(invocation);
+ pending.attach(toolFuture);
+ toolFuture.thenAccept(result -> {
+ if (!pendingExternalTools.remove(requestId, pending)) {
+ return;
+ }
try {
ToolResultObject toolResult;
if (result instanceof ToolResultObject tr) {
@@ -980,6 +1064,9 @@ private void executeToolAndRespondAsync(String requestId, String toolName, Strin
LOG.log(Level.WARNING, "Error sending tool result for requestId=" + requestId, e);
}
}).exceptionally(ex -> {
+ if (!pendingExternalTools.remove(requestId, pending)) {
+ return null;
+ }
try {
getRpc().tools.handlePendingToolCall(new SessionToolsHandlePendingToolCallParams(sessionId,
requestId, null, ex.getMessage() != null ? ex.getMessage() : ex.toString()));
@@ -987,8 +1074,11 @@ private void executeToolAndRespondAsync(String requestId, String toolName, Strin
LOG.log(Level.WARNING, "Error sending tool error for requestId=" + requestId, e);
}
return null;
- });
+ }).whenComplete((result, error) -> pendingExternalTools.remove(requestId, pending));
} catch (Exception e) {
+ if (!pendingExternalTools.remove(requestId, pending)) {
+ return;
+ }
LOG.log(Level.WARNING, "Error executing tool for requestId=" + requestId, e);
try {
getRpc().tools.handlePendingToolCall(new SessionToolsHandlePendingToolCallParams(sessionId,
@@ -1010,6 +1100,16 @@ private void executeToolAndRespondAsync(String requestId, String toolName, Strin
}
}
+ void cancelPendingExternalTools() {
+ List pending;
+ synchronized (this) {
+ externalToolsClosed = true;
+ pending = new ArrayList<>(pendingExternalTools.values());
+ pendingExternalTools.clear();
+ }
+ pending.forEach(PendingExternalTool::cancel);
+ }
+
/**
* Builds a {@link SessionUiHandlePendingElicitationParams} carrying a
* {@code cancel} action, used when an elicitation handler throws or the handler
@@ -2010,8 +2110,8 @@ public CompletableFuture abort() {
*/
public CompletableFuture setModel(String model, String reasoningEffort) {
ensureNotTerminated();
- return getRpc().model.switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, null, null,
- null, null, null, null, null, null, null, null, null, null)).thenApply(r -> null);
+ return getRpc().model.switchTo(new SessionModelSwitchToParams(sessionId, model, null, reasoningEffort, null,
+ null, null, null, null, null, null, null, null, null, null, null)).thenApply(r -> null);
}
/**
@@ -2073,30 +2173,141 @@ public CompletableFuture setModel(String model, String reasoningEffort,
public CompletableFuture setModel(String model, String reasoningEffort, String reasoningSummary,
com.github.copilot.rpc.ModelCapabilitiesOverride modelCapabilities) {
ensureNotTerminated();
- ModelCapabilitiesOverride generatedCapabilities = null;
- if (modelCapabilities != null) {
- ModelCapabilitiesOverrideSupports supports = null;
- if (modelCapabilities.getSupports() != null) {
- var s = modelCapabilities.getSupports();
- supports = new ModelCapabilitiesOverrideSupports(s.getVision().orElse(null),
- s.getReasoningEffort().orElse(null), null);
- }
- ModelCapabilitiesOverrideLimits limits = null;
- if (modelCapabilities.getLimits() != null) {
- limits = new ObjectMapper().convertValue(modelCapabilities.getLimits(),
- ModelCapabilitiesOverrideLimits.class);
- }
- generatedCapabilities = new ModelCapabilitiesOverride(supports, limits);
- }
+ ModelCapabilitiesOverride generatedCapabilities = toGeneratedCapabilities(modelCapabilities);
var generatedReasoningSummary = reasoningSummary == null
? null
: com.github.copilot.generated.rpc.ReasoningSummary.fromValue(reasoningSummary);
- return getRpc().model
- .switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, generatedReasoningSummary,
- null, generatedCapabilities, null, null, null, null, null, null, null, null, null))
+ return getRpc().model.switchTo(
+ new SessionModelSwitchToParams(sessionId, model, null, reasoningEffort, generatedReasoningSummary, null,
+ generatedCapabilities, null, null, null, null, null, null, null, null, null))
.thenApply(r -> null);
}
+ private static ModelCapabilitiesOverride toGeneratedCapabilities(
+ com.github.copilot.rpc.ModelCapabilitiesOverride modelCapabilities) {
+ if (modelCapabilities == null) {
+ return null;
+ }
+ ModelCapabilitiesOverrideSupports supports = null;
+ if (modelCapabilities.getSupports() != null) {
+ var s = modelCapabilities.getSupports();
+ supports = new ModelCapabilitiesOverrideSupports(s.getVision().orElse(null),
+ s.getReasoningEffort().orElse(null), null);
+ }
+ ModelCapabilitiesOverrideLimits limits = null;
+ if (modelCapabilities.getLimits() != null) {
+ limits = MAPPER.convertValue(modelCapabilities.getLimits(), ModelCapabilitiesOverrideLimits.class);
+ }
+ return new ModelCapabilitiesOverride(supports, limits);
+ }
+
+ private static com.github.copilot.generated.rpc.AutoTier toGeneratedAutoTier(
+ com.github.copilot.rpc.AutoTier autoTier) {
+ return autoTier == null ? null : com.github.copilot.generated.rpc.AutoTier.fromValue(autoTier.getValue());
+ }
+
+ /**
+ * Changes the model for this session using an options object.
+ *
+ * The new model takes effect for the next message. Conversation history is
+ * preserved. Use {@link com.github.copilot.rpc.SetModelOptions#setAutoTier} to
+ * request an Auto routing preference at the same time, which the runtime
+ * accepts only when the model is {@code "auto"}.
+ *
+ *
+ *
+ * @param options
+ * the switch settings; the model ID is required
+ * @return a future that completes when the model switch is acknowledged
+ * @throws IllegalArgumentException
+ * if {@code options} is {@code null}, if it carries no model ID, or
+ * if it requests both an explicit Auto tier and a return to
+ * provider-default Auto routing
+ * @throws IllegalStateException
+ * if this session has been terminated
+ * @since 1.6.0
+ */
+ public CompletableFuture setModel(com.github.copilot.rpc.SetModelOptions options) {
+ ensureNotTerminated();
+ if (options == null) {
+ throw new IllegalArgumentException("options must not be null");
+ }
+ if (options.getModel() == null) {
+ throw new IllegalArgumentException("options must specify a model");
+ }
+ if (options.getAutoTier() != null && options.isResetAutoTier()) {
+ throw new IllegalArgumentException(
+ "setModel cannot combine an explicit autoTier with resetAutoTier; choose one");
+ }
+ var generatedReasoningSummary = options.getReasoningSummary() == null
+ ? null
+ : com.github.copilot.generated.rpc.ReasoningSummary.fromValue(options.getReasoningSummary());
+ var params = new SessionModelSwitchToParams(sessionId, options.getModel(),
+ toGeneratedAutoTier(options.getAutoTier()), options.getReasoningEffort(), generatedReasoningSummary,
+ null, toGeneratedCapabilities(options.getModelCapabilities()), null, null, null, null, null, null, null,
+ null, null);
+ if (!options.isResetAutoTier()) {
+ return getRpc().model.switchTo(params).thenApply(r -> null);
+ }
+ // The generated params record omits null properties, but returning to
+ // provider-default Auto routing requires sending an explicit null tier, so
+ // build the payload directly and reinstate the null.
+ ObjectNode payload = MAPPER.valueToTree(params);
+ payload.putNull("autoTier");
+ return rpc.invoke("session.model.switchTo", payload, Void.class);
+ }
+
+ /**
+ * Changes the Auto routing preference without changing the selected model.
+ *
+ * The runtime does not apply the preference immediately. It records the request
+ * and commits it only when a later user turn using the {@code auto} model
+ * successfully obtains a usable model from the provider. A {@code pending}
+ * status therefore confirms that the request was accepted, not that it took
+ * effect.
+ *
+ * Watch for the outcome through the {@code session.model_change} event on
+ * success, or the ephemeral {@code session.auto_tier_switch_failed} event on
+ * failure. You can also read the committed and in-flight state at any time with
+ * {@code session.getRpc().model.getCurrent()}.
+ *
+ * Only the most recent request survives: a new request replaces any earlier one
+ * that no turn has claimed yet.
+ *
+ *
{@code
+ * var result = session.setAutoTier(AutoTier.INTELLIGENCE).get();
+ * if (result.status() == ModelSwitchAutoTierStatus.PENDING) {
+ * // Takes effect on a later turn that uses the `auto` model.
+ * }
+ * }
+ *
+ * @param autoTier
+ * the routing preference to activate, or {@code null} to return to
+ * the provider's default Auto routing
+ * @return a future completing with the runtime's immediate acknowledgement and
+ * Auto preference snapshot
+ * @throws IllegalStateException
+ * if this session has been terminated
+ * @since 1.6.0
+ */
+ @CopilotExperimental
+ public CompletableFuture setAutoTier(com.github.copilot.rpc.AutoTier autoTier) {
+ ensureNotTerminated();
+ var params = new SessionModelSwitchAutoTierParams(sessionId, toGeneratedAutoTier(autoTier), null);
+ if (autoTier != null) {
+ return getRpc().model.switchAutoTier(params);
+ }
+ // The generated params record omits null properties, but the runtime
+ // distinguishes an explicit null tier (return to provider-default routing)
+ // from an absent one, so build the payload directly and reinstate the null.
+ ObjectNode payload = MAPPER.valueToTree(params);
+ payload.putNull("autoTier");
+ return rpc.invoke("session.model.switchAutoTier", payload, SessionModelSwitchAutoTierResult.class);
+ }
+
/**
* Changes the model for this session.
*
* Checks, in order, the flat bundled layout ({@code runtime.node} directly next
- * to the CLI) and the npm package layout
- * ({@code prebuilds//runtime.node} next to the CLI), matching the
- * two layouts the {@code @github/copilot-} packages may ship.
+ * to the CLI) and the release package layout
+ * ({@code prebuilds//runtime.node} next to the CLI).
*/
static Path resolveFromCliPath(String cliPathStr) throws IOException {
if (cliPathStr == null || cliPathStr.isBlank()) {
diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/AutoTier.java b/java/sdk/src/main/java/com/github/copilot/rpc/AutoTier.java
new file mode 100644
index 0000000000..f9117abfb2
--- /dev/null
+++ b/java/sdk/src/main/java/com/github/copilot/rpc/AutoTier.java
@@ -0,0 +1,63 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.rpc;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Routing tier for the {@code auto} model with Auto mode V2.
+ *
+ * @see CapiSessionOptions#setAutoTier(AutoTier)
+ */
+public enum AutoTier {
+
+ /** Prioritize efficiency. */
+ EFFICIENCY("efficiency"),
+
+ /** Balance efficiency and intelligence. */
+ BALANCE("balance"),
+
+ /** Prioritize intelligence. */
+ INTELLIGENCE("intelligence");
+
+ private final String value;
+
+ AutoTier(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the JSON value for this routing tier.
+ *
+ * @return the string value used in JSON serialization
+ */
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Deserializes a JSON string into its routing tier.
+ *
+ * @param value
+ * the JSON string value
+ * @return the matching tier, or {@code null} if value is {@code null}
+ * @throws IllegalArgumentException
+ * if the value does not match a known routing tier
+ */
+ @JsonCreator
+ public static AutoTier fromValue(String value) {
+ if (value == null) {
+ return null;
+ }
+ for (AutoTier tier : values()) {
+ if (tier.value.equals(value)) {
+ return tier;
+ }
+ }
+ throw new IllegalArgumentException("Unknown AutoTier value: " + value);
+ }
+}
diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java
index d94d59f67b..1743f572ed 100644
--- a/java/sdk/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java
+++ b/java/sdk/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java
@@ -29,9 +29,41 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CapiSessionOptions {
+ @JsonProperty("autoTier")
+ private AutoTier autoTier;
+
@JsonProperty("enableWebSocketResponses")
private Boolean enableWebSocketResponses;
+ /**
+ * Gets the routing tier for the {@code auto} model (Auto mode V2).
+ *
+ * @return the explicit tier, or {@code null} to leave tier selection to the
+ * runtime
+ */
+ public AutoTier getAutoTier() {
+ return autoTier;
+ }
+
+ /**
+ * Sets the routing tier, meaningful only with model {@code auto} (Auto mode
+ * V2). Requires a runtime version that supports {@code capi.autoTier}.
+ *
+ * When omitted, the runtime chooses its default on create and preserves the
+ * persisted or current tier on resume. An explicit tier overrides the persisted
+ * tier on cold resume. On resident resume, a different tier requests a safe
+ * switch that the runtime applies after the resume succeeds; it cannot change a
+ * turn that is already in flight.
+ *
+ * @param autoTier
+ * the routing tier, or {@code null} to omit it from the request
+ * @return this config for method chaining
+ */
+ public CapiSessionOptions setAutoTier(AutoTier autoTier) {
+ this.autoTier = autoTier;
+ return this;
+ }
+
/**
* Gets whether CAPI Responses API WebSocket transport is enabled.
*
diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java b/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java
new file mode 100644
index 0000000000..ee9d97e1e9
--- /dev/null
+++ b/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java
@@ -0,0 +1,151 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.rpc;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Identity of the integrating application, declared on the
+ * {@code server.connect} handshake.
+ *
+ * Declaring it lets the telemetry the runtime emits on the connection be
+ * attributed to a single, consistent surface (the application and its Copilot
+ * integration) instead of the runtime's own build. All fields are optional; an
+ * empty field is omitted from the handshake.
+ *
+ *
Example Usage
+ *
+ *
{@code
+ * var options = new CopilotClientOptions().setClientInfo(new ClientInfo().setApplicationName("acme-developer-portal")
+ * .setApplicationVersion("2.4.0").setIntegrationName("copilot-assistant").setIntegrationVersion("1.5.0"));
+ * }
+ *
+ * @see CopilotClientOptions#setClientInfo(ClientInfo)
+ * @since 1.6.0
+ */
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class ClientInfo {
+
+ private String applicationName;
+
+ private String applicationVersion;
+
+ private String integrationName;
+
+ private String integrationVersion;
+
+ /**
+ * Gets the name of the application using the SDK.
+ *
+ * @return the application name, or {@code null}
+ */
+ @JsonProperty("editorName")
+ public String getApplicationName() {
+ return applicationName;
+ }
+
+ /**
+ * Sets the name of the application using the SDK.
+ *
+ * @param applicationName
+ * the application name
+ * @return this client info for method chaining
+ */
+ @JsonProperty("editorName")
+ public ClientInfo setApplicationName(String applicationName) {
+ this.applicationName = applicationName;
+ return this;
+ }
+
+ /**
+ * Gets the version of the application using the SDK.
+ *
+ * @return the application version, or {@code null}
+ */
+ @JsonProperty("editorVersion")
+ public String getApplicationVersion() {
+ return applicationVersion;
+ }
+
+ /**
+ * Sets the version of the application using the SDK.
+ *
+ * @param applicationVersion
+ * the application version
+ * @return this client info for method chaining
+ */
+ @JsonProperty("editorVersion")
+ public ClientInfo setApplicationVersion(String applicationVersion) {
+ this.applicationVersion = applicationVersion;
+ return this;
+ }
+
+ /**
+ * Gets the optional name of a specific integration within the application, such
+ * as an extension or plugin.
+ *
+ * @return the integration name, or {@code null}
+ */
+ @JsonProperty("extensionName")
+ public String getIntegrationName() {
+ return integrationName;
+ }
+
+ /**
+ * Sets the optional name of a specific integration within the application, such
+ * as an extension or plugin.
+ *
+ * @param integrationName
+ * the integration name
+ * @return this client info for method chaining
+ */
+ @JsonProperty("extensionName")
+ public ClientInfo setIntegrationName(String integrationName) {
+ this.integrationName = integrationName;
+ return this;
+ }
+
+ /**
+ * Gets the optional version of the named integration.
+ *
+ * @return the integration version, or {@code null}
+ */
+ @JsonProperty("extensionVersion")
+ public String getIntegrationVersion() {
+ return integrationVersion;
+ }
+
+ /**
+ * Sets the optional version of the named integration.
+ *
+ * @param integrationVersion
+ * the integration version
+ * @return this client info for method chaining
+ */
+ @JsonProperty("extensionVersion")
+ public ClientInfo setIntegrationVersion(String integrationVersion) {
+ this.integrationVersion = integrationVersion;
+ return this;
+ }
+
+ /**
+ * Returns whether no field carries a non-empty value, in which case the SDK
+ * omits {@code clientInfo} from the handshake so the runtime keeps its default
+ * attribution.
+ *
+ * @return {@code true} when every field is {@code null} or empty
+ */
+ @JsonIgnore
+ public boolean isEmpty() {
+ return isBlank(applicationName) && isBlank(applicationVersion) && isBlank(integrationName)
+ && isBlank(integrationVersion);
+ }
+
+ private static boolean isBlank(String value) {
+ return value == null || value.isEmpty();
+ }
+}
diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java
index a9c9cbcda7..c67947bf48 100644
--- a/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java
+++ b/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java
@@ -55,6 +55,7 @@ public class CopilotClientOptions {
private String cliPath;
private String cliUrl;
private RuntimeConnection connection;
+ private ClientInfo clientInfo;
private String copilotHome;
private String cwd;
private Map environment;
@@ -678,6 +679,36 @@ public CopilotClientOptions setTelemetry(TelemetryConfig telemetry) {
return this;
}
+ /**
+ * Gets the integrating application's declared identity.
+ *
+ * @return the client info, or {@code null}
+ * @since 1.6.0
+ */
+ public ClientInfo getClientInfo() {
+ return clientInfo;
+ }
+
+ /**
+ * Declares the integrating application's identity, forwarded to the runtime on
+ * the {@code server.connect} handshake.
+ *
+ * Declaring it lets the telemetry the runtime emits on this connection be
+ * attributed to a consistent surface (the application and its Copilot
+ * integration) instead of the runtime's own build. All fields on
+ * {@link ClientInfo} are optional; leave this unset to keep the runtime's
+ * default attribution.
+ *
+ * @param clientInfo
+ * the application identity to declare
+ * @return this options instance for method chaining
+ * @since 1.6.0
+ */
+ public CopilotClientOptions setClientInfo(ClientInfo clientInfo) {
+ this.clientInfo = Objects.requireNonNull(clientInfo, "clientInfo must not be null");
+ return this;
+ }
+
/**
* Gets the server-wide idle timeout for sessions in seconds.
*
@@ -830,6 +861,7 @@ public CopilotClientOptions clone() {
copy.cliPath = this.cliPath;
copy.cliUrl = this.cliUrl;
copy.connection = this.connection;
+ copy.clientInfo = this.clientInfo;
copy.copilotHome = this.copilotHome;
copy.cwd = this.cwd;
copy.environment = this.environment != null ? new java.util.HashMap<>(this.environment) : null;
diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/SetModelOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/SetModelOptions.java
new file mode 100644
index 0000000000..9ebfbf199b
--- /dev/null
+++ b/java/sdk/src/main/java/com/github/copilot/rpc/SetModelOptions.java
@@ -0,0 +1,177 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.rpc;
+
+import com.github.copilot.CopilotExperimental;
+
+/**
+ * Optional settings for a model switch.
+ *
+ * All setter methods return {@code this} for method chaining. {@code model} is
+ * required. Every other option is optional; an unset option leaves the
+ * corresponding session state unchanged.
+ *
+ *
+ *
+ * @since 1.6.0
+ */
+public class SetModelOptions {
+
+ private String model;
+
+ private String reasoningEffort;
+
+ private String reasoningSummary;
+
+ private ModelCapabilitiesOverride modelCapabilities;
+
+ private AutoTier autoTier;
+
+ private boolean resetAutoTier;
+
+ /**
+ * Gets the target model ID.
+ *
+ * @return the model ID, or {@code null} when none has been set
+ */
+ public String getModel() {
+ return model;
+ }
+
+ /**
+ * Sets the model to switch to. This option is required.
+ *
+ * @param model
+ * the model ID (e.g., {@code "gpt-5.4"} or {@code "auto"})
+ * @return this options object for method chaining
+ */
+ public SetModelOptions setModel(String model) {
+ this.model = model;
+ return this;
+ }
+
+ /**
+ * Gets the reasoning effort level.
+ *
+ * @return the reasoning effort level, or {@code null} to use the default
+ */
+ public String getReasoningEffort() {
+ return reasoningEffort;
+ }
+
+ /**
+ * Sets the reasoning effort level.
+ *
+ * @param reasoningEffort
+ * reasoning effort level (e.g., {@code "low"}, {@code "medium"},
+ * {@code "high"}, {@code "xhigh"}, {@code "max"}); {@code null} to
+ * use the default
+ * @return this options object for method chaining
+ */
+ public SetModelOptions setReasoningEffort(String reasoningEffort) {
+ this.reasoningEffort = reasoningEffort;
+ return this;
+ }
+
+ /**
+ * Gets the reasoning summary mode.
+ *
+ * @return the reasoning summary mode, or {@code null} to use the default
+ */
+ public String getReasoningSummary() {
+ return reasoningSummary;
+ }
+
+ /**
+ * Sets the reasoning summary mode.
+ *
+ * @param reasoningSummary
+ * reasoning summary mode ({@code "none"}, {@code "concise"}, or
+ * {@code "detailed"}); {@code null} to use the default
+ * @return this options object for method chaining
+ */
+ public SetModelOptions setReasoningSummary(String reasoningSummary) {
+ this.reasoningSummary = reasoningSummary;
+ return this;
+ }
+
+ /**
+ * Gets the model capability overrides.
+ *
+ * @return the capability overrides, or {@code null} to use runtime defaults
+ */
+ public ModelCapabilitiesOverride getModelCapabilities() {
+ return modelCapabilities;
+ }
+
+ /**
+ * Sets per-property overrides for model capabilities.
+ *
+ * @param modelCapabilities
+ * the capability overrides; {@code null} to use runtime defaults
+ * @return this options object for method chaining
+ */
+ public SetModelOptions setModelCapabilities(ModelCapabilitiesOverride modelCapabilities) {
+ this.modelCapabilities = modelCapabilities;
+ return this;
+ }
+
+ /**
+ * Gets the requested Auto routing preference.
+ *
+ * @return the requested tier, or {@code null} when no tier was requested
+ */
+ @CopilotExperimental
+ public AutoTier getAutoTier() {
+ return autoTier;
+ }
+
+ /**
+ * Requests an Auto routing preference alongside the model switch.
+ *
+ * The runtime records the request and commits it only when a later user turn
+ * using the {@code auto} model successfully obtains a usable model from the
+ * provider. Use {@link #setResetAutoTier(boolean)} to return to the provider's
+ * default Auto routing instead.
+ *
+ * @param autoTier
+ * the routing preference to request; {@code null} to leave the
+ * current preference unchanged
+ * @return this options object for method chaining
+ */
+ @CopilotExperimental
+ public SetModelOptions setAutoTier(AutoTier autoTier) {
+ this.autoTier = autoTier;
+ return this;
+ }
+
+ /**
+ * Gets whether the request returns to provider-default Auto routing.
+ *
+ * @return {@code true} when the request clears the Auto routing preference
+ */
+ @CopilotExperimental
+ public boolean isResetAutoTier() {
+ return resetAutoTier;
+ }
+
+ /**
+ * Requests a return to the provider's default Auto routing.
+ *
+ * This differs from leaving {@link #setAutoTier(AutoTier)} unset, which keeps
+ * the current preference. It cannot be combined with an explicit tier.
+ *
+ * @param resetAutoTier
+ * {@code true} to return to provider-default Auto routing
+ * @return this options object for method chaining
+ */
+ @CopilotExperimental
+ public SetModelOptions setResetAutoTier(boolean resetAutoTier) {
+ this.resetAutoTier = resetAutoTier;
+ return this;
+ }
+}
diff --git a/java/sdk/src/test/java/com/github/copilot/AutoTierIT.java b/java/sdk/src/test/java/com/github/copilot/AutoTierIT.java
new file mode 100644
index 0000000000..d486ce1c2c
--- /dev/null
+++ b/java/sdk/src/test/java/com/github/copilot/AutoTierIT.java
@@ -0,0 +1,120 @@
+package com.github.copilot;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import com.github.copilot.generated.rpc.ModelSwitchAutoTierStatus;
+import com.github.copilot.rpc.AutoTier;
+import com.github.copilot.rpc.PermissionHandler;
+import com.github.copilot.rpc.SessionConfig;
+import com.github.copilot.rpc.SetModelOptions;
+
+/**
+ * End-to-end coverage for Auto tier switching, mirroring
+ * {@code nodejs/test/e2e/auto_tier.e2e.test.ts}.
+ *
+ * The runtime stages an Auto routing preference rather than applying it
+ * immediately: a request stays unclaimed until a later turn using the
+ * {@code auto} model mints a usable model and token pair. These tests read the
+ * staged state back through {@code model.getCurrent()}, so they assert what the
+ * runtime actually recorded rather than what the SDK serialized.
+ */
+class AutoTierIT {
+
+ private static final String MODEL_ID = "auto";
+
+ private static E2ETestContext ctx;
+
+ @BeforeAll
+ static void setUp() throws Exception {
+ ctx = E2ETestContext.create();
+ }
+
+ @AfterAll
+ static void tearDown() throws Exception {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+
+ private static com.github.copilot.generated.rpc.AutoTier pendingAutoTier(CopilotSession session) throws Exception {
+ return session.getRpc().model.getCurrent().get(30, TimeUnit.SECONDS).pendingAutoTier();
+ }
+
+ private static CopilotSession createAutoSession(CopilotClient client) throws Exception {
+ return client
+ .createSession(
+ new SessionConfig().setModel(MODEL_ID).setOnPermissionRequest(PermissionHandler.APPROVE_ALL))
+ .get(30, TimeUnit.SECONDS);
+ }
+
+ @Test
+ void shouldStageAndResetAutoTierPreference() throws Exception {
+ ctx.configureForTest("auto_tier", "should_stage_and_reset_auto_tier_preference");
+
+ try (CopilotClient client = ctx.createClient()) {
+ CopilotSession session = createAutoSession(client);
+ try {
+ assertNull(pendingAutoTier(session));
+
+ var staged = session.setAutoTier(AutoTier.EFFICIENCY).get(30, TimeUnit.SECONDS);
+ assertEquals(ModelSwitchAutoTierStatus.PENDING, staged.status());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.EFFICIENCY, staged.pendingAutoTier());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.EFFICIENCY, pendingAutoTier(session));
+
+ // A second request replaces the first and reports the one it displaced.
+ var superseded = session.setAutoTier(AutoTier.INTELLIGENCE).get(30, TimeUnit.SECONDS);
+ assertEquals(ModelSwitchAutoTierStatus.PENDING, superseded.status());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.INTELLIGENCE, superseded.pendingAutoTier());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.EFFICIENCY, superseded.supersededAutoTier());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.INTELLIGENCE, pendingAutoTier(session));
+
+ // A null tier returns the session to provider-default routing. The status
+ // is `unchanged` because provider-default was already the committed
+ // preference; the request's effect is cancelling the staged one.
+ var reset = session.setAutoTier(null).get(30, TimeUnit.SECONDS);
+ assertEquals(ModelSwitchAutoTierStatus.UNCHANGED, reset.status());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.INTELLIGENCE, reset.supersededAutoTier());
+ assertNull(pendingAutoTier(session));
+ } finally {
+ session.close();
+ }
+ }
+ }
+
+ @Test
+ void shouldPreserveAutoTierWhenSetModelOmitsIt() throws Exception {
+ ctx.configureForTest("auto_tier", "should_preserve_auto_tier_when_set_model_omits_it");
+
+ try (CopilotClient client = ctx.createClient()) {
+ CopilotSession session = createAutoSession(client);
+ try {
+ session.setAutoTier(AutoTier.BALANCE).get(30, TimeUnit.SECONDS);
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.BALANCE, pendingAutoTier(session));
+
+ // Omitting the preference leaves the staged one alone.
+ session.setModel(new SetModelOptions().setModel(MODEL_ID)).get(30, TimeUnit.SECONDS);
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.BALANCE, pendingAutoTier(session));
+
+ // Supplying a tier replaces it.
+ session.setModel(new SetModelOptions().setModel(MODEL_ID).setAutoTier(AutoTier.INTELLIGENCE)).get(30,
+ TimeUnit.SECONDS);
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.INTELLIGENCE, pendingAutoTier(session));
+
+ // Requesting a reset clears it. Omission, an explicit tier, and a reset
+ // are three distinct outcomes.
+ session.setModel(new SetModelOptions().setModel(MODEL_ID).setResetAutoTier(true)).get(30,
+ TimeUnit.SECONDS);
+ assertNull(pendingAutoTier(session));
+ } finally {
+ session.close();
+ }
+ }
+ }
+}
diff --git a/java/sdk/src/test/java/com/github/copilot/CapiSessionOptionsTest.java b/java/sdk/src/test/java/com/github/copilot/CapiSessionOptionsTest.java
index 17e8f131f7..dccb4e9add 100644
--- a/java/sdk/src/test/java/com/github/copilot/CapiSessionOptionsTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/CapiSessionOptionsTest.java
@@ -9,12 +9,16 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import com.fasterxml.jackson.databind.JsonNode;
+import com.github.copilot.rpc.AutoTier;
import com.github.copilot.rpc.CapiSessionOptions;
import com.github.copilot.rpc.ResumeSessionConfig;
import com.github.copilot.rpc.SessionConfig;
@@ -29,6 +33,7 @@ void defaultsAreNull() {
var capi = new CapiSessionOptions();
assertNull(capi.getEnableWebSocketResponses());
+ assertNull(capi.getAutoTier());
}
@Test
@@ -37,6 +42,8 @@ void fluentSetterReturnsSameInstance() {
assertSame(capi, capi.setEnableWebSocketResponses(true));
assertEquals(Boolean.TRUE, capi.getEnableWebSocketResponses());
+ assertSame(capi, capi.setAutoTier(AutoTier.BALANCE));
+ assertEquals(AutoTier.BALANCE, capi.getAutoTier());
}
@Test
@@ -46,6 +53,7 @@ void serializesEnableWebSocketResponses() {
JsonNode json = JsonRpcClient.getObjectMapper().valueToTree(capi);
assertTrue(json.get("enableWebSocketResponses").asBoolean());
+ assertTrue(json.path("autoTier").isMissingNode());
}
@Test
@@ -55,6 +63,44 @@ void omitsUnsetEnableWebSocketResponses() {
JsonNode json = JsonRpcClient.getObjectMapper().valueToTree(capi);
assertTrue(json.path("enableWebSocketResponses").isMissingNode());
+ assertTrue(json.path("autoTier").isMissingNode());
+ assertEquals(0, json.size());
+ }
+
+ @ParameterizedTest
+ @CsvSource({"EFFICIENCY,efficiency", "BALANCE,balance", "INTELLIGENCE,intelligence"})
+ void autoTierCanonicalValuesRoundTripAndForward(AutoTier tier, String value) throws Exception {
+ var mapper = JsonRpcClient.getObjectMapper();
+ var capi = new CapiSessionOptions().setAutoTier(tier);
+ JsonNode json = mapper.valueToTree(capi);
+ assertEquals(value, json.get("autoTier").asText());
+ assertEquals(1, json.size());
+ assertEquals(tier, mapper.treeToValue(json, CapiSessionOptions.class).getAutoTier());
+
+ capi.setEnableWebSocketResponses(false);
+ var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig().setModel("auto").setCapi(capi),
+ "session-1");
+ var resume = SessionRequestBuilder.buildResumeRequest("session-1", new ResumeSessionConfig().setCapi(capi));
+ for (Object request : new Object[]{create, resume}) {
+ JsonNode requestJson = mapper.valueToTree(request);
+ assertEquals(value, requestJson.get("capi").get("autoTier").asText());
+ assertFalse(requestJson.get("capi").get("enableWebSocketResponses").asBoolean());
+ assertEquals(2, requestJson.get("capi").size());
+ }
+ }
+
+ @Test
+ void autoTierRejectsNoncanonicalValues() {
+ for (String value : new String[]{"balanced", "Balance", "unknown"}) {
+ assertThrows(IllegalArgumentException.class, () -> AutoTier.fromValue(value));
+ }
+ assertNull(AutoTier.fromValue(null));
+ }
+
+ @Test
+ void clearingAutoTierOmitsIt() {
+ var capi = new CapiSessionOptions().setAutoTier(AutoTier.BALANCE).setAutoTier(null);
+ JsonNode json = JsonRpcClient.getObjectMapper().valueToTree(capi);
assertEquals(0, json.size());
}
@@ -67,6 +113,7 @@ void createRequestIncludesCapiWhenSet() {
assertNotNull(request.getCapi());
assertTrue(json.get("capi").get("enableWebSocketResponses").asBoolean());
+ assertTrue(json.get("capi").path("autoTier").isMissingNode());
}
@Test
@@ -89,6 +136,7 @@ void resumeRequestIncludesCapiWhenSet() {
assertNotNull(request.getCapi());
assertTrue(json.get("capi").get("enableWebSocketResponses").asBoolean());
+ assertTrue(json.get("capi").path("autoTier").isMissingNode());
}
@Test
diff --git a/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java b/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java
index a227be04b9..b41f99adb7 100644
--- a/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java
@@ -93,6 +93,13 @@ void parseCliUrlWithHttpsPrefix() {
assertEquals("https://secure.host:443", uri.toString());
}
+ @Test
+ void parseCliUrlWithBracketedIpv6() {
+ URI uri = CliServerManager.parseCliUrl("[::1]:4321");
+ assertNotNull(uri.getHost());
+ assertEquals(4321, uri.getPort());
+ }
+
@Test
void parseCliUrlWithHostOnly() {
URI uri = CliServerManager.parseCliUrl("copilot.example.com");
diff --git a/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java b/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java
index 45056afdb4..a2561434d8 100644
--- a/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java
+++ b/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java
@@ -263,6 +263,8 @@ function resultFor(message) {
return { sessionId: message.params?.sessionId ?? 'fake-session', openCanvases: [] };
case 'session.resume':
return { sessionId: message.params?.sessionId ?? 'fake-session', openCanvases: [] };
+ case 'session.detach':
+ return { success: true };
case 'session.options.update':
return { success: true };
default:
diff --git a/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java
index 4cfd7f4c48..6b8e861e88 100644
--- a/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java
@@ -7,6 +7,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import com.github.copilot.generated.ExternalToolRequestedEvent;
import com.github.copilot.rpc.CopilotClientOptions;
import com.github.copilot.rpc.DeleteSessionResponse;
import com.github.copilot.rpc.GitHubTokenProviderResult;
@@ -15,14 +16,19 @@
import com.github.copilot.rpc.SessionConfig;
import com.github.copilot.rpc.SessionLifecycleEvent;
import com.github.copilot.rpc.SessionLifecycleEventTypes;
+import com.github.copilot.rpc.ToolDefinition;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@@ -113,6 +119,49 @@ void testForceStopAndExternalStopDoNotRequestRuntimeShutdown() throws Exception
verify(externalRpc, never()).invoke(eq("runtime.shutdown"), any(), eq(Void.class));
}
+ @Test
+ @SuppressWarnings("unchecked")
+ void testForceStopCancelsPendingExternalTools() throws Exception {
+ var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false));
+ var rpc = mock(JsonRpcClient.class);
+ setConnectionFuture(client, rpc, null);
+ var session = new CopilotSession("force-stop-session", rpc);
+ var toolFuture = new CompletableFuture();
+ var started = new CountDownLatch(1);
+ var lateStarted = new CountDownLatch(1);
+ var invocations = new AtomicInteger();
+ session.registerTools(List.of(ToolDefinition.create("blocked_tool", "Blocks", Map.of(), invocation -> {
+ if (invocations.incrementAndGet() == 1) {
+ started.countDown();
+ } else {
+ lateStarted.countDown();
+ }
+ return toolFuture;
+ })));
+ Field sessionsField = CopilotClient.class.getDeclaredField("sessions");
+ sessionsField.setAccessible(true);
+ var sessions = (Map) sessionsField.get(client);
+ sessions.put(session.getSessionId(), session);
+
+ var requested = new ExternalToolRequestedEvent();
+ requested.setData(new ExternalToolRequestedEvent.ExternalToolRequestedEventData("request-force-stop",
+ session.getSessionId(), "tool-call-force-stop", "blocked_tool", null, Map.of(), null, null, null));
+ session.dispatchEvent(requested);
+ assertTrue(started.await(1, TimeUnit.SECONDS));
+
+ client.forceStop().get();
+
+ assertThrows(CancellationException.class, () -> toolFuture.get(1, TimeUnit.SECONDS));
+ assertTrue(sessions.isEmpty());
+
+ var lateRequest = new ExternalToolRequestedEvent();
+ lateRequest.setData(new ExternalToolRequestedEvent.ExternalToolRequestedEventData("request-after-force-stop",
+ session.getSessionId(), "tool-call-after-force-stop", "blocked_tool", null, Map.of(), null, null,
+ null));
+ session.dispatchEvent(lateRequest);
+ assertFalse(lateStarted.await(100, TimeUnit.MILLISECONDS));
+ }
+
@Test
@SuppressWarnings("unchecked")
void testDeleteSessionReleasesGitHubTokenProvider() throws Exception {
@@ -120,8 +169,8 @@ void testDeleteSessionReleasesGitHubTokenProvider() throws Exception {
var rpc = mock(JsonRpcClient.class);
when(rpc.invoke(eq("session.delete"), any(), eq(DeleteSessionResponse.class)))
.thenReturn(CompletableFuture.completedFuture(new DeleteSessionResponse(true, null)));
- when(rpc.invoke(eq("session.destroy"), any(), eq(Void.class)))
- .thenReturn(CompletableFuture.completedFuture(null));
+ when(rpc.invoke(eq("session.detach"), any(), eq(CopilotSession.SessionDetachResponse.class)))
+ .thenReturn(CompletableFuture.completedFuture(new CopilotSession.SessionDetachResponse(true, null)));
setConnectionFuture(client, rpc, null);
var registry = new GitHubTokenProviderRegistry();
@@ -179,6 +228,15 @@ void testCliUrlOnlyConstruction() {
client.close();
}
+ @Test
+ void testBracketedIpv6CliUrlNormalizesHost() throws Exception {
+ try (var client = new CopilotClient(new CopilotClientOptions().setCliUrl("[::1]:4321"))) {
+ Field hostField = CopilotClient.class.getDeclaredField("optionsHost");
+ hostField.setAccessible(true);
+ assertEquals("::1", hostField.get(client));
+ }
+ }
+
@Test
void testCliUrlMutualExclusionWithCliPath() {
var options = new CopilotClientOptions().setCliUrl("localhost:3000").setCliPath("/path/to/cli");
diff --git a/java/sdk/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java b/java/sdk/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java
index 3025c64c39..b4159d839c 100644
--- a/java/sdk/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java
+++ b/java/sdk/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java
@@ -77,11 +77,11 @@ void threadsSessionIdForCapiAndByok() throws Exception {
// BYOK session.
int before = handler.inferenceRequests().size();
ProviderConfig provider = new ProviderConfig().setType("openai").setWireApi("responses")
- .setBaseUrl("https://byok.invalid/v1").setApiKey("byok-secret").setModelId("claude-sonnet-4.5")
- .setWireModel("claude-sonnet-4.5");
+ .setBaseUrl("https://byok.invalid/v1").setApiKey("byok-secret").setModelId("claude-sonnet-5")
+ .setWireModel("claude-sonnet-5");
CopilotSession byokSession = client
.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
- .setModel("claude-sonnet-4.5").setProvider(provider))
+ .setModel("claude-sonnet-5").setProvider(provider))
.get();
String byokSessionId = byokSession.getSessionId();
diff --git a/java/sdk/src/test/java/com/github/copilot/CopilotRequestTestSupport.java b/java/sdk/src/test/java/com/github/copilot/CopilotRequestTestSupport.java
index aa173ef30e..fa2a6354be 100644
--- a/java/sdk/src/test/java/com/github/copilot/CopilotRequestTestSupport.java
+++ b/java/sdk/src/test/java/com/github/copilot/CopilotRequestTestSupport.java
@@ -134,7 +134,7 @@ static String anthropicMessageSseBody(String text) {
startMessage.put("id", "msg_stub_1");
startMessage.put("type", "message");
startMessage.put("role", "assistant");
- startMessage.put("model", "claude-sonnet-4.5");
+ startMessage.put("model", "claude-sonnet-5");
startMessage.put("content", List.of());
startMessage.put("stop_reason", null);
startMessage.put("stop_sequence", null);
@@ -251,7 +251,7 @@ static HttpResponse buildInferenceResponse(String url, String bodyT
body.put("id", "msg_stub_1");
body.put("type", "message");
body.put("role", "assistant");
- body.put("model", "claude-sonnet-4.5");
+ body.put("model", "claude-sonnet-5");
body.put("content", List.of(Map.of("type", "text", "text", text)));
body.put("stop_reason", "end_turn");
body.put("stop_sequence", null);
@@ -301,14 +301,14 @@ static String modelCatalog(List supportedEndpoints) {
Map capabilities = new LinkedHashMap<>();
capabilities.put("type", "chat");
- capabilities.put("family", "claude-sonnet-4.5");
+ capabilities.put("family", "claude-sonnet-5");
capabilities.put("tokenizer", "o200k_base");
capabilities.put("limits", limits);
capabilities.put("supports", supports);
Map model = new LinkedHashMap<>();
- model.put("id", "claude-sonnet-4.5");
- model.put("name", "Claude Sonnet 4.5");
+ model.put("id", "claude-sonnet-5");
+ model.put("name", "Claude Sonnet 5");
model.put("object", "model");
model.put("vendor", "Anthropic");
model.put("version", "1");
@@ -416,7 +416,7 @@ private static Map chatChunkBase() {
base.put("id", "chatcmpl-stub-1");
base.put("object", "chat.completion.chunk");
base.put("created", 1);
- base.put("model", "claude-sonnet-4.5");
+ base.put("model", "claude-sonnet-5");
return base;
}
@@ -459,7 +459,7 @@ private static Map chatCompletion(String text) {
root.put("id", "chatcmpl-stub-1");
root.put("object", "chat.completion");
root.put("created", 1);
- root.put("model", "claude-sonnet-4.5");
+ root.put("model", "claude-sonnet-5");
root.put("choices", List.of(choice));
root.put("usage", chatUsage());
return root;
diff --git a/java/sdk/src/test/java/com/github/copilot/CopilotSessionTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotSessionTest.java
index eb061b029d..667769fab2 100644
--- a/java/sdk/src/test/java/com/github/copilot/CopilotSessionTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/CopilotSessionTest.java
@@ -352,6 +352,51 @@ void testShouldResumeSessionUsingNewClient() throws Exception {
}
}
+ @Test
+ @Tag("isolated-resume")
+ void testShouldRecoverMarkerAfterColdResumeWithExplicitSessionId() throws Exception {
+ final String snapshot = "should_recover_marker_after_cold_resume_with_explicit_session_id";
+ ctx.configureForTest("session", snapshot);
+
+ String sessionId = "e2e-cold-resume-" + java.util.UUID.randomUUID();
+
+ try (CopilotClient client1 = ctx.createClient()) {
+ CopilotSession session1 = client1.createSession(
+ new SessionConfig().setSessionId(sessionId).setOnPermissionRequest(PermissionHandler.APPROVE_ALL))
+ .get(30, TimeUnit.SECONDS);
+ assertEquals(sessionId, session1.getSessionId());
+
+ AssistantMessageEvent answer = session1.sendAndWait(new MessageOptions()
+ .setPrompt("Please remember this exact secret marker for later - MARKER-7f3ac21e. "
+ + "Reply with only the single word \"Acknowledged\"."))
+ .get(60, TimeUnit.SECONDS);
+ assertNotNull(answer);
+ assertTrue(answer.getData().content().contains("Acknowledged"),
+ "Response should contain Acknowledged: " + answer.getData().content());
+
+ session1.close();
+ client1.forceStop().get(30, TimeUnit.SECONDS);
+ }
+
+ try (CopilotClient client2 = ctx.createClient()) {
+ CopilotSession session2 = client2
+ .resumeSession(sessionId,
+ new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL))
+ .get(30, TimeUnit.SECONDS);
+ assertEquals(sessionId, session2.getSessionId());
+
+ AssistantMessageEvent answer2 = session2.sendAndWait(
+ new MessageOptions().setPrompt("What was the exact secret marker I asked you to remember earlier? "
+ + "Reply with only that marker value and nothing else."))
+ .get(60, TimeUnit.SECONDS);
+ assertNotNull(answer2);
+ assertTrue(answer2.getData().content().contains("MARKER-7f3ac21e"),
+ "Resumed response should contain marker: " + answer2.getData().content());
+
+ session2.close();
+ }
+ }
+
/**
* Verifies that sessions work with appended system message configuration.
*
diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java
index cb302a8cd2..a6bec65d2c 100644
--- a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java
+++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java
@@ -596,53 +596,15 @@ private static String getCliPath(Path repoRoot) throws IOException {
return envPath;
}
- // Try test harness platform-specific binary (preferred as it has correct
- // version)
- String os = System.getProperty("os.name").toLowerCase();
- String arch = System.getProperty("os.arch").toLowerCase();
- String platform = os.contains("mac") ? "darwin" : os.contains("win") ? "win32" : "linux";
- String cpuArch = arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64";
- Path platformBinary = repoRoot
- .resolve("test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/copilot");
- if (os.contains("win")) {
- platformBinary = repoRoot
- .resolve("test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/copilot.exe");
- }
- if (Files.exists(platformBinary)) {
- return platformBinary.toString();
- }
-
- // Try test harness npm-loader.js
- Path harnessCliPath = repoRoot.resolve("test/harness/node_modules/@github/copilot/npm-loader.js");
- if (Files.exists(harnessCliPath)) {
- return harnessCliPath.toString();
- }
-
- // Try nodejs installation. As of CLI 1.0.64-1 the @github/copilot package
- // is a thin loader; the runnable index.js ships in the installed
- // platform-specific package (e.g. @github/copilot-linux-x64). Exactly one
- // is installed. Running index.js under Node.js is the documented preferred
- // entry point and matches the Go, Python, Rust, and .NET test harnesses.
- Path githubModules = repoRoot.resolve("nodejs/node_modules/@github");
- if (Files.isDirectory(githubModules)) {
- try (var modules = Files.newDirectoryStream(githubModules, "copilot-*")) {
- for (Path module : modules) {
- Path indexJs = module.resolve("index.js");
- if (Files.exists(indexJs)) {
- return indexJs.toString();
- }
- }
+ try {
+ return TestUtil.preparePinnedCli(repoRoot);
+ } catch (Exception e) {
+ String copilotInPath = findCopilotInPath();
+ if (copilotInPath != null) {
+ return copilotInPath;
}
+ throw new IOException("The pinned CLI could not be prepared and no CLI was found on PATH.", e);
}
-
- // Fallback: try to find 'copilot' in PATH
- String copilotInPath = findCopilotInPath();
- if (copilotInPath != null) {
- return copilotInPath;
- }
-
- throw new IOException("CLI not found. Either install 'copilot' globally, set COPILOT_CLI_PATH, "
- + "or run 'npm install' in the nodejs directory or test/harness directory.");
}
private static String findCopilotInPath() {
diff --git a/java/sdk/src/test/java/com/github/copilot/ExternalToolCancellationE2ETest.java b/java/sdk/src/test/java/com/github/copilot/ExternalToolCancellationE2ETest.java
new file mode 100644
index 0000000000..d5ae322e32
--- /dev/null
+++ b/java/sdk/src/test/java/com/github/copilot/ExternalToolCancellationE2ETest.java
@@ -0,0 +1,91 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BooleanSupplier;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import com.github.copilot.rpc.MessageOptions;
+import com.github.copilot.rpc.PermissionHandler;
+import com.github.copilot.rpc.SessionConfig;
+import com.github.copilot.rpc.ToolDefinition;
+
+public class ExternalToolCancellationE2ETest {
+
+ private static E2ETestContext ctx;
+
+ @BeforeAll
+ static void setup() throws Exception {
+ ctx = E2ETestContext.create();
+ }
+
+ @AfterAll
+ static void teardown() throws Exception {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+
+ @Test
+ void shouldCancelToolHandlerWhenSessionDisconnects() throws Exception {
+ ctx.configureForTest("external_tool_cancellation", "should_cancel_tool_handler_when_session_disconnects");
+
+ var pendingTool = new AtomicReference>();
+ ToolDefinition slowTool = ToolDefinition.create("slow_analysis",
+ "A slow analysis tool that blocks until released", slowAnalysisSchema(), invocation -> {
+ CompletableFuture pending = new CompletableFuture<>();
+ pendingTool.set(pending);
+ return pending;
+ });
+
+ try (CopilotClient client = ctx.createClient()) {
+ CopilotSession session = client.createSession(new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setTools(List.of(slowTool)))
+ .get(60, TimeUnit.SECONDS);
+ try {
+ session.send(new MessageOptions()
+ .setPrompt("Use slow_analysis with value 'test_abort'. Wait for the result."))
+ .get(60, TimeUnit.SECONDS);
+
+ waitFor(() -> pendingTool.get() != null, 60_000);
+ session.close();
+ waitFor(() -> pendingTool.get() != null && pendingTool.get().isCancelled(), 60_000);
+ } finally {
+ if (session != null) {
+ session.close();
+ }
+ }
+ }
+ }
+
+ private static Map slowAnalysisSchema() {
+ Map props = new HashMap<>();
+ props.put("value", Map.of("type", "string", "description", "Value to analyze"));
+ Map schema = new HashMap<>();
+ schema.put("type", "object");
+ schema.put("properties", props);
+ schema.put("required", List.of("value"));
+ return schema;
+ }
+
+ private static void waitFor(BooleanSupplier predicate, long timeoutMillis) throws InterruptedException {
+ long deadline = System.currentTimeMillis() + timeoutMillis;
+ while (!predicate.getAsBoolean()) {
+ if (System.currentTimeMillis() > deadline) {
+ throw new AssertionError("waitFor timed out");
+ }
+ Thread.sleep(50);
+ }
+ }
+}
diff --git a/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java b/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java
index 7b0deb9977..10c47eee86 100644
--- a/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java
@@ -24,6 +24,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.github.copilot.generated.rpc.GitHubTelemetryNotification;
+import com.github.copilot.rpc.ClientInfo;
import com.github.copilot.rpc.CopilotClientOptions;
import com.github.copilot.rpc.PermissionHandler;
import com.github.copilot.rpc.ResumeSessionConfig;
@@ -188,6 +189,7 @@ void clientOmitsForwardingWhenNoHandler() throws Exception {
JsonNode connectParams = server.awaitConnect();
assertFalse(connectParams.has("enableGitHubTelemetryForwarding"),
"connect request should omit the flag when no handler is registered");
+ assertEquals("[\"agent\",\"client\",\"shell\"]", connectParams.path("supportedTaskKinds").toString());
client.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(15,
TimeUnit.SECONDS);
@@ -204,6 +206,96 @@ void clientOmitsForwardingWhenNoHandler() throws Exception {
}
}
+ @Test
+ void connectForwardsDeclaredClientInfo() throws Exception {
+ try (var server = new FakeRuntimeServer();
+ var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url())
+ .setClientInfo(new ClientInfo().setApplicationName("acme-developer-portal")
+ .setApplicationVersion("2.4.0").setIntegrationName("copilot-assistant")
+ .setIntegrationVersion("1.5.0")))) {
+
+ client.start().get(15, TimeUnit.SECONDS);
+
+ JsonNode connectParams = server.awaitConnect();
+ JsonNode clientInfo = connectParams.path("clientInfo");
+ assertEquals(4, clientInfo.size(), "clientInfo should carry only the four declared fields");
+ assertEquals("acme-developer-portal", clientInfo.path("editorName").asText());
+ assertEquals("2.4.0", clientInfo.path("editorVersion").asText());
+ assertEquals("copilot-assistant", clientInfo.path("extensionName").asText());
+ assertEquals("1.5.0", clientInfo.path("extensionVersion").asText());
+ }
+ }
+
+ @Test
+ void connectOmitsClientInfoWhenUnset() throws Exception {
+ try (var server = new FakeRuntimeServer();
+ var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()))) {
+
+ client.start().get(15, TimeUnit.SECONDS);
+
+ JsonNode connectParams = server.awaitConnect();
+ assertFalse(connectParams.has("clientInfo"),
+ "connect request should omit clientInfo when none was declared");
+ }
+ }
+
+ @Test
+ void connectOmitsEmptyClientInfoFields() throws Exception {
+ try (var server = new FakeRuntimeServer();
+ var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url())
+ .setClientInfo(new ClientInfo().setApplicationName("example-app")))) {
+
+ client.start().get(15, TimeUnit.SECONDS);
+
+ JsonNode connectParams = server.awaitConnect();
+ JsonNode clientInfo = connectParams.path("clientInfo");
+ assertEquals("example-app", clientInfo.path("editorName").asText());
+ assertFalse(clientInfo.has("editorVersion"), "unset editorVersion should be omitted");
+ assertFalse(clientInfo.has("extensionName"), "unset extensionName should be omitted");
+ assertFalse(clientInfo.has("extensionVersion"), "unset extensionVersion should be omitted");
+ }
+ }
+
+ @Test
+ void connectDropsEmptyClientInfoFields() throws Exception {
+ try (var server = new FakeRuntimeServer();
+ var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url())
+ .setClientInfo(new ClientInfo().setApplicationName("example-app").setApplicationVersion("")))) {
+
+ client.start().get(15, TimeUnit.SECONDS);
+
+ JsonNode connectParams = server.awaitConnect();
+ JsonNode clientInfo = connectParams.path("clientInfo");
+ assertEquals(1, clientInfo.size(), "clientInfo should carry only the non-empty field");
+ assertEquals("example-app", clientInfo.path("editorName").asText());
+ assertFalse(clientInfo.has("editorVersion"), "empty editorVersion should be dropped");
+ }
+ }
+
+ @Test
+ void connectOmitsAllEmptyClientInfo() throws Exception {
+ try (var server = new FakeRuntimeServer();
+ var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url())
+ .setClientInfo(new ClientInfo().setApplicationName("").setApplicationVersion("")
+ .setIntegrationName("").setIntegrationVersion("")))) {
+
+ client.start().get(15, TimeUnit.SECONDS);
+
+ JsonNode connectParams = server.awaitConnect();
+ assertFalse(connectParams.has("clientInfo"), "connect request should omit an all-empty clientInfo");
+ }
+ }
+
+ @Test
+ void optionsRetainAndCloneClientInfo() {
+ var info = new ClientInfo().setApplicationName("example-app");
+ var options = new CopilotClientOptions().setClientInfo(info);
+ assertSame(info, options.getClientInfo());
+
+ var copy = options.clone();
+ assertSame(info, copy.getClientInfo());
+ }
+
@Test
void optionsRetainAndCloneTelemetryHandler() {
Function> handler = n -> CompletableFuture
@@ -274,7 +366,8 @@ private void acceptLoop() {
respond(rpc, id, Map.of("sessionId", params.path("sessionId").asText("resume-1"),
"workspacePath", "/workspace"));
});
- rpc.registerMethodHandler("session.destroy", (id, params) -> respond(rpc, id, Map.of()));
+ rpc.registerMethodHandler("session.detach",
+ (id, params) -> respond(rpc, id, Map.of("success", true)));
rpc.registerMethodHandler("runtime.shutdown", (id, params) -> respond(rpc, id, Map.of()));
});
ready.complete(server);
diff --git a/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java
index 009f15c200..13668cd97f 100644
--- a/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java
@@ -16,6 +16,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
@@ -184,6 +185,36 @@ void testGetProcessNullForSocket() throws Exception {
}
}
+ @Test
+ void testCloseHandlerRunsOnceOnRemoteAndExplicitClose() throws Exception {
+ try (var pair = createSocketPair()) {
+ var closeCount = new AtomicInteger();
+ var closed = new CompletableFuture();
+ pair.client.setCloseHandler(() -> {
+ closeCount.incrementAndGet();
+ closed.complete(null);
+ });
+
+ pair.serverSide.close();
+ closed.get(5, TimeUnit.SECONDS);
+ pair.client.close();
+
+ assertEquals(1, closeCount.get());
+ }
+ }
+
+ @Test
+ void testCloseHandlerRunsWhenRegisteredAfterClose() throws Exception {
+ try (var pair = createSocketPair()) {
+ pair.client.close();
+ var closed = new CompletableFuture();
+
+ pair.client.setCloseHandler(() -> closed.complete(null));
+
+ closed.get(5, TimeUnit.SECONDS);
+ }
+ }
+
// ---- invoke() edge cases ----
@Test
diff --git a/java/sdk/src/test/java/com/github/copilot/McpAndAgentsTest.java b/java/sdk/src/test/java/com/github/copilot/McpAndAgentsTest.java
index 06d9dca39e..018204d030 100644
--- a/java/sdk/src/test/java/com/github/copilot/McpAndAgentsTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/McpAndAgentsTest.java
@@ -451,7 +451,7 @@ void testShouldAcceptDefaultAgentConfigurationOnSessionResume() throws Exception
assertNotNull(session.getSessionId());
String sessionId = session.getSessionId();
- // Do not call session.close() here — that invokes session.destroy on the
+ // Do not call session.close() here — that invokes session.detach on the
// server,
// which removes the session and causes the subsequent resumeSession to fail
// with "Session not found". The session handle is simply abandoned and the
diff --git a/java/sdk/src/test/java/com/github/copilot/McpAuthInterestRegistrationTest.java b/java/sdk/src/test/java/com/github/copilot/McpAuthInterestRegistrationTest.java
index 06ac08a2a4..b83cedc70a 100644
--- a/java/sdk/src/test/java/com/github/copilot/McpAuthInterestRegistrationTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/McpAuthInterestRegistrationTest.java
@@ -255,7 +255,10 @@ private static JsonNode resultFor(String method, JsonNode params) {
}
case "session.eventLog.registerInterest" -> result.put("id", "interest-1");
case "session.options.update" -> result.put("success", true);
- case "session.skills.reload", "session.destroy" -> {
+ case "session.skills.reload" -> {
+ }
+ case "session.detach" -> {
+ result.put("success", true);
}
default -> throw new IllegalStateException("Unexpected RPC method " + method);
}
diff --git a/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java b/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java
index 2393f334b2..6c9753025a 100644
--- a/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java
+++ b/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java
@@ -135,7 +135,7 @@ void testShouldCallRpcModelsListWithTypedResult() throws Exception {
var result = client.getRpc().models.list().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertNotNull(result.models());
- assertTrue(result.models().stream().anyMatch(model -> "claude-sonnet-4.5".equals(model.id())));
+ assertTrue(result.models().stream().anyMatch(model -> "claude-sonnet-5".equals(model.id())));
result.models().forEach(model -> {
assertFalse(model.id().isBlank());
assertFalse(model.name().isBlank());
diff --git a/java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java b/java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java
index 18045f3e86..235b1d5720 100644
--- a/java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java
+++ b/java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java
@@ -65,7 +65,7 @@ void testShouldAddByokProviderAndModelAtRuntime() throws Exception {
var selectionId = "java-e2e-provider/small";
session.getRpc().model.switchTo(new SessionModelSwitchToParams(null, selectionId, null, null, null,
- null, null, null, null, null, null, null, null, null, null)).get(30, TimeUnit.SECONDS);
+ null, null, null, null, null, null, null, null, null, null, null)).get(30, TimeUnit.SECONDS);
var current = session.getRpc().model.getCurrent().get(30, TimeUnit.SECONDS);
assertEquals(selectionId, current.modelId());
}
@@ -142,7 +142,7 @@ void testShouldUpdateAndClearLiveSubagentSettings() throws Exception {
session.getRpc().tools.updateSubagentSettings(new SessionToolsUpdateSubagentSettingsParams(null,
new SessionToolsUpdateSubagentSettingsParams.SessionToolsUpdateSubagentSettingsParamsSubagents(
Map.of("general-purpose",
- new SubagentSettingsEntry("gpt-5-mini", "low",
+ new SubagentSettingsEntry("gpt-5-mini", null, "low",
SubagentSettingsEntryContextTier.LONG_CONTEXT)),
List.of("legacy-agent"), null, null)))
.get(30, TimeUnit.SECONDS);
diff --git a/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java b/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java
index 3bb9001f35..7b55748bb8 100644
--- a/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java
@@ -206,7 +206,7 @@ void sessionRpc_model_switchTo_merges_sessionId_with_extra_params() {
// switchTo takes extra params beyond sessionId
var switchParams = new SessionModelSwitchToParams(null, "gpt-5", null, null, null, null, null, null, null, null,
- null, null, null, null, null);
+ null, null, null, null, null, null);
session.model.switchTo(switchParams);
assertEquals(1, stub.calls.size());
diff --git a/java/sdk/src/test/java/com/github/copilot/SessionAutoTierEventTest.java b/java/sdk/src/test/java/com/github/copilot/SessionAutoTierEventTest.java
new file mode 100644
index 0000000000..25e356a8d1
--- /dev/null
+++ b/java/sdk/src/test/java/com/github/copilot/SessionAutoTierEventTest.java
@@ -0,0 +1,104 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.github.copilot.generated.AutoTier;
+import com.github.copilot.generated.AutoTierSwitchFailureReason;
+import com.github.copilot.generated.SessionAutoTierSwitchFailedEvent;
+import com.github.copilot.generated.SessionEvent;
+import com.github.copilot.generated.SessionResumeEvent;
+import com.github.copilot.generated.SessionStartEvent;
+
+/**
+ * Verifies auto routing preferences on generated session lifecycle events.
+ */
+class SessionAutoTierEventTest {
+
+ private static final ObjectMapper MAPPER = JsonRpcClient.getObjectMapper();
+
+ @ParameterizedTest
+ @CsvSource({"session.start,EFFICIENCY,efficiency", "session.start,BALANCE,balance",
+ "session.start,INTELLIGENCE,intelligence", "session.resume,EFFICIENCY,efficiency",
+ "session.resume,BALANCE,balance", "session.resume,INTELLIGENCE,intelligence"})
+ void canonicalAutoTierRoundTrips(String type, AutoTier tier, String value) throws Exception {
+ String json = """
+ {"type":"%s","data":{"selectedModel":"auto","autoTier":"%s"}}
+ """.formatted(type, value);
+
+ var event = MAPPER.readValue(json, SessionEvent.class);
+ assertEquals(tier, autoTier(event, type));
+ String serialized = MAPPER.writeValueAsString(event);
+ assertEquals(value, MAPPER.readTree(serialized).path("data").path("autoTier").asText());
+ assertEquals(tier, autoTier(MAPPER.readValue(serialized, SessionEvent.class), type));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"session.start", "session.resume"})
+ void missingOrNullAutoTierRemainsOptional(String type) throws Exception {
+ for (String data : new String[]{"{}", "{\"autoTier\":null}"}) {
+ String json = """
+ {"type":"%s","data":%s}
+ """.formatted(type, data);
+
+ var event = MAPPER.readValue(json, SessionEvent.class);
+ assertNull(autoTier(event, type));
+ var serialized = MAPPER.readTree(MAPPER.writeValueAsString(event));
+ assertFalse(serialized.path("data").has("autoTier"));
+ }
+ }
+
+ private static AutoTier autoTier(SessionEvent event, String type) {
+ if ("session.start".equals(type)) {
+ return assertInstanceOf(SessionStartEvent.class, event).getData().autoTier();
+ }
+ return assertInstanceOf(SessionResumeEvent.class, event).getData().autoTier();
+ }
+
+ @ParameterizedTest
+ @CsvSource({"policy_rejected,POLICY_REJECTED", "request_failed,REQUEST_FAILED", "setup_failed,SETUP_FAILED",
+ "unsupported,UNSUPPORTED"})
+ void autoTierSwitchFailedEventDecodesEveryReason(String value, AutoTierSwitchFailureReason reason)
+ throws Exception {
+ String json = """
+ {"type":"session.auto_tier_switch_failed","data":{"effectiveAutoTier":"balance",
+ "requestedAutoTier":"intelligence","reason":"%s"}}
+ """.formatted(value);
+
+ var event = MAPPER.readValue(json, SessionEvent.class);
+
+ var data = assertInstanceOf(SessionAutoTierSwitchFailedEvent.class, event).getData();
+ assertEquals(AutoTier.BALANCE, data.effectiveAutoTier());
+ assertEquals(AutoTier.INTELLIGENCE, data.requestedAutoTier());
+ assertEquals(reason, data.reason());
+ }
+
+ @org.junit.jupiter.api.Test
+ void autoTierSwitchFailedEventAllowsNullRequestedTier() throws Exception {
+ // A null requested tier means the attempt to return to provider-default
+ // Auto routing is what failed.
+ String json = """
+ {"type":"session.auto_tier_switch_failed","data":{"effectiveAutoTier":"efficiency",
+ "requestedAutoTier":null,"reason":"unsupported"}}
+ """;
+
+ var event = MAPPER.readValue(json, SessionEvent.class);
+
+ var data = assertInstanceOf(SessionAutoTierSwitchFailedEvent.class, event).getData();
+ assertEquals(AutoTier.EFFICIENCY, data.effectiveAutoTier());
+ assertNull(data.requestedAutoTier());
+ assertEquals(AutoTierSwitchFailureReason.UNSUPPORTED, data.reason());
+ }
+}
diff --git a/java/sdk/src/test/java/com/github/copilot/SessionAutoTierSwitchTest.java b/java/sdk/src/test/java/com/github/copilot/SessionAutoTierSwitchTest.java
new file mode 100644
index 0000000000..adeeca2c6b
--- /dev/null
+++ b/java/sdk/src/test/java/com/github/copilot/SessionAutoTierSwitchTest.java
@@ -0,0 +1,228 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.copilot.generated.rpc.ModelSwitchAutoTierStatus;
+import com.github.copilot.generated.rpc.SessionModelSwitchAutoTierResult;
+import com.github.copilot.rpc.AutoTier;
+import com.github.copilot.rpc.SetModelOptions;
+import java.io.InputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies the wire payloads produced by Auto routing preference switches.
+ *
+ * The runtime treats an explicit {@code null} {@code autoTier} (return to
+ * provider-default routing) differently from an absent one (leave the
+ * preference unchanged), so these tests assert on the raw JSON rather than on
+ * the generated params records, which drop null properties.
+ */
+@AllowCopilotExperimental
+class SessionAutoTierSwitchTest {
+
+ @Test
+ void setModel_omits_autoTier_when_no_preference_is_requested() throws Exception {
+ try (var sockets = new SocketPair()) {
+ var session = new CopilotSession("sess-1", sockets.client());
+ var stub = sockets.stubServer();
+
+ session.setModel(new SetModelOptions().setModel("auto"));
+
+ var params = stub.readOneMessage().get("params");
+ assertEquals("auto", params.get("modelId").asText());
+ assertFalse(params.has("autoTier"), "an unset preference must not appear on the wire");
+ }
+ }
+
+ @Test
+ void setModel_sends_requested_autoTier() throws Exception {
+ try (var sockets = new SocketPair()) {
+ var session = new CopilotSession("sess-2", sockets.client());
+ var stub = sockets.stubServer();
+
+ session.setModel(new SetModelOptions().setModel("auto").setAutoTier(AutoTier.INTELLIGENCE)
+ .setReasoningEffort("high"));
+
+ var sent = stub.readOneMessage();
+ assertEquals("session.model.switchTo", sent.get("method").asText());
+ var params = sent.get("params");
+ assertEquals("intelligence", params.get("autoTier").asText());
+ assertEquals("high", params.get("reasoningEffort").asText());
+ assertEquals("sess-2", params.get("sessionId").asText());
+ }
+ }
+
+ @Test
+ void setModel_sends_explicit_null_autoTier_when_clearing() throws Exception {
+ try (var sockets = new SocketPair()) {
+ var session = new CopilotSession("sess-3", sockets.client());
+ var stub = sockets.stubServer();
+
+ session.setModel(new SetModelOptions().setModel("auto").setResetAutoTier(true));
+
+ var sent = stub.readOneMessage();
+ assertEquals("session.model.switchTo", sent.get("method").asText());
+ var params = sent.get("params");
+ assertTrue(params.has("autoTier"), "clearing must send the property");
+ assertTrue(params.get("autoTier").isNull(), "clearing must send an explicit null");
+ assertEquals("sess-3", params.get("sessionId").asText());
+ }
+ }
+
+ @Test
+ void setModel_rejects_a_tier_combined_with_clearing() throws Exception {
+ try (var sockets = new SocketPair()) {
+ var session = new CopilotSession("sess-4", sockets.client());
+
+ var options = new SetModelOptions().setModel("auto").setAutoTier(AutoTier.BALANCE).setResetAutoTier(true);
+
+ assertThrows(IllegalArgumentException.class, () -> session.setModel(options));
+ }
+ }
+
+ @Test
+ void setModel_requires_a_model() throws Exception {
+ try (var sockets = new SocketPair()) {
+ var session = new CopilotSession("sess-5", sockets.client());
+
+ assertThrows(IllegalArgumentException.class, () -> session.setModel(new SetModelOptions()));
+ assertThrows(IllegalArgumentException.class, () -> session.setModel((SetModelOptions) null));
+ }
+ }
+
+ @Test
+ void setAutoTier_sends_the_requested_tier() throws Exception {
+ try (var sockets = new SocketPair()) {
+ var session = new CopilotSession("sess-6", sockets.client());
+ var stub = sockets.stubServer();
+
+ session.setAutoTier(AutoTier.EFFICIENCY);
+
+ var sent = stub.readOneMessage();
+ assertEquals("session.model.switchAutoTier", sent.get("method").asText());
+ var params = sent.get("params");
+ assertEquals("efficiency", params.get("autoTier").asText());
+ assertEquals("sess-6", params.get("sessionId").asText());
+ }
+ }
+
+ @Test
+ void setAutoTier_sends_explicit_null_for_provider_default_routing() throws Exception {
+ try (var sockets = new SocketPair()) {
+ var session = new CopilotSession("sess-7", sockets.client());
+ var stub = sockets.stubServer();
+
+ session.setAutoTier(null);
+
+ var sent = stub.readOneMessage();
+ assertEquals("session.model.switchAutoTier", sent.get("method").asText());
+ var params = sent.get("params");
+ assertTrue(params.has("autoTier"), "returning to provider-default routing must send the property");
+ assertTrue(params.get("autoTier").isNull(), "returning to provider-default routing must send null");
+ assertEquals("sess-7", params.get("sessionId").asText());
+ }
+ }
+
+ @Test
+ void switchAutoTier_result_deserializes_every_field() throws Exception {
+ var json = """
+ {
+ "status": "pending",
+ "effectiveAutoTier": "balance",
+ "pendingAutoTier": "intelligence",
+ "activatingAutoTier": null,
+ "supersededAutoTier": "efficiency"
+ }
+ """;
+
+ var result = new ObjectMapper().readValue(json, SessionModelSwitchAutoTierResult.class);
+
+ assertEquals(ModelSwitchAutoTierStatus.PENDING, result.status());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.BALANCE, result.effectiveAutoTier());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.INTELLIGENCE, result.pendingAutoTier());
+ assertNull(result.activatingAutoTier());
+ assertEquals(com.github.copilot.generated.rpc.AutoTier.EFFICIENCY, result.supersededAutoTier());
+ }
+
+ /**
+ * Loopback socket pair; the client side backs a real {@link JsonRpcClient} and
+ * the server side exposes the raw outbound messages.
+ */
+ private static final class SocketPair implements AutoCloseable {
+
+ private final Socket clientSocket;
+ private final Socket serverSocket;
+ private final JsonRpcClient rpcClient;
+
+ SocketPair() throws Exception {
+ try (var ss = new ServerSocket(0)) {
+ clientSocket = new Socket("localhost", ss.getLocalPort());
+ serverSocket = ss.accept();
+ }
+ serverSocket.setSoTimeout(3000);
+ rpcClient = JsonRpcClient.fromSocket(clientSocket);
+ }
+
+ JsonRpcClient client() {
+ return rpcClient;
+ }
+
+ StubServer stubServer() {
+ return new StubServer(serverSocket);
+ }
+
+ @Override
+ public void close() throws Exception {
+ rpcClient.close();
+ clientSocket.close();
+ serverSocket.close();
+ }
+ }
+
+ /** Reads Content-Length framed JSON-RPC messages from the server socket. */
+ private static final class StubServer {
+
+ private static final ObjectMapper MAPPER = JsonRpcClient.getObjectMapper();
+
+ private final InputStream in;
+
+ StubServer(Socket socket) {
+ try {
+ this.in = socket.getInputStream();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ JsonNode readOneMessage() throws Exception {
+ var header = new StringBuilder();
+ int b;
+ while ((b = in.read()) != -1) {
+ if (b == '\n' && header.toString().endsWith("\r")) {
+ break;
+ }
+ header.append((char) b);
+ }
+ in.read();
+ in.read();
+
+ String hdr = header.toString().trim();
+ int colon = hdr.indexOf(':');
+ int len = Integer.parseInt(hdr.substring(colon + 1).trim());
+ byte[] body = in.readNBytes(len);
+ return MAPPER.readTree(body);
+ }
+ }
+}
diff --git a/java/sdk/src/test/java/com/github/copilot/SessionConfigE2ETest.java b/java/sdk/src/test/java/com/github/copilot/SessionConfigE2ETest.java
index 925fd6d873..e786bda994 100644
--- a/java/sdk/src/test/java/com/github/copilot/SessionConfigE2ETest.java
+++ b/java/sdk/src/test/java/com/github/copilot/SessionConfigE2ETest.java
@@ -125,7 +125,7 @@ void testShouldForwardProviderWireModel() throws Exception {
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client
- .createSession(new SessionConfig().setModel("claude-sonnet-4.5")
+ .createSession(new SessionConfig().setModel("claude-sonnet-5")
.setProvider(new ProviderConfig().setType("openai").setBaseUrl(ctx.getProxyUrl())
.setApiKey("test-provider-key").setWireModel("test-wire-model")
.setMaxOutputTokens(1024))
@@ -149,7 +149,7 @@ void testShouldUseProviderModelIdAsWireModel() throws Exception {
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(new SessionConfig()
.setProvider(new ProviderConfig().setType("openai").setBaseUrl(ctx.getProxyUrl())
- .setApiKey("test-provider-key").setModelId("claude-sonnet-4.5"))
+ .setApiKey("test-provider-key").setModelId("claude-sonnet-5"))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get();
session.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(30, TimeUnit.SECONDS);
@@ -158,7 +158,7 @@ void testShouldUseProviderModelIdAsWireModel() throws Exception {
assertFalse(exchanges.isEmpty(), "Should have at least one exchange");
@SuppressWarnings("unchecked")
Map request = (Map) exchanges.get(0).get("request");
- assertEquals("claude-sonnet-4.5", request.get("model"));
+ assertEquals("claude-sonnet-5", request.get("model"));
}
}
@@ -272,7 +272,7 @@ void testShouldEnableCitationsForAnthropicFileAttachmentsOnCreate() throws Excep
var handler = new CopilotRequestTestSupport.RecordingRequestHandler(SYNTHETIC_TEXT);
try (CopilotClient client = newLlmClient(ctx, handler)) {
- CopilotSession session = client.createSession(new SessionConfig().setModel("claude-sonnet-4.5")
+ CopilotSession session = client.createSession(new SessionConfig().setModel("claude-sonnet-5")
.setEnableCitations(true).setProvider(createAnthropicProvider())
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get();
@@ -296,7 +296,7 @@ void testShouldEnableCitationsForAnthropicFileAttachmentsOnResume() throws Excep
CopilotSession session1 = client
.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get();
CopilotSession session2 = client.resumeSession(session1.getSessionId(),
- new ResumeSessionConfig().setModel("claude-sonnet-4.5").setEnableCitations(true)
+ new ResumeSessionConfig().setModel("claude-sonnet-5").setEnableCitations(true)
.setProvider(createAnthropicProvider())
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL))
.get();
@@ -388,7 +388,7 @@ private static BlobAttachment createPdfAttachment() {
private static ProviderConfig createAnthropicProvider() {
return new ProviderConfig().setType("anthropic").setBaseUrl("https://anthropic-citations.invalid/v1")
- .setApiKey("test-provider-key").setModelId("claude-sonnet-4.5").setWireModel("claude-sonnet-4.5");
+ .setApiKey("test-provider-key").setModelId("claude-sonnet-5").setWireModel("claude-sonnet-5");
}
private static String singleInferenceRequestBody(CopilotRequestTestSupport.RecordingRequestHandler handler) {
diff --git a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java
index b75e710720..83913e82b1 100644
--- a/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java
@@ -10,9 +10,12 @@
import java.io.Closeable;
import java.lang.reflect.Method;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -26,11 +29,15 @@
import com.github.copilot.generated.SessionEvent;
import com.github.copilot.generated.AssistantMessageEvent;
+import com.github.copilot.generated.ExternalToolCompletedEvent;
+import com.github.copilot.generated.ExternalToolRequestedEvent;
import com.github.copilot.generated.SessionIdleEvent;
import com.github.copilot.generated.SessionMode;
import com.github.copilot.generated.SessionStartEvent;
+import com.github.copilot.generated.rpc.SessionToolsGetCurrentMetadataResult;
import com.github.copilot.rpc.MessageOptions;
import com.github.copilot.rpc.SendMessageResponse;
+import com.github.copilot.rpc.ToolDefinition;
/**
* Unit tests for session event handling API.
@@ -50,10 +57,14 @@ void setup() throws Exception {
}
private CopilotSession createTestSession() throws Exception {
+ return createTestSession(null);
+ }
+
+ private CopilotSession createTestSession(JsonRpcClient rpc) throws Exception {
// Use the package-private constructor via reflection for testing
var constructor = CopilotSession.class.getDeclaredConstructor(String.class, JsonRpcClient.class, String.class);
constructor.setAccessible(true);
- return constructor.newInstance("test-session-id", null, null);
+ return constructor.newInstance("test-session-id", rpc, null);
}
@Test
@@ -73,6 +84,103 @@ void testGenericEventHandler() {
assertInstanceOf(SessionIdleEvent.class, receivedEvents.get(2));
}
+ @Test
+ void testExternalToolCompletedCancelsBlockedHandler() throws Exception {
+ var toolFuture = new CompletableFuture();
+ var started = new CountDownLatch(1);
+ session.registerTools(List.of(ToolDefinition.create("blocked_tool", "Blocks", Map.of(), invocation -> {
+ started.countDown();
+ return toolFuture;
+ })));
+
+ var requested = new ExternalToolRequestedEvent();
+ requested.setData(new ExternalToolRequestedEvent.ExternalToolRequestedEventData("request-1", "test-session-id",
+ "tool-call-1", "blocked_tool", null, Map.of(), null, null, null));
+ dispatchEvent(requested);
+ assertTrue(started.await(1, TimeUnit.SECONDS));
+
+ var completed = new ExternalToolCompletedEvent();
+ completed.setData(new ExternalToolCompletedEvent.ExternalToolCompletedEventData("request-1"));
+ dispatchEvent(completed);
+
+ assertThrows(CancellationException.class, () -> toolFuture.get(1, TimeUnit.SECONDS));
+ }
+
+ @Test
+ void testCloseCancelsBlockedExternalTool() throws Exception {
+ var rpc = mock(JsonRpcClient.class);
+ when(rpc.invoke(eq("session.detach"), any(), eq(CopilotSession.SessionDetachResponse.class)))
+ .thenReturn(CompletableFuture.completedFuture(new CopilotSession.SessionDetachResponse(true, null)));
+ session = createTestSession(rpc);
+ var toolFuture = new CompletableFuture();
+ var started = new CountDownLatch(1);
+ session.registerTools(List.of(ToolDefinition.create("blocked_tool", "Blocks", Map.of(), invocation -> {
+ started.countDown();
+ return toolFuture;
+ })));
+
+ var requested = new ExternalToolRequestedEvent();
+ requested.setData(new ExternalToolRequestedEvent.ExternalToolRequestedEventData("request-close",
+ "test-session-id", "tool-call-close", "blocked_tool", null, Map.of(), null, null, null));
+ dispatchEvent(requested);
+ assertTrue(started.await(1, TimeUnit.SECONDS));
+
+ session.close();
+
+ assertThrows(CancellationException.class, () -> toolFuture.get(1, TimeUnit.SECONDS));
+ }
+
+ @Test
+ void testExternalToolCompletedDoesNotBlockOnSynchronousHandler() throws Exception {
+ var started = new CountDownLatch(1);
+ var release = new CountDownLatch(1);
+ session.registerTools(
+ List.of(ToolDefinition.create("blocked_tool", "Blocks synchronously", Map.of(), invocation -> {
+ started.countDown();
+ try {
+ release.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return CompletableFuture.completedFuture("done");
+ })));
+
+ var requested = new ExternalToolRequestedEvent();
+ requested.setData(new ExternalToolRequestedEvent.ExternalToolRequestedEventData("request-sync",
+ "test-session-id", "tool-call-sync", "blocked_tool", null, Map.of(), null, null, null));
+ dispatchEvent(requested);
+ assertTrue(started.await(1, TimeUnit.SECONDS));
+
+ var completed = new ExternalToolCompletedEvent();
+ completed.setData(new ExternalToolCompletedEvent.ExternalToolCompletedEventData("request-sync"));
+ try {
+ assertTimeoutPreemptively(Duration.ofSeconds(1), () -> dispatchEvent(completed));
+ } finally {
+ release.countDown();
+ }
+ }
+
+ @Test
+ void testToolSearchRunsWhenMetadataResultIsNull() throws Exception {
+ var rpc = mock(JsonRpcClient.class);
+ CompletableFuture metadata = CompletableFuture.completedFuture(null);
+ when(rpc.invoke(eq("session.tools.getCurrentMetadata"), any(), eq(SessionToolsGetCurrentMetadataResult.class)))
+ .thenReturn(metadata);
+ session = createTestSession(rpc);
+ var invoked = new CountDownLatch(1);
+ session.registerTools(List.of(ToolDefinition.create("tool_search_tool", "Searches", Map.of(), invocation -> {
+ invoked.countDown();
+ return new CompletableFuture<>();
+ })));
+
+ var requested = new ExternalToolRequestedEvent();
+ requested.setData(new ExternalToolRequestedEvent.ExternalToolRequestedEventData("request-search",
+ "test-session-id", "tool-call-search", "tool_search_tool", null, Map.of(), null, null, null));
+ dispatchEvent(requested);
+
+ assertTrue(invoked.await(1, TimeUnit.SECONDS));
+ }
+
@Test
void testTypedEventHandler() {
var receivedMessages = new ArrayList();
@@ -96,8 +204,8 @@ void testSendAndWaitSkipsAutopilotContinuationIdle() throws Exception {
var rpc = mock(JsonRpcClient.class);
when(rpc.invoke(eq("session.send"), any(), eq(SendMessageResponse.class)))
.thenReturn(CompletableFuture.completedFuture(new SendMessageResponse("message-1")));
- when(rpc.invoke(eq("session.destroy"), any(), eq(Void.class)))
- .thenReturn(CompletableFuture.completedFuture(null));
+ when(rpc.invoke(eq("session.detach"), any(), eq(CopilotSession.SessionDetachResponse.class)))
+ .thenReturn(CompletableFuture.completedFuture(new CopilotSession.SessionDetachResponse(true, null)));
session = new CopilotSession("test-session-id", rpc);
try {
diff --git a/java/sdk/src/test/java/com/github/copilot/TestUtil.java b/java/sdk/src/test/java/com/github/copilot/TestUtil.java
index 23bb53e493..8e90c2f48f 100644
--- a/java/sdk/src/test/java/com/github/copilot/TestUtil.java
+++ b/java/sdk/src/test/java/com/github/copilot/TestUtil.java
@@ -5,9 +5,12 @@
package com.github.copilot;
import java.io.BufferedReader;
+import java.io.File;
import java.io.InputStreamReader;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.regex.Pattern;
/**
* Shared test utilities for locating the Copilot CLI binary and other
@@ -37,10 +40,9 @@ public static String tempPath(String filename) {
* Resolution order:
*
*
Use the {@code COPILOT_CLI_PATH} environment variable when set.
+ *
Prepare the release pinned by {@code nodejs/package.json}.
*
Otherwise search the system PATH using {@code where.exe} (Windows) or
* {@code which} (Linux/macOS).
- *
Walk parent directories looking for
- * {@code nodejs/node_modules/@github/copilot/npm-loader.js}.
*
*
*
@@ -60,43 +62,54 @@ static String findCliPath() {
return envPath;
}
- String copilotInPath = findCopilotInPath();
- if (copilotInPath != null) {
- return copilotInPath;
- }
-
- // Walk parent directories looking for the CLI in the test harness or nodejs
- // installation. Mirrors the resolution order in E2ETestContext.getCliPath().
- String os = System.getProperty("os.name").toLowerCase();
- String arch = System.getProperty("os.arch").toLowerCase();
- String platform = os.contains("mac") ? "darwin" : os.contains("win") ? "win32" : "linux";
- String cpuArch = arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64";
- String binaryName = os.contains("win") ? "copilot.exe" : "copilot";
-
Path current = Paths.get(System.getProperty("user.dir"));
while (current != null) {
- // Test harness platform-specific binary
- Path platformBinary = current.resolve(
- "test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/" + binaryName);
- if (platformBinary.toFile().exists()) {
- return platformBinary.toString();
+ if (current.resolve("nodejs/package.json").toFile().exists()) {
+ try {
+ return preparePinnedCli(current);
+ } catch (Exception preparationFailed) {
+ break;
+ }
}
+ current = current.getParent();
+ }
- // Test harness npm-loader.js
- Path npmLoader = current.resolve("test/harness/node_modules/@github/copilot/npm-loader.js");
- if (npmLoader.toFile().exists()) {
- return npmLoader.toString();
- }
+ return findCopilotInPath();
+ }
- // nodejs installation (thin loader; resolves the platform-specific
- // CLI package internally)
- Path cliPath = current.resolve("nodejs/node_modules/@github/copilot/npm-loader.js");
- if (cliPath.toFile().exists()) {
- return cliPath.toString();
- }
- current = current.getParent();
+ static String preparePinnedCli(Path repoRoot) throws Exception {
+ var nodePath = findExecutableInPath("node");
+ if (nodePath == null) {
+ throw new IllegalStateException("Node.js was not found in PATH");
+ }
+ var process = new ProcessBuilder(nodePath, "node_modules/tsx/dist/cli.mjs", "scripts/prepare-runtime.ts",
+ "--print-path").directory(repoRoot.resolve("nodejs").toFile()).redirectErrorStream(true).start();
+ String output;
+ try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ output = reader.lines().reduce((first, second) -> second).orElse("").trim();
+ }
+ int exitCode = process.waitFor();
+ if (exitCode != 0 || output.isEmpty()) {
+ throw new IllegalStateException("Failed to prepare the pinned Copilot CLI: " + output);
+ }
+ return output;
+ }
+
+ private static String findExecutableInPath(String name) {
+ var pathValue = System.getenv("PATH");
+ if (pathValue == null || pathValue.isEmpty()) {
+ return null;
}
+ var windows = System.getProperty("os.name").toLowerCase().contains("win");
+ var fileName = windows ? name + ".exe" : name;
+ for (var directory : pathValue.split(Pattern.quote(File.pathSeparator))) {
+ var unquotedDirectory = directory.replaceAll("^\"|\"$", "");
+ var candidate = Paths.get(unquotedDirectory, fileName).toAbsolutePath().normalize();
+ if (Files.isRegularFile(candidate) && (windows || Files.isExecutable(candidate))) {
+ return candidate.toString();
+ }
+ }
return null;
}
diff --git a/java/sdk/src/test/java/com/github/copilot/TimeoutEdgeCaseTest.java b/java/sdk/src/test/java/com/github/copilot/TimeoutEdgeCaseTest.java
index 17e1851bb4..01530bc25f 100644
--- a/java/sdk/src/test/java/com/github/copilot/TimeoutEdgeCaseTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/TimeoutEdgeCaseTest.java
@@ -10,7 +10,11 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
import java.net.Socket;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.Test;
@@ -34,29 +38,46 @@
public class TimeoutEdgeCaseTest {
/**
- * Creates a {@link JsonRpcClient} whose {@code invoke()} returns futures that
- * never complete. The reader thread blocks forever on the input stream, and
- * writes go to a no-op output stream.
+ * Creates a {@link JsonRpcClient} whose prompt requests never complete but
+ * whose cleanup detach request succeeds.
*/
private JsonRpcClient createHangingRpcClient() throws Exception {
- InputStream blockingInput = new InputStream() {
+ PipedInputStream input = new PipedInputStream();
+ PipedOutputStream responseWriter = new PipedOutputStream(input);
+ OutputStream sinkOutput = new ByteArrayOutputStream() {
@Override
- public int read() throws IOException {
+ public synchronized void flush() throws IOException {
+ super.flush();
+ // Each JsonRpcClient.sendMessage() call writes exactly one
+ // complete message before calling flush(), so the buffer must
+ // be cleared after every flush; otherwise a later request's
+ // "id" lookup can match a stale, already-processed message
+ // still sitting in the buffer.
try {
- Thread.sleep(Long.MAX_VALUE);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return -1;
+ String request = toString(StandardCharsets.UTF_8);
+ if (!request.contains("\"method\":\"session.detach\"")) {
+ return;
+ }
+
+ int idIndex = request.indexOf("\"id\":");
+ int idEnd = request.indexOf(",", idIndex);
+ String id = request.substring(idIndex + "\"id\":".length(), idEnd);
+ String response = "{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"result\":{\"success\":true}}";
+ byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8);
+ responseWriter.write(
+ ("Content-Length: " + responseBytes.length + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
+ responseWriter.write(responseBytes);
+ responseWriter.flush();
+ } finally {
+ reset();
}
- return -1;
}
};
- ByteArrayOutputStream sinkOutput = new ByteArrayOutputStream();
var ctor = JsonRpcClient.class.getDeclaredConstructor(InputStream.class, java.io.OutputStream.class,
Socket.class, Process.class);
ctor.setAccessible(true);
- return (JsonRpcClient) ctor.newInstance(blockingInput, sinkOutput, null, null);
+ return (JsonRpcClient) ctor.newInstance(input, sinkOutput, null, null);
}
/**
@@ -64,7 +85,7 @@ public int read() throws IOException {
* completed by a stale timeout.
*