diff --git a/docs/features/index.md b/docs/features/index.md
index bbd005cb0..65a1f7535 100644
--- a/docs/features/index.md
+++ b/docs/features/index.md
@@ -17,6 +17,7 @@ These guides cover the capabilities you can add to your Copilot SDK application.
| [Streaming Events](./streaming-events.md) | Subscribe to real-time session events (40+ event types) |
| [Steering & Queueing](./steering-and-queueing.md) | Control message delivery — immediate steering vs. sequential queueing |
| [Session Persistence](./session-persistence.md) | Resume sessions across restarts, manage session storage |
+| [Remote Sessions](./remote-sessions.md) | Share sessions to GitHub web and mobile via Mission Control |
## Related
diff --git a/docs/features/remote-sessions.md b/docs/features/remote-sessions.md
new file mode 100644
index 000000000..b935287ce
--- /dev/null
+++ b/docs/features/remote-sessions.md
@@ -0,0 +1,163 @@
+# Remote Sessions
+
+Remote sessions let users access their Copilot session from GitHub web and mobile via [Mission Control](https://github.com). When enabled, the SDK connects each session to Mission Control, producing a URL that can be shared as a link or QR code.
+
+## Prerequisites
+
+- The user must be authenticated (GitHub token or logged-in user)
+- The session's working directory must be a GitHub repository
+
+## Enabling Remote Sessions
+
+### Always-on (client-level)
+
+Set `remote: true` when creating the client. Every session in a GitHub repo automatically gets a remote URL.
+
+
+
+#### **TypeScript**
+
+
+```typescript
+import { CopilotClient } from "@github/copilot-sdk";
+
+const client = new CopilotClient({ remote: true });
+const session = await client.createSession({
+ workingDirectory: "/path/to/github-repo",
+ onPermissionRequest: async () => ({ allowed: true }),
+});
+
+session.on("session.info", (event) => {
+ if (event.data.infoType === "remote") {
+ console.log("Remote URL:", event.data.url);
+ }
+});
+```
+
+#### **Python**
+
+
+```python
+from copilot import CopilotClient, SubprocessConfig
+
+client = CopilotClient(SubprocessConfig(remote=True))
+session = await client.create_session(
+ working_directory="/path/to/github-repo",
+ on_permission_request=lambda req: {"allowed": True},
+)
+
+def on_event(event):
+ if event.type == "session.info" and event.data.info_type == "remote":
+ print(f"Remote URL: {event.data.url}")
+
+session.on(on_event)
+```
+
+#### **Go**
+
+
+```go
+client, _ := copilot.NewClient(&copilot.ClientOptions{Remote: true})
+session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
+ WorkingDirectory: "/path/to/github-repo",
+ OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
+ return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
+ },
+})
+
+session.On(func(event copilot.SessionEvent) {
+ if event.Type == "session.info" {
+ // Check infoType and extract URL
+ }
+})
+```
+
+#### **C#**
+
+
+```csharp
+var client = new CopilotClient(new CopilotClientOptions { Remote = true });
+var session = await client.CreateSessionAsync(new SessionConfig
+{
+ WorkingDirectory = "/path/to/github-repo",
+ OnPermissionRequest = (req, inv) =>
+ Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved }),
+});
+
+session.On((SessionEvent e) =>
+{
+ if (e is SessionInfoEvent info && info.Data.InfoType == "remote")
+ {
+ Console.WriteLine($"Remote URL: {info.Data.Url}");
+ }
+});
+```
+
+
+
+### On-demand (per-session toggle)
+
+Use `session.rpc.remote.enable()` to start remote access mid-session, and `session.rpc.remote.disable()` to stop it. This is equivalent to the CLI's `/remote on` and `/remote off` commands.
+
+
+
+#### **TypeScript**
+
+
+```typescript
+const result = await session.rpc.remote.enable();
+console.log("Remote URL:", result.url);
+
+// Later: stop sharing
+await session.rpc.remote.disable();
+```
+
+#### **Python**
+
+
+```python
+result = await session.rpc.remote.enable()
+print(f"Remote URL: {result.url}")
+
+# Later: stop sharing
+await session.rpc.remote.disable()
+```
+
+#### **Go**
+
+
+```go
+result, err := session.RPC.Remote.Enable(ctx)
+fmt.Println("Remote URL:", *result.URL)
+
+// Later: stop sharing
+err = session.RPC.Remote.Disable(ctx)
+```
+
+#### **C#**
+
+
+```csharp
+var result = await session.Rpc.Remote.EnableAsync();
+Console.WriteLine($"Remote URL: {result.Url}");
+
+// Later: stop sharing
+await session.Rpc.Remote.DisableAsync();
+```
+
+
+
+## QR Code Generation
+
+The remote URL can be rendered as a QR code for easy mobile access. The SDK provides the URL — use your preferred QR code library:
+
+- **TypeScript**: [qrcode](https://www.npmjs.com/package/qrcode)
+- **Python**: [qrcode](https://pypi.org/project/qrcode/)
+- **Go**: [go-qrcode](https://github.com/skip2/go-qrcode)
+- **C#**: [QRCoder](https://www.nuget.org/packages/QRCoder)
+
+## Notes
+
+- The `remote` client option only applies when the SDK spawns the CLI process. It is ignored when connecting to an external server via `cliUrl`.
+- If the working directory is not a GitHub repository, remote setup is silently skipped (always-on mode) or returns an error (on-demand mode).
+- Remote sessions require authentication. Ensure `gitHubToken` or `useLoggedInUser` is configured.
diff --git a/docs/index.md b/docs/index.md
index 1b89439ae..89936df73 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -48,6 +48,7 @@ Guides for building with the SDK's capabilities.
- [Streaming Events](./features/streaming-events.md) — real-time event reference
- [Steering & Queueing](./features/steering-and-queueing.md) — message delivery modes
- [Session Persistence](./features/session-persistence.md) — resume sessions across restarts
+- [Remote Sessions](./features/remote-sessions.md) — share sessions to GitHub web and mobile
### [Hooks Reference](./hooks/index.md)
diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs
index cfe37cf77..7b2fb4ad1 100644
--- a/dotnet/src/Client.cs
+++ b/dotnet/src/Client.cs
@@ -1225,6 +1225,11 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
args.AddRange(["--session-idle-timeout", options.SessionIdleTimeoutSeconds.Value.ToString(CultureInfo.InvariantCulture)]);
}
+ if (options.Remote)
+ {
+ args.Add("--remote");
+ }
+
var (fileName, processArgs) = ResolveCliCommand(cliPath, args);
var startInfo = new ProcessStartInfo
@@ -1261,6 +1266,7 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
startInfo.Environment["COPILOT_CONNECTION_TOKEN"] = connectionToken;
}
+ // Set COPILOT_HOME if configured
if (!string.IsNullOrEmpty(options.CopilotHome))
{
startInfo.Environment["COPILOT_HOME"] = options.CopilotHome;
diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs
index 295c146b9..40b902044 100644
--- a/dotnet/src/Generated/Rpc.cs
+++ b/dotnet/src/Generated/Rpc.cs
@@ -2476,6 +2476,34 @@ internal sealed class SessionUsageGetMetricsRequest
public string SessionId { get; set; } = string.Empty;
}
+/// RPC data type for RemoteEnable operations.
+public sealed class RemoteEnableResult
+{
+ /// Whether remote steering is enabled.
+ [JsonPropertyName("remoteSteerable")]
+ public bool RemoteSteerable { get; set; }
+
+ /// Mission Control frontend URL for this session.
+ [JsonPropertyName("url")]
+ public string? Url { get; set; }
+}
+
+/// RPC data type for SessionRemoteEnable operations.
+internal sealed class SessionRemoteEnableRequest
+{
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
+/// RPC data type for SessionRemoteDisable operations.
+internal sealed class SessionRemoteDisableRequest
+{
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
/// Describes a filesystem error.
public sealed class SessionFsError
{
@@ -3419,6 +3447,7 @@ internal SessionRpc(JsonRpc rpc, string sessionId)
Shell = new ShellApi(rpc, sessionId);
History = new HistoryApi(rpc, sessionId);
Usage = new UsageApi(rpc, sessionId);
+ Remote = new RemoteApi(rpc, sessionId);
}
/// Auth APIs.
@@ -3484,6 +3513,9 @@ internal SessionRpc(JsonRpc rpc, string sessionId)
/// Usage APIs.
public UsageApi Usage { get; }
+ /// Remote APIs.
+ public RemoteApi Remote { get; }
+
/// Calls "session.suspend".
public async Task SuspendAsync(CancellationToken cancellationToken = default)
{
@@ -4163,6 +4195,33 @@ public async Task GetMetricsAsync(CancellationToken cance
}
}
+/// Provides session-scoped Remote APIs.
+public sealed class RemoteApi
+{
+ private readonly JsonRpc _rpc;
+ private readonly string _sessionId;
+
+ internal RemoteApi(JsonRpc rpc, string sessionId)
+ {
+ _rpc = rpc;
+ _sessionId = sessionId;
+ }
+
+ /// Calls "session.remote.enable".
+ public async Task EnableAsync(CancellationToken cancellationToken = default)
+ {
+ var request = new SessionRemoteEnableRequest { SessionId = _sessionId };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.remote.enable", [request], cancellationToken);
+ }
+
+ /// Calls "session.remote.disable".
+ public async Task DisableAsync(CancellationToken cancellationToken = default)
+ {
+ var request = new SessionRemoteDisableRequest { SessionId = _sessionId };
+ await CopilotClient.InvokeRpcAsync(_rpc, "session.remote.disable", [request], cancellationToken);
+ }
+}
+
/// Handles `sessionFs` client session API methods.
public interface ISessionFsHandler
{
@@ -4355,6 +4414,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func
@@ -195,6 +196,15 @@ public string? GithubToken
///
public string? TcpConnectionToken { get; set; }
+ ///
+ /// Enable remote session support (Mission Control integration).
+ /// When true, sessions in a GitHub repository working directory are
+ /// accessible from GitHub web and mobile.
+ /// This option is only used when the SDK spawns the CLI process; it is ignored
+ /// when connecting to an external server via .
+ ///
+ public bool Remote { get; set; }
+
///
/// Creates a shallow clone of this instance.
///
diff --git a/go/client.go b/go/client.go
index b61960a28..73ba8b2fe 100644
--- a/go/client.go
+++ b/go/client.go
@@ -235,6 +235,7 @@ func NewClient(options *ClientOptions) *Client {
opts.CopilotHome = options.CopilotHome
}
opts.SessionIdleTimeoutSeconds = options.SessionIdleTimeoutSeconds
+ opts.Remote = options.Remote
}
// Default Env to current environment if not set
@@ -1460,6 +1461,10 @@ func (c *Client) startCLIServer(ctx context.Context) error {
args = append(args, "--session-idle-timeout", strconv.Itoa(c.options.SessionIdleTimeoutSeconds))
}
+ if c.options.Remote {
+ args = append(args, "--remote")
+ }
+
// If CLIPath is a .js file, run it with node
// Note we can't rely on the shebang as Windows doesn't support it
command := cliPath
@@ -1491,6 +1496,11 @@ func (c *Client) startCLIServer(ctx context.Context) error {
c.process.Env = setEnvValue(c.process.Env, "COPILOT_HOME", c.options.CopilotHome)
}
+ // Set COPILOT_HOME if configured
+ if c.options.CopilotHome != "" {
+ c.process.Env = append(c.process.Env, "COPILOT_HOME="+c.options.CopilotHome)
+ }
+
if c.options.Telemetry != nil {
t := c.options.Telemetry
c.process.Env = setEnvValue(c.process.Env, "COPILOT_OTEL_ENABLED", "true")
diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go
index dd5ff61b8..86a1e63c3 100644
--- a/go/rpc/generated_rpc.go
+++ b/go/rpc/generated_rpc.go
@@ -246,6 +246,7 @@ type RPCTypes struct {
UIElicitationStringOneOfField UIElicitationStringOneOfField `json:"UIElicitationStringOneOfField"`
UIElicitationStringOneOfFieldOneOf UIElicitationStringOneOfFieldOneOf `json:"UIElicitationStringOneOfFieldOneOf"`
UIHandlePendingElicitationRequest UIHandlePendingElicitationRequest `json:"UIHandlePendingElicitationRequest"`
+ RemoteEnableResult RemoteEnableResult `json:"RemoteEnableResult"`
UsageGetMetricsResult UsageGetMetricsResult `json:"UsageGetMetricsResult"`
UsageMetricsCodeChanges UsageMetricsCodeChanges `json:"UsageMetricsCodeChanges"`
UsageMetricsModelMetric UsageMetricsModelMetric `json:"UsageMetricsModelMetric"`
@@ -1947,6 +1948,13 @@ type UsageGetMetricsResult struct {
TotalUserRequests int64 `json:"totalUserRequests"`
}
+type RemoteEnableResult struct {
+ // Mission Control frontend URL for this session
+ URL *string `json:"url,omitempty"`
+ // Whether remote steering is enabled
+ RemoteSteerable bool `json:"remoteSteerable"`
+}
+
// Aggregated code change metrics
type UsageMetricsCodeChanges struct {
// Number of distinct files modified
@@ -3663,6 +3671,27 @@ func (a *UsageApi) GetMetrics(ctx context.Context) (*UsageGetMetricsResult, erro
return &result, nil
}
+type RemoteApi sessionApi
+
+func (a *RemoteApi) Enable(ctx context.Context) (*RemoteEnableResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ raw, err := a.client.Request("session.remote.enable", req)
+ if err != nil {
+ return nil, err
+ }
+ var result RemoteEnableResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (a *RemoteApi) Disable(ctx context.Context) error {
+ req := map[string]any{"sessionId": a.sessionID}
+ _, err := a.client.Request("session.remote.disable", req)
+ return err
+}
+
// SessionRpc provides typed session-scoped RPC methods.
type SessionRpc struct {
common sessionApi // Reuse a single struct instead of allocating one for each service on the heap.
@@ -3688,6 +3717,7 @@ type SessionRpc struct {
Shell *ShellApi
History *HistoryApi
Usage *UsageApi
+ Remote *RemoteApi
}
func (a *SessionRpc) Suspend(ctx context.Context) (*SuspendResult, error) {
@@ -3752,6 +3782,7 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc {
r.Shell = (*ShellApi)(&r.common)
r.History = (*HistoryApi)(&r.common)
r.Usage = (*UsageApi)(&r.common)
+ r.Remote = (*RemoteApi)(&r.common)
return r
}
diff --git a/go/types.go b/go/types.go
index 2c1f6b67e..039744af8 100644
--- a/go/types.go
+++ b/go/types.go
@@ -90,6 +90,12 @@ type ClientOptions struct {
// This option is only used when the SDK spawns the CLI process; it is ignored
// when connecting to an external server via CLIUrl.
SessionIdleTimeoutSeconds int
+ // Remote enables remote session support (Mission Control integration).
+ // When true, sessions in a GitHub repository working directory are
+ // accessible from GitHub web and mobile.
+ // This option is only used when the SDK spawns the CLI process; it is ignored
+ // when connecting to an external server via CLIUrl.
+ Remote bool
}
// TelemetryConfig configures OpenTelemetry integration for the Copilot CLI process.
diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts
index 3f4f702d0..d85f9e8b4 100644
--- a/nodejs/src/client.ts
+++ b/nodejs/src/client.ts
@@ -385,6 +385,7 @@ export class CopilotClient {
telemetry: options.telemetry,
copilotHome: options.copilotHome,
sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
+ remote: options.remote ?? false,
};
}
@@ -1486,6 +1487,10 @@ export class CopilotClient {
);
}
+ if (this.options.remote) {
+ args.push("--remote");
+ }
+
// Suppress debug/trace output that might pollute stdout
const envWithoutNodeDebug = { ...this.options.env };
delete envWithoutNodeDebug.NODE_DEBUG;
@@ -1499,6 +1504,7 @@ export class CopilotClient {
envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
}
+ // Set COPILOT_HOME if configured
if (this.options.copilotHome) {
envWithoutNodeDebug.COPILOT_HOME = this.options.copilotHome;
}
diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts
index 6836324ab..72f13ae46 100644
--- a/nodejs/src/generated/rpc.ts
+++ b/nodejs/src/generated/rpc.ts
@@ -1477,6 +1477,17 @@ export interface PluginList {
plugins: Plugin[];
}
+export interface RemoteEnableResult {
+ /**
+ * Mission Control frontend URL for this session
+ */
+ url?: string;
+ /**
+ * Whether remote steering is enabled
+ */
+ remoteSteerable: boolean;
+}
+
export interface ServerSkill {
/**
* Unique identifier for the skill
@@ -2495,6 +2506,8 @@ export function createServerRpc(connection: MessageConnection) {
return {
ping: async (params: PingRequest): Promise =>
connection.sendRequest("ping", params),
+ connect: async (params: ConnectRequest): Promise =>
+ connection.sendRequest("connect", params),
models: {
list: async (params?: ModelsListRequest): Promise =>
connection.sendRequest("models.list", params),
@@ -2722,6 +2735,12 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
getMetrics: async (): Promise =>
connection.sendRequest("session.usage.getMetrics", { sessionId }),
},
+ remote: {
+ enable: async (): Promise =>
+ connection.sendRequest("session.remote.enable", { sessionId }),
+ disable: async (): Promise =>
+ connection.sendRequest("session.remote.disable", { sessionId }),
+ },
};
}
diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts
index 960d398a9..05aab9cae 100644
--- a/nodejs/src/types.ts
+++ b/nodejs/src/types.ts
@@ -211,6 +211,16 @@ export interface CopilotClientOptions {
* `useStdio: true` (stdio is pre-authenticated by transport).
*/
tcpConnectionToken?: string;
+
+ /**
+ * Enable remote session support (Mission Control integration).
+ * When true, sessions in a GitHub repository working directory are
+ * accessible from GitHub web and mobile.
+ * This option is only used when the SDK spawns the CLI process; it is ignored
+ * when connecting to an external server via {@link cliUrl}.
+ * @default false
+ */
+ remote?: boolean;
}
/**
diff --git a/python/copilot/client.py b/python/copilot/client.py
index 44f244e9a..9ccdfa128 100644
--- a/python/copilot/client.py
+++ b/python/copilot/client.py
@@ -176,6 +176,14 @@ class SubprocessConfig:
This option is only used when the SDK spawns the CLI process.
"""
+ remote: bool = False
+ """Enable remote session support (Mission Control integration).
+
+ When ``True``, sessions in a GitHub repository working directory are
+ accessible from GitHub web and mobile.
+ This option is only used when the SDK spawns the CLI process.
+ """
+
@dataclass
class ExternalServerConfig:
@@ -2367,6 +2375,9 @@ async def _start_cli_server(self) -> None:
if cfg.session_idle_timeout_seconds is not None and cfg.session_idle_timeout_seconds > 0:
args.extend(["--session-idle-timeout", str(cfg.session_idle_timeout_seconds)])
+ if cfg.remote:
+ args.append("--remote")
+
# If cli_path is a .js file, run it with node
# Note that we can't rely on the shebang as Windows doesn't support it
if cli_path.endswith(".js"):
@@ -2386,6 +2397,8 @@ async def _start_cli_server(self) -> None:
if self._effective_connection_token:
env["COPILOT_CONNECTION_TOKEN"] = self._effective_connection_token
+
+ # Set COPILOT_HOME if configured
if cfg.copilot_home:
env["COPILOT_HOME"] = cfg.copilot_home
diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py
index fc3eb7bdf..f12d97e21 100644
--- a/python/copilot/generated/rpc.py
+++ b/python/copilot/generated/rpc.py
@@ -5036,6 +5036,26 @@ def to_dict(self) -> dict:
result["totalNanoAiu"] = from_union([from_int, from_none], self.total_nano_aiu)
return result
+@dataclass
+class RemoteEnableResult:
+ """Result of enabling remote session."""
+ remote_steerable: bool
+ url: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'RemoteEnableResult':
+ assert isinstance(obj, dict)
+ remote_steerable = from_bool(obj.get("remoteSteerable"))
+ url = from_union([from_str, from_none], obj.get("url"))
+ return RemoteEnableResult(remote_steerable, url)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["remoteSteerable"] = from_bool(self.remote_steerable)
+ if self.url is not None:
+ result["url"] = from_union([from_str, from_none], self.url)
+ return result
+
@dataclass
class WorkspacesGetWorkspaceResult:
workspace: Workspace | None = None
@@ -6798,6 +6818,18 @@ async def get_metrics(self, *, timeout: float | None = None) -> UsageGetMetricsR
return UsageGetMetricsResult.from_dict(await self._client.request("session.usage.getMetrics", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))
+class RemoteApi:
+ def __init__(self, client: "JsonRpcClient", session_id: str):
+ self._client = client
+ self._session_id = session_id
+
+ async def enable(self, *, timeout: float | None = None) -> RemoteEnableResult:
+ return RemoteEnableResult.from_dict(await self._client.request("session.remote.enable", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))
+
+ async def disable(self, *, timeout: float | None = None) -> None:
+ await self._client.request("session.remote.disable", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))
+
+
class SessionRpc:
"""Typed session-scoped RPC methods."""
def __init__(self, client: "JsonRpcClient", session_id: str):
@@ -6824,6 +6856,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str):
self.shell = ShellApi(client, session_id)
self.history = HistoryApi(client, session_id)
self.usage = UsageApi(client, session_id)
+ self.remote = RemoteApi(client, session_id)
async def suspend(self, *, timeout: float | None = None) -> None:
await self._client.request("session.suspend", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))