From 130e54162d6b722c3ae202acf2e54f94ab3160ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 19:39:45 +0000 Subject: [PATCH 1/3] Initial plan From 764900f190b2ab45f711c41b14133e93bf9c3d99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 20:02:02 +0000 Subject: [PATCH 2/3] Port copilotHome, tcpConnectionToken, instructionDirectories, continuePendingWork, and MCP OAuth fields from reference implementation - Add copilotHome option to CopilotClientOptions (sets COPILOT_HOME env var) - Add tcpConnectionToken option to CopilotClientOptions (sets COPILOT_CONNECTION_TOKEN env var) - Add instructionDirectories to SessionConfig and ResumeSessionConfig - Add continuePendingWork to ResumeSessionConfig - Add oauthClientId, oauthPublicClient, oauthGrantType to McpHttpServerConfig - Update SessionRequestBuilder to pass new fields in RPC requests - Add unit tests for new request builder fields - Add E2E tests for instructionDirectories on create and resume - Update .lastmerge to e8dabaf9d9734c92f4d1541dd8c4169cedbfb688 Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- .lastmerge | 2 +- .../github/copilot/sdk/CliServerManager.java | 10 ++ .../copilot/sdk/SessionRequestBuilder.java | 3 + .../sdk/json/CopilotClientOptions.java | 61 +++++++++++ .../sdk/json/CreateSessionRequest.java | 15 +++ .../copilot/sdk/json/McpHttpServerConfig.java | 80 ++++++++++++++ .../copilot/sdk/json/ResumeSessionConfig.java | 60 ++++++++++ .../sdk/json/ResumeSessionRequest.java | 28 +++++ .../copilot/sdk/json/SessionConfig.java | 27 +++++ .../github/copilot/sdk/SessionConfigTest.java | 103 ++++++++++++++++++ .../sdk/SessionRequestBuilderTest.java | 27 +++++ 11 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/github/copilot/sdk/SessionConfigTest.java diff --git a/.lastmerge b/.lastmerge index d40c7b3af..631c04836 100644 --- a/.lastmerge +++ b/.lastmerge @@ -1 +1 @@ -e42b726ca42bd1b2e099a956c9287ba9435ba3e5 +e8dabaf9d9734c92f4d1541dd8c4169cedbfb688 diff --git a/src/main/java/com/github/copilot/sdk/CliServerManager.java b/src/main/java/com/github/copilot/sdk/CliServerManager.java index 07b5eefec..ff9f96eca 100644 --- a/src/main/java/com/github/copilot/sdk/CliServerManager.java +++ b/src/main/java/com/github/copilot/sdk/CliServerManager.java @@ -115,6 +115,16 @@ ProcessInfo startCliServer() throws IOException, InterruptedException { pb.environment().put("COPILOT_SDK_AUTH_TOKEN", options.getGitHubToken()); } + // Set connection token in environment if provided + if (options.getTcpConnectionToken() != null && !options.getTcpConnectionToken().isEmpty()) { + pb.environment().put("COPILOT_CONNECTION_TOKEN", options.getTcpConnectionToken()); + } + + // Set copilot home directory in environment if provided + if (options.getCopilotHome() != null && !options.getCopilotHome().isEmpty()) { + pb.environment().put("COPILOT_HOME", options.getCopilotHome()); + } + // Set telemetry environment variables if configured if (options.getTelemetry() != null) { var telemetry = options.getTelemetry(); diff --git a/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java b/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java index de6977ea8..b1290eeb0 100644 --- a/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java +++ b/src/main/java/com/github/copilot/sdk/SessionRequestBuilder.java @@ -122,6 +122,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setAgent(config.getAgent()); request.setInfiniteSessions(config.getInfiniteSessions()); request.setSkillDirectories(config.getSkillDirectories()); + request.setInstructionDirectories(config.getInstructionDirectories()); request.setDisabledSkills(config.getDisabledSkills()); request.setConfigDir(config.getConfigDir()); request.setEnableConfigDiscovery(config.getEnableConfigDiscovery()); @@ -199,8 +200,10 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setDefaultAgent(config.getDefaultAgent()); request.setAgent(config.getAgent()); request.setSkillDirectories(config.getSkillDirectories()); + request.setInstructionDirectories(config.getInstructionDirectories()); request.setDisabledSkills(config.getDisabledSkills()); request.setInfiniteSessions(config.getInfiniteSessions()); + request.setContinuePendingWork(config.getContinuePendingWork()); request.setModelCapabilities(config.getModelCapabilities()); if (config.getCommands() != null && !config.getCommands().isEmpty()) { diff --git a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java index 3a14d1733..a45336772 100644 --- a/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java +++ b/src/main/java/com/github/copilot/sdk/json/CopilotClientOptions.java @@ -44,6 +44,7 @@ public class CopilotClientOptions { private String[] cliArgs; private String cliPath; private String cliUrl; + private String copilotHome; private String cwd; private Map environment; private Executor executor; @@ -51,6 +52,7 @@ public class CopilotClientOptions { private String logLevel = "info"; private Supplier>> onListModels; private int port; + private String tcpConnectionToken; private TelemetryConfig telemetry; private Integer sessionIdleTimeoutSeconds; private Boolean useLoggedInUser; @@ -214,6 +216,36 @@ public CopilotClientOptions setCwd(String cwd) { return this; } + /** + * Gets the base directory for Copilot data (session state, config, etc.). + * + * @return the copilot home directory path, or {@code null} to use the CLI + * default ({@code ~/.copilot}) + * @since 1.4.0 + */ + public String getCopilotHome() { + return copilotHome; + } + + /** + * Sets the base directory for Copilot data (session state, config, etc.). + *

