mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-17 22:56:54 +00:00
5163dc2e4d
* 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
148 lines
5.7 KiB
JavaScript
148 lines
5.7 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import {
|
|
buildDeskAgentArgv,
|
|
isDeskProfile,
|
|
isSafeQuotedWindowsCmdArg,
|
|
isSafeWindowsCmdShim,
|
|
isWindowsAppExecutionAlias,
|
|
normalizeDeskProfile,
|
|
parsePluginMcpNames,
|
|
quoteWindowsCmdArgument,
|
|
} from "./launch-profile.mjs";
|
|
|
|
test("normalizes supported profiles and defaults unknown values to repo", () => {
|
|
assert.equal(isDeskProfile("repo"), true);
|
|
assert.equal(isDeskProfile("CONNECTED"), true);
|
|
assert.equal(isDeskProfile("other"), false);
|
|
assert.equal(normalizeDeskProfile("CONNECTED"), "connected");
|
|
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("quotes trusted cmd shim arguments and rejects percent-bearing paths", () => {
|
|
assert.equal(
|
|
quoteWindowsCmdArgument("C:\\Program Files\\Agency\\agency.cmd"),
|
|
"\"C:\\Program Files\\Agency\\agency.cmd\"");
|
|
assert.equal(quoteWindowsCmdArgument("--scope"), "\"--scope\"");
|
|
assert.equal(isSafeWindowsCmdShim("C:\\Program Files\\Agency\\agency.cmd"), true);
|
|
assert.equal(isSafeWindowsCmdShim("C:\\Users\\%USERNAME%\\agency.cmd"), false);
|
|
// Quoted args may contain parentheses (common workshop folders).
|
|
assert.equal(isSafeQuotedWindowsCmdArg("C:\\Work\\Project (1)"), true);
|
|
assert.equal(isSafeQuotedWindowsCmdArg("C:\\Users\\%USERNAME%\\w"), false);
|
|
assert.equal(isSafeQuotedWindowsCmdArg("C:\\Users\\!DELAY!\\w"), false);
|
|
});
|
|
|
|
test("executes a Windows cmd shim with safe quoting", {
|
|
skip: process.platform !== "win32",
|
|
}, () => {
|
|
const root = mkdtempSync(join(tmpdir(), "workshop-profile-"));
|
|
const shimDir = join(root, "Shim Name");
|
|
mkdirSync(shimDir);
|
|
const shim = join(shimDir, "copilot.cmd");
|
|
writeFileSync(shim, "@echo off\r\necho {\"plugins\":[]}\r\n");
|
|
|
|
const cmd = join(process.env.SystemRoot, "System32", "cmd.exe");
|
|
const commandLine = `"${[shim, "plugins", "list"]
|
|
.map(quoteWindowsCmdArgument)
|
|
.join(" ")}"`;
|
|
const result = spawnSync(cmd, ["/d", "/s", "/c", commandLine], {
|
|
encoding: "utf8",
|
|
windowsHide: true,
|
|
windowsVerbatimArguments: true,
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.match(result.stdout, /\{"plugins":\[\]\}/);
|
|
});
|
|
|
|
test("extracts enabled plugin-scoped MCP names and rejects unsafe names", () => {
|
|
const names = parsePluginMcpNames(JSON.stringify({
|
|
plugins: [
|
|
{ kind: "mcp", name: "teams", scope: "plugin", enabled: true },
|
|
{ kind: "mcp", name: "repo-mcp", source: "plugin", enabled: true },
|
|
{ kind: "mcp", name: "teams", scope: "plugin", enabled: true },
|
|
{ kind: "mcp", name: "disabled", scope: "plugin", enabled: false },
|
|
{ kind: "mcp", name: "workspace", scope: "repository", enabled: true },
|
|
{ kind: "skill", name: "not-an-mcp", scope: "plugin", enabled: true },
|
|
{ kind: "mcp", name: "bad;name", scope: "plugin", enabled: true },
|
|
],
|
|
}));
|
|
|
|
assert.deepEqual(names, ["teams", "repo-mcp"]);
|
|
assert.deepEqual(parsePluginMcpNames(`Agency startup\n${JSON.stringify({
|
|
plugins: [{ kind: "mcp", name: "ado", scope: "plugin", enabled: true }],
|
|
})}`), ["ado"]);
|
|
assert.equal(parsePluginMcpNames("not json"), null);
|
|
assert.equal(parsePluginMcpNames("{}"), null);
|
|
});
|
|
|
|
test("builds an Agency repo profile on top of the existing wrapper", () => {
|
|
assert.deepEqual(buildDeskAgentArgv({
|
|
deskName: "cost-desk",
|
|
workshopDir: "C:\\workshop",
|
|
useAgency: true,
|
|
agencyCommand: "C:\\tools\\agency.exe",
|
|
profile: "repo",
|
|
pluginMcpNames: ["teams", "ado"],
|
|
}), [
|
|
"C:\\tools\\agency.exe", "copilot", "--no-default-mcps",
|
|
"--disable-mcp-server", "teams",
|
|
"--disable-mcp-server", "ado",
|
|
"--add-dir", "C:\\workshop",
|
|
]);
|
|
});
|
|
|
|
test("builds a plain Copilot repo profile without Agency-only flags", () => {
|
|
assert.deepEqual(buildDeskAgentArgv({
|
|
deskName: "cost-desk",
|
|
workshopDir: "/workshop",
|
|
useAgency: false,
|
|
copilotCommand: "/usr/local/bin/copilot",
|
|
profile: "repo",
|
|
pluginMcpNames: ["calendar"],
|
|
}), [
|
|
"/usr/local/bin/copilot", "--name", "cost-desk",
|
|
"--disable-mcp-server", "calendar",
|
|
"--add-dir", "/workshop",
|
|
]);
|
|
});
|
|
|
|
test("connected preserves tools while Agency discovery failure still removes defaults", () => {
|
|
assert.deepEqual(buildDeskAgentArgv({
|
|
deskName: "cost-desk",
|
|
workshopDir: "/workshop",
|
|
useAgency: true,
|
|
profile: "connected",
|
|
pluginMcpNames: ["teams"],
|
|
}), [
|
|
"agency", "copilot", "--add-dir", "/workshop",
|
|
]);
|
|
|
|
assert.deepEqual(buildDeskAgentArgv({
|
|
deskName: "cost-desk",
|
|
workshopDir: "/workshop",
|
|
useAgency: true,
|
|
profile: "repo",
|
|
pluginMcpNames: [],
|
|
discoverySucceeded: false,
|
|
}), [
|
|
"agency", "copilot", "--no-default-mcps", "--add-dir", "/workshop",
|
|
]);
|
|
});
|