From 6c161b6659fa40025ad394e37c4b61f3995aa083 Mon Sep 17 00:00:00 2001 From: Jenny Ferries Date: Mon, 3 Aug 2026 18:05:05 -0700 Subject: [PATCH] fix: support Windows Terminal app aliases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 83dd4f49-a03d-44a0-bdd1-8c4f54da8ec2 --- .../signals-dashboard/extension.mjs | 20 ++++++++++++++----- .../signals-dashboard/launch-profile.mjs | 8 ++++++++ .../signals-dashboard/launch-profile.test.mjs | 13 ++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/extensions/signals-dashboard/extension.mjs b/.github/extensions/signals-dashboard/extension.mjs index 9fb8325..2df0abe 100644 --- a/.github/extensions/signals-dashboard/extension.mjs +++ b/.github/extensions/signals-dashboard/extension.mjs @@ -13,6 +13,7 @@ import { joinSession, createCanvas } from "@github/copilot-sdk/extension"; import { buildDeskAgentArgv, isDeskProfile, + isWindowsAppExecutionAlias, normalizeDeskProfile, parsePluginMcpNames, } from "./launch-profile.mjs"; @@ -93,11 +94,17 @@ function trySpawn(cmd, args, opts = {}) { // match, so auto-detection would pick the wrapper and the terminal would then // fail to run it with no fallback. function isExecutableFile(p) { + return probeExecutableFile(p).ok; +} + +function probeExecutableFile(p) { try { - if (!statSync(p).isFile()) return false; + if (!statSync(p).isFile()) return { ok: false, errorCode: null }; if (process.platform !== "win32") accessSync(p, fsConstants.X_OK); - return true; - } catch { return false; } + return { ok: true, errorCode: null }; + } catch (error) { + return { ok: false, errorCode: error?.code || null }; + } } function resolveOnPath(command, { directOnly = false, excludedRoot = null } = {}) { @@ -115,8 +122,11 @@ function resolveOnPath(command, { directOnly = false, excludedRoot = null } = {} for (const ext of exts) { if (directOnly && ![".EXE", ".COM"].includes(ext.toUpperCase())) continue; const candidate = join(dir, command + ext); - if (!isExecutableFile(candidate)) continue; - const resolved = realpathSync(candidate); + const probe = probeExecutableFile(candidate); + const appAlias = directOnly && probe.errorCode === "EACCES" && + isWindowsAppExecutionAlias(candidate, process.env.LOCALAPPDATA); + if (!appAlias && !probe.ok) continue; + const resolved = appAlias ? candidate : realpathSync(candidate); if (excludedRoot && isInsideRoot(excludedRoot, resolved)) continue; return resolved; } diff --git a/.github/extensions/signals-dashboard/launch-profile.mjs b/.github/extensions/signals-dashboard/launch-profile.mjs index dced7b4..265597f 100644 --- a/.github/extensions/signals-dashboard/launch-profile.mjs +++ b/.github/extensions/signals-dashboard/launch-profile.mjs @@ -9,6 +9,14 @@ export function normalizeDeskProfile(value, fallback = "repo") { return isDeskProfile(value) ? value.toLowerCase() : fallback; } +export function isWindowsAppExecutionAlias(candidate, localAppData) { + if (typeof candidate !== "string" || typeof localAppData !== "string") return false; + const normalized = candidate.replaceAll("/", "\\").toLowerCase(); + const root = `${localAppData.replaceAll("/", "\\").replace(/\\+$/, "")}` + + "\\microsoft\\windowsapps\\"; + return normalized.startsWith(root.toLowerCase()) && normalized.endsWith(".exe"); +} + export function parsePluginMcpNames(text) { let parsed; try { parsed = JSON.parse(text); } diff --git a/.github/extensions/signals-dashboard/launch-profile.test.mjs b/.github/extensions/signals-dashboard/launch-profile.test.mjs index 34164a2..c322f28 100644 --- a/.github/extensions/signals-dashboard/launch-profile.test.mjs +++ b/.github/extensions/signals-dashboard/launch-profile.test.mjs @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import { buildDeskAgentArgv, isDeskProfile, + isWindowsAppExecutionAlias, normalizeDeskProfile, parsePluginMcpNames, } from "./launch-profile.mjs"; @@ -15,6 +16,18 @@ test("normalizes supported profiles and defaults unknown values to repo", () => assert.equal(normalizeDeskProfile("other"), "repo"); }); +test("recognizes Windows App Execution Alias paths without trusting repository executables", () => { + assert.equal(isWindowsAppExecutionAlias( + "C:\\Users\\person\\AppData\\Local\\Microsoft\\WindowsApps\\wt.exe", + "C:\\Users\\person\\AppData\\Local"), true); + assert.equal(isWindowsAppExecutionAlias( + "C:\\repo\\wt.exe", + "C:\\Users\\person\\AppData\\Local"), false); + assert.equal(isWindowsAppExecutionAlias( + "C:\\Users\\person\\AppData\\Local\\Microsoft\\WindowsApps\\wt.cmd", + "C:\\Users\\person\\AppData\\Local"), false); +}); + test("extracts enabled plugin-scoped MCP names and rejects unsafe names", () => { const names = parsePluginMcpNames(JSON.stringify({ plugins: [