+ * Sets the {@code COPILOT_HOME} environment variable on the spawned CLI + * process. When {@code null}, the CLI defaults to {@code ~/.copilot}. + *

+ * This option is only used when the SDK spawns the CLI process; it is ignored + * when connecting to an external server via {@link #setCliUrl(String)}. + * + * @param copilotHome + * the directory path for Copilot data + * @return this options instance for method chaining + * @since 1.4.0 + */ + public CopilotClientOptions setCopilotHome(String copilotHome) { + this.copilotHome = copilotHome; + return this; + } + /** * Gets the environment variables for the CLI process. *

@@ -462,6 +494,33 @@ public CopilotClientOptions setSessionIdleTimeoutSeconds(Integer sessionIdleTime return this; } + /** + * Gets the connection token for the headless CLI server (TCP only). + * + * @return the connection token, or {@code null} if not set + * @since 1.4.0 + */ + public String getTcpConnectionToken() { + return tcpConnectionToken; + } + + /** + * Sets the connection token for the headless CLI server (TCP only). + *

+ * When the SDK spawns its own CLI in TCP mode and this is omitted, a UUID is + * generated automatically so the loopback listener is safe by default. Cannot + * be combined with {@link #setUseStdio(boolean)} set to {@code true}. + * + * @param tcpConnectionToken + * the connection token (must be non-empty if provided) + * @return this options instance for method chaining + * @since 1.4.0 + */ + public CopilotClientOptions setTcpConnectionToken(String tcpConnectionToken) { + this.tcpConnectionToken = tcpConnectionToken; + return this; + } + /** * Returns whether to use the logged-in user for authentication. * @@ -533,6 +592,7 @@ public CopilotClientOptions clone() { copy.cliArgs = this.cliArgs != null ? this.cliArgs.clone() : null; copy.cliPath = this.cliPath; copy.cliUrl = this.cliUrl; + copy.copilotHome = this.copilotHome; copy.cwd = this.cwd; copy.environment = this.environment != null ? new java.util.HashMap<>(this.environment) : null; copy.executor = this.executor; @@ -541,6 +601,7 @@ public CopilotClientOptions clone() { copy.onListModels = this.onListModels; copy.port = this.port; copy.sessionIdleTimeoutSeconds = this.sessionIdleTimeoutSeconds; + copy.tcpConnectionToken = this.tcpConnectionToken; copy.telemetry = this.telemetry; copy.useLoggedInUser = this.useLoggedInUser; copy.useStdio = this.useStdio; 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 ef8d5fda2..5243f99ec 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,9 @@ public final class CreateSessionRequest { @JsonProperty("skillDirectories") private List skillDirectories; + @JsonProperty("instructionDirectories") + private List instructionDirectories; + @JsonProperty("disabledSkills") private List disabledSkills; @@ -326,6 +329,18 @@ public void setSkillDirectories(List skillDirectories) { this.skillDirectories = skillDirectories; } + /** Gets instruction directories. @return the instruction directories */ + public List getInstructionDirectories() { + return instructionDirectories == null ? null : Collections.unmodifiableList(instructionDirectories); + } + + /** + * Sets instruction directories. @param instructionDirectories the directories + */ + public void setInstructionDirectories(List instructionDirectories) { + this.instructionDirectories = instructionDirectories; + } + /** Gets disabled skills. @return the disabled skill names */ public List getDisabledSkills() { return disabledSkills == null ? null : Collections.unmodifiableList(disabledSkills); diff --git a/src/main/java/com/github/copilot/sdk/json/McpHttpServerConfig.java b/src/main/java/com/github/copilot/sdk/json/McpHttpServerConfig.java index 7017db3d2..41d75bd44 100644 --- a/src/main/java/com/github/copilot/sdk/json/McpHttpServerConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/McpHttpServerConfig.java @@ -41,6 +41,15 @@ public final class McpHttpServerConfig extends McpServerConfig { @JsonProperty("headers") private Map headers; + @JsonProperty("oauthClientId") + private String oauthClientId; + + @JsonProperty("oauthPublicClient") + private Boolean oauthPublicClient; + + @JsonProperty("oauthGrantType") + private String oauthGrantType; + /** * Gets the server type discriminator. * @@ -92,6 +101,77 @@ public McpHttpServerConfig setHeaders(Map headers) { return this; } + /** + * Gets the optional OAuth client ID for the remote server. + * + * @return the OAuth client ID, or {@code null} + * @since 1.4.0 + */ + public String getOauthClientId() { + return oauthClientId; + } + + /** + * Sets the optional OAuth client ID for the remote server. + * + * @param oauthClientId + * the OAuth client ID + * @return this config for method chaining + * @since 1.4.0 + */ + public McpHttpServerConfig setOauthClientId(String oauthClientId) { + this.oauthClientId = oauthClientId; + return this; + } + + /** + * Gets whether this is a public OAuth client. + * + * @return {@code true} if public OAuth client, or {@code null} + * @since 1.4.0 + */ + public Boolean getOauthPublicClient() { + return oauthPublicClient; + } + + /** + * Sets whether this is a public OAuth client. + * + * @param oauthPublicClient + * whether this is a public OAuth client + * @return this config for method chaining + * @since 1.4.0 + */ + public McpHttpServerConfig setOauthPublicClient(Boolean oauthPublicClient) { + this.oauthPublicClient = oauthPublicClient; + return this; + } + + /** + * Gets the optional OAuth grant type for the remote server. + * + * @return the OAuth grant type (e.g., "authorization_code" or + * "client_credentials"), or {@code null} + * @since 1.4.0 + */ + public String getOauthGrantType() { + return oauthGrantType; + } + + /** + * Sets the optional OAuth grant type for the remote server. + * + * @param oauthGrantType + * the OAuth grant type (e.g., "authorization_code" or + * "client_credentials") + * @return this config for method chaining + * @since 1.4.0 + */ + public McpHttpServerConfig setOauthGrantType(String oauthGrantType) { + this.oauthGrantType = oauthGrantType; + return this; + } + @Override public McpHttpServerConfig setTools(List tools) { super.setTools(tools); 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 b4dacf370..50f9e0567 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionConfig.java @@ -59,8 +59,10 @@ public class ResumeSessionConfig { private DefaultAgentConfig defaultAgent; private String agent; private List skillDirectories; + private List instructionDirectories; private List disabledSkills; private InfiniteSessionConfig infiniteSessions; + private Boolean continuePendingWork; private Consumer onEvent; private List commands; private ElicitationHandler onElicitationRequest; @@ -591,6 +593,29 @@ public ResumeSessionConfig setSkillDirectories(List skillDirectories) { return this; } + /** + * Gets the instruction directories. + * + * @return the list of instruction directory paths + * @since 1.4.0 + */ + public List getInstructionDirectories() { + return instructionDirectories == null ? null : Collections.unmodifiableList(instructionDirectories); + } + + /** + * Sets additional directories to search for custom instruction files. + * + * @param instructionDirectories + * the list of instruction directory paths + * @return this config for method chaining + * @since 1.4.0 + */ + public ResumeSessionConfig setInstructionDirectories(List instructionDirectories) { + this.instructionDirectories = instructionDirectories; + return this; + } + /** * Gets the disabled skills. * @@ -635,6 +660,37 @@ public ResumeSessionConfig setInfiniteSessions(InfiniteSessionConfig infiniteSes return this; } + /** + * Gets whether to continue pending work on resume. + * + * @return {@code true} to continue pending work, {@code false} or {@code null} + * to treat pending work as interrupted + * @since 1.4.0 + */ + public Boolean getContinuePendingWork() { + return continuePendingWork; + } + + /** + * Sets whether to continue any tool calls or permission prompts that were still + * pending when the session was last suspended. + *

+ * When {@code true}, the runtime continues pending work on resume. When + * {@code false} (the default), the runtime treats pending work as interrupted. + * For permission requests, the runtime re-emits {@code permission.requested} so + * the registered handler can re-prompt; for external tool calls, the consumer + * is expected to supply the result via the corresponding low-level RPC method. + * + * @param continuePendingWork + * whether to continue pending work + * @return this config for method chaining + * @since 1.4.0 + */ + public ResumeSessionConfig setContinuePendingWork(Boolean continuePendingWork) { + this.continuePendingWork = continuePendingWork; + return this; + } + /** * Gets the event handler registered before the session.resume RPC is issued. * @@ -775,8 +831,12 @@ public ResumeSessionConfig clone() { copy.defaultAgent = this.defaultAgent; copy.agent = this.agent; copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.instructionDirectories = this.instructionDirectories != null + ? new ArrayList<>(this.instructionDirectories) + : null; copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.infiniteSessions = this.infiniteSessions; + copy.continuePendingWork = this.continuePendingWork; copy.onEvent = this.onEvent; copy.commands = this.commands != null ? new ArrayList<>(this.commands) : null; copy.onElicitationRequest = this.onElicitationRequest; 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 e36e90b67..a5968dcdc 100644 --- a/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java +++ b/src/main/java/com/github/copilot/sdk/json/ResumeSessionRequest.java @@ -98,12 +98,18 @@ public final class ResumeSessionRequest { @JsonProperty("skillDirectories") private List skillDirectories; + @JsonProperty("instructionDirectories") + private List instructionDirectories; + @JsonProperty("disabledSkills") private List disabledSkills; @JsonProperty("infiniteSessions") private InfiniteSessionConfig infiniteSessions; + @JsonProperty("continuePendingWork") + private Boolean continuePendingWork; + @JsonProperty("commands") private List commands; @@ -366,6 +372,18 @@ public void setSkillDirectories(List skillDirectories) { this.skillDirectories = skillDirectories; } + /** Gets instruction directories. @return the instruction directories */ + public List getInstructionDirectories() { + return instructionDirectories == null ? null : Collections.unmodifiableList(instructionDirectories); + } + + /** + * Sets instruction directories. @param instructionDirectories the directories + */ + public void setInstructionDirectories(List instructionDirectories) { + this.instructionDirectories = instructionDirectories; + } + /** Gets disabled skills. @return the disabled skill names */ public List getDisabledSkills() { return disabledSkills == null ? null : Collections.unmodifiableList(disabledSkills); @@ -389,6 +407,16 @@ public void setInfiniteSessions(InfiniteSessionConfig infiniteSessions) { this.infiniteSessions = infiniteSessions; } + /** Gets the continuePendingWork flag. @return the flag */ + public Boolean getContinuePendingWork() { + return continuePendingWork; + } + + /** Sets the continuePendingWork flag. @param continuePendingWork the flag */ + public void setContinuePendingWork(Boolean continuePendingWork) { + this.continuePendingWork = continuePendingWork; + } + /** Gets the commands wire definitions. @return the commands */ public List getCommands() { return commands == null ? null : Collections.unmodifiableList(commands); 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 09661346a..a24e9f10e 100644 --- a/src/main/java/com/github/copilot/sdk/json/SessionConfig.java +++ b/src/main/java/com/github/copilot/sdk/json/SessionConfig.java @@ -57,6 +57,7 @@ public class SessionConfig { private String agent; private InfiniteSessionConfig infiniteSessions; private List skillDirectories; + private List instructionDirectories; private List disabledSkills; private String configDir; private Boolean enableConfigDiscovery; @@ -550,6 +551,29 @@ public SessionConfig setSkillDirectories(List skillDirectories) { return this; } + /** + * Gets the instruction directories. + * + * @return the list of instruction directory paths + * @since 1.4.0 + */ + public List getInstructionDirectories() { + return instructionDirectories == null ? null : Collections.unmodifiableList(instructionDirectories); + } + + /** + * Sets additional directories to search for custom instruction files. + * + * @param instructionDirectories + * the list of instruction directory paths + * @return this config instance for method chaining + * @since 1.4.0 + */ + public SessionConfig setInstructionDirectories(List instructionDirectories) { + this.instructionDirectories = instructionDirectories; + return this; + } + /** * Gets the disabled skill names. * @@ -825,6 +849,9 @@ public SessionConfig clone() { copy.agent = this.agent; copy.infiniteSessions = this.infiniteSessions; copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.instructionDirectories = this.instructionDirectories != null + ? new ArrayList<>(this.instructionDirectories) + : null; copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.configDir = this.configDir; copy.enableConfigDiscovery = this.enableConfigDiscovery; diff --git a/src/test/java/com/github/copilot/sdk/SessionConfigTest.java b/src/test/java/com/github/copilot/sdk/SessionConfigTest.java new file mode 100644 index 000000000..77c310583 --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/SessionConfigTest.java @@ -0,0 +1,103 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.copilot.sdk.json.MessageOptions; +import com.github.copilot.sdk.json.PermissionHandler; +import com.github.copilot.sdk.json.ResumeSessionConfig; +import com.github.copilot.sdk.json.SessionConfig; + +/** + * Tests for session configuration features. + * + *

+ * These tests verify that session config options like instructionDirectories + * are correctly forwarded to the CLI. Snapshots are stored in + * test/snapshots/session_config/. + *

+ */ +public class SessionConfigTest { + + private static E2ETestContext ctx; + + @BeforeAll + static void setup() throws Exception { + ctx = E2ETestContext.create(); + } + + @AfterAll + static void teardown() throws Exception { + if (ctx != null) { + ctx.close(); + } + } + + @Test + void testShouldApplyInstructionDirectoriesOnCreate() throws Exception { + ctx.configureForTest("session_config", "should_apply_instructiondirectories_on_create"); + + Path projectDir = ctx.getWorkDir().resolve("instruction-create-project"); + Path instructionDir = ctx.getWorkDir().resolve("extra-create-instructions"); + Path instructionFilesDir = instructionDir.resolve(".github").resolve("instructions"); + Files.createDirectories(projectDir); + Files.createDirectories(instructionFilesDir); + Files.writeString(instructionFilesDir.resolve("extra.instructions.md"), + "Always include JAVA_CREATE_INSTRUCTION_DIRECTORIES_SENTINEL."); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client.createSession(new SessionConfig().setWorkingDirectory(projectDir.toString()) + .setInstructionDirectories(List.of(instructionDir.toString())) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(30, TimeUnit.SECONDS); + + assertNotNull(session.getSessionId()); + + var reply = session.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(30, TimeUnit.SECONDS); + + assertNotNull(reply, "Expected a reply from the assistant"); + } + } + + @Test + void testShouldApplyInstructionDirectoriesOnResume() throws Exception { + ctx.configureForTest("session_config", "should_apply_instructiondirectories_on_resume"); + + Path projectDir = ctx.getWorkDir().resolve("instruction-resume-project"); + Path instructionDir = ctx.getWorkDir().resolve("extra-resume-instructions"); + Path instructionFilesDir = instructionDir.resolve(".github").resolve("instructions"); + Files.createDirectories(projectDir); + Files.createDirectories(instructionFilesDir); + Files.writeString(instructionFilesDir.resolve("extra.instructions.md"), + "Always include JAVA_RESUME_INSTRUCTION_DIRECTORIES_SENTINEL."); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session1 = client.createSession(new SessionConfig() + .setWorkingDirectory(projectDir.toString()).setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(30, TimeUnit.SECONDS); + + CopilotSession session2 = client.resumeSession(session1.getSessionId(), + new ResumeSessionConfig().setWorkingDirectory(projectDir.toString()) + .setInstructionDirectories(List.of(instructionDir.toString())) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(30, TimeUnit.SECONDS); + + assertNotNull(session2.getSessionId()); + + var reply = session2.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(30, TimeUnit.SECONDS); + + assertNotNull(reply, "Expected a reply from the assistant"); + } + } +} diff --git a/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java b/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java index abb4447f5..037e61df9 100644 --- a/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java +++ b/src/test/java/com/github/copilot/sdk/SessionRequestBuilderTest.java @@ -441,4 +441,31 @@ void testBuildResumeRequestWithGitHubToken() { assertEquals("ghp_per_session_token", request.getGitHubToken()); } + + @Test + void testBuildCreateRequestWithInstructionDirectories() { + var config = new SessionConfig().setInstructionDirectories(List.of("/path/to/instructions")); + + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + + assertEquals(List.of("/path/to/instructions"), request.getInstructionDirectories()); + } + + @Test + void testBuildResumeRequestWithInstructionDirectories() { + var config = new ResumeSessionConfig().setInstructionDirectories(List.of("/path/to/instructions")); + + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("test-session", config); + + assertEquals(List.of("/path/to/instructions"), request.getInstructionDirectories()); + } + + @Test + void testBuildResumeRequestWithContinuePendingWork() { + var config = new ResumeSessionConfig().setContinuePendingWork(true); + + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("test-session", config); + + assertEquals(true, request.getContinuePendingWork()); + } } From 4c2ab53fc2e80964619eda7943f38d8e7bb9df47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 20:08:30 +0000 Subject: [PATCH 3/3] Add documentation for new features from reference implementation - Document instructionDirectories in advanced.md - Document continuePendingWork in advanced.md - Document copilotHome and tcpConnectionToken in advanced.md - Update Table of Contents Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- src/site/markdown/advanced.md | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/site/markdown/advanced.md b/src/site/markdown/advanced.md index a4c4d830a..4b00e97e4 100644 --- a/src/site/markdown/advanced.md +++ b/src/site/markdown/advanced.md @@ -25,13 +25,17 @@ This guide covers advanced scenarios for extending and customizing your Copilot - [Skills Configuration](#Skills_Configuration) - [Loading Skills](#Loading_Skills) - [Disabling Skills](#Disabling_Skills) +- [Instruction Directories](#Instruction_Directories) - [Custom Configuration Directory](#Custom_Configuration_Directory) +- [Continuing Pending Work on Resume](#Continuing_Pending_Work_on_Resume) - [Session Logging](#Session_Logging) - [Early Event Registration](#Early_Event_Registration) - [User Input Handling](#User_Input_Handling) - [Permission Handling](#Permission_Handling) - [Session Hooks](#Session_Hooks) - [Manual Server Control](#Manual_Server_Control) + - [Copilot Home Directory](#Copilot_Home_Directory) + - [TCP Connection Token](#TCP_Connection_Token) - [Session Context and Filtering](#Session_Context_and_Filtering) - [Listing Sessions with Context](#Listing_Sessions_with_Context) - [Filtering Sessions by Context](#Filtering_Sessions_by_Context) @@ -626,6 +630,32 @@ var session = client.createSession( --- +## Instruction Directories + +Specify additional directories to search for custom instruction files. These directories are scanned +for `.instructions.md` files (typically placed under `.github/instructions/`) which are included in +the system message for the session. + +```java +var session = client.createSession( + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setWorkingDirectory("/path/to/project") + .setInstructionDirectories(List.of("/path/to/extra-instructions")) +).get(); +``` + +Instruction directories can also be set when resuming a session: + +```java +var session = client.resumeSession(sessionId, + new ResumeSessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setInstructionDirectories(List.of("/path/to/extra-instructions")) +).get(); +``` + +--- + ## Custom Configuration Directory Use a custom configuration directory for session settings: @@ -641,6 +671,25 @@ This is useful when you need to isolate session configuration or use different s --- +## Continuing Pending Work on Resume + +When resuming a suspended session, you can instruct the runtime to continue any tool calls or +permission prompts that were still pending when the session was last suspended: + +```java +var session = client.resumeSession(sessionId, + new ResumeSessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setContinuePendingWork(true) +).get(); +``` + +When `continuePendingWork` is `false` (the default), the runtime treats pending work as +interrupted on resume. For permission requests, the runtime re-emits `permission.requested` +so the registered handler can re-prompt. + +--- + ## Session Logging Send log messages to the session for debugging, status updates, or UI feedback. @@ -828,6 +877,32 @@ client.forceStop().get(); > **Tip:** In `try-with-resources` blocks, `close()` delegates to `stop()`, so graceful session cleanup happens automatically. > `close()` is blocking and waits up to `CopilotClient.AUTOCLOSEABLE_TIMEOUT_SECONDS` seconds for shutdown to complete. +### Copilot Home Directory + +Configure a custom base directory for Copilot data (session state, config, etc.): + +```java +var client = new CopilotClient( + new CopilotClientOptions().setCopilotHome("/custom/copilot/data") +); +``` + +This sets the `COPILOT_HOME` environment variable on the spawned CLI process. When not set, the CLI defaults to `~/.copilot`. This option is only used when the SDK spawns the CLI process; it is ignored when connecting to an external server via `setCliUrl()`. + +### TCP Connection Token + +When using TCP transport, you can set a connection token for authentication: + +```java +var client = new CopilotClient( + new CopilotClientOptions() + .setUseStdio(false) + .setTcpConnectionToken("my-secret-token") +); +``` + +When the SDK spawns its own CLI in TCP mode and no token is specified, a UUID is generated automatically so the loopback listener is safe by default. The token cannot be used with stdio transport. + --- ## Session Context and Filtering