Files
awesome-copilot/extensions/signals-dashboard/launch-profile.mjs
T
jennyf19 5163dc2e4d feat: add fail-closed Local Delegation to Cairn (signals-dashboard 0.3.0) (#2666)
* feat: add fail-closed Local Delegation to Cairn dashboard

Port the-workshop Local Delegation seam into signals-dashboard 0.3.0.
Orthogonal off/on control beside repo/connected profiles; enable only when
local-agent-delegation is installed and a qualified route receipt is present.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

* fix: address Local Delegation review (skill path + README catalog)

- Walk marketplace/plugin and _direct install roots for local-agent-delegation
- Update plugins/signals-dashboard README and regenerate docs/README.plugins.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

* fix: keep Local Delegation Windows launches from reparsing -i prompt

Long LD orientation text split under wt/cmd on Windows (0x80070002). Keep
-i short/quote-free; rely on WORKSHOP_LOCAL_DELEGATION=enabled (+ skill)
for policy. Add charset/length guard tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

* fix: surface Local Delegation effective state on open toast and badge

Keep -i short with one optional ASCII line. Show operators
"Local Delegation effective · route <id>" on open and in the summary control.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

* fix: address Local Delegation GHCP review comments

- Cross-platform path.join in receipt fixture
- Atomic .local-delegation.json write (no symlink follow)
- Case-insensitive WORKSHOP_LOCAL_DELEGATION env clear
- Windows Terminal: set/clear env via cmd before agent
- aria-pressed on Local toggle

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

* fix: store Local Delegation preference user-locally, not in the workshop repo

A cloned workshop must not be able to ship preference:on. Key operator intent
under ~/.copilot/workshop-local-delegation/ by canonical workshop path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

* fix: preserve path case in Local Delegation preference key

Lowercasing collided distinct workshops on case-sensitive filesystems.
Normalize separators only; realpath already supplies a stable path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

* fix: allow parentheses in quoted Windows Local Delegation launches

Args are quote-wrapped; only block % and ! expanders inside quotes so paths
like C:\Work\Project (1) still launch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

* fix: apply quoted-arg Windows safety guard in launch path

Complete the parentheses fix: use isSafeQuotedWindowsCmdArg for cmdSafe.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750

---------

Co-authored-by: Jenny Ferries <jenny.ferries@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Michael Recachinas <mrecachinas@github.com>
Copilot-Session: c39b7696-c854-40c2-b2d6-cce84ba09750
2026-08-17 15:42:37 +10:00

99 lines
3.1 KiB
JavaScript

const PROFILES = new Set(["repo", "connected"]);
const SAFE_MCP_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
export function isDeskProfile(value) {
return typeof value === "string" && PROFILES.has(value.toLowerCase());
}
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 quoteWindowsCmdArgument(value) {
return `"${String(value).replaceAll('"', '""')}"`;
}
export function isSafeWindowsCmdShim(value) {
return typeof value === "string" && !/[%\r\n]/.test(value);
}
/**
* Args that will be wrapped with quoteWindowsCmdArgument. Inside double quotes,
* cmd still expands %VAR% and !VAR! (delayed expansion). Other metacharacters
* like & | < > ^ ( ) are literal when quoted, so paths such as
* C:\Work\Project (1) must be allowed.
*/
export function isSafeQuotedWindowsCmdArg(value) {
return typeof value === "string" && !/[%!\r\n]/.test(value);
}
export function parsePluginMcpNames(text) {
let parsed;
try { parsed = JSON.parse(text); }
catch {
// Agency may prefix its pass-through command with human-readable startup
// lines. Its underlying Copilot JSON is the final object in stdout.
let start = text.lastIndexOf("{");
while (start >= 0) {
try {
parsed = JSON.parse(text.slice(start));
break;
} catch {
start = text.lastIndexOf("{", start - 1);
}
}
if (start < 0) return null;
}
if (!Array.isArray(parsed?.plugins)) return null;
const names = [];
const seen = new Set();
for (const entry of parsed.plugins) {
if (entry?.kind !== "mcp" || entry.enabled === false) continue;
if (entry.scope !== "plugin" && entry.source !== "plugin") continue;
if (typeof entry.name !== "string" || !SAFE_MCP_NAME.test(entry.name)) continue;
if (!seen.has(entry.name)) {
seen.add(entry.name);
names.push(entry.name);
}
}
return names;
}
export function buildDeskAgentArgv({
deskName,
workshopDir,
useAgency,
agencyCommand = "agency",
copilotCommand = "copilot",
profile = "repo",
pluginMcpNames = [],
discoverySucceeded = true,
}) {
const argv = useAgency
? [agencyCommand, "copilot"]
: [copilotCommand, "--name", deskName];
if (profile === "repo") {
if (useAgency) argv.push("--no-default-mcps");
}
if (profile === "repo" && discoverySucceeded) {
for (const name of pluginMcpNames) {
if (SAFE_MCP_NAME.test(name)) argv.push("--disable-mcp-server", name);
}
}
argv.push("--add-dir", workshopDir);
return argv;
}