From 2108ece372154f02c47a5c40af15b2d18ead2648 Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Thu, 2 Apr 2026 12:06:09 -0400 Subject: [PATCH 01/47] On branch main Prepare to release 0.2.1-java.1 modified: pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eee587375..c83d7ff9b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java - 0.2.2-java.0-SNAPSHOT + 0.2.1-java.1-SNAPSHOT jar GitHub Copilot SDK :: Java From 8c2071597bf769b635790388fcea6e977d6b2302 Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Thu, 2 Apr 2026 12:15:27 -0400 Subject: [PATCH 02/47] On branch main Document JDK 25 recommended level. 17 is still minimum. modified: README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84f0d6d81..c081349cf 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A ### Requirements -- Java 17 or later -- GitHub Copilot CLI 0.0.411-1 or later installed and in PATH (or provide custom `cliPath`) +- Java 17 or later. **JDK 25 recommended**. Selecting JDK 25 enables the use of virtual threads, as shown in the [Quick Start](#quick-start). +- GitHub Copilot 1.0.15-0 or later installed and in `PATH` (or provide custom `cliPath`) ### Maven From 3ca5671f0cc4b336195ca97321478136d39f408a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Apr 2026 16:21:22 +0000 Subject: [PATCH 03/47] docs: update version references to 0.2.1-java.1 --- CHANGELOG.md | 14 +++++++++++--- README.md | 4 ++-- src/site/markdown/cookbook/error-handling.md | 14 +++++++------- src/site/markdown/cookbook/managing-local-files.md | 4 ++-- src/site/markdown/cookbook/multiple-sessions.md | 4 ++-- src/site/markdown/cookbook/persisting-sessions.md | 6 +++--- src/site/markdown/cookbook/pr-visualization.md | 2 +- 7 files changed, 28 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c54038b9c..e306db097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). > **Upstream sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15) +## [0.2.1-java.1] - 2026-04-02 + +> **Upstream sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15) ## [0.2.1-java.0] - 2026-03-26 > **Upstream sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15) @@ -462,12 +465,17 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` - Pre-commit hook for Spotless code formatting - Comprehensive API documentation -[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...HEAD +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD +[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1 +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD +[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1 [0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0 -[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...HEAD +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD +[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1 [0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0 [0.1.32-java.0]: https://github.com/github/copilot-sdk-java/compare/v1.0.11...v0.1.32-java.0 -[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...HEAD +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD +[0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1 [0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0 [0.1.32-java.0]: https://github.com/github/copilot-sdk-java/compare/v1.0.11...v0.1.32-java.0 [1.0.11]: https://github.com/github/copilot-sdk-java/compare/v1.0.10...v1.0.11 diff --git a/README.md b/README.md index c081349cf..3010b6839 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A com.github copilot-sdk-java - 0.2.1-java.0 + 0.2.1-java.1 ``` @@ -60,7 +60,7 @@ Snapshot builds of the next development version are published to Maven Central S ### Gradle ```groovy -implementation 'com.github:copilot-sdk-java:0.2.1-java.0' +implementation 'com.github:copilot-sdk-java:0.2.1-java.1' ``` ## Quick Start diff --git a/src/site/markdown/cookbook/error-handling.md b/src/site/markdown/cookbook/error-handling.md index d085ecd91..5ee5ef2ca 100644 --- a/src/site/markdown/cookbook/error-handling.md +++ b/src/site/markdown/cookbook/error-handling.md @@ -30,7 +30,7 @@ jbang BasicErrorHandling.java **Code:** ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -64,7 +64,7 @@ public class BasicErrorHandling { ## Handling specific error types ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import java.util.concurrent.ExecutionException; @@ -99,7 +99,7 @@ public class SpecificErrorHandling { ## Timeout handling ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotSession; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -130,7 +130,7 @@ public class TimeoutHandling { ## Aborting a request ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotSession; import com.github.copilot.sdk.json.MessageOptions; import java.util.concurrent.Executors; @@ -162,7 +162,7 @@ public class AbortRequest { ## Graceful shutdown ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; public class GracefulShutdown { @@ -192,7 +192,7 @@ public class GracefulShutdown { ## Try-with-resources pattern ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -224,7 +224,7 @@ public class TryWithResources { ## Handling tool errors ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; diff --git a/src/site/markdown/cookbook/managing-local-files.md b/src/site/markdown/cookbook/managing-local-files.md index 4c0622928..aa9ba23bc 100644 --- a/src/site/markdown/cookbook/managing-local-files.md +++ b/src/site/markdown/cookbook/managing-local-files.md @@ -34,7 +34,7 @@ jbang ManagingLocalFiles.java **Code:** ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.events.SessionIdleEvent; @@ -161,7 +161,7 @@ session.send(new MessageOptions().setPrompt(prompt)); ## Interactive file organization ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import java.io.BufferedReader; import java.io.InputStreamReader; diff --git a/src/site/markdown/cookbook/multiple-sessions.md b/src/site/markdown/cookbook/multiple-sessions.md index 0121bfc4c..fe5c2f0d9 100644 --- a/src/site/markdown/cookbook/multiple-sessions.md +++ b/src/site/markdown/cookbook/multiple-sessions.md @@ -30,7 +30,7 @@ jbang MultipleSessions.java **Code:** ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -123,7 +123,7 @@ try { ## Managing session lifecycle with CompletableFuture ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import java.util.concurrent.CompletableFuture; import java.util.List; diff --git a/src/site/markdown/cookbook/persisting-sessions.md b/src/site/markdown/cookbook/persisting-sessions.md index 213959ce6..e3fd11b13 100644 --- a/src/site/markdown/cookbook/persisting-sessions.md +++ b/src/site/markdown/cookbook/persisting-sessions.md @@ -30,7 +30,7 @@ jbang PersistingSessions.java **Code:** ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -127,7 +127,7 @@ public class DeleteSession { ## Getting session history ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.events.UserMessageEvent; @@ -162,7 +162,7 @@ public class SessionHistory { ## Complete example with session management ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import java.util.Scanner; public class SessionManager { diff --git a/src/site/markdown/cookbook/pr-visualization.md b/src/site/markdown/cookbook/pr-visualization.md index 77b6631b8..dbd240a40 100644 --- a/src/site/markdown/cookbook/pr-visualization.md +++ b/src/site/markdown/cookbook/pr-visualization.md @@ -34,7 +34,7 @@ jbang PRVisualization.java github/copilot-sdk ## Full example: PRVisualization.java ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.0 +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.events.ToolExecutionStartEvent; From 64dbfc0f9d23e2d3498057315980c0dca9faed2b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Apr 2026 16:21:43 +0000 Subject: [PATCH 04/47] [maven-release-plugin] prepare release v0.2.1-java.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c83d7ff9b..43e1b436d 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java - 0.2.1-java.1-SNAPSHOT + 0.2.1-java.1 jar GitHub Copilot SDK :: Java @@ -33,7 +33,7 @@ scm:git:https://github.com/github/copilot-sdk-java.git scm:git:https://github.com/github/copilot-sdk-java.git https://github.com/github/copilot-sdk-java - HEAD + v0.2.1-java.1 From 828d30010eb72f48a3340e524d09356104f91cc8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Apr 2026 16:21:45 +0000 Subject: [PATCH 05/47] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 43e1b436d..45404d99a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java - 0.2.1-java.1 + 0.2.2-java.1-SNAPSHOT jar GitHub Copilot SDK :: Java @@ -33,7 +33,7 @@ scm:git:https://github.com/github/copilot-sdk-java.git scm:git:https://github.com/github/copilot-sdk-java.git https://github.com/github/copilot-sdk-java - v0.2.1-java.1 + HEAD From 3862fdd97a20a6e763efa7873ce426ae45bc4bf4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:29:22 +0000 Subject: [PATCH 06/47] Initial plan From be757e1421924626969ca7dc4df9bd9796aae923 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:36:18 +0000 Subject: [PATCH 07/47] Fix weekly upstream sync agentic workflow prompt Rewrite the prompt from documentation-style to clear imperative instructions. The agent previously misinterpreted the documentation as instructions to perform the merge itself (invoking the agentic-merge-upstream skill), then failed because it couldn't push code from the sandbox. The new prompt: - Gives explicit step-by-step instructions - Provides bash commands for upstream change detection - Directs the agent to use safe-output tools (create_issue, close_issue, assign_to_agent, noop) - Explicitly prohibits invoking skills, editing files, or pushing code Only the markdown content changed; frontmatter is unchanged so no lock file recompilation is needed. Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/945e9454-9363-45bc-b281-0fe3d6c87019 Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- .github/workflows/weekly-upstream-sync.md | 134 ++++++++-------------- 1 file changed, 50 insertions(+), 84 deletions(-) diff --git a/.github/workflows/weekly-upstream-sync.md b/.github/workflows/weekly-upstream-sync.md index 641f29301..0aaff8d1e 100644 --- a/.github/workflows/weekly-upstream-sync.md +++ b/.github/workflows/weekly-upstream-sync.md @@ -41,111 +41,77 @@ safe-outputs: noop: report-as-issue: false --- -# Weekly Upstream Sync Agentic Workflow -This document describes the `weekly-upstream-sync.yml` GitHub Actions workflow, which automates the detection of new changes in the official [Copilot SDK](https://github.com/github/copilot-sdk) and delegates the merge work to the Copilot coding agent. +# Weekly Upstream Sync -## Overview +You are an automation agent that detects new upstream changes and creates GitHub issues. You do **NOT** perform any code merges, edits, or pushes. Do **NOT** invoke any skills (especially `agentic-merge-upstream`). Your only job is to check for changes and use safe-output tools to create or close issues. -The workflow runs on a **weekly schedule** (every Monday at 10:00 UTC) and can also be triggered manually. It does **not** perform the actual merge — instead, it detects upstream changes and creates a GitHub issue assigned to `copilot`, instructing the agent to follow the [agentic-merge-upstream](../prompts/agentic-merge-upstream.prompt.md) prompt to port the changes. +## Instructions -The agent must also create the Pull Request with the label `upstream-sync`. This allows the workflow to track the merge progress and avoid creating duplicate issues if the agent is still working on a previous sync. +Follow these steps exactly: -## Trigger +### Step 1: Read `.lastmerge` -| Trigger | Schedule | -|---|---| -| `schedule` | Every Monday at 10:00 UTC (`0 10 * * 1`) | -| `workflow_dispatch` | Manual trigger from the Actions tab | +Read the file `.lastmerge` in the repository root. It contains the SHA of the last upstream commit that was merged into this Java SDK. -## Workflow Steps +### Step 2: Check for upstream changes -### 1. Checkout repository +Clone the upstream repository and compare commits: -Checks out the repo to read the `.lastmerge` file, which contains the SHA of the last upstream commit that was merged into the Java SDK. - -### 2. Check for upstream changes - -- Reads the last merged commit hash from `.lastmerge` -- Clones the upstream `github/copilot-sdk` repository -- Compares `.lastmerge` against upstream `HEAD` -- If they match: sets `has_changes=false` -- If they differ: counts new commits, generates a summary (up to 20 most recent), and sets outputs (`commit_count`, `upstream_head`, `last_merge`, `summary`) - -### 3. Close previous upstream-sync issues (when changes found) - -**Condition:** `has_changes == true` +```bash +LAST_MERGE=$(cat .lastmerge) +git clone --quiet https://github.com/github/copilot-sdk.git /tmp/gh-aw/agent/upstream +cd /tmp/gh-aw/agent/upstream +UPSTREAM_HEAD=$(git rev-parse HEAD) +``` -Before creating a new issue, closes any existing open issues with the `upstream-sync` label. This prevents stale issues from accumulating when previous sync attempts were incomplete or superseded. Each closed issue receives a comment explaining it was superseded. +If `LAST_MERGE` equals `UPSTREAM_HEAD`, there are **no new changes**. Go to Step 3a. -### 4. Close stale upstream-sync issues (when no changes found) +If they differ, count the new commits and generate a summary: -**Condition:** `has_changes == false` +```bash +COMMIT_COUNT=$(git rev-list --count "$LAST_MERGE".."$UPSTREAM_HEAD") +SUMMARY=$(git log --oneline "$LAST_MERGE".."$UPSTREAM_HEAD" | head -20) +``` -If the upstream is already up to date, closes any lingering open `upstream-sync` issues with a comment noting that no changes were detected. This handles the case where a previous issue was created but the changes were merged manually (updating `.lastmerge`) before the agent completed. +Go to Step 3b. -### 5. Create issue and assign to Copilot +### Step 3a: No changes detected -**Condition:** `has_changes == true` +1. Search for any open issues with the `upstream-sync` label using the GitHub MCP tools. +2. If there are open `upstream-sync` issues, close each one using the `close_issue` safe-output tool with a comment: "No new upstream changes detected. The Java SDK is up to date. Closing." +3. Call the `noop` safe-output tool with message: "No new upstream changes since last merge ()." +4. **Stop here.** Do not proceed further. -Creates a new GitHub issue with: +### Step 3b: Changes detected -- **Title:** `Upstream sync: N new commits (YYYY-MM-DD)` -- **Label:** `upstream-sync` -- **Assignee:** `copilot` -- **Body:** Contains commit count, commit range links, a summary of recent commits, and a link to the merge prompt +1. Search for any open issues with the `upstream-sync` label using the GitHub MCP tools. +2. Close each existing open `upstream-sync` issue using the `close_issue` safe-output tool with a comment: "Superseded by a newer upstream sync check." +3. Create a new issue using the `create_issue` safe-output tool with: + - **Title:** `Upstream sync: new commits ()` + - **Body:** Include the following information: + ``` + ## Automated Upstream Sync -The Copilot coding agent picks up the issue, creates a branch and PR, then follows the merge prompt to port the changes. + There are **** new commits in the [official Copilot SDK](https://github.com/github/copilot-sdk) since the last merge. -### 6. Summary + - **Last merged commit:** [``](https://github.com/github/copilot-sdk/commit/) + - **Upstream HEAD:** [``](https://github.com/github/copilot-sdk/commit/) -Writes a GitHub Actions step summary with: + ### Recent upstream commits -- Whether changes were detected -- Commit count and range -- Recent upstream commits -- Link to the created issue (if any) + ``` + + ``` -## Flow Diagram + ### Instructions -``` -┌─────────────────────┐ -│ Schedule / Manual │ -└──────────┬──────────┘ - │ - ▼ -┌─────────────────────┐ -│ Read .lastmerge │ -│ Clone upstream SDK │ -│ Compare commits │ -└──────────┬──────────┘ - │ - ┌─────┴─────┐ - │ │ - changes? no changes - │ │ - ▼ ▼ -┌──────────┐ ┌──────────────────┐ -│ Close old│ │ Close stale │ -│ issues │ │ issues │ -└────┬─────┘ └──────────────────┘ - │ - ▼ -┌──────────────────────────┐ -│ Create issue assigned to │ -│ copilot │ -└──────────────────────────┘ - │ - ▼ -┌──────────────────────────┐ -│ Agent follows prompt to │ -│ port changes → PR │ -└──────────────────────────┘ -``` + Follow the [agentic-merge-upstream](.github/prompts/agentic-merge-upstream.prompt.md) prompt to port these changes to the Java SDK. + ``` +4. After creating the issue, use the `assign_to_agent` safe-output tool to assign Copilot to the newly created issue. -## Related Files +## Important constraints -| File | Purpose | -|---|---| -| `.lastmerge` | Stores the SHA of the last merged upstream commit | -| [agentic-merge-upstream.prompt.md](../prompts/agentic-merge-upstream.prompt.md) | Detailed instructions the Copilot agent follows to port changes | -| `.github/scripts/upstream-sync/` | Helper scripts used by the merge prompt | +- **Do NOT edit any files**, create branches, or push code. +- **Do NOT invoke any skills** such as `agentic-merge-upstream` or `commit-as-pull-request`. +- **Do NOT attempt to merge or port upstream changes.** That is done by a separate agent that picks up the issue you create. +- You **MUST** call at least one safe-output tool (`create_issue`, `close_issue`, `noop`, etc.) before finishing. From 844f3cf399af5f45279ed2cd6b8110e84bfe7a7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:24:14 +0000 Subject: [PATCH 08/47] Initial plan From cc84fab663d4809e3592ff21193bdd9cf8964cf0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:46:28 +0000 Subject: [PATCH 09/47] Port Commands, Elicitation, and Capabilities features from upstream Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/16d575c4-83f4-4d20-99d9-b48635b3791d Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .../com/github/copilot/sdk/CopilotClient.java | 31 ++ .../github/copilot/sdk/CopilotSession.java | 373 ++++++++++++++++++ .../copilot/sdk/SessionRequestBuilder.java | 33 ++ .../sdk/events/AbstractSessionEvent.java | 4 +- .../sdk/events/CapabilitiesChangedEvent.java | 44 +++ .../sdk/events/CommandExecuteEvent.java | 43 ++ .../sdk/events/ElicitationRequestedEvent.java | 54 +++ .../sdk/events/PermissionRequestedEvent.java | 3 +- .../sdk/events/SessionEventParser.java | 3 + .../copilot/sdk/json/CommandContext.java | 74 ++++ .../copilot/sdk/json/CommandDefinition.java | 98 +++++ .../copilot/sdk/json/CommandHandler.java | 41 ++ .../sdk/json/CommandWireDefinition.java | 58 +++ .../sdk/json/CreateSessionRequest.java | 26 ++ .../sdk/json/CreateSessionResponse.java | 5 +- .../copilot/sdk/json/ElicitationContext.java | 112 ++++++ .../copilot/sdk/json/ElicitationHandler.java | 44 +++ .../copilot/sdk/json/ElicitationParams.java | 58 +++ .../copilot/sdk/json/ElicitationResult.java | 68 ++++ .../sdk/json/ElicitationResultAction.java | 33 ++ .../copilot/sdk/json/ElicitationSchema.java | 92 +++++ .../sdk/json/GetSessionMetadataResponse.java | 15 + .../github/copilot/sdk/json/InputOptions.java | 108 +++++ .../copilot/sdk/json/ResumeSessionConfig.java | 54 +++ .../sdk/json/ResumeSessionRequest.java | 26 ++ .../sdk/json/ResumeSessionResponse.java | 5 +- .../copilot/sdk/json/SessionCapabilities.java | 39 ++ .../copilot/sdk/json/SessionConfig.java | 54 +++ .../github/copilot/sdk/json/SessionUiApi.java | 85 ++++ .../sdk/json/SessionUiCapabilities.java | 37 ++ 30 files changed, 1715 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/github/copilot/sdk/events/CapabilitiesChangedEvent.java create mode 100644 src/main/java/com/github/copilot/sdk/events/CommandExecuteEvent.java create mode 100644 src/main/java/com/github/copilot/sdk/events/ElicitationRequestedEvent.java create mode 100644 src/main/java/com/github/copilot/sdk/json/CommandContext.java create mode 100644 src/main/java/com/github/copilot/sdk/json/CommandDefinition.java create mode 100644 src/main/java/com/github/copilot/sdk/json/CommandHandler.java create mode 100644 src/main/java/com/github/copilot/sdk/json/CommandWireDefinition.java create mode 100644 src/main/java/com/github/copilot/sdk/json/ElicitationContext.java create mode 100644 src/main/java/com/github/copilot/sdk/json/ElicitationHandler.java create mode 100644 src/main/java/com/github/copilot/sdk/json/ElicitationParams.java create mode 100644 src/main/java/com/github/copilot/sdk/json/ElicitationResult.java create mode 100644 src/main/java/com/github/copilot/sdk/json/ElicitationResultAction.java create mode 100644 src/main/java/com/github/copilot/sdk/json/ElicitationSchema.java create mode 100644 src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java create mode 100644 src/main/java/com/github/copilot/sdk/json/InputOptions.java create mode 100644 src/main/java/com/github/copilot/sdk/json/SessionCapabilities.java create mode 100644 src/main/java/com/github/copilot/sdk/json/SessionUiApi.java create mode 100644 src/main/java/com/github/copilot/sdk/json/SessionUiCapabilities.java diff --git a/src/main/java/com/github/copilot/sdk/CopilotClient.java b/src/main/java/com/github/copilot/sdk/CopilotClient.java index e2790f6a3..f00e2fd11 100644 --- a/src/main/java/com/github/copilot/sdk/CopilotClient.java +++ b/src/main/java/com/github/copilot/sdk/CopilotClient.java @@ -24,6 +24,7 @@ import com.github.copilot.sdk.json.DeleteSessionResponse; import com.github.copilot.sdk.json.GetAuthStatusResponse; import com.github.copilot.sdk.json.GetLastSessionIdResponse; +import com.github.copilot.sdk.json.GetSessionMetadataResponse; import com.github.copilot.sdk.json.GetModelsResponse; import com.github.copilot.sdk.json.GetStatusResponse; import com.github.copilot.sdk.json.ListSessionsResponse; @@ -374,6 +375,7 @@ public CompletableFuture createSession(SessionConfig config) { return connection.rpc.invoke("session.create", request, CreateSessionResponse.class).thenApply(response -> { session.setWorkspacePath(response.workspacePath()); + session.setCapabilities(response.capabilities()); // 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(); @@ -444,6 +446,7 @@ public CompletableFuture resumeSession(String sessionId, ResumeS return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class).thenApply(response -> { session.setWorkspacePath(response.workspacePath()); + session.setCapabilities(response.capabilities()); // If the server returned a different sessionId than what was requested, re-key. String returnedId = response.sessionId(); if (returnedId != null && !returnedId.equals(sessionId)) { @@ -657,6 +660,34 @@ public CompletableFuture> listSessions(SessionListFilter f }); } + /** + * Gets metadata for a specific session by ID. + *

+ * This provides an efficient O(1) lookup of a single session's metadata instead + * of listing all sessions. + * + *

Example Usage

+ * + *
{@code
+     * var metadata = client.getSessionMetadata("session-123").get();
+     * if (metadata != null) {
+     * 	System.out.println("Session started at: " + metadata.getStartTime());
+     * }
+     * }
+ * + * @param sessionId + * the ID of the session to look up + * @return a future that resolves with the {@link SessionMetadata}, or + * {@code null} if the session was not found + * @see SessionMetadata + * @since 1.0.0 + */ + public CompletableFuture getSessionMetadata(String sessionId) { + return ensureConnected().thenCompose(connection -> connection.rpc + .invoke("session.getMetadata", Map.of("sessionId", sessionId), GetSessionMetadataResponse.class) + .thenApply(GetSessionMetadataResponse::session)); + } + /** * Gets the ID of the session currently displayed in the TUI. *

diff --git a/src/main/java/com/github/copilot/sdk/CopilotSession.java b/src/main/java/com/github/copilot/sdk/CopilotSession.java index 844737fc2..768f0adaa 100644 --- a/src/main/java/com/github/copilot/sdk/CopilotSession.java +++ b/src/main/java/com/github/copilot/sdk/CopilotSession.java @@ -31,14 +31,27 @@ 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.CapabilitiesChangedEvent; +import com.github.copilot.sdk.events.CommandExecuteEvent; +import com.github.copilot.sdk.events.ElicitationRequestedEvent; 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.CommandContext; +import com.github.copilot.sdk.json.CommandDefinition; +import com.github.copilot.sdk.json.CommandHandler; +import com.github.copilot.sdk.json.ElicitationContext; +import com.github.copilot.sdk.json.ElicitationHandler; +import com.github.copilot.sdk.json.ElicitationParams; +import com.github.copilot.sdk.json.ElicitationResult; +import com.github.copilot.sdk.json.ElicitationResultAction; +import com.github.copilot.sdk.json.ElicitationSchema; import com.github.copilot.sdk.json.GetMessagesResponse; import com.github.copilot.sdk.json.HookInvocation; +import com.github.copilot.sdk.json.InputOptions; import com.github.copilot.sdk.json.MessageOptions; import com.github.copilot.sdk.json.PermissionHandler; import com.github.copilot.sdk.json.PermissionInvocation; @@ -49,9 +62,12 @@ import com.github.copilot.sdk.json.PreToolUseHookInput; import com.github.copilot.sdk.json.SendMessageRequest; import com.github.copilot.sdk.json.SendMessageResponse; +import com.github.copilot.sdk.json.SessionCapabilities; import com.github.copilot.sdk.json.SessionEndHookInput; import com.github.copilot.sdk.json.SessionHooks; import com.github.copilot.sdk.json.SessionStartHookInput; +import com.github.copilot.sdk.json.SessionUiApi; +import com.github.copilot.sdk.json.SessionUiCapabilities; import com.github.copilot.sdk.json.ToolDefinition; import com.github.copilot.sdk.json.ToolResultObject; import com.github.copilot.sdk.json.UserInputHandler; @@ -116,11 +132,15 @@ public final class CopilotSession implements AutoCloseable { */ private volatile String sessionId; private volatile String workspacePath; + private volatile SessionCapabilities capabilities = new SessionCapabilities(); + private final SessionUiApi ui; private final JsonRpcClient rpc; private final Set> eventHandlers = ConcurrentHashMap.newKeySet(); private final Map toolHandlers = new ConcurrentHashMap<>(); + private final Map commandHandlers = new ConcurrentHashMap<>(); private final AtomicReference permissionHandler = new AtomicReference<>(); private final AtomicReference userInputHandler = new AtomicReference<>(); + private final AtomicReference elicitationHandler = new AtomicReference<>(); private final AtomicReference hooksHandler = new AtomicReference<>(); private volatile EventErrorHandler eventErrorHandler; private volatile EventErrorPolicy eventErrorPolicy = EventErrorPolicy.PROPAGATE_AND_LOG_ERRORS; @@ -163,6 +183,7 @@ public final class CopilotSession implements AutoCloseable { this.sessionId = sessionId; this.rpc = rpc; this.workspacePath = workspacePath; + this.ui = new SessionUiApiImpl(); var executor = new ScheduledThreadPoolExecutor(1, r -> { var t = new Thread(r, "sendAndWait-timeout"); t.setDaemon(true); @@ -225,6 +246,30 @@ void setWorkspacePath(String workspacePath) { this.workspacePath = workspacePath; } + /** + * Gets the capabilities reported by the host for this session. + *

+ * Capabilities are populated from the session create/resume response and + * updated in real time via {@code capabilities.changed} events. + * + * @return the session capabilities (never {@code null}) + */ + public SessionCapabilities getCapabilities() { + return capabilities; + } + + /** + * Gets the UI API for eliciting information from the user during this session. + *

+ * All methods on this API throw {@link IllegalStateException} if the host does + * not report elicitation support via {@link #getCapabilities()}. + * + * @return the UI API + */ + public SessionUiApi getUi() { + return ui; + } + /** * Sets a custom error handler for exceptions thrown by event handlers. *

@@ -669,11 +714,49 @@ private void handleBroadcastEventAsync(AbstractSessionEvent event) { if (data == null || data.requestId() == null || data.permissionRequest() == null) { return; } + if (Boolean.TRUE.equals(data.resolvedByHook())) { + return; // Already resolved by a permissionRequest hook; no client action needed. + } PermissionHandler handler = permissionHandler.get(); if (handler == null) { return; // This client doesn't handle permissions; another client will } executePermissionAndRespondAsync(data.requestId(), data.permissionRequest(), handler); + } else if (event instanceof CommandExecuteEvent cmdEvent) { + var data = cmdEvent.getData(); + if (data == null || data.requestId() == null) { + return; + } + executeCommandAndRespondAsync(data.requestId(), data.commandName(), data.command(), data.args()); + } else if (event instanceof ElicitationRequestedEvent elicitEvent) { + var data = elicitEvent.getData(); + if (data == null || data.requestId() == null) { + return; + } + ElicitationHandler handler = elicitationHandler.get(); + if (handler != null) { + ElicitationSchema schema = null; + if (data.requestedSchema() != null) { + schema = new ElicitationSchema().setType(data.requestedSchema().type()) + .setProperties(data.requestedSchema().properties()) + .setRequired(data.requestedSchema().required()); + } + var context = new ElicitationContext().setSessionId(sessionId).setMessage(data.message()) + .setRequestedSchema(schema).setMode(data.mode()).setElicitationSource(data.elicitationSource()) + .setUrl(data.url()); + handleElicitationRequestAsync(context, data.requestId()); + } + } else if (event instanceof CapabilitiesChangedEvent capEvent) { + var data = capEvent.getData(); + if (data != null) { + var newCapabilities = new SessionCapabilities(); + if (data.ui() != null) { + newCapabilities.setUi(new SessionUiCapabilities().setElicitation(data.ui().elicitation())); + } else { + newCapabilities.setUi(capabilities.getUi()); + } + capabilities = newCapabilities; + } } } @@ -816,6 +899,250 @@ void registerTools(List tools) { } } + /** + * Executes a command handler and sends the result back via + * {@code session.commands.handlePendingCommand}. + */ + private void executeCommandAndRespondAsync(String requestId, String commandName, String command, String args) { + CommandHandler handler = commandHandlers.get(commandName); + Runnable task = () -> { + if (handler == null) { + try { + rpc.invoke("session.commands.handlePendingCommand", Map.of("sessionId", sessionId, "requestId", + requestId, "error", "Unknown command: " + commandName), Object.class); + } catch (Exception e) { + LOG.log(Level.WARNING, "Error sending command error for requestId=" + requestId, e); + } + return; + } + try { + var ctx = new CommandContext().setSessionId(sessionId).setCommand(command).setCommandName(commandName) + .setArgs(args); + handler.handle(ctx).thenRun(() -> { + try { + rpc.invoke("session.commands.handlePendingCommand", + Map.of("sessionId", sessionId, "requestId", requestId), Object.class); + } catch (Exception e) { + LOG.log(Level.WARNING, "Error sending command result for requestId=" + requestId, e); + } + }).exceptionally(ex -> { + try { + String msg = ex.getMessage() != null ? ex.getMessage() : ex.toString(); + rpc.invoke("session.commands.handlePendingCommand", + Map.of("sessionId", sessionId, "requestId", requestId, "error", msg), Object.class); + } catch (Exception e) { + LOG.log(Level.WARNING, "Error sending command error for requestId=" + requestId, e); + } + return null; + }); + } catch (Exception e) { + LOG.log(Level.WARNING, "Error executing command for requestId=" + requestId, e); + try { + String msg = e.getMessage() != null ? e.getMessage() : e.toString(); + rpc.invoke("session.commands.handlePendingCommand", + Map.of("sessionId", sessionId, "requestId", requestId, "error", msg), Object.class); + } catch (Exception sendEx) { + LOG.log(Level.WARNING, "Error sending command error for requestId=" + requestId, sendEx); + } + } + }; + try { + if (executor != null) { + CompletableFuture.runAsync(task, executor); + } else { + CompletableFuture.runAsync(task); + } + } catch (RejectedExecutionException e) { + LOG.log(Level.WARNING, "Executor rejected command task for requestId=" + requestId + "; running inline", e); + task.run(); + } + } + + /** + * Dispatches an elicitation request to the registered handler and responds via + * {@code session.ui.handlePendingElicitation}. Auto-cancels on handler errors. + */ + private void handleElicitationRequestAsync(ElicitationContext context, String requestId) { + ElicitationHandler handler = elicitationHandler.get(); + if (handler == null) { + return; + } + Runnable task = () -> { + try { + handler.handle(context).thenAccept(result -> { + try { + String actionStr = result.getAction() != null + ? result.getAction().getValue() + : ElicitationResultAction.CANCEL.getValue(); + Map resultMap = result.getContent() != null + ? Map.of("action", actionStr, "content", result.getContent()) + : Map.of("action", actionStr); + rpc.invoke("session.ui.handlePendingElicitation", + Map.of("sessionId", sessionId, "requestId", requestId, "result", resultMap), + Object.class); + } catch (Exception e) { + LOG.log(Level.WARNING, "Error sending elicitation result for requestId=" + requestId, e); + } + }).exceptionally(ex -> { + try { + rpc.invoke("session.ui.handlePendingElicitation", Map.of("sessionId", sessionId, "requestId", + requestId, "result", Map.of("action", ElicitationResultAction.CANCEL.getValue())), + Object.class); + } catch (Exception e) { + LOG.log(Level.WARNING, "Error sending elicitation cancel for requestId=" + requestId, e); + } + return null; + }); + } catch (Exception e) { + LOG.log(Level.WARNING, "Error executing elicitation handler for requestId=" + requestId, e); + try { + rpc.invoke( + "session.ui.handlePendingElicitation", Map.of("sessionId", sessionId, "requestId", + requestId, "result", Map.of("action", ElicitationResultAction.CANCEL.getValue())), + Object.class); + } catch (Exception sendEx) { + LOG.log(Level.WARNING, "Error sending elicitation cancel for requestId=" + requestId, sendEx); + } + } + }; + try { + if (executor != null) { + CompletableFuture.runAsync(task, executor); + } else { + CompletableFuture.runAsync(task); + } + } catch (RejectedExecutionException e) { + LOG.log(Level.WARNING, "Executor rejected elicitation task for requestId=" + requestId + "; running inline", + e); + task.run(); + } + } + + /** + * Throws if the host does not support elicitation. + */ + private void assertElicitation() { + SessionCapabilities caps = capabilities; + if (caps == null || caps.getUi() == null || !Boolean.TRUE.equals(caps.getUi().getElicitation())) { + throw new IllegalStateException("Elicitation is not supported by the host. " + + "Check session.getCapabilities().getUi()?.getElicitation() before calling UI methods."); + } + } + + /** + * Implements {@link SessionUiApi} backed by the session's RPC connection. + */ + private final class SessionUiApiImpl implements SessionUiApi { + + @Override + public CompletableFuture elicitation(ElicitationParams params) { + assertElicitation(); + var schema = new java.util.HashMap(); + schema.put("type", params.getRequestedSchema().getType()); + schema.put("properties", params.getRequestedSchema().getProperties()); + if (params.getRequestedSchema().getRequired() != null) { + schema.put("required", params.getRequestedSchema().getRequired()); + } + return rpc.invoke("session.ui.elicitation", + Map.of("sessionId", sessionId, "message", params.getMessage(), "requestedSchema", schema), + ElicitationRpcResponse.class).thenApply(resp -> { + var result = new ElicitationResult(); + if (resp.action() != null) { + for (ElicitationResultAction a : ElicitationResultAction.values()) { + if (a.getValue().equalsIgnoreCase(resp.action())) { + result.setAction(a); + break; + } + } + } + if (result.getAction() == null) { + result.setAction(ElicitationResultAction.CANCEL); + } + result.setContent(resp.content()); + return result; + }); + } + + @Override + public CompletableFuture confirm(String message) { + assertElicitation(); + var field = Map.of("type", "boolean", "default", (Object) true); + var schema = Map.of("type", (Object) "object", "properties", (Object) Map.of("confirmed", (Object) field), + "required", (Object) new String[]{"confirmed"}); + return rpc.invoke("session.ui.elicitation", + Map.of("sessionId", sessionId, "message", message, "requestedSchema", schema), + ElicitationRpcResponse.class).thenApply(resp -> { + if ("accept".equalsIgnoreCase(resp.action()) && resp.content() != null) { + Object val = resp.content().get("confirmed"); + if (val instanceof Boolean b) { + return b; + } + if (val instanceof com.fasterxml.jackson.databind.node.BooleanNode bn) { + return bn.booleanValue(); + } + if (val instanceof String s) { + return Boolean.parseBoolean(s); + } + } + return false; + }); + } + + @Override + public CompletableFuture select(String message, String[] options) { + assertElicitation(); + var field = Map.of("type", (Object) "string", "enum", (Object) options); + var schema = Map.of("type", (Object) "object", "properties", (Object) Map.of("selection", (Object) field), + "required", (Object) new String[]{"selection"}); + return rpc.invoke("session.ui.elicitation", + Map.of("sessionId", sessionId, "message", message, "requestedSchema", schema), + ElicitationRpcResponse.class).thenApply(resp -> { + if ("accept".equalsIgnoreCase(resp.action()) && resp.content() != null) { + Object val = resp.content().get("selection"); + return val != null ? val.toString() : null; + } + return null; + }); + } + + @Override + public CompletableFuture input(String message, InputOptions options) { + assertElicitation(); + var field = new java.util.LinkedHashMap(); + field.put("type", "string"); + if (options != null) { + if (options.getTitle() != null) + field.put("title", options.getTitle()); + if (options.getDescription() != null) + field.put("description", options.getDescription()); + if (options.getMinLength() != null) + field.put("minLength", options.getMinLength()); + if (options.getMaxLength() != null) + field.put("maxLength", options.getMaxLength()); + if (options.getFormat() != null) + field.put("format", options.getFormat()); + if (options.getDefaultValue() != null) + field.put("default", options.getDefaultValue()); + } + var schema = Map.of("type", (Object) "object", "properties", (Object) Map.of("value", (Object) field), + "required", (Object) new String[]{"value"}); + return rpc.invoke("session.ui.elicitation", + Map.of("sessionId", sessionId, "message", message, "requestedSchema", schema), + ElicitationRpcResponse.class).thenApply(resp -> { + if ("accept".equalsIgnoreCase(resp.action()) && resp.content() != null) { + Object val = resp.content().get("value"); + return val != null ? val.toString() : null; + } + return null; + }); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private record ElicitationRpcResponse(@JsonProperty("action") String action, + @JsonProperty("content") Map content) { + } + /** * Retrieves a registered tool by name. * @@ -888,6 +1215,50 @@ void registerUserInputHandler(UserInputHandler handler) { userInputHandler.set(handler); } + /** + * Registers command handlers for this session. + *

+ * Called internally when creating or resuming a session with commands. + * + * @param commands + * the command definitions to register + */ + void registerCommands(java.util.List commands) { + commandHandlers.clear(); + if (commands != null) { + for (CommandDefinition cmd : commands) { + if (cmd.getName() != null && cmd.getHandler() != null) { + commandHandlers.put(cmd.getName(), cmd.getHandler()); + } + } + } + } + + /** + * Registers an elicitation handler for this session. + *

+ * Called internally when creating or resuming a session with an elicitation + * handler. + * + * @param handler + * the handler to invoke when an elicitation request is received + */ + void registerElicitationHandler(ElicitationHandler handler) { + elicitationHandler.set(handler); + } + + /** + * Sets the capabilities reported by the host for this session. + *

+ * Called internally after session create/resume response. + * + * @param sessionCapabilities + * the capabilities to set, or {@code null} for empty capabilities + */ + void setCapabilities(SessionCapabilities sessionCapabilities) { + this.capabilities = sessionCapabilities != null ? sessionCapabilities : new SessionCapabilities(); + } + /** * Handles a user input request from the Copilot CLI. *

@@ -1366,8 +1737,10 @@ public void close() { eventHandlers.clear(); toolHandlers.clear(); + commandHandlers.clear(); permissionHandler.set(null); userInputHandler.set(null); + elicitationHandler.set(null); hooksHandler.set(null); } diff --git a/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java b/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java index 6f1cd573c..d74bbfaf3 100644 --- a/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java +++ b/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java @@ -10,6 +10,7 @@ import java.util.function.Function; import com.github.copilot.sdk.json.CreateSessionRequest; +import com.github.copilot.sdk.json.CommandWireDefinition; import com.github.copilot.sdk.json.ResumeSessionConfig; import com.github.copilot.sdk.json.ResumeSessionRequest; import com.github.copilot.sdk.json.SectionOverride; @@ -122,6 +123,16 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setDisabledSkills(config.getDisabledSkills()); request.setConfigDir(config.getConfigDir()); + if (config.getCommands() != null && !config.getCommands().isEmpty()) { + var wireCommands = config.getCommands().stream() + .map(c -> new CommandWireDefinition(c.getName(), c.getDescription())) + .collect(java.util.stream.Collectors.toList()); + request.setCommands(wireCommands); + } + if (config.getOnElicitationRequest() != null) { + request.setRequestElicitation(true); + } + return request; } @@ -183,6 +194,16 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setDisabledSkills(config.getDisabledSkills()); request.setInfiniteSessions(config.getInfiniteSessions()); + if (config.getCommands() != null && !config.getCommands().isEmpty()) { + var wireCommands = config.getCommands().stream() + .map(c -> new CommandWireDefinition(c.getName(), c.getDescription())) + .collect(java.util.stream.Collectors.toList()); + request.setCommands(wireCommands); + } + if (config.getOnElicitationRequest() != null) { + request.setRequestElicitation(true); + } + return request; } @@ -211,6 +232,12 @@ static void configureSession(CopilotSession session, SessionConfig config) { if (config.getHooks() != null) { session.registerHooks(config.getHooks()); } + if (config.getCommands() != null) { + session.registerCommands(config.getCommands()); + } + if (config.getOnElicitationRequest() != null) { + session.registerElicitationHandler(config.getOnElicitationRequest()); + } if (config.getOnEvent() != null) { session.on(config.getOnEvent()); } @@ -241,6 +268,12 @@ static void configureSession(CopilotSession session, ResumeSessionConfig config) if (config.getHooks() != null) { session.registerHooks(config.getHooks()); } + if (config.getCommands() != null) { + session.registerCommands(config.getCommands()); + } + if (config.getOnElicitationRequest() != null) { + session.registerElicitationHandler(config.getOnElicitationRequest()); + } if (config.getOnEvent() != null) { session.on(config.getOnEvent()); } diff --git a/src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java b/src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java index 4626bb4f8..51f6d8712 100644 --- a/src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java +++ b/src/main/java/com/github/copilot/sdk/events/AbstractSessionEvent.java @@ -65,8 +65,8 @@ public abstract sealed class AbstractSessionEvent permits ToolExecutionCompleteEvent, // Broadcast request/completion events (protocol v3) ExternalToolRequestedEvent, ExternalToolCompletedEvent, PermissionRequestedEvent, PermissionCompletedEvent, - CommandQueuedEvent, CommandCompletedEvent, ExitPlanModeRequestedEvent, ExitPlanModeCompletedEvent, - SystemNotificationEvent, + CommandQueuedEvent, CommandCompletedEvent, CommandExecuteEvent, ElicitationRequestedEvent, + CapabilitiesChangedEvent, ExitPlanModeRequestedEvent, ExitPlanModeCompletedEvent, SystemNotificationEvent, // User events UserMessageEvent, PendingMessagesModifiedEvent, // Skill events diff --git a/src/main/java/com/github/copilot/sdk/events/CapabilitiesChangedEvent.java b/src/main/java/com/github/copilot/sdk/events/CapabilitiesChangedEvent.java new file mode 100644 index 000000000..0db68189d --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/events/CapabilitiesChangedEvent.java @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * 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: capabilities.changed + *

+ * Broadcast when the host's session capabilities change. The SDK updates + * {@link com.github.copilot.sdk.CopilotSession#getCapabilities()} accordingly. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class CapabilitiesChangedEvent extends AbstractSessionEvent { + + @JsonProperty("data") + private CapabilitiesChangedData data; + + @Override + public String getType() { + return "capabilities.changed"; + } + + public CapabilitiesChangedData getData() { + return data; + } + + public void setData(CapabilitiesChangedData data) { + this.data = data; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record CapabilitiesChangedData(@JsonProperty("ui") CapabilitiesChangedUi ui) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record CapabilitiesChangedUi(@JsonProperty("elicitation") Boolean elicitation) { + } +} diff --git a/src/main/java/com/github/copilot/sdk/events/CommandExecuteEvent.java b/src/main/java/com/github/copilot/sdk/events/CommandExecuteEvent.java new file mode 100644 index 000000000..c08c4a88d --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/events/CommandExecuteEvent.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: command.execute + *

+ * Broadcast when the user executes a slash command registered by this client. + * Clients that have a matching command handler should respond via + * {@code session.commands.handlePendingCommand}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class CommandExecuteEvent extends AbstractSessionEvent { + + @JsonProperty("data") + private CommandExecuteData data; + + @Override + public String getType() { + return "command.execute"; + } + + public CommandExecuteData getData() { + return data; + } + + public void setData(CommandExecuteData data) { + this.data = data; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record CommandExecuteData(@JsonProperty("requestId") String requestId, + @JsonProperty("command") String command, @JsonProperty("commandName") String commandName, + @JsonProperty("args") String args) { + } +} diff --git a/src/main/java/com/github/copilot/sdk/events/ElicitationRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/ElicitationRequestedEvent.java new file mode 100644 index 000000000..e459dfb77 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/events/ElicitationRequestedEvent.java @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.events; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Event: elicitation.requested + *

+ * Broadcast when the server or an MCP tool requests structured input from the + * user. Clients that have an elicitation handler should respond via + * {@code session.ui.handlePendingElicitation}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class ElicitationRequestedEvent extends AbstractSessionEvent { + + @JsonProperty("data") + private ElicitationRequestedData data; + + @Override + public String getType() { + return "elicitation.requested"; + } + + public ElicitationRequestedData getData() { + return data; + } + + public void setData(ElicitationRequestedData data) { + this.data = data; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record ElicitationRequestedData(@JsonProperty("requestId") String requestId, + @JsonProperty("toolCallId") String toolCallId, @JsonProperty("elicitationSource") String elicitationSource, + @JsonProperty("message") String message, @JsonProperty("mode") String mode, + @JsonProperty("requestedSchema") ElicitationRequestedSchema requestedSchema, + @JsonProperty("url") String url) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record ElicitationRequestedSchema(@JsonProperty("type") String type, + @JsonProperty("properties") Map properties, + @JsonProperty("required") List required) { + } +} diff --git a/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java b/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java index d8f9ec147..7ebce5ac7 100644 --- a/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java +++ b/src/main/java/com/github/copilot/sdk/events/PermissionRequestedEvent.java @@ -38,6 +38,7 @@ public void setData(PermissionRequestedData data) { @JsonIgnoreProperties(ignoreUnknown = true) public record PermissionRequestedData(@JsonProperty("requestId") String requestId, - @JsonProperty("permissionRequest") PermissionRequest permissionRequest) { + @JsonProperty("permissionRequest") PermissionRequest permissionRequest, + @JsonProperty("resolvedByHook") Boolean resolvedByHook) { } } 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 308317e6b..dda971769 100644 --- a/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java +++ b/src/main/java/com/github/copilot/sdk/events/SessionEventParser.java @@ -99,6 +99,9 @@ public class SessionEventParser { TYPE_MAP.put("permission.completed", PermissionCompletedEvent.class); TYPE_MAP.put("command.queued", CommandQueuedEvent.class); TYPE_MAP.put("command.completed", CommandCompletedEvent.class); + TYPE_MAP.put("command.execute", CommandExecuteEvent.class); + TYPE_MAP.put("elicitation.requested", ElicitationRequestedEvent.class); + TYPE_MAP.put("capabilities.changed", CapabilitiesChangedEvent.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/json/CommandContext.java b/src/main/java/com/github/copilot/sdk/json/CommandContext.java new file mode 100644 index 000000000..4657699bb --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/CommandContext.java @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +/** + * Context passed to a {@link CommandHandler} when a slash command is executed. + * + * @since 1.0.0 + */ +public class CommandContext { + + private String sessionId; + private String command; + private String commandName; + private String args; + + /** Gets the session ID where the command was invoked. @return the session ID */ + public String getSessionId() { + return sessionId; + } + + /** Sets the session ID. @param sessionId the session ID @return this */ + public CommandContext setSessionId(String sessionId) { + this.sessionId = sessionId; + return this; + } + + /** + * Gets the full command text (e.g., {@code /deploy production}). + * + * @return the full command text + */ + public String getCommand() { + return command; + } + + /** Sets the full command text. @param command the command text @return this */ + public CommandContext setCommand(String command) { + this.command = command; + return this; + } + + /** + * Gets the command name without the leading {@code /}. + * + * @return the command name + */ + public String getCommandName() { + return commandName; + } + + /** Sets the command name. @param commandName the command name @return this */ + public CommandContext setCommandName(String commandName) { + this.commandName = commandName; + return this; + } + + /** + * Gets the raw argument string after the command name. + * + * @return the argument string + */ + public String getArgs() { + return args; + } + + /** Sets the argument string. @param args the argument string @return this */ + public CommandContext setArgs(String args) { + this.args = args; + return this; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/CommandDefinition.java b/src/main/java/com/github/copilot/sdk/json/CommandDefinition.java new file mode 100644 index 000000000..33a6cbada --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/CommandDefinition.java @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +/** + * Defines a slash command that users can invoke from the CLI TUI. + *

+ * Register commands via {@link SessionConfig#setCommands(java.util.List)} or + * {@link ResumeSessionConfig#setCommands(java.util.List)}. Each command appears + * as {@code /name} in the CLI TUI. + * + *

Example Usage

+ * + *
{@code
+ * var config = new SessionConfig().setCommands(List.of(
+ * 		new CommandDefinition().setName("deploy").setDescription("Deploy the application").setHandler(context -> {
+ * 			System.out.println("Deploying: " + context.getArgs());
+ * 			return CompletableFuture.completedFuture(null);
+ * 		})));
+ * }
+ * + * @see CommandHandler + * @see CommandContext + * @since 1.0.0 + */ +public class CommandDefinition { + + private String name; + private String description; + private CommandHandler handler; + + /** + * Gets the command name (without leading {@code /}). + * + * @return the command name + */ + public String getName() { + return name; + } + + /** + * Sets the command name (without leading {@code /}). + *

+ * For example, {@code "deploy"} registers the {@code /deploy} command. + * + * @param name + * the command name + * @return this instance for method chaining + */ + public CommandDefinition setName(String name) { + this.name = name; + return this; + } + + /** + * Gets the human-readable description shown in the command completion UI. + * + * @return the description, or {@code null} if not set + */ + public String getDescription() { + return description; + } + + /** + * Sets the human-readable description shown in the command completion UI. + * + * @param description + * the description + * @return this instance for method chaining + */ + public CommandDefinition setDescription(String description) { + this.description = description; + return this; + } + + /** + * Gets the handler invoked when the command is executed. + * + * @return the command handler + */ + public CommandHandler getHandler() { + return handler; + } + + /** + * Sets the handler invoked when the command is executed. + * + * @param handler + * the command handler + * @return this instance for method chaining + */ + public CommandDefinition setHandler(CommandHandler handler) { + this.handler = handler; + return this; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/CommandHandler.java b/src/main/java/com/github/copilot/sdk/json/CommandHandler.java new file mode 100644 index 000000000..d63955638 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/CommandHandler.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +import java.util.concurrent.CompletableFuture; + +/** + * Functional interface for handling slash-command executions. + *

+ * Implement this interface to define the behavior of a registered slash + * command. The handler is invoked when the user executes the command in the CLI + * TUI. + * + *

Example Usage

+ * + *
{@code
+ * CommandHandler deployHandler = context -> {
+ * 	System.out.println("Deploying with args: " + context.getArgs());
+ * 	// perform deployment...
+ * 	return CompletableFuture.completedFuture(null);
+ * };
+ * }
+ * + * @see CommandDefinition + * @since 1.0.0 + */ +@FunctionalInterface +public interface CommandHandler { + + /** + * Handles a slash-command execution. + * + * @param context + * the command context containing session ID, command text, and + * arguments + * @return a future that completes when the command handling is done + */ + CompletableFuture handle(CommandContext context); +} diff --git a/src/main/java/com/github/copilot/sdk/json/CommandWireDefinition.java b/src/main/java/com/github/copilot/sdk/json/CommandWireDefinition.java new file mode 100644 index 000000000..2ee65c58e --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/CommandWireDefinition.java @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Wire-format representation of a command definition for RPC serialization. + *

+ * This is a low-level class used internally. Use {@link CommandDefinition} to + * define commands for a session. + * + * @since 1.0.0 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public final class CommandWireDefinition { + + @JsonProperty("name") + private String name; + + @JsonProperty("description") + private String description; + + /** Creates an empty definition. */ + public CommandWireDefinition() { + } + + /** Creates a definition with name and description. */ + public CommandWireDefinition(String name, String description) { + this.name = name; + this.description = description; + } + + /** Gets the command name. @return the name */ + public String getName() { + return name; + } + + /** Sets the command name. @param name the name @return this */ + public CommandWireDefinition setName(String name) { + this.name = name; + return this; + } + + /** Gets the description. @return the description */ + public String getDescription() { + return description; + } + + /** Sets the description. @param description the description @return this */ + public CommandWireDefinition setDescription(String description) { + this.description = description; + return this; + } +} 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 c0243f14b..d030631de 100644 --- a/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java +++ b/src/main/java/com/github/copilot/sdk/json/CreateSessionRequest.java @@ -91,6 +91,12 @@ public final class CreateSessionRequest { @JsonProperty("configDir") private String configDir; + @JsonProperty("commands") + private List commands; + + @JsonProperty("requestElicitation") + private Boolean requestElicitation; + /** Gets the model name. @return the model */ public String getModel() { return model; @@ -312,4 +318,24 @@ public String getConfigDir() { public void setConfigDir(String configDir) { this.configDir = configDir; } + + /** Gets the commands wire definitions. @return the commands */ + public List getCommands() { + return commands == null ? null : Collections.unmodifiableList(commands); + } + + /** Sets the commands wire definitions. @param commands the commands */ + public void setCommands(List commands) { + this.commands = commands; + } + + /** Gets the requestElicitation flag. @return the flag */ + public Boolean getRequestElicitation() { + return requestElicitation; + } + + /** Sets the requestElicitation flag. @param requestElicitation the flag */ + public void setRequestElicitation(Boolean requestElicitation) { + this.requestElicitation = requestElicitation; + } } diff --git a/src/main/java/com/github/copilot/sdk/json/CreateSessionResponse.java b/src/main/java/com/github/copilot/sdk/json/CreateSessionResponse.java index 5b1a177f0..b47af050b 100644 --- a/src/main/java/com/github/copilot/sdk/json/CreateSessionResponse.java +++ b/src/main/java/com/github/copilot/sdk/json/CreateSessionResponse.java @@ -11,9 +11,12 @@ * @param workspacePath * the workspace path, or {@code null} if infinite sessions are * disabled + * @param capabilities + * the capabilities reported by the host, or {@code null} * @since 1.0.0 */ @JsonInclude(JsonInclude.Include.NON_NULL) public record CreateSessionResponse(@JsonProperty("sessionId") String sessionId, - @JsonProperty("workspacePath") String workspacePath) { + @JsonProperty("workspacePath") String workspacePath, + @JsonProperty("capabilities") SessionCapabilities capabilities) { } diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationContext.java b/src/main/java/com/github/copilot/sdk/json/ElicitationContext.java new file mode 100644 index 000000000..87687b194 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/ElicitationContext.java @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +/** + * Context for an elicitation request received from the server or MCP tools. + * + * @since 1.0.0 + */ +public class ElicitationContext { + + private String sessionId; + private String message; + private ElicitationSchema requestedSchema; + private String mode; + private String elicitationSource; + private String url; + + /** + * Gets the session ID that triggered the elicitation request. @return the + * session ID + */ + public String getSessionId() { + return sessionId; + } + + /** Sets the session ID. @param sessionId the session ID @return this */ + public ElicitationContext setSessionId(String sessionId) { + this.sessionId = sessionId; + return this; + } + + /** + * Gets the message describing what information is needed from the user. + * + * @return the message + */ + public String getMessage() { + return message; + } + + /** Sets the message. @param message the message @return this */ + public ElicitationContext setMessage(String message) { + this.message = message; + return this; + } + + /** + * Gets the JSON Schema describing the form fields to present (form mode only). + * + * @return the schema, or {@code null} + */ + public ElicitationSchema getRequestedSchema() { + return requestedSchema; + } + + /** Sets the schema. @param requestedSchema the schema @return this */ + public ElicitationContext setRequestedSchema(ElicitationSchema requestedSchema) { + this.requestedSchema = requestedSchema; + return this; + } + + /** + * Gets the elicitation mode: {@code "form"} for structured input, {@code "url"} + * for browser redirect. + * + * @return the mode, or {@code null} (defaults to {@code "form"}) + */ + public String getMode() { + return mode; + } + + /** Sets the mode. @param mode the mode @return this */ + public ElicitationContext setMode(String mode) { + this.mode = mode; + return this; + } + + /** + * Gets the source that initiated the request (e.g., MCP server name). + * + * @return the elicitation source, or {@code null} + */ + public String getElicitationSource() { + return elicitationSource; + } + + /** + * Sets the elicitation source. @param elicitationSource the source @return this + */ + public ElicitationContext setElicitationSource(String elicitationSource) { + this.elicitationSource = elicitationSource; + return this; + } + + /** + * Gets the URL to open in the user's browser (url mode only). + * + * @return the URL, or {@code null} + */ + public String getUrl() { + return url; + } + + /** Sets the URL. @param url the URL @return this */ + public ElicitationContext setUrl(String url) { + this.url = url; + return this; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationHandler.java b/src/main/java/com/github/copilot/sdk/json/ElicitationHandler.java new file mode 100644 index 000000000..d0a0d0616 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/ElicitationHandler.java @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +import java.util.concurrent.CompletableFuture; + +/** + * Functional interface for handling elicitation requests from the server. + *

+ * Register an elicitation handler via + * {@link SessionConfig#setOnElicitationRequest(ElicitationHandler)} or + * {@link ResumeSessionConfig#setOnElicitationRequest(ElicitationHandler)}. When + * provided, the server routes elicitation requests to this handler and reports + * elicitation as a supported capability. + * + *

Example Usage

+ * + *
{@code
+ * ElicitationHandler handler = context -> {
+ * 	// Show the form to the user and collect responses
+ * 	Map formValues = showForm(context.getMessage(), context.getRequestedSchema());
+ * 	return CompletableFuture.completedFuture(
+ * 			new ElicitationResult().setAction(ElicitationResultAction.ACCEPT).setContent(formValues));
+ * };
+ * }
+ * + * @see ElicitationContext + * @see ElicitationResult + * @since 1.0.0 + */ +@FunctionalInterface +public interface ElicitationHandler { + + /** + * Handles an elicitation request from the server. + * + * @param context + * the elicitation context containing the message, schema, and mode + * @return a future that resolves with the elicitation result + */ + CompletableFuture handle(ElicitationContext context); +} diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationParams.java b/src/main/java/com/github/copilot/sdk/json/ElicitationParams.java new file mode 100644 index 000000000..8bd81022e --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/ElicitationParams.java @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +/** + * Parameters for an elicitation request sent from the SDK to the host. + * + * @since 1.0.0 + */ +public class ElicitationParams { + + private String message; + private ElicitationSchema requestedSchema; + + /** + * Gets the message describing what information is needed from the user. + * + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * Sets the message describing what information is needed from the user. + * + * @param message + * the message + * @return this instance for method chaining + */ + public ElicitationParams setMessage(String message) { + this.message = message; + return this; + } + + /** + * Gets the JSON Schema describing the form fields to present. + * + * @return the requested schema + */ + public ElicitationSchema getRequestedSchema() { + return requestedSchema; + } + + /** + * Sets the JSON Schema describing the form fields to present. + * + * @param requestedSchema + * the schema + * @return this instance for method chaining + */ + public ElicitationParams setRequestedSchema(ElicitationSchema requestedSchema) { + this.requestedSchema = requestedSchema; + return this; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationResult.java b/src/main/java/com/github/copilot/sdk/json/ElicitationResult.java new file mode 100644 index 000000000..3ba30b83d --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/ElicitationResult.java @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +import java.util.Map; + +/** + * Result returned from an elicitation dialog. + * + * @since 1.0.0 + */ +public class ElicitationResult { + + private ElicitationResultAction action; + private Map content; + + /** + * Gets the user action taken on the elicitation dialog. + *

+ * {@link ElicitationResultAction#ACCEPT} means the user submitted the form, + * {@link ElicitationResultAction#DECLINE} means the user rejected the request, + * and {@link ElicitationResultAction#CANCEL} means the user dismissed the + * dialog. + * + * @return the user action + */ + public ElicitationResultAction getAction() { + return action; + } + + /** + * Sets the user action taken on the elicitation dialog. + * + * @param action + * the user action + * @return this instance for method chaining + */ + public ElicitationResult setAction(ElicitationResultAction action) { + this.action = action; + return this; + } + + /** + * Gets the form values submitted by the user. + *

+ * Only present when {@link #getAction()} is + * {@link ElicitationResultAction#ACCEPT}. + * + * @return the submitted form values, or {@code null} if the user did not accept + */ + public Map getContent() { + return content; + } + + /** + * Sets the form values submitted by the user. + * + * @param content + * the submitted form values + * @return this instance for method chaining + */ + public ElicitationResult setContent(Map content) { + this.content = content; + return this; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationResultAction.java b/src/main/java/com/github/copilot/sdk/json/ElicitationResultAction.java new file mode 100644 index 000000000..fd280cdeb --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/ElicitationResultAction.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +/** + * Action value for an {@link ElicitationResult}. + * + * @since 1.0.0 + */ +public enum ElicitationResultAction { + + /** The user submitted the form (accepted). */ + ACCEPT("accept"), + + /** The user explicitly rejected the request. */ + DECLINE("decline"), + + /** The user dismissed the dialog without responding. */ + CANCEL("cancel"); + + private final String value; + + ElicitationResultAction(String value) { + this.value = value; + } + + /** Returns the wire-format string value. @return the string value */ + public String getValue() { + return value; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/ElicitationSchema.java b/src/main/java/com/github/copilot/sdk/json/ElicitationSchema.java new file mode 100644 index 000000000..c3d548775 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/ElicitationSchema.java @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * JSON Schema describing the form fields to present for an elicitation dialog. + * + * @since 1.0.0 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ElicitationSchema { + + @JsonProperty("type") + private String type = "object"; + + @JsonProperty("properties") + private Map properties; + + @JsonProperty("required") + private List required; + + /** + * Gets the schema type indicator (always {@code "object"}). + * + * @return the type + */ + public String getType() { + return type; + } + + /** + * Sets the schema type indicator. + * + * @param type + * the type (typically {@code "object"}) + * @return this instance for method chaining + */ + public ElicitationSchema setType(String type) { + this.type = type; + return this; + } + + /** + * Gets the form field definitions, keyed by field name. + * + * @return the properties map + */ + public Map getProperties() { + return properties; + } + + /** + * Sets the form field definitions, keyed by field name. + * + * @param properties + * the properties map + * @return this instance for method chaining + */ + public ElicitationSchema setProperties(Map properties) { + this.properties = properties; + return this; + } + + /** + * Gets the list of required field names. + * + * @return the required field names, or {@code null} + */ + public List getRequired() { + return required; + } + + /** + * Sets the list of required field names. + * + * @param required + * the required field names + * @return this instance for method chaining + */ + public ElicitationSchema setRequired(List required) { + this.required = required; + return this; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java b/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java new file mode 100644 index 000000000..87c686f58 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java @@ -0,0 +1,15 @@ +package com.github.copilot.sdk.json; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Internal response object from getting session metadata by ID. + * + * @param session + * the session metadata, or {@code null} if not found + * @since 1.0.0 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public record GetSessionMetadataResponse(@JsonProperty("session") SessionMetadata session) { +} diff --git a/src/main/java/com/github/copilot/sdk/json/InputOptions.java b/src/main/java/com/github/copilot/sdk/json/InputOptions.java new file mode 100644 index 000000000..9b0b6c8dd --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/InputOptions.java @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +/** + * Options for the {@link SessionUiApi#input(String, InputOptions)} convenience + * method. + * + * @since 1.0.0 + */ +public class InputOptions { + + private String title; + private String description; + private Integer minLength; + private Integer maxLength; + private String format; + private String defaultValue; + + /** Gets the title label for the input field. @return the title */ + public String getTitle() { + return title; + } + + /** + * Sets the title label for the input field. @param title the title @return this + */ + public InputOptions setTitle(String title) { + this.title = title; + return this; + } + + /** Gets the descriptive text shown below the field. @return the description */ + public String getDescription() { + return description; + } + + /** + * Sets the descriptive text shown below the field. @param description the + * description @return this + */ + public InputOptions setDescription(String description) { + this.description = description; + return this; + } + + /** Gets the minimum character length. @return the min length */ + public Integer getMinLength() { + return minLength; + } + + /** + * Sets the minimum character length. @param minLength the min length @return + * this + */ + public InputOptions setMinLength(Integer minLength) { + this.minLength = minLength; + return this; + } + + /** Gets the maximum character length. @return the max length */ + public Integer getMaxLength() { + return maxLength; + } + + /** + * Sets the maximum character length. @param maxLength the max length @return + * this + */ + public InputOptions setMaxLength(Integer maxLength) { + this.maxLength = maxLength; + return this; + } + + /** + * Gets the semantic format hint (e.g., {@code "email"}, {@code "uri"}, + * {@code "date"}, {@code "date-time"}). + * + * @return the format hint + */ + public String getFormat() { + return format; + } + + /** Sets the semantic format hint. @param format the format @return this */ + public InputOptions setFormat(String format) { + this.format = format; + return this; + } + + /** + * Gets the default value pre-populated in the field. @return the default value + */ + public String getDefaultValue() { + return defaultValue; + } + + /** + * Sets the default value pre-populated in the field. @param defaultValue the + * default value @return this + */ + public InputOptions setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + return this; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java index eab3c789c..139f5238b 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java @@ -58,6 +58,8 @@ public class ResumeSessionConfig { private List disabledSkills; private InfiniteSessionConfig infiniteSessions; private Consumer onEvent; + private List commands; + private ElicitationHandler onElicitationRequest; /** * Gets the AI model to use. @@ -555,6 +557,56 @@ public ResumeSessionConfig setOnEvent(Consumer onEvent) { return this; } + /** + * Gets the slash commands registered for this session. + * + * @return the list of command definitions, or {@code null} + */ + public List getCommands() { + return commands == null ? null : Collections.unmodifiableList(commands); + } + + /** + * Sets slash commands registered for this session. + *

+ * When the CLI has a TUI, each command appears as {@code /name} for the user to + * invoke. The handler is called when the user executes the command. + * + * @param commands + * the list of command definitions + * @return this config for method chaining + * @see CommandDefinition + */ + public ResumeSessionConfig setCommands(List commands) { + this.commands = commands; + return this; + } + + /** + * Gets the elicitation request handler. + * + * @return the elicitation handler, or {@code null} + */ + public ElicitationHandler getOnElicitationRequest() { + return onElicitationRequest; + } + + /** + * Sets a handler for elicitation requests from the server or MCP tools. + *

+ * When provided, the server will route elicitation requests to this handler and + * report elicitation as a supported capability. + * + * @param onElicitationRequest + * the elicitation handler + * @return this config for method chaining + * @see ElicitationHandler + */ + public ResumeSessionConfig setOnElicitationRequest(ElicitationHandler onElicitationRequest) { + this.onElicitationRequest = onElicitationRequest; + return this; + } + /** * Creates a shallow clone of this {@code ResumeSessionConfig} instance. *

@@ -591,6 +643,8 @@ public ResumeSessionConfig clone() { copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.infiniteSessions = this.infiniteSessions; copy.onEvent = this.onEvent; + copy.commands = this.commands != null ? new ArrayList<>(this.commands) : null; + copy.onElicitationRequest = this.onElicitationRequest; return copy; } } diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java index 31d88399a..7be9a6281 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java @@ -95,6 +95,12 @@ public final class ResumeSessionRequest { @JsonProperty("infiniteSessions") private InfiniteSessionConfig infiniteSessions; + @JsonProperty("commands") + private List commands; + + @JsonProperty("requestElicitation") + private Boolean requestElicitation; + /** Gets the session ID. @return the session ID */ public String getSessionId() { return sessionId; @@ -332,4 +338,24 @@ public InfiniteSessionConfig getInfiniteSessions() { public void setInfiniteSessions(InfiniteSessionConfig infiniteSessions) { this.infiniteSessions = infiniteSessions; } + + /** Gets the commands wire definitions. @return the commands */ + public List getCommands() { + return commands == null ? null : Collections.unmodifiableList(commands); + } + + /** Sets the commands wire definitions. @param commands the commands */ + public void setCommands(List commands) { + this.commands = commands; + } + + /** Gets the requestElicitation flag. @return the flag */ + public Boolean getRequestElicitation() { + return requestElicitation; + } + + /** Sets the requestElicitation flag. @param requestElicitation the flag */ + public void setRequestElicitation(Boolean requestElicitation) { + this.requestElicitation = requestElicitation; + } } diff --git a/src/main/java/com/github/copilot/sdk/json/ResumeSessionResponse.java b/src/main/java/com/github/copilot/sdk/json/ResumeSessionResponse.java index 654c1486c..8349c5d30 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionResponse.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionResponse.java @@ -11,9 +11,12 @@ * @param workspacePath * the workspace path, or {@code null} if infinite sessions are * disabled + * @param capabilities + * the capabilities reported by the host, or {@code null} * @since 1.0.0 */ @JsonInclude(JsonInclude.Include.NON_NULL) public record ResumeSessionResponse(@JsonProperty("sessionId") String sessionId, - @JsonProperty("workspacePath") String workspacePath) { + @JsonProperty("workspacePath") String workspacePath, + @JsonProperty("capabilities") SessionCapabilities capabilities) { } diff --git a/src/main/java/com/github/copilot/sdk/json/SessionCapabilities.java b/src/main/java/com/github/copilot/sdk/json/SessionCapabilities.java new file mode 100644 index 000000000..4eb4fc025 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/SessionCapabilities.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +/** + * Represents the capabilities reported by the host for a session. + *

+ * Capabilities are populated from the session create/resume response and + * updated in real time via {@code capabilities.changed} events. + * + * @since 1.0.0 + */ +public class SessionCapabilities { + + private SessionUiCapabilities ui; + + /** + * Gets the UI-related capabilities. + * + * @return the UI capabilities, or {@code null} if not reported + */ + public SessionUiCapabilities getUi() { + return ui; + } + + /** + * Sets the UI-related capabilities. + * + * @param ui + * the UI capabilities + * @return this instance for method chaining + */ + public SessionCapabilities setUi(SessionUiCapabilities ui) { + this.ui = ui; + return this; + } +} diff --git a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java index 76c15660d..5dcd39788 100644 --- a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java @@ -58,6 +58,8 @@ public class SessionConfig { private List disabledSkills; private String configDir; private Consumer onEvent; + private List commands; + private ElicitationHandler onElicitationRequest; /** * Gets the custom session ID. @@ -595,6 +597,56 @@ public SessionConfig setOnEvent(Consumer onEvent) { return this; } + /** + * Gets the slash commands registered for this session. + * + * @return the list of command definitions, or {@code null} + */ + public List getCommands() { + return commands == null ? null : Collections.unmodifiableList(commands); + } + + /** + * Sets slash commands registered for this session. + *

+ * When the CLI has a TUI, each command appears as {@code /name} for the user to + * invoke. The handler is called when the user executes the command. + * + * @param commands + * the list of command definitions + * @return this config instance for method chaining + * @see CommandDefinition + */ + public SessionConfig setCommands(List commands) { + this.commands = commands; + return this; + } + + /** + * Gets the elicitation request handler. + * + * @return the elicitation handler, or {@code null} + */ + public ElicitationHandler getOnElicitationRequest() { + return onElicitationRequest; + } + + /** + * Sets a handler for elicitation requests from the server or MCP tools. + *

+ * When provided, the server will route elicitation requests to this handler and + * report elicitation as a supported capability. + * + * @param onElicitationRequest + * the elicitation handler + * @return this config instance for method chaining + * @see ElicitationHandler + */ + public SessionConfig setOnElicitationRequest(ElicitationHandler onElicitationRequest) { + this.onElicitationRequest = onElicitationRequest; + return this; + } + /** * Creates a shallow clone of this {@code SessionConfig} instance. *

@@ -631,6 +683,8 @@ public SessionConfig clone() { copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.configDir = this.configDir; copy.onEvent = this.onEvent; + copy.commands = this.commands != null ? new ArrayList<>(this.commands) : null; + copy.onElicitationRequest = this.onElicitationRequest; return copy; } } diff --git a/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java b/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java new file mode 100644 index 000000000..bfc9fc161 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +import java.util.concurrent.CompletableFuture; + +/** + * Provides UI methods for eliciting information from the user during a session. + *

+ * All methods on this interface throw {@link IllegalStateException} if the host + * does not report elicitation support via + * {@link com.github.copilot.sdk.CopilotSession#getCapabilities()}. Check + * {@code session.getCapabilities().getUi()?.getElicitation() == true} before + * calling. + * + *

Example Usage

+ * + *
{@code
+ * if (Boolean.TRUE
+ * 		.equals(session.getCapabilities().getUi() != null && session.getCapabilities().getUi().getElicitation())) {
+ * 	boolean confirmed = session.getUi().confirm("Are you sure?").get();
+ * }
+ * }
+ * + * @see com.github.copilot.sdk.CopilotSession#getUi() + * @since 1.0.0 + */ +public interface SessionUiApi { + + /** + * Shows a generic elicitation dialog with a custom schema. + * + * @param params + * the elicitation parameters including message and schema + * @return a future that resolves with the {@link ElicitationResult} + * @throws IllegalStateException + * if the host does not support elicitation + */ + CompletableFuture elicitation(ElicitationParams params); + + /** + * Shows a confirmation dialog and returns the user's boolean answer. + *

+ * Returns {@code false} if the user declines or cancels. + * + * @param message + * the message to display + * @return a future that resolves to {@code true} if the user confirmed + * @throws IllegalStateException + * if the host does not support elicitation + */ + CompletableFuture confirm(String message); + + /** + * Shows a selection dialog with the given options. + *

+ * Returns the selected value, or {@code null} if the user declines/cancels. + * + * @param message + * the message to display + * @param options + * the options to present + * @return a future that resolves to the selected string, or {@code null} + * @throws IllegalStateException + * if the host does not support elicitation + */ + CompletableFuture select(String message, String[] options); + + /** + * Shows a text input dialog. + *

+ * Returns the entered text, or {@code null} if the user declines/cancels. + * + * @param message + * the message to display + * @param options + * optional input field options, or {@code null} + * @return a future that resolves to the entered string, or {@code null} + * @throws IllegalStateException + * if the host does not support elicitation + */ + CompletableFuture input(String message, InputOptions options); +} diff --git a/src/main/java/com/github/copilot/sdk/json/SessionUiCapabilities.java b/src/main/java/com/github/copilot/sdk/json/SessionUiCapabilities.java new file mode 100644 index 000000000..9b8e0b587 --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/json/SessionUiCapabilities.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk.json; + +/** + * UI-specific capability flags for a session. + * + * @since 1.0.0 + */ +public class SessionUiCapabilities { + + private Boolean elicitation; + + /** + * Returns whether the host supports interactive elicitation dialogs. + * + * @return {@code true} if elicitation is supported, {@code false} or + * {@code null} otherwise + */ + public Boolean getElicitation() { + return elicitation; + } + + /** + * Sets whether the host supports interactive elicitation dialogs. + * + * @param elicitation + * {@code true} if elicitation is supported + * @return this instance for method chaining + */ + public SessionUiCapabilities setElicitation(Boolean elicitation) { + this.elicitation = elicitation; + return this; + } +} From 4001135367c73413f74b4f6e4cd8c3490b4b4cda Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:58:53 +0000 Subject: [PATCH 10/47] Add tests, documentation, and update .lastmerge to f7fd757 Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/16d575c4-83f4-4d20-99d9-b48635b3791d Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .lastmerge | 2 +- README.md | 2 +- src/site/markdown/advanced.md | 137 +++++++++++++ src/site/markdown/index.md | 2 +- .../com/github/copilot/sdk/CommandsTest.java | 138 +++++++++++++ .../copilot/sdk/CopilotSessionTest.java | 28 +++ .../github/copilot/sdk/ElicitationTest.java | 191 ++++++++++++++++++ 7 files changed, 497 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/github/copilot/sdk/CommandsTest.java create mode 100644 src/test/java/com/github/copilot/sdk/ElicitationTest.java diff --git a/.lastmerge b/.lastmerge index a0cf76b72..0d0067b5b 100644 --- a/.lastmerge +++ b/.lastmerge @@ -1 +1 @@ -40887393a9e687dacc141a645799441b0313ff15 +f7fd7577109d64e261456b16c49baa56258eae4e diff --git a/README.md b/README.md index 3010b6839..f71424950 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A ### Requirements - Java 17 or later. **JDK 25 recommended**. Selecting JDK 25 enables the use of virtual threads, as shown in the [Quick Start](#quick-start). -- GitHub Copilot 1.0.15-0 or later installed and in `PATH` (or provide custom `cliPath`) +- GitHub Copilot 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`) ### Maven diff --git a/src/site/markdown/advanced.md b/src/site/markdown/advanced.md index bc9302840..598acc2f7 100644 --- a/src/site/markdown/advanced.md +++ b/src/site/markdown/advanced.md @@ -1093,6 +1093,143 @@ See [TelemetryConfig](apidocs/com/github/copilot/sdk/json/TelemetryConfig.html) --- +## Slash Commands + +Register custom slash commands that users can invoke from the CLI TUI with `/commandname`. + +### Registering Commands + +```java +var config = new SessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setCommands(List.of( + new CommandDefinition() + .setName("deploy") + .setDescription("Deploy the current branch") + .setHandler(context -> { + System.out.println("Deploying with args: " + context.getArgs()); + // perform deployment ... + return CompletableFuture.completedFuture(null); + }), + new CommandDefinition() + .setName("rollback") + .setDescription("Roll back the last deployment") + .setHandler(context -> { + // perform rollback ... + return CompletableFuture.completedFuture(null); + }) + )); + +try (CopilotClient client = new CopilotClient()) { + client.start().get(); + var session = client.createSession(config).get(); + // Users can now type /deploy or /rollback in the TUI +} +``` + +Each `CommandDefinition` requires a `name` (without the leading `/`), an optional `description` shown in the TUI's command completion UI, and a `CommandHandler` that is invoked when the user executes the command. + +The `CommandContext` passed to the handler provides: +- `getSessionId()` — the ID of the session where the command was invoked +- `getCommand()` — the full command text (e.g., `/deploy production`) +- `getCommandName()` — command name without the leading `/` (e.g., `deploy`) +- `getArgs()` — the argument string after the command name (e.g., `production`) + +--- + +## Elicitation (UI Dialogs) + +Elicitation allows your application to present structured UI dialogs to the user. There are two directions: + +1. **Incoming** — The server or an MCP tool requests input from the user via your `onElicitationRequest` handler. +2. **Outgoing** — Your session-side code proactively requests input via `session.getUi()`. + +### Incoming Elicitation Handler + +Register a handler to receive elicitation requests from the server: + +```java +var config = new SessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setOnElicitationRequest(context -> { + System.out.println("Elicitation request: " + context.getMessage()); + // Show the form to the user ... + var content = Map.of("confirmed", true); + return CompletableFuture.completedFuture( + new ElicitationResult() + .setAction(ElicitationResultAction.ACCEPT) + .setContent(content) + ); + }); +``` + +When `onElicitationRequest` is set, the SDK reports elicitation as a supported capability and the server will route elicitation requests to your handler. + +### Session Capabilities + +After `createSession` or `resumeSession`, check `session.getCapabilities()` to see what the host supports: + +```java +var session = client.createSession(config).get(); + +var caps = session.getCapabilities(); +if (caps.getUi() != null && Boolean.TRUE.equals(caps.getUi().getElicitation())) { + System.out.println("Elicitation is supported"); +} +``` + +Capabilities are updated in real time when a `capabilities.changed` event is received. + +### Outgoing Elicitation via `session.getUi()` + +If the host reports elicitation support, you can call the convenience methods on `session.getUi()`: + +```java +var ui = session.getUi(); + +// Boolean confirmation +boolean confirmed = ui.confirm("Are you sure you want to proceed?").get(); + +// Selection from options +String choice = ui.select("Choose an environment", new String[]{"dev", "staging", "prod"}).get(); + +// Text input +String value = ui.input("Enter your name", null).get(); + +// Custom schema +var result = ui.elicitation(new ElicitationParams() + .setMessage("Enter deployment details") + .setRequestedSchema(new ElicitationSchema() + .setProperties(Map.of( + "branch", Map.of("type", "string"), + "environment", Map.of("type", "string", "enum", List.of("dev", "staging", "prod")) + )) + .setRequired(List.of("branch", "environment")) + )).get(); +``` + +All `getUi()` methods throw `IllegalStateException` if the host does not support elicitation. Always check capabilities first. + +--- + +## Getting Session Metadata by ID + +Retrieve metadata for a specific session without listing all sessions: + +```java +SessionMetadata metadata = client.getSessionMetadata("session-123").get(); +if (metadata != null) { + System.out.println("Session: " + metadata.getSessionId()); + System.out.println("Started: " + metadata.getStartTime()); +} else { + System.out.println("Session not found"); +} +``` + +This is more efficient than `listSessions()` when you already know the session ID, as it performs a direct O(1) lookup instead of scanning all sessions. + +--- + ## Next Steps - 📖 **[Documentation](documentation.html)** - Core concepts, events, streaming, models, tool filtering, reasoning effort diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 2f93c4ce9..b599484d9 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -9,7 +9,7 @@ Welcome to the documentation for the **GitHub Copilot SDK for Java** — a Java ### Requirements - Java 17 or later -- GitHub Copilot CLI 0.0.411-1 or later installed and in PATH (or provide custom `cliPath`) +- GitHub Copilot CLI 1.0.17 or later installed and in PATH (or provide custom `cliPath`) ### Installation diff --git a/src/test/java/com/github/copilot/sdk/CommandsTest.java b/src/test/java/com/github/copilot/sdk/CommandsTest.java new file mode 100644 index 000000000..dad26afbb --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/CommandsTest.java @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.sdk.json.CommandContext; +import com.github.copilot.sdk.json.CommandDefinition; +import com.github.copilot.sdk.json.CommandHandler; +import com.github.copilot.sdk.json.CommandWireDefinition; +import com.github.copilot.sdk.json.PermissionHandler; +import com.github.copilot.sdk.json.ResumeSessionConfig; +import com.github.copilot.sdk.json.SessionConfig; + +/** + * Unit tests for the Commands feature (CommandDefinition, CommandContext, + * SessionConfig.commands, ResumeSessionConfig.commands, and the wire + * representation). + * + *

+ * Ported from {@code CommandsTests.cs} in the upstream dotnet SDK. + *

+ */ +class CommandsTest { + + @Test + void commandDefinitionHasRequiredProperties() { + CommandHandler handler = context -> CompletableFuture.completedFuture(null); + var cmd = new CommandDefinition().setName("deploy").setDescription("Deploy the app").setHandler(handler); + + assertEquals("deploy", cmd.getName()); + assertEquals("Deploy the app", cmd.getDescription()); + assertNotNull(cmd.getHandler()); + } + + @Test + void commandContextHasAllProperties() { + var ctx = new CommandContext().setSessionId("session-1").setCommand("/deploy production") + .setCommandName("deploy").setArgs("production"); + + assertEquals("session-1", ctx.getSessionId()); + assertEquals("/deploy production", ctx.getCommand()); + assertEquals("deploy", ctx.getCommandName()); + assertEquals("production", ctx.getArgs()); + } + + @Test + void sessionConfigCommandsAreCloned() { + CommandHandler handler = ctx -> CompletableFuture.completedFuture(null); + var config = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setCommands(List.of(new CommandDefinition().setName("deploy").setHandler(handler))); + + var clone = config.clone(); + + assertNotNull(clone.getCommands()); + assertEquals(1, clone.getCommands().size()); + assertEquals("deploy", clone.getCommands().get(0).getName()); + + // Collections should be independent — clone list is a copy + assertNotSame(config.getCommands(), clone.getCommands()); + } + + @Test + void resumeConfigCommandsAreCloned() { + CommandHandler handler = ctx -> CompletableFuture.completedFuture(null); + var config = new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setCommands(List.of(new CommandDefinition().setName("deploy").setHandler(handler))); + + var clone = config.clone(); + + assertNotNull(clone.getCommands()); + assertEquals(1, clone.getCommands().size()); + assertEquals("deploy", clone.getCommands().get(0).getName()); + } + + @Test + void buildCreateRequestIncludesCommandWireDefinitions() { + CommandHandler handler = ctx -> CompletableFuture.completedFuture(null); + var config = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setCommands( + List.of(new CommandDefinition().setName("deploy").setDescription("Deploy").setHandler(handler), + new CommandDefinition().setName("rollback").setHandler(handler))); + + var request = SessionRequestBuilder.buildCreateRequest(config); + + assertNotNull(request.getCommands()); + assertEquals(2, request.getCommands().size()); + assertEquals("deploy", request.getCommands().get(0).getName()); + assertEquals("Deploy", request.getCommands().get(0).getDescription()); + assertEquals("rollback", request.getCommands().get(1).getName()); + assertNull(request.getCommands().get(1).getDescription()); + } + + @Test + void buildResumeRequestIncludesCommandWireDefinitions() { + CommandHandler handler = ctx -> CompletableFuture.completedFuture(null); + var config = new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setCommands( + List.of(new CommandDefinition().setName("deploy").setDescription("Deploy").setHandler(handler))); + + var request = SessionRequestBuilder.buildResumeRequest("session-1", config); + + assertNotNull(request.getCommands()); + assertEquals(1, request.getCommands().size()); + assertEquals("deploy", request.getCommands().get(0).getName()); + assertEquals("Deploy", request.getCommands().get(0).getDescription()); + } + + @Test + void buildCreateRequestWithNoCommandsHasNullCommandsList() { + var config = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL); + + var request = SessionRequestBuilder.buildCreateRequest(config); + + assertNull(request.getCommands()); + } + + @Test + void commandWireDefinitionHasNameAndDescription() { + var wire = new CommandWireDefinition("deploy", "Deploy the app"); + + assertEquals("deploy", wire.getName()); + assertEquals("Deploy the app", wire.getDescription()); + } + + @Test + void commandWireDefinitionNullDescriptionAllowed() { + var wire = new CommandWireDefinition("rollback", null); + + assertEquals("rollback", wire.getName()); + assertNull(wire.getDescription()); + } +} diff --git a/src/test/java/com/github/copilot/sdk/CopilotSessionTest.java b/src/test/java/com/github/copilot/sdk/CopilotSessionTest.java index 787312cef..39406d260 100644 --- a/src/test/java/com/github/copilot/sdk/CopilotSessionTest.java +++ b/src/test/java/com/github/copilot/sdk/CopilotSessionTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -821,4 +822,31 @@ void testSessionListFilterFluentAPI() throws Exception { session.close(); } } + + /** + * Verifies that getSessionMetadata returns metadata for a known session ID. + * + * @see Snapshot: session/should_get_session_metadata_by_id + */ + @Test + void testShouldGetSessionMetadataById() throws Exception { + ctx.configureForTest("session", "should_get_session_metadata_by_id"); + + try (CopilotClient client = ctx.createClient()) { + var session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); + + session.sendAndWait(new MessageOptions().setPrompt("Say hello")).get(60, TimeUnit.SECONDS); + + var metadata = client.getSessionMetadata(session.getSessionId()).get(30, TimeUnit.SECONDS); + assertNotNull(metadata, "Metadata should not be null for known session"); + assertEquals(session.getSessionId(), metadata.getSessionId(), "Metadata session ID should match"); + + // A non-existent session should return null + var notFound = client.getSessionMetadata("non-existent-session-id").get(30, TimeUnit.SECONDS); + assertNull(notFound, "Non-existent session should return null"); + + session.close(); + } + } } diff --git a/src/test/java/com/github/copilot/sdk/ElicitationTest.java b/src/test/java/com/github/copilot/sdk/ElicitationTest.java new file mode 100644 index 000000000..330153dd2 --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/ElicitationTest.java @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.sdk.json.ElicitationContext; +import com.github.copilot.sdk.json.ElicitationHandler; +import com.github.copilot.sdk.json.ElicitationParams; +import com.github.copilot.sdk.json.ElicitationResult; +import com.github.copilot.sdk.json.ElicitationResultAction; +import com.github.copilot.sdk.json.ElicitationSchema; +import com.github.copilot.sdk.json.InputOptions; +import com.github.copilot.sdk.json.PermissionHandler; +import com.github.copilot.sdk.json.ResumeSessionConfig; +import com.github.copilot.sdk.json.SessionCapabilities; +import com.github.copilot.sdk.json.SessionConfig; +import com.github.copilot.sdk.json.SessionUiCapabilities; + +/** + * Unit tests for the Elicitation feature and Session Capabilities. + * + *

+ * Ported from {@code ElicitationTests.cs} in the upstream dotnet SDK. + *

+ */ +class ElicitationTest { + + @Test + void sessionCapabilitiesTypesAreProperlyStructured() { + var capabilities = new SessionCapabilities().setUi(new SessionUiCapabilities().setElicitation(true)); + + assertNotNull(capabilities.getUi()); + assertTrue(capabilities.getUi().getElicitation()); + + // Test with null UI + var emptyCapabilities = new SessionCapabilities(); + assertNull(emptyCapabilities.getUi()); + assertNull(emptyCapabilities.getUi()); + } + + @Test + void defaultCapabilitiesAreEmpty() { + var capabilities = new SessionCapabilities(); + + assertNull(capabilities.getUi()); + } + + @Test + void elicitationResultActionValues() { + assertEquals("accept", ElicitationResultAction.ACCEPT.getValue()); + assertEquals("decline", ElicitationResultAction.DECLINE.getValue()); + assertEquals("cancel", ElicitationResultAction.CANCEL.getValue()); + } + + @Test + void elicitationResultHasActionAndContent() { + var content = Map.of("name", (Object) "Alice"); + var result = new ElicitationResult().setAction(ElicitationResultAction.ACCEPT).setContent(content); + + assertEquals(ElicitationResultAction.ACCEPT, result.getAction()); + assertEquals(content, result.getContent()); + } + + @Test + void elicitationSchemaHasTypeAndProperties() { + var properties = Map.of("name", (Object) Map.of("type", "string")); + var schema = new ElicitationSchema().setType("object").setProperties(properties).setRequired(List.of("name")); + + assertEquals("object", schema.getType()); + assertEquals(properties, schema.getProperties()); + assertEquals(List.of("name"), schema.getRequired()); + } + + @Test + void elicitationSchemaDefaultTypeIsObject() { + var schema = new ElicitationSchema(); + + assertEquals("object", schema.getType()); + } + + @Test + void elicitationContextHasAllProperties() { + var properties = Map.of("field", (Object) Map.of("type", "string")); + var schema = new ElicitationSchema().setProperties(properties); + + var ctx = new ElicitationContext().setSessionId("session-1").setMessage("Please enter your name") + .setRequestedSchema(schema).setMode("form").setElicitationSource("mcp-server").setUrl(null); + + assertEquals("session-1", ctx.getSessionId()); + assertEquals("Please enter your name", ctx.getMessage()); + assertEquals(schema, ctx.getRequestedSchema()); + assertEquals("form", ctx.getMode()); + assertEquals("mcp-server", ctx.getElicitationSource()); + assertNull(ctx.getUrl()); + } + + @Test + void elicitationParamsHasMessageAndSchema() { + var schema = new ElicitationSchema().setProperties(Map.of("field", (Object) Map.of("type", "string"))); + var params = new ElicitationParams().setMessage("Enter name").setRequestedSchema(schema); + + assertEquals("Enter name", params.getMessage()); + assertEquals(schema, params.getRequestedSchema()); + } + + @Test + void inputOptionsHasAllFields() { + var opts = new InputOptions().setTitle("My Title").setDescription("My Desc").setMinLength(1).setMaxLength(100) + .setFormat("email").setDefaultValue("default@example.com"); + + assertEquals("My Title", opts.getTitle()); + assertEquals("My Desc", opts.getDescription()); + assertEquals(1, opts.getMinLength()); + assertEquals(100, opts.getMaxLength()); + assertEquals("email", opts.getFormat()); + assertEquals("default@example.com", opts.getDefaultValue()); + } + + @Test + void sessionConfigOnElicitationRequestIsCloned() { + ElicitationHandler handler = ctx -> CompletableFuture + .completedFuture(new ElicitationResult().setAction(ElicitationResultAction.ACCEPT)); + + var config = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setOnElicitationRequest(handler); + + var clone = config.clone(); + + // Handler reference is shared (not deep-cloned), but the field is copied + assertNotNull(clone.getOnElicitationRequest()); + assertSame(handler, clone.getOnElicitationRequest()); + } + + @Test + void resumeConfigOnElicitationRequestIsCloned() { + ElicitationHandler handler = ctx -> CompletableFuture + .completedFuture(new ElicitationResult().setAction(ElicitationResultAction.CANCEL)); + + var config = new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setOnElicitationRequest(handler); + + var clone = config.clone(); + + assertNotNull(clone.getOnElicitationRequest()); + assertSame(handler, clone.getOnElicitationRequest()); + } + + @Test + void buildCreateRequestSetsRequestElicitationWhenHandlerPresent() { + ElicitationHandler handler = ctx -> CompletableFuture + .completedFuture(new ElicitationResult().setAction(ElicitationResultAction.ACCEPT)); + + var config = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setOnElicitationRequest(handler); + + var request = SessionRequestBuilder.buildCreateRequest(config); + + assertTrue(Boolean.TRUE.equals(request.getRequestElicitation())); + } + + @Test + void buildCreateRequestDoesNotSetRequestElicitationWhenNoHandler() { + var config = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL); + + var request = SessionRequestBuilder.buildCreateRequest(config); + + assertNull(request.getRequestElicitation()); + } + + @Test + void buildResumeRequestSetsRequestElicitationWhenHandlerPresent() { + ElicitationHandler handler = ctx -> CompletableFuture + .completedFuture(new ElicitationResult().setAction(ElicitationResultAction.ACCEPT)); + + var config = new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setOnElicitationRequest(handler); + + var request = SessionRequestBuilder.buildResumeRequest("session-1", config); + + assertTrue(Boolean.TRUE.equals(request.getRequestElicitation())); + } +} From 2da6b5a8334f1fc9c696858401856e277d3e1948 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 11:01:31 +0000 Subject: [PATCH 11/47] Fix code review issues: duplicate assertion, copyright header, Javadoc example Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/16d575c4-83f4-4d20-99d9-b48635b3791d Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .../github/copilot/sdk/json/GetSessionMetadataResponse.java | 4 ++++ src/main/java/com/github/copilot/sdk/json/SessionUiApi.java | 4 ++-- src/test/java/com/github/copilot/sdk/ElicitationTest.java | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java b/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java index 87c686f58..eeceb4177 100644 --- a/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java +++ b/src/main/java/com/github/copilot/sdk/json/GetSessionMetadataResponse.java @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + package com.github.copilot.sdk.json; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java b/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java index bfc9fc161..6dc583457 100644 --- a/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java +++ b/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java @@ -18,8 +18,8 @@ *

Example Usage

* *
{@code
- * if (Boolean.TRUE
- * 		.equals(session.getCapabilities().getUi() != null && session.getCapabilities().getUi().getElicitation())) {
+ * var caps = session.getCapabilities();
+ * if (caps.getUi() != null && Boolean.TRUE.equals(caps.getUi().getElicitation())) {
  * 	boolean confirmed = session.getUi().confirm("Are you sure?").get();
  * }
  * }
diff --git a/src/test/java/com/github/copilot/sdk/ElicitationTest.java b/src/test/java/com/github/copilot/sdk/ElicitationTest.java index 330153dd2..d6e2ac0b5 100644 --- a/src/test/java/com/github/copilot/sdk/ElicitationTest.java +++ b/src/test/java/com/github/copilot/sdk/ElicitationTest.java @@ -44,7 +44,6 @@ void sessionCapabilitiesTypesAreProperlyStructured() { // Test with null UI var emptyCapabilities = new SessionCapabilities(); assertNull(emptyCapabilities.getUi()); - assertNull(emptyCapabilities.getUi()); } @Test From 7060eed9c996e58f9c3896a72e65fa108dfc509b Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Mon, 6 Apr 2026 13:12:05 -0400 Subject: [PATCH 12/47] Update src/main/java/com/github/copilot/sdk/CopilotSession.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/com/github/copilot/sdk/CopilotSession.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/copilot/sdk/CopilotSession.java b/src/main/java/com/github/copilot/sdk/CopilotSession.java index 768f0adaa..23b1b5368 100644 --- a/src/main/java/com/github/copilot/sdk/CopilotSession.java +++ b/src/main/java/com/github/copilot/sdk/CopilotSession.java @@ -724,7 +724,7 @@ private void handleBroadcastEventAsync(AbstractSessionEvent event) { executePermissionAndRespondAsync(data.requestId(), data.permissionRequest(), handler); } else if (event instanceof CommandExecuteEvent cmdEvent) { var data = cmdEvent.getData(); - if (data == null || data.requestId() == null) { + if (data == null || data.requestId() == null || data.commandName() == null) { return; } executeCommandAndRespondAsync(data.requestId(), data.commandName(), data.command(), data.args()); From 0bd084c6a6c8c06a6becf63d8e2efc4d51b5ef68 Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Tue, 31 Mar 2026 16:33:52 -0400 Subject: [PATCH 13/47] Reapply "On branch edburns/spotless-agentic-workflow-42" This reverts commit 05d06d97f398a092897725a7651af10f96a047da. --- .github/copilot-instructions.md | 12 ++++++++++++ .github/workflows/copilot-setup-steps.yml | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d7dafb081..e3a8eb275 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -244,6 +244,18 @@ This SDK is designed to be **lightweight with minimal dependencies**: 5. Check for security vulnerabilities 6. Get team approval for non-trivial additions +## Pre-commit Hooks and Formatting (Coding Agent) + +The repository has a pre-commit hook (`.githooks/pre-commit`) that is **automatically enabled** in the Copilot coding agent environment via `copilot-setup-steps.yml`. The hook runs `mvn spotless:check` on any commit that includes changes under `src/`. + +**If a commit fails due to the pre-commit hook:** + +1. Run `mvn spotless:apply` to auto-fix formatting issues. +2. Re-stage the changed files with `git add -u`. +3. Retry the commit. + +**Best practice:** Always run `mvn spotless:apply` before committing Java source changes to avoid hook failures in the first place. If you forget and the hook rejects the commit, follow the three steps above and continue. + ## Commit and PR Guidelines ### Commit Messages diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 6a0cdec5b..8d8aa75c9 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -41,6 +41,10 @@ jobs: distribution: 'temurin' cache: 'maven' + # Enable pre-commit hooks so Spotless formatting is enforced on every commit + - name: Enable pre-commit hooks + run: git config core.hooksPath .githooks + # Verify installations - name: Verify tool installations run: | @@ -50,4 +54,6 @@ jobs: java -version gh --version gh aw version + echo "--- Git hooks path ---" + git config core.hooksPath echo "✅ All tools installed successfully" From 99d456780806e148292e25b51d0f8449545c00bd Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Tue, 31 Mar 2026 16:33:56 -0400 Subject: [PATCH 14/47] Reapply "Update .github/workflows/copilot-setup-steps.yml" This reverts commit afc34c439dd3e393840fe3c213ccc4d7b12fed39. --- .github/workflows/copilot-setup-steps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 8d8aa75c9..145629457 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -41,7 +41,7 @@ jobs: distribution: 'temurin' cache: 'maven' - # Enable pre-commit hooks so Spotless formatting is enforced on every commit + # Enable repository pre-commit hooks (including Spotless checks for relevant source changes) - name: Enable pre-commit hooks run: git config core.hooksPath .githooks From e665e2100ab90262ba9e3a4d2fc04ca1f45c339e Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Mon, 6 Apr 2026 13:27:33 -0400 Subject: [PATCH 15/47] On branch edburns/re-enable-pre-commit-hooks modified: .github/copilot-instructions.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply @Copilot review recommendation. > This new section says the pre-commit hook is “automatically enabled” via `copilot-setup-steps.yml`, but earlier in the same document (and in CONTRIBUTING/README) the hook is described as something developers must manually enable with `git config core.hooksPath .githooks`. Please clarify the scope (e.g., auto-enabled only in the Copilot coding agent environment; local development still requires manual enablement) to avoid conflicting guidance, and consider linking explicitly to `.github/workflows/copilot-setup-steps.yml` for precision. Signed-off-by: Ed Burns --- .github/copilot-instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e3a8eb275..284e2b800 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -104,7 +104,7 @@ When porting from .NET: - 4-space indentation (enforced by Spotless with Eclipse formatter) - Fluent setter pattern for configuration classes (e.g., `new SessionConfig().setModel("gpt-5").setTools(tools)`) - Public APIs require Javadoc (enforced by Checkstyle, except `json` and `events` packages) -- Pre-commit hook runs `mvn spotless:check` - enable with: `git config core.hooksPath .githooks` +- Pre-commit hook runs `mvn spotless:check` - Must be manually enabled with: `git config core.hooksPath .githooks`, except in the Copilot coding agent environment. This hook is explicitly enabled in the Copilot coding agent environment. See [copilot-setup-steps.yml](workflows/copilot-setup-steps.yml). ### Handler Pattern From 8cb5827b9423bdf91fe0c1cf8ac0abb1fec53a11 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Mon, 6 Apr 2026 13:40:27 -0400 Subject: [PATCH 16/47] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f71424950..677389c6e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A ### Requirements - Java 17 or later. **JDK 25 recommended**. Selecting JDK 25 enables the use of virtual threads, as shown in the [Quick Start](#quick-start). -- GitHub Copilot 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`) +- GitHub Copilot CLI 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`) ### Maven From e365e05e692ff57f6371c43837e8db7ec299e6a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:42:42 +0000 Subject: [PATCH 17/47] Fix invalid Java ?. operator in SessionUiApi Javadoc prose Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/b5d79053-ae26-4c98-9047-b2457c08b0b8 Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- src/main/java/com/github/copilot/sdk/json/SessionUiApi.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java b/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java index 6dc583457..f0a43f261 100644 --- a/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java +++ b/src/main/java/com/github/copilot/sdk/json/SessionUiApi.java @@ -12,8 +12,9 @@ * All methods on this interface throw {@link IllegalStateException} if the host * does not report elicitation support via * {@link com.github.copilot.sdk.CopilotSession#getCapabilities()}. Check - * {@code session.getCapabilities().getUi()?.getElicitation() == true} before - * calling. + * {@code session.getCapabilities().getUi() != null && + * Boolean.TRUE.equals(session.getCapabilities().getUi().getElicitation())} + * before calling. * *

Example Usage

* From 8835bd0fe108bff43844c0b678a0143d735bcb17 Mon Sep 17 00:00:00 2001 From: brunoborges <129743+brunoborges@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:29:56 +0000 Subject: [PATCH 18/47] Update JaCoCo coverage badge --- .github/badges/jacoco.svg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg index 482adb13b..a310734d0 100644 --- a/.github/badges/jacoco.svg +++ b/.github/badges/jacoco.svg @@ -6,13 +6,13 @@ - + coverage coverage - 84.7% - 84.7% + 78.3% + 78.3% From 538f138ad33680771cdb132b1c5ea9d856d49fa4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:18:16 +0000 Subject: [PATCH 19/47] Initial plan From 2bd1bf82152887efe1d6e5c2edf2568faeb0b3d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:50:36 +0000 Subject: [PATCH 20/47] Add tests to increase JaCoCo coverage from 78.7% to 85.2% Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/ee2608f6-2d6c-477e-9f79-a2968dec2436 Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .../com/github/copilot/sdk/AgentInfoTest.java | 64 ++++++ .../copilot/sdk/CliServerManagerTest.java | 27 +++ .../com/github/copilot/sdk/CommandsTest.java | 18 ++ .../github/copilot/sdk/ConfigCloneTest.java | 96 +++++++++ .../copilot/sdk/DataObjectCoverageTest.java | 172 ++++++++++++++++ .../com/github/copilot/sdk/ModelInfoTest.java | 68 +++++++ .../copilot/sdk/RpcHandlerDispatcherTest.java | 48 +++++ .../copilot/sdk/SessionEventParserTest.java | 184 ++++++++++++++++++ .../sdk/SessionRequestBuilderTest.java | 95 +++++++++ .../copilot/sdk/TelemetryConfigTest.java | 77 ++++++++ 10 files changed, 849 insertions(+) create mode 100644 src/test/java/com/github/copilot/sdk/AgentInfoTest.java create mode 100644 src/test/java/com/github/copilot/sdk/DataObjectCoverageTest.java create mode 100644 src/test/java/com/github/copilot/sdk/ModelInfoTest.java create mode 100644 src/test/java/com/github/copilot/sdk/TelemetryConfigTest.java diff --git a/src/test/java/com/github/copilot/sdk/AgentInfoTest.java b/src/test/java/com/github/copilot/sdk/AgentInfoTest.java new file mode 100644 index 000000000..0893773e7 --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/AgentInfoTest.java @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.sdk.json.AgentInfo; + +/** + * Unit tests for {@link AgentInfo} getters, setters, and fluent chaining. + */ +class AgentInfoTest { + + @Test + void defaultValuesAreNull() { + var agent = new AgentInfo(); + assertNull(agent.getName()); + assertNull(agent.getDisplayName()); + assertNull(agent.getDescription()); + } + + @Test + void nameGetterSetter() { + var agent = new AgentInfo(); + agent.setName("coder"); + assertEquals("coder", agent.getName()); + } + + @Test + void displayNameGetterSetter() { + var agent = new AgentInfo(); + agent.setDisplayName("Code Assistant"); + assertEquals("Code Assistant", agent.getDisplayName()); + } + + @Test + void descriptionGetterSetter() { + var agent = new AgentInfo(); + agent.setDescription("Helps with coding tasks"); + assertEquals("Helps with coding tasks", agent.getDescription()); + } + + @Test + void fluentChainingReturnsThis() { + var agent = new AgentInfo().setName("coder").setDisplayName("Code Assistant") + .setDescription("Helps with coding tasks"); + + assertEquals("coder", agent.getName()); + assertEquals("Code Assistant", agent.getDisplayName()); + assertEquals("Helps with coding tasks", agent.getDescription()); + } + + @Test + void fluentChainingReturnsSameInstance() { + var agent = new AgentInfo(); + assertSame(agent, agent.setName("test")); + assertSame(agent, agent.setDisplayName("Test")); + assertSame(agent, agent.setDescription("A test agent")); + } +} diff --git a/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java b/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java index f17201583..32257b0a5 100644 --- a/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java +++ b/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test; import com.github.copilot.sdk.json.CopilotClientOptions; +import com.github.copilot.sdk.json.TelemetryConfig; /** * Unit tests for {@link CliServerManager} covering parseCliUrl, @@ -212,4 +213,30 @@ void startCliServerWithNullCliPath() throws Exception { assertNotNull(e); } } + + @Test + void startCliServerWithTelemetryAllOptions() throws Exception { + // The telemetry env vars are applied before ProcessBuilder.start() + // so even with a nonexistent CLI path, the telemetry code path is exercised + var telemetry = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setFilePath("/tmp/telemetry.log") + .setExporterType("otlp-http").setSourceName("test-app").setCaptureContent(true); + var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setTelemetry(telemetry) + .setUseStdio(true); + var manager = new CliServerManager(options); + + var ex = assertThrows(IOException.class, () -> manager.startCliServer()); + assertNotNull(ex); + } + + @Test + void startCliServerWithTelemetryCaptureContentFalse() throws Exception { + // Test the false branch of getCaptureContent() + var telemetry = new TelemetryConfig().setCaptureContent(false); + var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setTelemetry(telemetry) + .setUseStdio(true); + var manager = new CliServerManager(options); + + var ex = assertThrows(IOException.class, () -> manager.startCliServer()); + assertNotNull(ex); + } } diff --git a/src/test/java/com/github/copilot/sdk/CommandsTest.java b/src/test/java/com/github/copilot/sdk/CommandsTest.java index dad26afbb..baf26b39b 100644 --- a/src/test/java/com/github/copilot/sdk/CommandsTest.java +++ b/src/test/java/com/github/copilot/sdk/CommandsTest.java @@ -135,4 +135,22 @@ void commandWireDefinitionNullDescriptionAllowed() { assertEquals("rollback", wire.getName()); assertNull(wire.getDescription()); } + + @Test + void commandWireDefinitionFluentSetters() { + var wire = new CommandWireDefinition(); + wire.setName("status"); + wire.setDescription("Show deployment status"); + + assertEquals("status", wire.getName()); + assertEquals("Show deployment status", wire.getDescription()); + } + + @Test + void commandWireDefinitionFluentSettersChaining() { + var wire = new CommandWireDefinition().setName("logs").setDescription("View application logs"); + + assertEquals("logs", wire.getName()); + assertEquals("View application logs", wire.getDescription()); + } } diff --git a/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java index bf4881d5c..f3787705f 100644 --- a/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java +++ b/src/test/java/com/github/copilot/sdk/ConfigCloneTest.java @@ -17,10 +17,13 @@ import com.github.copilot.sdk.events.AbstractSessionEvent; import com.github.copilot.sdk.json.CopilotClientOptions; +import com.github.copilot.sdk.json.InfiniteSessionConfig; import com.github.copilot.sdk.json.MessageOptions; import com.github.copilot.sdk.json.ModelInfo; import com.github.copilot.sdk.json.ResumeSessionConfig; import com.github.copilot.sdk.json.SessionConfig; +import com.github.copilot.sdk.json.SystemMessageConfig; +import com.github.copilot.sdk.json.TelemetryConfig; class ConfigCloneTest { @@ -193,4 +196,97 @@ void clonePreservesNullFields() { MessageOptions msgClone = msg.clone(); assertNull(msgClone.getMode()); } + + @Test + @SuppressWarnings("deprecation") + void copilotClientOptionsDeprecatedAutoRestart() { + CopilotClientOptions opts = new CopilotClientOptions(); + assertFalse(opts.isAutoRestart()); + opts.setAutoRestart(true); + assertTrue(opts.isAutoRestart()); + } + + @Test + void copilotClientOptionsSetCliArgsNullClearsExisting() { + CopilotClientOptions opts = new CopilotClientOptions(); + opts.setCliArgs(new String[]{"--flag1"}); + assertNotNull(opts.getCliArgs()); + + // Setting null should clear the existing array + opts.setCliArgs(null); + assertNotNull(opts.getCliArgs()); + assertEquals(0, opts.getCliArgs().length); + } + + @Test + void copilotClientOptionsSetEnvironmentNullClearsExisting() { + CopilotClientOptions opts = new CopilotClientOptions(); + opts.setEnvironment(Map.of("KEY", "VALUE")); + assertNotNull(opts.getEnvironment()); + + // Setting null should clear the existing map (clears in-place → returns empty + // map) + opts.setEnvironment(null); + var env = opts.getEnvironment(); + assertTrue(env == null || env.isEmpty()); + } + + @Test + @SuppressWarnings("deprecation") + void copilotClientOptionsDeprecatedGithubToken() { + CopilotClientOptions opts = new CopilotClientOptions(); + opts.setGithubToken("ghp_deprecated_token"); + assertEquals("ghp_deprecated_token", opts.getGithubToken()); + assertEquals("ghp_deprecated_token", opts.getGitHubToken()); + } + + @Test + void copilotClientOptionsSetTelemetry() { + var telemetry = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318"); + var opts = new CopilotClientOptions(); + opts.setTelemetry(telemetry); + assertSame(telemetry, opts.getTelemetry()); + } + + @Test + void copilotClientOptionsSetUseLoggedInUserNull() { + var opts = new CopilotClientOptions(); + opts.setUseLoggedInUser(null); + // null → Boolean.FALSE + assertEquals(Boolean.FALSE, opts.getUseLoggedInUser()); + } + + @Test + void resumeSessionConfigAllSetters() { + var config = new ResumeSessionConfig(); + + var sysMsg = new SystemMessageConfig(); + config.setSystemMessage(sysMsg); + assertSame(sysMsg, config.getSystemMessage()); + + config.setAvailableTools(List.of("bash", "read_file")); + assertEquals(List.of("bash", "read_file"), config.getAvailableTools()); + + config.setExcludedTools(List.of("write_file")); + assertEquals(List.of("write_file"), config.getExcludedTools()); + + config.setReasoningEffort("high"); + assertEquals("high", config.getReasoningEffort()); + + config.setWorkingDirectory("/project/src"); + assertEquals("/project/src", config.getWorkingDirectory()); + + config.setConfigDir("/home/user/.config/copilot"); + assertEquals("/home/user/.config/copilot", config.getConfigDir()); + + config.setSkillDirectories(List.of("/skills/custom")); + assertEquals(List.of("/skills/custom"), config.getSkillDirectories()); + + config.setDisabledSkills(List.of("some-skill")); + assertEquals(List.of("some-skill"), config.getDisabledSkills()); + + var infiniteConfig = new InfiniteSessionConfig().setEnabled(true); + config.setInfiniteSessions(infiniteConfig); + assertSame(infiniteConfig, config.getInfiniteSessions()); + } } diff --git a/src/test/java/com/github/copilot/sdk/DataObjectCoverageTest.java b/src/test/java/com/github/copilot/sdk/DataObjectCoverageTest.java new file mode 100644 index 000000000..203f5faed --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/DataObjectCoverageTest.java @@ -0,0 +1,172 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.copilot.sdk.json.GetForegroundSessionResponse; +import com.github.copilot.sdk.json.PermissionRequest; +import com.github.copilot.sdk.json.PermissionRequestResult; +import com.github.copilot.sdk.json.PostToolUseHookInput; +import com.github.copilot.sdk.json.PostToolUseHookOutput; +import com.github.copilot.sdk.json.PreToolUseHookInput; +import com.github.copilot.sdk.json.PreToolUseHookOutput; +import com.github.copilot.sdk.json.SectionOverride; +import com.github.copilot.sdk.json.SetForegroundSessionResponse; +import com.github.copilot.sdk.json.ToolBinaryResult; +import com.github.copilot.sdk.json.ToolResultObject; + +/** + * Unit tests for various data transfer objects and record types that were + * missing coverage, including hook output factory methods, record constructors, + * and getters for hook inputs. + */ +class DataObjectCoverageTest { + + // ===== PreToolUseHookOutput factory methods ===== + + @Test + void preToolUseHookOutputDenyWithReason() { + var output = PreToolUseHookOutput.deny("Security policy violation"); + assertEquals("deny", output.permissionDecision()); + assertEquals("Security policy violation", output.permissionDecisionReason()); + assertNull(output.modifiedArgs()); + } + + @Test + void preToolUseHookOutputAsk() { + var output = PreToolUseHookOutput.ask(); + assertEquals("ask", output.permissionDecision()); + assertNull(output.permissionDecisionReason()); + } + + @Test + void preToolUseHookOutputWithModifiedArgs() { + ObjectNode args = JsonNodeFactory.instance.objectNode(); + args.put("path", "/safe/path"); + + var output = PreToolUseHookOutput.withModifiedArgs("allow", args); + assertEquals("allow", output.permissionDecision()); + assertEquals(args, output.modifiedArgs()); + } + + // ===== PostToolUseHookOutput record ===== + + @Test + void postToolUseHookOutputRecord() { + var output = new PostToolUseHookOutput(null, "Extra context", false); + assertNull(output.modifiedResult()); + assertEquals("Extra context", output.additionalContext()); + assertFalse(output.suppressOutput()); + } + + // ===== ToolBinaryResult record ===== + + @Test + void toolBinaryResultRecord() { + var result = new ToolBinaryResult("base64data==", "image/png", "image", "A chart"); + assertEquals("base64data==", result.data()); + assertEquals("image/png", result.mimeType()); + assertEquals("image", result.type()); + assertEquals("A chart", result.description()); + } + + // ===== GetForegroundSessionResponse record ===== + + @Test + void getForegroundSessionResponseRecord() { + var response = new GetForegroundSessionResponse("session-123", "/home/user/project"); + assertEquals("session-123", response.sessionId()); + assertEquals("/home/user/project", response.workspacePath()); + } + + // ===== SetForegroundSessionResponse record ===== + + @Test + void setForegroundSessionResponseRecord() { + var successResponse = new SetForegroundSessionResponse(true, null); + assertTrue(successResponse.success()); + assertNull(successResponse.error()); + + var errorResponse = new SetForegroundSessionResponse(false, "Session not found"); + assertFalse(errorResponse.success()); + assertEquals("Session not found", errorResponse.error()); + } + + // ===== ToolResultObject factory methods ===== + + @Test + void toolResultObjectErrorWithTextAndError() { + var result = ToolResultObject.error("partial output", "File not found"); + assertEquals("error", result.resultType()); + assertEquals("partial output", result.textResultForLlm()); + assertEquals("File not found", result.error()); + } + + @Test + void toolResultObjectFailure() { + var result = ToolResultObject.failure("Tool unavailable", "Unknown tool"); + assertEquals("failure", result.resultType()); + assertEquals("Tool unavailable", result.textResultForLlm()); + assertEquals("Unknown tool", result.error()); + } + + // ===== PermissionRequest additional setters ===== + + @Test + void permissionRequestSetExtensionData() { + var req = new PermissionRequest(); + req.setExtensionData(java.util.Map.of("key", "value")); + assertEquals("value", req.getExtensionData().get("key")); + } + + // ===== SectionOverride setContent ===== + + @Test + void sectionOverrideSetContent() { + var override = new SectionOverride(); + override.setContent("Custom content"); + assertEquals("Custom content", override.getContent()); + } + + // ===== PreToolUseHookInput getters ===== + + @Test + void preToolUseHookInputGetters() { + var input = new PreToolUseHookInput(); + // Default values + assertEquals(0L, input.getTimestamp()); + assertNull(input.getCwd()); + assertNull(input.getToolArgs()); + } + + // ===== PostToolUseHookInput getters ===== + + @Test + void postToolUseHookInputGetters() { + var input = new PostToolUseHookInput(); + // Default values + assertEquals(0L, input.getTimestamp()); + assertNull(input.getCwd()); + assertNull(input.getToolArgs()); + } + + // ===== PermissionRequestResult setRules ===== + + @Test + void permissionRequestResultSetRules() { + var result = new PermissionRequestResult().setKind("allow"); + var rules = new java.util.ArrayList(); + rules.add("bash:read"); + rules.add("bash:write"); + result.setRules(rules); + assertEquals(2, result.getRules().size()); + assertEquals("bash:read", result.getRules().get(0)); + } +} diff --git a/src/test/java/com/github/copilot/sdk/ModelInfoTest.java b/src/test/java/com/github/copilot/sdk/ModelInfoTest.java new file mode 100644 index 000000000..f36d0c4bd --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/ModelInfoTest.java @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.sdk.json.ModelInfo; +import com.github.copilot.sdk.json.ModelSupports; +import com.github.copilot.sdk.json.SessionMetadata; + +/** + * Unit tests for {@link ModelInfo}, {@link ModelSupports}, and + * {@link SessionMetadata} getters and setters. + */ +class ModelInfoTest { + + @Test + void modelSupportsReasoningEffortGetterSetter() { + var supports = new ModelSupports(); + assertFalse(supports.isReasoningEffort()); + + supports.setReasoningEffort(true); + assertTrue(supports.isReasoningEffort()); + } + + @Test + void modelSupportsFluentChaining() { + var supports = new ModelSupports().setVision(true).setReasoningEffort(true); + assertTrue(supports.isVision()); + assertTrue(supports.isReasoningEffort()); + } + + @Test + void modelInfoSupportedReasoningEffortsGetterSetter() { + var model = new ModelInfo(); + assertNull(model.getSupportedReasoningEfforts()); + + model.setSupportedReasoningEfforts(List.of("low", "medium", "high")); + assertEquals(List.of("low", "medium", "high"), model.getSupportedReasoningEfforts()); + } + + @Test + void modelInfoDefaultReasoningEffortGetterSetter() { + var model = new ModelInfo(); + assertNull(model.getDefaultReasoningEffort()); + + model.setDefaultReasoningEffort("medium"); + assertEquals("medium", model.getDefaultReasoningEffort()); + } + + @Test + void sessionMetadataGettersAndSetters() { + var meta = new SessionMetadata(); + assertNull(meta.getStartTime()); + assertNull(meta.getModifiedTime()); + assertNull(meta.getSummary()); + assertFalse(meta.isRemote()); + + meta.setRemote(true); + assertTrue(meta.isRemote()); + } +} diff --git a/src/test/java/com/github/copilot/sdk/RpcHandlerDispatcherTest.java b/src/test/java/com/github/copilot/sdk/RpcHandlerDispatcherTest.java index 79f5d7c7e..315a38b90 100644 --- a/src/test/java/com/github/copilot/sdk/RpcHandlerDispatcherTest.java +++ b/src/test/java/com/github/copilot/sdk/RpcHandlerDispatcherTest.java @@ -542,4 +542,52 @@ void hooksInvokeWithNoHooksRegistered() throws Exception { JsonNode output = response.get("result").get("output"); assertTrue(output == null || output.isNull(), "Output should be null when no hooks registered"); } + + // ===== systemMessage.transform tests ===== + + @Test + void systemMessageTransformWithUnknownSession() throws Exception { + ObjectNode params = MAPPER.createObjectNode(); + params.put("sessionId", "nonexistent"); + params.putObject("sections"); + + invokeHandler("systemMessage.transform", "40", params); + + JsonNode response = readResponse(); + assertNotNull(response.get("error")); + assertEquals(-32602, response.get("error").get("code").asInt()); + } + + @Test + void systemMessageTransformWithNullSessionId() throws Exception { + ObjectNode params = MAPPER.createObjectNode(); + // sessionId omitted → null → session lookup returns null → error + params.putObject("sections"); + + invokeHandler("systemMessage.transform", "41", params); + + JsonNode response = readResponse(); + assertNotNull(response.get("error")); + assertEquals(-32602, response.get("error").get("code").asInt()); + } + + @Test + void systemMessageTransformWithKnownSessionNoCallbacks() throws Exception { + // Session without transform callbacks returns the sections unchanged + createSession("s1"); + + ObjectNode params = MAPPER.createObjectNode(); + params.put("sessionId", "s1"); + ObjectNode sections = params.putObject("sections"); + ObjectNode sectionData = sections.putObject("identity"); + sectionData.put("content", "Original content"); + + invokeHandler("systemMessage.transform", "42", params); + + JsonNode response = readResponse(); + assertNotNull(response.get("result")); + JsonNode resultSections = response.get("result").get("sections"); + assertNotNull(resultSections); + assertEquals("Original content", resultSections.get("identity").get("content").asText()); + } } diff --git a/src/test/java/com/github/copilot/sdk/SessionEventParserTest.java b/src/test/java/com/github/copilot/sdk/SessionEventParserTest.java index 5898d5301..4a63bb243 100644 --- a/src/test/java/com/github/copilot/sdk/SessionEventParserTest.java +++ b/src/test/java/com/github/copilot/sdk/SessionEventParserTest.java @@ -2385,4 +2385,188 @@ void testParseSystemNotificationEvent() throws Exception { assertNotNull(event.getData()); assertTrue(event.getData().content().contains("Agent completed")); } + + @Test + void testParseCapabilitiesChangedEvent() throws Exception { + String json = """ + { + "type": "capabilities.changed", + "data": { + "ui": { + "elicitation": true + } + } + } + """; + + AbstractSessionEvent event = parseJson(json); + assertNotNull(event); + assertInstanceOf(CapabilitiesChangedEvent.class, event); + assertEquals("capabilities.changed", event.getType()); + + var castedEvent = (CapabilitiesChangedEvent) event; + assertNotNull(castedEvent.getData()); + assertNotNull(castedEvent.getData().ui()); + assertTrue(castedEvent.getData().ui().elicitation()); + + // Verify setData round-trip + var newData = new CapabilitiesChangedEvent.CapabilitiesChangedData( + new CapabilitiesChangedEvent.CapabilitiesChangedUi(false)); + castedEvent.setData(newData); + assertFalse(castedEvent.getData().ui().elicitation()); + } + + @Test + void testParseCommandExecuteEvent() throws Exception { + String json = """ + { + "type": "command.execute", + "data": { + "requestId": "req-001", + "command": "/deploy production", + "commandName": "deploy", + "args": "production" + } + } + """; + + AbstractSessionEvent event = parseJson(json); + assertNotNull(event); + assertInstanceOf(CommandExecuteEvent.class, event); + assertEquals("command.execute", event.getType()); + + var castedEvent = (CommandExecuteEvent) event; + assertNotNull(castedEvent.getData()); + assertEquals("req-001", castedEvent.getData().requestId()); + assertEquals("/deploy production", castedEvent.getData().command()); + assertEquals("deploy", castedEvent.getData().commandName()); + assertEquals("production", castedEvent.getData().args()); + + // Verify setData round-trip + castedEvent.setData(new CommandExecuteEvent.CommandExecuteData("req-002", "/rollback", "rollback", null)); + assertEquals("req-002", castedEvent.getData().requestId()); + } + + @Test + void testParseElicitationRequestedEvent() throws Exception { + String json = """ + { + "type": "elicitation.requested", + "data": { + "requestId": "elix-001", + "toolCallId": "tc-123", + "elicitationSource": "mcp_tool", + "message": "Please provide your name", + "mode": "form", + "requestedSchema": { + "type": "object", + "properties": { + "name": {"type": "string"} + }, + "required": ["name"] + }, + "url": null + } + } + """; + + AbstractSessionEvent event = parseJson(json); + assertNotNull(event); + assertInstanceOf(ElicitationRequestedEvent.class, event); + assertEquals("elicitation.requested", event.getType()); + + var castedEvent = (ElicitationRequestedEvent) event; + assertNotNull(castedEvent.getData()); + assertEquals("elix-001", castedEvent.getData().requestId()); + assertEquals("tc-123", castedEvent.getData().toolCallId()); + assertEquals("mcp_tool", castedEvent.getData().elicitationSource()); + assertEquals("Please provide your name", castedEvent.getData().message()); + assertEquals("form", castedEvent.getData().mode()); + assertNotNull(castedEvent.getData().requestedSchema()); + assertEquals("object", castedEvent.getData().requestedSchema().type()); + assertNotNull(castedEvent.getData().requestedSchema().properties()); + assertNotNull(castedEvent.getData().requestedSchema().required()); + assertTrue(castedEvent.getData().requestedSchema().required().contains("name")); + + // Verify setData round-trip + castedEvent.setData(new ElicitationRequestedEvent.ElicitationRequestedData("elix-002", null, null, "Enter URL", + "url", null, "https://example.com")); + assertEquals("elix-002", castedEvent.getData().requestId()); + assertEquals("url", castedEvent.getData().mode()); + } + + @Test + void testParseSessionContextChangedEvent() throws Exception { + String json = """ + { + "type": "session.context_changed", + "data": { + "cwd": "/home/user/project", + "gitRoot": "/home/user/project", + "repository": "my-repo", + "branch": "main" + } + } + """; + + AbstractSessionEvent event = parseJson(json); + assertNotNull(event); + assertInstanceOf(SessionContextChangedEvent.class, event); + assertEquals("session.context_changed", event.getType()); + + var castedEvent = (SessionContextChangedEvent) event; + assertNotNull(castedEvent.getData()); + assertEquals("/home/user/project", castedEvent.getData().getCwd()); + + // Verify setData round-trip + castedEvent.setData(null); + assertNull(castedEvent.getData()); + } + + @Test + void testParseSessionTaskCompleteEvent() throws Exception { + String json = """ + { + "type": "session.task_complete", + "data": { + "summary": "Task completed successfully" + } + } + """; + + AbstractSessionEvent event = parseJson(json); + assertNotNull(event); + assertInstanceOf(SessionTaskCompleteEvent.class, event); + assertEquals("session.task_complete", event.getType()); + + var castedEvent = (SessionTaskCompleteEvent) event; + assertNotNull(castedEvent.getData()); + assertEquals("Task completed successfully", castedEvent.getData().summary()); + + // Verify setData round-trip + castedEvent.setData(new SessionTaskCompleteEvent.SessionTaskCompleteData("New summary")); + assertEquals("New summary", castedEvent.getData().summary()); + } + + @Test + void testParseSubagentDeselectedEvent() throws Exception { + String json = """ + { + "type": "subagent.deselected", + "data": {} + } + """; + + AbstractSessionEvent event = parseJson(json); + assertNotNull(event); + assertInstanceOf(SubagentDeselectedEvent.class, event); + assertEquals("subagent.deselected", event.getType()); + + var castedEvent = (SubagentDeselectedEvent) event; + assertNotNull(castedEvent.getData()); + + // Verify setData round-trip + castedEvent.setData(new SubagentDeselectedEvent.SubagentDeselectedData()); + assertNotNull(castedEvent.getData()); + } } diff --git a/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java b/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java index 1a64b7534..75457583e 100644 --- a/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java +++ b/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java @@ -13,6 +13,9 @@ import org.junit.jupiter.api.Test; import com.github.copilot.sdk.json.CreateSessionRequest; +import com.github.copilot.sdk.json.ElicitationHandler; +import com.github.copilot.sdk.json.ElicitationResult; +import com.github.copilot.sdk.json.ElicitationResultAction; import com.github.copilot.sdk.json.ResumeSessionConfig; import com.github.copilot.sdk.json.ResumeSessionRequest; import com.github.copilot.sdk.json.SessionConfig; @@ -305,4 +308,96 @@ void extractTransformCallbacks_customizeModeWithTransform_extractsCallbacks() { assertEquals(com.github.copilot.sdk.json.SectionOverrideAction.TRANSFORM, wireSection.getAction()); assertNull(wireSection.getTransform()); } + + @Test + @SuppressWarnings("deprecation") + void buildCreateRequestWithSessionId_usesProvidedSessionId() { + var config = new SessionConfig(); + config.setSessionId("my-session-id"); + + // The deprecated single-arg overload uses the sessionId from config when set + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + + assertEquals("my-session-id", request.getSessionId()); + } + + @Test + void configureSessionWithNullConfig_returnsEarly() { + // configureSession with null config should return without error + CopilotSession session = new CopilotSession("session-1", null); + // Covers the null config early-return branch (L219-220) + assertDoesNotThrow(() -> SessionRequestBuilder.configureSession(session, (SessionConfig) null)); + } + + @Test + void configureSessionWithCommands_registersCommands() { + CopilotSession session = new CopilotSession("session-1", null); + + var cmd = new com.github.copilot.sdk.json.CommandDefinition().setName("deploy") + .setHandler(ctx -> CompletableFuture.completedFuture(null)); + var config = new SessionConfig().setCommands(List.of(cmd)); + + // Covers config.getCommands() != null branch (L235-236) + SessionRequestBuilder.configureSession(session, config); + // If no exception thrown, the branch was covered + } + + @Test + void configureSessionWithElicitationHandler_registersHandler() { + CopilotSession session = new CopilotSession("session-1", null); + + ElicitationHandler handler = (context) -> CompletableFuture + .completedFuture(new ElicitationResult().setAction(ElicitationResultAction.CANCEL)); + var config = new SessionConfig().setOnElicitationRequest(handler); + + // Covers config.getOnElicitationRequest() != null branch (L238-239) + SessionRequestBuilder.configureSession(session, config); + } + + @Test + void configureSessionWithOnEvent_registersEventHandler() { + CopilotSession session = new CopilotSession("session-1", null); + + var config = new SessionConfig().setOnEvent(event -> { + }); + + // Covers config.getOnEvent() != null branch (L241-242) + SessionRequestBuilder.configureSession(session, config); + } + + @Test + void configureResumedSessionWithCommands_registersCommands() { + CopilotSession session = new CopilotSession("session-1", null); + + var cmd = new com.github.copilot.sdk.json.CommandDefinition().setName("rollback") + .setHandler(ctx -> CompletableFuture.completedFuture(null)); + var config = new ResumeSessionConfig().setCommands(List.of(cmd)); + + // Covers ResumeSessionConfig.getCommands() != null branch (L271-272) + SessionRequestBuilder.configureSession(session, config); + } + + @Test + void configureResumedSessionWithElicitationHandler_registersHandler() { + CopilotSession session = new CopilotSession("session-1", null); + + ElicitationHandler handler = (context) -> CompletableFuture + .completedFuture(new ElicitationResult().setAction(ElicitationResultAction.CANCEL)); + var config = new ResumeSessionConfig().setOnElicitationRequest(handler); + + // Covers ResumeSessionConfig.getOnElicitationRequest() != null branch + // (L274-275) + SessionRequestBuilder.configureSession(session, config); + } + + @Test + void configureResumedSessionWithOnEvent_registersEventHandler() { + CopilotSession session = new CopilotSession("session-1", null); + + var config = new ResumeSessionConfig().setOnEvent(event -> { + }); + + // Covers ResumeSessionConfig.getOnEvent() != null branch (L277-278) + SessionRequestBuilder.configureSession(session, config); + } } diff --git a/src/test/java/com/github/copilot/sdk/TelemetryConfigTest.java b/src/test/java/com/github/copilot/sdk/TelemetryConfigTest.java new file mode 100644 index 000000000..99b360d2d --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/TelemetryConfigTest.java @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.sdk.json.TelemetryConfig; + +/** + * Unit tests for {@link TelemetryConfig} getters, setters, and fluent chaining. + */ +class TelemetryConfigTest { + + @Test + void defaultValuesAreNull() { + var config = new TelemetryConfig(); + assertNull(config.getOtlpEndpoint()); + assertNull(config.getFilePath()); + assertNull(config.getExporterType()); + assertNull(config.getSourceName()); + assertNull(config.getCaptureContent()); + } + + @Test + void otlpEndpointGetterSetter() { + var config = new TelemetryConfig(); + config.setOtlpEndpoint("http://localhost:4318"); + assertEquals("http://localhost:4318", config.getOtlpEndpoint()); + } + + @Test + void filePathGetterSetter() { + var config = new TelemetryConfig(); + config.setFilePath("/tmp/telemetry.log"); + assertEquals("/tmp/telemetry.log", config.getFilePath()); + } + + @Test + void exporterTypeGetterSetter() { + var config = new TelemetryConfig(); + config.setExporterType("otlp-http"); + assertEquals("otlp-http", config.getExporterType()); + } + + @Test + void sourceNameGetterSetter() { + var config = new TelemetryConfig(); + config.setSourceName("my-app"); + assertEquals("my-app", config.getSourceName()); + } + + @Test + void captureContentGetterSetter() { + var config = new TelemetryConfig(); + config.setCaptureContent(true); + assertTrue(config.getCaptureContent()); + + config.setCaptureContent(false); + assertFalse(config.getCaptureContent()); + } + + @Test + void fluentChainingReturnsThis() { + var config = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setFilePath("/tmp/spans.json") + .setExporterType("file").setSourceName("sdk-test").setCaptureContent(true); + + assertEquals("http://localhost:4318", config.getOtlpEndpoint()); + assertEquals("/tmp/spans.json", config.getFilePath()); + assertEquals("file", config.getExporterType()); + assertEquals("sdk-test", config.getSourceName()); + assertTrue(config.getCaptureContent()); + } +} From 2b24c6f639e1f51dd6228b6ef8ea4360f111ca41 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Mon, 6 Apr 2026 18:30:36 -0400 Subject: [PATCH 21/47] Update Mockito to 5.23.0 for Java 25 support Mockito 5.17.0 bundled ByteBuddy 1.15.11 which does not support Java 25 class files, causing SchedulerShutdownRaceTest and ZeroTimeoutContractTest to fail with 'Mockito cannot mock this class'. - Upgrade mockito-core from 5.17.0 to 5.23.0 (includes ByteBuddy 1.17.5+) - Add JDK 21+ profile that passes -XX:+EnableDynamicAgentLoading to Surefire, allowing Mockito/ByteBuddy to attach at runtime (JEP 451) - Use a default-empty surefire.jvm.args property so JDK 17-20 are unaffected by the flag Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pom.xml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 45404d99a..43e36ec4d 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,8 @@ ${copilot.sdk.clone.dir}/test false + + @@ -89,7 +91,7 @@ org.mockito mockito-core - 5.17.0 + 5.23.0 test @@ -245,8 +247,8 @@ maven-surefire-plugin 3.5.4 - - ${testExecutionAgentArgs} + + ${testExecutionAgentArgs} ${surefire.jvm.args} ${copilot.tests.dir} ${copilot.sdk.clone.dir} @@ -543,6 +545,18 @@ + + + jdk21+ + + [21,) + + + -XX:+EnableDynamicAgentLoading + + skip-test-harness From cce87cb1caf9701753bf64ee25be76cc3156d224 Mon Sep 17 00:00:00 2001 From: brunoborges <129743+brunoborges@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:53:38 +0000 Subject: [PATCH 22/47] Update JaCoCo coverage badge --- .github/badges/jacoco.svg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg index a310734d0..f1a7c5eb3 100644 --- a/.github/badges/jacoco.svg +++ b/.github/badges/jacoco.svg @@ -6,13 +6,13 @@ - + coverage coverage - 78.3% - 78.3% + 84.4% + 84.4% From 00a3aa58630e142c23b7c85a80e0caa91a2dcc79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:05:43 +0000 Subject: [PATCH 23/47] Initial plan From 7e440159b95bda22d9cee3239f96befe5cf848eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:28:30 +0000 Subject: [PATCH 24/47] Update .lastmerge to c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1 Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .lastmerge | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lastmerge b/.lastmerge index 0d0067b5b..83feb636c 100644 --- a/.lastmerge +++ b/.lastmerge @@ -1 +1 @@ -f7fd7577109d64e261456b16c49baa56258eae4e +c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1 From 1508b9f323dec865eadef9fb53c7512bbdda1b80 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 09:37:25 -0400 Subject: [PATCH 25/47] Update documentation for PR #52 upstream sync features Add coverage for slash commands, elicitation (UI dialogs), session capabilities, SessionUiApi, and getSessionMetadata across: - CHANGELOG.md: Unreleased section with Added/Fixed entries - documentation.md: event types, SessionConfig reference, resume options - advanced.md: table of contents entries for new sections - site.xml: navigation menu items Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 22 +++++++++++++++++++++- src/site/markdown/advanced.md | 7 +++++++ src/site/markdown/documentation.md | 17 +++++++++++++++++ src/site/site.xml | 3 +++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e306db097..f5050a3d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] -> **Upstream sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15) +> **Upstream sync:** [`github/copilot-sdk@f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd7577109d64e261456b16c49baa56258eae4e) + +### Added + +- Slash commands — register `/command` handlers invoked from the CLI TUI via `SessionConfig.setCommands()` (upstream: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757)) +- `CommandDefinition`, `CommandContext`, `CommandHandler`, `CommandWireDefinition` — types for defining and handling slash commands +- `CommandExecuteEvent` — event dispatched when a registered slash command is executed +- Elicitation (UI dialogs) — incoming handler via `SessionConfig.setOnElicitationRequest()` and outgoing convenience methods via `session.getUi()` (upstream: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757)) +- `ElicitationContext`, `ElicitationHandler`, `ElicitationParams`, `ElicitationResult`, `ElicitationResultAction`, `ElicitationSchema`, `InputOptions` — types for elicitation +- `ElicitationRequestedEvent` — event dispatched when an elicitation request is received +- `SessionUiApi` — convenience API on `session.getUi()` for `confirm()`, `select()`, `input()`, and `elicitation()` calls +- `SessionCapabilities` and `SessionUiCapabilities` — session capability reporting populated from create/resume response +- `CapabilitiesChangedEvent` — event dispatched when session capabilities are updated +- `CopilotClient.getSessionMetadata(String)` — O(1) session lookup by ID +- `GetSessionMetadataResponse` — response type for `getSessionMetadata` + +### Fixed + +- Permission events already resolved by a pre-hook now short-circuit before invoking the client-side handler +- `SessionUiApi` Javadoc now uses valid Java null-check syntax instead of `?.` +- README updated to say "GitHub Copilot CLI 1.0.17" instead of "GitHub Copilot 1.0.17" ## [0.2.1-java.1] - 2026-04-02 diff --git a/src/site/markdown/advanced.md b/src/site/markdown/advanced.md index 598acc2f7..5ae5c8f94 100644 --- a/src/site/markdown/advanced.md +++ b/src/site/markdown/advanced.md @@ -47,6 +47,13 @@ This guide covers advanced scenarios for extending and customizing your Copilot - [Custom Event Error Handler](#Custom_Event_Error_Handler) - [Event Error Policy](#Event_Error_Policy) - [OpenTelemetry](#OpenTelemetry) +- [Slash Commands](#Slash_Commands) + - [Registering Commands](#Registering_Commands) +- [Elicitation (UI Dialogs)](#Elicitation_UI_Dialogs) + - [Incoming Elicitation Handler](#Incoming_Elicitation_Handler) + - [Session Capabilities](#Session_Capabilities) + - [Outgoing Elicitation via session.getUi()](#Outgoing_Elicitation_via_session.getUi) +- [Getting Session Metadata by ID](#Getting_Session_Metadata_by_ID) --- diff --git a/src/site/markdown/documentation.md b/src/site/markdown/documentation.md index 8a9f919ac..a96f66698 100644 --- a/src/site/markdown/documentation.md +++ b/src/site/markdown/documentation.md @@ -245,6 +245,19 @@ The SDK supports event types organized by category. All events extend `AbstractS |-------|-------------|-------------| | `CommandQueuedEvent` | `command.queued` | A command was queued for execution | | `CommandCompletedEvent` | `command.completed` | A queued command completed | +| `CommandExecuteEvent` | `command.execute` | A registered slash command was dispatched for execution | + +### Elicitation Events + +| Event | Type String | Description | +|-------|-------------|-------------| +| `ElicitationRequestedEvent` | `elicitation.requested` | An elicitation (UI dialog) request was received | + +### Capability Events + +| Event | Type String | Description | +|-------|-------------|-------------| +| `CapabilitiesChangedEvent` | `capabilities.changed` | Session capabilities were updated | ### Plan Mode Events @@ -633,6 +646,8 @@ When resuming a session, you can optionally reconfigure many settings. This is u | `skillDirectories` | Directories to load skills from | | `disabledSkills` | Skills to disable | | `infiniteSessions` | Configure infinite session behavior | +| `commands` | Slash command definitions for the resumed session | +| `onElicitationRequest` | Handler for incoming elicitation requests | | `disableResume` | When `true`, resumes without emitting a `session.resume` event | | `onEvent` | Event handler registered before session resumption | @@ -691,6 +706,8 @@ Complete list of all `SessionConfig` options for `createSession()`: | `skillDirectories` | List<String> | Directories to load skills from | [Skills](advanced.html#Skills_Configuration) | | `disabledSkills` | List<String> | Skills to disable by name | [Skills](advanced.html#Skills_Configuration) | | `configDir` | String | Custom configuration directory | [Config Dir](advanced.html#Custom_Configuration_Directory) | +| `commands` | List<CommandDefinition> | Slash command definitions | [Slash Commands](advanced.html#Slash_Commands) | +| `onElicitationRequest` | ElicitationHandler | Handler for incoming elicitation requests | [Elicitation](advanced.html#Elicitation_UI_Dialogs) | | `onEvent` | Consumer<AbstractSessionEvent> | Event handler registered before session creation | [Early Event Registration](advanced.html#Early_Event_Registration) | ### Cloning SessionConfig diff --git a/src/site/site.xml b/src/site/site.xml index f89ebe076..d012c0335 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -59,6 +59,9 @@ + + + From 04d03a798acc69044aa5e6377334fe34e0cc952a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Apr 2026 14:13:12 +0000 Subject: [PATCH 26/47] docs: update version references to 0.2.2-java.1 --- CHANGELOG.md | 19 ++++++++++++++----- README.md | 4 ++-- src/site/markdown/cookbook/error-handling.md | 14 +++++++------- .../markdown/cookbook/managing-local-files.md | 4 ++-- .../markdown/cookbook/multiple-sessions.md | 4 ++-- .../markdown/cookbook/persisting-sessions.md | 6 +++--- .../markdown/cookbook/pr-visualization.md | 2 +- 7 files changed, 31 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5050a3d2..38f6e6589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] -> **Upstream sync:** [`github/copilot-sdk@f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd7577109d64e261456b16c49baa56258eae4e) +> **Upstream sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1) +## [0.2.2-java.1] - 2026-04-07 + +> **Upstream sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1) ### Added - Slash commands — register `/command` handlers invoked from the CLI TUI via `SessionConfig.setCommands()` (upstream: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757)) @@ -485,16 +488,22 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` - Pre-commit hook for Spotless code formatting - Comprehensive API documentation -[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD +[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1 +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD +[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1 [0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1 -[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD +[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1 [0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1 [0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0 -[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD +[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1 [0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1 [0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0 [0.1.32-java.0]: https://github.com/github/copilot-sdk-java/compare/v1.0.11...v0.1.32-java.0 -[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...HEAD +[Unreleased]: https://github.com/github/copilot-sdk-java/compare/v0.2.2-java.1...HEAD +[0.2.2-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.1...v0.2.2-java.1 [0.2.1-java.1]: https://github.com/github/copilot-sdk-java/compare/v0.2.1-java.0...v0.2.1-java.1 [0.2.1-java.0]: https://github.com/github/copilot-sdk-java/compare/v0.1.32-java.0...v0.2.1-java.0 [0.1.32-java.0]: https://github.com/github/copilot-sdk-java/compare/v1.0.11...v0.1.32-java.0 diff --git a/README.md b/README.md index 677389c6e..539a33895 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A com.github copilot-sdk-java - 0.2.1-java.1 + 0.2.2-java.1 ``` @@ -60,7 +60,7 @@ Snapshot builds of the next development version are published to Maven Central S ### Gradle ```groovy -implementation 'com.github:copilot-sdk-java:0.2.1-java.1' +implementation 'com.github:copilot-sdk-java:0.2.2-java.1' ``` ## Quick Start diff --git a/src/site/markdown/cookbook/error-handling.md b/src/site/markdown/cookbook/error-handling.md index 5ee5ef2ca..4240dc1ff 100644 --- a/src/site/markdown/cookbook/error-handling.md +++ b/src/site/markdown/cookbook/error-handling.md @@ -30,7 +30,7 @@ jbang BasicErrorHandling.java **Code:** ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -64,7 +64,7 @@ public class BasicErrorHandling { ## Handling specific error types ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import java.util.concurrent.ExecutionException; @@ -99,7 +99,7 @@ public class SpecificErrorHandling { ## Timeout handling ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotSession; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -130,7 +130,7 @@ public class TimeoutHandling { ## Aborting a request ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotSession; import com.github.copilot.sdk.json.MessageOptions; import java.util.concurrent.Executors; @@ -162,7 +162,7 @@ public class AbortRequest { ## Graceful shutdown ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; public class GracefulShutdown { @@ -192,7 +192,7 @@ public class GracefulShutdown { ## Try-with-resources pattern ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -224,7 +224,7 @@ public class TryWithResources { ## Handling tool errors ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; diff --git a/src/site/markdown/cookbook/managing-local-files.md b/src/site/markdown/cookbook/managing-local-files.md index aa9ba23bc..9535772b2 100644 --- a/src/site/markdown/cookbook/managing-local-files.md +++ b/src/site/markdown/cookbook/managing-local-files.md @@ -34,7 +34,7 @@ jbang ManagingLocalFiles.java **Code:** ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.events.SessionIdleEvent; @@ -161,7 +161,7 @@ session.send(new MessageOptions().setPrompt(prompt)); ## Interactive file organization ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import java.io.BufferedReader; import java.io.InputStreamReader; diff --git a/src/site/markdown/cookbook/multiple-sessions.md b/src/site/markdown/cookbook/multiple-sessions.md index fe5c2f0d9..776b6db6d 100644 --- a/src/site/markdown/cookbook/multiple-sessions.md +++ b/src/site/markdown/cookbook/multiple-sessions.md @@ -30,7 +30,7 @@ jbang MultipleSessions.java **Code:** ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -123,7 +123,7 @@ try { ## Managing session lifecycle with CompletableFuture ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import java.util.concurrent.CompletableFuture; import java.util.List; diff --git a/src/site/markdown/cookbook/persisting-sessions.md b/src/site/markdown/cookbook/persisting-sessions.md index e3fd11b13..e653b8a6a 100644 --- a/src/site/markdown/cookbook/persisting-sessions.md +++ b/src/site/markdown/cookbook/persisting-sessions.md @@ -30,7 +30,7 @@ jbang PersistingSessions.java **Code:** ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.json.MessageOptions; @@ -127,7 +127,7 @@ public class DeleteSession { ## Getting session history ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.events.UserMessageEvent; @@ -162,7 +162,7 @@ public class SessionHistory { ## Complete example with session management ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import java.util.Scanner; public class SessionManager { diff --git a/src/site/markdown/cookbook/pr-visualization.md b/src/site/markdown/cookbook/pr-visualization.md index dbd240a40..ad2939842 100644 --- a/src/site/markdown/cookbook/pr-visualization.md +++ b/src/site/markdown/cookbook/pr-visualization.md @@ -34,7 +34,7 @@ jbang PRVisualization.java github/copilot-sdk ## Full example: PRVisualization.java ```java -//DEPS com.github:copilot-sdk-java:0.2.1-java.1 +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.events.ToolExecutionStartEvent; From a86edb2891b5784c271f787c29ea368b12734f61 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Apr 2026 14:13:34 +0000 Subject: [PATCH 27/47] [maven-release-plugin] prepare release v0.2.2-java.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 43e36ec4d..8e3e7e1df 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java - 0.2.2-java.1-SNAPSHOT + 0.2.2-java.1 jar GitHub Copilot SDK :: Java @@ -33,7 +33,7 @@ scm:git:https://github.com/github/copilot-sdk-java.git scm:git:https://github.com/github/copilot-sdk-java.git https://github.com/github/copilot-sdk-java - HEAD + v0.2.2-java.1 From 0704bc5b44d7802aa1fe31a62f98c7e0dd50aef7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Apr 2026 14:13:38 +0000 Subject: [PATCH 28/47] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8e3e7e1df..b61c36166 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github copilot-sdk-java - 0.2.2-java.1 + 0.2.3-java.1-SNAPSHOT jar GitHub Copilot SDK :: Java @@ -33,7 +33,7 @@ scm:git:https://github.com/github/copilot-sdk-java.git scm:git:https://github.com/github/copilot-sdk-java.git https://github.com/github/copilot-sdk-java - v0.2.2-java.1 + HEAD From a1c3bdd42b5ffdbde680563a63f3b3afb65be391 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:18:01 -0400 Subject: [PATCH 29/47] Update GitHub release title to 'GitHub Copilot SDK for Java' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/publish-maven.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml index a7bb58f01..f2960a756 100644 --- a/.github/workflows/publish-maven.yml +++ b/.github/workflows/publish-maven.yml @@ -206,7 +206,7 @@ jobs: # Build the gh release command GH_ARGS=("${CURRENT_TAG}") - GH_ARGS+=("--title" "Copilot Java SDK ${VERSION}") + GH_ARGS+=("--title" "GitHub Copilot SDK for Java ${VERSION}") GH_ARGS+=("--notes" "${RELEASE_NOTES}") GH_ARGS+=("--generate-notes") From 9079e047a279a9a34aba3bc6fcb3a5b2db6de92f Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:25:54 -0400 Subject: [PATCH 30/47] Update disclaimer from pre-GA to public preview Replace 'Disclaimer' warnings with 'Public Preview' notices across README, site docs, release notes template, and HTML version selector. Drop 'Use at your own risk' language and tone down to reflect public preview status. Fix 'SDKS' typo to 'SDKs'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/templates/index.html | 4 ++-- .github/workflows/notes.template | 2 +- README.md | 2 +- instructions/copilot-sdk-java.instructions.md | 2 +- src/site/markdown/index.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/templates/index.html b/.github/templates/index.html index 9af01dded..85b96106f 100644 --- a/.github/templates/index.html +++ b/.github/templates/index.html @@ -65,8 +65,8 @@

Available Versions

-
- ⚠️ Disclaimer: This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKS are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. As such this implementation may introduce breaking changes, according to the policy declared by the reference implementations. Use at your own risk. +
+ ℹ️ Public Preview: This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases.
diff --git a/.github/workflows/notes.template b/.github/workflows/notes.template index 0fd7af642..0199452eb 100644 --- a/.github/workflows/notes.template +++ b/.github/workflows/notes.template @@ -1,6 +1,6 @@ # Installation -⚠️ **Disclaimer:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKS are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. As such this implementation may introduce breaking changes, according to the policy declared by the reference implementations. Use at your own risk. +ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases. ⚠️ **Artifact versioning plan:** Releases of this implementation track releases of the reference implementation. For each release of the reference implementation, there may follow a corresponding relase of this implementation with the same number as the reference implementation. Release identifiers of the reference implementation are in the form `vMaj.Min.Micro`. For example v0.1.32. The corresponding maven version for the release will be `Maj.Min.Micro-java.N`, where `Maj`, `Min` and `Micro` are the corresponding numbers for the reference impementation release, and `N` is a monotonically increasing sequence number starting with 0 for each release. See the corrseponding architectural decision record for more information in the `docs/adr` directory of the source code. diff --git a/README.md b/README.md index 539a33895..699fa3f9e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ## Background -> ⚠️ **Disclaimer:** This SDK tracks the pre-GA [GitHub Copilot SDKs](https://github.com/github/copilot-sdk) for [.NET](https://github.com/github/copilot-sdk/tree/main/dotnet) and [nodejs](https://github.com/github/copilot-sdk/tree/main/nodejs). This SDK may change in breaking ways. Use at your own risk. +> ℹ️ **Public Preview:** This SDK tracks the [GitHub Copilot SDKs](https://github.com/github/copilot-sdk) for [.NET](https://github.com/github/copilot-sdk/tree/main/dotnet) and [nodejs](https://github.com/github/copilot-sdk/tree/main/nodejs). While in public preview, minor breaking changes may still occur between releases. Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build AI-powered applications and agentic workflows. diff --git a/instructions/copilot-sdk-java.instructions.md b/instructions/copilot-sdk-java.instructions.md index bf18a3c5a..7881322fd 100644 --- a/instructions/copilot-sdk-java.instructions.md +++ b/instructions/copilot-sdk-java.instructions.md @@ -6,7 +6,7 @@ name: 'GitHub Copilot SDK Java Instructions' ## Core Principles -- The SDK is in technical preview and may have breaking changes +- The SDK is in public preview and may have breaking changes - Requires Java 17 or later - Requires GitHub Copilot CLI installed and in PATH - Uses `CompletableFuture` for all async operations diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index b599484d9..b2a85c567 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -1,6 +1,6 @@ # GitHub Copilot SDK for Java -> ⚠️ **Disclaimer:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKS are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. As such this implementation may introduce breaking changes, according to the policy declared by the reference implementations. Use at your own risk. +> ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases. Welcome to the documentation for the **GitHub Copilot SDK for Java** — a Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build AI-powered applications and agentic workflows. From 89be8e74452f4ee6e7fcb66b96196c0efdcca54e Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:32:11 -0400 Subject: [PATCH 31/47] Fix stale snapshot version in README and automate updates Update the snapshot version in README.md from 0.2.1-java.0-SNAPSHOT to 0.2.3-java.1-SNAPSHOT to match the current pom.xml. Add a sed command to the publish-maven workflow to update the snapshot version during releases, preventing it from going stale again. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/publish-maven.yml | 4 ++++ README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml index f2960a756..e258a54aa 100644 --- a/.github/workflows/publish-maven.yml +++ b/.github/workflows/publish-maven.yml @@ -121,6 +121,10 @@ jobs: sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|${VERSION}|g" README.md sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|copilot-sdk-java:${VERSION}|g" README.md + # Update snapshot version in README.md + DEV_VERSION="${{ steps.versions.outputs.dev_version }}" + sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}-SNAPSHOT|${DEV_VERSION}|g" README.md + # Update version in jbang-example.java sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|copilot-sdk-java:${VERSION}|g" jbang-example.java diff --git a/README.md b/README.md index 539a33895..af621d9bb 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Snapshot builds of the next development version are published to Maven Central S com.github copilot-sdk-java - 0.2.1-java.0-SNAPSHOT + 0.2.3-java.1-SNAPSHOT ``` From ef08ee6e07c022b4fdb83ff55f9294d6a0dc4cf4 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:35:55 -0400 Subject: [PATCH 32/47] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 699fa3f9e..0009483f2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ## Background -> ℹ️ **Public Preview:** This SDK tracks the [GitHub Copilot SDKs](https://github.com/github/copilot-sdk) for [.NET](https://github.com/github/copilot-sdk/tree/main/dotnet) and [nodejs](https://github.com/github/copilot-sdk/tree/main/nodejs). While in public preview, minor breaking changes may still occur between releases. +> ℹ️ **Public Preview:** This SDK tracks the [GitHub Copilot SDKs](https://github.com/github/copilot-sdk) for [.NET](https://github.com/github/copilot-sdk/tree/main/dotnet) and [Node.js](https://github.com/github/copilot-sdk/tree/main/nodejs). While in public preview, minor breaking changes may still occur between releases. Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build AI-powered applications and agentic workflows. From 8069433e8bb07bb209793671196abd7a812e039b Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:36:09 -0400 Subject: [PATCH 33/47] Update .github/templates/index.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/templates/index.html b/.github/templates/index.html index 85b96106f..d273ad074 100644 --- a/.github/templates/index.html +++ b/.github/templates/index.html @@ -66,7 +66,7 @@

Available Versions

- ℹ️ Public Preview: This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases. + ℹ️ Public Preview: This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and Node.js SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases.
From f0a82b906f55e2122c20411e457f55618a7f331a Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:36:17 -0400 Subject: [PATCH 34/47] Update .github/workflows/notes.template Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/notes.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/notes.template b/.github/workflows/notes.template index 0199452eb..9c148cdf1 100644 --- a/.github/workflows/notes.template +++ b/.github/workflows/notes.template @@ -1,6 +1,6 @@ # Installation -ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases. +ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and Node.js SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases. ⚠️ **Artifact versioning plan:** Releases of this implementation track releases of the reference implementation. For each release of the reference implementation, there may follow a corresponding relase of this implementation with the same number as the reference implementation. Release identifiers of the reference implementation are in the form `vMaj.Min.Micro`. For example v0.1.32. The corresponding maven version for the release will be `Maj.Min.Micro-java.N`, where `Maj`, `Min` and `Micro` are the corresponding numbers for the reference impementation release, and `N` is a monotonically increasing sequence number starting with 0 for each release. See the corrseponding architectural decision record for more information in the `docs/adr` directory of the source code. From 2dc74128c3d2179f02d7478a9adaf40ee1ee4cfd Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:36:25 -0400 Subject: [PATCH 35/47] Update src/site/markdown/index.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/site/markdown/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index b2a85c567..60b96ce9d 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -1,6 +1,6 @@ # GitHub Copilot SDK for Java -> ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and nodejs SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases. +> ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and Node.js SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. While in public preview, minor breaking changes may still occur between releases. Welcome to the documentation for the **GitHub Copilot SDK for Java** — a Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build AI-powered applications and agentic workflows. From 86593d0e501621bb928151b940769b1449ee0f46 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:49:40 -0400 Subject: [PATCH 36/47] Fix jbang-example.java to use actual version instead of Maven placeholder Replace ${project.version} with a real version number (0.2.2-java.1) in jbang-example.java since this file is not Maven-filtered. Also add a fallback sed pattern in the release workflow to handle the placeholder during the transition. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/publish-maven.yml | 1 + jbang-example.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml index f2960a756..154ab908a 100644 --- a/.github/workflows/publish-maven.yml +++ b/.github/workflows/publish-maven.yml @@ -123,6 +123,7 @@ jobs: # Update version in jbang-example.java sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|copilot-sdk-java:${VERSION}|g" jbang-example.java + sed -i "s|copilot-sdk-java:\${project\.version}|copilot-sdk-java:${VERSION}|g" jbang-example.java # Update version in cookbook files (hardcoded for direct GitHub browsing and JBang usage) find src/site/markdown/cookbook -name "*.md" -type f -exec \ diff --git a/jbang-example.java b/jbang-example.java index 3d02653c1..dd1f80762 100644 --- a/jbang-example.java +++ b/jbang-example.java @@ -1,5 +1,5 @@ ! -//DEPS com.github:copilot-sdk-java:${project.version} +//DEPS com.github:copilot-sdk-java:0.2.2-java.1 import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.events.SessionUsageInfoEvent; From 0d1b0dc0c1175821df9d9fad827ae8ce9d250e07 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 11:04:06 -0400 Subject: [PATCH 37/47] Update .github/workflows/publish-maven.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/publish-maven.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml index 154ab908a..c0df3ed0d 100644 --- a/.github/workflows/publish-maven.yml +++ b/.github/workflows/publish-maven.yml @@ -123,7 +123,7 @@ jobs: # Update version in jbang-example.java sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|copilot-sdk-java:${VERSION}|g" jbang-example.java - sed -i "s|copilot-sdk-java:\${project\.version}|copilot-sdk-java:${VERSION}|g" jbang-example.java + sed -i 's|copilot-sdk-java:${project\.version}|copilot-sdk-java:'"${VERSION}"'|g' jbang-example.java # Update version in cookbook files (hardcoded for direct GitHub browsing and JBang usage) find src/site/markdown/cookbook -name "*.md" -type f -exec \ From 62ff965cca742ca9384560137958095d70a68d49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 15:01:32 +0000 Subject: [PATCH 38/47] Initial plan From 20acdbfc0a7e43a8b5bdf06cd83cac5f2e50e236 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 15:08:23 +0000 Subject: [PATCH 39/47] Fix cross-platform process creation in tests: use cmd /c more on Windows Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/a5638a0d-f412-4810-b724-45bc5f10438d Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .../com/github/copilot/sdk/CliServerManagerTest.java | 7 ++++++- .../com/github/copilot/sdk/JsonRpcClientTest.java | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java b/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java index 32257b0a5..e59d03ae8 100644 --- a/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java +++ b/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java @@ -70,13 +70,18 @@ void connectToServerTcpMode() throws Exception { } } + private static Process startBlockingProcess() throws IOException { + boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); + return (isWindows ? new ProcessBuilder("cmd", "/c", "more") : new ProcessBuilder("cat")).start(); + } + @Test void connectToServerStdioMode() throws Exception { var options = new CopilotClientOptions(); var manager = new CliServerManager(options); // Create a dummy process for stdio mode - Process process = new ProcessBuilder("cat").start(); + Process process = startBlockingProcess(); try { JsonRpcClient client = manager.connectToServer(process, null, null); assertNotNull(client); diff --git a/src/test/java/com/github/copilot/sdk/JsonRpcClientTest.java b/src/test/java/com/github/copilot/sdk/JsonRpcClientTest.java index 8b5c1858c..4fb43f4b6 100644 --- a/src/test/java/com/github/copilot/sdk/JsonRpcClientTest.java +++ b/src/test/java/com/github/copilot/sdk/JsonRpcClientTest.java @@ -133,9 +133,14 @@ void testIsConnectedWithSocketClosed() throws Exception { pair.serverSocket.close(); } + private static Process startBlockingProcess() throws IOException { + boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); + return (isWindows ? new ProcessBuilder("cmd", "/c", "more") : new ProcessBuilder("cat")).start(); + } + @Test void testIsConnectedWithProcess() throws Exception { - Process proc = new ProcessBuilder("cat").start(); + Process proc = startBlockingProcess(); try (var client = JsonRpcClient.fromProcess(proc)) { assertTrue(client.isConnected()); } @@ -143,7 +148,7 @@ void testIsConnectedWithProcess() throws Exception { @Test void testIsConnectedWithProcessDead() throws Exception { - Process proc = new ProcessBuilder("cat").start(); + Process proc = startBlockingProcess(); var client = JsonRpcClient.fromProcess(proc); proc.destroy(); proc.waitFor(5, TimeUnit.SECONDS); @@ -155,7 +160,7 @@ void testIsConnectedWithProcessDead() throws Exception { @Test void testGetProcessReturnsProcess() throws Exception { - Process proc = new ProcessBuilder("cat").start(); + Process proc = startBlockingProcess(); try (var client = JsonRpcClient.fromProcess(proc)) { assertSame(proc, client.getProcess()); } From fc71b95636ac94b4816b6399233af799d657c234 Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Sat, 11 Apr 2026 10:40:47 -0400 Subject: [PATCH 40/47] Fix Windows test compatibility for ProcessBuilder usage On Windows, Java's ProcessBuilder cannot directly run shell wrappers like `npx` (installed as npx.cmd) or Unix commands like `cat`. Tests that used these commands failed with "Cannot run program" errors. Additionally, Unix-style paths like "/nonexistent/copilot" are not absolute on Windows, causing assertThrows(IOException) tests to pass unexpectedly when CliServerManager wrapped them with "cmd /c". Changes: - CapiProxy: use "cmd /c npx" on Windows to launch the test harness - CliServerManagerTest: replace "cat" with cross-platform dummy process; use a platform-appropriate nonexistent absolute path so IOException is thrown on all platforms - JsonRpcClientTest: replace "cat" with cross-platform dummy process All changes use runtime os.name detection and preserve existing behavior on Linux and macOS. Full test suite passes on all platforms (556 tests, 0 failures, 0 errors). --- .../com/github/copilot/sdk/CapiProxy.java | 6 ++++- .../copilot/sdk/CliServerManagerTest.java | 26 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/github/copilot/sdk/CapiProxy.java b/src/test/java/com/github/copilot/sdk/CapiProxy.java index 1a7df2d6c..bcd064d94 100644 --- a/src/test/java/com/github/copilot/sdk/CapiProxy.java +++ b/src/test/java/com/github/copilot/sdk/CapiProxy.java @@ -89,7 +89,11 @@ public String start() throws IOException, InterruptedException { } // Start the harness server using npx tsx - var pb = new ProcessBuilder("npx", "tsx", "server.ts"); + // On Windows, npx is installed as npx.cmd which requires cmd /c to launch + boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win"); + var pb = isWindows + ? new ProcessBuilder("cmd", "/c", "npx", "tsx", "server.ts") + : new ProcessBuilder("npx", "tsx", "server.ts"); pb.directory(harnessDir.toFile()); pb.redirectErrorStream(false); diff --git a/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java b/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java index e59d03ae8..e556839cc 100644 --- a/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java +++ b/src/test/java/com/github/copilot/sdk/CliServerManagerTest.java @@ -131,6 +131,14 @@ void processInfoWithNullPort() { // resolveCliCommand is private, so we test indirectly through startCliServer // with specific cliPath values. + // On Windows, "/nonexistent/copilot" is not an absolute path (no drive letter), + // so resolveCliCommand wraps it with "cmd /c" and ProcessBuilder.start() + // succeeds + // (launching cmd.exe). Use a Windows-absolute path to ensure IOException. + private static final String NONEXISTENT_CLI = System.getProperty("os.name").toLowerCase().contains("win") + ? "C:\\nonexistent\\copilot" + : "/nonexistent/copilot"; + @Test void startCliServerWithJsFile() throws Exception { // Using a .js file path causes resolveCliCommand to prepend "node" @@ -152,8 +160,8 @@ void startCliServerWithJsFile() throws Exception { @Test void startCliServerWithCliArgs() throws Exception { // Test that cliArgs are included in the command - var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot") - .setCliArgs(new String[]{"--extra-flag"}).setUseStdio(true); + var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setCliArgs(new String[]{"--extra-flag"}) + .setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); @@ -163,7 +171,7 @@ void startCliServerWithCliArgs() throws Exception { @Test void startCliServerWithExplicitPort() throws Exception { // Test the explicit port branch (useStdio=false, port > 0) - var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setUseStdio(false).setPort(9999); + var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setUseStdio(false).setPort(9999); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); @@ -173,7 +181,7 @@ void startCliServerWithExplicitPort() throws Exception { @Test void startCliServerWithGitHubToken() throws Exception { // Test the github token branch - var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setGitHubToken("ghp_test123") + var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setGitHubToken("ghp_test123") .setUseStdio(true); var manager = new CliServerManager(options); @@ -184,7 +192,7 @@ void startCliServerWithGitHubToken() throws Exception { @Test void startCliServerWithUseLoggedInUserExplicit() throws Exception { // Test the explicit useLoggedInUser=false branch (adds --no-auto-login) - var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setUseLoggedInUser(false) + var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setUseLoggedInUser(false) .setUseStdio(true); var manager = new CliServerManager(options); @@ -195,7 +203,7 @@ void startCliServerWithUseLoggedInUserExplicit() throws Exception { @Test void startCliServerWithGitHubTokenAndNoExplicitUseLoggedInUser() throws Exception { // When gitHubToken is set and useLoggedInUser is null, defaults to false - var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setGitHubToken("ghp_test123") + var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setGitHubToken("ghp_test123") .setUseStdio(true); var manager = new CliServerManager(options); @@ -225,8 +233,7 @@ void startCliServerWithTelemetryAllOptions() throws Exception { // so even with a nonexistent CLI path, the telemetry code path is exercised var telemetry = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setFilePath("/tmp/telemetry.log") .setExporterType("otlp-http").setSourceName("test-app").setCaptureContent(true); - var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setTelemetry(telemetry) - .setUseStdio(true); + var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setTelemetry(telemetry).setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); @@ -237,8 +244,7 @@ void startCliServerWithTelemetryAllOptions() throws Exception { void startCliServerWithTelemetryCaptureContentFalse() throws Exception { // Test the false branch of getCaptureContent() var telemetry = new TelemetryConfig().setCaptureContent(false); - var options = new CopilotClientOptions().setCliPath("/nonexistent/copilot").setTelemetry(telemetry) - .setUseStdio(true); + var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setTelemetry(telemetry).setUseStdio(true); var manager = new CliServerManager(options); var ex = assertThrows(IOException.class, () -> manager.startCliServer()); From 29a3c2cd81d006491724e8f618e6e06b2f8a011e Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Tue, 24 Mar 2026 14:59:20 -0400 Subject: [PATCH 41/47] On branch edburns/dd-2853206-reference-impl-not-upstream We are tracking a reference implementation, not an upstream. In `git` an `upstream` is well defined. To avoid LLM confusion, let us use the proper term. new file: .claude/skills/agentic-merge-reference-impl/SKILL.md modified: .github/copilot-instructions.md new file: .github/prompts/agentic-merge-reference-impl.prompt.md new file: .github/prompts/coding-agent-merge-reference-impl-instructions.md new file: .github/scripts/reference-impl-sync/merge-reference-impl-diff.sh new file: .github/scripts/reference-impl-sync/merge-reference-impl-finish.sh new file: .github/scripts/reference-impl-sync/merge-reference-impl-start.sh new file: .github/skills/agentic-merge-reference-impl/SKILL.md new file: .github/workflows/weekly-reference-impl-sync.lock.yml new file: .github/workflows/weekly-reference-impl-sync.md new file: .github/workflows/weekly-reference-impl-sync.yml modified: CONTRIBUTING.md modified: README.md modified: docs/WORKFLOWS.md Signed-off-by: Ed Burns --- .../agentic-merge-reference-impl/SKILL.md | 7 + .github/copilot-instructions.md | 8 +- .../agentic-merge-reference-impl.prompt.md | 439 ++++++ ...agent-merge-reference-impl-instructions.md | 19 + .../merge-reference-impl-diff.sh | 86 ++ .../merge-reference-impl-finish.sh | 63 + .../merge-reference-impl-start.sh | 88 ++ .../agentic-merge-reference-impl/SKILL.md | 7 + .../weekly-reference-impl-sync.lock.yml | 1239 +++++++++++++++++ .../workflows/weekly-reference-impl-sync.md | 151 ++ .../workflows/weekly-reference-impl-sync.yml | 181 +++ CONTRIBUTING.md | 2 +- README.md | 10 +- docs/WORKFLOWS.md | 22 +- 14 files changed, 2301 insertions(+), 21 deletions(-) create mode 100644 .claude/skills/agentic-merge-reference-impl/SKILL.md create mode 100644 .github/prompts/agentic-merge-reference-impl.prompt.md create mode 100644 .github/prompts/coding-agent-merge-reference-impl-instructions.md create mode 100755 .github/scripts/reference-impl-sync/merge-reference-impl-diff.sh create mode 100755 .github/scripts/reference-impl-sync/merge-reference-impl-finish.sh create mode 100755 .github/scripts/reference-impl-sync/merge-reference-impl-start.sh create mode 100644 .github/skills/agentic-merge-reference-impl/SKILL.md create mode 100644 .github/workflows/weekly-reference-impl-sync.lock.yml create mode 100644 .github/workflows/weekly-reference-impl-sync.md create mode 100644 .github/workflows/weekly-reference-impl-sync.yml diff --git a/.claude/skills/agentic-merge-reference-impl/SKILL.md b/.claude/skills/agentic-merge-reference-impl/SKILL.md new file mode 100644 index 000000000..421ea147b --- /dev/null +++ b/.claude/skills/agentic-merge-reference-impl/SKILL.md @@ -0,0 +1,7 @@ +--- +name: agentic-merge-reference-impl +description: Merge reference implementation changes from the official Copilot SDK into this Java SDK. +license: MIT +--- + +Follow instructions in the [agentic-merge-reference-impl prompt](../../../.github/prompts/agentic-merge-reference-impl.prompt.md) to merge reference implementation changes from the official Copilot SDK into this Java SDK. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 284e2b800..edd40797b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -89,9 +89,9 @@ Tests use the official copilot-sdk test harness from `https://github.com/github/ ## Key Conventions -### Upstream Merging +### Reference Implementation Merging -This SDK tracks the official .NET implementation at `github/copilot-sdk`. The `.lastmerge` file contains the last merged upstream commit hash. Use the `agentic-merge-upstream` skill (see `.github/prompts/agentic-merge-upstream.prompt.md`) to port changes. +This SDK tracks the official .NET implementation at `github/copilot-sdk`. The `.lastmerge` file contains the last merged reference implementation commit hash. Use the `agentic-merge-reference-impl` skill (see `.github/prompts/agentic-merge-reference-impl.prompt.md`) to port changes. When porting from .NET: - Adapt to Java idioms, don't copy C# patterns directly @@ -278,7 +278,7 @@ The repository has a pre-commit hook (`.githooks/pre-commit`) that is **automati - Include tests for new functionality - Update documentation if adding/changing public APIs - Reference related issues using `#issue-number` -- For upstream merges, follow the `agentic-merge-upstream` skill workflow +- For reference implementation merges, follow the `agentic-merge-reference-impl` skill workflow ## Development Workflow @@ -301,7 +301,7 @@ The release process is automated via the `publish-maven.yml` GitHub Actions work - Updates version comparison links at the bottom of CHANGELOG.md - Injects the upstream SDK commit hash (from `.lastmerge`) as a `> **Upstream sync:**` blockquote in both the new `[Unreleased]` section and the released version section -2. **Upstream Sync Tracking**: Each release records which commit from the official `github/copilot-sdk` it is synced to: +2. **Reference Implementation Sync Tracking**: Each release records which commit from the official `github/copilot-sdk` it is synced to: - The `.lastmerge` file is read during the release workflow - The commit hash is injected into `CHANGELOG.md` under the release heading - Format: `> **Upstream sync:** [\`github/copilot-sdk@SHORT_HASH\`](link-to-commit)` diff --git a/.github/prompts/agentic-merge-reference-impl.prompt.md b/.github/prompts/agentic-merge-reference-impl.prompt.md new file mode 100644 index 000000000..fd7c3fa13 --- /dev/null +++ b/.github/prompts/agentic-merge-reference-impl.prompt.md @@ -0,0 +1,439 @@ +# Merge Reference Implementation SDK Changes + +You are an expert Java developer tasked with porting changes from the reference implementation of the Copilot SDK (primarily the .NET implementation) to this Java SDK. + +## ⚠️ IMPORTANT: Java SDK Design Takes Priority + +**The current design and architecture of the Java SDK is the priority.** When porting changes from the reference implementation: + +1. **Adapt, don't copy** - Translate reference implementation features to fit the Java SDK's existing patterns, naming conventions, and architecture +2. **Preserve Java idioms** - The Java SDK should feel natural to Java developers, not like a C# port +3. **Maintain consistency** - New code must match the existing codebase style and structure +4. **Evaluate before porting** - Not every reference implementation change needs to be ported; some may not be applicable or may conflict with Java SDK design decisions + +Before making any changes, **read and understand the existing Java SDK implementation** to ensure new code integrates seamlessly. + +## Utility Scripts + +The `.github/scripts/` directory contains helper scripts that automate the repeatable parts of this workflow. **Use these scripts instead of running the commands manually.** + +| Script | Purpose | +|---|---| +| `.github/scripts/reference-impl-sync/merge-reference-impl-start.sh` | Creates branch, updates CLI, clones reference implementation, reads `.lastmerge`, prints commit summary | +| `.github/scripts/reference-impl-sync/merge-reference-impl-diff.sh` | Detailed diff analysis grouped by area (`.NET src`, tests, snapshots, docs, etc.) | +| `.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh` | Runs format + test + build, updates `.lastmerge`, commits, pushes branch | +| `.github/scripts/build/format-and-test.sh` | Standalone `spotless:apply` + `mvn clean verify` (useful during porting too) | + +All scripts write/read a `.merge-env` file (git-ignored) to share state (branch name, reference-impl dir, last-merge commit). + +## Workflow Overview + +1. Run `./.github/scripts/reference-impl-sync/merge-reference-impl-start.sh` (creates branch, clones reference implementation, shows summary) +2. Run `./.github/scripts/reference-impl-sync/merge-reference-impl-diff.sh` (analyze changes) +3. Update README with minimum CLI version requirement +4. Identify reference implementation changes to port +5. Apply changes to Java SDK (commit as you go) +6. Port/adjust tests from reference implementation 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 reference implementation change**) +10. Run `./.github/scripts/reference-impl-sync/merge-reference-impl-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 + +--- + +## Step 1: Initialize Reference Implementation Sync + +Run the start script to create a branch, update the CLI, clone the reference implementation repo, and see a summary of new commits: + +```bash +./.github/scripts/reference-impl-sync/merge-reference-impl-start.sh +``` + +This writes a `.merge-env` file used by the other scripts. It outputs: +- The branch name created +- The Copilot CLI version +- The reference-impl dir path +- A short log of reference implementation commits since `.lastmerge` + +## Step 2: Analyze Upstream Changes + +Run the diff script for a detailed breakdown by area: + +```bash +./.github/scripts/reference-impl-sync/merge-reference-impl-diff.sh # stat only +./.github/scripts/reference-impl-sync/merge-reference-impl-diff.sh --full # full diffs +``` + +The diff script groups changes into: .NET source, .NET tests, test snapshots, documentation, protocol/config, Go/Node.js/Python SDKs, and other files. + +## Step 3: Update README with CLI Version + +After the start script runs, check the CLI version it printed (also saved in `.merge-env` as `CLI_VERSION`). Update the Requirements section in `README.md` and `src/site/markdown/index.md` to specify the minimum CLI version requirement. + +Commit this change before proceeding: + +```bash +git add README.md src/site/markdown/index.md +git commit -m "Update Copilot CLI minimum version requirement" +``` + +## Step 4: Identify Changes to Port + +Using the output from `merge-reference-impl-diff.sh`, focus on: +- `dotnet/src/` - Primary reference implementation +- `dotnet/test/` - Test cases to port +- `docs/` - Documentation updates +- `sdk-protocol-version.json` - Protocol version changes + +For each change in the reference implementation diff, determine: + +1. **New Features**: New methods, classes, or capabilities added to the SDK +2. **Bug Fixes**: Corrections to existing functionality +3. **API Changes**: Changes to public interfaces or method signatures +4. **Protocol Updates**: Changes to the JSON-RPC protocol or message types +5. **Test Updates**: New or modified test cases + +### Key Files to Compare + +| Upstream (.NET) | Java SDK Equivalent | +|------------------------------------|--------------------------------------------------------| +| `dotnet/src/Client.cs` | `src/main/java/com/github/copilot/sdk/CopilotClient.java` | +| `dotnet/src/Session.cs` | `src/main/java/com/github/copilot/sdk/CopilotSession.java` | +| `dotnet/src/Types.cs` | `src/main/java/com/github/copilot/sdk/types/*.java` | +| `dotnet/src/Generated/*.cs` | `src/main/java/com/github/copilot/sdk/types/*.java` | +| `dotnet/test/*.cs` | `src/test/java/com/github/copilot/sdk/*Test.java` | +| `docs/getting-started.md` | `README.md` and `src/site/markdown/*.md` | +| `docs/*.md` (new files) | `src/site/markdown/*.md` + update `src/site/site.xml` | +| `sdk-protocol-version.json` | (embedded in Java code or resource file) | + +> **⚠️ Important:** When adding new documentation pages, always update `src/site/site.xml` to include them in the navigation menu. + +## Step 5: Apply Changes to Java SDK + +When porting changes: + +### ⚠️ Priority: Preserve Java SDK Design + +Before modifying any code: + +1. **Read the existing Java implementation first** - Understand current patterns, class structure, and naming +2. **Identify the Java equivalent approach** - Don't replicate C# patterns; find the idiomatic Java way +3. **Check for existing abstractions** - The Java SDK may already have mechanisms that differ from .NET +4. **Preserve backward compatibility** - Existing API signatures should not break unless absolutely necessary +5. **When in doubt, match existing code** - Follow what's already in the Java SDK, not the upstream + +### Commit Changes Incrementally + +**Important:** Commit your changes as you work, grouping related changes together: + +```bash +# After porting a feature or fix, commit with a descriptive message +git add +git commit -m "Port from the reference implementation" + +# Example commits: +# git commit -m "Port new authentication flow from the reference implementation" +# git commit -m "Add new message types from the reference implementation protocol update" +# git commit -m "Port bug fix for session handling from the reference implementation" +``` + +This creates a clear history of changes that can be reviewed in the Pull Request. + +### General Guidelines + +- **Naming Conventions**: Convert C# PascalCase to Java camelCase for methods/variables +- **Async Patterns**: C# `async/await` → Java `CompletableFuture` or synchronous equivalents +- **Nullable Types**: C# `?` nullable → Java `@Nullable` annotations or `Optional` +- **Properties**: C# properties → Java getters/setters or records +- **Records**: C# records → Java records (Java 17+) +- **Events**: C# events → Java callbacks or listeners + +### Type Mappings + +| C# Type | Java Equivalent | +|------------------------|------------------------------| +| `string` | `String` | +| `int` | `int` / `Integer` | +| `bool` | `boolean` / `Boolean` | +| `Task` | `CompletableFuture` | +| `CancellationToken` | (custom implementation) | +| `IAsyncEnumerable` | `Stream` or `Iterator` | +| `JsonElement` | `JsonNode` (Jackson) | +| `Dictionary` | `Map` | +| `List` | `List` | + +### Code Style + +Follow the existing Java SDK patterns: +- Use Jackson for JSON serialization (`ObjectMapper`) +- Use Java records for DTOs where appropriate +- Follow the existing package structure under `com.github.copilot.sdk` +- Maintain backward compatibility when possible +- **Match the style of surrounding code** - Consistency with existing code is more important than reference implementation patterns +- **Prefer existing abstractions** - If the Java SDK already solves a problem differently than .NET, keep the Java approach + +## Step 6: Port Tests + +After porting implementation changes, **always check for new or updated tests** in the reference implementation repository: + +### Check for New Tests + +```bash +cd "$TEMP_DIR/copilot-sdk" +git diff "$LAST_MERGE_COMMIT"..origin/main --stat -- dotnet/test/ +git diff "$LAST_MERGE_COMMIT"..origin/main --stat -- test/snapshots/ +``` + +### Port Test Cases + +For each new or modified test file in `dotnet/test/`: + +1. **Create corresponding Java test class** in `src/test/java/com/github/copilot/sdk/` +2. **Follow existing test patterns** - Look at existing tests like `PermissionsTest.java` for structure +3. **Use the E2ETestContext** infrastructure for tests that need the test harness +4. **Match snapshot directory names** - Test snapshots in `test/snapshots/` must match the directory name used in `ctx.configureForTest()` + +### Test File Mapping + +| Upstream Test (.NET) | Java SDK Test | +|-----------------------------|--------------------------------------------------------| +| `dotnet/test/AskUserTests.cs` | `src/test/java/com/github/copilot/sdk/AskUserTest.java` | +| `dotnet/test/HooksTests.cs` | `src/test/java/com/github/copilot/sdk/HooksTest.java` | +| `dotnet/test/ClientTests.cs` | `src/test/java/com/github/copilot/sdk/CopilotClientTest.java` | +| `dotnet/test/*Tests.cs` | `src/test/java/com/github/copilot/sdk/*Test.java` | + +### Test Snapshot Compatibility + +New test snapshots are stored in `test/snapshots/` in the reference implementation repository. These snapshots are automatically cloned during the Maven build process. + +If tests fail with errors like `TypeError: Cannot read properties of undefined`, the test harness may not yet support the new RPC methods. In this case: + +1. **Mark tests as `@Disabled`** with a clear reason (e.g., `@Disabled("Requires test harness update with X support - see upstream PR #NNN")`) +2. **Document the dependency** in the test class Javadoc +3. **Enable tests later** once the harness is updated + +### Unit Tests vs E2E Tests + +- **Unit tests** (like auth option validation) can run without the test harness +- **E2E tests** require the test harness with matching snapshots + +Commit tests separately or together with their corresponding implementation changes. + +## Step 7: Format and Run Tests + +After applying changes, use the convenience script: + +```bash +./.github/scripts/build/format-and-test.sh # format + full verify +./.github/scripts/build/format-and-test.sh --debug # with debug logging +``` + +Or for quicker iteration during porting: + +```bash +./.github/scripts/build/format-and-test.sh --format-only # just spotless +./.github/scripts/build/format-and-test.sh --test-only # skip formatting +``` + +### If Tests Fail + +1. Read the test output carefully +2. Identify the root cause (compilation error, runtime error, assertion failure) +3. Fix the issue in the Java code +4. Re-run tests +5. Repeat until all tests pass + +### Common Issues + +- **Missing imports**: Add required import statements +- **Type mismatches**: Ensure proper type conversions +- **Null handling**: Add null checks where C# had nullable types +- **JSON serialization**: Verify Jackson annotations are correct + +## Step 8: Build the Package + +Once tests pass, build the complete package: + +```bash +mvn clean package -DskipTests +``` + +Verify: +- No compilation errors +- No warnings (if possible) +- JAR file is generated in `target/` + +## Step 9: Update Documentation + +**Documentation is critical for new features.** Every new feature ported from the reference implementation 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 + +For each new feature or significant change: + +1. **README.md**: Update the main README if there are user-facing changes +2. **src/site/markdown/index.md**: Update if requirements or quick start examples change +3. **src/site/markdown/documentation.md**: Add sections for new basic usage patterns +4. **src/site/markdown/advanced.md**: Add sections for new advanced features (tools, handlers, configurations) +5. **src/site/markdown/mcp.md**: Update if MCP-related changes are made +6. **Javadoc**: Add/update Javadoc comments for all new/changed public APIs +7. **src/site/site.xml**: Update if new documentation pages were added + +### Documentation Requirements for New Features + +When adding a new feature, ensure the documentation includes: + +- **What it does**: Clear explanation of the feature's purpose +- **How to use it**: Code example showing typical usage +- **API reference**: Link to relevant Javadoc +- **Configuration options**: All available settings/properties + +### Example: Documenting a New Handler + +If a new handler (like `UserInputHandler`, `PermissionHandler`) is added, create a section in `advanced.md`: + +```markdown +## Feature Name + +Brief description of what the feature does. + +\`\`\`java +var session = client.createSession( + new SessionConfig() + .setOnFeatureRequest((request, invocation) -> { + // Handle the request + return CompletableFuture.completedFuture(result); + }) +).get(); +\`\`\` + +Explain the request/response objects and their properties. + +See [FeatureHandler](apidocs/com/github/copilot/sdk/json/FeatureHandler.html) Javadoc for more details. +``` + +### Verify Documentation Consistency + +Ensure consistency across all documentation files: + +- Requirements section should match in `README.md` and `src/site/markdown/index.md` +- Code examples should use the same patterns and be tested +- Links to Javadoc should use correct paths (`apidocs/...`) + +## Step 10: Finish, Push, and Finalize Pull Request + +Run the finish script which updates `.lastmerge`, runs a final build, and pushes the branch: + +```bash +./.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh # full format + test + push +./.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh --skip-tests # if tests already passed +``` + +### PR Handling: Coding Agent vs. Manual Workflow + +**If running as a Copilot coding agent** (triggered via GitHub issue assignment by the weekly sync workflow), a pull request has **already been created automatically** for you. Do NOT create a new one. Just push your commits to the current branch — the existing PR will be updated. Add the `reference-impl-sync` label to the existing PR by running this command in a terminal: + +```bash +gh pr edit --add-label "reference-impl-sync" +``` + +> **No-changes scenario (coding agent only):** If after analyzing the reference implementation diff there are no relevant changes to port to the Java SDK, push an empty commit with a message explaining why no changes were needed, so the PR reflects the analysis outcome. The repository maintainer will close the PR and issue manually. + +**If running manually** (e.g., from VS Code via the reusable prompt), create the Pull Request using `gh` CLI or the GitHub MCP tool. Then add the label: + +```bash +gh pr create --base main --title "Merge reference implementation SDK changes (YYYY-MM-DD)" --body-file /dev/stdin <<< "$PR_BODY" +gh pr edit --add-label "reference-impl-sync" +``` + +The PR body should include: +1. **Title**: `Merge reference implementation SDK changes (YYYY-MM-DD)` +2. **Body** with: + - Summary of reference implementation commits analyzed (with count and commit range) + - Table of changes ported (commit hash + description) + - List of changes intentionally not ported (with reasons) + - Verification status (test count, build status) + +### PR Body Template + +```markdown +## Reference Implementation Merge + +Ports changes from the official Copilot SDK ([github/copilot-sdk](https://github.com/github/copilot-sdk)) since last merge (``→``). + +### Reference implementation commits analyzed (N commits) + +- Brief description of each reference implementation change and whether it was ported or not + +### Changes ported + +| Commit | Description | +|---|---| +| `` | Description of change | + +### Not ported (intentionally) + +- **Feature name** — Reason why it wasn't ported + +### Verification + +- All **N tests pass** (`mvn clean test`) +- Package builds successfully (`mvn clean package -DskipTests`) +- Code formatted with Spotless +``` + +## Step 11: Final Review + +Before finishing: + +1. Run `git log --oneline main..$BRANCH_NAME` to review all commits +2. Run `git diff main..$BRANCH_NAME --stat` to see a summary of all changes +3. Ensure no unintended changes were made +4. Verify code follows project conventions +5. Confirm the branch was pushed to remote +6. Confirm the Pull Request is ready (created or updated) and provide the PR URL to the user + +--- + +## Checklist + +- [ ] New branch created from `main` +- [ ] Copilot CLI updated to latest version +- [ ] README.md updated with minimum CLI version requirement +- [ ] Upstream repository cloned +- [ ] Diff analyzed between `.lastmerge` commit and HEAD +- [ ] New features/fixes identified +- [ ] Changes ported to Java SDK following conventions +- [ ] **New/updated tests ported from the reference implementation** (check `dotnet/test/` and `test/snapshots/`) +- [ ] Tests marked `@Disabled` if harness doesn't support new features yet +- [ ] Changes committed incrementally with descriptive messages +- [ ] `mvn test` passes +- [ ] `mvn package` builds successfully +- [ ] **Documentation updated for new features:** + - [ ] `README.md` updated if user-facing changes + - [ ] `src/site/markdown/index.md` updated if requirements changed + - [ ] `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 reference implementation 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 +- [ ] **Pull Request finalized** (coding agent: push to existing PR; manual: create via `mcp_github_create_pull_request`) +- [ ] **`reference-impl-sync` label added** to the PR via `mcp_github_add_issue_labels` +- [ ] PR URL provided to user + +--- + +## Notes + +- The reference implementation SDK is at: `https://github.com/github/copilot-sdk.git` +- Primary reference implementation is in `dotnet/` folder +- This Java SDK targets Java 17+ +- Uses Jackson for JSON processing +- Uses JUnit 5 for testing +- **Java SDK design decisions take precedence over reference implementation patterns** +- **Adapt reference implementation changes to fit Java idioms, not the other way around** diff --git a/.github/prompts/coding-agent-merge-reference-impl-instructions.md b/.github/prompts/coding-agent-merge-reference-impl-instructions.md new file mode 100644 index 000000000..cee6ef8a7 --- /dev/null +++ b/.github/prompts/coding-agent-merge-reference-impl-instructions.md @@ -0,0 +1,19 @@ + + + +Follow the agentic-merge-reference-impl prompt at .github/prompts/agentic-merge-reference-impl.prompt.md +to port reference implementation changes to the Java SDK. + +Use the utility scripts in .github/scripts/ subfolders for initialization, diffing, formatting, and testing. +Commit changes incrementally. Update .lastmerge when done. + +IMPORTANT: A pull request has already been created automatically for you — do NOT create a new +one. Push your commits to the current branch, and the existing PR will be updated. + +Add the 'reference-impl-sync' label to the existing PR by running this command in a terminal: + + gh pr edit --add-label "reference-impl-sync" + +If after analyzing the reference implementation diff there are no relevant changes to port to the Java SDK, +push an empty commit with a message explaining why no changes were needed, so the PR reflects +the analysis outcome. The repository maintainer will close the PR and issue manually. diff --git a/.github/scripts/reference-impl-sync/merge-reference-impl-diff.sh b/.github/scripts/reference-impl-sync/merge-reference-impl-diff.sh new file mode 100755 index 000000000..8ff870103 --- /dev/null +++ b/.github/scripts/reference-impl-sync/merge-reference-impl-diff.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# ────────────────────────────────────────────────────────────── +# merge-reference-impl-diff.sh +# +# Generates a detailed diff analysis of reference implementation changes since +# the last merge, grouped by area of interest: +# • .NET source (primary reference) +# • .NET tests +# • Test snapshots +# • Documentation +# • Protocol / config files +# +# Usage: ./.github/scripts/reference-impl-sync/merge-reference-impl-diff.sh [--full] +# --full Show actual diffs, not just stats +# +# Requires: .merge-env written by merge-reference-impl-start.sh +# ────────────────────────────────────────────────────────────── +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +ENV_FILE="$ROOT_DIR/.merge-env" + +if [[ ! -f "$ENV_FILE" ]]; then + echo "❌ $ENV_FILE not found. Run ./.github/scripts/reference-impl-sync/merge-reference-impl-start.sh first." + exit 1 +fi + +# shellcheck source=/dev/null +source "$ENV_FILE" + +SHOW_FULL=false +if [[ "${1:-}" == "--full" ]]; then + SHOW_FULL=true +fi + +cd "$REFERENCE_IMPL_DIR" +git fetch origin main 2>/dev/null + +RANGE="$LAST_MERGE_COMMIT..origin/main" + +echo "════════════════════════════════════════════════════════════" +echo " Reference implementation diff analysis: $RANGE" +echo "════════════════════════════════════════════════════════════" + +# ── Commit log ──────────────────────────────────────────────── +echo "" +echo "── Commit log ──" +git log --oneline --no-decorate "$RANGE" +echo "" + +# Helper to print a section +section() { + local title="$1"; shift + local paths=("$@") + + echo "── $title ──" + local stat + stat=$(git diff "$RANGE" --stat -- "${paths[@]}" 2>/dev/null || true) + if [[ -z "$stat" ]]; then + echo " (no changes)" + else + echo "$stat" + if $SHOW_FULL; then + echo "" + git diff "$RANGE" -- "${paths[@]}" 2>/dev/null || true + fi + fi + echo "" +} + +# ── Sections ────────────────────────────────────────────────── +section ".NET source (dotnet/src)" "dotnet/src/" +section ".NET tests (dotnet/test)" "dotnet/test/" +section "Test snapshots" "test/snapshots/" +section "Documentation (docs/)" "docs/" +section "Protocol & config" "sdk-protocol-version.json" "package.json" "justfile" +section "Go SDK" "go/" +section "Node.js SDK" "nodejs/" +section "Python SDK" "python/" +section "Other files" "README.md" "CONTRIBUTING.md" "SECURITY.md" "SUPPORT.md" + +echo "════════════════════════════════════════════════════════════" +echo " To see full diffs: $0 --full" +echo " To see a specific path:" +echo " cd $REFERENCE_IMPL_DIR && git diff $RANGE -- " +echo "════════════════════════════════════════════════════════════" diff --git a/.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh b/.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh new file mode 100755 index 000000000..480d43bc0 --- /dev/null +++ b/.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# ────────────────────────────────────────────────────────────── +# merge-reference-impl-finish.sh +# +# Finalises a reference implementation merge: +# 1. Runs format + test + build (via format-and-test.sh) +# 2. Updates .lastmerge to reference implementation HEAD +# 3. Commits the .lastmerge update +# 4. Pushes the branch to origin +# +# Usage: ./.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh +# ./.github/scripts/reference-impl-sync/merge-reference-impl-finish.sh --skip-tests +# +# Requires: .merge-env written by merge-reference-impl-start.sh +# ────────────────────────────────────────────────────────────── +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +ENV_FILE="$ROOT_DIR/.merge-env" + +if [[ ! -f "$ENV_FILE" ]]; then + echo "❌ $ENV_FILE not found. Run ./.github/scripts/reference-impl-sync/merge-reference-impl-start.sh first." + exit 1 +fi + +# shellcheck source=/dev/null +source "$ENV_FILE" + +SKIP_TESTS=false +if [[ "${1:-}" == "--skip-tests" ]]; then + SKIP_TESTS=true +fi + +cd "$ROOT_DIR" + +# ── 1. Format, test, build ─────────────────────────────────── +if $SKIP_TESTS; then + echo "▸ Formatting only (tests skipped)…" + mvn spotless:apply + mvn clean package -DskipTests +else + echo "▸ Running format + test + build…" + "$ROOT_DIR/.github/scripts/build/format-and-test.sh" +fi + +# ── 2. Update .lastmerge ───────────────────────────────────── +echo "▸ Updating .lastmerge…" +NEW_COMMIT=$(cd "$REFERENCE_IMPL_DIR" && git rev-parse origin/main) +echo "$NEW_COMMIT" > "$ROOT_DIR/.lastmerge" + +git add .lastmerge +git commit -m "Update .lastmerge to $NEW_COMMIT" + +# ── 3. Push branch ─────────────────────────────────────────── +echo "▸ Pushing branch $BRANCH_NAME to origin…" +git push -u origin "$BRANCH_NAME" + +echo "" +echo "✅ Branch pushed. Next step:" +echo " Create a Pull Request (base: main, head: $BRANCH_NAME)" +echo "" +echo " Suggested title: Merge reference implementation SDK changes ($(date +%Y-%m-%d))" +echo " Don't forget to add the 'reference-impl-sync' label." diff --git a/.github/scripts/reference-impl-sync/merge-reference-impl-start.sh b/.github/scripts/reference-impl-sync/merge-reference-impl-start.sh new file mode 100755 index 000000000..76408e4ec --- /dev/null +++ b/.github/scripts/reference-impl-sync/merge-reference-impl-start.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# ────────────────────────────────────────────────────────────── +# merge-reference-impl-start.sh +# +# Prepares the workspace for a reference implementation merge: +# 1. Creates a dated branch from main +# 2. Updates Copilot CLI and records the new version +# 3. Clones the reference implementation copilot-sdk repo into a temp dir +# 4. Reads .lastmerge and prints a short summary of new commits +# +# Usage: ./.github/scripts/reference-impl-sync/merge-reference-impl-start.sh +# Output: Exports REFERENCE_IMPL_DIR and LAST_MERGE_COMMIT to a +# .merge-env file so other scripts can source it. +# ────────────────────────────────────────────────────────────── +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +cd "$ROOT_DIR" + +REFERENCE_IMPL_REPO="https://github.com/github/copilot-sdk.git" +ENV_FILE="$ROOT_DIR/.merge-env" + +# ── 1. Create branch (or reuse existing) ───────────────────── +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + +if [[ "$CURRENT_BRANCH" != "main" ]]; then + # Already on a non-main branch (e.g., coding agent's auto-created PR branch). + # Stay on this branch — do not create a new one. + BRANCH_NAME="$CURRENT_BRANCH" + echo "▸ Already on branch '$BRANCH_NAME' — reusing it (coding agent mode)." + git pull origin main --no-edit 2>/dev/null || echo " (pull from main skipped or fast-forward not possible)" +else + echo "▸ Ensuring main is up to date…" + git pull origin main + + BRANCH_NAME="merge-reference-impl-$(date +%Y%m%d)" + echo "▸ Creating branch: $BRANCH_NAME" + git checkout -b "$BRANCH_NAME" +fi + +# ── 2. Update Copilot CLI ──────────────────────────────────── +echo "▸ Updating Copilot CLI…" +if command -v copilot &>/dev/null; then + copilot update || echo " (copilot update returned non-zero – check manually)" + CLI_VERSION=$(copilot --version | head -n 1 | awk '{print $NF}') + echo " Copilot CLI version: $CLI_VERSION" +else + echo " ⚠ 'copilot' command not found – skipping CLI update." + CLI_VERSION="UNKNOWN" +fi + +# ── 3. Clone reference implementation ──────────────────────────────────────── +TEMP_DIR=$(mktemp -d) +REFERENCE_IMPL_DIR="$TEMP_DIR/copilot-sdk" +echo "▸ Cloning reference implementation into $REFERENCE_IMPL_DIR …" +git clone --depth=200 "$REFERENCE_IMPL_REPO" "$REFERENCE_IMPL_DIR" + +# ── 4. Read last merge commit ──────────────────────────────── +if [[ ! -f "$ROOT_DIR/.lastmerge" ]]; then + echo "❌ .lastmerge file not found in repo root." + exit 1 +fi +LAST_MERGE_COMMIT=$(tr -d '[:space:]' < "$ROOT_DIR/.lastmerge") +echo "▸ Last merged reference implementation commit: $LAST_MERGE_COMMIT" + +# Quick summary +echo "" +echo "── Reference implementation commits since last merge ──" +(cd "$REFERENCE_IMPL_DIR" && git fetch origin main && \ + git log --oneline "$LAST_MERGE_COMMIT"..origin/main) || \ + echo " (could not generate log – the commit may have been rebased)" +echo "" + +# ── 5. Write env file for other scripts ────────────────────── +cat > "$ENV_FILE" < + 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-reference-impl-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 + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + issues: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_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: weeklyreferenceimplsync + 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: ${{ needs.activation.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + with: + destination: /opt/gh-aw/actions + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + 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" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + (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 }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_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/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + 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.23.0 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + 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.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 << '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":["reference-impl-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 \"[reference-impl-sync] \". Labels [reference-impl-sync] will be automatically added. Assignees [copilot] will be automatically assigned.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.", + "type": "string" + }, + "labels": { + "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.", + "items": { + "type": "string" + }, + "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_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 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": { + "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_issue" + }, + { + "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": { + "body": { + "description": "Closing comment explaining why the issue is being closed and summarizing any resolution, workaround, or conclusion.", + "type": "string" + }, + "issue_number": { + "description": "Issue number to close. This is the numeric ID from the GitHub URL (e.g., 901 in github.com/owner/repo/issues/901). If omitted, closes the issue that triggered this workflow (requires an issue event trigger).", + "type": [ + "number", + "string" + ] + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "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. 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. 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 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" + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "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\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "agent": { + "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", + "type": "string" + }, + "issue_number": { + "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 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" + }, + "name": "assign_to_agent" + }, + { + "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "reason": { + "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", + "type": "string" + }, + "tool": { + "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "name": "missing_tool" + }, + { + "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "noop" + }, + { + "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "context": { + "description": "Additional context about the missing data or where it should come from (max 256 characters).", + "type": "string" + }, + "data_type": { + "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", + "type": "string" + }, + "reason": { + "description": "Explanation of why this data is needed to complete the task (max 256 characters).", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "missing_data" + } + ] + GH_AW_SAFE_OUTPUTS_TOOLS_EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "assign_to_agent": { + "defaultMax": 1, + "fields": { + "agent": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "issue_number": { + "issueNumberOrTemporaryId": true + }, + "pull_number": { + "optionalPositiveInteger": true + }, + "pull_request_repo": { + "type": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" + }, + "close_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "issue_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "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": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + 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 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 << 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.31.0", + "env": { + "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues" + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + 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 + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + # 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 }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + 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 + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + 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" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + 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/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Safe Outputs + if: always() + 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 }} + 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 }} + 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/collect_ndjson_output.cjs'); + await main(); + - name: Upload sanitized agent output + if: always() && env.GH_AW_AGENT_OUTPUT + 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: agent_outputs + path: | + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + if-no-files-found: ignore + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + 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_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + 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_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # 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 + # 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /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 Reference Implementation Sync Agentic Workflow" + WORKFLOW_DESCRIPTION: "Weekly reference implementation 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 + - safe_outputs + if: (always()) && (needs.agent.result != 'skipped') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + 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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" + 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/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" + 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/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" + 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-reference-impl-sync" + 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: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "false" + 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/handle_noop_message.cjs'); + await main(); + + safe_outputs: + needs: agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + 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-reference-impl-sync" + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" + outputs: + 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@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + 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\":[\"reference-impl-sync\"],\"target\":\"*\"},\"create_issue\":{\"assignees\":[\"copilot\"],\"expires\":144,\"labels\":[\"reference-impl-sync\"],\"max\":1,\"title_prefix\":\"[reference-impl-sync] \"},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_ASSIGN_COPILOT: "true" + 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/safe_output_handler_manager.cjs'); + await main(); + - name: Assign Copilot to created issues + if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != '' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }} + with: + github-token: ${{ secrets.GH_AW_AGENT_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/assign_copilot_to_created_issues.cjs'); + await main(); + - 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: + github-token: ${{ secrets.GH_AW_AGENT_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/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-reference-impl-sync.md b/.github/workflows/weekly-reference-impl-sync.md new file mode 100644 index 000000000..7e72248e1 --- /dev/null +++ b/.github/workflows/weekly-reference-impl-sync.md @@ -0,0 +1,151 @@ +--- +description: | + Weekly reference implementation sync workflow. Checks for new commits in the official + Copilot SDK (github/copilot-sdk) and assigns to Copilot to port changes. + +on: + schedule: weekly + workflow_dispatch: + +permissions: + contents: read + actions: read + issues: read + +network: + allowed: + - defaults + - github + +tools: + github: + toolsets: [context, repos, issues] + +safe-outputs: + create-issue: + title-prefix: "[reference-impl-sync] " + assignees: [copilot] + labels: [reference-impl-sync] + expires: 6 + close-issue: + required-labels: [reference-impl-sync] + target: "*" + max: 10 + add-comment: + target: "*" + max: 10 + assign-to-agent: + name: "copilot" + model: "claude-opus-4.6" + target: "*" + noop: + report-as-issue: false +--- +# Weekly Reference Implementation Sync Agentic Workflow +This document describes the `weekly-reference-impl-sync.yml` GitHub Actions workflow, which automates the detection of new changes in the official [Copilot SDK](https://github.com/github/copilot-sdk) and delegates the merge work to the Copilot coding agent. + +## Overview + +The workflow runs on a **weekly schedule** (every Monday at 10:00 UTC) and can also be triggered manually. It does **not** perform the actual merge — instead, it detects reference implementation changes and creates a GitHub issue assigned to `copilot`, instructing the agent to follow the [agentic-merge-reference-impl](../prompts/agentic-merge-reference-impl.prompt.md) prompt to port the changes. + +The agent must also create the Pull Request with the label `reference-impl-sync`. This allows the workflow to track the merge progress and avoid creating duplicate issues if the agent is still working on a previous sync. + +## Trigger + +| Trigger | Schedule | +|---|---| +| `schedule` | Every Monday at 10:00 UTC (`0 10 * * 1`) | +| `workflow_dispatch` | Manual trigger from the Actions tab | + +## Workflow Steps + +### 1. Checkout repository + +Checks out the repo to read the `.lastmerge` file, which contains the SHA of the last reference implementation commit that was merged into the Java SDK. + +### 2. Check for reference implementation changes + +- Reads the last merged commit hash from `.lastmerge` +- Clones the reference implementation `github/copilot-sdk` repository +- Compares `.lastmerge` against reference implementation `HEAD` +- If they match: sets `has_changes=false` +- If they differ: counts new commits, generates a summary (up to 20 most recent), and sets outputs (`commit_count`, `reference_impl_head`, `last_merge`, `summary`) + +### 3. Close previous reference-impl-sync issues (when changes found) + +**Condition:** `has_changes == true` + +Before creating a new issue, closes any existing open issues with the `reference-impl-sync` label. This prevents stale issues from accumulating when previous sync attempts were incomplete or superseded. Each closed issue receives a comment explaining it was superseded. + +### 4. Close stale reference-impl-sync issues (when no changes found) + +**Condition:** `has_changes == false` + +If the reference implementation is already up to date, closes any lingering open `reference-impl-sync` issues with a comment noting that no changes were detected. This handles the case where a previous issue was created but the changes were merged manually (updating `.lastmerge`) before the agent completed. + +### 5. Create issue and assign to Copilot + +**Condition:** `has_changes == true` + +Creates a new GitHub issue with: + +- **Title:** `Reference implementation sync: N new commits (YYYY-MM-DD)` +- **Label:** `reference-impl-sync` +- **Assignee:** `copilot` +- **Body:** Contains commit count, commit range links, a summary of recent commits, and a link to the merge prompt + +The Copilot coding agent picks up the issue, creates a branch and PR, then follows the merge prompt to port the changes. + +### 6. Summary + +Writes a GitHub Actions step summary with: + +- Whether changes were detected +- Commit count and range +- Recent reference implementation commits +- Link to the created issue (if any) + +## Flow Diagram + +``` +┌─────────────────────┐ +│ Schedule / Manual │ +└──────────┬──────────┘ + │ + ▼ +┌──────────────────────────┐ +│ Read .lastmerge │ +│ Clone reference impl SDK │ +│ Compare commits │ +└──────────┬───────────────┘ + │ + ┌─────┴─────┐ + │ │ + changes? no changes + │ │ + ▼ ▼ +┌──────────┐ ┌──────────────────┐ +│ Close old│ │ Close stale │ +│ issues │ │ issues │ +└────┬─────┘ └──────────────────┘ + │ + ▼ +┌──────────────────────────┐ +│ Create issue assigned to │ +│ copilot │ +└──────────────────────────┘ + │ + ▼ +┌──────────────────────────┐ +│ Agent follows prompt to │ +│ port changes → PR │ +└──────────────────────────┘ +``` + +## Related Files + +| File | Purpose | +|---|---| +| `.lastmerge` | Stores the SHA of the last merged reference implementation commit | +| [agentic-merge-reference-impl.prompt.md](../prompts/agentic-merge-reference-impl.prompt.md) | Detailed instructions the Copilot agent follows to port changes | +| `.github/scripts/reference-impl-sync/` | Helper scripts used by the merge prompt | diff --git a/.github/workflows/weekly-reference-impl-sync.yml b/.github/workflows/weekly-reference-impl-sync.yml new file mode 100644 index 000000000..aa3fc971a --- /dev/null +++ b/.github/workflows/weekly-reference-impl-sync.yml @@ -0,0 +1,181 @@ +name: "Weekly Reference Implementation Sync" + +on: + schedule: + # Every Monday at 10:00 UTC (5:00 AM EST / 6:00 AM EDT) + - cron: '0 10 * * 1' + workflow_dispatch: + +permissions: + contents: write + issues: write + pull-requests: write + +env: + # Used for `gh` CLI operations to create/close/comment issues. Must be a PAT with repo permissions because the default + GH_AW_AGENT_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN }} + GH_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN }} + +jobs: + + check-and-sync: + name: "Check reference implementation & trigger Copilot merge" + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Check for reference implementation changes + id: check + run: | + LAST_MERGE=$(cat .lastmerge) + echo "Last merged commit: $LAST_MERGE" + + git clone --quiet https://github.com/github/copilot-sdk.git /tmp/reference-impl + cd /tmp/reference-impl + + REFERENCE_IMPL_HEAD=$(git rev-parse HEAD) + echo "Reference implementation HEAD: $REFERENCE_IMPL_HEAD" + + echo "last_merge=$LAST_MERGE" >> "$GITHUB_OUTPUT" + + if [ "$LAST_MERGE" = "$REFERENCE_IMPL_HEAD" ]; then + echo "No new reference implementation changes since last merge." + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + COMMIT_COUNT=$(git rev-list --count "$LAST_MERGE".."$REFERENCE_IMPL_HEAD") + echo "Found $COMMIT_COUNT new reference implementation commits." + echo "has_changes=true" >> "$GITHUB_OUTPUT" + echo "commit_count=$COMMIT_COUNT" >> "$GITHUB_OUTPUT" + echo "reference_impl_head=$REFERENCE_IMPL_HEAD" >> "$GITHUB_OUTPUT" + + # Generate a short summary of changes + SUMMARY=$(git log --oneline "$LAST_MERGE".."$REFERENCE_IMPL_HEAD" | head -20) + { + echo "summary<> "$GITHUB_OUTPUT" + fi + + - name: Close previous reference-impl-sync issues + if: steps.check.outputs.has_changes == 'true' + run: | + # Find all open issues with the reference-impl-sync label + OPEN_ISSUES=$(gh issue list \ + --repo "${{ github.repository }}" \ + --label "reference-impl-sync" \ + --state open \ + --json number \ + --jq '.[].number') + + for ISSUE_NUM in $OPEN_ISSUES; do + echo "Closing superseded issue #${ISSUE_NUM}" + gh issue comment "$ISSUE_NUM" \ + --repo "${{ github.repository }}" \ + --body "Superseded by a newer reference implementation sync issue. Closing this one." + gh issue close "$ISSUE_NUM" \ + --repo "${{ github.repository }}" \ + --reason "not planned" + done + + - name: Close stale reference-impl-sync issues (no changes) + if: steps.check.outputs.has_changes == 'false' + run: | + OPEN_ISSUES=$(gh issue list \ + --repo "${{ github.repository }}" \ + --label "reference-impl-sync" \ + --state open \ + --json number \ + --jq '.[].number') + + for ISSUE_NUM in $OPEN_ISSUES; do + echo "Closing stale issue #${ISSUE_NUM} — reference implementation is up to date" + gh issue comment "$ISSUE_NUM" \ + --repo "${{ github.repository }}" \ + --body "No new reference implementation changes detected. The Java SDK is up to date. Closing." + gh issue close "$ISSUE_NUM" \ + --repo "${{ github.repository }}" \ + --reason "completed" + done + + - name: Create issue and assign to Copilot + id: create-issue + if: steps.check.outputs.has_changes == 'true' + env: + SUMMARY: ${{ steps.check.outputs.summary }} + run: | + COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" + LAST_MERGE="${{ steps.check.outputs.last_merge }}" + REFERENCE_IMPL_HEAD="${{ steps.check.outputs.reference_impl_head }}" + DATE=$(date -u +"%Y-%m-%d") + REPO="${{ github.repository }}" + + BODY="## Automated Reference Implementation Sync + + There are **${COMMIT_COUNT}** new commits in the [official Copilot SDK](https://github.com/github/copilot-sdk) since the last merge. + + - **Last merged commit:** [\`${LAST_MERGE}\`](https://github.com/github/copilot-sdk/commit/${LAST_MERGE}) + - **Reference implementation HEAD:** [\`${REFERENCE_IMPL_HEAD}\`](https://github.com/github/copilot-sdk/commit/${REFERENCE_IMPL_HEAD}) + + ### Recent reference implementation commits + + \`\`\` + ${SUMMARY} + \`\`\` + + ### Instructions + + Follow the [agentic-merge-reference-impl](.github/prompts/agentic-merge-reference-impl.prompt.md) prompt to port these changes to the Java SDK." + + # Create the issue and assign to Copilot coding agent + ISSUE_URL=$(gh issue create \ + --repo "$REPO" \ + --title "Reference implementation sync: ${COMMIT_COUNT} new commits (${DATE})" \ + --body "$BODY" \ + --label "reference-impl-sync" \ + --assignee "copilot-swe-agent") + + echo "issue_url=$ISSUE_URL" >> "$GITHUB_OUTPUT" + echo "✅ Issue created and assigned to Copilot coding agent: $ISSUE_URL" + + - name: Summary + if: always() + env: + SUMMARY: ${{ steps.check.outputs.summary }} + run: | + HAS_CHANGES="${{ steps.check.outputs.has_changes }}" + COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" + LAST_MERGE="${{ steps.check.outputs.last_merge }}" + REFERENCE_IMPL_HEAD="${{ steps.check.outputs.reference_impl_head }}" + ISSUE_URL="${{ steps.create-issue.outputs.issue_url }}" + + { + echo "## Weekly Reference Implementation Sync" + echo "" + if [ "$HAS_CHANGES" = "true" ]; then + echo "### ✅ New reference implementation changes detected" + echo "" + echo "| | |" + echo "|---|---|" + echo "| **New commits** | ${COMMIT_COUNT} |" + echo "| **Last merged** | \`${LAST_MERGE:0:12}\` |" + echo "| **Reference implementation HEAD** | \`${REFERENCE_IMPL_HEAD:0:12}\` |" + echo "" + echo "An issue has been created and assigned to the Copilot coding agent: " + echo " -> ${ISSUE_URL}" + echo "" + echo "### Recent reference implementation commits" + echo "" + echo '```' + echo "$SUMMARY" + echo '```' + else + echo "### ⏭️ No changes" + echo "" + echo "The Java SDK is already up to date with the reference implementation Copilot SDK." + echo "" + echo "**Last merged commit:** \`${LAST_MERGE:0:12}\`" + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9320ee1a..6c85671bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ We'd love your help with: * 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. +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 [Copilot SDK](https://github.com/github/copilot-sdk) reference implementation and can be maintained in sync. Note that this repo periodically merges reference implementation changes — see the [README](README.md#agentic-reference-implementation-merge-and-sync) for details on how that works. ## Prerequisites for running and testing code diff --git a/README.md b/README.md index a33aaedda..34c04ace7 100644 --- a/README.md +++ b/README.md @@ -161,14 +161,14 @@ See [WORKFLOWS.md](docs/WORKFLOWS.md) for a full overview and details on each wo Contributions are welcome! Please see the [Contributing Guide](CONTRIBUTING.md) for details. -### Agentic Upstream Merge and Sync +### Agentic Reference Implementation Merge and Sync -This SDK tracks the official [Copilot SDK](https://github.com/github/copilot-sdk) (.NET reference implementation) and ports changes to Java. The upstream merge process is automated with AI assistance: +This SDK tracks the official [Copilot SDK](https://github.com/github/copilot-sdk) (.NET reference implementation) and ports changes to Java. The reference implementation merge process is automated with AI assistance: -**Weekly automated sync** — A [scheduled GitHub Actions workflow](.github/workflows/weekly-upstream-sync.yml) runs every Monday at 5 AM ET. It checks for new upstream commits since the last merge (tracked in [`.lastmerge`](.lastmerge)), and if changes are found, creates an issue labeled `upstream-sync` and assigns it to the GitHub Copilot coding agent. Any previously open `upstream-sync` issues are automatically closed. +**Weekly automated sync** — A [scheduled GitHub Actions workflow](.github/workflows/weekly-reference-impl-sync.yml) runs every Monday at 5 AM ET. It checks for new reference implementation commits since the last merge (tracked in [`.lastmerge`](.lastmerge)), and if changes are found, creates an issue labeled `reference-impl-sync` and assigns it to the GitHub Copilot coding agent. Any previously open `reference-impl-sync` issues are automatically closed. -**Reusable prompt** — The merge workflow is defined in [`agentic-merge-upstream.prompt.md`](.github/prompts/agentic-merge-upstream.prompt.md). It can be triggered manually from: -- **VS Code Copilot Chat** — type `/agentic-merge-upstream` +**Reusable prompt** — The merge workflow is defined in [`agentic-merge-reference-impl.prompt.md`](.github/prompts/agentic-merge-reference-impl.prompt.md). It can be triggered manually from: +- **VS Code Copilot Chat** — type `/agentic-merge-reference-impl` - **GitHub Copilot CLI** — use `copilot` CLI with the same skill reference ### Development Setup diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md index 74afbd337..57ec34f71 100644 --- a/docs/WORKFLOWS.md +++ b/docs/WORKFLOWS.md @@ -7,8 +7,8 @@ | [Build & Test](workflows/build-test.yml) | Builds, lints, and tests the Java SDK | `push` (main), `pull_request`, `merge_group`, `workflow_dispatch` | Sundays at 00:00 UTC | | [Deploy Documentation](workflows/deploy-site.yml) | Generates and deploys versioned docs to GitHub Pages | `workflow_run` (after Build & Test), `release`, `workflow_dispatch` | — | | [Publish to Maven Central](workflows/publish-maven.yml) | Releases the SDK to Maven Central and creates a GitHub Release | `workflow_dispatch` | — | -| [Weekly Upstream Sync](workflows/weekly-upstream-sync.yml) | Checks for new upstream commits and creates an issue for Copilot to merge | `workflow_dispatch` | Mondays at 10:00 UTC | -| [Weekly Upstream Sync (Agentic)](workflows/weekly-upstream-sync.lock.yml) | Compiled agentic workflow that executes the upstream sync via `gh-aw` | `workflow_dispatch` | Tuesdays at 08:39 UTC (scattered) | +| [Weekly Reference Implementation Sync](workflows/weekly-reference-impl-sync.yml) | Checks for new reference implementation commits and creates an issue for Copilot to merge | `workflow_dispatch` | Mondays at 10:00 UTC | +| [Weekly Reference Implementation Sync (Agentic)](workflows/weekly-reference-impl-sync.lock.yml) | Compiled agentic workflow that executes the reference implementation sync via `gh-aw` | `workflow_dispatch` | Tuesdays at 08:39 UTC (scattered) | | [Copilot Setup Steps](workflows/copilot-setup-steps.yml) | Configures the environment for the GitHub Copilot coding agent | `push` (on self-change), `workflow_dispatch` | — | --- @@ -53,7 +53,7 @@ Manual-only workflow that performs a full release: 1. Determines release and next development versions (auto-derived from `pom.xml` or manually specified) 2. Updates `CHANGELOG.md`, `README.md`, and `jbang-example.java` with the release version -3. Injects the upstream sync commit hash from `.lastmerge` into the changelog +3. Injects the reference implementation sync commit hash from `.lastmerge` into the changelog 4. Runs `mvn release:prepare` and `mvn release:perform` to deploy to Maven Central 5. Creates a GitHub Release with auto-generated notes 6. Moves the `latest` git tag forward @@ -62,26 +62,26 @@ Manual-only workflow that performs a full release: --- -## Weekly Upstream Sync +## Weekly Reference Implementation Sync -**File:** [`weekly-upstream-sync.yml`](workflows/weekly-upstream-sync.yml) +**File:** [`weekly-reference-impl-sync.yml`](workflows/weekly-reference-impl-sync.yml) Runs every Monday at 10:00 UTC. Clones the official `github/copilot-sdk` repository and compares `HEAD` against the commit hash stored in `.lastmerge`. If new commits are found: -1. Closes any previously open `upstream-sync` issues -2. Creates a new issue with a summary of upstream changes +1. Closes any previously open `reference-impl-sync` issues +2. Creates a new issue with a summary of reference implementation changes 3. Assigns the issue to `copilot-swe-agent` for automated porting -If no changes are found, any stale open `upstream-sync` issues are closed. +If no changes are found, any stale open `reference-impl-sync` issues are closed. --- -## Weekly Upstream Sync (Agentic Workflow: Experimental) +## Weekly Reference Implementation Sync (Agentic Workflow: Experimental) -**File:** [`weekly-upstream-sync.lock.yml`](workflows/weekly-upstream-sync.lock.yml) +**File:** [`weekly-reference-impl-sync.lock.yml`](workflows/weekly-reference-impl-sync.lock.yml) -Auto-generated compiled workflow produced by `gh aw compile` from the corresponding `.md` source. This is the agentic counterpart that actually executes the upstream merge using the `gh-aw` MCP server and Copilot coding agent. +Auto-generated compiled workflow produced by `gh aw compile` from the corresponding `.md` source. This is the agentic counterpart that actually executes the reference implementation merge using the `gh-aw` MCP server and Copilot coding agent. > **Do not edit this file directly.** Edit the `.md` source and run `gh aw compile`. From 7de4b86aa2e32174fea78b04a77fce1f0165566a Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Thu, 16 Apr 2026 15:11:00 -0400 Subject: [PATCH 42/47] Replace upstream terminology with reference implementation Rebase the topic branch onto current main and preserve the original intent: replace "upstream" naming with "reference implementation" across the repo. - Update docs, prompts, workflows, and release automation wording - Remove legacy upstream-sync assets superseded by reference-impl-sync assets - Update references in tests and project guidance content - Keep behavior aligned while clarifying terminology - Apply Spotless formatting updates in touched Java test files --- .../skills/agentic-merge-upstream/SKILL.md | 7 - .github/copilot-instructions.md | 11 +- .../agentic-merge-reference-impl.prompt.md | 17 +- .../prompts/agentic-merge-upstream.prompt.md | 439 ------ .../coding-agent-merge-instructions.md | 19 - .github/scripts/release/update-changelog.sh | 33 +- .../upstream-sync/merge-upstream-diff.sh | 86 -- .../upstream-sync/merge-upstream-finish.sh | 63 - .../upstream-sync/merge-upstream-start.sh | 88 -- .../skills/agentic-merge-upstream/SKILL.md | 7 - .github/workflows/publish-maven.yml | 15 +- .../workflows/weekly-upstream-sync.lock.yml | 1239 ----------------- .github/workflows/weekly-upstream-sync.md | 117 -- .github/workflows/weekly-upstream-sync.yml | 181 --- CHANGELOG.md | 111 +- CONTRIBUTING.md | 3 +- README.md | 3 +- docs/WORKFLOWS.md | 5 +- ...adr-001-semver-pre-general-availability.md | 3 +- ...n-and-reference-implementation-tracking.md | 3 +- .../com/github/copilot/sdk/CommandsTest.java | 3 +- .../github/copilot/sdk/ElicitationTest.java | 3 +- .../com/github/copilot/sdk/HooksTest.java | 4 +- 23 files changed, 113 insertions(+), 2347 deletions(-) delete mode 100644 .claude/skills/agentic-merge-upstream/SKILL.md delete mode 100644 .github/prompts/agentic-merge-upstream.prompt.md delete mode 100644 .github/prompts/coding-agent-merge-instructions.md delete mode 100755 .github/scripts/upstream-sync/merge-upstream-diff.sh delete mode 100755 .github/scripts/upstream-sync/merge-upstream-finish.sh delete mode 100755 .github/scripts/upstream-sync/merge-upstream-start.sh delete mode 100644 .github/skills/agentic-merge-upstream/SKILL.md delete mode 100644 .github/workflows/weekly-upstream-sync.lock.yml delete mode 100644 .github/workflows/weekly-upstream-sync.md delete mode 100644 .github/workflows/weekly-upstream-sync.yml diff --git a/.claude/skills/agentic-merge-upstream/SKILL.md b/.claude/skills/agentic-merge-upstream/SKILL.md deleted file mode 100644 index 32428db89..000000000 --- a/.claude/skills/agentic-merge-upstream/SKILL.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: agentic-merge-upstream -description: Merge upstream changes from the official Copilot SDK into this Java SDK. -license: MIT ---- - -Follow instructions in the [agentic-merge-upstream prompt](../../../.github/prompts/agentic-merge-upstream.prompt.md) to merge upstream changes from the official Copilot SDK into this Java SDK. \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index edd40797b..c1a2d8dec 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -9,7 +9,7 @@ These instructions guide GitHub Copilot when assisting with this repository. The - **Tech Stack**: Java 17+, Maven, Jackson for JSON, JUnit for testing - **Purpose**: Provide a Java SDK for programmatic control of GitHub Copilot CLI - **Architecture**: JSON-RPC client communicating with Copilot CLI over stdio -- **Key Goals**: Maintain parity with upstream .NET SDK while following Java idioms +- **Key Goals**: Maintain parity with reference implementation .NET SDK while following Java idioms ## Build & Test Commands @@ -85,7 +85,7 @@ Tests use the official copilot-sdk test harness from `https://github.com/github/ - **E2ETestContext** - Manages test environment with CapiProxy for deterministic API responses - **CapiProxy** - Node.js-based replaying proxy using YAML snapshots from `test/snapshots/` -- Test snapshots are stored in the upstream repo's `test/snapshots/` directory +- Test snapshots are stored in the reference implementation repo's `test/snapshots/` directory ## Key Conventions @@ -217,7 +217,7 @@ Test method names are converted to lowercase snake_case for snapshot filenames t - **DO NOT** modify `target/` directory - this contains build artifacts - **DO NOT** edit `pom.xml` dependencies without careful consideration - this SDK has minimal dependencies by design - **DO NOT** change the Jackson version without testing against all serialization patterns -- **DO NOT** modify test snapshots in `target/copilot-sdk/test/snapshots/` - these come from upstream +- **DO NOT** modify test snapshots in `target/copilot-sdk/test/snapshots/` - these come from reference implementation - **DO NOT** alter the Eclipse formatter configuration in `pom.xml` without team consensus - **DO NOT** remove or skip Checkstyle or Spotless checks @@ -299,12 +299,12 @@ The release process is automated via the `publish-maven.yml` GitHub Actions work - Converts the `## [Unreleased]` section to `## [version] - date` - Creates a new empty `## [Unreleased]` section at the top - Updates version comparison links at the bottom of CHANGELOG.md - - Injects the upstream SDK commit hash (from `.lastmerge`) as a `> **Upstream sync:**` blockquote in both the new `[Unreleased]` section and the released version section + - Injects the reference implementation SDK commit hash (from `.lastmerge`) as a `> **Reference implementation sync:**` blockquote in both the new `[Unreleased]` section and the released version section 2. **Reference Implementation Sync Tracking**: Each release records which commit from the official `github/copilot-sdk` it is synced to: - The `.lastmerge` file is read during the release workflow - The commit hash is injected into `CHANGELOG.md` under the release heading - - Format: `> **Upstream sync:** [\`github/copilot-sdk@SHORT_HASH\`](link-to-commit)` + - Format: `> **Reference implementation sync:** [\`github/copilot-sdk@SHORT_HASH\`](link-to-commit)` 3. **Documentation Updates**: README.md and jbang-example.java are updated with the new version. @@ -316,3 +316,4 @@ The release process is automated via the `publish-maven.yml` GitHub Actions work 5. **Rollback**: If the release fails, the documentation commit is automatically reverted The workflow is triggered manually via workflow_dispatch with optional version parameters. + diff --git a/.github/prompts/agentic-merge-reference-impl.prompt.md b/.github/prompts/agentic-merge-reference-impl.prompt.md index fd7c3fa13..1c19b6f1c 100644 --- a/.github/prompts/agentic-merge-reference-impl.prompt.md +++ b/.github/prompts/agentic-merge-reference-impl.prompt.md @@ -56,7 +56,7 @@ This writes a `.merge-env` file used by the other scripts. It outputs: - The reference-impl dir path - A short log of reference implementation commits since `.lastmerge` -## Step 2: Analyze Upstream Changes +## Step 2: Analyze reference implementation Changes Run the diff script for a detailed breakdown by area: @@ -96,7 +96,7 @@ For each change in the reference implementation diff, determine: ### Key Files to Compare -| Upstream (.NET) | Java SDK Equivalent | +| reference implementation (.NET) | Java SDK Equivalent | |------------------------------------|--------------------------------------------------------| | `dotnet/src/Client.cs` | `src/main/java/com/github/copilot/sdk/CopilotClient.java` | | `dotnet/src/Session.cs` | `src/main/java/com/github/copilot/sdk/CopilotSession.java` | @@ -121,7 +121,7 @@ Before modifying any code: 2. **Identify the Java equivalent approach** - Don't replicate C# patterns; find the idiomatic Java way 3. **Check for existing abstractions** - The Java SDK may already have mechanisms that differ from .NET 4. **Preserve backward compatibility** - Existing API signatures should not break unless absolutely necessary -5. **When in doubt, match existing code** - Follow what's already in the Java SDK, not the upstream +5. **When in doubt, match existing code** - Follow what's already in the Java SDK, not the reference implementation ### Commit Changes Incrementally @@ -181,8 +181,8 @@ After porting implementation changes, **always check for new or updated tests** ```bash cd "$TEMP_DIR/copilot-sdk" -git diff "$LAST_MERGE_COMMIT"..origin/main --stat -- dotnet/test/ -git diff "$LAST_MERGE_COMMIT"..origin/main --stat -- test/snapshots/ +git diff "$LAST_REFERENCE_IMPL_COMMIT"..origin/main --stat -- dotnet/test/ +git diff "$LAST_REFERENCE_IMPL_COMMIT"..origin/main --stat -- test/snapshots/ ``` ### Port Test Cases @@ -196,7 +196,7 @@ For each new or modified test file in `dotnet/test/`: ### Test File Mapping -| Upstream Test (.NET) | Java SDK Test | +| reference implementation Test (.NET) | Java SDK Test | |-----------------------------|--------------------------------------------------------| | `dotnet/test/AskUserTests.cs` | `src/test/java/com/github/copilot/sdk/AskUserTest.java` | | `dotnet/test/HooksTests.cs` | `src/test/java/com/github/copilot/sdk/HooksTest.java` | @@ -209,7 +209,7 @@ New test snapshots are stored in `test/snapshots/` in the reference implementati If tests fail with errors like `TypeError: Cannot read properties of undefined`, the test harness may not yet support the new RPC methods. In this case: -1. **Mark tests as `@Disabled`** with a clear reason (e.g., `@Disabled("Requires test harness update with X support - see upstream PR #NNN")`) +1. **Mark tests as `@Disabled`** with a clear reason (e.g., `@Disabled("Requires test harness update with X support - see reference implementation PR #NNN")`) 2. **Document the dependency** in the test class Javadoc 3. **Enable tests later** once the harness is updated @@ -403,7 +403,7 @@ Before finishing: - [ ] New branch created from `main` - [ ] Copilot CLI updated to latest version - [ ] README.md updated with minimum CLI version requirement -- [ ] Upstream repository cloned +- [ ] reference implementation repository cloned - [ ] Diff analyzed between `.lastmerge` commit and HEAD - [ ] New features/fixes identified - [ ] Changes ported to Java SDK following conventions @@ -437,3 +437,4 @@ Before finishing: - Uses JUnit 5 for testing - **Java SDK design decisions take precedence over reference implementation patterns** - **Adapt reference implementation changes to fit Java idioms, not the other way around** + diff --git a/.github/prompts/agentic-merge-upstream.prompt.md b/.github/prompts/agentic-merge-upstream.prompt.md deleted file mode 100644 index c1d2c54a0..000000000 --- a/.github/prompts/agentic-merge-upstream.prompt.md +++ /dev/null @@ -1,439 +0,0 @@ -# Merge Upstream SDK Changes - -You are an expert Java developer tasked with porting changes from the official Copilot SDK (primarily the .NET implementation) to this Java SDK. - -## ⚠️ IMPORTANT: Java SDK Design Takes Priority - -**The current design and architecture of the Java SDK is the priority.** When porting changes from upstream: - -1. **Adapt, don't copy** - Translate upstream features to fit the Java SDK's existing patterns, naming conventions, and architecture -2. **Preserve Java idioms** - The Java SDK should feel natural to Java developers, not like a C# port -3. **Maintain consistency** - New code must match the existing codebase style and structure -4. **Evaluate before porting** - Not every upstream change needs to be ported; some may not be applicable or may conflict with Java SDK design decisions - -Before making any changes, **read and understand the existing Java SDK implementation** to ensure new code integrates seamlessly. - -## Utility Scripts - -The `.github/scripts/` directory contains helper scripts that automate the repeatable parts of this workflow. **Use these scripts instead of running the commands manually.** - -| Script | Purpose | -|---|---| -| `.github/scripts/upstream-sync/merge-upstream-start.sh` | Creates branch, updates CLI, clones upstream, reads `.lastmerge`, prints commit summary | -| `.github/scripts/upstream-sync/merge-upstream-diff.sh` | Detailed diff analysis grouped by area (`.NET src`, tests, snapshots, docs, etc.) | -| `.github/scripts/upstream-sync/merge-upstream-finish.sh` | Runs format + test + build, updates `.lastmerge`, commits, pushes branch | -| `.github/scripts/build/format-and-test.sh` | Standalone `spotless:apply` + `mvn clean verify` (useful during porting too) | - -All scripts write/read a `.merge-env` file (git-ignored) to share state (branch name, upstream dir, last-merge commit). - -## Workflow Overview - -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. 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 - ---- - -## 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: - -```bash -./.github/scripts/upstream-sync/merge-upstream-start.sh -``` - -This writes a `.merge-env` file used by the other scripts. It outputs: -- The branch name created -- The Copilot CLI version -- The upstream dir path -- A short log of upstream commits since `.lastmerge` - -## 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 -./.github/scripts/upstream-sync/merge-upstream-diff.sh --full # full diffs -``` - -The diff script groups changes into: .NET source, .NET tests, test snapshots, documentation, protocol/config, Go/Node.js/Python SDKs, and other files. - -## Step 3: Update README with CLI Version - -After the start script runs, check the CLI version it printed (also saved in `.merge-env` as `CLI_VERSION`). Update the Requirements section in `README.md` and `src/site/markdown/index.md` to specify the minimum CLI version requirement. - -Commit this change before proceeding: - -```bash -git add README.md src/site/markdown/index.md -git commit -m "Update Copilot CLI minimum version requirement" -``` - -## Step 4: Identify Changes to Port - -Using the output from `merge-upstream-diff.sh`, focus on: -- `dotnet/src/` - Primary reference implementation -- `dotnet/test/` - Test cases to port -- `docs/` - Documentation updates -- `sdk-protocol-version.json` - Protocol version changes - -For each change in the upstream diff, determine: - -1. **New Features**: New methods, classes, or capabilities added to the SDK -2. **Bug Fixes**: Corrections to existing functionality -3. **API Changes**: Changes to public interfaces or method signatures -4. **Protocol Updates**: Changes to the JSON-RPC protocol or message types -5. **Test Updates**: New or modified test cases - -### Key Files to Compare - -| Upstream (.NET) | Java SDK Equivalent | -|------------------------------------|--------------------------------------------------------| -| `dotnet/src/Client.cs` | `src/main/java/com/github/copilot/sdk/CopilotClient.java` | -| `dotnet/src/Session.cs` | `src/main/java/com/github/copilot/sdk/CopilotSession.java` | -| `dotnet/src/Types.cs` | `src/main/java/com/github/copilot/sdk/types/*.java` | -| `dotnet/src/Generated/*.cs` | `src/main/java/com/github/copilot/sdk/types/*.java` | -| `dotnet/test/*.cs` | `src/test/java/com/github/copilot/sdk/*Test.java` | -| `docs/getting-started.md` | `README.md` and `src/site/markdown/*.md` | -| `docs/*.md` (new files) | `src/site/markdown/*.md` + update `src/site/site.xml` | -| `sdk-protocol-version.json` | (embedded in Java code or resource file) | - -> **⚠️ Important:** When adding new documentation pages, always update `src/site/site.xml` to include them in the navigation menu. - -## Step 5: Apply Changes to Java SDK - -When porting changes: - -### ⚠️ Priority: Preserve Java SDK Design - -Before modifying any code: - -1. **Read the existing Java implementation first** - Understand current patterns, class structure, and naming -2. **Identify the Java equivalent approach** - Don't replicate C# patterns; find the idiomatic Java way -3. **Check for existing abstractions** - The Java SDK may already have mechanisms that differ from .NET -4. **Preserve backward compatibility** - Existing API signatures should not break unless absolutely necessary -5. **When in doubt, match existing code** - Follow what's already in the Java SDK, not the upstream - -### Commit Changes Incrementally - -**Important:** Commit your changes as you work, grouping related changes together: - -```bash -# After porting a feature or fix, commit with a descriptive message -git add -git commit -m "Port from upstream" - -# Example commits: -# git commit -m "Port new authentication flow from upstream" -# git commit -m "Add new message types from upstream protocol update" -# git commit -m "Port bug fix for session handling from upstream" -``` - -This creates a clear history of changes that can be reviewed in the Pull Request. - -### General Guidelines - -- **Naming Conventions**: Convert C# PascalCase to Java camelCase for methods/variables -- **Async Patterns**: C# `async/await` → Java `CompletableFuture` or synchronous equivalents -- **Nullable Types**: C# `?` nullable → Java `@Nullable` annotations or `Optional` -- **Properties**: C# properties → Java getters/setters or records -- **Records**: C# records → Java records (Java 17+) -- **Events**: C# events → Java callbacks or listeners - -### Type Mappings - -| C# Type | Java Equivalent | -|------------------------|------------------------------| -| `string` | `String` | -| `int` | `int` / `Integer` | -| `bool` | `boolean` / `Boolean` | -| `Task` | `CompletableFuture` | -| `CancellationToken` | (custom implementation) | -| `IAsyncEnumerable` | `Stream` or `Iterator` | -| `JsonElement` | `JsonNode` (Jackson) | -| `Dictionary` | `Map` | -| `List` | `List` | - -### Code Style - -Follow the existing Java SDK patterns: -- Use Jackson for JSON serialization (`ObjectMapper`) -- Use Java records for DTOs where appropriate -- Follow the existing package structure under `com.github.copilot.sdk` -- Maintain backward compatibility when possible -- **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 6: Port Tests - -After porting implementation changes, **always check for new or updated tests** in the upstream repository: - -### Check for New Tests - -```bash -cd "$TEMP_DIR/copilot-sdk" -git diff "$LAST_MERGE_COMMIT"..origin/main --stat -- dotnet/test/ -git diff "$LAST_MERGE_COMMIT"..origin/main --stat -- test/snapshots/ -``` - -### Port Test Cases - -For each new or modified test file in `dotnet/test/`: - -1. **Create corresponding Java test class** in `src/test/java/com/github/copilot/sdk/` -2. **Follow existing test patterns** - Look at existing tests like `PermissionsTest.java` for structure -3. **Use the E2ETestContext** infrastructure for tests that need the test harness -4. **Match snapshot directory names** - Test snapshots in `test/snapshots/` must match the directory name used in `ctx.configureForTest()` - -### Test File Mapping - -| Upstream Test (.NET) | Java SDK Test | -|-----------------------------|--------------------------------------------------------| -| `dotnet/test/AskUserTests.cs` | `src/test/java/com/github/copilot/sdk/AskUserTest.java` | -| `dotnet/test/HooksTests.cs` | `src/test/java/com/github/copilot/sdk/HooksTest.java` | -| `dotnet/test/ClientTests.cs` | `src/test/java/com/github/copilot/sdk/CopilotClientTest.java` | -| `dotnet/test/*Tests.cs` | `src/test/java/com/github/copilot/sdk/*Test.java` | - -### Test Snapshot Compatibility - -New test snapshots are stored in `test/snapshots/` in the upstream repository. These snapshots are automatically cloned during the Maven build process. - -If tests fail with errors like `TypeError: Cannot read properties of undefined`, the test harness may not yet support the new RPC methods. In this case: - -1. **Mark tests as `@Disabled`** with a clear reason (e.g., `@Disabled("Requires test harness update with X support - see upstream PR #NNN")`) -2. **Document the dependency** in the test class Javadoc -3. **Enable tests later** once the harness is updated - -### Unit Tests vs E2E Tests - -- **Unit tests** (like auth option validation) can run without the test harness -- **E2E tests** require the test harness with matching snapshots - -Commit tests separately or together with their corresponding implementation changes. - -## Step 7: Format and Run Tests - -After applying changes, use the convenience script: - -```bash -./.github/scripts/build/format-and-test.sh # format + full verify -./.github/scripts/build/format-and-test.sh --debug # with debug logging -``` - -Or for quicker iteration during porting: - -```bash -./.github/scripts/build/format-and-test.sh --format-only # just spotless -./.github/scripts/build/format-and-test.sh --test-only # skip formatting -``` - -### If Tests Fail - -1. Read the test output carefully -2. Identify the root cause (compilation error, runtime error, assertion failure) -3. Fix the issue in the Java code -4. Re-run tests -5. Repeat until all tests pass - -### Common Issues - -- **Missing imports**: Add required import statements -- **Type mismatches**: Ensure proper type conversions -- **Null handling**: Add null checks where C# had nullable types -- **JSON serialization**: Verify Jackson annotations are correct - -## Step 8: Build the Package - -Once tests pass, build the complete package: - -```bash -mvn clean package -DskipTests -``` - -Verify: -- No compilation errors -- No warnings (if possible) -- JAR file is generated in `target/` - -## 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 - -For each new feature or significant change: - -1. **README.md**: Update the main README if there are user-facing changes -2. **src/site/markdown/index.md**: Update if requirements or quick start examples change -3. **src/site/markdown/documentation.md**: Add sections for new basic usage patterns -4. **src/site/markdown/advanced.md**: Add sections for new advanced features (tools, handlers, configurations) -5. **src/site/markdown/mcp.md**: Update if MCP-related changes are made -6. **Javadoc**: Add/update Javadoc comments for all new/changed public APIs -7. **src/site/site.xml**: Update if new documentation pages were added - -### Documentation Requirements for New Features - -When adding a new feature, ensure the documentation includes: - -- **What it does**: Clear explanation of the feature's purpose -- **How to use it**: Code example showing typical usage -- **API reference**: Link to relevant Javadoc -- **Configuration options**: All available settings/properties - -### Example: Documenting a New Handler - -If a new handler (like `UserInputHandler`, `PermissionHandler`) is added, create a section in `advanced.md`: - -```markdown -## Feature Name - -Brief description of what the feature does. - -\`\`\`java -var session = client.createSession( - new SessionConfig() - .setOnFeatureRequest((request, invocation) -> { - // Handle the request - return CompletableFuture.completedFuture(result); - }) -).get(); -\`\`\` - -Explain the request/response objects and their properties. - -See [FeatureHandler](apidocs/com/github/copilot/sdk/json/FeatureHandler.html) Javadoc for more details. -``` - -### Verify Documentation Consistency - -Ensure consistency across all documentation files: - -- Requirements section should match in `README.md` and `src/site/markdown/index.md` -- Code examples should use the same patterns and be tested -- Links to Javadoc should use correct paths (`apidocs/...`) - -## Step 10: Finish, Push, and Finalize Pull Request - -Run the finish script which updates `.lastmerge`, runs a final build, and pushes the branch: - -```bash -./.github/scripts/upstream-sync/merge-upstream-finish.sh # full format + test + push -./.github/scripts/upstream-sync/merge-upstream-finish.sh --skip-tests # if tests already passed -``` - -### PR Handling: Coding Agent vs. Manual Workflow - -**If running as a Copilot coding agent** (triggered via GitHub issue assignment by the weekly sync workflow), a pull request has **already been created automatically** for you. Do NOT create a new one. Just push your commits to the current branch — the existing PR will be updated. Add the `upstream-sync` label to the existing PR by running this command in a terminal: - -```bash -gh pr edit --add-label "upstream-sync" -``` - -> **No-changes scenario (coding agent only):** If after analyzing the upstream diff there are no relevant changes to port to the Java SDK, push an empty commit with a message explaining why no changes were needed, so the PR reflects the analysis outcome. The repository maintainer will close the PR and issue manually. - -**If running manually** (e.g., from VS Code via the reusable prompt), create the Pull Request using `gh` CLI or the GitHub MCP tool. Then add the label: - -```bash -gh pr create --base main --title "Merge upstream SDK changes (YYYY-MM-DD)" --body-file /dev/stdin <<< "$PR_BODY" -gh pr edit --add-label "upstream-sync" -``` - -The PR body should include: -1. **Title**: `Merge upstream SDK changes (YYYY-MM-DD)` -2. **Body** with: - - Summary of upstream commits analyzed (with count and commit range) - - Table of changes ported (commit hash + description) - - List of changes intentionally not ported (with reasons) - - Verification status (test count, build status) - -### PR Body Template - -```markdown -## Upstream Merge - -Ports changes from the official Copilot SDK ([github/copilot-sdk](https://github.com/github/copilot-sdk)) since last merge (``→``). - -### Upstream commits analyzed (N commits) - -- Brief description of each upstream change and whether it was ported or not - -### Changes ported - -| Commit | Description | -|---|---| -| `` | Description of change | - -### Not ported (intentionally) - -- **Feature name** — Reason why it wasn't ported - -### Verification - -- All **N tests pass** (`mvn clean test`) -- Package builds successfully (`mvn clean package -DskipTests`) -- Code formatted with Spotless -``` - -## Step 11: Final Review - -Before finishing: - -1. Run `git log --oneline main..$BRANCH_NAME` to review all commits -2. Run `git diff main..$BRANCH_NAME --stat` to see a summary of all changes -3. Ensure no unintended changes were made -4. Verify code follows project conventions -5. Confirm the branch was pushed to remote -6. Confirm the Pull Request is ready (created or updated) and provide the PR URL to the user - ---- - -## Checklist - -- [ ] New branch created from `main` -- [ ] Copilot CLI updated to latest version -- [ ] README.md updated with minimum CLI version requirement -- [ ] Upstream repository cloned -- [ ] Diff analyzed between `.lastmerge` commit and HEAD -- [ ] New features/fixes identified -- [ ] Changes ported to Java SDK following conventions -- [ ] **New/updated tests ported from upstream** (check `dotnet/test/` and `test/snapshots/`) -- [ ] Tests marked `@Disabled` if harness doesn't support new features yet -- [ ] Changes committed incrementally with descriptive messages -- [ ] `mvn test` passes -- [ ] `mvn package` builds successfully -- [ ] **Documentation updated for new features:** - - [ ] `README.md` updated if user-facing changes - - [ ] `src/site/markdown/index.md` updated if requirements changed - - [ ] `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 -- [ ] **Pull Request finalized** (coding agent: push to existing PR; manual: create via `mcp_github_create_pull_request`) -- [ ] **`upstream-sync` label added** to the PR via `mcp_github_add_issue_labels` -- [ ] PR URL provided to user - ---- - -## Notes - -- The upstream SDK is at: `https://github.com/github/copilot-sdk.git` -- Primary reference implementation is in `dotnet/` folder -- This Java SDK targets Java 17+ -- Uses Jackson for JSON processing -- 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/prompts/coding-agent-merge-instructions.md b/.github/prompts/coding-agent-merge-instructions.md deleted file mode 100644 index 1c18e1f5f..000000000 --- a/.github/prompts/coding-agent-merge-instructions.md +++ /dev/null @@ -1,19 +0,0 @@ - - - -Follow the agentic-merge-upstream prompt at .github/prompts/agentic-merge-upstream.prompt.md -to port upstream changes to the Java SDK. - -Use the utility scripts in .github/scripts/ subfolders for initialization, diffing, formatting, and testing. -Commit changes incrementally. Update .lastmerge when done. - -IMPORTANT: A pull request has already been created automatically for you — do NOT create a new -one. Push your commits to the current branch, and the existing PR will be updated. - -Add the 'upstream-sync' label to the existing PR by running this command in a terminal: - - gh pr edit --add-label "upstream-sync" - -If after analyzing the upstream diff there are no relevant changes to port to the Java SDK, -push an empty commit with a message explaining why no changes were needed, so the PR reflects -the analysis outcome. The repository maintainer will close the PR and issue manually. diff --git a/.github/scripts/release/update-changelog.sh b/.github/scripts/release/update-changelog.sh index a18d35b02..926da5b3b 100755 --- a/.github/scripts/release/update-changelog.sh +++ b/.github/scripts/release/update-changelog.sh @@ -2,24 +2,24 @@ set -e # Script to update CHANGELOG.md during release process -# Usage: ./update-changelog.sh [upstream-hash] +# Usage: ./update-changelog.sh [reference implementation-hash] # Example: ./update-changelog.sh 1.0.8 # Example: ./update-changelog.sh 1.0.8 05e3c46c8c23130c9c064dc43d00ec78f7a75eab if [ -z "$1" ]; then echo "Error: Version argument required" - echo "Usage: $0 [upstream-hash]" + echo "Usage: $0 [reference implementation-hash]" exit 1 fi VERSION="$1" -UPSTREAM_HASH="${2:-}" +REFERENCE_IMPL_HASH="${2:-}" CHANGELOG_FILE="${CHANGELOG_FILE:-CHANGELOG.md}" RELEASE_DATE=$(date +%Y-%m-%d) echo "Updating CHANGELOG.md for version ${VERSION} (${RELEASE_DATE})" -if [ -n "$UPSTREAM_HASH" ]; then - echo " Upstream SDK sync: ${UPSTREAM_HASH:0:7}" +if [ -n "$REFERENCE_IMPL_HASH" ]; then + echo " Reference implementation SDK sync: ${REFERENCE_IMPL_HASH:0:7}" fi # Check if CHANGELOG.md exists @@ -38,7 +38,7 @@ fi TEMP_FILE=$(mktemp) # Process the CHANGELOG -awk -v version="$VERSION" -v date="$RELEASE_DATE" -v upstream_hash="$UPSTREAM_HASH" ' +awk -v version="$VERSION" -v date="$RELEASE_DATE" -v REFERENCE_IMPL_HASH="$REFERENCE_IMPL_HASH" ' BEGIN { unreleased_found = 0 content_found = 0 @@ -65,26 +65,26 @@ links_section && repo_url == "" && /^\[[0-9]+\.[0-9]+\.[0-9]+(-java\.[0-9]+)?\]: if (!unreleased_found) { print "## [Unreleased]" print "" - if (upstream_hash != "") { - short_hash = substr(upstream_hash, 1, 7) - print "> **Upstream sync:** [`github/copilot-sdk@" short_hash "`](https://github.com/github/copilot-sdk/commit/" upstream_hash ")" + if (REFERENCE_IMPL_HASH != "") { + short_hash = substr(REFERENCE_IMPL_HASH, 1, 7) + print "> **Reference implementation sync:** [`github/copilot-sdk@" short_hash "`](https://github.com/github/copilot-sdk/commit/" REFERENCE_IMPL_HASH ")" print "" } print "## [" version "] - " date - if (upstream_hash != "") { + if (REFERENCE_IMPL_HASH != "") { print "" - print "> **Upstream sync:** [`github/copilot-sdk@" short_hash "`](https://github.com/github/copilot-sdk/commit/" upstream_hash ")" + print "> **Reference implementation sync:** [`github/copilot-sdk@" short_hash "`](https://github.com/github/copilot-sdk/commit/" REFERENCE_IMPL_HASH ")" } unreleased_found = 1 - skip_old_upstream = 1 + skip_old_reference_impl = 1 next } } -# Skip the old upstream sync line and surrounding blank lines from the previous [Unreleased] section -skip_old_upstream && /^[[:space:]]*$/ { next } -skip_old_upstream && /^> \*\*Upstream sync:\*\*/ { next } -skip_old_upstream && !/^[[:space:]]*$/ && !/^> \*\*Upstream sync:\*\*/ { skip_old_upstream = 0 } +# Skip the old Reference implementation sync line and surrounding blank lines from the previous [Unreleased] section +skip_old_reference_impl && /^[[:space:]]*$/ { next } +skip_old_reference_impl && /^> \*\*Reference implementation sync:\*\*/ { next } +skip_old_reference_impl && !/^[[:space:]]*$/ && !/^> \*\*Reference implementation sync:\*\*/ { skip_old_reference_impl = 0 } # Capture the first version link to get the previous version links_section && first_version_link == "" && /^\[[0-9]+\.[0-9]+\.[0-9]+(-java\.[0-9]+)?\]:/ { @@ -119,3 +119,4 @@ echo "✓ CHANGELOG.md updated successfully" echo " - Added version ${VERSION} with date ${RELEASE_DATE}" echo " - Created new [Unreleased] section" echo " - Updated version comparison links" + diff --git a/.github/scripts/upstream-sync/merge-upstream-diff.sh b/.github/scripts/upstream-sync/merge-upstream-diff.sh deleted file mode 100755 index ee61b6ffc..000000000 --- a/.github/scripts/upstream-sync/merge-upstream-diff.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash -# ────────────────────────────────────────────────────────────── -# merge-upstream-diff.sh -# -# Generates a detailed diff analysis of upstream changes since -# the last merge, grouped by area of interest: -# • .NET source (primary reference) -# • .NET tests -# • Test snapshots -# • Documentation -# • Protocol / config files -# -# Usage: ./.github/scripts/upstream-sync/merge-upstream-diff.sh [--full] -# --full Show actual diffs, not just stats -# -# Requires: .merge-env written by merge-upstream-start.sh -# ────────────────────────────────────────────────────────────── -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" -ENV_FILE="$ROOT_DIR/.merge-env" - -if [[ ! -f "$ENV_FILE" ]]; then - echo "❌ $ENV_FILE not found. Run ./.github/scripts/upstream-sync/merge-upstream-start.sh first." - exit 1 -fi - -# shellcheck source=/dev/null -source "$ENV_FILE" - -SHOW_FULL=false -if [[ "${1:-}" == "--full" ]]; then - SHOW_FULL=true -fi - -cd "$UPSTREAM_DIR" -git fetch origin main 2>/dev/null - -RANGE="$LAST_MERGE_COMMIT..origin/main" - -echo "════════════════════════════════════════════════════════════" -echo " Upstream diff analysis: $RANGE" -echo "════════════════════════════════════════════════════════════" - -# ── Commit log ──────────────────────────────────────────────── -echo "" -echo "── Commit log ──" -git log --oneline --no-decorate "$RANGE" -echo "" - -# Helper to print a section -section() { - local title="$1"; shift - local paths=("$@") - - echo "── $title ──" - local stat - stat=$(git diff "$RANGE" --stat -- "${paths[@]}" 2>/dev/null || true) - if [[ -z "$stat" ]]; then - echo " (no changes)" - else - echo "$stat" - if $SHOW_FULL; then - echo "" - git diff "$RANGE" -- "${paths[@]}" 2>/dev/null || true - fi - fi - echo "" -} - -# ── Sections ────────────────────────────────────────────────── -section ".NET source (dotnet/src)" "dotnet/src/" -section ".NET tests (dotnet/test)" "dotnet/test/" -section "Test snapshots" "test/snapshots/" -section "Documentation (docs/)" "docs/" -section "Protocol & config" "sdk-protocol-version.json" "package.json" "justfile" -section "Go SDK" "go/" -section "Node.js SDK" "nodejs/" -section "Python SDK" "python/" -section "Other files" "README.md" "CONTRIBUTING.md" "SECURITY.md" "SUPPORT.md" - -echo "════════════════════════════════════════════════════════════" -echo " To see full diffs: $0 --full" -echo " To see a specific path:" -echo " cd $UPSTREAM_DIR && git diff $RANGE -- " -echo "════════════════════════════════════════════════════════════" diff --git a/.github/scripts/upstream-sync/merge-upstream-finish.sh b/.github/scripts/upstream-sync/merge-upstream-finish.sh deleted file mode 100755 index 1663ef259..000000000 --- a/.github/scripts/upstream-sync/merge-upstream-finish.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -# ────────────────────────────────────────────────────────────── -# merge-upstream-finish.sh -# -# Finalises an upstream merge: -# 1. Runs format + test + build (via format-and-test.sh) -# 2. Updates .lastmerge to upstream HEAD -# 3. Commits the .lastmerge update -# 4. Pushes the branch to origin -# -# Usage: ./.github/scripts/upstream-sync/merge-upstream-finish.sh -# ./.github/scripts/upstream-sync/merge-upstream-finish.sh --skip-tests -# -# Requires: .merge-env written by merge-upstream-start.sh -# ────────────────────────────────────────────────────────────── -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" -ENV_FILE="$ROOT_DIR/.merge-env" - -if [[ ! -f "$ENV_FILE" ]]; then - echo "❌ $ENV_FILE not found. Run ./.github/scripts/upstream-sync/merge-upstream-start.sh first." - exit 1 -fi - -# shellcheck source=/dev/null -source "$ENV_FILE" - -SKIP_TESTS=false -if [[ "${1:-}" == "--skip-tests" ]]; then - SKIP_TESTS=true -fi - -cd "$ROOT_DIR" - -# ── 1. Format, test, build ─────────────────────────────────── -if $SKIP_TESTS; then - echo "▸ Formatting only (tests skipped)…" - mvn spotless:apply - mvn clean package -DskipTests -else - echo "▸ Running format + test + build…" - "$ROOT_DIR/.github/scripts/build/format-and-test.sh" -fi - -# ── 2. Update .lastmerge ───────────────────────────────────── -echo "▸ Updating .lastmerge…" -NEW_COMMIT=$(cd "$UPSTREAM_DIR" && git rev-parse origin/main) -echo "$NEW_COMMIT" > "$ROOT_DIR/.lastmerge" - -git add .lastmerge -git commit -m "Update .lastmerge to $NEW_COMMIT" - -# ── 3. Push branch ─────────────────────────────────────────── -echo "▸ Pushing branch $BRANCH_NAME to origin…" -git push -u origin "$BRANCH_NAME" - -echo "" -echo "✅ Branch pushed. Next step:" -echo " Create a Pull Request (base: main, head: $BRANCH_NAME)" -echo "" -echo " Suggested title: Merge upstream SDK changes ($(date +%Y-%m-%d))" -echo " Don't forget to add the 'upstream-sync' label." diff --git a/.github/scripts/upstream-sync/merge-upstream-start.sh b/.github/scripts/upstream-sync/merge-upstream-start.sh deleted file mode 100755 index 755361cd1..000000000 --- a/.github/scripts/upstream-sync/merge-upstream-start.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash -# ────────────────────────────────────────────────────────────── -# merge-upstream-start.sh -# -# Prepares the workspace for an upstream merge: -# 1. Creates a dated branch from main -# 2. Updates Copilot CLI and records the new version -# 3. Clones the upstream copilot-sdk repo into a temp dir -# 4. Reads .lastmerge and prints a short summary of new commits -# -# Usage: ./.github/scripts/upstream-sync/merge-upstream-start.sh -# Output: Exports UPSTREAM_DIR and LAST_MERGE_COMMIT to a -# .merge-env file so other scripts can source it. -# ────────────────────────────────────────────────────────────── -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)" -cd "$ROOT_DIR" - -UPSTREAM_REPO="https://github.com/github/copilot-sdk.git" -ENV_FILE="$ROOT_DIR/.merge-env" - -# ── 1. Create branch (or reuse existing) ───────────────────── -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) - -if [[ "$CURRENT_BRANCH" != "main" ]]; then - # Already on a non-main branch (e.g., coding agent's auto-created PR branch). - # Stay on this branch — do not create a new one. - BRANCH_NAME="$CURRENT_BRANCH" - echo "▸ Already on branch '$BRANCH_NAME' — reusing it (coding agent mode)." - git pull origin main --no-edit 2>/dev/null || echo " (pull from main skipped or fast-forward not possible)" -else - echo "▸ Ensuring main is up to date…" - git pull origin main - - BRANCH_NAME="merge-upstream-$(date +%Y%m%d)" - echo "▸ Creating branch: $BRANCH_NAME" - git checkout -b "$BRANCH_NAME" -fi - -# ── 2. Update Copilot CLI ──────────────────────────────────── -echo "▸ Updating Copilot CLI…" -if command -v copilot &>/dev/null; then - copilot update || echo " (copilot update returned non-zero – check manually)" - CLI_VERSION=$(copilot --version | head -n 1 | awk '{print $NF}') - echo " Copilot CLI version: $CLI_VERSION" -else - echo " ⚠ 'copilot' command not found – skipping CLI update." - CLI_VERSION="UNKNOWN" -fi - -# ── 3. Clone upstream ──────────────────────────────────────── -TEMP_DIR=$(mktemp -d) -UPSTREAM_DIR="$TEMP_DIR/copilot-sdk" -echo "▸ Cloning upstream into $UPSTREAM_DIR …" -git clone --depth=200 "$UPSTREAM_REPO" "$UPSTREAM_DIR" - -# ── 4. Read last merge commit ──────────────────────────────── -if [[ ! -f "$ROOT_DIR/.lastmerge" ]]; then - echo "❌ .lastmerge file not found in repo root." - exit 1 -fi -LAST_MERGE_COMMIT=$(tr -d '[:space:]' < "$ROOT_DIR/.lastmerge") -echo "▸ Last merged upstream commit: $LAST_MERGE_COMMIT" - -# Quick summary -echo "" -echo "── Upstream commits since last merge ──" -(cd "$UPSTREAM_DIR" && git fetch origin main && \ - git log --oneline "$LAST_MERGE_COMMIT"..origin/main) || \ - echo " (could not generate log – the commit may have been rebased)" -echo "" - -# ── 5. Write env file for other scripts ────────────────────── -cat > "$ENV_FILE" <[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-java\.[0-9][0-9]*\)\{0,1\}|${VERSION}|g" README.md @@ -249,3 +249,4 @@ jobs: -f publish_as_latest=true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/weekly-upstream-sync.lock.yml b/.github/workflows/weekly-upstream-sync.lock.yml deleted file mode 100644 index c2bceee20..000000000 --- a/.github/workflows/weekly-upstream-sync.lock.yml +++ /dev/null @@ -1,1239 +0,0 @@ -# -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ -# | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ -# \_| |_/\__, |\___|_| |_|\__|_|\___| -# __/ | -# _ _ |___/ -# | | | | / _| | -# | | | | ___ _ __ _ __| |_| | _____ ____ -# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| -# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ -# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ -# -# 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 -# 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. -# -# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"fc14b09206c7aeafcd52c843adce996a1c14cf15875f9b647ef71f631b3b296e","compiler_version":"v0.51.6"} - -name: "Weekly Upstream Sync Agentic Workflow" -"on": - schedule: - - cron: "39 8 * * 2" - # Friendly format: weekly (scattered) - workflow_dispatch: - -permissions: {} - -concurrency: - group: "gh-aw-${{ github.workflow }}" - -run-name: "Weekly Upstream Sync Agentic Workflow" - -jobs: - activation: - runs-on: ubuntu-slim - permissions: - contents: read - 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@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: - GH_AW_WORKFLOW_FILE: "weekly-upstream-sync.lock.yml" - 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/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 - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - issues: read - concurrency: - group: "gh-aw-copilot-${{ github.workflow }}" - env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GH_AW_ASSETS_ALLOWED_EXTS: "" - GH_AW_ASSETS_BRANCH: "" - GH_AW_ASSETS_MAX_SIZE_KB: 0 - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - GH_AW_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: ${{ needs.activation.outputs.model }} - output: ${{ steps.collect_output.outputs.output }} - output_types: ${{ steps.collect_output.outputs.output_types }} - steps: - - name: Setup Scripts - uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 - with: - destination: /opt/gh-aw/actions - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: Create gh-aw temp directory - run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - 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" - echo "Git configured with standard GitHub Actions identity" - - name: Checkout PR branch - id: checkout-pr - if: | - (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 }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_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/checkout_pr_branch.cjs'); - await main(); - - name: Install GitHub Copilot CLI - 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.23.0 - - name: Determine automatic lockdown mode for GitHub MCP Server - id: determine-automatic-lockdown - 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.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 << '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.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "body": { - "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.", - "type": "string" - }, - "labels": { - "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.", - "items": { - "type": "string" - }, - "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_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 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": { - "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.", - "type": "string" - } - }, - "required": [ - "title", - "body" - ], - "type": "object" - }, - "name": "create_issue" - }, - { - "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": { - "body": { - "description": "Closing comment explaining why the issue is being closed and summarizing any resolution, workaround, or conclusion.", - "type": "string" - }, - "issue_number": { - "description": "Issue number to close. This is the numeric ID from the GitHub URL (e.g., 901 in github.com/owner/repo/issues/901). If omitted, closes the issue that triggered this workflow (requires an issue event trigger).", - "type": [ - "number", - "string" - ] - } - }, - "required": [ - "body" - ], - "type": "object" - }, - "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. 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. 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 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" - } - }, - "required": [ - "body" - ], - "type": "object" - }, - "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\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "agent": { - "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", - "type": "string" - }, - "issue_number": { - "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 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" - }, - "name": "assign_to_agent" - }, - { - "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "alternatives": { - "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", - "type": "string" - }, - "reason": { - "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", - "type": "string" - }, - "tool": { - "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", - "type": "string" - } - }, - "required": [ - "reason" - ], - "type": "object" - }, - "name": "missing_tool" - }, - { - "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "message": { - "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", - "type": "string" - } - }, - "required": [ - "message" - ], - "type": "object" - }, - "name": "noop" - }, - { - "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "alternatives": { - "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", - "type": "string" - }, - "context": { - "description": "Additional context about the missing data or where it should come from (max 256 characters).", - "type": "string" - }, - "data_type": { - "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", - "type": "string" - }, - "reason": { - "description": "Explanation of why this data is needed to complete the task (max 256 characters).", - "type": "string" - } - }, - "required": [], - "type": "object" - }, - "name": "missing_data" - } - ] - GH_AW_SAFE_OUTPUTS_TOOLS_EOF - cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 - } - } - }, - "assign_to_agent": { - "defaultMax": 1, - "fields": { - "agent": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "issue_number": { - "issueNumberOrTemporaryId": true - }, - "pull_number": { - "optionalPositiveInteger": true - }, - "pull_request_repo": { - "type": "string", - "maxLength": 256 - }, - "repo": { - "type": "string", - "maxLength": 256 - } - }, - "customValidation": "requiresOneOf:issue_number,pull_number" - }, - "close_issue": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "issue_number": { - "optionalPositiveInteger": true - }, - "repo": { - "type": "string", - "maxLength": 256 - } - } - }, - "create_issue": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "parent": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "temporary_id": { - "type": "string" - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "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": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - } - } - } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_EOF - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash /opt/gh-aw/actions/start_safe_outputs_server.sh - - - name: Start MCP Gateway - id: start-mcp-gateway - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} - GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -eo pipefail - mkdir -p /tmp/gh-aw/mcp-config - - # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="80" - export MCP_GATEWAY_DOMAIN="host.docker.internal" - MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${MCP_GATEWAY_API_KEY}" - 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 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 << 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.31.0", - "env": { - "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", - "GITHUB_READ_ONLY": "1", - "GITHUB_TOOLSETS": "context,repos,issues" - } - }, - "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" - } - } - }, - "gateway": { - "port": $MCP_GATEWAY_PORT, - "domain": "${MCP_GATEWAY_DOMAIN}", - "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" - } - } - GH_AW_MCP_CONFIG_EOF - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - 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 - id: agentic_execution - # Copilot CLI tool arguments (sorted): - timeout-minutes: 20 - run: | - set -o pipefail - # 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 }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json - 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 - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - 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" - echo "Git configured with standard GitHub Actions identity" - - name: Copy Copilot session state files to logs - if: always() - continue-on-error: true - run: | - # Copy Copilot session state files to logs folder for artifact collection - # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them - SESSION_STATE_DIR="$HOME/.copilot/session-state" - LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" - - if [ -d "$SESSION_STATE_DIR" ]; then - echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" - mkdir -p "$LOGS_DIR" - cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true - echo "Session state files copied successfully" - else - echo "No session-state directory found at $SESSION_STATE_DIR" - fi - - name: Stop MCP Gateway - if: always() - continue-on-error: true - env: - MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} - run: | - bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" - - name: Redact secrets in logs - if: always() - 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/redact_secrets.cjs'); - await main(); - env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload Safe Outputs - if: always() - 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 }} - 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 }} - 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/collect_ndjson_output.cjs'); - await main(); - - name: Upload sanitized agent output - if: always() && env.GH_AW_AGENT_OUTPUT - 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - with: - name: agent_outputs - path: | - /tmp/gh-aw/sandbox/agent/logs/ - /tmp/gh-aw/redacted-urls.log - if-no-files-found: ignore - - name: Parse agent logs for step summary - if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ - 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_copilot_log.cjs'); - await main(); - - name: Parse MCP Gateway logs for step summary - if: always() - 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_mcp_gateway_log.cjs'); - await main(); - - name: Print firewall logs - if: always() - continue-on-error: true - env: - AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs - run: | - # 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 - # 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - with: - name: agent-artifacts - path: | - /tmp/gh-aw/aw-prompts/prompt.txt - /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 - - safe_outputs - if: (always()) && (needs.agent.result != 'skipped') - runs-on: ubuntu-slim - permissions: - contents: read - discussions: write - issues: write - pull-requests: write - outputs: - noop_message: ${{ steps.noop.outputs.noop_message }} - tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} - total_count: ${{ steps.missing_tool.outputs.total_count }} - steps: - - name: Setup Scripts - 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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - name: agent-output - path: /tmp/gh-aw/safeoutputs/ - - name: Setup agent output environment variable - run: | - mkdir -p /tmp/gh-aw/safeoutputs/ - find "/tmp/gh-aw/safeoutputs/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - - name: Process No-Op Messages - id: noop - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - 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 }} - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/noop.cjs'); - await main(); - - name: Record Missing Tool - id: missing_tool - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" - 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/missing_tool.cjs'); - await main(); - - name: Handle Agent Failure - id: handle_agent_failure - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" - 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.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: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); - await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Upstream Sync Agentic Workflow" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "false" - 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/handle_noop_message.cjs'); - await main(); - - safe_outputs: - needs: agent - if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') - runs-on: ubuntu-slim - permissions: - contents: read - discussions: write - issues: write - 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" - outputs: - 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@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 - with: - destination: /opt/gh-aw/actions - - name: Download agent output artifact - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - name: agent-output - path: /tmp/gh-aw/safeoutputs/ - - name: Setup agent output environment variable - run: | - mkdir -p /tmp/gh-aw/safeoutputs/ - find "/tmp/gh-aw/safeoutputs/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - - name: Process Safe Outputs - id: process_safe_outputs - 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: - 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/safe_output_handler_manager.cjs'); - await main(); - - name: Assign Copilot to created issues - if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != '' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }} - with: - github-token: ${{ secrets.GH_AW_AGENT_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/assign_copilot_to_created_issues.cjs'); - await main(); - - 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: - github-token: ${{ secrets.GH_AW_AGENT_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/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 deleted file mode 100644 index 0aaff8d1e..000000000 --- a/.github/workflows/weekly-upstream-sync.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -description: | - Weekly upstream sync workflow. Checks for new commits in the official - Copilot SDK (github/copilot-sdk) and assigns to Copilot to port changes. - -on: - schedule: weekly - workflow_dispatch: - -permissions: - contents: read - actions: read - issues: read - -network: - allowed: - - defaults - - github - -tools: - github: - toolsets: [context, repos, issues] - -safe-outputs: - create-issue: - title-prefix: "[upstream-sync] " - assignees: [copilot] - labels: [upstream-sync] - expires: 6 - close-issue: - required-labels: [upstream-sync] - target: "*" - max: 10 - add-comment: - target: "*" - max: 10 - assign-to-agent: - name: "copilot" - model: "claude-opus-4.6" - target: "*" - noop: - report-as-issue: false ---- -# Weekly Upstream Sync - -You are an automation agent that detects new upstream changes and creates GitHub issues. You do **NOT** perform any code merges, edits, or pushes. Do **NOT** invoke any skills (especially `agentic-merge-upstream`). Your only job is to check for changes and use safe-output tools to create or close issues. - -## Instructions - -Follow these steps exactly: - -### Step 1: Read `.lastmerge` - -Read the file `.lastmerge` in the repository root. It contains the SHA of the last upstream commit that was merged into this Java SDK. - -### Step 2: Check for upstream changes - -Clone the upstream repository and compare commits: - -```bash -LAST_MERGE=$(cat .lastmerge) -git clone --quiet https://github.com/github/copilot-sdk.git /tmp/gh-aw/agent/upstream -cd /tmp/gh-aw/agent/upstream -UPSTREAM_HEAD=$(git rev-parse HEAD) -``` - -If `LAST_MERGE` equals `UPSTREAM_HEAD`, there are **no new changes**. Go to Step 3a. - -If they differ, count the new commits and generate a summary: - -```bash -COMMIT_COUNT=$(git rev-list --count "$LAST_MERGE".."$UPSTREAM_HEAD") -SUMMARY=$(git log --oneline "$LAST_MERGE".."$UPSTREAM_HEAD" | head -20) -``` - -Go to Step 3b. - -### Step 3a: No changes detected - -1. Search for any open issues with the `upstream-sync` label using the GitHub MCP tools. -2. If there are open `upstream-sync` issues, close each one using the `close_issue` safe-output tool with a comment: "No new upstream changes detected. The Java SDK is up to date. Closing." -3. Call the `noop` safe-output tool with message: "No new upstream changes since last merge ()." -4. **Stop here.** Do not proceed further. - -### Step 3b: Changes detected - -1. Search for any open issues with the `upstream-sync` label using the GitHub MCP tools. -2. Close each existing open `upstream-sync` issue using the `close_issue` safe-output tool with a comment: "Superseded by a newer upstream sync check." -3. Create a new issue using the `create_issue` safe-output tool with: - - **Title:** `Upstream sync: new commits ()` - - **Body:** Include the following information: - ``` - ## Automated Upstream Sync - - There are **** new commits in the [official Copilot SDK](https://github.com/github/copilot-sdk) since the last merge. - - - **Last merged commit:** [``](https://github.com/github/copilot-sdk/commit/) - - **Upstream HEAD:** [``](https://github.com/github/copilot-sdk/commit/) - - ### Recent upstream commits - - ``` - - ``` - - ### Instructions - - Follow the [agentic-merge-upstream](.github/prompts/agentic-merge-upstream.prompt.md) prompt to port these changes to the Java SDK. - ``` -4. After creating the issue, use the `assign_to_agent` safe-output tool to assign Copilot to the newly created issue. - -## Important constraints - -- **Do NOT edit any files**, create branches, or push code. -- **Do NOT invoke any skills** such as `agentic-merge-upstream` or `commit-as-pull-request`. -- **Do NOT attempt to merge or port upstream changes.** That is done by a separate agent that picks up the issue you create. -- You **MUST** call at least one safe-output tool (`create_issue`, `close_issue`, `noop`, etc.) before finishing. diff --git a/.github/workflows/weekly-upstream-sync.yml b/.github/workflows/weekly-upstream-sync.yml deleted file mode 100644 index 5281fa032..000000000 --- a/.github/workflows/weekly-upstream-sync.yml +++ /dev/null @@ -1,181 +0,0 @@ -name: "Weekly Upstream Sync" - -on: - schedule: - # Every Monday at 10:00 UTC (5:00 AM EST / 6:00 AM EDT) - - cron: '0 10 * * 1' - workflow_dispatch: - -permissions: - contents: write - issues: write - pull-requests: write - -env: - # Used for `gh` CLI operations to create/close/comment issues. Must be a PAT with repo permissions because the default - GH_AW_AGENT_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN }} - GH_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN }} - -jobs: - - check-and-sync: - name: "Check upstream & trigger Copilot merge" - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Check for upstream changes - id: check - run: | - LAST_MERGE=$(cat .lastmerge) - echo "Last merged commit: $LAST_MERGE" - - git clone --quiet https://github.com/github/copilot-sdk.git /tmp/upstream - cd /tmp/upstream - - UPSTREAM_HEAD=$(git rev-parse HEAD) - echo "Upstream HEAD: $UPSTREAM_HEAD" - - echo "last_merge=$LAST_MERGE" >> "$GITHUB_OUTPUT" - - if [ "$LAST_MERGE" = "$UPSTREAM_HEAD" ]; then - echo "No new upstream changes since last merge." - echo "has_changes=false" >> "$GITHUB_OUTPUT" - else - COMMIT_COUNT=$(git rev-list --count "$LAST_MERGE".."$UPSTREAM_HEAD") - echo "Found $COMMIT_COUNT new upstream commits." - echo "has_changes=true" >> "$GITHUB_OUTPUT" - echo "commit_count=$COMMIT_COUNT" >> "$GITHUB_OUTPUT" - echo "upstream_head=$UPSTREAM_HEAD" >> "$GITHUB_OUTPUT" - - # Generate a short summary of changes - SUMMARY=$(git log --oneline "$LAST_MERGE".."$UPSTREAM_HEAD" | head -20) - { - echo "summary<> "$GITHUB_OUTPUT" - fi - - - name: Close previous upstream-sync issues - if: steps.check.outputs.has_changes == 'true' - run: | - # Find all open issues with the upstream-sync label - OPEN_ISSUES=$(gh issue list \ - --repo "${{ github.repository }}" \ - --label "upstream-sync" \ - --state open \ - --json number \ - --jq '.[].number') - - for ISSUE_NUM in $OPEN_ISSUES; do - echo "Closing superseded issue #${ISSUE_NUM}" - gh issue comment "$ISSUE_NUM" \ - --repo "${{ github.repository }}" \ - --body "Superseded by a newer upstream sync issue. Closing this one." - gh issue close "$ISSUE_NUM" \ - --repo "${{ github.repository }}" \ - --reason "not planned" - done - - - name: Close stale upstream-sync issues (no changes) - if: steps.check.outputs.has_changes == 'false' - run: | - OPEN_ISSUES=$(gh issue list \ - --repo "${{ github.repository }}" \ - --label "upstream-sync" \ - --state open \ - --json number \ - --jq '.[].number') - - for ISSUE_NUM in $OPEN_ISSUES; do - echo "Closing stale issue #${ISSUE_NUM} — upstream is up to date" - gh issue comment "$ISSUE_NUM" \ - --repo "${{ github.repository }}" \ - --body "No new upstream changes detected. The Java SDK is up to date. Closing." - gh issue close "$ISSUE_NUM" \ - --repo "${{ github.repository }}" \ - --reason "completed" - done - - - name: Create issue and assign to Copilot - id: create-issue - if: steps.check.outputs.has_changes == 'true' - env: - SUMMARY: ${{ steps.check.outputs.summary }} - run: | - COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" - LAST_MERGE="${{ steps.check.outputs.last_merge }}" - UPSTREAM_HEAD="${{ steps.check.outputs.upstream_head }}" - DATE=$(date -u +"%Y-%m-%d") - REPO="${{ github.repository }}" - - BODY="## Automated Upstream Sync - - There are **${COMMIT_COUNT}** new commits in the [official Copilot SDK](https://github.com/github/copilot-sdk) since the last merge. - - - **Last merged commit:** [\`${LAST_MERGE}\`](https://github.com/github/copilot-sdk/commit/${LAST_MERGE}) - - **Upstream HEAD:** [\`${UPSTREAM_HEAD}\`](https://github.com/github/copilot-sdk/commit/${UPSTREAM_HEAD}) - - ### Recent upstream commits - - \`\`\` - ${SUMMARY} - \`\`\` - - ### Instructions - - Follow the [agentic-merge-upstream](.github/prompts/agentic-merge-upstream.prompt.md) prompt to port these changes to the Java SDK." - - # Create the issue and assign to Copilot coding agent - ISSUE_URL=$(gh issue create \ - --repo "$REPO" \ - --title "Upstream sync: ${COMMIT_COUNT} new commits (${DATE})" \ - --body "$BODY" \ - --label "upstream-sync" \ - --assignee "copilot-swe-agent") - - echo "issue_url=$ISSUE_URL" >> "$GITHUB_OUTPUT" - echo "✅ Issue created and assigned to Copilot coding agent: $ISSUE_URL" - - - name: Summary - if: always() - env: - SUMMARY: ${{ steps.check.outputs.summary }} - run: | - HAS_CHANGES="${{ steps.check.outputs.has_changes }}" - COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" - LAST_MERGE="${{ steps.check.outputs.last_merge }}" - UPSTREAM_HEAD="${{ steps.check.outputs.upstream_head }}" - ISSUE_URL="${{ steps.create-issue.outputs.issue_url }}" - - { - echo "## Weekly Upstream Sync" - echo "" - if [ "$HAS_CHANGES" = "true" ]; then - echo "### ✅ New upstream changes detected" - echo "" - echo "| | |" - echo "|---|---|" - echo "| **New commits** | ${COMMIT_COUNT} |" - echo "| **Last merged** | \`${LAST_MERGE:0:12}\` |" - echo "| **Upstream HEAD** | \`${UPSTREAM_HEAD:0:12}\` |" - echo "" - echo "An issue has been created and assigned to the Copilot coding agent: " - echo " -> ${ISSUE_URL}" - echo "" - echo "### Recent upstream commits" - echo "" - echo '```' - echo "$SUMMARY" - echo '```' - else - echo "### ⏭️ No changes" - echo "" - echo "The Java SDK is already up to date with the upstream Copilot SDK." - echo "" - echo "**Last merged commit:** \`${LAST_MERGE:0:12}\`" - fi - } >> "$GITHUB_STEP_SUMMARY" diff --git a/CHANGELOG.md b/CHANGELOG.md index 38f6e6589..130bc5105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,17 +8,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] -> **Upstream sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1) +> **Reference implementation sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1) ## [0.2.2-java.1] - 2026-04-07 -> **Upstream sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1) +> **Reference implementation sync:** [`github/copilot-sdk@c3fa6cb`](https://github.com/github/copilot-sdk/commit/c3fa6cbfb83d4a20b7912b1a17013d48f5a277a1) ### Added -- Slash commands — register `/command` handlers invoked from the CLI TUI via `SessionConfig.setCommands()` (upstream: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757)) +- Slash commands — register `/command` handlers invoked from the CLI TUI via `SessionConfig.setCommands()` (reference implementation: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757)) - `CommandDefinition`, `CommandContext`, `CommandHandler`, `CommandWireDefinition` — types for defining and handling slash commands - `CommandExecuteEvent` — event dispatched when a registered slash command is executed -- Elicitation (UI dialogs) — incoming handler via `SessionConfig.setOnElicitationRequest()` and outgoing convenience methods via `session.getUi()` (upstream: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757)) +- Elicitation (UI dialogs) — incoming handler via `SessionConfig.setOnElicitationRequest()` and outgoing convenience methods via `session.getUi()` (reference implementation: [`f7fd757`](https://github.com/github/copilot-sdk/commit/f7fd757)) - `ElicitationContext`, `ElicitationHandler`, `ElicitationParams`, `ElicitationResult`, `ElicitationResultAction`, `ElicitationSchema`, `InputOptions` — types for elicitation - `ElicitationRequestedEvent` — event dispatched when an elicitation request is received - `SessionUiApi` — convenience API on `session.getUi()` for `confirm()`, `select()`, `input()`, and `elicitation()` calls @@ -35,27 +35,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [0.2.1-java.1] - 2026-04-02 -> **Upstream sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15) +> **Reference implementation sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15) ## [0.2.1-java.0] - 2026-03-26 -> **Upstream sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15) +> **Reference implementation sync:** [`github/copilot-sdk@4088739`](https://github.com/github/copilot-sdk/commit/40887393a9e687dacc141a645799441b0313ff15) ### Added -- `UnknownSessionEvent` — forward-compatible placeholder for event types not yet known to the SDK; unknown events are now dispatched to handlers instead of being silently dropped (upstream: [`d82fd62`](https://github.com/github/copilot-sdk/commit/d82fd62)) -- `PermissionRequestResultKind.NO_RESULT` — new constant that signals the handler intentionally abstains from answering a permission request, leaving it unanswered for another client (upstream: [`df59a0e`](https://github.com/github/copilot-sdk/commit/df59a0e)) -- `ToolDefinition.skipPermission` field and `ToolDefinition.createSkipPermission()` factory — marks a tool to skip the permission prompt (upstream: [`10c4d02`](https://github.com/github/copilot-sdk/commit/10c4d02)) -- `SystemMessageMode.CUSTOMIZE` — new enum value for fine-grained system prompt customization (upstream: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) -- `SectionOverrideAction` enum — specifies the operation on a system prompt section (replace, remove, append, prepend, transform) (upstream: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) -- `SectionOverride` class — describes how one section of the system prompt should be modified, with optional transform callback (upstream: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) -- `SystemPromptSections` constants — well-known section identifier strings for use with CUSTOMIZE mode (upstream: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) -- `SystemMessageConfig.setSections(Map)` — section-level overrides for CUSTOMIZE mode (upstream: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) -- `systemMessage.transform` RPC handler — the SDK now registers a handler that invokes transform callbacks registered in the session config (upstream: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) -- `CopilotSession.setModel(String, String)` — new overload that accepts an optional reasoning effort level (upstream: [`ea90f07`](https://github.com/github/copilot-sdk/commit/ea90f07)) +- `UnknownSessionEvent` — forward-compatible placeholder for event types not yet known to the SDK; unknown events are now dispatched to handlers instead of being silently dropped (reference implementation: [`d82fd62`](https://github.com/github/copilot-sdk/commit/d82fd62)) +- `PermissionRequestResultKind.NO_RESULT` — new constant that signals the handler intentionally abstains from answering a permission request, leaving it unanswered for another client (reference implementation: [`df59a0e`](https://github.com/github/copilot-sdk/commit/df59a0e)) +- `ToolDefinition.skipPermission` field and `ToolDefinition.createSkipPermission()` factory — marks a tool to skip the permission prompt (reference implementation: [`10c4d02`](https://github.com/github/copilot-sdk/commit/10c4d02)) +- `SystemMessageMode.CUSTOMIZE` — new enum value for fine-grained system prompt customization (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) +- `SectionOverrideAction` enum — specifies the operation on a system prompt section (replace, remove, append, prepend, transform) (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) +- `SectionOverride` class — describes how one section of the system prompt should be modified, with optional transform callback (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) +- `SystemPromptSections` constants — well-known section identifier strings for use with CUSTOMIZE mode (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) +- `SystemMessageConfig.setSections(Map)` — section-level overrides for CUSTOMIZE mode (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) +- `systemMessage.transform` RPC handler — the SDK now registers a handler that invokes transform callbacks registered in the session config (reference implementation: [`005b780`](https://github.com/github/copilot-sdk/commit/005b780)) +- `CopilotSession.setModel(String, String)` — new overload that accepts an optional reasoning effort level (reference implementation: [`ea90f07`](https://github.com/github/copilot-sdk/commit/ea90f07)) - `CopilotSession.log(String, String, Boolean, String)` — new overload with an optional `url` parameter (minor addition) -- `BlobAttachment` class — inline base64-encoded binary attachment for messages (e.g., images) (upstream: [`698b259`](https://github.com/github/copilot-sdk/commit/698b259)) +- `BlobAttachment` class — inline base64-encoded binary attachment for messages (e.g., images) (reference implementation: [`698b259`](https://github.com/github/copilot-sdk/commit/698b259)) - `MessageAttachment` sealed interface — type-safe base for all attachment types (`Attachment`, `BlobAttachment`), with Jackson polymorphic serialization support -- `TelemetryConfig` class — OpenTelemetry configuration for the CLI server; set on `CopilotClientOptions.setTelemetry()` (upstream: [`f2d21a0`](https://github.com/github/copilot-sdk/commit/f2d21a0)) -- `CopilotClientOptions.setTelemetry(TelemetryConfig)` — enables OpenTelemetry instrumentation in the CLI server (upstream: [`f2d21a0`](https://github.com/github/copilot-sdk/commit/f2d21a0)) +- `TelemetryConfig` class — OpenTelemetry configuration for the CLI server; set on `CopilotClientOptions.setTelemetry()` (reference implementation: [`f2d21a0`](https://github.com/github/copilot-sdk/commit/f2d21a0)) +- `CopilotClientOptions.setTelemetry(TelemetryConfig)` — enables OpenTelemetry instrumentation in the CLI server (reference implementation: [`f2d21a0`](https://github.com/github/copilot-sdk/commit/f2d21a0)) ### Changed @@ -70,61 +70,61 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [0.1.32-java.0] - 2026-03-17 -> **Upstream sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83) +> **Reference implementation sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83) ## [1.0.11] - 2026-03-12 -> **Upstream sync:** [`github/copilot-sdk@062b61c`](https://github.com/github/copilot-sdk/commit/062b61c8aa63b9b5d45fa1d7b01723e6660ffa83) +> **Reference implementation 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)) +- `CopilotClientOptions.setOnListModels(Supplier>>)` — custom handler for `listModels()` used in BYOK mode to return models from a custom provider instead of querying the CLI (reference implementation: [`e478657`](https://github.com/github/copilot-sdk/commit/e478657)) +- `SessionConfig.setAgent(String)` — pre-selects a custom agent by name when creating a session (reference implementation: [`7766b1a`](https://github.com/github/copilot-sdk/commit/7766b1a)) +- `ResumeSessionConfig.setAgent(String)` — pre-selects a custom agent by name when resuming a session (reference implementation: [`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 (reference implementation: [`4125fe7`](https://github.com/github/copilot-sdk/commit/4125fe7)) +- `ResumeSessionConfig.setOnEvent(Consumer)` — registers an event handler before the `session.resume` RPC is issued (reference implementation: [`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`) (reference implementation: [`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 (reference implementation: [`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)) +- **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 (reference implementation: [`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 (reference implementation: [`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) +> **Reference implementation 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) +- `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 (reference implementation: [`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 (reference implementation: [`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 (reference implementation: [`f843c80`](https://github.com/github/copilot-sdk/commit/f843c80)) +- `CopilotSession.listAgents()` — lists custom agents available for selection (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `CopilotSession.getCurrentAgent()` — gets the currently selected custom agent (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `CopilotSession.selectAgent(String)` — selects a custom agent for the session (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `CopilotSession.deselectAgent()` — deselects the current custom agent (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `CopilotSession.compact()` — triggers immediate session context compaction (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- `AgentInfo` — new JSON type representing a custom agent with `name`, `displayName`, and `description` (reference implementation: [`9d998fb`](https://github.com/github/copilot-sdk/commit/9d998fb)) +- New event types: `SessionTaskCompleteEvent` (`session.task_complete`), `AssistantStreamingDeltaEvent` (`assistant.streaming_delta`), `SubagentDeselectedEvent` (`subagent.deselected`) (reference implementation: 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)) +- E2E tests for custom tool permission approval and denial flows (reference implementation: [`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) +- **Breaking:** `createSession(SessionConfig)` now requires a non-null `onPermissionRequest` handler; throws `IllegalArgumentException` if not provided (reference implementation: [`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 (reference implementation: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4)) +- **Breaking:** The no-arg `createSession()` and `resumeSession(String)` overloads were removed (reference implementation: [`279f6c4`](https://github.com/github/copilot-sdk/commit/279f6c4)) +- `AssistantMessageDeltaEvent` data: `totalResponseSizeBytes` field moved to new `AssistantStreamingDeltaEvent` (reference implementation: 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)) +- Permission checks now also apply to SDK-registered custom tools, invoking the `onPermissionRequest` handler with `kind="custom-tool"` before executing tools (reference implementation: [`388f2f3`](https://github.com/github/copilot-sdk/commit/388f2f3)) ## [1.0.9] - 2026-02-16 -> **Upstream sync:** [`github/copilot-sdk@e40d57c`](https://github.com/github/copilot-sdk/commit/e40d57c86e18b495722adbf42045288c03924342) +> **Reference implementation sync:** [`github/copilot-sdk@e40d57c`](https://github.com/github/copilot-sdk/commit/e40d57c86e18b495722adbf42045288c03924342) ### Added #### Cookbook with Practical Recipes @@ -191,7 +191,7 @@ session.on(SessionContextChangedEvent.class, event -> { ## [1.0.8] - 2026-02-08 -> **Upstream sync:** [`github/copilot-sdk@05e3c46`](https://github.com/github/copilot-sdk/commit/05e3c46c8c23130c9c064dc43d00ec78f7a75eab) +> **Reference implementation sync:** [`github/copilot-sdk@05e3c46`](https://github.com/github/copilot-sdk/commit/05e3c46c8c23130c9c064dc43d00ec78f7a75eab) ### Added @@ -347,7 +347,7 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` ### Changed - Enhanced permission request handling with graceful error recovery -- Updated test harness integration to clone from upstream SDK +- Updated test harness integration to clone from reference implementation SDK - Improved logging for session events and user input requests ### Fixed @@ -366,8 +366,8 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` ### Changed -- Merged upstream SDK changes (commit 87ff5510) -- Added agentic-merge-upstream Claude skill for tracking upstream changes +- Merged reference implementation SDK changes (commit 87ff5510) +- Added agentic-merge-reference-impl Claude skill for tracking reference implementation changes ### Fixed @@ -376,7 +376,7 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` - NPM dependency installation in CI workflow - Enhanced error handling in permission request processing - Checkstyle and Maven Resources Plugin version updates -- Test harness CLI installation to match upstream version +- Test harness CLI installation to match reference implementation version ## [1.0.4] - 2026-01-27 @@ -394,7 +394,7 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` - Refactored tool argument handling for improved type safety - Optimized event listener registration in examples - Enhanced site navigation with documentation links -- Merged upstream SDK changes from commit f902b76 +- Merged reference implementation SDK changes from commit f902b76 ### Fixed @@ -410,7 +410,7 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` - MCP Servers documentation and integration examples - Infinite sessions documentation section - Versioned documentation template with version selector -- Guidelines for porting upstream SDK changes to Java +- Guidelines for porting reference implementation SDK changes to Java - Configuration for automatically generated release notes ### Changed @@ -519,3 +519,4 @@ New types: `GetForegroundSessionResponse`, `SetForegroundSessionResponse` [1.0.2]: https://github.com/github/copilot-sdk-java/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/github/copilot-sdk-java/compare/1.0.0...v1.0.1 [1.0.0]: https://github.com/github/copilot-sdk-java/releases/tag/1.0.0 + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c85671bf..f86976c01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ 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 **GitHub 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. +This repository contains the **GitHub 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 reference implementation 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). @@ -65,3 +65,4 @@ Here are a few things you can do that will increase the likelihood of your pull - [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/README.md b/README.md index 34c04ace7..d425fa4eb 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ jbang https://github.com/github/copilot-sdk-java/blob/latest/jbang-example.java ## CI/CD Workflows -This project uses several GitHub Actions workflows for building, testing, releasing, and syncing with the upstream SDK. +This project uses several GitHub Actions workflows for building, testing, releasing, and syncing with the reference implementation SDK. See [WORKFLOWS.md](docs/WORKFLOWS.md) for a full overview and details on each workflow. @@ -212,3 +212,4 @@ MIT — see [LICENSE](LICENSE) for details. [![Star History Chart](https://api.star-history.com/svg?repos=github/copilot-sdk-java&type=Date)](https://www.star-history.com/#github/copilot-sdk-java&Date) ⭐ Drop a star if you find this useful! + diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md index 57ec34f71..1cbb22b36 100644 --- a/docs/WORKFLOWS.md +++ b/docs/WORKFLOWS.md @@ -21,9 +21,9 @@ Runs on every push to `main`, on pull requests, and in merge groups. Also runs w Steps: 1. Checks code formatting with Spotless -2. Compiles the SDK and clones the upstream test harness +2. Compiles the SDK and clones the reference implementation test harness 3. Verifies Javadoc generation -4. Installs the Copilot CLI from the cloned upstream SDK +4. Installs the Copilot CLI from the cloned reference implementation SDK 5. Runs the full test suite with `mvn verify` 6. Uploads test results (JaCoCo + Surefire) as artifacts for site generation @@ -98,3 +98,4 @@ Sets up: - JDK 17 (Temurin) - `gh-aw` CLI extension - Maven cache + diff --git a/docs/adr/adr-001-semver-pre-general-availability.md b/docs/adr/adr-001-semver-pre-general-availability.md index 44c9cf6db..33bdcc86c 100644 --- a/docs/adr/adr-001-semver-pre-general-availability.md +++ b/docs/adr/adr-001-semver-pre-general-availability.md @@ -20,8 +20,9 @@ We took an architectural decision to enable us to pursue investigating this poss 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. +To some extent, I would use qualifiers to mark a release as having some feature that is awaiting an reference implementation full release before it goes full ga, i.e you put out 0.1.46-virtualthreads.3 until reference implementation 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/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md b/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md index 7ef3d8ec8..6a0b54f22 100644 --- a/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md +++ b/docs/adr/adr-002-maven-version-and-reference-implementation-tracking.md @@ -6,7 +6,7 @@ Releases of this implementation track releases of the reference implementation. ## Considered Options -- Simple number qualifier (0.1.32-0, 0.1.32-1, ...) fails on a subtle but important point: 0.1.32-0 is treated identically to 0.1.32 by Maven (trailing zeros are normalized away), and bare numeric qualifiers are pre-release semantics. Your "first release" would sort before the upstream bare version. +- Simple number qualifier (0.1.32-0, 0.1.32-1, ...) fails on a subtle but important point: 0.1.32-0 is treated identically to 0.1.32 by Maven (trailing zeros are normalized away), and bare numeric qualifiers are pre-release semantics. Your "first release" would sort before the reference implementation bare version. - Java and number in the qualifier (0.1.32-java.N @@ -54,3 +54,4 @@ Everything looks healthy. Here's the status: ## Related work items - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2766089 + diff --git a/src/test/java/com/github/copilot/sdk/CommandsTest.java b/src/test/java/com/github/copilot/sdk/CommandsTest.java index baf26b39b..6bddbed28 100644 --- a/src/test/java/com/github/copilot/sdk/CommandsTest.java +++ b/src/test/java/com/github/copilot/sdk/CommandsTest.java @@ -25,7 +25,8 @@ * representation). * *

- * Ported from {@code CommandsTests.cs} in the upstream dotnet SDK. + * Ported from {@code CommandsTests.cs} in the reference implementation dotnet + * SDK. *

*/ class CommandsTest { diff --git a/src/test/java/com/github/copilot/sdk/ElicitationTest.java b/src/test/java/com/github/copilot/sdk/ElicitationTest.java index d6e2ac0b5..6b748b4cc 100644 --- a/src/test/java/com/github/copilot/sdk/ElicitationTest.java +++ b/src/test/java/com/github/copilot/sdk/ElicitationTest.java @@ -29,7 +29,8 @@ * Unit tests for the Elicitation feature and Session Capabilities. * *

- * Ported from {@code ElicitationTests.cs} in the upstream dotnet SDK. + * Ported from {@code ElicitationTests.cs} in the reference implementation + * dotnet SDK. *

*/ class ElicitationTest { diff --git a/src/test/java/com/github/copilot/sdk/HooksTest.java b/src/test/java/com/github/copilot/sdk/HooksTest.java index 05cd3292b..1278d082b 100644 --- a/src/test/java/com/github/copilot/sdk/HooksTest.java +++ b/src/test/java/com/github/copilot/sdk/HooksTest.java @@ -36,8 +36,8 @@ * *

* Note: Tests for userPromptSubmitted, sessionStart, and sessionEnd hooks are - * not included as they are not tested in the upstream .NET or Node.js SDKs and - * require test harness updates to properly invoke these hooks. + * not included as they are not tested in the reference implementation .NET or + * Node.js SDKs and require test harness updates to properly invoke these hooks. *

*/ public class HooksTest { From 406186b80d5ed1f5fef26e516a4caa1b1432fdfa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 19:26:14 +0000 Subject: [PATCH 43/47] Fix [reference-impl-hash] parameter naming in update-changelog.sh Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/dc42a537-067a-4251-bd14-0fb8fede94d9 Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .github/scripts/release/update-changelog.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/release/update-changelog.sh b/.github/scripts/release/update-changelog.sh index 926da5b3b..ff35dec72 100755 --- a/.github/scripts/release/update-changelog.sh +++ b/.github/scripts/release/update-changelog.sh @@ -2,13 +2,13 @@ set -e # Script to update CHANGELOG.md during release process -# Usage: ./update-changelog.sh [reference implementation-hash] +# Usage: ./update-changelog.sh [reference-impl-hash] # Example: ./update-changelog.sh 1.0.8 # Example: ./update-changelog.sh 1.0.8 05e3c46c8c23130c9c064dc43d00ec78f7a75eab if [ -z "$1" ]; then echo "Error: Version argument required" - echo "Usage: $0 [reference implementation-hash]" + echo "Usage: $0 [reference-impl-hash]" exit 1 fi From e3f246a1cd7a79507dc7dc9e8cbc815fe81110dc Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Thu, 16 Apr 2026 18:15:55 -0400 Subject: [PATCH 44/47] Ignore tilde files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ddb2508ba..f079a3d20 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ changebundle.txt* .classpath .project .settings +*~ From df6a925db1e400dee2eae6cd63a5238590fe79fc Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Thu, 16 Apr 2026 18:26:33 -0400 Subject: [PATCH 45/47] Address very important comment by copilot. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @Copilot wrote: > This file is runtime-imported by weekly-reference-impl-sync.lock.yml as the agent prompt, but its content is written as documentation rather than imperative agent instructions (e.g., it doesn’t tell the agent to read .lastmerge, clone, compare commits, and must call a safe-output tool). As a result, the agentic workflow may fail or behave unpredictably. Please restore the prior imperative, step-by-step instruction style (like the deleted weekly-upstream-sync.md), updated to the new reference-impl terminology, and ensure it explicitly requires calling create-issue/close-issue/noop as appropriate. --- .../workflows/weekly-reference-impl-sync.md | 134 +++++++----------- 1 file changed, 50 insertions(+), 84 deletions(-) diff --git a/.github/workflows/weekly-reference-impl-sync.md b/.github/workflows/weekly-reference-impl-sync.md index 7e72248e1..711f16ccb 100644 --- a/.github/workflows/weekly-reference-impl-sync.md +++ b/.github/workflows/weekly-reference-impl-sync.md @@ -41,111 +41,77 @@ safe-outputs: noop: report-as-issue: false --- -# Weekly Reference Implementation Sync Agentic Workflow -This document describes the `weekly-reference-impl-sync.yml` GitHub Actions workflow, which automates the detection of new changes in the official [Copilot SDK](https://github.com/github/copilot-sdk) and delegates the merge work to the Copilot coding agent. +# Weekly Reference Implementation Sync -## Overview +You are an automation agent that detects new reference implementation changes and creates GitHub issues. You do **NOT** perform any code merges, edits, or pushes. Do **NOT** invoke any skills (especially `agentic-merge-reference-impl`). Your only job is to check for changes and use safe-output tools to create or close issues. -The workflow runs on a **weekly schedule** (every Monday at 10:00 UTC) and can also be triggered manually. It does **not** perform the actual merge — instead, it detects reference implementation changes and creates a GitHub issue assigned to `copilot`, instructing the agent to follow the [agentic-merge-reference-impl](../prompts/agentic-merge-reference-impl.prompt.md) prompt to port the changes. +## Instructions -The agent must also create the Pull Request with the label `reference-impl-sync`. This allows the workflow to track the merge progress and avoid creating duplicate issues if the agent is still working on a previous sync. +Follow these steps exactly: -## Trigger +### Step 1: Read `.lastmerge` -| Trigger | Schedule | -|---|---| -| `schedule` | Every Monday at 10:00 UTC (`0 10 * * 1`) | -| `workflow_dispatch` | Manual trigger from the Actions tab | +Read the file `.lastmerge` in the repository root. It contains the SHA of the last reference implementation commit that was merged into this Java SDK. -## Workflow Steps +### Step 2: Check for reference implementation changes -### 1. Checkout repository +Clone the reference implementation repository and compare commits: -Checks out the repo to read the `.lastmerge` file, which contains the SHA of the last reference implementation commit that was merged into the Java SDK. - -### 2. Check for reference implementation changes - -- Reads the last merged commit hash from `.lastmerge` -- Clones the reference implementation `github/copilot-sdk` repository -- Compares `.lastmerge` against reference implementation `HEAD` -- If they match: sets `has_changes=false` -- If they differ: counts new commits, generates a summary (up to 20 most recent), and sets outputs (`commit_count`, `reference_impl_head`, `last_merge`, `summary`) - -### 3. Close previous reference-impl-sync issues (when changes found) - -**Condition:** `has_changes == true` +```bash +LAST_MERGE=$(cat .lastmerge) +git clone --quiet https://github.com/github/copilot-sdk.git /tmp/gh-aw/agent/refererce-impl +cd /tmp/gh-aw/agent/refererce-impl +REFERENCE_IMPL_HEAD=$(git rev-parse HEAD) +``` -Before creating a new issue, closes any existing open issues with the `reference-impl-sync` label. This prevents stale issues from accumulating when previous sync attempts were incomplete or superseded. Each closed issue receives a comment explaining it was superseded. +If `LAST_MERGE` equals `REFERENCE_IMPL_HEAD`, there are **no new changes**. Go to Step 3a. -### 4. Close stale reference-impl-sync issues (when no changes found) +If they differ, count the new commits and generate a summary: -**Condition:** `has_changes == false` +```bash +COMMIT_COUNT=$(git rev-list --count "$LAST_MERGE".."$REFERENCE_IMPL_HEAD") +SUMMARY=$(git log --oneline "$LAST_MERGE".."$REFERENCE_IMPL_HEAD" | head -20) +``` -If the reference implementation is already up to date, closes any lingering open `reference-impl-sync` issues with a comment noting that no changes were detected. This handles the case where a previous issue was created but the changes were merged manually (updating `.lastmerge`) before the agent completed. +Go to Step 3b. -### 5. Create issue and assign to Copilot +### Step 3a: No changes detected -**Condition:** `has_changes == true` +1. Search for any open issues with the `reference-impl-sync` label using the GitHub MCP tools. +2. If there are open `reference-impl-sync` labeled issues, close each one using the `close_issue` safe-output tool with a comment: "No new reference implementation changes detected. The Java SDK is up to date. Closing." +3. Call the `noop` safe-output tool with message: "No new reference implementation changes since last merge ()." +4. **Stop here.** Do not proceed further. -Creates a new GitHub issue with: +### Step 3b: Changes detected -- **Title:** `Reference implementation sync: N new commits (YYYY-MM-DD)` -- **Label:** `reference-impl-sync` -- **Assignee:** `copilot` -- **Body:** Contains commit count, commit range links, a summary of recent commits, and a link to the merge prompt +1. Search for any open issues with the `refererce-impl-sync` label using the GitHub MCP tools. +2. Close each existing open `refererce-impl-sync` issue using the `close_issue` safe-output tool with a comment: "Superseded by a newer reference implementation sync check." +3. Create a new issue using the `create_issue` safe-output tool with: + - **Title:** `Reference Implementation sync: new commits ()` + - **Body:** Include the following information: + ``` + ## Automated Reference Implementation Sync -The Copilot coding agent picks up the issue, creates a branch and PR, then follows the merge prompt to port the changes. + There are **** new commits in the [official Copilot SDK](https://github.com/github/copilot-sdk) since the last merge. -### 6. Summary + - **Last merged commit:** [``](https://github.com/github/copilot-sdk/commit/) + - **Reference Implementation HEAD:** [``](https://github.com/github/copilot-sdk/commit/) -Writes a GitHub Actions step summary with: + ### Recent reference implementation commits -- Whether changes were detected -- Commit count and range -- Recent reference implementation commits -- Link to the created issue (if any) + ``` + + ``` -## Flow Diagram + ### Instructions -``` -┌─────────────────────┐ -│ Schedule / Manual │ -└──────────┬──────────┘ - │ - ▼ -┌──────────────────────────┐ -│ Read .lastmerge │ -│ Clone reference impl SDK │ -│ Compare commits │ -└──────────┬───────────────┘ - │ - ┌─────┴─────┐ - │ │ - changes? no changes - │ │ - ▼ ▼ -┌──────────┐ ┌──────────────────┐ -│ Close old│ │ Close stale │ -│ issues │ │ issues │ -└────┬─────┘ └──────────────────┘ - │ - ▼ -┌──────────────────────────┐ -│ Create issue assigned to │ -│ copilot │ -└──────────────────────────┘ - │ - ▼ -┌──────────────────────────┐ -│ Agent follows prompt to │ -│ port changes → PR │ -└──────────────────────────┘ -``` + Follow the [agentic-merge-reference-impl](.github/prompts/agentic-merge-reference-impl.prompt.md) prompt to port these changes to the Java SDK. + ``` +4. After creating the issue, use the `assign_to_agent` safe-output tool to assign Copilot to the newly created issue. -## Related Files +## Important constraints -| File | Purpose | -|---|---| -| `.lastmerge` | Stores the SHA of the last merged reference implementation commit | -| [agentic-merge-reference-impl.prompt.md](../prompts/agentic-merge-reference-impl.prompt.md) | Detailed instructions the Copilot agent follows to port changes | -| `.github/scripts/reference-impl-sync/` | Helper scripts used by the merge prompt | +- **Do NOT edit any files**, create branches, or push code. +- **Do NOT invoke any skills** such as `agentic-merge-agentic-merge-reference-impl` or `commit-as-pull-request`. +- **Do NOT attempt to merge or port reference implementation changes.** That is done by a separate agent that picks up the issue you create. +- You **MUST** call at least one safe-output tool (`create_issue`, `close_issue`, `noop`, etc.) before finishing. From 03cfc350f8cf23bdf918cf0a631dc2408f14153d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 23:02:30 +0000 Subject: [PATCH 46/47] Apply review suggestions: fix assignee, grammar, and refererce-impl typo Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/b635280e-2e95-4f3d-a5d9-a261f99c6e74 Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .github/workflows/weekly-reference-impl-sync.md | 10 +++++----- docs/adr/adr-001-semver-pre-general-availability.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/weekly-reference-impl-sync.md b/.github/workflows/weekly-reference-impl-sync.md index 711f16ccb..1d1f9a4fd 100644 --- a/.github/workflows/weekly-reference-impl-sync.md +++ b/.github/workflows/weekly-reference-impl-sync.md @@ -24,7 +24,7 @@ tools: safe-outputs: create-issue: title-prefix: "[reference-impl-sync] " - assignees: [copilot] + assignees: [copilot-swe-agent] labels: [reference-impl-sync] expires: 6 close-issue: @@ -59,8 +59,8 @@ Clone the reference implementation repository and compare commits: ```bash LAST_MERGE=$(cat .lastmerge) -git clone --quiet https://github.com/github/copilot-sdk.git /tmp/gh-aw/agent/refererce-impl -cd /tmp/gh-aw/agent/refererce-impl +git clone --quiet https://github.com/github/copilot-sdk.git /tmp/gh-aw/agent/reference-impl +cd /tmp/gh-aw/agent/reference-impl REFERENCE_IMPL_HEAD=$(git rev-parse HEAD) ``` @@ -84,8 +84,8 @@ Go to Step 3b. ### Step 3b: Changes detected -1. Search for any open issues with the `refererce-impl-sync` label using the GitHub MCP tools. -2. Close each existing open `refererce-impl-sync` issue using the `close_issue` safe-output tool with a comment: "Superseded by a newer reference implementation sync check." +1. Search for any open issues with the `reference-impl-sync` label using the GitHub MCP tools. +2. Close each existing open `reference-impl-sync` issue using the `close_issue` safe-output tool with a comment: "Superseded by a newer reference implementation sync check." 3. Create a new issue using the `create_issue` safe-output tool with: - **Title:** `Reference Implementation sync: new commits ()` - **Body:** Include the following information: diff --git a/docs/adr/adr-001-semver-pre-general-availability.md b/docs/adr/adr-001-semver-pre-general-availability.md index 33bdcc86c..25008b0f5 100644 --- a/docs/adr/adr-001-semver-pre-general-availability.md +++ b/docs/adr/adr-001-semver-pre-general-availability.md @@ -20,7 +20,7 @@ We took an architectural decision to enable us to pursue investigating this poss 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 reference implementation full release before it goes full ga, i.e you put out 0.1.46-virtualthreads.3 until reference implementation 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. +To some extent, I would use qualifiers to mark a release as having some feature that is awaiting a reference implementation full release before it goes full ga, i.e you put out 0.1.46-virtualthreads.3 until reference implementation 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 From 996d241a5ebf4cd01fe3b0ac73527f7f777a46ab Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Thu, 16 Apr 2026 22:55:30 -0400 Subject: [PATCH 47/47] Recompile agentic workflow lock file from updated .md source modified: .github/aw/actions-lock.json - Adds new pinned entry for `actions/github-script@v9` (sha `373c709c...`) - Updates `github/gh-aw-actions/setup` pin from `v0.63.1` to `v0.68.3` with new sha modified: .github/workflows/weekly-reference-impl-sync.lock.yml - Regenerated by `gh aw compile` (v0.68.3) from the `.md` source that was hand-edited in PR #77 without recompiling - Compiler upgraded from v0.51.6 to v0.68.3; schema version v1 to v3 - Frontmatter hash updated to reflect current `.md` content - `create_issue` assignee now correctly reflects `copilot-swe-agent` (was `copilot`) - `assign_to_agent` now includes `model: claude-opus-4.6` - Safe-outputs config updated with new tool entries (`create_report_incomplete_issue`, `report_incomplete`, `noop`) - Action pin references updated to match newer gh-aw-actions versions Signed-off-by: Ed Burns --- .github/aw/actions-lock.json | 11 +- .../weekly-reference-impl-sync.lock.yml | 1337 +++++++++-------- 2 files changed, 690 insertions(+), 658 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 0da84b9c6..1b8b4b56b 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -5,6 +5,11 @@ "version": "v8", "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" }, + "actions/github-script@v9": { + "repo": "actions/github-script", + "version": "v9", + "sha": "373c709c69115d41ff229c7e5df9f8788daa9553" + }, "actions/setup-java@v4": { "repo": "actions/setup-java", "version": "v4", @@ -15,10 +20,10 @@ "version": "v4", "sha": "49933ea5288caeca8642d1e84afbd3f7d6820020" }, - "github/gh-aw-actions/setup@v0.63.1": { + "github/gh-aw-actions/setup@v0.68.3": { "repo": "github/gh-aw-actions/setup", - "version": "v0.63.1", - "sha": "53e09ec0be6271e81a69f51ef93f37212c8834b0" + "version": "v0.68.3", + "sha": "ba90f2186d7ad780ec640f364005fa24e797b360" }, "github/gh-aw/actions/setup@v0.51.6": { "repo": "github/gh-aw/actions/setup", diff --git a/.github/workflows/weekly-reference-impl-sync.lock.yml b/.github/workflows/weekly-reference-impl-sync.lock.yml index 25de1e1e8..49bfd3ac0 100644 --- a/.github/workflows/weekly-reference-impl-sync.lock.yml +++ b/.github/workflows/weekly-reference-impl-sync.lock.yml @@ -1,4 +1,5 @@ -# +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"54f733578d3011b148c7aff09a0e72085d787a777a996488f08aeffdf52ec93b","compiler_version":"v0.68.3","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba90f2186d7ad780ec640f364005fa24e797b360","version":"v0.68.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.20"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.19"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0"},{"image":"node:lts-alpine"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -13,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.51.6). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.68.3). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -24,88 +25,135 @@ # Weekly reference implementation sync workflow. Checks for new commits in the official # Copilot SDK (github/copilot-sdk) and assigns to Copilot to port changes. # -# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"fc14b09206c7aeafcd52c843adce996a1c14cf15875f9b647ef71f631b3b296e","compiler_version":"v0.51.6"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_AGENT_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.20 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.20 +# - ghcr.io/github/gh-aw-mcpg:v0.2.19 +# - ghcr.io/github/github-mcp-server:v0.32.0 +# - node:lts-alpine -name: "Weekly Reference Implementation Sync Agentic Workflow" +name: "Weekly Reference Implementation Sync" "on": schedule: - - cron: "39 8 * * 2" + - cron: "40 11 * * 0" # Friendly format: weekly (scattered) workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string permissions: {} concurrency: group: "gh-aw-${{ github.workflow }}" -run-name: "Weekly Reference Implementation Sync Agentic Workflow" +run-name: "Weekly Reference Implementation Sync" jobs: activation: runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: comment_id: "" comment_repo: "" + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 with: - destination: /opt/gh-aw/actions + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - 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 Reference Implementation Sync Agentic Workflow" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} + GH_AW_INFO_VERSION: "1.0.21" + GH_AW_INFO_AGENT_VERSION: "1.0.21" + GH_AW_INFO_CLI_VERSION: "v0.68.3" + GH_AW_INFO_WORKFLOW_NAME: "Weekly Reference Implementation Sync" 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_AWF_VERSION: "v0.25.20" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 with: script: | - const { main } = require('/opt/gh-aw/actions/generate_aw_info.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/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 + run: bash "${RUNNER_TEMP}/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: + persist-credentials: false 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 + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_AW_WORKFLOW_FILE: "weekly-reference-impl-sync.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" 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/check_workflow_timestamp_api.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_COMPILED_VERSION: "v0.68.3" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.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_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl 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 }} @@ -114,19 +162,20 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec run: | - bash /opt/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_EOF' + cat << 'GH_AW_PROMPT_9a049ffd9b204d97_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' + GH_AW_PROMPT_9a049ffd9b204d97_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_9a049ffd9b204d97_EOF' - Tools: add_comment, create_issue, close_issue, assign_to_agent, missing_tool, missing_data, noop + Tools: add_comment(max:10), create_issue, close_issue(max:10), assign_to_agent, missing_tool, missing_data, noop The following GitHub context information is available for this workflow: @@ -156,26 +205,25 @@ jobs: {{/if}} - GH_AW_PROMPT_EOF - cat << 'GH_AW_PROMPT_EOF' + GH_AW_PROMPT_9a049ffd9b204d97_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_9a049ffd9b204d97_EOF' - GH_AW_PROMPT_EOF - cat << 'GH_AW_PROMPT_EOF' {{#runtime-import .github/workflows/weekly-reference-impl-sync.md}} - GH_AW_PROMPT_EOF + GH_AW_PROMPT_9a049ffd9b204d97_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 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'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); await main(); - name: Substitute placeholders - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_GITHUB_ACTOR: ${{ github.actor }} @@ -188,10 +236,10 @@ jobs: 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 { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); - const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); // Call the substitution function return await substitutePlaceholders({ @@ -210,19 +258,23 @@ jobs: - 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 + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - run: bash /opt/gh-aw/actions/print_prompt_summary.sh + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: activation path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -240,425 +292,292 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - 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: weeklyreferenceimplsync outputs: + agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} 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 }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 with: - destination: /opt/gh-aw/actions + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Create gh-aw temp directory - run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} 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" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr if: | - (github.event.pull_request) || (github.event.issue.pull_request) - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_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/checkout_pr_branch.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - 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.23.0 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 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'); + const determineAutomaticLockdown = require('${{ runner.temp }}/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.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 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 ghcr.io/github/gh-aw-mcpg:v0.2.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p /opt/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - 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":["reference-impl-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 \"[reference-impl-sync] \". Labels [reference-impl-sync] will be automatically added. Assignees [copilot] will be automatically assigned.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "body": { - "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.", - "type": "string" - }, - "labels": { - "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.", - "items": { - "type": "string" - }, - "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_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 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": { - "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.", - "type": "string" - } - }, - "required": [ - "title", - "body" - ], - "type": "object" - }, - "name": "create_issue" - }, + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2a317c5680ea1925_EOF' + {"add_comment":{"max":10,"target":"*"},"assign_to_agent":{"max":1,"model":"claude-opus-4.6","name":"copilot","target":"*"},"close_issue":{"max":10,"required_labels":["reference-impl-sync"],"target":"*"},"create_issue":{"assignees":["copilot-swe-agent"],"expires":144,"labels":["reference-impl-sync"],"max":1,"title_prefix":"[reference-impl-sync] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_2a317c5680ea1925_EOF + - name: Write Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | { - "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": { - "body": { - "description": "Closing comment explaining why the issue is being closed and summarizing any resolution, workaround, or conclusion.", - "type": "string" - }, - "issue_number": { - "description": "Issue number to close. This is the numeric ID from the GitHub URL (e.g., 901 in github.com/owner/repo/issues/901). If omitted, closes the issue that triggered this workflow (requires an issue event trigger).", - "type": [ - "number", - "string" - ] - } - }, - "required": [ - "body" - ], - "type": "object" + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 10 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.", + "assign_to_agent": " CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", + "close_issue": " CONSTRAINTS: Maximum 10 issue(s) can be closed. Target: *.", + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[reference-impl-sync] \". Labels [\"reference-impl-sync\"] will be automatically added. Assignees [\"copilot-swe-agent\"] will be automatically assigned." }, - "name": "close_issue" - }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | { - "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": { + "add_comment": { + "defaultMax": 1, + "fields": { "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. 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" + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 }, "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 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" + "issueOrPRNumber": true + }, + "reply_to_id": { + "type": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 } - }, - "required": [ - "body" - ], - "type": "object" + } }, - "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\", pull_request_repo=\"owner/repo\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", - "inputSchema": { - "additionalProperties": false, - "properties": { + "assign_to_agent": { + "defaultMax": 1, + "fields": { "agent": { - "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", - "type": "string" + "type": "string", + "sanitize": true, + "maxLength": 128 }, "issue_number": { - "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" - ] + "issueNumberOrTemporaryId": true }, "pull_number": { - "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" - ] + "optionalPositiveInteger": true }, "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": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 } }, - "type": "object" + "customValidation": "requiresOneOf:issue_number,pull_number" }, - "name": "assign_to_agent" - }, - { - "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "alternatives": { - "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", - "type": "string" + "close_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 }, - "reason": { - "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", - "type": "string" + "issue_number": { + "optionalPositiveInteger": true }, - "tool": { - "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", - "type": "string" + "repo": { + "type": "string", + "maxLength": 256 } - }, - "required": [ - "reason" - ], - "type": "object" + } }, - "name": "missing_tool" - }, - { - "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "message": { - "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 } - }, - "required": [ - "message" - ], - "type": "object" + } }, - "name": "noop" - }, - { - "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", - "inputSchema": { - "additionalProperties": false, - "properties": { + "missing_data": { + "defaultMax": 20, + "fields": { "alternatives": { - "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", - "type": "string" + "type": "string", + "sanitize": true, + "maxLength": 256 }, "context": { - "description": "Additional context about the missing data or where it should come from (max 256 characters).", - "type": "string" + "type": "string", + "sanitize": true, + "maxLength": 256 }, "data_type": { - "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", - "type": "string" + "type": "string", + "sanitize": true, + "maxLength": 128 }, "reason": { - "description": "Explanation of why this data is needed to complete the task (max 256 characters).", - "type": "string" + "type": "string", + "sanitize": true, + "maxLength": 256 } - }, - "required": [], - "type": "object" - }, - "name": "missing_data" - } - ] - GH_AW_SAFE_OUTPUTS_TOOLS_EOF - cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 - } - } - }, - "assign_to_agent": { - "defaultMax": 1, - "fields": { - "agent": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "issue_number": { - "issueNumberOrTemporaryId": true - }, - "pull_number": { - "optionalPositiveInteger": true - }, - "pull_request_repo": { - "type": "string", - "maxLength": 256 - }, - "repo": { - "type": "string", - "maxLength": 256 } }, - "customValidation": "requiresOneOf:issue_number,pull_number" - }, - "close_issue": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "issue_number": { - "optionalPositiveInteger": true - }, - "repo": { - "type": "string", - "maxLength": 256 - } - } - }, - "create_issue": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "parent": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "temporary_id": { - "type": "string" - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "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": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } } } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -681,29 +600,32 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash /opt/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} - GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | set -eo pipefail @@ -721,20 +643,26 @@ jobs: 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 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' + 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_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e 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.2.19' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_ee30822602a32ffa_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v0.31.0", + "container": "ghcr.io/github/github-mcp-server:v0.32.0", "env": { - "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } } }, "safeoutputs": { @@ -742,6 +670,13 @@ jobs: "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", "headers": { "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } } } }, @@ -752,67 +687,70 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_EOF + GH_AW_MCP_CONFIG_ee30822602a32ffa_EOF - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: activation path: /tmp/gh-aw - name: Clean git credentials - run: bash /opt/gh-aw/actions/clean_git_credentials.sh + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 run: | set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /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 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --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,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json - GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.68.3 GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true 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_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] XDG_CONFIG_HOME: /home/runner + - name: Detect Copilot errors + id: detect-copilot-errors + if: always() + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} 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" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: | - # Copy Copilot session state files to logs folder for artifact collection - # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them - SESSION_STATE_DIR="$HOME/.copilot/session-state" - LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" - - if [ -d "$SESSION_STATE_DIR" ]; then - echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" - mkdir -p "$LOGS_DIR" - cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true - echo "Session state files copied successfully" - else - echo "No session-state directory found at $SESSION_STATE_DIR" - fi + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -821,15 +759,15 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 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/redact_secrets.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' @@ -837,62 +775,51 @@ jobs: SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload Safe Outputs + - name: Append agent step summary if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - with: - name: safe-output - path: ${{ env.GH_AW_SAFE_OUTPUTS }} - if-no-files-found: warn + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true - name: Ingest agent output id: collect_output if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,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" + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} 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/collect_ndjson_output.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); await main(); - - name: Upload sanitized agent output - if: always() && env.GH_AW_AGENT_OUTPUT - 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - with: - name: agent_outputs - path: | - /tmp/gh-aw/sandbox/agent/logs/ - /tmp/gh-aw/redacted-urls.log - if-no-files-found: ignore - name: Parse agent logs for step summary if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ 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_copilot_log.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); await main(); - name: Parse MCP Gateway logs for step summary if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + id: parse-mcp-gateway + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 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_mcp_gateway_log.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); await main(); - name: Print firewall logs if: always() @@ -909,26 +836,236 @@ jobs: else echo 'AWF binary not installed, skipping firewall log summary' fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: agent-artifacts + name: agent path: | /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ - /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ if-no-files-found: ignore - # --- Threat Detection (inline) --- + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-weekly-reference-impl-sync" + cancel-in-progress: false + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync" + 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-reference-impl-sync" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_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_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_TIMEOUT_MINUTES: "20" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 - 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 }} + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} run: | if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then echo "run_detection=true" >> "$GITHUB_OUTPUT" @@ -952,194 +1089,93 @@ jobs: for f in /tmp/gh-aw/aw-*.patch; do [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done echo "Prepared threat detection files:" ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - name: Setup threat detection if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" + WORKFLOW_NAME: "Weekly Reference Implementation Sync" WORKFLOW_DESCRIPTION: "Weekly reference implementation 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 }} + 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'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/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: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 - 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 + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) # 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 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --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,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 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 || '' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.68.3 GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true 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_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] 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 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: threat-detection.log + name: detection path: /tmp/gh-aw/threat-detection/detection.log if-no-files-found: ignore - - name: Set detection conclusion + - name: Parse and conclude threat detection id: detection_conclusion if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 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 - - safe_outputs - if: (always()) && (needs.agent.result != 'skipped') - runs-on: ubuntu-slim - permissions: - contents: read - discussions: write - issues: write - pull-requests: write - outputs: - noop_message: ${{ steps.noop.outputs.noop_message }} - tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} - total_count: ${{ steps.missing_tool.outputs.total_count }} - steps: - - name: Setup Scripts - 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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - name: agent-output - path: /tmp/gh-aw/safeoutputs/ - - name: Setup agent output environment variable - run: | - mkdir -p /tmp/gh-aw/safeoutputs/ - find "/tmp/gh-aw/safeoutputs/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - - name: Process No-Op Messages - id: noop - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" 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/noop.cjs'); - await main(); - - name: Record Missing Tool - id: missing_tool - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" - 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/missing_tool.cjs'); - await main(); - - name: Handle Agent Failure - id: handle_agent_failure - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" - 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-reference-impl-sync" - 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: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); - await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "false" - 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/handle_noop_message.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); await main(); safe_outputs: - needs: agent - if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' runs-on: ubuntu-slim permissions: contents: read @@ -1148,14 +1184,18 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/${{ github.workflow }}" + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/weekly-reference-impl-sync" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "weekly-reference-impl-sync" - GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync Agentic Workflow" + GH_AW_WORKFLOW_NAME: "Weekly Reference Implementation Sync" outputs: - 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 }} + assign_to_agent_assigned: ${{ steps.process_safe_outputs.outputs.assign_to_agent_assigned }} + assign_to_agent_assignment_error_count: ${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_error_count }} + assign_to_agent_assignment_errors: ${{ steps.process_safe_outputs.outputs.assign_to_agent_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 }} @@ -1168,72 +1208,59 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@33cd6c7f1fee588654ef19def2e6a4174be66197 # v0.51.6 + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 with: - destination: /opt/gh-aw/actions + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact + id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent-output - path: /tmp/gh-aw/safeoutputs/ + name: agent + path: /tmp/gh-aw/ - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash run: | - mkdir -p /tmp/gh-aw/safeoutputs/ - find "/tmp/gh-aw/safeoutputs/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - name: Process Safe Outputs id: process_safe_outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 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" + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.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,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"close_issue\":{\"max\":10,\"required_labels\":[\"reference-impl-sync\"],\"target\":\"*\"},\"create_issue\":{\"assignees\":[\"copilot\"],\"expires\":144,\"labels\":[\"reference-impl-sync\"],\"max\":1,\"title_prefix\":\"[reference-impl-sync] \"},\"missing_data\":{},\"missing_tool\":{}}" - GH_AW_ASSIGN_COPILOT: "true" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"assign_to_agent\":{\"max\":1,\"model\":\"claude-opus-4.6\",\"name\":\"copilot\",\"target\":\"*\"},\"close_issue\":{\"max\":10,\"required_labels\":[\"reference-impl-sync\"],\"target\":\"*\"},\"create_issue\":{\"assignees\":[\"copilot-swe-agent\"],\"expires\":144,\"labels\":[\"reference-impl-sync\"],\"max\":1,\"title_prefix\":\"[reference-impl-sync] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} 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/safe_output_handler_manager.cjs'); + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Assign Copilot to created issues - if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != '' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }} - with: - github-token: ${{ secrets.GH_AW_AGENT_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/assign_copilot_to_created_issues.cjs'); - await main(); - - 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: - github-token: ${{ secrets.GH_AW_AGENT_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/assign_to_agent.cjs'); - await main(); - - name: Upload safe output items manifest + - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: safe-output-items - path: /tmp/safe-output-items.jsonl - if-no-files-found: warn + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore