diff --git a/Directory.Build.targets b/Directory.Build.targets
index 5ec9801ce..48f354345 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -90,4 +90,11 @@
+
+
+
diff --git a/build/Reactor.CopilotCli.targets b/build/Reactor.CopilotCli.targets
new file mode 100644
index 000000000..40f3457dd
--- /dev/null
+++ b/build/Reactor.CopilotCli.targets
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+ <_ReactorCopilotManage>true
+ true
+
+ <_ReactorCopilotCacheDir>$(IntermediateOutputPath)copilot-cli\$(CopilotCliVersion)\$(_CopilotPlatform)
+ <_ReactorCopilotCacheBin>$(_ReactorCopilotCacheDir)\$(_CopilotBinary)
+ <_ReactorCopilotRegistry>$([System.String]::Copy('$(CopilotNpmRegistryUrl)').TrimEnd('/'))
+ <_ReactorCopilotRegistry Condition="'$(_ReactorCopilotRegistry)' == ''">https://registry.npmjs.org
+ <_ReactorCopilotRegistryIsPublic>false
+ <_ReactorCopilotRegistryIsPublic Condition="'$(_ReactorCopilotRegistry)' == 'https://registry.npmjs.org'">true
+
+
+
+
+
+
+ <_ReactorCopilotTgzUrl>$(_ReactorCopilotRegistry)/@github/copilot-$(_CopilotPlatform)/-/copilot-$(_CopilotPlatform)-$(CopilotCliVersion).tgz
+ <_ReactorCopilotArchive>$(_ReactorCopilotCacheDir)\copilot.tgz
+ <_ReactorCopilotNpmTgz>$(_ReactorCopilotCacheDir)\github-copilot-$(_CopilotPlatform)-$(CopilotCliVersion).tgz
+ <_ReactorCopilotNpmRegistryArg Condition="'$(_ReactorCopilotRegistryIsPublic)' != 'true'"> --registry $(_ReactorCopilotRegistry)
+ <_ReactorCopilotTar Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(SystemRoot)\System32\tar.exe
+ <_ReactorCopilotTar Condition="'$(_ReactorCopilotTar)' == ''">tar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ReactorCopilotRuntimeNode>$(_ReactorCopilotCacheDir)\prebuilds\$(_CopilotPlatform)\runtime.node
+
+
+
+
+
+
+
+
+
+
+ <_ReactorCopilotOutDir>$(OutDir)runtimes\$(_CopilotRid)\native
+ <_ReactorCopilotRuntimeNode>$(_ReactorCopilotCacheDir)\prebuilds\$(_CopilotPlatform)\runtime.node
+
+
+
+
+
+
+
diff --git a/build/pipelines/templates/reactor-build-steps.yml b/build/pipelines/templates/reactor-build-steps.yml
index 5333c4b78..a11cc5984 100644
--- a/build/pipelines/templates/reactor-build-steps.yml
+++ b/build/pipelines/templates/reactor-build-steps.yml
@@ -27,7 +27,13 @@ parameters:
- name: npmRegistry
displayName: 'Internal npm registry (ADO feed) for transitive npm downloads.'
type: string
- default: 'https://github-private.pkgs.visualstudio.com/microsoft/_packaging/microsoft-ui-reactor/npm/registry/'
+ # Modern pkgs.dev.azure.com domain (org/project scoped), matching nuget.internal.config's
+ # feed. Equivalent to the legacy https://github-private.pkgs.visualstudio.com/microsoft/...
+ # form; the pkgs.dev.azure.com domain is Microsoft's preferred/required endpoint (agent
+ # firewall rules, reliability). The `microsoft-ui-reactor` feed proxies registry.npmjs.org
+ # via its npm upstream source, so transitive npm downloads (e.g. the GitHub.Copilot.SDK CLI)
+ # resolve internally with feed auth.
+ default: 'https://pkgs.dev.azure.com/github-private/microsoft/_packaging/microsoft-ui-reactor/npm/registry/'
- name: version
displayName: 'Explicit version; "auto" or blank => MinVer.'
type: string
diff --git a/samples/apps/widget-creator/README.md b/samples/apps/widget-creator/README.md
index 100aef5dd..66d0e1e6a 100644
--- a/samples/apps/widget-creator/README.md
+++ b/samples/apps/widget-creator/README.md
@@ -69,6 +69,28 @@ dotnet run --project samples/apps/widget-creator/widget-creator.csproj -p:Platfo
feed. Run `mur pack-local` if it's missing. Override the feed path with
`WIDGET_CREATOR_NUPKGS`.
+### The Copilot CLI at build time (internal vs external)
+
+The `GitHub.Copilot.SDK` bundles a native `copilot.exe` that it downloads at
+**build** time from an npm registry. The repo makes this resilient for everyone
+(`build/Reactor.CopilotCli.targets`), so `dotnet build` / `dotnet run` never
+hard-fails on a restricted network:
+
+- **External / OSS** — the committed `nuget.config` + `.npmrc` stay on public
+ `nuget.org` / `registry.npmjs.org`; the CLI downloads directly. Nothing to set up.
+- **Internal (Microsoft)** — point npm at the internal Azure Artifacts feed
+ (`https://pkgs.dev.azure.com/github-private/microsoft/_packaging/microsoft-ui-reactor/npm/registry/`)
+ via an authenticated `.npmrc` (CI does this automatically; locally use
+ `az artifacts` or the feed's *Connect to feed* → npm instructions). The build then
+ acquires the CLI through that feed with `npm pack`, since Azure Artifacts feeds
+ require auth that the SDK's raw download can't provide.
+- **Offline / no feed** — the build still succeeds *without* a bundled CLI (a
+ `REACTORCLI001` warning is emitted). To run generation you then need a Copilot
+ CLI binary available at run time: set `COPILOT_CLI_PATH` to a local `copilot.exe`
+ (or install the standalone Copilot CLI in its default per-user location). Note
+ `gh auth` only supplies Copilot credentials — it is not itself a runnable
+ `copilot.exe`.
+
Type a prompt, click **Generate & Run**. The generated source streams into the
right panel; the build + `wxc-exec` log streams below it. The widget window opens
sandboxed — close it to finish the run. If it crashes instead, the creator keeps
diff --git a/samples/apps/widget-creator/Services/CopilotSdkClient.cs b/samples/apps/widget-creator/Services/CopilotSdkClient.cs
index 03b507837..f2235ecff 100644
--- a/samples/apps/widget-creator/Services/CopilotSdkClient.cs
+++ b/samples/apps/widget-creator/Services/CopilotSdkClient.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
@@ -42,6 +44,18 @@ async Task GetClientAsync(CancellationToken ct)
{
if (_client is not null) return _client;
var options = new CopilotClientOptions();
+ // The SDK's default stdio transport looks ONLY for a CLI bundled next to
+ // the app (runtimes//native/copilot.exe) and does not consult
+ // COPILOT_CLI_PATH. On builds where the CLI could not be bundled at build
+ // time (offline / restricted-network / authenticated-feed environments —
+ // see build/Reactor.CopilotCli.targets), resolve one at run time and pass
+ // it explicitly so generation still works.
+ var cliPath = ResolveCliPath();
+ if (cliPath is not null)
+ {
+ SessionLog.Write($"[CopilotSdk] no bundled CLI; using resolved CLI at {cliPath}");
+ options.Connection = RuntimeConnection.ForStdio(path: cliPath);
+ }
var client = new CopilotClient(options);
SessionLog.Write($"[CopilotSdk] starting CLI server (model={_model})");
await client.StartAsync().ConfigureAwait(false);
@@ -55,6 +69,44 @@ async Task GetClientAsync(CancellationToken ct)
}
}
+ ///
+ /// Resolves a Copilot CLI to spawn when the SDK's bundled binary is absent.
+ /// Returns null when the bundled CLI is present (let the SDK use it) or
+ /// when nothing could be found (let the SDK throw its descriptive error).
+ /// Order: bundled next to the app → COPILOT_CLI_PATH → a locally
+ /// installed Copilot CLI in its well-known per-user location. We deliberately
+ /// do NOT probe PATH for copilot.exe — that would let any
+ /// unrelated (or malicious) binary earlier on PATH be launched. To use
+ /// a CLI in a non-standard location, set COPILOT_CLI_PATH explicitly.
+ ///
+ static string? ResolveCliPath()
+ {
+ var rid = "win-" + (RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "arm64" : "x64");
+ var bundled = Path.Combine(AppContext.BaseDirectory, "runtimes", rid, "native", "copilot.exe");
+ if (File.Exists(bundled))
+ return null; // SDK's default resolution works.
+
+ var env = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH");
+ if (!string.IsNullOrWhiteSpace(env) && File.Exists(env))
+ return env;
+
+ foreach (var candidate in EnumerateInstalledCliPaths())
+ if (File.Exists(candidate))
+ return candidate;
+
+ return null;
+ }
+
+ static IEnumerable EnumerateInstalledCliPaths()
+ {
+ // Standalone GitHub Copilot CLI install (winget / gh), in its well-known
+ // per-user location. Intentionally a fixed, trusted path — never a PATH
+ // scan, so an unrelated copilot.exe on PATH can't be picked up and run.
+ var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ if (!string.IsNullOrEmpty(localAppData))
+ yield return Path.Combine(localAppData, "GitHub CLI", "copilot", "copilot.exe");
+ }
+
public async Task StartConversationAsync(string systemPrompt, CancellationToken ct)
{
var client = await GetClientAsync(ct).ConfigureAwait(false);
diff --git a/samples/apps/widget-creator/Services/MxcBinaryManifest.cs b/samples/apps/widget-creator/Services/MxcBinaryManifest.cs
index 0eaa1274e..055ea249a 100644
--- a/samples/apps/widget-creator/Services/MxcBinaryManifest.cs
+++ b/samples/apps/widget-creator/Services/MxcBinaryManifest.cs
@@ -25,12 +25,12 @@ public static class MxcBinaryManifest
{
["win-arm64"] = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
- ["winhttp-proxy-shim.exe"] = "aca7cd110b09a1045c53a5c9bd73089f63e6c66f0b3d2627a5bdfb55bb524fdc",
- ["wxc-exec.exe"] = "e430d0e4f44f616e91db684f8d825a6dc93e06a1262b8d00bcaac7522a317aab",
- ["wxc-host-prep.exe"] = "3ef702332286a39153fc259310b5021e3de3c191751d7522684f6475f73af5ef",
- ["wxc-test-proxy.exe"] = "1d1a5821a65c9b4aceb2f1788ca54b08d06b92b784daa1926ab978f4a49f1f00",
- ["wxc-windows-sandbox-daemon.exe"] = "fc8079bddf5db77ee4ecea91d7f22a543fbec3618945a7ab97269dcfef3f66b1",
- ["wxc-windows-sandbox-guest.exe"] = "69c972ce4a65d337f15d828e40b92fcb2f89665d32f2cb598606b45c76adfde3",
+ ["winhttp-proxy-shim.exe"] = "f1fdf37c66af032f4472f5de24fae766544d2fac19da75e45cecb38c5437caa8",
+ ["wxc-exec.exe"] = "d12957f434871af4cee0fbccfe12a91a615d3e91d8b81c7713f4141eb1f466df",
+ ["wxc-host-prep.exe"] = "ec91ae4a8d8b537b11c8401f3c1efd7ffc0fb4fba26d0f822f5138aa6cb08aee",
+ ["wxc-test-proxy.exe"] = "553fbc073ff820b674a87e0a348ddb8814031144812e5df2bc87e4ce7ba34ffa",
+ ["wxc-windows-sandbox-daemon.exe"] = "68054c012e8fcea6f8b82336edad58f96debb4d02b6f0a145befd9d7eaf301c1",
+ ["wxc-windows-sandbox-guest.exe"] = "985b88a7871653518f6db0103abf491c2e510be2405bf6a3dff8e64dece7f6f2",
},
["win-x64"] = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
diff --git a/samples/apps/widget-creator/tools/mxc/win-arm64/winhttp-proxy-shim.exe b/samples/apps/widget-creator/tools/mxc/win-arm64/winhttp-proxy-shim.exe
index 3a3536894..a5f26b7e7 100644
Binary files a/samples/apps/widget-creator/tools/mxc/win-arm64/winhttp-proxy-shim.exe and b/samples/apps/widget-creator/tools/mxc/win-arm64/winhttp-proxy-shim.exe differ
diff --git a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-exec.exe b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-exec.exe
index f15b34c5c..18e42fdb4 100644
Binary files a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-exec.exe and b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-exec.exe differ
diff --git a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-host-prep.exe b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-host-prep.exe
index eb23f88ba..cb7d194cc 100644
Binary files a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-host-prep.exe and b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-host-prep.exe differ
diff --git a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-test-proxy.exe b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-test-proxy.exe
index 17fe97a50..ef102584a 100644
Binary files a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-test-proxy.exe and b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-test-proxy.exe differ
diff --git a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-windows-sandbox-daemon.exe b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-windows-sandbox-daemon.exe
index d74b038ec..546076128 100644
Binary files a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-windows-sandbox-daemon.exe and b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-windows-sandbox-daemon.exe differ
diff --git a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-windows-sandbox-guest.exe b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-windows-sandbox-guest.exe
index 78aeb591d..f30cc87af 100644
Binary files a/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-windows-sandbox-guest.exe and b/samples/apps/widget-creator/tools/mxc/win-arm64/wxc-windows-sandbox-guest.exe differ