From 3a142fd438d78556974604751a4fd6d40c4aac80 Mon Sep 17 00:00:00 2001 From: Patrick Nikoletich Date: Sun, 3 May 2026 21:36:12 -0700 Subject: [PATCH 1/6] feat: add remote session support across all SDKs Add a `remote` option to CopilotClientOptions in all 4 language SDKs (Node, Python, Go, .NET) that passes `--remote` to the CLI process, enabling Mission Control integration for GitHub web and mobile access. Also adds `session.rpc.remote.enable()` and `session.rpc.remote.disable()` RPC methods for on-demand per-session toggling, providing parity with the CLI's `/remote on` and `/remote off` commands. Changes: - Node: remote option in CopilotClientOptions, generated RPC types - Python: remote field on SubprocessConfig, manual RPC types - Go: Remote field on ClientOptions, manual RPC types - .NET: Remote property on CopilotClientOptions + clone, generated RPC - Docs: new docs/features/remote-sessions.md with all-language examples - Updated docs/features/index.md and docs/index.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/index.md | 1 + docs/features/remote-sessions.md | 154 +++++++++++++++++++++++++++++++ docs/index.md | 1 + dotnet/src/Client.cs | 5 + dotnet/src/Generated/Rpc.cs | 64 ++++++++++++- dotnet/src/Types.cs | 10 ++ go/client.go | 5 + go/rpc/generated_rpc.go | 31 +++++++ go/types.go | 6 ++ nodejs/src/client.ts | 5 + nodejs/src/generated/rpc.ts | 19 ++++ nodejs/src/types.ts | 10 ++ python/copilot/client.py | 11 +++ python/copilot/generated/rpc.py | 33 +++++++ 14 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 docs/features/remote-sessions.md 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..f58438acc --- /dev/null +++ b/docs/features/remote-sessions.md @@ -0,0 +1,154 @@ +# 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) copilot.PermissionResponse { + return copilot.PermissionResponse{Allowed: true} + }, +}) + +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 = async (req, ct) => new() { Allowed = true }, +}); + +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 3ba14bebe..9c6b36fce 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1311,6 +1311,11 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) 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 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 851dcf4e2..5e2a547fc 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 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 0fa9a18d0..4cce207f5 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 b1b6b4f46..9c6494198 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -397,6 +397,7 @@ export class CopilotClient { telemetry: options.telemetry, copilotHome: options.copilotHome, sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0, + remote: options.remote ?? false, }; } @@ -1502,6 +1503,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; 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 59dff3d82..c7c6c8622 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 0e03dcbf7..f406c7f5b 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: @@ -2375,6 +2383,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"): 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)) From c3c877cbbf5e8bc90fd03e805d42a8d40c150f41 Mon Sep 17 00:00:00 2001 From: Patrick Nikoletich Date: Sun, 3 May 2026 21:56:20 -0700 Subject: [PATCH 2/6] fix: add docs-validate skip directives and fix API examples option not in published SDK yet, fragments lack standalone context) - Fix C# permission handler: use Kind=Approved pattern - Fix Go permission handler: use correct 2-param signature Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/remote-sessions.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/features/remote-sessions.md b/docs/features/remote-sessions.md index f58438acc..b935287ce 100644 --- a/docs/features/remote-sessions.md +++ b/docs/features/remote-sessions.md @@ -17,6 +17,7 @@ Set `remote: true` when creating the client. Every session in a GitHub repo auto #### **TypeScript** + ```typescript import { CopilotClient } from "@github/copilot-sdk"; @@ -35,6 +36,7 @@ session.on("session.info", (event) => { #### **Python** + ```python from copilot import CopilotClient, SubprocessConfig @@ -53,12 +55,13 @@ 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) copilot.PermissionResponse { - return copilot.PermissionResponse{Allowed: true} + OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) { + return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil }, }) @@ -71,12 +74,14 @@ session.On(func(event copilot.SessionEvent) { #### **C#** + ```csharp var client = new CopilotClient(new CopilotClientOptions { Remote = true }); var session = await client.CreateSessionAsync(new SessionConfig { WorkingDirectory = "/path/to/github-repo", - OnPermissionRequest = async (req, ct) => new() { Allowed = true }, + OnPermissionRequest = (req, inv) => + Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved }), }); session.On((SessionEvent e) => @@ -98,6 +103,7 @@ Use `session.rpc.remote.enable()` to start remote access mid-session, and `sessi #### **TypeScript** + ```typescript const result = await session.rpc.remote.enable(); console.log("Remote URL:", result.url); @@ -108,6 +114,7 @@ await session.rpc.remote.disable(); #### **Python** + ```python result = await session.rpc.remote.enable() print(f"Remote URL: {result.url}") @@ -118,6 +125,7 @@ await session.rpc.remote.disable() #### **Go** + ```go result, err := session.RPC.Remote.Enable(ctx) fmt.Println("Remote URL:", *result.URL) @@ -128,6 +136,7 @@ err = session.RPC.Remote.Disable(ctx) #### **C#** + ```csharp var result = await session.Rpc.Remote.EnableAsync(); Console.WriteLine($"Remote URL: {result.Url}"); From 4bc255ca13bd1d10daba1f42c1c1ff5606b6af0a Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 6 May 2026 15:36:01 -0400 Subject: [PATCH 3/6] Update to latest runtime --- dotnet/src/Generated/Rpc.cs | 6 +++- go/rpc/generated_rpc.go | 36 ++++++++++++++------- nodejs/package-lock.json | 56 ++++++++++++++++----------------- nodejs/package.json | 2 +- nodejs/src/generated/rpc.ts | 4 +-- python/copilot/generated/rpc.py | 49 ++++++++++++++++------------- rust/src/generated/api_types.rs | 38 ++++++++++++++++++++++ rust/src/generated/rpc.rs | 56 ++++++++++++++++++++++++++++++++- test/harness/package-lock.json | 56 ++++++++++++++++----------------- test/harness/package.json | 2 +- 10 files changed, 211 insertions(+), 94 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 40b902044..cee6ce945 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -2477,6 +2477,7 @@ internal sealed class SessionUsageGetMetricsRequest } /// RPC data type for RemoteEnable operations. +[Experimental(Diagnostics.Experimental)] public sealed class RemoteEnableResult { /// Whether remote steering is enabled. @@ -2489,6 +2490,7 @@ public sealed class RemoteEnableResult } /// RPC data type for SessionRemoteEnable operations. +[Experimental(Diagnostics.Experimental)] internal sealed class SessionRemoteEnableRequest { /// Target session identifier. @@ -2497,6 +2499,7 @@ internal sealed class SessionRemoteEnableRequest } /// RPC data type for SessionRemoteDisable operations. +[Experimental(Diagnostics.Experimental)] internal sealed class SessionRemoteDisableRequest { /// Target session identifier. @@ -4196,6 +4199,7 @@ public async Task GetMetricsAsync(CancellationToken cance } /// Provides session-scoped Remote APIs. +[Experimental(Diagnostics.Experimental)] public sealed class RemoteApi { private readonly JsonRpc _rpc; @@ -4507,4 +4511,4 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func => connection.sendRequest("ping", params), - connect: async (params: ConnectRequest): Promise => - connection.sendRequest("connect", params), models: { list: async (params?: ModelsListRequest): Promise => connection.sendRequest("models.list", params), @@ -2735,6 +2734,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin getMetrics: async (): Promise => connection.sendRequest("session.usage.getMetrics", { sessionId }), }, + /** @experimental */ remote: { enable: async (): Promise => connection.sendRequest("session.remote.enable", { sessionId }), diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index f12d97e21..b18e9c7c3 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -1342,6 +1342,29 @@ def to_dict(self) -> dict: result["version"] = from_union([from_str, from_none], self.version) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class RemoteEnableResult: + remote_steerable: bool + """Whether remote steering is enabled""" + + url: str | None = None + """Mission Control frontend URL for this session""" + + @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 ServerSkill: description: str @@ -5036,26 +5059,6 @@ 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 @@ -5745,6 +5748,7 @@ class RPC: plan_update_request: PlanUpdateRequest plugin: Plugin plugin_list: PluginList + remote_enable_result: RemoteEnableResult server_skill: ServerSkill server_skill_list: ServerSkillList session_auth_status: SessionAuthStatus @@ -5973,6 +5977,7 @@ def from_dict(obj: Any) -> 'RPC': plan_update_request = PlanUpdateRequest.from_dict(obj.get("PlanUpdateRequest")) plugin = Plugin.from_dict(obj.get("Plugin")) plugin_list = PluginList.from_dict(obj.get("PluginList")) + remote_enable_result = RemoteEnableResult.from_dict(obj.get("RemoteEnableResult")) server_skill = ServerSkill.from_dict(obj.get("ServerSkill")) server_skill_list = ServerSkillList.from_dict(obj.get("ServerSkillList")) session_auth_status = SessionAuthStatus.from_dict(obj.get("SessionAuthStatus")) @@ -6067,7 +6072,7 @@ def from_dict(obj: Any) -> 'RPC': workspaces_list_files_result = WorkspacesListFilesResult.from_dict(obj.get("WorkspacesListFilesResult")) workspaces_read_file_request = WorkspacesReadFileRequest.from_dict(obj.get("WorkspacesReadFileRequest")) workspaces_read_file_result = WorkspacesReadFileResult.from_dict(obj.get("WorkspacesReadFileResult")) - return RPC(account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, commands_handle_pending_command_request, commands_handle_pending_command_result, connect_request, connect_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, embedded_blob_resource_contents, embedded_text_resource_contents, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_oauth_login_request, mcp_oauth_login_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, server_skill, server_skill_list, session_auth_status, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_log_level, session_mode, sessions_fork_request, sessions_fork_result, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, shell_kill_signal, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, task_agent_info, task_agent_info_execution_mode, task_agent_info_status, task_info, task_list, tasks_cancel_request, tasks_cancel_result, task_shell_info, task_shell_info_attachment_mode, task_shell_info_execution_mode, task_shell_info_status, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_remove_request, tasks_remove_result, tasks_start_agent_request, tasks_start_agent_result, tool, tool_list, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result) + return RPC(account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, commands_handle_pending_command_request, commands_handle_pending_command_result, connect_request, connect_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, embedded_blob_resource_contents, embedded_text_resource_contents, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_oauth_login_request, mcp_oauth_login_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, remote_enable_result, server_skill, server_skill_list, session_auth_status, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_log_level, session_mode, sessions_fork_request, sessions_fork_result, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, shell_kill_signal, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, task_agent_info, task_agent_info_execution_mode, task_agent_info_status, task_info, task_list, tasks_cancel_request, tasks_cancel_result, task_shell_info, task_shell_info_attachment_mode, task_shell_info_execution_mode, task_shell_info_status, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_remove_request, tasks_remove_result, tasks_start_agent_request, tasks_start_agent_result, tool, tool_list, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result) def to_dict(self) -> dict: result: dict = {} @@ -6201,6 +6206,7 @@ def to_dict(self) -> dict: result["PlanUpdateRequest"] = to_class(PlanUpdateRequest, self.plan_update_request) result["Plugin"] = to_class(Plugin, self.plugin) result["PluginList"] = to_class(PluginList, self.plugin_list) + result["RemoteEnableResult"] = to_class(RemoteEnableResult, self.remote_enable_result) result["ServerSkill"] = to_class(ServerSkill, self.server_skill) result["ServerSkillList"] = to_class(ServerSkillList, self.server_skill_list) result["SessionAuthStatus"] = to_class(SessionAuthStatus, self.session_auth_status) @@ -6818,6 +6824,7 @@ 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))) +# Experimental: this API group is experimental and may change or be removed. class RemoteApi: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 2b78fbae5..d0b7bf5b7 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -152,6 +152,10 @@ pub mod rpc_methods { pub const SESSION_HISTORY_TRUNCATE: &str = "session.history.truncate"; /// `session.usage.getMetrics` pub const SESSION_USAGE_GETMETRICS: &str = "session.usage.getMetrics"; + /// `session.remote.enable` + pub const SESSION_REMOTE_ENABLE: &str = "session.remote.enable"; + /// `session.remote.disable` + pub const SESSION_REMOTE_DISABLE: &str = "session.remote.disable"; /// `sessionFs.readFile` pub const SESSIONFS_READFILE: &str = "sessionFs.readFile"; /// `sessionFs.writeFile` @@ -1279,6 +1283,16 @@ pub struct PluginList { pub plugins: Vec, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoteEnableResult { + /// Whether remote steering is enabled + pub remote_steerable: bool, + /// Mission Control frontend URL for this session + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ServerSkill { @@ -2734,6 +2748,30 @@ pub struct SessionUsageGetMetricsResult { pub total_user_requests: i64, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionRemoteEnableParams { + /// Target session identifier + pub session_id: SessionId, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionRemoteEnableResult { + /// Whether remote steering is enabled + pub remote_steerable: bool, + /// Mission Control frontend URL for this session + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionRemoteDisableParams { + /// Target session identifier + pub session_id: SessionId, +} + /// Authentication type #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AuthInfoType { diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index ee38f27a5..e772c087b 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -8,7 +8,8 @@ #![allow(missing_docs)] #![allow(clippy::too_many_arguments)] -use super::api_types::{rpc_methods, *}; +use super::api_types::rpc_methods; +use super::api_types::*; use crate::session::Session; use crate::{Client, Error}; @@ -437,6 +438,13 @@ impl<'a> SessionRpc<'a> { } } + /// `session.remote.*` sub-namespace. + pub fn remote(&self) -> SessionRpcRemote<'a> { + SessionRpcRemote { + session: self.session, + } + } + /// `session.shell.*` sub-namespace. pub fn shell(&self) -> SessionRpcShell<'a> { SessionRpcShell { @@ -1191,6 +1199,52 @@ impl<'a> SessionRpcPlugins<'a> { } } +/// `session.remote.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcRemote<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcRemote<'a> { + /// Wire method: `session.remote.enable`. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn enable(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_REMOTE_ENABLE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Wire method: `session.remote.disable`. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn disable(&self) -> Result<(), Error> { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_REMOTE_DISABLE, Some(wire_params)) + .await?; + Ok(()) + } +} + /// `session.shell.*` RPCs. #[derive(Clone, Copy)] pub struct SessionRpcShell<'a> { diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index c5bbaabae..9a1b7d587 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.42", + "@github/copilot": "^1.0.43-0", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -464,27 +464,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.42.tgz", - "integrity": "sha512-ODW5+aJi595Tb2WUaAlshBoUkOBuh9MegXXwXzMoar+k9fZzzDy3oNJLFg7ni4UtkUZvj/WL/y3s5O+FlsF2HA==", + "version": "1.0.43-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.43-0.tgz", + "integrity": "sha512-wsSlDIJj6tFIOchzuA+rIYJXP3OwrsMR1j/k8WB/4gxxACm5Q7XSRmH7Ul88k/vXak44sBC9EmJaEgAm6enwbg==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.42", - "@github/copilot-darwin-x64": "1.0.42", - "@github/copilot-linux-arm64": "1.0.42", - "@github/copilot-linux-x64": "1.0.42", - "@github/copilot-win32-arm64": "1.0.42", - "@github/copilot-win32-x64": "1.0.42" + "@github/copilot-darwin-arm64": "1.0.43-0", + "@github/copilot-darwin-x64": "1.0.43-0", + "@github/copilot-linux-arm64": "1.0.43-0", + "@github/copilot-linux-x64": "1.0.43-0", + "@github/copilot-win32-arm64": "1.0.43-0", + "@github/copilot-win32-x64": "1.0.43-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.42.tgz", - "integrity": "sha512-2w89QLRgMR7hWwV1KG3uXqu98WST6afJCfvtYtqvPdf6ZQC7Fj2HhPNCrMxZk/H8mZwTgYJeg30gZjvV1698EA==", + "version": "1.0.43-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.43-0.tgz", + "integrity": "sha512-50YRUf03lUJ110PbKaMRjeIG5pvEgxm6+X4nRuYtEQujp/RUFT2Rd3g6I2f9hfIrdMIxfSNw8KXanuPcrCPZqA==", "cpu": [ "arm64" ], @@ -499,9 +499,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.42.tgz", - "integrity": "sha512-G2//tgGSKXx3ZGMqe774UnewasYMh+j0ZeQ3injtuZpSpzN+OAuNkzwXpvFHprdbgekMb0oAPN+Xm3rHuQY8Xw==", + "version": "1.0.43-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.43-0.tgz", + "integrity": "sha512-LvbJD23zbuGNOvYheS8PPT5l+TLOy6FWRc1qF7hf6PMSsqvDTqseO6Bq5S99A/Z7A/v3kh/xUZglJwrIxtVxvg==", "cpu": [ "x64" ], @@ -516,9 +516,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.42.tgz", - "integrity": "sha512-Ai6J4hUKVuE5ztsLspp/I7ByXtL2V6tF+AOn0q+hHp1MOA5JLq5/G8PE+c0VzG69x4hkt1lROQDjvXJGY7sv+g==", + "version": "1.0.43-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.43-0.tgz", + "integrity": "sha512-gBfW5f5XZIAxkQU7NXQytxImORMbzgrk9WcMfcTuTNgr1OXM1oaKWvXH2vc6ZVZbPhPnAcTymCC0bFOSXQBE2g==", "cpu": [ "arm64" ], @@ -533,9 +533,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.42.tgz", - "integrity": "sha512-yYfuL6Hk3uLQuIgfxpEMCyoowFq2Bew1EaXmvg4lnDjj95tvEmyMCX77aIZ2AKwBOgp1nMV7L1B1QL9/mw6BTA==", + "version": "1.0.43-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.43-0.tgz", + "integrity": "sha512-S/0F05plYabV6siVHBcuGiYzamB/GaEVvt8jMo3CeuPJl6/AZtuU6WMWngWooCQg4PyM/zDfcnvH88xU5NDs7w==", "cpu": [ "x64" ], @@ -550,9 +550,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.42.tgz", - "integrity": "sha512-WgnV6AxsvbvZdNW/42JFikK/SqR1JMw6juRpGKXZr70ond/cHK6trtrmt3dXYPymBO14ppJMFdm4+chJzKGKMw==", + "version": "1.0.43-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.43-0.tgz", + "integrity": "sha512-obpdmbbDF1kuanKyIjb1Mc4+GrNEfLRT2QO0DV20uy0JXRf0duYe92Q40T7vRxfp7MRpfclpdT4wNEkVc2GZVA==", "cpu": [ "arm64" ], @@ -567,9 +567,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.42.tgz", - "integrity": "sha512-J5jtrcYuODuD4LPPRHjOCMJGO6+vKZ71n8PTiHPCg9lpfThXDDXxrB7nDDkhxl23zSXlUrpWwkMI+a2Ax/AxGg==", + "version": "1.0.43-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.43-0.tgz", + "integrity": "sha512-5Kgk1wtoV7dMMf++RLTqjwZfGUkXOrEu5f/2ijMbMVEkjaBIHwxkK3xvPsun3xBU8htJy1PpCSBm2e/2rv78MA==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 874aeca16..29bf7a014 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.42", + "@github/copilot": "^1.0.43-0", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", From a2baee6c83790ef6819cf6dffa54ac0582606de3 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 6 May 2026 15:40:48 -0400 Subject: [PATCH 4/6] CR fixes --- docs/features/remote-sessions.md | 4 +++- dotnet/test/Unit/CloneTests.cs | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/features/remote-sessions.md b/docs/features/remote-sessions.md index b935287ce..3274aa78b 100644 --- a/docs/features/remote-sessions.md +++ b/docs/features/remote-sessions.md @@ -128,7 +128,9 @@ await session.rpc.remote.disable() ```go result, err := session.RPC.Remote.Enable(ctx) -fmt.Println("Remote URL:", *result.URL) +if result.URL != nil { + fmt.Println("Remote URL:", *result.URL) +} // Later: stop sharing err = session.RPC.Remote.Disable(ctx) diff --git a/dotnet/test/Unit/CloneTests.cs b/dotnet/test/Unit/CloneTests.cs index 10e0bbf45..d0b0d5162 100644 --- a/dotnet/test/Unit/CloneTests.cs +++ b/dotnet/test/Unit/CloneTests.cs @@ -27,6 +27,7 @@ public void CopilotClientOptions_Clone_CopiesAllProperties() GitHubToken = "ghp_test", UseLoggedInUser = false, CopilotHome = "/custom/copilot/home", + Remote = true, SessionIdleTimeoutSeconds = 600, }; @@ -45,6 +46,7 @@ public void CopilotClientOptions_Clone_CopiesAllProperties() Assert.Equal(original.GitHubToken, clone.GitHubToken); Assert.Equal(original.UseLoggedInUser, clone.UseLoggedInUser); Assert.Equal(original.CopilotHome, clone.CopilotHome); + Assert.Equal(original.Remote, clone.Remote); Assert.Equal(original.SessionIdleTimeoutSeconds, clone.SessionIdleTimeoutSeconds); } From 1d4d4a2d98cf06c59f65fb79de522827ec8332a3 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 6 May 2026 15:47:20 -0400 Subject: [PATCH 5/6] Equivalent Rust support --- docs/features/remote-sessions.md | 38 +++++++++++++++++++++++++++ rust/src/lib.rs | 44 +++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/docs/features/remote-sessions.md b/docs/features/remote-sessions.md index 3274aa78b..f3e41bd8c 100644 --- a/docs/features/remote-sessions.md +++ b/docs/features/remote-sessions.md @@ -93,6 +93,30 @@ session.On((SessionEvent e) => }); ``` +#### **Rust** + + +```rust +use copilot_sdk::{Client, ClientOptions}; + +let client = Client::start( + ClientOptions::new().with_remote(true) +).await?; +let session = client.create_session( + SessionConfig::new("/path/to/github-repo") + .with_permission_handler(|_req, _inv| async { + Ok(PermissionRequestResult::approved()) + }), +).await?; + +let mut events = session.subscribe(); +while let Ok(event) = events.recv().await { + if event.event_type == "session.info" { + // Check info_type and extract URL + } +} +``` + ### On-demand (per-session toggle) @@ -147,6 +171,19 @@ Console.WriteLine($"Remote URL: {result.Url}"); await session.Rpc.Remote.DisableAsync(); ``` +#### **Rust** + + +```rust +let result = session.rpc().remote().enable().await?; +if let Some(url) = &result.url { + println!("Remote URL: {url}"); +} + +// Later: stop sharing +session.rpc().remote().disable().await?; +``` + ## QR Code Generation @@ -157,6 +194,7 @@ The remote URL can be rendered as a QR code for easy mobile access. The SDK prov - **Python**: [qrcode](https://pypi.org/project/qrcode/) - **Go**: [go-qrcode](https://github.com/skip2/go-qrcode) - **C#**: [QRCoder](https://www.nuget.org/packages/QRCoder) +- **Rust**: [qrcode](https://crates.io/crates/qrcode) ## Notes diff --git a/rust/src/lib.rs b/rust/src/lib.rs index dfe6d8ba3..7c3d2422b 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -393,6 +393,12 @@ pub struct ClientOptions { /// is safe by default. Combining with [`Transport::Stdio`] is invalid /// and surfaces as an error from [`Client::start`]. pub tcp_connection_token: Option, + /// Enable remote session support (Mission Control integration). + /// When `true`, the SDK passes `--remote` to the spawned CLI process so + /// sessions in a GitHub repository working directory are accessible from + /// GitHub web and mobile. Ignored when connecting to an external server + /// via [`Transport::External`]. + pub remote: bool, } impl std::fmt::Debug for ClientOptions { @@ -430,6 +436,7 @@ impl std::fmt::Debug for ClientOptions { "tcp_connection_token", &self.tcp_connection_token.as_ref().map(|_| ""), ) + .field("remote", &self.remote) .finish() } } @@ -636,6 +643,7 @@ impl Default for ClientOptions { telemetry: None, copilot_home: None, tcp_connection_token: None, + remote: false, } } } @@ -793,6 +801,13 @@ impl ClientOptions { self.tcp_connection_token = Some(token.into()); self } + + /// Enable remote session support (Mission Control). Passes `--remote` + /// to the spawned CLI process. + pub fn with_remote(mut self, enabled: bool) -> Self { + self.remote = enabled; + self + } } /// Validate a [`SessionFsConfig`] before sending `sessionFs.setProvider`. @@ -1233,6 +1248,14 @@ impl Client { } } + fn remote_args(options: &ClientOptions) -> Vec { + if options.remote { + vec!["--remote".to_string()] + } else { + Vec::new() + } + } + fn spawn_stdio(program: &Path, options: &ClientOptions) -> Result { info!(cwd = ?options.cwd, program = %program.display(), "spawning copilot CLI (stdio)"); let mut command = Self::build_command(program, options); @@ -1247,6 +1270,7 @@ impl Client { ]) .args(Self::auth_args(options)) .args(Self::session_idle_timeout_args(options)) + .args(Self::remote_args(options)) .args(&options.extra_args) .stdin(Stdio::piped()); Ok(command.spawn()?) @@ -1271,6 +1295,7 @@ impl Client { ]) .args(Self::auth_args(options)) .args(Self::session_idle_timeout_args(options)) + .args(Self::remote_args(options)) .args(&options.extra_args) .stdin(Stdio::null()); let mut child = command.spawn()?; @@ -1901,7 +1926,8 @@ mod tests { .with_github_token("ghp_test") .with_use_logged_in_user(false) .with_log_level(LogLevel::Debug) - .with_session_idle_timeout_seconds(120); + .with_session_idle_timeout_seconds(120) + .with_remote(true); assert!(matches!(opts.program, CliProgram::Path(_))); assert_eq!(opts.prefix_args, vec![std::ffi::OsString::from("node")]); assert_eq!(opts.cwd, PathBuf::from("/tmp")); @@ -1918,6 +1944,7 @@ mod tests { assert_eq!(opts.use_logged_in_user, Some(false)); assert!(matches!(opts.log_level, Some(LogLevel::Debug))); assert_eq!(opts.session_idle_timeout_seconds, Some(120)); + assert!(opts.remote); } #[test] @@ -2233,6 +2260,21 @@ mod tests { ); } + #[test] + fn remote_args_omitted_by_default() { + let opts = ClientOptions::default(); + assert!(Client::remote_args(&opts).is_empty()); + } + + #[test] + fn remote_args_emit_flag_when_enabled() { + let opts = ClientOptions { + remote: true, + ..Default::default() + }; + assert_eq!(Client::remote_args(&opts), vec!["--remote".to_string()]); + } + #[test] fn log_level_str_round_trips() { for level in [ From ec854d90371a4e87de06489ba979c4c923dda64c Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 6 May 2026 15:47:48 -0400 Subject: [PATCH 6/6] Formatting --- rust/src/generated/rpc.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index e772c087b..eed4aea2a 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -8,8 +8,7 @@ #![allow(missing_docs)] #![allow(clippy::too_many_arguments)] -use super::api_types::rpc_methods; -use super::api_types::*; +use super::api_types::{rpc_methods, *}; use crate::session::Session; use crate::{Client, Error};