diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..6912dbc77
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* edburns@github.com
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
new file mode 100644
index 000000000..9e5a9238f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -0,0 +1,41 @@
+name: Bug
+description: File a bug report
+title: "[BUG]: "
+labels: ["Type: Bug", "Status: Triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: What did you do? What happened? What did you expect to happen?
+ placeholder: Put your description of the bug here.
+ validations:
+ required: true
+ - type: textarea
+ id: versions
+ attributes:
+ label: Versions
+ description: What versions of the relevant software are you running?
+ placeholder: copilot-sdk-java v1.0.0, Java 17.0.10, Maven 3.9.6, Copilot CLI v1.0.0
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ Please check your logs before submission to ensure sensitive information is redacted.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml
new file mode 100644
index 000000000..7f049a2ed
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/documentation.yml
@@ -0,0 +1,41 @@
+name: Documentation
+description: Update or add documentation
+title: "[DOCS]: "
+labels: ["Type: Documentation", "Status: Triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill this out!
+ - type: textarea
+ id: describe-need
+ attributes:
+ label: Describe the need
+ description: What do you wish was different about our docs?
+ placeholder: Describe the need for documentation updates here.
+ validations:
+ required: true
+ - type: input
+ id: sdk_version
+ attributes:
+ label: SDK Version
+ description: Do these docs apply to a specific SDK version?
+ placeholder: copilot-sdk-java v1.0.0
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ Please check your logs before submission to ensure sensitive information is redacted.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
new file mode 100644
index 000000000..377cd12ab
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -0,0 +1,41 @@
+name: Feature
+description: Suggest an idea for a new feature or enhancement
+title: "[FEAT]: "
+labels: ["Type: Feature", "Status: Triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill this out!
+ - type: textarea
+ id: describe-need
+ attributes:
+ label: Describe the need
+ description: What do you want to happen? What problem are you trying to solve?
+ placeholder: Describe the need for the feature.
+ validations:
+ required: true
+ - type: input
+ id: sdk_version
+ attributes:
+ label: SDK Version
+ description: Does this feature suggestion apply to a specific SDK version?
+ placeholder: copilot-sdk-java v1.0.0
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ Please check your logs before submission to ensure sensitive information is redacted.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/maintenance.yml b/.github/ISSUE_TEMPLATE/maintenance.yml
new file mode 100644
index 000000000..de21996db
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/maintenance.yml
@@ -0,0 +1,41 @@
+name: Maintenance
+description: Dependencies, cleanup, refactoring, reworking of code
+title: "[MAINT]: "
+labels: ["Type: Maintenance", "Status: Triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill this out!
+ - type: textarea
+ id: describe-need
+ attributes:
+ label: Describe the need
+ description: What do you want to happen?
+ placeholder: Describe the maintenance need here.
+ validations:
+ required: true
+ - type: input
+ id: sdk_version
+ attributes:
+ label: SDK Version
+ description: Does this maintenance apply to a specific SDK version?
+ placeholder: copilot-sdk-java v1.0.0
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: |
+ Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ Please check your logs before submission to ensure sensitive information is redacted.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json
index 36881a0f5..d81867b96 100644
--- a/.github/aw/actions-lock.json
+++ b/.github/aw/actions-lock.json
@@ -14,6 +14,11 @@
"repo": "actions/setup-node",
"version": "v4",
"sha": "49933ea5288caeca8642d1e84afbd3f7d6820020"
+ },
+ "github/gh-aw/actions/setup@v0.51.6": {
+ "repo": "github/gh-aw/actions/setup",
+ "version": "v0.51.6",
+ "sha": "33cd6c7f1fee588654ef19def2e6a4174be66197"
}
}
}
diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
index 388fa4643..8ef59f460 100644
--- a/.github/badges/jacoco.svg
+++ b/.github/badges/jacoco.svg
@@ -6,13 +6,13 @@
-
+ coveragecoverage
- 91%
- 91%
+ 87.5%
+ 87.5%
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index bc28a29fb..7112a3f51 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -124,7 +124,7 @@ session.createSession(new SessionConfig()
Sessions emit typed events via `session.on()`:
```java
-session.on(AssistantMessageEvent.class, msg -> System.out.println(msg.getData().getContent()));
+session.on(AssistantMessageEvent.class, msg -> System.out.println(msg.getData().content()));
session.on(SessionIdleEvent.class, idle -> done.complete(null));
```
diff --git a/.github/prompts/agentic-merge-upstream.prompt.md b/.github/prompts/agentic-merge-upstream.prompt.md
index 8db8e8446..c1d2c54a0 100644
--- a/.github/prompts/agentic-merge-upstream.prompt.md
+++ b/.github/prompts/agentic-merge-upstream.prompt.md
@@ -31,15 +31,18 @@ All scripts write/read a `.merge-env` file (git-ignored) to share state (branch
1. Run `./.github/scripts/upstream-sync/merge-upstream-start.sh` (creates branch, clones upstream, shows summary)
2. Run `./.github/scripts/upstream-sync/merge-upstream-diff.sh` (analyze changes)
3. Update README with minimum CLI version requirement
-4. Port changes to Java SDK (commit as you go)
-5. Run `./.github/scripts/build/format-and-test.sh` frequently while porting
-6. Update documentation
-7. Run `./.github/scripts/upstream-sync/merge-upstream-finish.sh` (final test + push)
-8. Finalize Pull Request (see note below about coding agent vs. manual workflow)
+4. Identify upstream changes to port
+5. Apply changes to Java SDK (commit as you go)
+6. Port/adjust tests from upstream changes
+7. Run `./.github/scripts/build/format-and-test.sh` frequently while porting
+8. Build the package
+9. Update documentation (**required for every user-facing upstream change**)
+10. Run `./.github/scripts/upstream-sync/merge-upstream-finish.sh` (final test + push) and finalize Pull Request (see note below about coding agent vs. manual workflow)
+11. Perform final review before handing off
---
-## Steps 1-2: Initialize and Analyze
+## Step 1: Initialize Upstream Sync
Run the start script to create a branch, update the CLI, clone the upstream repo, and see a summary of new commits:
@@ -53,7 +56,9 @@ This writes a `.merge-env` file used by the other scripts. It outputs:
- The upstream dir path
- A short log of upstream commits since `.lastmerge`
-Then run the diff script for a detailed breakdown by area:
+## Step 2: Analyze Upstream Changes
+
+Run the diff script for a detailed breakdown by area:
```bash
./.github/scripts/upstream-sync/merge-upstream-diff.sh # stat only
@@ -104,7 +109,7 @@ For each change in the upstream diff, determine:
> **⚠️ Important:** When adding new documentation pages, always update `src/site/site.xml` to include them in the navigation menu.
-## Step 7: Apply Changes to Java SDK
+## Step 5: Apply Changes to Java SDK
When porting changes:
@@ -168,7 +173,7 @@ Follow the existing Java SDK patterns:
- **Match the style of surrounding code** - Consistency with existing code is more important than upstream patterns
- **Prefer existing abstractions** - If the Java SDK already solves a problem differently than .NET, keep the Java approach
-## Step 7.5: Port Tests
+## Step 6: Port Tests
After porting implementation changes, **always check for new or updated tests** in the upstream repository:
@@ -215,7 +220,7 @@ If tests fail with errors like `TypeError: Cannot read properties of undefined`,
Commit tests separately or together with their corresponding implementation changes.
-## Step 8: Format and Run Tests
+## Step 7: Format and Run Tests
After applying changes, use the convenience script:
@@ -246,7 +251,7 @@ Or for quicker iteration during porting:
- **Null handling**: Add null checks where C# had nullable types
- **JSON serialization**: Verify Jackson annotations are correct
-## Step 9: Build the Package
+## Step 8: Build the Package
Once tests pass, build the complete package:
@@ -259,9 +264,11 @@ Verify:
- No warnings (if possible)
- JAR file is generated in `target/`
-## Step 10: Update Documentation
+## Step 9: Update Documentation
**Documentation is critical for new features.** Every new feature ported from upstream must be documented before the merge is complete.
+Review and complete this documentation checklist before proceeding to Step 10.
+If you determine no docs changes are needed, document that decision and rationale in the PR body under a clear heading (for example, `Documentation Impact`).
### Documentation Checklist
@@ -316,7 +323,7 @@ Ensure consistency across all documentation files:
- Code examples should use the same patterns and be tested
- Links to Javadoc should use correct paths (`apidocs/...`)
-## Steps 11-12: Finish, Push, and Finalize Pull Request
+## Step 10: Finish, Push, and Finalize Pull Request
Run the finish script which updates `.lastmerge`, runs a final build, and pushes the branch:
@@ -378,7 +385,7 @@ Ports changes from the official Copilot SDK ([github/copilot-sdk](https://github
- Code formatted with Spotless
```
-## Step 13: Final Review
+## Step 11: Final Review
Before finishing:
@@ -411,6 +418,7 @@ Before finishing:
- [ ] `src/site/markdown/documentation.md` updated for new basic usage
- [ ] `src/site/markdown/advanced.md` updated for new advanced features
- [ ] Javadoc added/updated for new public APIs
+- [ ] If no documentation files were changed for user-facing upstream changes, PR body explicitly explains why documentation changes were not needed
- [ ] `src/site/site.xml` updated if new documentation pages were added
- [ ] `.lastmerge` file updated with new commit hash
- [ ] Branch pushed to remote
@@ -429,4 +437,3 @@ Before finishing:
- Uses JUnit 5 for testing
- **Java SDK design decisions take precedence over upstream patterns**
- **Adapt upstream changes to fit Java idioms, not the other way around**
-
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..8993ca146
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,29 @@
+
+
+Resolves #ISSUE_NUMBER
+
+----
+
+### Before the change?
+
+
+*
+
+### After the change?
+
+
+*
+
+### Pull request checklist
+- [ ] Tests for the changes have been added (for bug fixes / features)
+- [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features)
+- [ ] `mvn spotless:apply` has been run to format the code
+- [ ] `mvn clean verify` passes locally
+
+### Does this introduce a breaking change?
+
+
+- [ ] Yes
+- [ ] No
+
+----
diff --git a/.github/scripts/build/format-and-test.sh b/.github/scripts/build/format-and-test.sh
index 6de3972f3..c827813bb 100755
--- a/.github/scripts/build/format-and-test.sh
+++ b/.github/scripts/build/format-and-test.sh
@@ -13,7 +13,7 @@
# ──────────────────────────────────────────────────────────────
set -euo pipefail
-ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
+ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
cd "$ROOT_DIR"
FORMAT=true
diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml
new file mode 100644
index 000000000..bb7a31d5d
--- /dev/null
+++ b/.github/workflows/agentics-maintenance.yml
@@ -0,0 +1,82 @@
+#
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.51.6). DO NOT EDIT.
+#
+# 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/
+#
+# Alternative regeneration methods:
+# make recompile
+#
+# Or use the gh-aw CLI directly:
+# ./gh-aw compile --validate --verbose
+#
+# The workflow is generated when any workflow uses the 'expires' field
+# in create-discussions, create-issues, or create-pull-request safe-outputs configuration.
+# Schedule frequency is automatically determined by the shortest expiration time.
+#
+name: Agentic Maintenance
+
+on:
+ schedule:
+ - cron: "37 0 * * *" # Daily (based on minimum expires: 6 days)
+ workflow_dispatch:
+
+permissions: {}
+
+jobs:
+ close-expired-entities:
+ if: ${{ !github.event.repository.fork }}
+ runs-on: ubuntu-slim
+ permissions:
+ discussions: write
+ issues: write
+ pull-requests: write
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6
+ with:
+ destination: /opt/gh-aw/actions
+
+ - name: Close expired discussions
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/close_expired_discussions.cjs');
+ await main();
+
+ - name: Close expired issues
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/close_expired_issues.cjs');
+ await main();
+
+ - name: Close expired pull requests
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/close_expired_pull_requests.cjs');
+ await main();
diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml
index ea5ee4975..5596cbd11 100644
--- a/.github/workflows/publish-maven.yml
+++ b/.github/workflows/publish-maven.yml
@@ -59,8 +59,8 @@ jobs:
server-id: central
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
- gpg-passphrase: MAVEN_GPG_PASSPHRASE
+ gpg-private-key: ${{ secrets.GPG_SECRET_KEY }}
+ gpg-passphrase: GPG_PASSPHRASE
- name: Determine versions
id: versions
@@ -141,7 +141,7 @@ jobs:
env:
MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
- MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Perform Release and Deploy to Maven Central
run: |
@@ -151,7 +151,7 @@ jobs:
env:
MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
- MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Rollback documentation commit on failure
if: failure() && steps.update-docs.outputs.docs_commit_sha != ''
@@ -177,16 +177,28 @@ jobs:
VERSION="${{ needs.publish-maven.outputs.version }}"
GROUP_ID="io.github.copilot-community-sdk"
ARTIFACT_ID="copilot-sdk"
+ CURRENT_TAG="v${VERSION}"
+
+ if gh release view "${CURRENT_TAG}" >/dev/null 2>&1; then
+ echo "Release ${CURRENT_TAG} already exists. Skipping creation."
+ exit 0
+ fi
# Generate release notes from template
export VERSION GROUP_ID ARTIFACT_ID
RELEASE_NOTES=$(envsubst < .github/workflows/notes.template)
# Get the previous tag for generating notes
- PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
+ PREV_TAG=$(git tag --list 'v*' --sort=-version:refname \
+ | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
+ | grep -Fxv "${CURRENT_TAG}" \
+ | head -n 1)
+
+ echo "Current tag: ${CURRENT_TAG}"
+ echo "Previous tag: ${PREV_TAG}"
# Build the gh release command
- GH_ARGS=("v${VERSION}")
+ GH_ARGS=("${CURRENT_TAG}")
GH_ARGS+=("--title" "Copilot Java SDK ${VERSION}")
GH_ARGS+=("--notes" "${RELEASE_NOTES}")
GH_ARGS+=("--generate-notes")
diff --git a/.github/workflows/weekly-upstream-sync.lock.yml b/.github/workflows/weekly-upstream-sync.lock.yml
index 163600e1e..cfd1342fa 100644
--- a/.github/workflows/weekly-upstream-sync.lock.yml
+++ b/.github/workflows/weekly-upstream-sync.lock.yml
@@ -13,16 +13,18 @@
# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
#
-# This file was automatically generated by gh-aw (v0.43.2). DO NOT EDIT.
+# This file was automatically generated by gh-aw (v0.51.6). DO NOT EDIT.
#
# To update this file, edit the corresponding .md file and run:
# gh aw compile
-# For more information: https://github.com/github/gh-aw/blob/main/.github/aw/github-agentic-workflows.md
+# Not all edits will cause changes to this file.
+#
+# For more information: https://github.github.com/gh-aw/introduction/overview/
#
# Weekly upstream sync workflow. Checks for new commits in the official
# Copilot SDK (github/copilot-sdk) and assigns to Copilot to port changes.
#
-# frontmatter-hash: 27b0f4284357c75008ce6e8d1d772fe9871a3548be6a6a4996125b6ca980cc2e
+# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"fc14b09206c7aeafcd52c843adce996a1c14cf15875f9b647ef71f631b3b296e","compiler_version":"v0.51.6"}
name: "Weekly Upstream Sync Agentic Workflow"
"on":
@@ -46,11 +48,50 @@ jobs:
outputs:
comment_id: ""
comment_repo: ""
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@v0.43.2
+ uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6
with:
destination: /opt/gh-aw/actions
+ - 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 || '' }}
+ GH_AW_INFO_VERSION: ""
+ GH_AW_INFO_AGENT_VERSION: "0.0.420"
+ GH_AW_INFO_CLI_VERSION: "v0.51.6"
+ GH_AW_INFO_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow"
+ 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.23.0"
+ GH_AW_INFO_AWMG_VERSION: ""
+ GH_AW_INFO_FIREWALL_TYPE: "squid"
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { main } = require('/opt/gh-aw/actions/generate_aw_info.cjs');
+ await main(core, context);
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Checkout .github and .agents folders
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ sparse-checkout: |
+ .github
+ .agents
+ sparse-checkout-cone-mode: true
+ fetch-depth: 1
+ persist-credentials: false
- name: Check workflow file timestamps
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
@@ -61,6 +102,128 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs');
await main();
+ - name: Create prompt with built-in context
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ run: |
+ bash /opt/gh-aw/actions/create_prompt_first.sh
+ {
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat "/opt/gh-aw/prompts/xpia.md"
+ cat "/opt/gh-aw/prompts/temp_folder_prompt.md"
+ cat "/opt/gh-aw/prompts/markdown.md"
+ cat "/opt/gh-aw/prompts/safe_outputs_prompt.md"
+ cat << 'GH_AW_PROMPT_EOF'
+
+ Tools: add_comment, create_issue, close_issue, assign_to_agent, missing_tool, missing_data, noop
+
+
+ The following GitHub context information is available for this workflow:
+ {{#if __GH_AW_GITHUB_ACTOR__ }}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_REPOSITORY__ }}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_WORKSPACE__ }}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
+ - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
+ - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
+ - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
+ - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_RUN_ID__ }}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ {{#runtime-import .github/workflows/weekly-upstream-sync.md}}
+ GH_AW_PROMPT_EOF
+ } > "$GH_AW_PROMPT"
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
+ await main();
+ - name: Substitute placeholders
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+
+ const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
+
+ // Call the substitution function
+ return await substitutePlaceholders({
+ file: process.env.GH_AW_PROMPT,
+ substitutions: {
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ 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
+ }
+ });
+ - name: Validate prompt placeholders
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
+ - name: Print prompt
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/print_prompt_summary.sh
+ - name: Upload activation artifact
+ if: success()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: activation
+ path: |
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ retention-days: 1
agent:
needs: activation
@@ -80,25 +243,23 @@ jobs:
GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ GH_AW_WORKFLOW_ID_SANITIZED: weeklyupstreamsync
outputs:
checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
+ detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
+ detection_success: ${{ steps.detection_conclusion.outputs.success }}
has_patch: ${{ steps.collect_output.outputs.has_patch }}
- model: ${{ steps.generate_aw_info.outputs.model }}
+ model: ${{ needs.activation.outputs.model }}
output: ${{ steps.collect_output.outputs.output }}
output_types: ${{ steps.collect_output.outputs.output_types }}
- secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@v0.43.2
+ uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6
with:
destination: /opt/gh-aw/actions
- - name: Checkout .github and .agents folders
+ - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
- sparse-checkout: |
- .github
- .agents
- fetch-depth: 1
persist-credentials: false
- name: Create gh-aw temp directory
run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
@@ -109,6 +270,7 @@ jobs:
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
# Re-authenticate git with GitHub token
SERVER_URL_STRIPPED="${SERVER_URL#https://}"
git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
@@ -116,7 +278,7 @@ jobs:
- name: Checkout PR branch
id: checkout-pr
if: |
- github.event.pull_request
+ (github.event.pull_request) || (github.event.issue.pull_request)
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -127,81 +289,31 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
await main();
- - name: Generate agentic run info
- id: generate_aw_info
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const fs = require('fs');
-
- const awInfo = {
- engine_id: "copilot",
- engine_name: "GitHub Copilot CLI",
- model: process.env.GH_AW_MODEL_AGENT_COPILOT || "",
- version: "",
- agent_version: "0.0.405",
- cli_version: "v0.43.2",
- workflow_name: "Weekly Upstream Sync Agentic Workflow",
- experimental: false,
- supports_tools_allowlist: true,
- supports_http_transport: true,
- run_id: context.runId,
- run_number: context.runNumber,
- run_attempt: process.env.GITHUB_RUN_ATTEMPT,
- repository: context.repo.owner + '/' + context.repo.repo,
- ref: context.ref,
- sha: context.sha,
- actor: context.actor,
- event_name: context.eventName,
- staged: false,
- allowed_domains: ["defaults","github"],
- firewall_enabled: true,
- awf_version: "v0.13.12",
- awmg_version: "",
- steps: {
- firewall: "squid"
- },
- created_at: new Date().toISOString()
- };
-
- // Write to /tmp/gh-aw directory to avoid inclusion in PR
- const tmpPath = '/tmp/gh-aw/aw_info.json';
- fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2));
- console.log('Generated aw_info.json at:', tmpPath);
- console.log(JSON.stringify(awInfo, null, 2));
-
- // Set model as output for reuse in other steps/jobs
- core.setOutput('model', awInfo.model);
- - name: Validate COPILOT_GITHUB_TOKEN secret
- id: validate-secret
- run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
- env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Install GitHub Copilot CLI
- run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.420
- name: Install awf binary
- run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.13.12
- - name: Determine automatic lockdown mode for GitHub MCP server
+ run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0
+ - name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
- env:
- TOKEN_CHECK: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- if: env.TOKEN_CHECK != ''
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
with:
script: |
const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
await determineAutomaticLockdown(github, context, core);
- name: Download container images
- run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:v0.0.113 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine
+ run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.6 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine
- name: Write Safe Outputs Config
run: |
mkdir -p /opt/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > /opt/gh-aw/safeoutputs/config.json << 'EOF'
- {"add_comment":{"max":10,"target":"*"},"assign_to_agent":{"default_agent":"copilot","target":"*"},"close_issue":{"max":10,"required_labels":["upstream-sync"]},"create_issue":{"expires":144,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}}
- EOF
- cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF'
+ cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF'
+ {"add_comment":{"max":10,"target":"*"},"assign_to_agent":{"default_agent":"copilot","max":1,"target":"*"},"close_issue":{"max":10,"required_labels":["upstream-sync"],"target":"*"},"create_issue":{"expires":144,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_EOF
+ cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF'
[
{
"description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[upstream-sync] \". Labels [upstream-sync] will be automatically added. Assignees [copilot] will be automatically assigned.",
@@ -220,14 +332,15 @@ jobs:
"type": "array"
},
"parent": {
- "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123def456') from a previously created issue in the same workflow run.",
+ "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from a previously created issue in the same workflow run.",
"type": [
"number",
"string"
]
},
"temporary_id": {
- "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 12 hex characters (e.g., 'aw_abc123def456'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.",
+ "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 3 to 8 alphanumeric characters (e.g., 'aw_abc1', 'aw_Test123'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.",
+ "pattern": "^aw_[A-Za-z0-9]{3,8}$",
"type": "string"
},
"title": {
@@ -244,7 +357,7 @@ jobs:
"name": "create_issue"
},
{
- "description": "Close a GitHub issue with a closing comment. Use this when work is complete, the issue is no longer relevant, or it's a duplicate. The closing comment should explain the resolution or reason for closing. CONSTRAINTS: Maximum 10 issue(s) can be closed. Target: *.",
+ "description": "Close a GitHub issue with a closing comment. You can and should always add a comment when closing an issue to explain the action or provide context. This tool is ONLY for closing issues - use update_issue if you need to change the title, body, labels, or other metadata without closing. Use close_issue when work is complete, the issue is no longer relevant, or it's a duplicate. The closing comment should explain the resolution or reason for closing. If the issue is already closed, a comment will still be posted. CONSTRAINTS: Maximum 10 issue(s) can be closed. Target: *.",
"inputSchema": {
"additionalProperties": false,
"properties": {
@@ -268,16 +381,16 @@ jobs:
"name": "close_issue"
},
{
- "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 10 comment(s) can be added. Target: *.",
+ "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 10 comment(s) can be added. Target: *.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"body": {
- "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation.",
+ "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.",
"type": "string"
},
"item_number": {
- "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool will attempt to resolve the target from the current workflow context (triggering issue, PR, or discussion).",
+ "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers — it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the comment will be silently discarded.",
"type": "number"
}
},
@@ -289,7 +402,7 @@ jobs:
"name": "add_comment"
},
{
- "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\")",
+ "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.",
"inputSchema": {
"additionalProperties": false,
"properties": {
@@ -298,18 +411,22 @@ jobs:
"type": "string"
},
"issue_number": {
- "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). Can also be a temporary_id (e.g., 'aw_abc123def456') from an issue created earlier in the same workflow run. The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.",
+ "description": "Issue number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from an issue created earlier in the same workflow run. The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.",
"type": [
"number",
"string"
]
},
"pull_number": {
- "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.",
+ "description": "Pull request number to assign the Copilot coding agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.",
"type": [
"number",
"string"
]
+ },
+ "pull_request_repo": {
+ "description": "Target repository where the pull request should be created, in 'owner/repo' format. If omitted, the PR will be created in the same repository as the issue. This allows issues and code to live in different repositories. The global pull-request-repo configuration (if set) is automatically allowed; additional repositories must be listed in allowed-pull-request-repos.",
+ "type": "string"
}
},
"type": "object"
@@ -386,8 +503,8 @@ jobs:
"name": "missing_data"
}
]
- EOF
- cat > /opt/gh-aw/safeoutputs/validation.json << 'EOF'
+ GH_AW_SAFE_OUTPUTS_TOOLS_EOF
+ cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF'
{
"add_comment": {
"defaultMax": 1,
@@ -400,6 +517,10 @@ jobs:
},
"item_number": {
"issueOrPRNumber": true
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
}
}
},
@@ -416,6 +537,14 @@ jobs:
},
"pull_number": {
"optionalPositiveInteger": true
+ },
+ "pull_request_repo": {
+ "type": "string",
+ "maxLength": 256
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
}
},
"customValidation": "requiresOneOf:issue_number,pull_number"
@@ -431,6 +560,10 @@ jobs:
},
"issue_number": {
"optionalPositiveInteger": true
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
}
}
},
@@ -467,6 +600,31 @@ jobs:
}
}
},
+ "missing_data": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "context": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "data_type": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "reason": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ }
+ }
+ },
"missing_tool": {
"defaultMax": 20,
"fields": {
@@ -500,7 +658,7 @@ jobs:
}
}
}
- EOF
+ GH_AW_SAFE_OUTPUTS_VALIDATION_EOF
- name: Generate Safe Outputs MCP Server Config
id: safe-outputs-config
run: |
@@ -539,7 +697,7 @@ jobs:
bash /opt/gh-aw/actions/start_safe_outputs_server.sh
- - name: Start MCP gateway
+ - name: Start MCP Gateway
id: start-mcp-gateway
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
@@ -559,18 +717,19 @@ jobs:
export MCP_GATEWAY_API_KEY
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
+ export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -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_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_LOCKDOWN -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 GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -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 ghcr.io/github/gh-aw-mcpg:v0.0.113'
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/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 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_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_LOCKDOWN -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 GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -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 ghcr.io/github/gh-aw-mcpg:v0.1.6'
mkdir -p /home/runner/.copilot
- cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
+ cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
{
"mcpServers": {
"github": {
"type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v0.30.3",
+ "container": "ghcr.io/github/github-mcp-server:v0.31.0",
"env": {
"GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN",
"GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
@@ -593,131 +752,12 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- MCPCONFIG_EOF
- - name: Generate workflow overview
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
- await generateWorkflowOverview(core);
- - name: Create prompt with built-in context
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
- GH_AW_GITHUB_ACTOR: ${{ github.actor }}
- GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
- GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
- GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
- GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
- run: |
- bash /opt/gh-aw/actions/create_prompt_first.sh
- cat << 'PROMPT_EOF' > "$GH_AW_PROMPT"
-
- PROMPT_EOF
- cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT"
- cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
-
- GitHub API Access Instructions
-
- The gh CLI is NOT authenticated. Do NOT use gh commands for GitHub operations.
-
-
- To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls.
-
- Discover available tools from the safeoutputs MCP server.
-
- **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped.
-
- **Note**: If you made no other safe output tool calls during this workflow execution, call the "noop" tool to provide a status message indicating completion or that no actions were needed.
-
-
-
- The following GitHub context information is available for this workflow:
- {{#if __GH_AW_GITHUB_ACTOR__ }}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if __GH_AW_GITHUB_REPOSITORY__ }}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if __GH_AW_GITHUB_WORKSPACE__ }}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
- - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
- - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
- - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
- - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
- {{/if}}
- {{#if __GH_AW_GITHUB_RUN_ID__ }}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- PROMPT_EOF
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
-
- PROMPT_EOF
- cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
- {{#runtime-import .github/workflows/weekly-upstream-sync.md}}
- PROMPT_EOF
- - name: Substitute placeholders
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_GITHUB_ACTOR: ${{ github.actor }}
- GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
- GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
- GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
- GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ GH_AW_MCP_CONFIG_EOF
+ - name: Download activation artifact
+ uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
- script: |
- const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
-
- // Call the substitution function
- return await substitutePlaceholders({
- file: process.env.GH_AW_PROMPT,
- substitutions: {
- GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
- GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
- 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
- }
- });
- - name: Interpolate variables and render templates
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
- await main();
- - name: Validate prompt placeholders
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
- - name: Print prompt
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- run: bash /opt/gh-aw/actions/print_prompt_summary.sh
+ name: activation
+ path: /tmp/gh-aw
- name: Clean git credentials
run: bash /opt/gh-aw/actions/clean_git_credentials.sh
- name: Execute GitHub Copilot CLI
@@ -726,9 +766,9 @@ jobs:
timeout-minutes: 20
run: |
set -o pipefail
- sudo -E awf --enable-chroot --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-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,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.13.12 --skip-pull \
- -- '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' \
- 2>&1 | tee /tmp/gh-aw/agent-stdio.log
+ # shellcheck disable=SC1003
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-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,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
env:
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
@@ -736,8 +776,11 @@ jobs:
GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GITHUB_API_URL: ${{ github.api_url }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
GITHUB_WORKSPACE: ${{ github.workspace }}
XDG_CONFIG_HOME: /home/runner
@@ -748,6 +791,7 @@ jobs:
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
# Re-authenticate git with GitHub token
SERVER_URL_STRIPPED="${SERVER_URL#https://}"
git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
@@ -769,7 +813,7 @@ jobs:
else
echo "No session-state directory found at $SESSION_STATE_DIR"
fi
- - name: Stop MCP gateway
+ - name: Stop MCP Gateway
if: always()
continue-on-error: true
env:
@@ -795,13 +839,14 @@ jobs:
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Safe Outputs
if: always()
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: safe-output
path: ${{ env.GH_AW_SAFE_OUTPUTS }}
if-no-files-found: warn
- name: Ingest agent output
id: collect_output
+ if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
@@ -816,13 +861,13 @@ jobs:
await main();
- name: Upload sanitized agent output
if: always() && env.GH_AW_AGENT_OUTPUT
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: agent-output
path: ${{ env.GH_AW_AGENT_OUTPUT }}
if-no-files-found: warn
- name: Upload engine output files
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: agent_outputs
path: |
@@ -840,7 +885,7 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs');
await main();
- - name: Parse MCP gateway logs for step summary
+ - name: Parse MCP Gateway logs for step summary
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
@@ -858,27 +903,146 @@ jobs:
# Fix permissions on firewall logs so they can be uploaded as artifacts
# AWF runs with sudo, creating files owned by root
sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
- awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
+ if command -v awf &> /dev/null; then
+ awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ else
+ echo 'AWF binary not installed, skipping firewall log summary'
+ fi
- name: Upload agent artifacts
if: always()
continue-on-error: true
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: agent-artifacts
path: |
/tmp/gh-aw/aw-prompts/prompt.txt
- /tmp/gh-aw/aw_info.json
/tmp/gh-aw/mcp-logs/
/tmp/gh-aw/sandbox/firewall/logs/
/tmp/gh-aw/agent-stdio.log
/tmp/gh-aw/agent/
if-no-files-found: ignore
+ # --- Threat Detection (inline) ---
+ - name: Check if detection needed
+ id: detection_guard
+ if: always()
+ env:
+ OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }}
+ HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
+ run: |
+ if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
+ echo "run_detection=true" >> "$GITHUB_OUTPUT"
+ echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
+ else
+ echo "run_detection=false" >> "$GITHUB_OUTPUT"
+ echo "Detection skipped: no agent outputs or patches to analyze"
+ fi
+ - name: Clear MCP configuration for detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
+ rm -f /home/runner/.copilot/mcp-config.json
+ rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
+ - name: Prepare threat detection files
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
+ cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
+ 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
+ echo "Prepared threat detection files:"
+ ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ - name: Setup threat detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow"
+ WORKFLOW_DESCRIPTION: "Weekly upstream sync workflow. Checks for new commits in the official\nCopilot SDK (github/copilot-sdk) and assigns to Copilot to port changes."
+ HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/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: Execute GitHub Copilot CLI
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ id: detection_agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool shell(cat)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(head)
+ # --allow-tool shell(jq)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(wc)
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ # shellcheck disable=SC1003
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Parse threat detection results
+ id: parse_detection_results
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
+ await main();
+ - name: Upload threat detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: threat-detection.log
+ path: /tmp/gh-aw/threat-detection/detection.log
+ if-no-files-found: ignore
+ - name: Set detection conclusion
+ id: detection_conclusion
+ if: always()
+ env:
+ RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
+ DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }}
+ run: |
+ if [[ "$RUN_DETECTION" != "true" ]]; then
+ echo "conclusion=skipped" >> "$GITHUB_OUTPUT"
+ echo "success=true" >> "$GITHUB_OUTPUT"
+ echo "Detection was not needed, marking as skipped"
+ elif [[ "$DETECTION_SUCCESS" == "true" ]]; then
+ echo "conclusion=success" >> "$GITHUB_OUTPUT"
+ echo "success=true" >> "$GITHUB_OUTPUT"
+ echo "Detection passed successfully"
+ else
+ echo "conclusion=failure" >> "$GITHUB_OUTPUT"
+ echo "success=false" >> "$GITHUB_OUTPUT"
+ echo "Detection found issues"
+ fi
conclusion:
needs:
- activation
- agent
- - detection
- safe_outputs
if: (always()) && (needs.agent.result != 'skipped')
runs-on: ubuntu-slim
@@ -893,23 +1057,12 @@ jobs:
total_count: ${{ steps.missing_tool.outputs.total_count }}
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@v0.43.2
+ uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6
with:
destination: /opt/gh-aw/actions
- - name: Debug job inputs
- env:
- COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
- AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
- AGENT_CONCLUSION: ${{ needs.agent.result }}
- run: |
- echo "Comment ID: $COMMENT_ID"
- echo "Comment Repo: $COMMENT_REPO"
- echo "Agent Output Types: $AGENT_OUTPUT_TYPES"
- echo "Agent Conclusion: $AGENT_CONCLUSION"
- name: Download agent output artifact
continue-on-error: true
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
name: agent-output
path: /tmp/gh-aw/safeoutputs/
@@ -923,7 +1076,7 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
- GH_AW_NOOP_MAX: 1
+ GH_AW_NOOP_MAX: "1"
GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -954,10 +1107,11 @@ 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: "weekly-upstream-sync"
- GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }}
+ GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }}
GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }}
+ GH_AW_GROUP_REPORTS: "false"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -982,131 +1136,10 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs');
await main();
- - name: Update reaction comment with completion status
- id: conclusion
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
- GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
- GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow"
- GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
- GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.result }}
- with:
- github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/notify_comment_error.cjs');
- await main();
-
- detection:
- needs: agent
- if: needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true'
- runs-on: ubuntu-latest
- permissions: {}
- concurrency:
- group: "gh-aw-copilot-${{ github.workflow }}"
- timeout-minutes: 10
- outputs:
- success: ${{ steps.parse_results.outputs.success }}
- steps:
- - name: Setup Scripts
- uses: github/gh-aw/actions/setup@v0.43.2
- with:
- destination: /opt/gh-aw/actions
- - name: Download agent artifacts
- continue-on-error: true
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- with:
- name: agent-artifacts
- path: /tmp/gh-aw/threat-detection/
- - name: Download agent output artifact
- continue-on-error: true
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- with:
- name: agent-output
- path: /tmp/gh-aw/threat-detection/
- - name: Echo agent output types
- env:
- AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
- run: |
- echo "Agent output-types: $AGENT_OUTPUT_TYPES"
- - name: Setup threat detection
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- env:
- WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow"
- WORKFLOW_DESCRIPTION: "Weekly upstream sync workflow. Checks for new commits in the official\nCopilot SDK (github/copilot-sdk) and assigns to Copilot to port changes."
- HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs');
- await main();
- - name: Ensure threat-detection directory and log
- run: |
- mkdir -p /tmp/gh-aw/threat-detection
- touch /tmp/gh-aw/threat-detection/detection.log
- - name: Validate COPILOT_GITHUB_TOKEN secret
- id: validate-secret
- run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
- env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- - name: Install GitHub Copilot CLI
- run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405
- - name: Execute GitHub Copilot CLI
- id: agentic_execution
- # Copilot CLI tool arguments (sorted):
- # --allow-tool shell(cat)
- # --allow-tool shell(grep)
- # --allow-tool shell(head)
- # --allow-tool shell(jq)
- # --allow-tool shell(ls)
- # --allow-tool shell(tail)
- # --allow-tool shell(wc)
- timeout-minutes: 20
- run: |
- set -o pipefail
- COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"
- mkdir -p /tmp/
- mkdir -p /tmp/gh-aw/
- mkdir -p /tmp/gh-aw/agent/
- mkdir -p /tmp/gh-aw/sandbox/agent/logs/
- copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool 'shell(cat)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(jq)' --allow-tool 'shell(ls)' --allow-tool 'shell(tail)' --allow-tool 'shell(wc)' --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$COPILOT_CLI_INSTRUCTION"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"} 2>&1 | tee /tmp/gh-aw/threat-detection/detection.log
- env:
- COPILOT_AGENT_RUNNER_TYPE: STANDALONE
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GITHUB_HEAD_REF: ${{ github.head_ref }}
- GITHUB_REF_NAME: ${{ github.ref_name }}
- GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
- GITHUB_WORKSPACE: ${{ github.workspace }}
- XDG_CONFIG_HOME: /home/runner
- - name: Parse threat detection results
- id: parse_results
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
- with:
- script: |
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
- await main();
- - name: Upload threat detection log
- if: always()
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- with:
- name: threat-detection.log
- path: /tmp/gh-aw/threat-detection/detection.log
- if-no-files-found: ignore
safe_outputs:
- needs:
- - agent
- - detection
- if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.detection.outputs.success == 'true')
+ needs: agent
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true')
runs-on: ubuntu-slim
permissions:
contents: read
@@ -1115,6 +1148,7 @@ jobs:
pull-requests: write
timeout-minutes: 15
env:
+ GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/${{ github.workflow }}"
GH_AW_ENGINE_ID: "copilot"
GH_AW_WORKFLOW_ID: "weekly-upstream-sync"
GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow"
@@ -1122,18 +1156,24 @@ jobs:
assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }}
assign_to_agent_assignment_error_count: ${{ steps.assign_to_agent.outputs.assignment_error_count }}
assign_to_agent_assignment_errors: ${{ steps.assign_to_agent.outputs.assignment_errors }}
+ code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
+ code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
+ comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }}
+ 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 }}
+ 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_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
- uses: github/gh-aw/actions/setup@v0.43.2
+ uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6
with:
destination: /opt/gh-aw/actions
- name: Download agent output artifact
continue-on-error: true
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8
with:
name: agent-output
path: /tmp/gh-aw/safeoutputs/
@@ -1147,6 +1187,9 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ 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,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"close_issue\":{\"max\":10,\"required_labels\":[\"upstream-sync\"],\"target\":\"*\"},\"create_issue\":{\"assignees\":[\"copilot\"],\"expires\":144,\"labels\":[\"upstream-sync\"],\"max\":1,\"title_prefix\":\"[upstream-sync] \"},\"missing_data\":{},\"missing_tool\":{}}"
GH_AW_ASSIGN_COPILOT: "true"
with:
@@ -1168,13 +1211,15 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/assign_copilot_to_created_issues.cjs');
await main();
- - name: Assign To Agent
+ - name: Assign to agent
id: assign_to_agent
if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'assign_to_agent'))
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_AGENT_MAX_COUNT: 1
GH_AW_AGENT_DEFAULT: "copilot"
+ GH_AW_AGENT_DEFAULT_MODEL: "claude-opus-4.6"
GH_AW_AGENT_TARGET: "*"
GH_AW_TEMPORARY_ID_MAP: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
with:
@@ -1184,4 +1229,11 @@ jobs:
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/assign_to_agent.cjs');
await main();
+ - name: Upload safe output items manifest
+ if: always()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: safe-output-items
+ path: /tmp/safe-output-items.jsonl
+ if-no-files-found: warn
diff --git a/.github/workflows/weekly-upstream-sync.md b/.github/workflows/weekly-upstream-sync.md
index 31a445df4..641f29301 100644
--- a/.github/workflows/weekly-upstream-sync.md
+++ b/.github/workflows/weekly-upstream-sync.md
@@ -36,6 +36,7 @@ safe-outputs:
max: 10
assign-to-agent:
name: "copilot"
+ model: "claude-opus-4.6"
target: "*"
noop:
report-as-issue: false
diff --git a/.gitignore b/.gitignore
index a36367133..f6b0681d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,6 @@
target
examples-test/
.merge-env
+blog-copilotsdk/
+.claude/worktrees
+smoke-test
diff --git a/.lastmerge b/.lastmerge
index 6d9098bab..c5649a512 100644
--- a/.lastmerge
+++ b/.lastmerge
@@ -1 +1 @@
-e40d57c86e18b495722adbf42045288c03924342
+062b61c8aa63b9b5d45fa1d7b01723e6660ffa83
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 649255c77..1404ba336 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,62 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
-> **Upstream sync:** [`github/copilot-sdk@e40d57c`](https://github.com/github/copilot-sdk/commit/e40d57c86e18b495722adbf42045288c03924342)
+> **Upstream sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83)
+
+### Notice
+
+- **This repository is now archived.** The official GitHub Copilot SDK for Java is maintained at [github/copilot-sdk-java](https://github.com/github/copilot-sdk-java). No further changes or releases will be made in this repository.
+
+## [1.0.11] - 2026-03-12
+
+> **Upstream sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83)
+### Added
+
+- `CopilotClientOptions.setOnListModels(Supplier>>)` — custom handler for `listModels()` used in BYOK mode to return models from a custom provider instead of querying the CLI (upstream: [`e478657`](https://github.com/github/copilot-sdk/commit/e478657))
+- `SessionConfig.setAgent(String)` — pre-selects a custom agent by name when creating a session (upstream: [`7766b1a`](https://github.com/github/copilot-sdk/commit/7766b1a))
+- `ResumeSessionConfig.setAgent(String)` — pre-selects a custom agent by name when resuming a session (upstream: [`7766b1a`](https://github.com/github/copilot-sdk/commit/7766b1a))
+- `SessionConfig.setOnEvent(Consumer)` — registers an event handler before the `session.create` RPC is issued, ensuring no early events are missed (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+- `ResumeSessionConfig.setOnEvent(Consumer)` — registers an event handler before the `session.resume` RPC is issued (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+- New broadcast session event types (protocol v3): `ExternalToolRequestedEvent` (`external_tool.requested`), `ExternalToolCompletedEvent` (`external_tool.completed`), `PermissionRequestedEvent` (`permission.requested`), `PermissionCompletedEvent` (`permission.completed`), `CommandQueuedEvent` (`command.queued`), `CommandCompletedEvent` (`command.completed`), `ExitPlanModeRequestedEvent` (`exit_plan_mode.requested`), `ExitPlanModeCompletedEvent` (`exit_plan_mode.completed`), `SystemNotificationEvent` (`system.notification`) (upstream: [`1653812`](https://github.com/github/copilot-sdk/commit/1653812), [`396e8b3`](https://github.com/github/copilot-sdk/commit/396e8b3))
+- `CopilotSession.log(String)` and `CopilotSession.log(String, String, Boolean)` — log a message to the session timeline (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7))
+
+### Changed
+
+- **Protocol version bumped to v3.** The SDK now supports CLI servers running v2 or v3 (backward-compatible range). Sessions are now registered in the client's session map *before* the `session.create`/`session.resume` RPC is issued, ensuring broadcast events emitted immediately on session start are never dropped (upstream: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7), [`1653812`](https://github.com/github/copilot-sdk/commit/1653812))
+- In protocol v3, tool calls and permission requests that have a registered handler are now handled automatically via `ExternalToolRequestedEvent` and `PermissionRequestedEvent` broadcast events; results are sent back via `session.tools.handlePendingToolCall` and `session.permissions.handlePendingPermissionRequest` RPC calls (upstream: [`1653812`](https://github.com/github/copilot-sdk/commit/1653812))
+
+## [1.0.10] - 2026-03-03
+
+> **Upstream sync:** [`github/copilot-sdk@dcd86c1`](https://github.com/github/copilot-sdk/commit/dcd86c189501ce1b46b787ca60d90f3f315f3079)
+### Added
+
+- `CopilotSession.setModel(String)` — changes the model for an existing session mid-conversation; the new model takes effect for the next message, and conversation history is preserved (upstream: [`bd98e3a`](https://github.com/github/copilot-sdk/commit/bd98e3a))
+- `ToolDefinition.createOverride(String, String, Map, ToolHandler)` — creates a tool definition that overrides a built-in CLI tool with the same name (upstream: [`f843c80`](https://github.com/github/copilot-sdk/commit/f843c80))
+- `ToolDefinition` record now includes `overridesBuiltInTool` field; when `true`, signals to the CLI that the custom tool intentionally replaces a built-in (upstream: [`f843c80`](https://github.com/github/copilot-sdk/commit/f843c80))
+- `CopilotSession.listAgents()` — lists custom agents available for selection (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `CopilotSession.getCurrentAgent()` — gets the currently selected custom agent (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `CopilotSession.selectAgent(String)` — selects a custom agent for the session (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `CopilotSession.deselectAgent()` — deselects the current custom agent (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `CopilotSession.compact()` — triggers immediate session context compaction (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- `AgentInfo` — new JSON type representing a custom agent with `name`, `displayName`, and `description` (upstream: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb))
+- New event types: `SessionTaskCompleteEvent` (`session.task_complete`), `AssistantStreamingDeltaEvent` (`assistant.streaming_delta`), `SubagentDeselectedEvent` (`subagent.deselected`) (upstream: various commits)
+- `AssistantTurnStartEvent` data now includes `interactionId` field
+- `AssistantMessageEvent` data now includes `interactionId` field
+- `ToolExecutionCompleteEvent` data now includes `model` and `interactionId` fields
+- `SkillInvokedEvent` data now includes `pluginName` and `pluginVersion` fields
+- `AssistantUsageEvent` data now includes `copilotUsage` field with `CopilotUsage` and `TokenDetails` nested types
+- E2E tests for custom tool permission approval and denial flows (upstream: [`388f2f3`](https://github.com/github/copilot-sdk/commit/388f2f3))
+
+### Changed
+
+- **Breaking:** `createSession(SessionConfig)` now requires a non-null `onPermissionRequest` handler; throws `IllegalArgumentException` if not provided (upstream: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4))
+- **Breaking:** `resumeSession(String, ResumeSessionConfig)` now requires a non-null `onPermissionRequest` handler; throws `IllegalArgumentException` if not provided (upstream: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4))
+- **Breaking:** The no-arg `createSession()` and `resumeSession(String)` overloads were removed (upstream: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4))
+- `AssistantMessageDeltaEvent` data: `totalResponseSizeBytes` field moved to new `AssistantStreamingDeltaEvent` (upstream: various)
+
+### Fixed
+
+- Permission checks now also apply to SDK-registered custom tools, invoking the `onPermissionRequest` handler with `kind="custom-tool"` before executing tools (upstream: [`388f2f3`](https://github.com/github/copilot-sdk/commit/388f2f3))
## [1.0.9] - 2026-02-16
@@ -376,7 +431,14 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse`
- Pre-commit hook for Spotless code formatting
- Comprehensive API documentation
-[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.9...HEAD
+[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD
+[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11
+[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD
+[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11
+[1.0.10]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.9...v1.0.10
+[Unreleased]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.11...HEAD
+[1.0.11]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.10...v1.0.11
+[1.0.10]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.9...v1.0.10
[1.0.9]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.8...v1.0.9
[1.0.8]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.7...v1.0.8
[1.0.7]: https://github.com/copilot-community-sdk/copilot-sdk-java/compare/v1.0.6...v1.0.7
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..c75895245
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+- The use of sexualized language or imagery and unwelcome sexual attention or
+ advance
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at . All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..fe7c9ec09
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,67 @@
+## Contributing
+
+[fork]: https://github.com/github/copilot-sdk-java/fork
+[pr]: https://github.com/github/copilot-sdk-java/compare
+
+Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
+
+This repository contains the **Copilot SDK for Java**, the official Java variant of the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk). For issues or features related to the upstream SDK, please contribute there instead.
+
+Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).
+
+Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
+
+## What kinds of contributions we're looking for
+
+We'd love your help with:
+
+ * Fixing any bugs in the existing feature set
+ * Making the SDK more idiomatic and nice to use for Java developers
+ * Improving documentation
+
+If you have ideas for entirely new features, please post an issue or start a discussion. We're very open to new features but need to make sure they align with the direction of the upstream [Copilot SDK](https://github.com/github/copilot-sdk) and can be maintained in sync. Note that this repo periodically merges upstream changes — see the [README](README.md#agentic-upstream-merge-and-sync) for details on how that works.
+
+## Prerequisites for running and testing code
+
+1. Install [Java 17+](https://openjdk.org/) (JDK)
+1. Install [Maven 3.9+](https://maven.apache.org/download.cgi) (or use the included `mvnw` wrapper)
+1. Install [Node.js](https://nodejs.org/) (v18+) — required for the E2E test harness
+
+## Submitting a pull request
+
+1. [Fork][fork] and clone the repository
+1. Enable git hooks: `git config core.hooksPath .githooks`
+1. Make sure the tests pass on your machine: `mvn clean verify`
+1. Make sure formatting passes: `mvn spotless:check`
+1. Create a new branch: `git checkout -b my-branch-name`
+1. Make your change, add tests, and make sure the tests and linter still pass
+1. Push to your fork and [submit a pull request][pr]
+1. Pat yourself on the back and wait for your pull request to be reviewed and merged.
+
+### Running tests and linters
+
+```bash
+# Build and run all tests
+mvn clean verify
+
+# Run a single test class
+mvn test -Dtest=CopilotClientTest
+
+# Format code (required before commit)
+mvn spotless:apply
+
+# Check formatting only
+mvn spotless:check
+```
+
+Here are a few things you can do that will increase the likelihood of your pull request being accepted:
+
+- Write tests.
+- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
+- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+
+## Resources
+
+- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
+- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
+- [GitHub Help](https://help.github.com)
diff --git a/LICENSE b/LICENSE
index 4fa9fd93f..28a50fa22 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2026 Bruno Borges and the Copilot Community SDK contributors
+Copyright GitHub, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index e50b13b42..d4b0a54ab 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Copilot SDK for Java
+> 🚨 **This repository is archived.** The official GitHub Copilot SDK for Java is now maintained at **[github/copilot-sdk-java](https://github.com/github/copilot-sdk-java)**. No further changes or releases will be made here. Please use the official SDK going forward.
+
[](https://github.com/copilot-community-sdk/copilot-sdk-java/actions/workflows/build-test.yml)
[](https://github.com/copilot-community-sdk/copilot-sdk-java/actions/workflows/deploy-site.yml)
[](https://copilot-community-sdk.github.io/copilot-sdk-java/snapshot/jacoco/index.html)
@@ -11,12 +13,10 @@
[](https://github.com/copilot-community-sdk/copilot-sdk-java/releases)
[](https://github.com/copilot-community-sdk/copilot-sdk-java/releases)
[](https://central.sonatype.com/artifact/io.github.copilot-community-sdk/copilot-sdk)
-[](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/)
+[](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/)
[](https://javadoc.io/doc/io.github.copilot-community-sdk/copilot-sdk/latest/index.html)
-## Overview
-
-> ⚠️ **Disclaimer:** This is an **unofficial, community-driven SDK** and is **not supported or endorsed by GitHub**. This SDK may change in breaking ways. Use at your own risk.
+## Background
Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build AI-powered applications and agentic workflows.
@@ -25,7 +25,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A
### Requirements
- Java 17 or later
-- GitHub Copilot CLI 0.0.409 or later installed and in PATH (or provide custom `cliPath`)
+- GitHub Copilot CLI 0.0.411-1 or later installed and in PATH (or provide custom `cliPath`)
### Maven
@@ -33,14 +33,14 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A
io.github.copilot-community-sdkcopilot-sdk
- 1.0.9
+ 1.0.11
```
### Gradle
```groovy
-implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.9'
+implementation 'io.github.copilot-community-sdk:copilot-sdk:1.0.11'
```
## Quick Start
@@ -59,20 +59,20 @@ public class CopilotSDK {
// Create a session
var session = client.createSession(
- new SessionConfig().setModel("claude-sonnet-4.5")).get();
+ new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get();
// Handle assistant message events
session.on(AssistantMessageEvent.class, msg -> {
- System.out.println(msg.getData().getContent());
+ System.out.println(msg.getData().content());
});
// Handle session usage info events
session.on(SessionUsageInfoEvent.class, usage -> {
var data = usage.getData();
System.out.println("\n--- Usage Metrics ---");
- System.out.println("Current tokens: " + (int) data.getCurrentTokens());
- System.out.println("Token limit: " + (int) data.getTokenLimit());
- System.out.println("Messages count: " + (int) data.getMessagesLength());
+ System.out.println("Current tokens: " + (int) data.currentTokens());
+ System.out.println("Token limit: " + (int) data.tokenLimit());
+ System.out.println("Messages count: " + (int) data.messagesLength());
});
// Send a message
@@ -102,9 +102,9 @@ jbang https://github.com/copilot-community-sdk/copilot-sdk-java/blob/latest/jban
### Quick Links
-- [Getting Started](https://copilot-community-sdk.github.io/copilot-sdk-java/documentation.html)
-- [Javadoc API Reference](https://copilot-community-sdk.github.io/copilot-sdk-java/apidocs/)
-- [MCP Servers Integration](https://copilot-community-sdk.github.io/copilot-sdk-java/mcp.html)
+- [Getting Started](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/documentation.html)
+- [Javadoc API Reference](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/apidocs/)
+- [MCP Servers Integration](https://copilot-community-sdk.github.io/copilot-sdk-java/latest/mcp.html)
- [Cookbook](src/site/markdown/cookbook/) — Practical recipes for common use cases
## Projects Using This SDK
@@ -135,27 +135,6 @@ This SDK tracks the official [Copilot SDK](https://github.com/github/copilot-sdk
- **VS Code Copilot Chat** — type `/agentic-merge-upstream`
- **GitHub Copilot CLI** — use `copilot` CLI with the same skill reference
-### Automated Test Coverage Improvement
-
-The repository includes an [Agentic Workflow](https://github.com/github/gh-aw) that automatically identifies untested code and creates comprehensive tests to increase coverage. The workflow is triggered manually:
-
-**To run the workflow:**
-1. Navigate to the repository on GitHub
-2. Go to **Actions** → **Increase Test Coverage** workflow
-3. Click "Run workflow"
-4. Optionally specify:
- - `target_package` — Focus on a specific package (e.g., `com.github.copilot.sdk.json`)
- - `min_coverage_threshold` — Target coverage percentage (default: 80%)
-
-**What it does:**
-- Analyzes current test coverage using JaCoCo
-- Identifies methods, branches, and edge cases lacking tests
-- Creates high-quality tests following repository patterns
-- Groups related tests logically
-- Creates a PR for human review with coverage metrics
-
-The workflow follows all repository conventions (Spotless formatting, existing test patterns, E2E test infrastructure) and ensures no production code is modified unnecessarily.
-
### Development Setup
```bash
@@ -172,10 +151,26 @@ mvn clean verify
The tests require the official [copilot-sdk](https://github.com/github/copilot-sdk) test harness, which is automatically cloned during build.
+## Support
+
+See [SUPPORT.md](SUPPORT.md) for how to file issues and get help.
+
+## Code of Conduct
+
+This project has adopted the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for details.
+
+## Security
+
+See [SECURITY.md](SECURITY.md) for reporting security vulnerabilities.
+
## License
MIT — see [LICENSE](LICENSE) for details.
+## Acknowledgement
+
+- Initially developed with Copilot and [Bruno Borges](https://www.linkedin.com/in/brunocborges/).
+
## Star History
[](https://www.star-history.com/#copilot-community-sdk/copilot-sdk-java&Date)
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..ef183e6fc
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,31 @@
+Thanks for helping make GitHub safe for everyone.
+
+# Security
+
+GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub).
+
+Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation.
+
+## Reporting Security Issues
+
+If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure.
+
+**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
+
+Instead, please send an email to opensource-security[@]github.com.
+
+Please include as much of the information listed below as you can to help us better understand and resolve the issue:
+
+- The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting)
+- Full paths of source file(s) related to the manifestation of the issue
+- The location of the affected source code (tag/branch/commit or direct URL)
+- Any special configuration required to reproduce the issue
+- Step-by-step instructions to reproduce the issue
+- Proof-of-concept or exploit code (if possible)
+- Impact of the issue, including how an attacker might exploit the issue
+
+This information will help us triage your report more quickly.
+
+## Policy
+
+See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms)
diff --git a/SUPPORT.md b/SUPPORT.md
new file mode 100644
index 000000000..980b3dce8
--- /dev/null
+++ b/SUPPORT.md
@@ -0,0 +1,13 @@
+# Support
+
+## How to file issues and get help
+
+This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue.
+
+For help or questions about using this project, please file an issue.
+
+**Copilot SDK for Java** is under active development and maintained by the community. We will do our best to respond to support, feature requests, and community questions in a timely manner.
+
+## GitHub Support Policy
+
+Support for this project is limited to the resources listed above.
diff --git a/docs/adr/adr-001-semver-pre-general-availability.md b/docs/adr/adr-001-semver-pre-general-availability.md
new file mode 100644
index 000000000..44c9cf6db
--- /dev/null
+++ b/docs/adr/adr-001-semver-pre-general-availability.md
@@ -0,0 +1,27 @@
+# SemVer requirements pre general-availability of Reference Implementation
+
+## Context and Problem Statement
+
+Steve Sanderson agreed that `copilot-sdk-java` will track reference implementation version numbers directly, with one exception: when the Java SDK needs to ship a breaking change before 1.0, the reference implementation will bump its minor version to accommodate, giving our release a clean version number that signals the change to users.
+
+The reference implementation makes no backward compatibility guarantees pre-1.0 — and neither will we. That said, we're choosing to hold ourselves to a higher standard as a matter of good practice: we'll use minor version bumps as a signal to users when we do ship something breaking.
+
+The 2026-02 state of `copilot-sdk-java` is that it takes Java 17+ as its baseline. This decision precludes the use of Java 21 features such as virtual threads. Our pre-analysis showed the **possibility** of a significant performance benefit when using Virtual Threads with Java 21.
+
+We took an architectural decision to enable us to pursue investigating this possibility immediately.
+
+## Considered Options
+
+* Track SemVer of reference implementation, with one exception.
+* Completely avoid the need for this by doing no breaking changes pre-1.0.
+* Abandon the policy of tracking the versions of the reference implementation directly, just do our own thing.
+
+## Decision Outcome
+
+Chosen option: "Track SemVer of reference implementation, with one exception.", because this enables us to pursue Virtual Threads without delaying the first public release of `copilot-sdk-java`. Also, we're supposed to be aggressively modernizing our customers.
+
+To some extent, I would use qualifiers to mark a release as having some feature that is awaiting an upstream full release before it goes full ga, i.e you put out 0.1.46-virtualthreads.3 until upstream is ready to move to 0.2.0 then you release your virtual threads change and go 0.2.0. So I would make your agreement that your version numbers would match with the exception of qualifiers that you might add in exceptional circumstances.
+
+## Related work items
+
+- https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2745172
diff --git a/jbang-example.java b/jbang-example.java
index 4a02cfb41..c99178c6d 100644
--- a/jbang-example.java
+++ b/jbang-example.java
@@ -1,10 +1,12 @@
-//DEPS io.github.copilot-community-sdk:copilot-sdk:1.0.9
+//DEPS io.github.copilot-community-sdk:copilot-sdk:1.0.11
import com.github.copilot.sdk.*;
import com.github.copilot.sdk.events.*;
import com.github.copilot.sdk.json.*;
import java.util.concurrent.CompletableFuture;
+import static java.lang.System.out;
+
class CopilotSDK {
public static void main(String[] args) throws Exception {
// Create and start client
@@ -13,20 +15,20 @@ public static void main(String[] args) throws Exception {
// Create a session
var session = client.createSession(
- new SessionConfig().setModel("claude-sonnet-4.5")).get();
+ new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get();
// Handle assistant message events
session.on(AssistantMessageEvent.class, msg -> {
- System.out.println(msg.getData().getContent());
+ out.println(msg.getData().content());
});
// Handle session usage info events
session.on(SessionUsageInfoEvent.class, usage -> {
var data = usage.getData();
- System.out.println("\n--- Usage Metrics ---");
- System.out.println("Current tokens: " + (int) data.getCurrentTokens());
- System.out.println("Token limit: " + (int) data.getTokenLimit());
- System.out.println("Messages count: " + (int) data.getMessagesLength());
+ out.println("\n--- Usage Metrics ---");
+ out.println("Current tokens: " + (int) data.currentTokens());
+ out.println("Token limit: " + (int) data.tokenLimit());
+ out.println("Messages count: " + (int) data.messagesLength());
});
// Send a message
diff --git a/pom.xml b/pom.xml
index b0b72ab2f..cea1f9575 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
io.github.copilot-community-sdkcopilot-sdk
- 1.0.9
+ 1.0.12-SNAPSHOTjarGitHub Copilot Community SDK :: Java
@@ -33,7 +33,7 @@
scm:git:https://github.com/copilot-community-sdk/copilot-sdk-java.gitscm:git:https://github.com/copilot-community-sdk/copilot-sdk-java.githttps://github.com/copilot-community-sdk/copilot-sdk-java
- v1.0.9
+ HEAD
@@ -49,17 +49,17 @@
com.fasterxml.jackson.corejackson-databind
- 2.20.1
+ 2.21.1com.fasterxml.jackson.corejackson-annotations
- 2.20
+ 2.21com.fasterxml.jackson.datatypejackson-datatype-jsr310
- 2.20.1
+ 2.21.1
@@ -108,6 +108,18 @@
maven-compiler-plugin3.14.1
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
+
+
+
+ com.github.copilot.sdk.java
+
+
+
+ org.apache.maven.plugins
diff --git a/src/main/java/com/github/copilot/sdk/CliServerManager.java b/src/main/java/com/github/copilot/sdk/CliServerManager.java
index 1ac43c719..b2a798ada 100644
--- a/src/main/java/com/github/copilot/sdk/CliServerManager.java
+++ b/src/main/java/com/github/copilot/sdk/CliServerManager.java
@@ -32,6 +32,7 @@ final class CliServerManager {
private static final Logger LOG = Logger.getLogger(CliServerManager.class.getName());
private final CopilotClientOptions options;
+ private final StringBuilder stderrBuffer = new StringBuilder();
CliServerManager(CopilotClientOptions options) {
this.options = options;
@@ -47,6 +48,8 @@ final class CliServerManager {
* if interrupted while waiting for port detection
*/
ProcessInfo startCliServer() throws IOException, InterruptedException {
+ clearStderrBuffer();
+
String cliPath = options.getCliPath() != null ? options.getCliPath() : "copilot";
var args = new ArrayList();
@@ -67,15 +70,15 @@ ProcessInfo startCliServer() throws IOException, InterruptedException {
}
// Add auth-related flags
- if (options.getGithubToken() != null && !options.getGithubToken().isEmpty()) {
+ if (options.getGitHubToken() != null && !options.getGitHubToken().isEmpty()) {
args.add("--auth-token-env");
args.add("COPILOT_SDK_AUTH_TOKEN");
}
- // Default UseLoggedInUser to false when GithubToken is provided
+ // Default UseLoggedInUser to false when GitHubToken is provided
boolean useLoggedInUser = options.getUseLoggedInUser() != null
? options.getUseLoggedInUser()
- : (options.getGithubToken() == null || options.getGithubToken().isEmpty());
+ : (options.getGitHubToken() == null || options.getGitHubToken().isEmpty());
if (!useLoggedInUser) {
args.add("--no-auto-login");
}
@@ -85,6 +88,13 @@ ProcessInfo startCliServer() throws IOException, InterruptedException {
var pb = new ProcessBuilder(command);
pb.redirectErrorStream(false);
+ // Note: On Windows, console window visibility depends on how the parent Java
+ // process was launched. GUI applications started with 'javaw' will not create
+ // visible console windows for subprocesses. Console applications started with
+ // 'java' will share their console with subprocesses. Java's ProcessBuilder
+ // doesn't provide explicit CREATE_NO_WINDOW flags like native Windows APIs,
+ // but the default behavior is appropriate for most use cases.
+
if (options.getCwd() != null) {
pb.directory(new File(options.getCwd()));
}
@@ -96,8 +106,8 @@ ProcessInfo startCliServer() throws IOException, InterruptedException {
pb.environment().remove("NODE_DEBUG");
// Set auth token in environment if provided
- if (options.getGithubToken() != null && !options.getGithubToken().isEmpty()) {
- pb.environment().put("COPILOT_SDK_AUTH_TOKEN", options.getGithubToken());
+ if (options.getGitHubToken() != null && !options.getGitHubToken().isEmpty()) {
+ pb.environment().put("COPILOT_SDK_AUTH_TOKEN", options.getGitHubToken());
}
Process process = pb.start();
@@ -145,6 +155,9 @@ private void startStderrReader(Process process) {
new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
+ synchronized (stderrBuffer) {
+ stderrBuffer.append(line).append('\n');
+ }
LOG.fine("[CLI] " + line);
}
} catch (IOException e) {
@@ -164,6 +177,10 @@ private Integer waitForPortAnnouncement(Process process) throws IOException {
while (System.currentTimeMillis() < deadline) {
String line = reader.readLine();
if (line == null) {
+ String stderr = getStderrOutput();
+ if (!stderr.isEmpty()) {
+ throw new IOException("CLI process exited unexpectedly. stderr: " + stderr);
+ }
throw new IOException("CLI process exited unexpectedly");
}
@@ -178,6 +195,18 @@ private Integer waitForPortAnnouncement(Process process) throws IOException {
}
}
+ String getStderrOutput() {
+ synchronized (stderrBuffer) {
+ return stderrBuffer.toString().trim();
+ }
+ }
+
+ private void clearStderrBuffer() {
+ synchronized (stderrBuffer) {
+ stderrBuffer.setLength(0);
+ }
+ }
+
private List resolveCliCommand(String cliPath, List args) {
boolean isJsFile = cliPath.toLowerCase().endsWith(".js");
diff --git a/src/main/java/com/github/copilot/sdk/CopilotClient.java b/src/main/java/com/github/copilot/sdk/CopilotClient.java
index 46190c469..39034c910 100644
--- a/src/main/java/com/github/copilot/sdk/CopilotClient.java
+++ b/src/main/java/com/github/copilot/sdk/CopilotClient.java
@@ -4,6 +4,7 @@
package com.github.copilot.sdk;
+import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
@@ -46,7 +47,10 @@
* try (var client = new CopilotClient()) {
* client.start().get();
*
- * var session = client.createSession(new SessionConfig().setModel("gpt-5")).get();
+ * var session = client
+ * .createSession(
+ * new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5"))
+ * .get();
*
* session.on(AssistantMessageEvent.class, msg -> {
* System.out.println(msg.getData().content());
@@ -109,9 +113,9 @@ public CopilotClient(CopilotClientOptions options) {
// Validate auth options with external server
if (this.options.getCliUrl() != null && !this.options.getCliUrl().isEmpty()
- && (this.options.getGithubToken() != null || this.options.getUseLoggedInUser() != null)) {
+ && (this.options.getGitHubToken() != null || this.options.getUseLoggedInUser() != null)) {
throw new IllegalArgumentException(
- "GithubToken and UseLoggedInUser cannot be used with CliUrl (external server manages its own auth)");
+ "GitHubToken and UseLoggedInUser cannot be used with CliUrl (external server manages its own auth)");
}
// Parse CliUrl if provided
@@ -174,11 +178,18 @@ private CompletableFuture startCore() {
LOG.info("Copilot client connected");
return connection;
} catch (Exception e) {
+ String stderr = serverManager.getStderrOutput();
+ if (!stderr.isEmpty()) {
+ throw new CompletionException(
+ new IOException("CLI process exited unexpectedly. stderr: " + stderr, e));
+ }
throw new CompletionException(e);
}
});
}
+ private static final int MIN_PROTOCOL_VERSION = 2;
+
private void verifyProtocolVersion(Connection connection) throws Exception {
int expectedVersion = SdkProtocolVersion.get();
var params = new HashMap();
@@ -191,15 +202,27 @@ private void verifyProtocolVersion(Connection connection) throws Exception {
+ "Please update your server to ensure compatibility.");
}
- if (pingResponse.protocolVersion() != expectedVersion) {
+ int serverVersion = pingResponse.protocolVersion();
+ if (serverVersion < MIN_PROTOCOL_VERSION || serverVersion > expectedVersion) {
throw new RuntimeException("SDK protocol version mismatch: SDK expects version " + expectedVersion
- + ", but server reports version " + pingResponse.protocolVersion() + ". "
+ + " (minimum " + MIN_PROTOCOL_VERSION + "), but server reports version " + serverVersion + ". "
+ "Please update your SDK or server to ensure compatibility.");
}
}
/**
- * Stops the client and closes all sessions.
+ * Disconnects from the Copilot server and closes all active sessions.
+ *
+ * This method performs graceful cleanup:
+ *
+ *
Closes all active sessions (releases in-memory resources)
+ *
Closes the JSON-RPC connection
+ *
Terminates the CLI server process (if spawned by this client)
+ *
+ *
+ * Note: session data on disk is preserved, so sessions can be resumed later. To
+ * permanently remove session data before stopping, call
+ * {@link #deleteSession(String)} for each session first.
*
* @return A future that completes when the client is stopped
*/
@@ -267,76 +290,125 @@ private CompletableFuture cleanupConnection() {
*
* The session maintains conversation state and can be used to send messages and
* receive responses. Remember to close the session when done.
+ *
+ * A permission handler is required when creating a session. Use
+ * {@link com.github.copilot.sdk.json.PermissionHandler#APPROVE_ALL} to approve
+ * all permission requests, or provide a custom handler to control permissions
+ * selectively.
+ *
+ *
*
* @param config
- * configuration for the session (model, tools, etc.)
+ * configuration for the session, including the required
+ * {@link SessionConfig#setOnPermissionRequest(com.github.copilot.sdk.json.PermissionHandler)}
+ * handler
* @return a future that resolves with the created CopilotSession
- * @see #createSession()
+ * @throws IllegalArgumentException
+ * if {@code config} is {@code null} or does not have a permission
+ * handler set
* @see SessionConfig
+ * @see com.github.copilot.sdk.json.PermissionHandler#APPROVE_ALL
*/
public CompletableFuture createSession(SessionConfig config) {
+ if (config == null || config.getOnPermissionRequest() == null) {
+ return CompletableFuture.failedFuture(
+ new IllegalArgumentException("An onPermissionRequest handler is required when creating a session. "
+ + "For example, to allow all permissions, use: "
+ + "new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)"));
+ }
return ensureConnected().thenCompose(connection -> {
- var request = SessionRequestBuilder.buildCreateRequest(config);
+ // Pre-generate session ID so the session can be registered before the RPC call,
+ // ensuring no events emitted by the CLI during creation are lost.
+ String sessionId = config.getSessionId() != null
+ ? config.getSessionId()
+ : java.util.UUID.randomUUID().toString();
+
+ var session = new CopilotSession(sessionId, connection.rpc);
+ SessionRequestBuilder.configureSession(session, config);
+ sessions.put(sessionId, session);
+
+ var request = SessionRequestBuilder.buildCreateRequest(config, sessionId);
return connection.rpc.invoke("session.create", request, CreateSessionResponse.class).thenApply(response -> {
- var session = new CopilotSession(response.sessionId(), connection.rpc, response.workspacePath());
- SessionRequestBuilder.configureSession(session, config);
- sessions.put(response.sessionId(), session);
+ session.setWorkspacePath(response.workspacePath());
+ // If the server returned a different sessionId (e.g. a v2 CLI that ignores
+ // the client-supplied ID), re-key the sessions map.
+ String returnedId = response.sessionId();
+ if (returnedId != null && !returnedId.equals(sessionId)) {
+ sessions.remove(sessionId);
+ session.setActiveSessionId(returnedId);
+ sessions.put(returnedId, session);
+ }
return session;
+ }).exceptionally(ex -> {
+ sessions.remove(sessionId);
+ throw ex instanceof RuntimeException re ? re : new RuntimeException(ex);
});
});
}
- /**
- * Creates a new Copilot session with default configuration.
- *
- * @return a future that resolves with the created CopilotSession
- * @see #createSession(SessionConfig)
- */
- public CompletableFuture createSession() {
- return createSession(null);
- }
-
/**
* Resumes an existing Copilot session.
*
* This restores a previously saved session, allowing you to continue a
* conversation. The session's history is preserved.
+ *
+ * A permission handler is required when resuming a session. Use
+ * {@link com.github.copilot.sdk.json.PermissionHandler#APPROVE_ALL} to approve
+ * all permission requests, or provide a custom handler to control permissions
+ * selectively.
*
* @param sessionId
* the ID of the session to resume
* @param config
- * configuration for the resumed session
+ * configuration for the resumed session, including the required
+ * {@link ResumeSessionConfig#setOnPermissionRequest(com.github.copilot.sdk.json.PermissionHandler)}
+ * handler
* @return a future that resolves with the resumed CopilotSession
- * @see #resumeSession(String)
+ * @throws IllegalArgumentException
+ * if {@code config} is {@code null} or does not have a permission
+ * handler set
* @see #listSessions()
* @see #getLastSessionId()
+ * @see com.github.copilot.sdk.json.PermissionHandler#APPROVE_ALL
*/
public CompletableFuture resumeSession(String sessionId, ResumeSessionConfig config) {
+ if (config == null || config.getOnPermissionRequest() == null) {
+ return CompletableFuture.failedFuture(
+ new IllegalArgumentException("An onPermissionRequest handler is required when resuming a session. "
+ + "For example, to allow all permissions, use: "
+ + "new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)"));
+ }
return ensureConnected().thenCompose(connection -> {
+ // Register the session before the RPC call to avoid missing early events.
+ var session = new CopilotSession(sessionId, connection.rpc);
+ SessionRequestBuilder.configureSession(session, config);
+ sessions.put(sessionId, session);
+
var request = SessionRequestBuilder.buildResumeRequest(sessionId, config);
return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class).thenApply(response -> {
- var session = new CopilotSession(response.sessionId(), connection.rpc, response.workspacePath());
- SessionRequestBuilder.configureSession(session, config);
- sessions.put(response.sessionId(), session);
+ session.setWorkspacePath(response.workspacePath());
+ // If the server returned a different sessionId than what was requested, re-key.
+ String returnedId = response.sessionId();
+ if (returnedId != null && !returnedId.equals(sessionId)) {
+ sessions.remove(sessionId);
+ session.setActiveSessionId(returnedId);
+ sessions.put(returnedId, session);
+ }
return session;
+ }).exceptionally(ex -> {
+ sessions.remove(sessionId);
+ throw ex instanceof RuntimeException re ? re : new RuntimeException(ex);
});
});
}
- /**
- * Resumes an existing session with default configuration.
- *
- * @param sessionId
- * the ID of the session to resume
- * @return a future that resolves with the resumed CopilotSession
- * @see #resumeSession(String, ResumeSessionConfig)
- */
- public CompletableFuture resumeSession(String sessionId) {
- return resumeSession(sessionId, null);
- }
-
/**
* Gets the current connection state.
*
@@ -397,6 +469,10 @@ public CompletableFuture getAuthStatus() {
*
* Results are cached after the first successful call to avoid rate limiting.
* The cache is cleared when the client disconnects.
+ *
+ * If an {@code onListModels} handler was provided in
+ * {@link com.github.copilot.sdk.json.CopilotClientOptions}, it is called
+ * instead of querying the CLI server. This is useful in BYOK mode.
*
* @return a future that resolves with a list of available models
* @see ModelInfo
@@ -408,6 +484,22 @@ public CompletableFuture> listModels() {
return CompletableFuture.completedFuture(new ArrayList<>(cached));
}
+ // If a custom handler is configured, use it instead of querying the CLI server
+ var onListModels = options.getOnListModels();
+ if (onListModels != null) {
+ synchronized (modelsCacheLock) {
+ if (modelsCache != null) {
+ return CompletableFuture.completedFuture(new ArrayList<>(modelsCache));
+ }
+ }
+ return onListModels.get().thenApply(models -> {
+ synchronized (modelsCacheLock) {
+ modelsCache = models;
+ }
+ return new ArrayList<>(models);
+ });
+ }
+
return ensureConnected().thenCompose(connection -> {
// Double-check cache inside lock
synchronized (modelsCacheLock) {
@@ -434,7 +526,7 @@ public CompletableFuture> listModels() {
*
* @return a future that resolves with the last session ID, or {@code null} if
* no sessions exist
- * @see #resumeSession(String)
+ * @see #resumeSession(String, com.github.copilot.sdk.json.ResumeSessionConfig)
*/
public CompletableFuture getLastSessionId() {
return ensureConnected().thenCompose(
@@ -443,9 +535,12 @@ public CompletableFuture getLastSessionId() {
}
/**
- * Deletes a session by ID.
+ * Permanently deletes a session and all its data from disk, including
+ * conversation history, planning state, and artifacts.
*
- * This permanently removes the session and its conversation history.
+ * Unlike {@link CopilotSession#close()}, which only releases in-memory
+ * resources and preserves session data for later resumption, this method is
+ * irreversible. The session cannot be resumed after deletion.
*
* @param sessionId
* the ID of the session to delete
@@ -472,7 +567,7 @@ public CompletableFuture deleteSession(String sessionId) {
*
* @return a future that resolves with a list of session metadata
* @see SessionMetadata
- * @see #resumeSession(String)
+ * @see #resumeSession(String, com.github.copilot.sdk.json.ResumeSessionConfig)
*/
public CompletableFuture> listSessions() {
return listSessions(null);
@@ -502,7 +597,7 @@ public CompletableFuture> listSessions() {
* @return a future that resolves with a list of session metadata
* @see SessionMetadata
* @see SessionListFilter
- * @see #resumeSession(String)
+ * @see #resumeSession(String, com.github.copilot.sdk.json.ResumeSessionConfig)
*/
public CompletableFuture> listSessions(SessionListFilter filter) {
return ensureConnected().thenCompose(connection -> {
diff --git a/src/main/java/com/github/copilot/sdk/CopilotSession.java b/src/main/java/com/github/copilot/sdk/CopilotSession.java
index 722407c50..452e82671 100644
--- a/src/main/java/com/github/copilot/sdk/CopilotSession.java
+++ b/src/main/java/com/github/copilot/sdk/CopilotSession.java
@@ -7,6 +7,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -20,13 +21,18 @@
import java.util.logging.Level;
import java.util.logging.Logger;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.copilot.sdk.events.AbstractSessionEvent;
import com.github.copilot.sdk.events.AssistantMessageEvent;
+import com.github.copilot.sdk.events.ExternalToolRequestedEvent;
+import com.github.copilot.sdk.events.PermissionRequestedEvent;
import com.github.copilot.sdk.events.SessionErrorEvent;
import com.github.copilot.sdk.events.SessionEventParser;
import com.github.copilot.sdk.events.SessionIdleEvent;
+import com.github.copilot.sdk.json.AgentInfo;
import com.github.copilot.sdk.json.GetMessagesResponse;
import com.github.copilot.sdk.json.HookInvocation;
import com.github.copilot.sdk.json.MessageOptions;
@@ -34,6 +40,7 @@
import com.github.copilot.sdk.json.PermissionInvocation;
import com.github.copilot.sdk.json.PermissionRequest;
import com.github.copilot.sdk.json.PermissionRequestResult;
+import com.github.copilot.sdk.json.PermissionRequestResultKind;
import com.github.copilot.sdk.json.PostToolUseHookInput;
import com.github.copilot.sdk.json.PreToolUseHookInput;
import com.github.copilot.sdk.json.SendMessageRequest;
@@ -42,6 +49,7 @@
import com.github.copilot.sdk.json.SessionHooks;
import com.github.copilot.sdk.json.SessionStartHookInput;
import com.github.copilot.sdk.json.ToolDefinition;
+import com.github.copilot.sdk.json.ToolResultObject;
import com.github.copilot.sdk.json.UserInputHandler;
import com.github.copilot.sdk.json.UserInputInvocation;
import com.github.copilot.sdk.json.UserInputRequest;
@@ -54,12 +62,21 @@
* A session maintains conversation state, handles events, and manages tool
* execution. Sessions are created via {@link CopilotClient#createSession} or
* resumed via {@link CopilotClient#resumeSession}.
+ *
+ * {@code CopilotSession} implements {@link AutoCloseable}. Use the
+ * try-with-resources pattern for automatic cleanup, or call {@link #close()}
+ * explicitly. Closing a session releases in-memory resources but preserves
+ * session data on disk — the conversation can be resumed later via
+ * {@link CopilotClient#resumeSession}. To permanently delete session data, use
+ * {@link CopilotClient#deleteSession}.
*
*
Example Usage
*
*
{@code
- * // Create a session
- * var session = client.createSession(new SessionConfig().setModel("gpt-5")).get();
+ * // Create a session with a permission handler (required)
+ * var session = client
+ * .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5"))
+ * .get();
*
* // Register type-safe event handlers
* session.on(AssistantMessageEvent.class, msg -> {
@@ -87,8 +104,14 @@ public final class CopilotSession implements AutoCloseable {
private static final Logger LOG = Logger.getLogger(CopilotSession.class.getName());
private static final ObjectMapper MAPPER = JsonRpcClient.getObjectMapper();
- private final String sessionId;
- private final String workspacePath;
+ /**
+ * The current active session ID. Initialized to the pre-generated value and may
+ * be updated after session.create / session.resume if the server returns a
+ * different ID (e.g. when working against a v2 CLI that ignores the
+ * client-supplied sessionId).
+ */
+ private volatile String sessionId;
+ private volatile String workspacePath;
private final JsonRpcClient rpc;
private final Set> eventHandlers = ConcurrentHashMap.newKeySet();
private final Map toolHandlers = new ConcurrentHashMap<>();
@@ -144,6 +167,18 @@ public String getSessionId() {
return sessionId;
}
+ /**
+ * Updates the active session ID. Package-private; called by CopilotClient if
+ * the server returns a different session ID than the pre-generated one (e.g.
+ * when a v2 CLI ignores the client-supplied sessionId).
+ *
+ * @param sessionId
+ * the server-confirmed session ID
+ */
+ void setActiveSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ }
+
/**
* Gets the path to the session workspace directory when infinite sessions are
* enabled.
@@ -157,6 +192,17 @@ public String getWorkspacePath() {
return workspacePath;
}
+ /**
+ * Sets the workspace path. Package-private; called by CopilotClient after
+ * session.create or session.resume RPC response.
+ *
+ * @param workspacePath
+ * the workspace path
+ */
+ void setWorkspacePath(String workspacePath) {
+ this.workspacePath = workspacePath;
+ }
+
/**
* Sets a custom error handler for exceptions thrown by event handlers.
*
@@ -312,6 +358,12 @@ public CompletableFuture send(MessageOptions options) {
* This method blocks until the assistant finishes processing the message or
* until the timeout expires. It's suitable for simple request/response
* interactions where you don't need to process streaming events.
+ *
+ * The returned future can be cancelled via
+ * {@link java.util.concurrent.Future#cancel(boolean)}. If cancelled externally,
+ * the future completes with {@link java.util.concurrent.CancellationException}.
+ * If the timeout expires first, the future completes exceptionally with a
+ * {@link TimeoutException}.
*
* @param options
* the message options containing the prompt and attachments
@@ -320,7 +372,7 @@ public CompletableFuture send(MessageOptions options) {
* @return a future that resolves with the final assistant message event, or
* {@code null} if no assistant message was received. The future
* completes exceptionally with a TimeoutException if the timeout
- * expires.
+ * expires, or with CancellationException if cancelled externally.
* @throws IllegalStateException
* if this session has been terminated
* @see #sendAndWait(MessageOptions)
@@ -367,14 +419,33 @@ public CompletableFuture sendAndWait(MessageOptions optio
scheduler.shutdown();
}, timeoutMs, TimeUnit.MILLISECONDS);
- return future.whenComplete((result, ex) -> {
+ var result = new CompletableFuture();
+
+ // When inner future completes, run cleanup and propagate to result
+ future.whenComplete((r, ex) -> {
try {
subscription.close();
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error closing subscription", e);
}
scheduler.shutdown();
+ if (!result.isDone()) {
+ if (ex != null) {
+ result.completeExceptionally(ex);
+ } else {
+ result.complete(r);
+ }
+ }
+ });
+
+ // When result is cancelled externally, cancel inner future to trigger cleanup
+ result.whenComplete((v, ex) -> {
+ if (result.isCancelled() && !future.isDone()) {
+ future.cancel(true);
+ }
});
+
+ return result;
}
/**
@@ -513,6 +584,10 @@ public Closeable on(Class eventType, Consume
* @see #setEventErrorPolicy(EventErrorPolicy)
*/
void dispatchEvent(AbstractSessionEvent event) {
+ // Handle broadcast request events (protocol v3) before dispatching to user
+ // handlers. These are fire-and-forget: the response is sent asynchronously.
+ handleBroadcastEventAsync(event);
+
for (Consumer handler : eventHandlers) {
try {
handler.accept(event);
@@ -534,6 +609,136 @@ void dispatchEvent(AbstractSessionEvent event) {
}
}
+ /**
+ * Handles broadcast request events by executing local handlers and responding
+ * via RPC (protocol v3).
+ *
+ *
+ * @param model
+ * the model ID to switch to (e.g., {@code "gpt-4.1"})
+ * @return a future that completes when the model switch is acknowledged
+ * @throws IllegalStateException
+ * if this session has been terminated
+ * @since 1.0.11
+ */
+ public CompletableFuture setModel(String model) {
+ ensureNotTerminated();
+ return rpc.invoke("session.model.switchTo", Map.of("sessionId", sessionId, "modelId", model), Void.class);
+ }
+
+ /**
+ * Logs a message to the session timeline.
+ *
+ * The message appears in the session event stream and is visible to SDK
+ * consumers. Non-ephemeral messages are also persisted to the session event log
+ * on disk.
+ *
+ *
+ *
+ * @param message
+ * the message to log
+ * @param level
+ * the log severity level ({@code "info"}, {@code "warning"},
+ * {@code "error"}), or {@code null} to use the default
+ * ({@code "info"})
+ * @param ephemeral
+ * when {@code true}, the message is transient and not persisted to
+ * disk; {@code null} uses default behavior
+ * @return a future that completes when the message is logged
+ * @throws IllegalStateException
+ * if this session has been terminated
+ */
+ public CompletableFuture log(String message, String level, Boolean ephemeral) {
+ ensureNotTerminated();
+ var params = new java.util.HashMap();
+ params.put("sessionId", sessionId);
+ params.put("message", message);
+ if (level != null) {
+ params.put("level", level);
+ }
+ if (ephemeral != null) {
+ params.put("ephemeral", ephemeral);
+ }
+ return rpc.invoke("session.log", params, Void.class);
+ }
+
+ /**
+ * Logs an informational message to the session timeline.
+ *
+ * @param message
+ * the message to log
+ * @return a future that completes when the message is logged
+ * @throws IllegalStateException
+ * if this session has been terminated
+ */
+ public CompletableFuture log(String message) {
+ return log(message, null, null);
+ }
+
+ /**
+ * Lists the custom agents available for selection in this session.
+ *
+ * @return a future that resolves with the list of available agents
+ * @throws IllegalStateException
+ * if this session has been terminated
+ * @since 1.0.11
+ */
+ public CompletableFuture> listAgents() {
+ ensureNotTerminated();
+ return rpc.invoke("session.agent.list", Map.of("sessionId", sessionId), AgentListResponse.class)
+ .thenApply(response -> response.agents() != null
+ ? Collections.unmodifiableList(response.agents())
+ : Collections.emptyList());
+ }
+
+ /**
+ * Gets the currently selected custom agent for this session, or {@code null} if
+ * no custom agent is selected.
+ *
+ * @return a future that resolves with the current agent, or {@code null} if
+ * using the default agent
+ * @throws IllegalStateException
+ * if this session has been terminated
+ * @since 1.0.11
+ */
+ public CompletableFuture getCurrentAgent() {
+ ensureNotTerminated();
+ return rpc.invoke("session.agent.getCurrent", Map.of("sessionId", sessionId), AgentGetCurrentResponse.class)
+ .thenApply(AgentGetCurrentResponse::agent);
+ }
+
+ /**
+ * Selects a custom agent for this session.
+ *
+ * @param agentName
+ * the name/identifier of the agent to select
+ * @return a future that resolves with the selected agent information
+ * @throws IllegalStateException
+ * if this session has been terminated
+ * @since 1.0.11
+ */
+ public CompletableFuture selectAgent(String agentName) {
+ ensureNotTerminated();
+ return rpc.invoke("session.agent.select", Map.of("sessionId", sessionId, "name", agentName),
+ AgentSelectResponse.class).thenApply(AgentSelectResponse::agent);
+ }
+
+ /**
+ * Deselects the currently selected custom agent, returning to the default
+ * agent.
+ *
+ * @return a future that completes when the agent is deselected
+ * @throws IllegalStateException
+ * if this session has been terminated
+ * @since 1.0.11
+ */
+ public CompletableFuture deselectAgent() {
+ ensureNotTerminated();
+ return rpc.invoke("session.agent.deselect", Map.of("sessionId", sessionId), Void.class);
+ }
+
+ /**
+ * Compacts the session context to reduce token usage.
+ *
+ * Broadcast when a pending tool call has been resolved by a client (protocol
+ * v3).
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class ExternalToolCompletedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private ExternalToolCompletedData data;
+
+ @Override
+ public String getType() {
+ return "external_tool.completed";
+ }
+
+ public ExternalToolCompletedData getData() {
+ return data;
+ }
+
+ public void setData(ExternalToolCompletedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record ExternalToolCompletedData(@JsonProperty("requestId") String requestId) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/ExternalToolRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/ExternalToolRequestedEvent.java
new file mode 100644
index 000000000..8eb11f5b8
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/ExternalToolRequestedEvent.java
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: external_tool.requested
+ *
+ * Broadcast when the CLI needs a client to handle a tool call (protocol v3).
+ * Clients that own the named tool should respond via
+ * {@code session.tools.handlePendingToolCall}.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class ExternalToolRequestedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private ExternalToolRequestedData data;
+
+ @Override
+ public String getType() {
+ return "external_tool.requested";
+ }
+
+ public ExternalToolRequestedData getData() {
+ return data;
+ }
+
+ public void setData(ExternalToolRequestedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record ExternalToolRequestedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("sessionId") String sessionId, @JsonProperty("toolCallId") String toolCallId,
+ @JsonProperty("toolName") String toolName, @JsonProperty("arguments") Object arguments) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/PermissionCompletedEvent.java b/src/main/java/com/github/copilot/sdk/events/PermissionCompletedEvent.java
new file mode 100644
index 000000000..90daf3b49
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/PermissionCompletedEvent.java
@@ -0,0 +1,45 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: permission.completed
+ *
+ * Broadcast when a pending permission request has been resolved by a client
+ * (protocol v3).
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class PermissionCompletedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private PermissionCompletedData data;
+
+ @Override
+ public String getType() {
+ return "permission.completed";
+ }
+
+ public PermissionCompletedData getData() {
+ return data;
+ }
+
+ public void setData(PermissionCompletedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record PermissionCompletedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("result") PermissionCompletedResult result) {
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record PermissionCompletedResult(@JsonProperty("kind") String kind) {
+ }
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java
new file mode 100644
index 000000000..d8f9ec147
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.copilot.sdk.json.PermissionRequest;
+
+/**
+ * Event: permission.requested
+ *
+ * Broadcast when the CLI needs a client to handle a permission request
+ * (protocol v3). Clients that have a permission handler should respond via
+ * {@code session.permissions.handlePendingPermissionRequest}.
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class PermissionRequestedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private PermissionRequestedData data;
+
+ @Override
+ public String getType() {
+ return "permission.requested";
+ }
+
+ public PermissionRequestedData getData() {
+ return data;
+ }
+
+ public void setData(PermissionRequestedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record PermissionRequestedData(@JsonProperty("requestId") String requestId,
+ @JsonProperty("permissionRequest") PermissionRequest permissionRequest) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java b/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java
index 4cc1763cf..75971b29e 100644
--- a/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java
+++ b/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java
@@ -55,6 +55,9 @@ public class SessionEventParser {
TYPE_MAP.put("session.idle", SessionIdleEvent.class);
TYPE_MAP.put("session.info", SessionInfoEvent.class);
TYPE_MAP.put("session.model_change", SessionModelChangeEvent.class);
+ TYPE_MAP.put("session.mode_changed", SessionModeChangedEvent.class);
+ TYPE_MAP.put("session.plan_changed", SessionPlanChangedEvent.class);
+ TYPE_MAP.put("session.workspace_file_changed", SessionWorkspaceFileChangedEvent.class);
TYPE_MAP.put("session.handoff", SessionHandoffEvent.class);
TYPE_MAP.put("session.truncation", SessionTruncationEvent.class);
TYPE_MAP.put("session.snapshot_rewind", SessionSnapshotRewindEvent.class);
@@ -62,6 +65,7 @@ public class SessionEventParser {
TYPE_MAP.put("session.compaction_start", SessionCompactionStartEvent.class);
TYPE_MAP.put("session.compaction_complete", SessionCompactionCompleteEvent.class);
TYPE_MAP.put("session.context_changed", SessionContextChangedEvent.class);
+ TYPE_MAP.put("session.task_complete", SessionTaskCompleteEvent.class);
TYPE_MAP.put("user.message", UserMessageEvent.class);
TYPE_MAP.put("pending_messages.modified", PendingMessagesModifiedEvent.class);
TYPE_MAP.put("assistant.turn_start", AssistantTurnStartEvent.class);
@@ -70,6 +74,7 @@ public class SessionEventParser {
TYPE_MAP.put("assistant.reasoning_delta", AssistantReasoningDeltaEvent.class);
TYPE_MAP.put("assistant.message", AssistantMessageEvent.class);
TYPE_MAP.put("assistant.message_delta", AssistantMessageDeltaEvent.class);
+ TYPE_MAP.put("assistant.streaming_delta", AssistantStreamingDeltaEvent.class);
TYPE_MAP.put("assistant.turn_end", AssistantTurnEndEvent.class);
TYPE_MAP.put("assistant.usage", AssistantUsageEvent.class);
TYPE_MAP.put("abort", AbortEvent.class);
@@ -82,11 +87,21 @@ public class SessionEventParser {
TYPE_MAP.put("subagent.completed", SubagentCompletedEvent.class);
TYPE_MAP.put("subagent.failed", SubagentFailedEvent.class);
TYPE_MAP.put("subagent.selected", SubagentSelectedEvent.class);
+ TYPE_MAP.put("subagent.deselected", SubagentDeselectedEvent.class);
TYPE_MAP.put("hook.start", HookStartEvent.class);
TYPE_MAP.put("hook.end", HookEndEvent.class);
TYPE_MAP.put("system.message", SystemMessageEvent.class);
TYPE_MAP.put("session.shutdown", SessionShutdownEvent.class);
TYPE_MAP.put("skill.invoked", SkillInvokedEvent.class);
+ TYPE_MAP.put("external_tool.requested", ExternalToolRequestedEvent.class);
+ TYPE_MAP.put("external_tool.completed", ExternalToolCompletedEvent.class);
+ TYPE_MAP.put("permission.requested", PermissionRequestedEvent.class);
+ TYPE_MAP.put("permission.completed", PermissionCompletedEvent.class);
+ TYPE_MAP.put("command.queued", CommandQueuedEvent.class);
+ TYPE_MAP.put("command.completed", CommandCompletedEvent.class);
+ TYPE_MAP.put("exit_plan_mode.requested", ExitPlanModeRequestedEvent.class);
+ TYPE_MAP.put("exit_plan_mode.completed", ExitPlanModeCompletedEvent.class);
+ TYPE_MAP.put("system.notification", SystemNotificationEvent.class);
}
/**
diff --git a/src/main/java/com/github/copilot/sdk/events/SessionModeChangedEvent.java b/src/main/java/com/github/copilot/sdk/events/SessionModeChangedEvent.java
new file mode 100644
index 000000000..3c5b5d661
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/SessionModeChangedEvent.java
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: session.mode_changed
+ *
+ * @since 1.0.10
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class SessionModeChangedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private SessionModeChangedData data;
+
+ @Override
+ public String getType() {
+ return "session.mode_changed";
+ }
+
+ public SessionModeChangedData getData() {
+ return data;
+ }
+
+ public void setData(SessionModeChangedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record SessionModeChangedData(@JsonProperty("previousMode") String previousMode,
+ @JsonProperty("newMode") String newMode) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/SessionPlanChangedEvent.java b/src/main/java/com/github/copilot/sdk/events/SessionPlanChangedEvent.java
new file mode 100644
index 000000000..2010cf146
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/SessionPlanChangedEvent.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: session.plan_changed
+ *
+ * @since 1.0.10
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class SessionPlanChangedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private SessionPlanChangedData data;
+
+ @Override
+ public String getType() {
+ return "session.plan_changed";
+ }
+
+ public SessionPlanChangedData getData() {
+ return data;
+ }
+
+ public void setData(SessionPlanChangedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record SessionPlanChangedData(@JsonProperty("operation") String operation) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/SessionTaskCompleteEvent.java b/src/main/java/com/github/copilot/sdk/events/SessionTaskCompleteEvent.java
new file mode 100644
index 000000000..d82b516ab
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/SessionTaskCompleteEvent.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: session.task_complete
+ *
+ * @since 1.0.11
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class SessionTaskCompleteEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private SessionTaskCompleteData data;
+
+ @Override
+ public String getType() {
+ return "session.task_complete";
+ }
+
+ public SessionTaskCompleteData getData() {
+ return data;
+ }
+
+ public void setData(SessionTaskCompleteData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record SessionTaskCompleteData(@JsonProperty("summary") String summary) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/SessionWorkspaceFileChangedEvent.java b/src/main/java/com/github/copilot/sdk/events/SessionWorkspaceFileChangedEvent.java
new file mode 100644
index 000000000..0ed30aeb8
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/SessionWorkspaceFileChangedEvent.java
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: session.workspace_file_changed
+ *
+ * @since 1.0.10
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class SessionWorkspaceFileChangedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private SessionWorkspaceFileChangedData data;
+
+ @Override
+ public String getType() {
+ return "session.workspace_file_changed";
+ }
+
+ public SessionWorkspaceFileChangedData getData() {
+ return data;
+ }
+
+ public void setData(SessionWorkspaceFileChangedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record SessionWorkspaceFileChangedData(@JsonProperty("path") String path,
+ @JsonProperty("operation") String operation) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/SkillInvokedEvent.java b/src/main/java/com/github/copilot/sdk/events/SkillInvokedEvent.java
index febe01253..4ed8e2327 100644
--- a/src/main/java/com/github/copilot/sdk/events/SkillInvokedEvent.java
+++ b/src/main/java/com/github/copilot/sdk/events/SkillInvokedEvent.java
@@ -40,6 +40,7 @@ public void setData(SkillInvokedData data) {
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record SkillInvokedData(@JsonProperty("name") String name, @JsonProperty("path") String path,
- @JsonProperty("content") String content, @JsonProperty("allowedTools") List allowedTools) {
+ @JsonProperty("content") String content, @JsonProperty("allowedTools") List allowedTools,
+ @JsonProperty("pluginName") String pluginName, @JsonProperty("pluginVersion") String pluginVersion) {
}
}
diff --git a/src/main/java/com/github/copilot/sdk/events/SubagentDeselectedEvent.java b/src/main/java/com/github/copilot/sdk/events/SubagentDeselectedEvent.java
new file mode 100644
index 000000000..f49f46330
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/SubagentDeselectedEvent.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: subagent.deselected
+ *
+ * @since 1.0.11
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class SubagentDeselectedEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private SubagentDeselectedData data;
+
+ @Override
+ public String getType() {
+ return "subagent.deselected";
+ }
+
+ public SubagentDeselectedData getData() {
+ return data;
+ }
+
+ public void setData(SubagentDeselectedData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record SubagentDeselectedData() {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/SystemNotificationEvent.java b/src/main/java/com/github/copilot/sdk/events/SystemNotificationEvent.java
new file mode 100644
index 000000000..38711f276
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/events/SystemNotificationEvent.java
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.events;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Event: system.notification
+ *
+ * @since 1.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class SystemNotificationEvent extends AbstractSessionEvent {
+
+ @JsonProperty("data")
+ private SystemNotificationData data;
+
+ @Override
+ public String getType() {
+ return "system.notification";
+ }
+
+ public SystemNotificationData getData() {
+ return data;
+ }
+
+ public void setData(SystemNotificationData data) {
+ this.data = data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record SystemNotificationData(@JsonProperty("content") String content, @JsonProperty("kind") Object kind) {
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/events/ToolExecutionCompleteEvent.java b/src/main/java/com/github/copilot/sdk/events/ToolExecutionCompleteEvent.java
index d6cc1662f..f085f43ce 100644
--- a/src/main/java/com/github/copilot/sdk/events/ToolExecutionCompleteEvent.java
+++ b/src/main/java/com/github/copilot/sdk/events/ToolExecutionCompleteEvent.java
@@ -36,9 +36,10 @@ public void setData(ToolExecutionCompleteData data) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record ToolExecutionCompleteData(@JsonProperty("toolCallId") String toolCallId,
- @JsonProperty("success") boolean success, @JsonProperty("isUserRequested") Boolean isUserRequested,
- @JsonProperty("result") Result result, @JsonProperty("error") Error error,
- @JsonProperty("toolTelemetry") Map toolTelemetry,
+ @JsonProperty("success") boolean success, @JsonProperty("model") String model,
+ @JsonProperty("interactionId") String interactionId,
+ @JsonProperty("isUserRequested") Boolean isUserRequested, @JsonProperty("result") Result result,
+ @JsonProperty("error") Error error, @JsonProperty("toolTelemetry") Map toolTelemetry,
@JsonProperty("parentToolCallId") String parentToolCallId) {
/** Returns a defensive copy of the tool telemetry map. */
diff --git a/src/main/java/com/github/copilot/sdk/json/AgentInfo.java b/src/main/java/com/github/copilot/sdk/json/AgentInfo.java
new file mode 100644
index 000000000..1cc10a91d
--- /dev/null
+++ b/src/main/java/com/github/copilot/sdk/json/AgentInfo.java
@@ -0,0 +1,89 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------------------------------------------*/
+
+package com.github.copilot.sdk.json;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Represents a custom agent available for selection in a session.
+ *
+ * @since 1.0.11
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AgentInfo {
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("displayName")
+ private String displayName;
+
+ @JsonProperty("description")
+ private String description;
+
+ /**
+ * Gets the unique identifier of the agent.
+ *
+ * @return the agent name/identifier
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the unique identifier of the agent.
+ *
+ * @param name
+ * the agent name/identifier
+ * @return this instance for chaining
+ */
+ public AgentInfo setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Gets the human-readable display name of the agent.
+ *
+ * @return the display name
+ */
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * Sets the human-readable display name of the agent.
+ *
+ * @param displayName
+ * the display name
+ * @return this instance for chaining
+ */
+ public AgentInfo setDisplayName(String displayName) {
+ this.displayName = displayName;
+ return this;
+ }
+
+ /**
+ * Gets the description of the agent's purpose.
+ *
+ * @return the description
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Sets the description of the agent's purpose.
+ *
+ * @param description
+ * the description
+ * @return this instance for chaining
+ */
+ public AgentInfo setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+}
diff --git a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java
index b51ab7a19..4fd55d3ba 100644
--- a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java
+++ b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java
@@ -4,7 +4,10 @@
package com.github.copilot.sdk.json;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -41,8 +44,9 @@ public class CopilotClientOptions {
private boolean autoStart = true;
private boolean autoRestart = true;
private Map environment;
- private String githubToken;
+ private String gitHubToken;
private Boolean useLoggedInUser;
+ private Supplier>> onListModels;
/**
* Gets the path to the Copilot CLI executable.
@@ -279,8 +283,8 @@ public CopilotClientOptions setEnvironment(Map environment) {
*
* @return the GitHub token, or {@code null} to use other authentication methods
*/
- public String getGithubToken() {
- return githubToken;
+ public String getGitHubToken() {
+ return gitHubToken;
}
/**
@@ -289,12 +293,37 @@ public String getGithubToken() {
* When provided, the token is passed to the CLI server via environment
* variable. This takes priority over other authentication methods.
*
+ * @param gitHubToken
+ * the GitHub token
+ * @return this options instance for method chaining
+ */
+ public CopilotClientOptions setGitHubToken(String gitHubToken) {
+ this.gitHubToken = gitHubToken;
+ return this;
+ }
+
+ /**
+ * Gets the GitHub token for authentication.
+ *
+ * @return the GitHub token, or {@code null} to use other authentication methods
+ * @deprecated Use {@link #getGitHubToken()} instead.
+ */
+ @Deprecated
+ public String getGithubToken() {
+ return gitHubToken;
+ }
+
+ /**
+ * Sets the GitHub token to use for authentication.
+ *
* @param githubToken
* the GitHub token
* @return this options instance for method chaining
+ * @deprecated Use {@link #setGitHubToken(String)} instead.
*/
+ @Deprecated
public CopilotClientOptions setGithubToken(String githubToken) {
- this.githubToken = githubToken;
+ this.gitHubToken = githubToken;
return this;
}
@@ -312,8 +341,8 @@ public Boolean getUseLoggedInUser() {
* Sets whether to use the logged-in user for authentication.
*
* When true, the CLI server will attempt to use stored OAuth tokens or gh CLI
- * auth. When false, only explicit tokens (githubToken or environment variables)
- * are used. Default: true (but defaults to false when githubToken is provided).
+ * auth. When false, only explicit tokens (gitHubToken or environment variables)
+ * are used. Default: true (but defaults to false when gitHubToken is provided).
*
* @param useLoggedInUser
* {@code true} to use logged-in user auth, {@code false} otherwise
@@ -323,4 +352,58 @@ public CopilotClientOptions setUseLoggedInUser(Boolean useLoggedInUser) {
this.useLoggedInUser = useLoggedInUser;
return this;
}
+
+ /**
+ * Gets the custom handler for listing available models.
+ *
+ * @return the handler, or {@code null} if not set
+ */
+ public Supplier>> getOnListModels() {
+ return onListModels;
+ }
+
+ /**
+ * Sets a custom handler for listing available models.
+ *
+ * When provided, {@code listModels()} calls this handler instead of querying
+ * the CLI server. Useful in BYOK (Bring Your Own Key) mode to return models
+ * available from your custom provider.
+ *
+ * @param onListModels
+ * the handler that returns the list of available models
+ * @return this options instance for method chaining
+ */
+ public CopilotClientOptions setOnListModels(Supplier>> onListModels) {
+ this.onListModels = onListModels;
+ return this;
+ }
+
+ /**
+ * Creates a shallow clone of this {@code CopilotClientOptions} instance.
+ *
+ * Array properties (like {@code cliArgs}) are copied into new arrays so that
+ * modifications to the clone do not affect the original. The
+ * {@code environment} map is also copied to a new map instance. Other
+ * reference-type properties are shared between the original and clone.
+ *
+ * @return a clone of this options instance
+ */
+ @Override
+ public CopilotClientOptions clone() {
+ CopilotClientOptions copy = new CopilotClientOptions();
+ copy.cliPath = this.cliPath;
+ copy.cliArgs = this.cliArgs != null ? this.cliArgs.clone() : null;
+ copy.cwd = this.cwd;
+ copy.port = this.port;
+ copy.useStdio = this.useStdio;
+ copy.cliUrl = this.cliUrl;
+ copy.logLevel = this.logLevel;
+ copy.autoStart = this.autoStart;
+ copy.autoRestart = this.autoRestart;
+ copy.environment = this.environment != null ? new java.util.HashMap<>(this.environment) : null;
+ copy.gitHubToken = this.gitHubToken;
+ copy.useLoggedInUser = this.useLoggedInUser;
+ copy.onListModels = this.onListModels;
+ return copy;
+ }
}
diff --git a/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java b/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java
index 1e1ee621a..c0243f14b 100644
--- a/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java
+++ b/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java
@@ -31,6 +31,9 @@ public final class CreateSessionRequest {
@JsonProperty("sessionId")
private String sessionId;
+ @JsonProperty("clientName")
+ private String clientName;
+
@JsonProperty("reasoningEffort")
private String reasoningEffort;
@@ -67,9 +70,15 @@ public final class CreateSessionRequest {
@JsonProperty("mcpServers")
private Map mcpServers;
+ @JsonProperty("envValueMode")
+ private String envValueMode;
+
@JsonProperty("customAgents")
private List customAgents;
+ @JsonProperty("agent")
+ private String agent;
+
@JsonProperty("infiniteSessions")
private InfiniteSessionConfig infiniteSessions;
@@ -102,6 +111,16 @@ public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
+ /** Gets the client name. @return the client name */
+ public String getClientName() {
+ return clientName;
+ }
+
+ /** Sets the client name. @param clientName the client name */
+ public void setClientName(String clientName) {
+ this.clientName = clientName;
+ }
+
/** Gets the reasoning effort. @return the reasoning effort level */
public String getReasoningEffort() {
return reasoningEffort;
@@ -224,6 +243,16 @@ public void setMcpServers(Map mcpServers) {
this.mcpServers = mcpServers;
}
+ /** Gets MCP environment variable value mode. @return the mode */
+ public String getEnvValueMode() {
+ return envValueMode;
+ }
+
+ /** Sets MCP environment variable value mode. @param envValueMode the mode */
+ public void setEnvValueMode(String envValueMode) {
+ this.envValueMode = envValueMode;
+ }
+
/** Gets custom agents. @return the agents */
public List getCustomAgents() {
return customAgents == null ? null : Collections.unmodifiableList(customAgents);
@@ -234,6 +263,16 @@ public void setCustomAgents(List customAgents) {
this.customAgents = customAgents;
}
+ /** Gets the pre-selected agent name. @return the agent name */
+ public String getAgent() {
+ return agent;
+ }
+
+ /** Sets the pre-selected agent name. @param agent the agent name */
+ public void setAgent(String agent) {
+ this.agent = agent;
+ }
+
/** Gets infinite sessions config. @return the config */
public InfiniteSessionConfig getInfiniteSessions() {
return infiniteSessions;
diff --git a/src/main/java/com/github/copilot/sdk/json/MessageOptions.java b/src/main/java/com/github/copilot/sdk/json/MessageOptions.java
index 99c4214b8..6ba629b7b 100644
--- a/src/main/java/com/github/copilot/sdk/json/MessageOptions.java
+++ b/src/main/java/com/github/copilot/sdk/json/MessageOptions.java
@@ -4,6 +4,7 @@
package com.github.copilot.sdk.json;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -107,4 +108,23 @@ public String getMode() {
return mode;
}
+ /**
+ * Creates a shallow clone of this {@code MessageOptions} instance.
+ *
+ * Mutable collection properties are copied into new collection instances so
+ * that modifications to those collections on the clone do not affect the
+ * original. Other reference-type properties (like attachment items) are not
+ * deep-cloned; the original and the clone will share those objects.
+ *
+ * @return a clone of this options instance
+ */
+ @Override
+ public MessageOptions clone() {
+ MessageOptions copy = new MessageOptions();
+ copy.prompt = this.prompt;
+ copy.attachments = this.attachments != null ? new ArrayList<>(this.attachments) : null;
+ copy.mode = this.mode;
+ return copy;
+ }
+
}
diff --git a/src/main/java/com/github/copilot/sdk/json/PermissionHandler.java b/src/main/java/com/github/copilot/sdk/json/PermissionHandler.java
index 259c058f3..21b822e41 100644
--- a/src/main/java/com/github/copilot/sdk/json/PermissionHandler.java
+++ b/src/main/java/com/github/copilot/sdk/json/PermissionHandler.java
@@ -28,6 +28,10 @@
* };
* }
*
+ *
+ * A pre-built handler that approves all requests is available as
+ * {@link #APPROVE_ALL}.
+ *
* @see SessionConfig#setOnPermissionRequest(PermissionHandler)
* @see PermissionRequest
* @see PermissionRequestResult
@@ -36,6 +40,14 @@
@FunctionalInterface
public interface PermissionHandler {
+ /**
+ * A pre-built handler that approves all permission requests.
+ *
+ * @since 1.0.11
+ */
+ PermissionHandler APPROVE_ALL = (request, invocation) -> CompletableFuture
+ .completedFuture(new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED));
+
/**
* Handles a permission request from the assistant.
*