chore: publish from main

This commit is contained in:
github-actions[bot]
2026-08-17 05:43:12 +00:00
parent 4b887e09a2
commit 3e8da0a7c0
26 changed files with 3329 additions and 159 deletions
+2 -2
View File
@@ -1282,8 +1282,8 @@
{
"name": "signals-dashboard",
"source": "plugins/signals-dashboard",
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, and cost-aware repo or connected desk launch profiles.",
"version": "0.2.0"
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, cost-aware desk profiles, and fail-closed Local Delegation.",
"version": "0.3.0"
},
{
"name": "site-studio",
+1 -1
View File
@@ -102,7 +102,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-plugins) for guidelines on how t
| [rust-mcp-development](../plugins/rust-mcp-development/README.md) | Build high-performance Model Context Protocol servers in Rust using the official rmcp SDK with async/await, procedural macros, and type-safe implementations. | 0 items | rust, mcp, model-context-protocol, server-development, sdk, tokio, async, macros, rmcp |
| [salesforce-development](../plugins/salesforce-development/README.md) | Complete Salesforce agentic development environment covering Apex & Triggers, Flow automation, Lightning Web Components, Aura components, and Visualforce pages. | 0 items | salesforce, apex, triggers, lwc, aura, flow, visualforce, crm, salesforce-dx |
| [security-best-practices](../plugins/security-best-practices/README.md) | Security frameworks, accessibility guidelines, performance optimization, and code quality best practices for building secure, maintainable, and high-performance applications. | 0 items | security, accessibility, performance, code-quality, owasp, a11y, optimization, best-practices |
| [signals-dashboard](../plugins/signals-dashboard/README.md) | Real-time Workshop dashboard with agent signals, honesty calibration, and cost-aware repo or connected desk launch profiles. | 1 items | agent-signals, dashboard, multi-agent, coordination, canvas |
| [signals-dashboard](../plugins/signals-dashboard/README.md) | Real-time Workshop dashboard with agent signals, honesty calibration, cost-aware desk profiles, and fail-closed Local Delegation. | 1 items | agent-signals, dashboard, multi-agent, coordination, canvas, local-delegation |
| [site-studio](../plugins/site-studio/README.md) | Plan, draft, and track a personal website section by section — a shared canvas where you and your agent author content, watch progress, and review every change. | 1 items | agent-collaboration, content-authoring, personal-website, progress-tracking, section-planning, site-builder |
| [skill-image-gen](../plugins/skill-image-gen/README.md) | Generate images using AI directly from your coding workflow. Supports OpenAI (gpt-image-2) and Google Gemini. BYO API key — the skill guides you through setup on first use. | 0 items | image-generation, openai, gemini, ai, art, sprites, textures, icons |
| [software-engineering-team](../plugins/software-engineering-team/README.md) | 7 specialized agents covering the full software development lifecycle from UX design and architecture to security and DevOps. | 0 items | team, enterprise, security, devops, ux, architecture, product, ai-ethics |
+145
View File
@@ -0,0 +1,145 @@
# 🪨 Cairn — Signals Dashboard
A live GitHub Copilot CLI **canvas extension** that shows the pulse of every
desk in your Workshop. It reads the agent signals your desks emit and renders
them as a compact, always-current dashboard in a side panel — so you can direct
the work instead of polling each desk by hand.
It replaces the old Blazor **WorkshopRoom** dashboard (`src/WorkshopRoom/`) with
a native canvas that runs inside GHCP, with no separate web app to launch.
## What it is
Each desk in the Workshop leaves signals behind — small stones on the trail —
by writing JSON files into its `.signals/` folder. Cairn scans those folders,
picks the most recent signal per desk, and renders:
- **Score bars** for the desk's self-assessment (intent, confidence, accuracy,
completeness).
- **Patterns** the desk reported: what worked ✓, what was hard △, and skill
gaps ✗.
- **Escalations** — desks that raised their hand, with what they're blocked on
and their recommendation, pinned to the top and pulsing red.
## How to open it
The dashboard is registered as the **🪨 Cairn** canvas (`signals-dashboard`).
Ask Copilot to open it and pass your workshop root as `workshopDir`:
> Open the 🪨 Cairn canvas with `workshopDir` set to the folder that contains
> my `desks/` directory.
`workshopDir` must be the **absolute path to the workshop root** — the folder
that holds `desks/` (and optionally `classroom/`). If omitted, it falls back to
the current working directory.
## Features
- **Signal scanning** — walks `desks/*/.signals/` and `classroom/*/.signals/`,
reading the newest `*.json` per desk (mirrors `SignalReader.cs`).
- **Score bars** — color-coded intent / confidence / accuracy / completeness,
scored out of 5.
- **Escalation alerts** — escalation signals sort to the very top, render with a
pulsing red border, and surface the blocker + recommendation.
- **Active desks first** — sorted escalations → recent signals → desks with no
signal yet, then by recency.
- **Stash / restore** — pause a workstream by stashing its desk. Stashed desks
drop off the active view and auto-expire after a **48-hour TTL**; restore any
time before then. Stash state lives in `.desk-stash.json` at the workshop
root.
- **Auto-refresh** — the panel refreshes every 5 seconds using a background
fetch (no full page reload), so scores and escalations stay current smoothly.
- **Summary bar** — desk count, how many are reporting vs. awaiting, an
escalation badge, and average scores across the room.
- **Cost-aware desk launch** — **open** starts a repo-profile desk that keeps the
verified Workshop root available while suppressing ambient plugin MCPs.
**connected** preserves every configured MCP for work that needs external
systems. Agency remains the preferred wrapper when installed.
- **Local Delegation** — orthogonal off/on control. When available and on, the
frontier desk may use the installed [`local-agent-delegation`](https://github.com/jennyf19/sealed-delegation)
skill for bounded, independently gated read/evidence work. Fail-closed: no
skill or no qualified route receipt means the toggle cannot take effect, and
no local-savings credit is awarded.
## Agent actions
The canvas also exposes actions Copilot can invoke directly:
- `refresh` — force a rescan and return current signal data as JSON.
- `stash` — stash a desk by `deskName`.
- `restore` — restore a stashed desk by `deskName`.
- `open_desk` — open a desk with optional `profile: "repo" | "connected"` and
optional `localDelegation: "off" | "on"`.
## Desk launch profiles
`repo` is the default. At launch, Cairn asks Copilot for the enabled
plugin-scoped MCP inventory and disables those ambient servers for the topic
desk. User-, workspace-, organization-, and built-in resources are left alone.
If discovery fails, Copilot plugin MCP suppression fails open; Agency repo mode
still omits Agency's own default MCPs.
When Agency is installed, Cairn keeps the existing `agency copilot` launch and
adds Agency's `--no-default-mcps` in repo mode. Outside Agency, the same profile
is applied directly to Copilot CLI.
Both profiles pass `--add-dir <workshop-root>` so a desk can intentionally read
another desk's journal or artifact without receiving access outside the room.
Set `WORKSHOP_DESK_PROFILE=connected` to retain the historical default for the
main **open** button. The separate **connected** button is always available when
repo mode is the default.
## Local Delegation
Local Delegation is **not** a third desk profile and does not replace the frontier
model. It is a separate permission bit:
```text
repo / connected = which MCPs and tools the frontier desk can see
Local Delegation off/on = whether the frontier desk may invoke a bounded local worker
```
Availability is fail-closed. Cairn enables the lane only when:
1. the `local-agent-delegation` skill is installed, and
2. a qualified route is declared via `WORKSHOP_LOCAL_DELEGATION_ROUTE_ID` or a
receipt at `~/.copilot/local-agent-runs/qualified-route.json`
(`status: "qualified"`, safe `route_id`).
Operator preference is stored **user-locally** under
`~/.copilot/workshop-local-delegation/` (keyed by the canonical workshop path),
never in the cloned workshop — a repo cannot ship `preference: on`.
When the preference is on but availability fails, opens still launch as frontier
desks and surface the reason — they never silently fall back with savings credit.
When effective, Cairn sets `WORKSHOP_LOCAL_DELEGATION=enabled` on the launched
process, shows an open toast/badge (`Local Delegation effective · route …`), and
may append one short ASCII line to `-i` (`Local Delegation env is enabled.`) when
that combined prompt stays quote-free and under the length guard. Full policy
still lives in the env flag plus the installed `local-agent-delegation` skill —
never a long multi-sentence `-i` appendix (Windows Terminal reparse).
The runtime, launcher, and gates remain owned by
[Sealed Delegation](https://github.com/jennyf19/sealed-delegation).
## Signal shape
Cairn reads the agent-signals protocol used across the Workshop:
```json
{
"signal_type": "execution",
"agent_name": "desk-name",
"self_assessment": { "intent": 5, "confidence": 4, "accuracy": 4, "completeness": 3 },
"patterns": { "what_worked": "...", "what_was_hard": "...", "skill_gap": "..." },
"escalation": { "reason": "...", "blocked_on": "...", "recommendation": "..." }
}
```
`escalation` is only present on `signal_type: "escalation"` signals.
## Replaces the Blazor WorkshopRoom
This canvas supersedes the standalone Blazor dashboard in `src/WorkshopRoom/`.
The data is the truth and the UI is just a view — Cairn renders the same signal
data natively inside GHCP, so there's no separate server to run.
+238 -47
View File
@@ -5,20 +5,33 @@
import { createServer } from "node:http";
import { statSync, accessSync, realpathSync, constants as fsConstants } from "node:fs";
import { readdir, readFile, writeFile, stat } from "node:fs/promises";
import { join, delimiter, isAbsolute, sep } from "node:path";
import { readdir, readFile, writeFile, stat, rename, unlink, mkdir } from "node:fs/promises";
import { join, delimiter, isAbsolute, sep, dirname } from "node:path";
import { spawn } from "node:child_process";
import { randomBytes } from "node:crypto";
import { joinSession, createCanvas } from "@github/copilot-sdk/extension";
import {
buildDeskAgentArgv,
isDeskProfile,
isSafeQuotedWindowsCmdArg,
isSafeWindowsCmdShim,
isWindowsAppExecutionAlias,
normalizeDeskProfile,
parsePluginMcpNames,
quoteWindowsCmdArgument,
} from "./launch-profile.mjs";
import {
buildLocalDelegationLaunchEnv,
deskOrientPrompt,
formatLocalDelegationOpenNotice,
localDelegationPreferencePath,
normalizeLocalDelegationPreference,
parseLocalDelegationState,
resolveLocalDelegationAvailability,
resolveLocalDelegationLaunch,
serializeLocalDelegationState,
windowsLocalDelegationCmdPrefix,
} from "./local-delegation.mjs";
const servers = new Map();
const STASH_TTL_MS = 48 * 60 * 60 * 1000;
@@ -60,11 +73,10 @@ function isValidDeskName(name) {
// path is then only ever passed as a spawn cwd, an argv element, or a
// single-quoted literal inside the macOS Terminal command — never concatenated
// raw onto a command line — so no character filtering of the path is required.
function deskOrientPrompt(deskName) {
return `You are sitting down at the ${deskName} desk in this workshop. ` +
`Read journal.md in this folder first to pick up where the last session ` +
`left off, then continue the desk's work. Write your journal before you stop.`;
}
//
// Local Delegation is orthogonal to repo/connected: it never changes the tool
// surface. When effective, only the orientation prompt and child env mark that
// the frontier desk may use the installed local-agent-delegation skill.
// Spawn detached and resolve true only once the OS confirms the process
// started ('spawn'), false on failure ('error', e.g. the binary is missing) so
@@ -354,7 +366,62 @@ function isInsideRoot(root, target) {
} catch { return false; }
}
async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFAULT_DESK_PROFILE) {
function preferenceStatePath(workshopDir) {
return localDelegationPreferencePath(workshopDir, {
resolvePath: (p) => {
try { return realpathSync(p); } catch { return p; }
},
});
}
async function readLocalDelegationPreference(workshopDir) {
// Never read preference from the workshop repo — a clone can ship
// preference:on. Only user-local state (or explicit env) counts.
try {
const raw = JSON.parse(await readFile(preferenceStatePath(workshopDir), "utf8"));
return parseLocalDelegationState(raw).preference;
} catch {
return normalizeLocalDelegationPreference(
process.env.WORKSHOP_LOCAL_DELEGATION_PREFERENCE, "off");
}
}
async function writeLocalDelegationPreference(workshopDir, preference) {
const state = serializeLocalDelegationState({ preference });
const target = preferenceStatePath(workshopDir);
await mkdir(dirname(target), { recursive: true });
// Atomic replace in the user-local dir (temp + rename).
const tmp = join(
dirname(target),
`.pref.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
const body = JSON.stringify(state, null, 2) + "\n";
try {
await writeFile(tmp, body, { encoding: "utf8", flag: "wx" });
try {
await rename(tmp, target);
} catch {
await unlink(target).catch(() => {});
await rename(tmp, target);
}
} catch (err) {
await unlink(tmp).catch(() => {});
throw err;
}
return state;
}
function currentLocalDelegationLaunch(preference) {
const availability = resolveLocalDelegationAvailability();
return resolveLocalDelegationLaunch({ preference, availability });
}
async function launchDeskConsole(
deskPath,
deskName,
workshopDir,
profile = DEFAULT_DESK_PROFILE,
localDelegation = { effective: false },
) {
// deskName must be a plain slug so it is safe on every command line and shell
// below, and the resolved desk must still live inside the workshop root
// (which defeats a symlinked desk that escapes the repo). deskPath itself is
@@ -367,24 +434,27 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
if (!isInsideRoot(workshopDir, deskPath)) return false;
const agent = await deskAgentArgv(deskName, workshopDir, profile);
if (!agent) return false;
const run = [...agent, "-i", deskOrientPrompt(deskName)];
const effective = Boolean(localDelegation?.effective);
const run = [...agent, "-i", deskOrientPrompt(deskName, { localDelegationEffective: effective })];
const env = buildLocalDelegationLaunchEnv(process.env, { localDelegationEffective: effective });
if (process.platform === "win32") {
const wt = resolveOnPath("wt", { directOnly: true, excludedRoot: workshopDir });
const cmd = resolveSystem32Executable("cmd.exe");
const direct = /\.(exe|com)$/i.test(run[0]);
if (direct && wt && await trySpawn(wt, ["-d", deskPath, ...run])) return true;
// Older installs can expose .cmd/.bat shims. Only use cmd.exe when every
// argument is free of cmd metacharacters; otherwise fail closed and let
// the UI copy the desk path rather than reparse an unsafe workshop path.
const cmdSafe = run.every((arg) => !/[&|<>^%!()\r\n]/.test(arg));
if (cmdSafe && wt && cmd &&
await trySpawn(wt, ["-d", deskPath, cmd, "/k", ...run])) return true;
// Fallback when wt.exe is absent: a fresh console window via `start`,
// still through cmd /k only when the arguments are safe for reparsing.
return cmdSafe && cmd
? await trySpawn(cmd, ["/c", "start", "", cmd, "/k", ...run], { cwd: deskPath })
: false;
// wt.exe does not reliably forward Node's spawn env into a new tab when
// Windows Terminal is already running. Always start through cmd.exe and
// set/clear WORKSHOP_LOCAL_DELEGATION in the command string itself.
// Args are quoteWindowsCmdArgument'd, so only block expanders that still
// fire inside quotes (% and !) — allow parentheses in workshop paths.
const cmdSafe = run.every((arg) => isSafeQuotedWindowsCmdArg(arg));
if (!cmdSafe || !cmd) return false;
const inner = windowsLocalDelegationCmdPrefix(effective)
+ run.map(quoteWindowsCmdArgument).join(" ");
if (wt && await trySpawn(wt, ["-d", deskPath, cmd, "/d", "/s", "/k", inner], { env })) {
return true;
}
// Fallback when wt.exe is absent: a fresh console window via `start`.
return await trySpawn(
cmd, ["/c", "start", "", cmd, "/d", "/s", "/k", inner], { cwd: deskPath, env });
}
if (process.platform === "darwin") {
const osascript = "/usr/bin/osascript";
@@ -393,13 +463,18 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
// to cd into the desk and exec the agent. Each argv element is POSIX
// single-quoted so the shell can't reinterpret it, and osascript itself
// is spawned via argv (no shell).
const line = "cd " + shSingleQuote(deskPath) + " && exec " +
// Local-delegation env is exported in-line so the Terminal session sees it
// without inheriting a polluted parent shell forever.
const envPrefix = effective
? "export WORKSHOP_LOCAL_DELEGATION=enabled; "
: "unset WORKSHOP_LOCAL_DELEGATION; ";
const line = "cd " + shSingleQuote(deskPath) + " && " + envPrefix + "exec " +
run.map(shSingleQuote).join(" ");
const script = 'tell application "Terminal"\n' +
" activate\n" +
" do script " + osaStringLiteral(line) + "\n" +
"end tell";
return await trySpawn(osascript, ["-e", script]);
return await trySpawn(osascript, ["-e", script], { env });
}
// Linux/other: best-effort across common terminal emulators. Each is spawned
// via argv (no shell) with the agent command after the emulator's exec flag,
@@ -412,7 +487,7 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
];
for (const [term, args] of linuxTerms) {
const executable = resolveOnPath(term, { excludedRoot: workshopDir });
if (executable && await trySpawn(executable, args, { cwd: deskPath })) return true;
if (executable && await trySpawn(executable, args, { cwd: deskPath, env })) return true;
}
return false;
}
@@ -728,7 +803,42 @@ function avgScore(signals) {
return { confidence: avg("confidence"), accuracy: avg("accuracy"), completeness: avg("completeness"), intent: avg("intentScore") };
}
function renderSummaryBar(activeSignals) {
function renderLocalDelegationControl(localDelegation) {
const pref = localDelegation?.preference || "off";
const available = Boolean(localDelegation?.availability?.available);
const effective = Boolean(localDelegation?.effective);
const reason = localDelegation?.availability?.reason || "Local Delegation unavailable";
const routeId = localDelegation?.availability?.routeId || null;
const next = pref === "on" ? "off" : "on";
const label = effective ? "On" : (pref === "on" ? "On*" : "Off");
const color = effective ? "#86efac" : (pref === "on" ? "#fbbf24" : "#94a3b8");
const border = effective ? "#166534" : (pref === "on" ? "#854d0e" : "#334155");
const title = available
? (effective
? `Local Delegation effective${routeId ? ` · route ${routeId}` : ""}`
: "Local Delegation available but currently off")
: reason;
const note = effective && routeId
? `<span style="font-size:10px;color:#86efac;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(title)}">effective · ${esc(truncate(routeId, 28))}</span>`
: !available
? `<span style="font-size:10px;color:#64748b;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(reason)}">${esc(truncate(reason, 48))}</span>`
: (pref === "on" && !effective
? `<span style="font-size:10px;color:#fbbf24;">requested, unavailable</span>`
: "");
return `
<div style="display:flex;align-items:center;gap:6px;" title="${esc(title)}">
<span style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:.04em;">Local</span>
<button data-act="local-delegation" data-preference="${esc(next)}"
aria-label="Local Delegation ${esc(label)}${effective && routeId ? ` route ${esc(routeId)}` : ""}"
aria-pressed="${pref === "on" ? "true" : "false"}"
style="background:#020617;border:1px solid ${border};color:${color};padding:2px 8px;border-radius:999px;
font-size:11px;cursor:pointer;font-weight:600;min-width:42px;"
title="${esc(title)}">${esc(label)}</button>
${note}
</div>`;
}
function renderSummaryBar(activeSignals, localDelegation) {
const escalations = activeSignals.filter(s => s.signalType === "escalation").length;
const withSignals = activeSignals.filter(s => s.signalType !== "none").length;
const awaiting = activeSignals.filter(s => s.signalType === "none").length;
@@ -763,13 +873,14 @@ function renderSummaryBar(activeSignals) {
return `
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 14px;
background:#0f172a;border:1px solid #1e293b;border-radius:8px;margin-bottom:14px;">
<div style="display:flex;align-items:center;gap:12px;">
background:#0f172a;border:1px solid #1e293b;border-radius:8px;margin-bottom:14px;gap:12px;flex-wrap:wrap;">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<span style="font-size:13px;color:#cbd5e1;"><b style="color:#f1f5f9;">${activeSignals.length}</b> desk${activeSignals.length !== 1 ? "s" : ""}</span>
<span style="font-size:11px;color:#475569;">${withSignals} reporting · ${awaiting} awaiting</span>
${tokenBadge}
${calibrationBadge}
${escBadge}
${renderLocalDelegationControl(localDelegation)}
</div>
${avgBlock}
</div>`;
@@ -945,8 +1056,9 @@ function renderStashedCard(entry) {
</div>`;
}
function renderDashboard(signals, stashed, capabilityToken) {
function renderDashboard(signals, stashed, capabilityToken, localDelegation) {
const activeSignals = sortSignals(signals.filter(s => !stashed.some(e => e.name === s.deskName)));
const localDelegationState = localDelegation || currentLocalDelegationLaunch("off");
const cards = activeSignals.length > 0
? activeSignals.map(renderSignalCard).join("")
@@ -975,7 +1087,9 @@ function renderDashboard(signals, stashed, capabilityToken) {
</div>
</div>`;
const summaryBar = activeSignals.length > 0 ? renderSummaryBar(activeSignals) : "";
// Always show the Local Delegation control so operators can see availability
// even before the first desk signal arrives.
const summaryBar = renderSummaryBar(activeSignals, localDelegationState);
const stashedSection = stashed.length > 0 ? `
<div style="margin-top:20px;padding-top:12px;border-top:1px solid #1a1a1a;">
@@ -1058,27 +1172,55 @@ function renderDashboard(signals, stashed, capabilityToken) {
const data = await res.json();
if (data.ok) {
const path = data.deskPath || name;
const notice = data.localDelegationNotice || {};
const localTitle = notice.titleSuffix || '';
const localDetail = notice.detail || '';
if (data.launched) {
// A successful open shouldn't hijack the user's clipboard.
showToast('opening ' + name + ' desk (' + selectedProfile + ')…', path);
// Surface LD state in the toast — operators cannot rely on -i alone.
showToast('opening ' + name + ' desk (' + selectedProfile + localTitle + ')…',
localDetail || path);
} else {
// No terminal launched from here, so copy the path as the
// fallback handle, but only claim the copy when it actually
// succeeded. The path shows in the toast either way.
let copied = false;
try { await navigator.clipboard.writeText(path); copied = true; } catch {}
showToast(copied ? (name + ' · path copied') : (name + ' · copy this path'), path);
const copyTitle = copied ? (name + ' · path copied') : (name + ' · copy this path');
showToast(copyTitle + localTitle, localDetail || path);
}
} else {
showToast(name + ' · not found', '');
}
}
async function setLocalDelegation(preference) {
const res = await fetch('/api/local-delegation?preference=' +
encodeURIComponent(preference || 'off'), POST_OPTS);
const data = await res.json();
if (data.ok) {
const ld = data.localDelegation || {};
const routeId = ld.availability && ld.availability.routeId;
const label = ld.effective
? ('Local Delegation effective' + (routeId ? (' · route ' + routeId) : ''))
: (ld.preference === 'on'
? 'Local Delegation requested (unavailable)'
: 'Local Delegation off');
showToast(label, ld.availability?.reason || '');
refresh();
} else {
showToast('Local Delegation · not updated', data.error || '');
}
}
document.addEventListener('click', (e) => {
const btn = e.target.closest('button[data-act]');
if (!btn) return;
const act = btn.getAttribute('data-act');
if (act === 'local-delegation') {
setLocalDelegation(btn.getAttribute('data-preference') || 'off');
return;
}
const name = btn.getAttribute('data-desk');
if (!name) return;
const act = btn.getAttribute('data-act');
const profile = btn.getAttribute('data-profile');
if (act === 'stash') stashDesk(name);
else if (act === 'restore') restoreDesk(name);
@@ -1102,19 +1244,26 @@ function renderDashboard(signals, stashed, capabilityToken) {
active.getAttribute('data-act'),
active.getAttribute('data-desk'),
active.getAttribute('data-profile') || '',
active.getAttribute('data-preference') || '',
]);
}
content.innerHTML = newContent.innerHTML;
if (focusKey) {
const [act, desk, profile] = JSON.parse(focusKey);
const escDesk = (window.CSS && CSS.escape) ? CSS.escape(desk) : desk;
const profileSelector = profile
? '[data-profile="' + profile + '"]'
: ':not([data-profile])';
const target = content.querySelector(
'button[data-act="' + act + '"][data-desk="' + escDesk + '"]' +
profileSelector);
const [act, desk, profile, preference] = JSON.parse(focusKey);
let target = null;
if (act === 'local-delegation') {
target = content.querySelector('button[data-act="local-delegation"]');
} else {
const escDesk = (window.CSS && CSS.escape) ? CSS.escape(desk) : desk;
const profileSelector = profile
? '[data-profile="' + profile + '"]'
: ':not([data-profile])';
target = content.querySelector(
'button[data-act="' + act + '"][data-desk="' + escDesk + '"]' +
profileSelector);
}
if (target) target.focus();
void preference;
}
}
} catch {}
@@ -1180,6 +1329,20 @@ async function startServer(instanceId, workshopDir) {
res.end(JSON.stringify({ ok: true }));
return;
}
if (req.method === "POST" && url.pathname === "/api/local-delegation") {
const preferenceInput = url.searchParams.get("preference") || "off";
if (!["off", "on"].includes(String(preferenceInput).toLowerCase())) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: false, error: "Invalid local delegation preference" }));
return;
}
const preference = normalizeLocalDelegationPreference(preferenceInput, "off");
await writeLocalDelegationPreference(workshopDir, preference);
const localDelegation = currentLocalDelegationLaunch(preference);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, localDelegation }));
return;
}
if (req.method === "POST" && url.pathname.startsWith("/api/open/")) {
const deskName = decodeURIComponent(url.pathname.split("/api/open/")[1]);
const profileInput = url.searchParams.get("profile") || DEFAULT_DESK_PROFILE;
@@ -1194,14 +1357,25 @@ async function startServer(instanceId, workshopDir) {
return;
}
const profile = normalizeDeskProfile(profileInput);
const preference = await readLocalDelegationPreference(workshopDir);
const localDelegation = currentLocalDelegationLaunch(preference);
for (const subdir of ["desks", "classroom"]) {
const deskPath = join(workshopDir, subdir, deskName);
try {
const s = await stat(deskPath);
if (s.isDirectory()) {
const launched = await launchDeskConsole(deskPath, deskName, workshopDir, profile);
const launched = await launchDeskConsole(
deskPath, deskName, workshopDir, profile, localDelegation);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, deskName, deskPath, launched, profile }));
res.end(JSON.stringify({
ok: true,
deskName,
deskPath,
launched,
profile,
localDelegation,
localDelegationNotice: formatLocalDelegationOpenNotice(localDelegation),
}));
return;
}
} catch {}
@@ -1213,8 +1387,10 @@ async function startServer(instanceId, workshopDir) {
const signals = await scanSignals(workshopDir);
const stashed = await readStash(workshopDir);
const preference = await readLocalDelegationPreference(workshopDir);
const localDelegation = currentLocalDelegationLaunch(preference);
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(renderDashboard(signals, stashed, capabilityToken));
res.end(renderDashboard(signals, stashed, capabilityToken, localDelegation));
} catch (err) {
// Top-level boundary: never leave a request hanging or let a
// rejection become an unhandled crash — e.g. malformed %-encoding
@@ -1327,7 +1503,7 @@ const session = await joinSession({
},
{
name: "open_desk",
description: "Open a desk as an in-place Copilot CLI session. Repo profile suppresses ambient plugin MCPs; connected keeps every configured tool. Returns the desk path, profile, and whether a terminal was launched.",
description: "Open a desk as an in-place Copilot CLI session. Repo profile suppresses ambient plugin MCPs; connected keeps every configured tool. Local Delegation is orthogonal and fail-closed: when available and preferred on, the frontier desk may use sealed local-agent-delegation for bounded read/evidence work. Returns the desk path, profile, localDelegation state, and whether a terminal was launched.",
inputSchema: {
type: "object",
properties: {
@@ -1337,6 +1513,11 @@ const session = await joinSession({
enum: ["repo", "connected"],
description: `Tool profile. Defaults to ${DEFAULT_DESK_PROFILE}.`,
},
localDelegation: {
type: "string",
enum: ["off", "on"],
description: "Optional Local Delegation preference for this launch. Defaults to the workshop Cairn toggle (.local-delegation.json).",
},
},
required: ["deskName"],
},
@@ -1347,13 +1528,21 @@ const session = await joinSession({
const profileInput = ctx.input.profile || DEFAULT_DESK_PROFILE;
if (!isDeskProfile(profileInput)) return { error: "Invalid desk profile" };
const profile = normalizeDeskProfile(profileInput);
const preferenceInput = ctx.input.localDelegation
?? await readLocalDelegationPreference(entry.workshopDir);
const preference = normalizeLocalDelegationPreference(preferenceInput, "off");
const localDelegation = currentLocalDelegationLaunch(preference);
for (const subdir of ["desks", "classroom"]) {
const deskPath = join(entry.workshopDir, subdir, ctx.input.deskName);
try {
const s = await stat(deskPath);
if (s.isDirectory()) {
const launched = await launchDeskConsole(
deskPath, ctx.input.deskName, entry.workshopDir, profile);
deskPath,
ctx.input.deskName,
entry.workshopDir,
profile,
localDelegation);
return {
ok: true,
deskName: ctx.input.deskName,
@@ -1361,6 +1550,8 @@ const session = await joinSession({
launched,
workshopDir: entry.workshopDir,
profile,
localDelegation,
localDelegationNotice: formatLocalDelegationOpenNotice(localDelegation),
};
}
} catch {}
@@ -25,6 +25,16 @@ 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); }
@@ -7,6 +7,7 @@ import { join } from "node:path";
import {
buildDeskAgentArgv,
isDeskProfile,
isSafeQuotedWindowsCmdArg,
isSafeWindowsCmdShim,
isWindowsAppExecutionAlias,
normalizeDeskProfile,
@@ -41,6 +42,10 @@ test("quotes trusted cmd shim arguments and rejects percent-bearing paths", () =
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", {
@@ -0,0 +1,401 @@
import { createHash } from "node:crypto";
import { accessSync, constants as fsConstants, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const PREFERENCES = new Set(["off", "on"]);
const SAFE_ROUTE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
/** @deprecated Repo-root state is rejected; kept only for docs/migration mentions. */
export const LOCAL_DELEGATION_STATE_FILE = ".local-delegation.json";
export const LOCAL_DELEGATION_ENV = "WORKSHOP_LOCAL_DELEGATION";
export const LOCAL_DELEGATION_SKILL_NAME = "local-agent-delegation";
export const LOCAL_DELEGATION_USER_STATE_DIR = join(".copilot", "workshop-local-delegation");
/**
* Stable user-local preference path for a workshop root.
* Permission state must NOT live in the cloned workshop (a repo can ship
* preference:on). Key by a hash of the canonical workshop path under ~/.copilot.
*/
export function localDelegationPreferencePath(workshopDir, {
home = homedir(),
resolvePath = (p) => p,
} = {}) {
if (typeof workshopDir !== "string" || !workshopDir) {
throw new Error("workshopDir is required");
}
let canonical = workshopDir;
try { canonical = resolvePath(workshopDir); } catch { /* keep input */ }
// Normalize separators only. Do not lowercase: on case-sensitive filesystems
// /work/Foo and /work/foo are distinct workshops and must not share state.
const key = createHash("sha256")
.update(String(canonical).replaceAll("\\", "/"))
.digest("hex")
.slice(0, 32);
return join(home, LOCAL_DELEGATION_USER_STATE_DIR, `${key}.json`);
}
export function isLocalDelegationPreference(value) {
return typeof value === "string" && PREFERENCES.has(value.toLowerCase());
}
export function normalizeLocalDelegationPreference(value, fallback = "off") {
return isLocalDelegationPreference(value) ? value.toLowerCase() : fallback;
}
// Windows Terminal / cmd reparse cannot safely carry long multi-space -i strings.
// Keep the orientation prompt short, ASCII, and quote-free. Local Delegation
// policy lives in WORKSHOP_LOCAL_DELEGATION=enabled + the installed skill — not
// on the CLI.
const MAX_ORIENT_PROMPT_CHARS = 280;
// Allow apostrophes (desk's). Ban double quotes, backticks, dashes that WT/cmd
// have split on, and classic cmd metacharacters.
const UNSAFE_ORIENT_CHARS = /["`—–|&<>^%!()\r\n]/;
export function isSafeDeskOrientPrompt(prompt) {
return typeof prompt === "string"
&& prompt.length > 0
&& prompt.length <= MAX_ORIENT_PROMPT_CHARS
&& !UNSAFE_ORIENT_CHARS.test(prompt);
}
/** One short ASCII notice operators can see in the session start prompt. */
export const LOCAL_DELEGATION_ORIENT_LINE = " Local Delegation env is enabled.";
export function deskOrientPrompt(deskName, { localDelegationEffective = false } = {}) {
// deskName is already constrained to a slug by the launcher; still keep the
// prompt free of punctuation that cmd/wt have historically mis-parsed.
let prompt = `You are sitting down at the ${deskName} desk in this workshop. ` +
`Read journal.md in this folder first to pick up where the last session ` +
`left off, then continue the desk's work. Write your journal before you stop.`;
// Only a single short ASCII line may ride on -i. Full policy stays in env + skill.
if (localDelegationEffective) {
const withNotice = prompt + LOCAL_DELEGATION_ORIENT_LINE;
if (isSafeDeskOrientPrompt(withNotice)) prompt = withNotice;
}
return prompt;
}
/**
* Operator-visible summary for open toasts and badges.
* Never claims savings; only reports effective state + route id when known.
*/
export function formatLocalDelegationOpenNotice(localDelegation) {
if (!localDelegation || typeof localDelegation !== "object") {
return { titleSuffix: "", detail: "" };
}
const routeId = localDelegation.availability?.routeId || null;
if (localDelegation.effective) {
const routePart = routeId ? ` · route ${routeId}` : "";
return {
titleSuffix: " · Local Delegation effective",
detail: `Local Delegation effective${routePart}`,
};
}
if (localDelegation.requested) {
return {
titleSuffix: " · local unavailable",
detail: localDelegation.warning
|| localDelegation.availability?.reason
|| "Local Delegation requested but unavailable",
};
}
return { titleSuffix: "", detail: "" };
}
export function buildLocalDelegationLaunchEnv(baseEnv = {}, { localDelegationEffective = false } = {}) {
const env = { ...baseEnv };
// Windows env names are case-insensitive; spreading process.env yields a
// case-sensitive object, so clear every spelling before optionally setting.
const target = LOCAL_DELEGATION_ENV.toLowerCase();
for (const key of Object.keys(env)) {
if (key.toLowerCase() === target) delete env[key];
}
if (localDelegationEffective) {
env[LOCAL_DELEGATION_ENV] = "enabled";
}
return env;
}
/**
* cmd.exe prefix that forces WORKSHOP_LOCAL_DELEGATION on or off inside a new
* Windows Terminal / console session. wt.exe does not reliably forward the
* caller's process env into a new tab when Terminal is already running.
*/
export function windowsLocalDelegationCmdPrefix(localDelegationEffective = false) {
return localDelegationEffective
? 'set "WORKSHOP_LOCAL_DELEGATION=enabled"&& '
: 'set "WORKSHOP_LOCAL_DELEGATION="&& ';
}
/**
* Savings credit is utilization accounting, not a price claim.
* Failed, unaccepted, redone, or escalated local work earns zero.
*/
export function localSavingsCredit({
attempted = false,
gateAccepted = false,
redone = false,
escalated = false,
} = {}) {
if (!attempted || !gateAccepted || redone || escalated) {
return {
credit: 0,
utilization: attempted ? "handled_locally_unaccepted" : "not_attempted",
reason: !attempted
? "not_attempted"
: escalated
? "escalated"
: redone
? "redone"
: "gate_not_accepted",
};
}
return {
credit: 0, // dollar savings are never claimed by Cairn
utilization: "handled_locally_accepted",
reason: "accepted_utilization_only",
};
}
function isReadableFile(path) {
try {
if (!statSync(path).isFile()) return false;
accessSync(path, fsConstants.R_OK);
return true;
} catch {
return false;
}
}
function skillMarkerPath(dir) {
return join(dir, "SKILL.md");
}
function looksLikeSkillDir(dir) {
return isReadableFile(skillMarkerPath(dir));
}
/**
* Discover the installed local-agent-delegation skill.
* Injectable probes keep unit tests filesystem-free.
*/
export function findLocalDelegationSkillDir({
env = process.env,
home = homedir(),
exists = existsSync,
isSkillDir = looksLikeSkillDir,
} = {}) {
const explicit = (env.WORKSHOP_LOCAL_DELEGATION_SKILL_DIR || "").trim();
if (explicit) {
return isSkillDir(explicit) ? explicit : null;
}
const candidates = [
join(home, ".copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(home, ".agents", "skills", LOCAL_DELEGATION_SKILL_NAME),
];
for (const candidate of candidates) {
if (isSkillDir(candidate)) return candidate;
}
// Marketplace: ~/.copilot/installed-plugins/<marketplace>/<plugin>/
// Direct: ~/.copilot/installed-plugins/_direct/<plugin>/
// Skill dirs may live at skills/, .github/skills/, or com.github.copilot/skills/.
const pluginsRoot = join(home, ".copilot", "installed-plugins");
if (exists(pluginsRoot)) {
try {
for (const market of readdirSync(pluginsRoot, { withFileTypes: true })) {
if (!market.isDirectory()) continue;
const marketRoot = join(pluginsRoot, market.name);
let pluginEntries;
try {
pluginEntries = readdirSync(marketRoot, { withFileTypes: true });
} catch {
continue;
}
for (const plugin of pluginEntries) {
if (!plugin.isDirectory()) continue;
const pluginRoot = join(marketRoot, plugin.name);
const nestedCandidates = [
join(pluginRoot, "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, ".github", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, "com.github.copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, "com.github.awesome-copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
];
for (const nested of nestedCandidates) {
if (isSkillDir(nested)) return nested;
}
}
}
} catch {
// fail closed on scan errors
}
}
return null;
}
function readJsonFile(path, readFile = readFileSync) {
try {
return JSON.parse(readFile(path, "utf8"));
} catch {
return null;
}
}
function isRouteId(value) {
return typeof value === "string" && SAFE_ROUTE_ID.test(value);
}
/**
* Fail-closed availability. Enable only when skill + qualified route receipt
* (or explicit env route id) are present. Never invent availability.
*/
export function resolveLocalDelegationAvailability({
env = process.env,
home = homedir(),
now = Date.now(),
findSkill = findLocalDelegationSkillDir,
readFile = readFileSync,
exists = existsSync,
} = {}) {
const forced = (env.WORKSHOP_LOCAL_DELEGATION_AVAILABLE || "").trim().toLowerCase();
if (forced === "0" || forced === "false" || forced === "unavailable") {
return {
available: false,
reason: "Forced unavailable by WORKSHOP_LOCAL_DELEGATION_AVAILABLE",
skillDir: null,
routeId: null,
};
}
const skillDir = findSkill({ env, home, exists });
if (!skillDir) {
return {
available: false,
reason: "local-agent-delegation skill is not installed",
skillDir: null,
routeId: null,
};
}
const envRoute = (env.WORKSHOP_LOCAL_DELEGATION_ROUTE_ID || "").trim();
if (envRoute) {
if (!isRouteId(envRoute)) {
return {
available: false,
reason: "WORKSHOP_LOCAL_DELEGATION_ROUTE_ID is not a safe route id",
skillDir,
routeId: null,
};
}
return {
available: true,
reason: "Skill installed; route id provided by environment",
skillDir,
routeId: envRoute,
};
}
const receiptPath = (env.WORKSHOP_LOCAL_DELEGATION_RECEIPT || "").trim()
|| join(home, ".copilot", "local-agent-runs", "qualified-route.json");
if (!exists(receiptPath)) {
return {
available: false,
reason: "No qualified route receipt (set WORKSHOP_LOCAL_DELEGATION_ROUTE_ID or write ~/.copilot/local-agent-runs/qualified-route.json)",
skillDir,
routeId: null,
};
}
const receipt = readJsonFile(receiptPath, readFile);
if (!receipt || typeof receipt !== "object") {
return {
available: false,
reason: "Qualified route receipt is unreadable",
skillDir,
routeId: null,
};
}
const status = String(receipt.status || "").toLowerCase();
if (status !== "qualified") {
return {
available: false,
reason: `Route receipt status is '${receipt.status || "missing"}', not qualified`,
skillDir,
routeId: isRouteId(receipt.route_id || receipt.routeId) ? (receipt.route_id || receipt.routeId) : null,
};
}
const routeId = receipt.route_id || receipt.routeId || null;
if (!isRouteId(routeId)) {
return {
available: false,
reason: "Route receipt is missing a safe route_id",
skillDir,
routeId: null,
};
}
if (receipt.expires_at || receipt.expiresAt) {
const expires = Date.parse(receipt.expires_at || receipt.expiresAt);
if (!Number.isFinite(expires) || expires <= now) {
return {
available: false,
reason: "Qualified route receipt has expired",
skillDir,
routeId,
};
}
}
return {
available: true,
reason: "Skill installed; qualified route receipt present",
skillDir,
routeId,
};
}
export function resolveLocalDelegationLaunch({
preference = "off",
availability,
} = {}) {
const pref = normalizeLocalDelegationPreference(preference, "off");
const available = Boolean(availability?.available);
if (pref === "on" && !available) {
return {
preference: pref,
requested: true,
effective: false,
availability,
warning: availability?.reason || "Local Delegation unavailable",
};
}
return {
preference: pref,
requested: pref === "on",
effective: pref === "on" && available,
availability,
warning: null,
};
}
export function parseLocalDelegationState(raw) {
if (!raw || typeof raw !== "object") {
return { preference: "off" };
}
return {
preference: normalizeLocalDelegationPreference(raw.preference, "off"),
};
}
export function serializeLocalDelegationState(state) {
return {
preference: normalizeLocalDelegationPreference(state?.preference, "off"),
updatedAt: new Date().toISOString(),
};
}
@@ -0,0 +1,298 @@
import test from "node:test";
import assert from "node:assert/strict";
import { join, sep } from "node:path";
import {
LOCAL_DELEGATION_ORIENT_LINE,
buildLocalDelegationLaunchEnv,
deskOrientPrompt,
findLocalDelegationSkillDir,
formatLocalDelegationOpenNotice,
isLocalDelegationPreference,
isSafeDeskOrientPrompt,
localDelegationPreferencePath,
localSavingsCredit,
normalizeLocalDelegationPreference,
parseLocalDelegationState,
resolveLocalDelegationAvailability,
resolveLocalDelegationLaunch,
serializeLocalDelegationState,
windowsLocalDelegationCmdPrefix,
} from "./local-delegation.mjs";
import {
buildDeskAgentArgv,
normalizeDeskProfile,
quoteWindowsCmdArgument,
} from "./launch-profile.mjs";
test("normalizes local-delegation preference independently of desk profile", () => {
assert.equal(isLocalDelegationPreference("on"), true);
assert.equal(isLocalDelegationPreference("OFF"), true);
assert.equal(isLocalDelegationPreference("maybe"), false);
assert.equal(normalizeLocalDelegationPreference("ON"), "on");
assert.equal(normalizeLocalDelegationPreference("nope"), "off");
// Profile axis remains orthogonal and untouched.
assert.equal(normalizeDeskProfile("connected"), "connected");
assert.equal(normalizeDeskProfile("repo"), "repo");
});
test("availability is fail-closed without skill or route receipt", () => {
const missingSkill = resolveLocalDelegationAvailability({
env: {},
home: "C:\\home",
findSkill: () => null,
exists: () => false,
});
assert.equal(missingSkill.available, false);
assert.match(missingSkill.reason, /not installed/i);
const skillOnly = resolveLocalDelegationAvailability({
env: {},
home: "C:\\home",
findSkill: () => "C:\\home\\.copilot\\skills\\local-agent-delegation",
exists: () => false,
});
assert.equal(skillOnly.available, false);
assert.match(skillOnly.reason, /No qualified route receipt/i);
});
test("availability accepts env route id or a qualified receipt", () => {
const home = join("home");
const skillDir = join("skills", "local-agent-delegation");
const viaEnv = resolveLocalDelegationAvailability({
env: { WORKSHOP_LOCAL_DELEGATION_ROUTE_ID: "foundry-qwen25-7b-qualified" },
home,
findSkill: () => skillDir,
exists: () => false,
});
assert.equal(viaEnv.available, true);
assert.equal(viaEnv.routeId, "foundry-qwen25-7b-qualified");
// Match host path.join separators so Linux CI exercises the receipt branch.
const receiptPath = join(home, ".copilot", "local-agent-runs", "qualified-route.json");
const viaReceipt = resolveLocalDelegationAvailability({
env: {},
home,
now: Date.parse("2026-08-14T12:00:00Z"),
findSkill: () => skillDir,
exists: (p) => p === receiptPath,
readFile: () => JSON.stringify({
status: "qualified",
route_id: "foundry-qwen25-7b-qualified",
expires_at: "2026-12-01T00:00:00Z",
}),
});
assert.equal(viaReceipt.available, true);
assert.equal(viaReceipt.routeId, "foundry-qwen25-7b-qualified");
const expired = resolveLocalDelegationAvailability({
env: {},
home,
now: Date.parse("2027-01-01T00:00:00Z"),
findSkill: () => skillDir,
exists: (p) => p === receiptPath,
readFile: () => JSON.stringify({
status: "qualified",
route_id: "foundry-qwen25-7b-qualified",
expires_at: "2026-12-01T00:00:00Z",
}),
});
assert.equal(expired.available, false);
assert.match(expired.reason, /expired/i);
});
test("requested on + unavailable stays ineffective with a warning", () => {
const launch = resolveLocalDelegationLaunch({
preference: "on",
availability: {
available: false,
reason: "local-agent-delegation skill is not installed",
},
});
assert.equal(launch.requested, true);
assert.equal(launch.effective, false);
assert.match(launch.warning, /not installed/i);
});
test("launch env enables when effective; -i prompt stays short and quote-free", () => {
const base = {
PATH: "/usr/bin",
WORKSHOP_LOCAL_DELEGATION: "enabled",
workshop_local_delegation: "enabled",
};
const offEnv = buildLocalDelegationLaunchEnv(base, { localDelegationEffective: false });
assert.equal(Object.hasOwn(offEnv, "WORKSHOP_LOCAL_DELEGATION"), false);
assert.equal(Object.hasOwn(offEnv, "workshop_local_delegation"), false);
assert.equal(offEnv.PATH, "/usr/bin");
const onEnv = buildLocalDelegationLaunchEnv(base, { localDelegationEffective: true });
assert.equal(onEnv.WORKSHOP_LOCAL_DELEGATION, "enabled");
assert.equal(Object.hasOwn(onEnv, "workshop_local_delegation"), false);
const offPrompt = deskOrientPrompt("cost-desk", { localDelegationEffective: false });
const onPrompt = deskOrientPrompt("cost-desk", { localDelegationEffective: true });
assert.match(offPrompt, /cost-desk/);
assert.equal(offPrompt.includes("Local Delegation"), false);
// One short ASCII line only — never the long policy appendix.
assert.equal(onPrompt, offPrompt + LOCAL_DELEGATION_ORIENT_LINE);
assert.match(onPrompt, /Local Delegation env is enabled\./);
assert.equal(onPrompt.includes("do not delegate"), false);
assert.equal(isSafeDeskOrientPrompt(onPrompt), true);
assert.equal(isSafeDeskOrientPrompt('bad "quote"'), false);
assert.equal(isSafeDeskOrientPrompt("em dash — bad"), false);
assert.match(windowsLocalDelegationCmdPrefix(true), /enabled/);
assert.match(windowsLocalDelegationCmdPrefix(false), /WORKSHOP_LOCAL_DELEGATION="&&/);
const run = ["C:\\tools\\copilot.exe", "-i", onPrompt];
const cmdLine = windowsLocalDelegationCmdPrefix(true) + run.map(quoteWindowsCmdArgument).join(" ");
assert.match(cmdLine, /^set "WORKSHOP_LOCAL_DELEGATION=enabled"&& /);
assert.match(cmdLine, /copilot\.exe/);
});
test("open notice reports effective route without savings claims", () => {
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: true,
requested: true,
availability: { available: true, routeId: "foundry-qwen25-7b-qualified" },
}), {
titleSuffix: " · Local Delegation effective",
detail: "Local Delegation effective · route foundry-qwen25-7b-qualified",
});
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: false,
requested: true,
warning: "local-agent-delegation skill is not installed",
availability: { available: false, reason: "local-agent-delegation skill is not installed" },
}).detail, "local-agent-delegation skill is not installed");
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: false,
requested: false,
}), { titleSuffix: "", detail: "" });
});
test("repo/connected argv stays orthogonal to local-delegation preference", () => {
const repo = buildDeskAgentArgv({
deskName: "cost-desk",
workshopDir: "/workshop",
useAgency: false,
copilotCommand: "copilot",
profile: "repo",
pluginMcpNames: ["teams"],
});
const connected = buildDeskAgentArgv({
deskName: "cost-desk",
workshopDir: "/workshop",
useAgency: false,
copilotCommand: "copilot",
profile: "connected",
pluginMcpNames: ["teams"],
});
assert.deepEqual(repo, [
"copilot", "--name", "cost-desk",
"--disable-mcp-server", "teams",
"--add-dir", "/workshop",
]);
assert.deepEqual(connected, [
"copilot", "--name", "cost-desk",
"--add-dir", "/workshop",
]);
// Local delegation never injects into argv — only env/prompt.
assert.equal(repo.includes("local"), false);
assert.equal(connected.includes("local"), false);
});
test("failed or unaccepted local work earns zero savings credit", () => {
assert.equal(localSavingsCredit({ attempted: false }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: false }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: true, redone: true }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: true, escalated: true }).credit, 0);
const accepted = localSavingsCredit({ attempted: true, gateAccepted: true });
assert.equal(accepted.credit, 0);
assert.equal(accepted.utilization, "handled_locally_accepted");
});
test("state parse/serialize defaults to off", () => {
assert.deepEqual(parseLocalDelegationState(null), { preference: "off" });
assert.equal(parseLocalDelegationState({ preference: "ON" }).preference, "on");
const serialized = serializeLocalDelegationState({ preference: "on" });
assert.equal(serialized.preference, "on");
assert.equal(typeof serialized.updatedAt, "string");
});
test("preference path is user-local and keyed by workshop path, not the repo root", () => {
const home = join("user-home");
const a = localDelegationPreferencePath(join("repos", "workshop-a"), {
home,
resolvePath: (p) => p,
});
const b = localDelegationPreferencePath(join("repos", "workshop-b"), {
home,
resolvePath: (p) => p,
});
const again = localDelegationPreferencePath(join("repos", "workshop-a"), {
home,
resolvePath: (p) => p,
});
assert.match(a, /workshop-local-delegation/);
assert.equal(a.startsWith(home), true);
assert.equal(a.includes(`${sep}repos${sep}`), false);
assert.notEqual(a, b);
assert.equal(a, again);
// Repo-shipped .local-delegation.json is never the preference path.
assert.equal(a.endsWith(".local-delegation.json"), false);
// Case-sensitive filesystems: Foo and foo must not share permission state.
const upper = localDelegationPreferencePath("/work/Foo", {
home,
resolvePath: (p) => p,
});
const lower = localDelegationPreferencePath("/work/foo", {
home,
resolvePath: (p) => p,
});
assert.notEqual(upper, lower);
});
test("skill discovery respects explicit dir and common install roots", () => {
const found = findLocalDelegationSkillDir({
env: { WORKSHOP_LOCAL_DELEGATION_SKILL_DIR: "D:\\sealed\\.github\\skills\\local-agent-delegation" },
home: "C:\\home",
exists: () => false,
isSkillDir: (p) => p === "D:\\sealed\\.github\\skills\\local-agent-delegation",
});
assert.equal(found, "D:\\sealed\\.github\\skills\\local-agent-delegation");
const missingExplicit = findLocalDelegationSkillDir({
env: { WORKSHOP_LOCAL_DELEGATION_SKILL_DIR: "D:\\missing" },
home: "C:\\home",
exists: () => false,
isSkillDir: () => false,
});
assert.equal(missingExplicit, null);
});
test("skill discovery walks marketplace/plugin and _direct install layouts", async () => {
const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import("node:fs");
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const home = mkdtempSync(join(tmpdir(), "ld-skill-"));
try {
const skillDir = join(
home, ".copilot", "installed-plugins", "awesome-copilot", "sealed-delegation",
".github", "skills", "local-agent-delegation");
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), "# local-agent-delegation\n");
const found = findLocalDelegationSkillDir({ home, env: {} });
assert.equal(found, skillDir);
rmSync(join(home, ".copilot", "installed-plugins", "awesome-copilot"), { recursive: true, force: true });
const direct = join(
home, ".copilot", "installed-plugins", "_direct", "sealed-delegation",
"skills", "local-agent-delegation");
mkdirSync(direct, { recursive: true });
writeFileSync(join(direct, "SKILL.md"), "# local-agent-delegation\n");
const foundDirect = findLocalDelegationSkillDir({ home, env: {} });
assert.equal(foundDirect, direct);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
+3 -3
View File
@@ -1,15 +1,15 @@
{
"name": "signals-dashboard",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"main": "extension.mjs",
"scripts": {
"test": "node --test launch-profile.test.mjs"
"test": "node --test launch-profile.test.mjs local-delegation.test.mjs"
},
"dependencies": {
"@github/copilot-sdk": "latest"
},
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, and cost-aware repo or connected desk launch profiles.",
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, cost-aware desk profiles, and fail-closed Local Delegation.",
"keywords": [
"agent-signals",
"dashboard",
+15 -3
View File
@@ -1,15 +1,27 @@
# Signals Dashboard Plugin
Real-time Workshop dashboard with agent signals, honesty calibration, and cost-aware repo or connected desk launch profiles.
Real-time Workshop dashboard with agent signals, honesty calibration, cost-aware
desk profiles (`repo` / `connected`), and fail-closed Local Delegation.
## Installation
``bash
```bash
copilot plugin install signals-dashboard@awesome-copilot
``
```
## Features
- Live desk signals, score bars, patterns, and escalations
- Cost-aware **open** (repo) and **connected** desk launch profiles
- **Local Delegation** toggle: when available, the frontier desk may use the
installed `local-agent-delegation` skill for bounded read/evidence work
- Fail-closed availability (skill + qualified route receipt); no silent savings credit
## Source
Canonical implementation: [jennyf19/the-workshop](https://github.com/jennyf19/the-workshop).
Local worker runtime: [jennyf19/sealed-delegation](https://github.com/jennyf19/sealed-delegation).
This plugin is part of [Awesome Copilot](https://github.com/github/awesome-copilot).
## License
@@ -0,0 +1,145 @@
# 🪨 Cairn — Signals Dashboard
A live GitHub Copilot CLI **canvas extension** that shows the pulse of every
desk in your Workshop. It reads the agent signals your desks emit and renders
them as a compact, always-current dashboard in a side panel — so you can direct
the work instead of polling each desk by hand.
It replaces the old Blazor **WorkshopRoom** dashboard (`src/WorkshopRoom/`) with
a native canvas that runs inside GHCP, with no separate web app to launch.
## What it is
Each desk in the Workshop leaves signals behind — small stones on the trail —
by writing JSON files into its `.signals/` folder. Cairn scans those folders,
picks the most recent signal per desk, and renders:
- **Score bars** for the desk's self-assessment (intent, confidence, accuracy,
completeness).
- **Patterns** the desk reported: what worked ✓, what was hard △, and skill
gaps ✗.
- **Escalations** — desks that raised their hand, with what they're blocked on
and their recommendation, pinned to the top and pulsing red.
## How to open it
The dashboard is registered as the **🪨 Cairn** canvas (`signals-dashboard`).
Ask Copilot to open it and pass your workshop root as `workshopDir`:
> Open the 🪨 Cairn canvas with `workshopDir` set to the folder that contains
> my `desks/` directory.
`workshopDir` must be the **absolute path to the workshop root** — the folder
that holds `desks/` (and optionally `classroom/`). If omitted, it falls back to
the current working directory.
## Features
- **Signal scanning** — walks `desks/*/.signals/` and `classroom/*/.signals/`,
reading the newest `*.json` per desk (mirrors `SignalReader.cs`).
- **Score bars** — color-coded intent / confidence / accuracy / completeness,
scored out of 5.
- **Escalation alerts** — escalation signals sort to the very top, render with a
pulsing red border, and surface the blocker + recommendation.
- **Active desks first** — sorted escalations → recent signals → desks with no
signal yet, then by recency.
- **Stash / restore** — pause a workstream by stashing its desk. Stashed desks
drop off the active view and auto-expire after a **48-hour TTL**; restore any
time before then. Stash state lives in `.desk-stash.json` at the workshop
root.
- **Auto-refresh** — the panel refreshes every 5 seconds using a background
fetch (no full page reload), so scores and escalations stay current smoothly.
- **Summary bar** — desk count, how many are reporting vs. awaiting, an
escalation badge, and average scores across the room.
- **Cost-aware desk launch** — **open** starts a repo-profile desk that keeps the
verified Workshop root available while suppressing ambient plugin MCPs.
**connected** preserves every configured MCP for work that needs external
systems. Agency remains the preferred wrapper when installed.
- **Local Delegation** — orthogonal off/on control. When available and on, the
frontier desk may use the installed [`local-agent-delegation`](https://github.com/jennyf19/sealed-delegation)
skill for bounded, independently gated read/evidence work. Fail-closed: no
skill or no qualified route receipt means the toggle cannot take effect, and
no local-savings credit is awarded.
## Agent actions
The canvas also exposes actions Copilot can invoke directly:
- `refresh` — force a rescan and return current signal data as JSON.
- `stash` — stash a desk by `deskName`.
- `restore` — restore a stashed desk by `deskName`.
- `open_desk` — open a desk with optional `profile: "repo" | "connected"` and
optional `localDelegation: "off" | "on"`.
## Desk launch profiles
`repo` is the default. At launch, Cairn asks Copilot for the enabled
plugin-scoped MCP inventory and disables those ambient servers for the topic
desk. User-, workspace-, organization-, and built-in resources are left alone.
If discovery fails, Copilot plugin MCP suppression fails open; Agency repo mode
still omits Agency's own default MCPs.
When Agency is installed, Cairn keeps the existing `agency copilot` launch and
adds Agency's `--no-default-mcps` in repo mode. Outside Agency, the same profile
is applied directly to Copilot CLI.
Both profiles pass `--add-dir <workshop-root>` so a desk can intentionally read
another desk's journal or artifact without receiving access outside the room.
Set `WORKSHOP_DESK_PROFILE=connected` to retain the historical default for the
main **open** button. The separate **connected** button is always available when
repo mode is the default.
## Local Delegation
Local Delegation is **not** a third desk profile and does not replace the frontier
model. It is a separate permission bit:
```text
repo / connected = which MCPs and tools the frontier desk can see
Local Delegation off/on = whether the frontier desk may invoke a bounded local worker
```
Availability is fail-closed. Cairn enables the lane only when:
1. the `local-agent-delegation` skill is installed, and
2. a qualified route is declared via `WORKSHOP_LOCAL_DELEGATION_ROUTE_ID` or a
receipt at `~/.copilot/local-agent-runs/qualified-route.json`
(`status: "qualified"`, safe `route_id`).
Operator preference is stored **user-locally** under
`~/.copilot/workshop-local-delegation/` (keyed by the canonical workshop path),
never in the cloned workshop — a repo cannot ship `preference: on`.
When the preference is on but availability fails, opens still launch as frontier
desks and surface the reason — they never silently fall back with savings credit.
When effective, Cairn sets `WORKSHOP_LOCAL_DELEGATION=enabled` on the launched
process, shows an open toast/badge (`Local Delegation effective · route …`), and
may append one short ASCII line to `-i` (`Local Delegation env is enabled.`) when
that combined prompt stays quote-free and under the length guard. Full policy
still lives in the env flag plus the installed `local-agent-delegation` skill —
never a long multi-sentence `-i` appendix (Windows Terminal reparse).
The runtime, launcher, and gates remain owned by
[Sealed Delegation](https://github.com/jennyf19/sealed-delegation).
## Signal shape
Cairn reads the agent-signals protocol used across the Workshop:
```json
{
"signal_type": "execution",
"agent_name": "desk-name",
"self_assessment": { "intent": 5, "confidence": 4, "accuracy": 4, "completeness": 3 },
"patterns": { "what_worked": "...", "what_was_hard": "...", "skill_gap": "..." },
"escalation": { "reason": "...", "blocked_on": "...", "recommendation": "..." }
}
```
`escalation` is only present on `signal_type: "escalation"` signals.
## Replaces the Blazor WorkshopRoom
This canvas supersedes the standalone Blazor dashboard in `src/WorkshopRoom/`.
The data is the truth and the UI is just a view — Cairn renders the same signal
data natively inside GHCP, so there's no separate server to run.
@@ -5,20 +5,33 @@
import { createServer } from "node:http";
import { statSync, accessSync, realpathSync, constants as fsConstants } from "node:fs";
import { readdir, readFile, writeFile, stat } from "node:fs/promises";
import { join, delimiter, isAbsolute, sep } from "node:path";
import { readdir, readFile, writeFile, stat, rename, unlink, mkdir } from "node:fs/promises";
import { join, delimiter, isAbsolute, sep, dirname } from "node:path";
import { spawn } from "node:child_process";
import { randomBytes } from "node:crypto";
import { joinSession, createCanvas } from "@github/copilot-sdk/extension";
import {
buildDeskAgentArgv,
isDeskProfile,
isSafeQuotedWindowsCmdArg,
isSafeWindowsCmdShim,
isWindowsAppExecutionAlias,
normalizeDeskProfile,
parsePluginMcpNames,
quoteWindowsCmdArgument,
} from "./launch-profile.mjs";
import {
buildLocalDelegationLaunchEnv,
deskOrientPrompt,
formatLocalDelegationOpenNotice,
localDelegationPreferencePath,
normalizeLocalDelegationPreference,
parseLocalDelegationState,
resolveLocalDelegationAvailability,
resolveLocalDelegationLaunch,
serializeLocalDelegationState,
windowsLocalDelegationCmdPrefix,
} from "./local-delegation.mjs";
const servers = new Map();
const STASH_TTL_MS = 48 * 60 * 60 * 1000;
@@ -60,11 +73,10 @@ function isValidDeskName(name) {
// path is then only ever passed as a spawn cwd, an argv element, or a
// single-quoted literal inside the macOS Terminal command — never concatenated
// raw onto a command line — so no character filtering of the path is required.
function deskOrientPrompt(deskName) {
return `You are sitting down at the ${deskName} desk in this workshop. ` +
`Read journal.md in this folder first to pick up where the last session ` +
`left off, then continue the desk's work. Write your journal before you stop.`;
}
//
// Local Delegation is orthogonal to repo/connected: it never changes the tool
// surface. When effective, only the orientation prompt and child env mark that
// the frontier desk may use the installed local-agent-delegation skill.
// Spawn detached and resolve true only once the OS confirms the process
// started ('spawn'), false on failure ('error', e.g. the binary is missing) so
@@ -354,7 +366,62 @@ function isInsideRoot(root, target) {
} catch { return false; }
}
async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFAULT_DESK_PROFILE) {
function preferenceStatePath(workshopDir) {
return localDelegationPreferencePath(workshopDir, {
resolvePath: (p) => {
try { return realpathSync(p); } catch { return p; }
},
});
}
async function readLocalDelegationPreference(workshopDir) {
// Never read preference from the workshop repo — a clone can ship
// preference:on. Only user-local state (or explicit env) counts.
try {
const raw = JSON.parse(await readFile(preferenceStatePath(workshopDir), "utf8"));
return parseLocalDelegationState(raw).preference;
} catch {
return normalizeLocalDelegationPreference(
process.env.WORKSHOP_LOCAL_DELEGATION_PREFERENCE, "off");
}
}
async function writeLocalDelegationPreference(workshopDir, preference) {
const state = serializeLocalDelegationState({ preference });
const target = preferenceStatePath(workshopDir);
await mkdir(dirname(target), { recursive: true });
// Atomic replace in the user-local dir (temp + rename).
const tmp = join(
dirname(target),
`.pref.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
const body = JSON.stringify(state, null, 2) + "\n";
try {
await writeFile(tmp, body, { encoding: "utf8", flag: "wx" });
try {
await rename(tmp, target);
} catch {
await unlink(target).catch(() => {});
await rename(tmp, target);
}
} catch (err) {
await unlink(tmp).catch(() => {});
throw err;
}
return state;
}
function currentLocalDelegationLaunch(preference) {
const availability = resolveLocalDelegationAvailability();
return resolveLocalDelegationLaunch({ preference, availability });
}
async function launchDeskConsole(
deskPath,
deskName,
workshopDir,
profile = DEFAULT_DESK_PROFILE,
localDelegation = { effective: false },
) {
// deskName must be a plain slug so it is safe on every command line and shell
// below, and the resolved desk must still live inside the workshop root
// (which defeats a symlinked desk that escapes the repo). deskPath itself is
@@ -367,24 +434,27 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
if (!isInsideRoot(workshopDir, deskPath)) return false;
const agent = await deskAgentArgv(deskName, workshopDir, profile);
if (!agent) return false;
const run = [...agent, "-i", deskOrientPrompt(deskName)];
const effective = Boolean(localDelegation?.effective);
const run = [...agent, "-i", deskOrientPrompt(deskName, { localDelegationEffective: effective })];
const env = buildLocalDelegationLaunchEnv(process.env, { localDelegationEffective: effective });
if (process.platform === "win32") {
const wt = resolveOnPath("wt", { directOnly: true, excludedRoot: workshopDir });
const cmd = resolveSystem32Executable("cmd.exe");
const direct = /\.(exe|com)$/i.test(run[0]);
if (direct && wt && await trySpawn(wt, ["-d", deskPath, ...run])) return true;
// Older installs can expose .cmd/.bat shims. Only use cmd.exe when every
// argument is free of cmd metacharacters; otherwise fail closed and let
// the UI copy the desk path rather than reparse an unsafe workshop path.
const cmdSafe = run.every((arg) => !/[&|<>^%!()\r\n]/.test(arg));
if (cmdSafe && wt && cmd &&
await trySpawn(wt, ["-d", deskPath, cmd, "/k", ...run])) return true;
// Fallback when wt.exe is absent: a fresh console window via `start`,
// still through cmd /k only when the arguments are safe for reparsing.
return cmdSafe && cmd
? await trySpawn(cmd, ["/c", "start", "", cmd, "/k", ...run], { cwd: deskPath })
: false;
// wt.exe does not reliably forward Node's spawn env into a new tab when
// Windows Terminal is already running. Always start through cmd.exe and
// set/clear WORKSHOP_LOCAL_DELEGATION in the command string itself.
// Args are quoteWindowsCmdArgument'd, so only block expanders that still
// fire inside quotes (% and !) — allow parentheses in workshop paths.
const cmdSafe = run.every((arg) => isSafeQuotedWindowsCmdArg(arg));
if (!cmdSafe || !cmd) return false;
const inner = windowsLocalDelegationCmdPrefix(effective)
+ run.map(quoteWindowsCmdArgument).join(" ");
if (wt && await trySpawn(wt, ["-d", deskPath, cmd, "/d", "/s", "/k", inner], { env })) {
return true;
}
// Fallback when wt.exe is absent: a fresh console window via `start`.
return await trySpawn(
cmd, ["/c", "start", "", cmd, "/d", "/s", "/k", inner], { cwd: deskPath, env });
}
if (process.platform === "darwin") {
const osascript = "/usr/bin/osascript";
@@ -393,13 +463,18 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
// to cd into the desk and exec the agent. Each argv element is POSIX
// single-quoted so the shell can't reinterpret it, and osascript itself
// is spawned via argv (no shell).
const line = "cd " + shSingleQuote(deskPath) + " && exec " +
// Local-delegation env is exported in-line so the Terminal session sees it
// without inheriting a polluted parent shell forever.
const envPrefix = effective
? "export WORKSHOP_LOCAL_DELEGATION=enabled; "
: "unset WORKSHOP_LOCAL_DELEGATION; ";
const line = "cd " + shSingleQuote(deskPath) + " && " + envPrefix + "exec " +
run.map(shSingleQuote).join(" ");
const script = 'tell application "Terminal"\n' +
" activate\n" +
" do script " + osaStringLiteral(line) + "\n" +
"end tell";
return await trySpawn(osascript, ["-e", script]);
return await trySpawn(osascript, ["-e", script], { env });
}
// Linux/other: best-effort across common terminal emulators. Each is spawned
// via argv (no shell) with the agent command after the emulator's exec flag,
@@ -412,7 +487,7 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
];
for (const [term, args] of linuxTerms) {
const executable = resolveOnPath(term, { excludedRoot: workshopDir });
if (executable && await trySpawn(executable, args, { cwd: deskPath })) return true;
if (executable && await trySpawn(executable, args, { cwd: deskPath, env })) return true;
}
return false;
}
@@ -728,7 +803,42 @@ function avgScore(signals) {
return { confidence: avg("confidence"), accuracy: avg("accuracy"), completeness: avg("completeness"), intent: avg("intentScore") };
}
function renderSummaryBar(activeSignals) {
function renderLocalDelegationControl(localDelegation) {
const pref = localDelegation?.preference || "off";
const available = Boolean(localDelegation?.availability?.available);
const effective = Boolean(localDelegation?.effective);
const reason = localDelegation?.availability?.reason || "Local Delegation unavailable";
const routeId = localDelegation?.availability?.routeId || null;
const next = pref === "on" ? "off" : "on";
const label = effective ? "On" : (pref === "on" ? "On*" : "Off");
const color = effective ? "#86efac" : (pref === "on" ? "#fbbf24" : "#94a3b8");
const border = effective ? "#166534" : (pref === "on" ? "#854d0e" : "#334155");
const title = available
? (effective
? `Local Delegation effective${routeId ? ` · route ${routeId}` : ""}`
: "Local Delegation available but currently off")
: reason;
const note = effective && routeId
? `<span style="font-size:10px;color:#86efac;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(title)}">effective · ${esc(truncate(routeId, 28))}</span>`
: !available
? `<span style="font-size:10px;color:#64748b;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(reason)}">${esc(truncate(reason, 48))}</span>`
: (pref === "on" && !effective
? `<span style="font-size:10px;color:#fbbf24;">requested, unavailable</span>`
: "");
return `
<div style="display:flex;align-items:center;gap:6px;" title="${esc(title)}">
<span style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:.04em;">Local</span>
<button data-act="local-delegation" data-preference="${esc(next)}"
aria-label="Local Delegation ${esc(label)}${effective && routeId ? ` route ${esc(routeId)}` : ""}"
aria-pressed="${pref === "on" ? "true" : "false"}"
style="background:#020617;border:1px solid ${border};color:${color};padding:2px 8px;border-radius:999px;
font-size:11px;cursor:pointer;font-weight:600;min-width:42px;"
title="${esc(title)}">${esc(label)}</button>
${note}
</div>`;
}
function renderSummaryBar(activeSignals, localDelegation) {
const escalations = activeSignals.filter(s => s.signalType === "escalation").length;
const withSignals = activeSignals.filter(s => s.signalType !== "none").length;
const awaiting = activeSignals.filter(s => s.signalType === "none").length;
@@ -763,13 +873,14 @@ function renderSummaryBar(activeSignals) {
return `
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 14px;
background:#0f172a;border:1px solid #1e293b;border-radius:8px;margin-bottom:14px;">
<div style="display:flex;align-items:center;gap:12px;">
background:#0f172a;border:1px solid #1e293b;border-radius:8px;margin-bottom:14px;gap:12px;flex-wrap:wrap;">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<span style="font-size:13px;color:#cbd5e1;"><b style="color:#f1f5f9;">${activeSignals.length}</b> desk${activeSignals.length !== 1 ? "s" : ""}</span>
<span style="font-size:11px;color:#475569;">${withSignals} reporting · ${awaiting} awaiting</span>
${tokenBadge}
${calibrationBadge}
${escBadge}
${renderLocalDelegationControl(localDelegation)}
</div>
${avgBlock}
</div>`;
@@ -945,8 +1056,9 @@ function renderStashedCard(entry) {
</div>`;
}
function renderDashboard(signals, stashed, capabilityToken) {
function renderDashboard(signals, stashed, capabilityToken, localDelegation) {
const activeSignals = sortSignals(signals.filter(s => !stashed.some(e => e.name === s.deskName)));
const localDelegationState = localDelegation || currentLocalDelegationLaunch("off");
const cards = activeSignals.length > 0
? activeSignals.map(renderSignalCard).join("")
@@ -975,7 +1087,9 @@ function renderDashboard(signals, stashed, capabilityToken) {
</div>
</div>`;
const summaryBar = activeSignals.length > 0 ? renderSummaryBar(activeSignals) : "";
// Always show the Local Delegation control so operators can see availability
// even before the first desk signal arrives.
const summaryBar = renderSummaryBar(activeSignals, localDelegationState);
const stashedSection = stashed.length > 0 ? `
<div style="margin-top:20px;padding-top:12px;border-top:1px solid #1a1a1a;">
@@ -1058,27 +1172,55 @@ function renderDashboard(signals, stashed, capabilityToken) {
const data = await res.json();
if (data.ok) {
const path = data.deskPath || name;
const notice = data.localDelegationNotice || {};
const localTitle = notice.titleSuffix || '';
const localDetail = notice.detail || '';
if (data.launched) {
// A successful open shouldn't hijack the user's clipboard.
showToast('opening ' + name + ' desk (' + selectedProfile + ')…', path);
// Surface LD state in the toast — operators cannot rely on -i alone.
showToast('opening ' + name + ' desk (' + selectedProfile + localTitle + ')…',
localDetail || path);
} else {
// No terminal launched from here, so copy the path as the
// fallback handle, but only claim the copy when it actually
// succeeded. The path shows in the toast either way.
let copied = false;
try { await navigator.clipboard.writeText(path); copied = true; } catch {}
showToast(copied ? (name + ' · path copied') : (name + ' · copy this path'), path);
const copyTitle = copied ? (name + ' · path copied') : (name + ' · copy this path');
showToast(copyTitle + localTitle, localDetail || path);
}
} else {
showToast(name + ' · not found', '');
}
}
async function setLocalDelegation(preference) {
const res = await fetch('/api/local-delegation?preference=' +
encodeURIComponent(preference || 'off'), POST_OPTS);
const data = await res.json();
if (data.ok) {
const ld = data.localDelegation || {};
const routeId = ld.availability && ld.availability.routeId;
const label = ld.effective
? ('Local Delegation effective' + (routeId ? (' · route ' + routeId) : ''))
: (ld.preference === 'on'
? 'Local Delegation requested (unavailable)'
: 'Local Delegation off');
showToast(label, ld.availability?.reason || '');
refresh();
} else {
showToast('Local Delegation · not updated', data.error || '');
}
}
document.addEventListener('click', (e) => {
const btn = e.target.closest('button[data-act]');
if (!btn) return;
const act = btn.getAttribute('data-act');
if (act === 'local-delegation') {
setLocalDelegation(btn.getAttribute('data-preference') || 'off');
return;
}
const name = btn.getAttribute('data-desk');
if (!name) return;
const act = btn.getAttribute('data-act');
const profile = btn.getAttribute('data-profile');
if (act === 'stash') stashDesk(name);
else if (act === 'restore') restoreDesk(name);
@@ -1102,19 +1244,26 @@ function renderDashboard(signals, stashed, capabilityToken) {
active.getAttribute('data-act'),
active.getAttribute('data-desk'),
active.getAttribute('data-profile') || '',
active.getAttribute('data-preference') || '',
]);
}
content.innerHTML = newContent.innerHTML;
if (focusKey) {
const [act, desk, profile] = JSON.parse(focusKey);
const escDesk = (window.CSS && CSS.escape) ? CSS.escape(desk) : desk;
const profileSelector = profile
? '[data-profile="' + profile + '"]'
: ':not([data-profile])';
const target = content.querySelector(
'button[data-act="' + act + '"][data-desk="' + escDesk + '"]' +
profileSelector);
const [act, desk, profile, preference] = JSON.parse(focusKey);
let target = null;
if (act === 'local-delegation') {
target = content.querySelector('button[data-act="local-delegation"]');
} else {
const escDesk = (window.CSS && CSS.escape) ? CSS.escape(desk) : desk;
const profileSelector = profile
? '[data-profile="' + profile + '"]'
: ':not([data-profile])';
target = content.querySelector(
'button[data-act="' + act + '"][data-desk="' + escDesk + '"]' +
profileSelector);
}
if (target) target.focus();
void preference;
}
}
} catch {}
@@ -1180,6 +1329,20 @@ async function startServer(instanceId, workshopDir) {
res.end(JSON.stringify({ ok: true }));
return;
}
if (req.method === "POST" && url.pathname === "/api/local-delegation") {
const preferenceInput = url.searchParams.get("preference") || "off";
if (!["off", "on"].includes(String(preferenceInput).toLowerCase())) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: false, error: "Invalid local delegation preference" }));
return;
}
const preference = normalizeLocalDelegationPreference(preferenceInput, "off");
await writeLocalDelegationPreference(workshopDir, preference);
const localDelegation = currentLocalDelegationLaunch(preference);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, localDelegation }));
return;
}
if (req.method === "POST" && url.pathname.startsWith("/api/open/")) {
const deskName = decodeURIComponent(url.pathname.split("/api/open/")[1]);
const profileInput = url.searchParams.get("profile") || DEFAULT_DESK_PROFILE;
@@ -1194,14 +1357,25 @@ async function startServer(instanceId, workshopDir) {
return;
}
const profile = normalizeDeskProfile(profileInput);
const preference = await readLocalDelegationPreference(workshopDir);
const localDelegation = currentLocalDelegationLaunch(preference);
for (const subdir of ["desks", "classroom"]) {
const deskPath = join(workshopDir, subdir, deskName);
try {
const s = await stat(deskPath);
if (s.isDirectory()) {
const launched = await launchDeskConsole(deskPath, deskName, workshopDir, profile);
const launched = await launchDeskConsole(
deskPath, deskName, workshopDir, profile, localDelegation);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, deskName, deskPath, launched, profile }));
res.end(JSON.stringify({
ok: true,
deskName,
deskPath,
launched,
profile,
localDelegation,
localDelegationNotice: formatLocalDelegationOpenNotice(localDelegation),
}));
return;
}
} catch {}
@@ -1213,8 +1387,10 @@ async function startServer(instanceId, workshopDir) {
const signals = await scanSignals(workshopDir);
const stashed = await readStash(workshopDir);
const preference = await readLocalDelegationPreference(workshopDir);
const localDelegation = currentLocalDelegationLaunch(preference);
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(renderDashboard(signals, stashed, capabilityToken));
res.end(renderDashboard(signals, stashed, capabilityToken, localDelegation));
} catch (err) {
// Top-level boundary: never leave a request hanging or let a
// rejection become an unhandled crash — e.g. malformed %-encoding
@@ -1327,7 +1503,7 @@ const session = await joinSession({
},
{
name: "open_desk",
description: "Open a desk as an in-place Copilot CLI session. Repo profile suppresses ambient plugin MCPs; connected keeps every configured tool. Returns the desk path, profile, and whether a terminal was launched.",
description: "Open a desk as an in-place Copilot CLI session. Repo profile suppresses ambient plugin MCPs; connected keeps every configured tool. Local Delegation is orthogonal and fail-closed: when available and preferred on, the frontier desk may use sealed local-agent-delegation for bounded read/evidence work. Returns the desk path, profile, localDelegation state, and whether a terminal was launched.",
inputSchema: {
type: "object",
properties: {
@@ -1337,6 +1513,11 @@ const session = await joinSession({
enum: ["repo", "connected"],
description: `Tool profile. Defaults to ${DEFAULT_DESK_PROFILE}.`,
},
localDelegation: {
type: "string",
enum: ["off", "on"],
description: "Optional Local Delegation preference for this launch. Defaults to the workshop Cairn toggle (.local-delegation.json).",
},
},
required: ["deskName"],
},
@@ -1347,13 +1528,21 @@ const session = await joinSession({
const profileInput = ctx.input.profile || DEFAULT_DESK_PROFILE;
if (!isDeskProfile(profileInput)) return { error: "Invalid desk profile" };
const profile = normalizeDeskProfile(profileInput);
const preferenceInput = ctx.input.localDelegation
?? await readLocalDelegationPreference(entry.workshopDir);
const preference = normalizeLocalDelegationPreference(preferenceInput, "off");
const localDelegation = currentLocalDelegationLaunch(preference);
for (const subdir of ["desks", "classroom"]) {
const deskPath = join(entry.workshopDir, subdir, ctx.input.deskName);
try {
const s = await stat(deskPath);
if (s.isDirectory()) {
const launched = await launchDeskConsole(
deskPath, ctx.input.deskName, entry.workshopDir, profile);
deskPath,
ctx.input.deskName,
entry.workshopDir,
profile,
localDelegation);
return {
ok: true,
deskName: ctx.input.deskName,
@@ -1361,6 +1550,8 @@ const session = await joinSession({
launched,
workshopDir: entry.workshopDir,
profile,
localDelegation,
localDelegationNotice: formatLocalDelegationOpenNotice(localDelegation),
};
}
} catch {}
@@ -25,6 +25,16 @@ 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); }
@@ -7,6 +7,7 @@ import { join } from "node:path";
import {
buildDeskAgentArgv,
isDeskProfile,
isSafeQuotedWindowsCmdArg,
isSafeWindowsCmdShim,
isWindowsAppExecutionAlias,
normalizeDeskProfile,
@@ -41,6 +42,10 @@ test("quotes trusted cmd shim arguments and rejects percent-bearing paths", () =
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", {
@@ -0,0 +1,401 @@
import { createHash } from "node:crypto";
import { accessSync, constants as fsConstants, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const PREFERENCES = new Set(["off", "on"]);
const SAFE_ROUTE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
/** @deprecated Repo-root state is rejected; kept only for docs/migration mentions. */
export const LOCAL_DELEGATION_STATE_FILE = ".local-delegation.json";
export const LOCAL_DELEGATION_ENV = "WORKSHOP_LOCAL_DELEGATION";
export const LOCAL_DELEGATION_SKILL_NAME = "local-agent-delegation";
export const LOCAL_DELEGATION_USER_STATE_DIR = join(".copilot", "workshop-local-delegation");
/**
* Stable user-local preference path for a workshop root.
* Permission state must NOT live in the cloned workshop (a repo can ship
* preference:on). Key by a hash of the canonical workshop path under ~/.copilot.
*/
export function localDelegationPreferencePath(workshopDir, {
home = homedir(),
resolvePath = (p) => p,
} = {}) {
if (typeof workshopDir !== "string" || !workshopDir) {
throw new Error("workshopDir is required");
}
let canonical = workshopDir;
try { canonical = resolvePath(workshopDir); } catch { /* keep input */ }
// Normalize separators only. Do not lowercase: on case-sensitive filesystems
// /work/Foo and /work/foo are distinct workshops and must not share state.
const key = createHash("sha256")
.update(String(canonical).replaceAll("\\", "/"))
.digest("hex")
.slice(0, 32);
return join(home, LOCAL_DELEGATION_USER_STATE_DIR, `${key}.json`);
}
export function isLocalDelegationPreference(value) {
return typeof value === "string" && PREFERENCES.has(value.toLowerCase());
}
export function normalizeLocalDelegationPreference(value, fallback = "off") {
return isLocalDelegationPreference(value) ? value.toLowerCase() : fallback;
}
// Windows Terminal / cmd reparse cannot safely carry long multi-space -i strings.
// Keep the orientation prompt short, ASCII, and quote-free. Local Delegation
// policy lives in WORKSHOP_LOCAL_DELEGATION=enabled + the installed skill — not
// on the CLI.
const MAX_ORIENT_PROMPT_CHARS = 280;
// Allow apostrophes (desk's). Ban double quotes, backticks, dashes that WT/cmd
// have split on, and classic cmd metacharacters.
const UNSAFE_ORIENT_CHARS = /["`—–|&<>^%!()\r\n]/;
export function isSafeDeskOrientPrompt(prompt) {
return typeof prompt === "string"
&& prompt.length > 0
&& prompt.length <= MAX_ORIENT_PROMPT_CHARS
&& !UNSAFE_ORIENT_CHARS.test(prompt);
}
/** One short ASCII notice operators can see in the session start prompt. */
export const LOCAL_DELEGATION_ORIENT_LINE = " Local Delegation env is enabled.";
export function deskOrientPrompt(deskName, { localDelegationEffective = false } = {}) {
// deskName is already constrained to a slug by the launcher; still keep the
// prompt free of punctuation that cmd/wt have historically mis-parsed.
let prompt = `You are sitting down at the ${deskName} desk in this workshop. ` +
`Read journal.md in this folder first to pick up where the last session ` +
`left off, then continue the desk's work. Write your journal before you stop.`;
// Only a single short ASCII line may ride on -i. Full policy stays in env + skill.
if (localDelegationEffective) {
const withNotice = prompt + LOCAL_DELEGATION_ORIENT_LINE;
if (isSafeDeskOrientPrompt(withNotice)) prompt = withNotice;
}
return prompt;
}
/**
* Operator-visible summary for open toasts and badges.
* Never claims savings; only reports effective state + route id when known.
*/
export function formatLocalDelegationOpenNotice(localDelegation) {
if (!localDelegation || typeof localDelegation !== "object") {
return { titleSuffix: "", detail: "" };
}
const routeId = localDelegation.availability?.routeId || null;
if (localDelegation.effective) {
const routePart = routeId ? ` · route ${routeId}` : "";
return {
titleSuffix: " · Local Delegation effective",
detail: `Local Delegation effective${routePart}`,
};
}
if (localDelegation.requested) {
return {
titleSuffix: " · local unavailable",
detail: localDelegation.warning
|| localDelegation.availability?.reason
|| "Local Delegation requested but unavailable",
};
}
return { titleSuffix: "", detail: "" };
}
export function buildLocalDelegationLaunchEnv(baseEnv = {}, { localDelegationEffective = false } = {}) {
const env = { ...baseEnv };
// Windows env names are case-insensitive; spreading process.env yields a
// case-sensitive object, so clear every spelling before optionally setting.
const target = LOCAL_DELEGATION_ENV.toLowerCase();
for (const key of Object.keys(env)) {
if (key.toLowerCase() === target) delete env[key];
}
if (localDelegationEffective) {
env[LOCAL_DELEGATION_ENV] = "enabled";
}
return env;
}
/**
* cmd.exe prefix that forces WORKSHOP_LOCAL_DELEGATION on or off inside a new
* Windows Terminal / console session. wt.exe does not reliably forward the
* caller's process env into a new tab when Terminal is already running.
*/
export function windowsLocalDelegationCmdPrefix(localDelegationEffective = false) {
return localDelegationEffective
? 'set "WORKSHOP_LOCAL_DELEGATION=enabled"&& '
: 'set "WORKSHOP_LOCAL_DELEGATION="&& ';
}
/**
* Savings credit is utilization accounting, not a price claim.
* Failed, unaccepted, redone, or escalated local work earns zero.
*/
export function localSavingsCredit({
attempted = false,
gateAccepted = false,
redone = false,
escalated = false,
} = {}) {
if (!attempted || !gateAccepted || redone || escalated) {
return {
credit: 0,
utilization: attempted ? "handled_locally_unaccepted" : "not_attempted",
reason: !attempted
? "not_attempted"
: escalated
? "escalated"
: redone
? "redone"
: "gate_not_accepted",
};
}
return {
credit: 0, // dollar savings are never claimed by Cairn
utilization: "handled_locally_accepted",
reason: "accepted_utilization_only",
};
}
function isReadableFile(path) {
try {
if (!statSync(path).isFile()) return false;
accessSync(path, fsConstants.R_OK);
return true;
} catch {
return false;
}
}
function skillMarkerPath(dir) {
return join(dir, "SKILL.md");
}
function looksLikeSkillDir(dir) {
return isReadableFile(skillMarkerPath(dir));
}
/**
* Discover the installed local-agent-delegation skill.
* Injectable probes keep unit tests filesystem-free.
*/
export function findLocalDelegationSkillDir({
env = process.env,
home = homedir(),
exists = existsSync,
isSkillDir = looksLikeSkillDir,
} = {}) {
const explicit = (env.WORKSHOP_LOCAL_DELEGATION_SKILL_DIR || "").trim();
if (explicit) {
return isSkillDir(explicit) ? explicit : null;
}
const candidates = [
join(home, ".copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(home, ".agents", "skills", LOCAL_DELEGATION_SKILL_NAME),
];
for (const candidate of candidates) {
if (isSkillDir(candidate)) return candidate;
}
// Marketplace: ~/.copilot/installed-plugins/<marketplace>/<plugin>/
// Direct: ~/.copilot/installed-plugins/_direct/<plugin>/
// Skill dirs may live at skills/, .github/skills/, or com.github.copilot/skills/.
const pluginsRoot = join(home, ".copilot", "installed-plugins");
if (exists(pluginsRoot)) {
try {
for (const market of readdirSync(pluginsRoot, { withFileTypes: true })) {
if (!market.isDirectory()) continue;
const marketRoot = join(pluginsRoot, market.name);
let pluginEntries;
try {
pluginEntries = readdirSync(marketRoot, { withFileTypes: true });
} catch {
continue;
}
for (const plugin of pluginEntries) {
if (!plugin.isDirectory()) continue;
const pluginRoot = join(marketRoot, plugin.name);
const nestedCandidates = [
join(pluginRoot, "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, ".github", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, "com.github.copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, "com.github.awesome-copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
];
for (const nested of nestedCandidates) {
if (isSkillDir(nested)) return nested;
}
}
}
} catch {
// fail closed on scan errors
}
}
return null;
}
function readJsonFile(path, readFile = readFileSync) {
try {
return JSON.parse(readFile(path, "utf8"));
} catch {
return null;
}
}
function isRouteId(value) {
return typeof value === "string" && SAFE_ROUTE_ID.test(value);
}
/**
* Fail-closed availability. Enable only when skill + qualified route receipt
* (or explicit env route id) are present. Never invent availability.
*/
export function resolveLocalDelegationAvailability({
env = process.env,
home = homedir(),
now = Date.now(),
findSkill = findLocalDelegationSkillDir,
readFile = readFileSync,
exists = existsSync,
} = {}) {
const forced = (env.WORKSHOP_LOCAL_DELEGATION_AVAILABLE || "").trim().toLowerCase();
if (forced === "0" || forced === "false" || forced === "unavailable") {
return {
available: false,
reason: "Forced unavailable by WORKSHOP_LOCAL_DELEGATION_AVAILABLE",
skillDir: null,
routeId: null,
};
}
const skillDir = findSkill({ env, home, exists });
if (!skillDir) {
return {
available: false,
reason: "local-agent-delegation skill is not installed",
skillDir: null,
routeId: null,
};
}
const envRoute = (env.WORKSHOP_LOCAL_DELEGATION_ROUTE_ID || "").trim();
if (envRoute) {
if (!isRouteId(envRoute)) {
return {
available: false,
reason: "WORKSHOP_LOCAL_DELEGATION_ROUTE_ID is not a safe route id",
skillDir,
routeId: null,
};
}
return {
available: true,
reason: "Skill installed; route id provided by environment",
skillDir,
routeId: envRoute,
};
}
const receiptPath = (env.WORKSHOP_LOCAL_DELEGATION_RECEIPT || "").trim()
|| join(home, ".copilot", "local-agent-runs", "qualified-route.json");
if (!exists(receiptPath)) {
return {
available: false,
reason: "No qualified route receipt (set WORKSHOP_LOCAL_DELEGATION_ROUTE_ID or write ~/.copilot/local-agent-runs/qualified-route.json)",
skillDir,
routeId: null,
};
}
const receipt = readJsonFile(receiptPath, readFile);
if (!receipt || typeof receipt !== "object") {
return {
available: false,
reason: "Qualified route receipt is unreadable",
skillDir,
routeId: null,
};
}
const status = String(receipt.status || "").toLowerCase();
if (status !== "qualified") {
return {
available: false,
reason: `Route receipt status is '${receipt.status || "missing"}', not qualified`,
skillDir,
routeId: isRouteId(receipt.route_id || receipt.routeId) ? (receipt.route_id || receipt.routeId) : null,
};
}
const routeId = receipt.route_id || receipt.routeId || null;
if (!isRouteId(routeId)) {
return {
available: false,
reason: "Route receipt is missing a safe route_id",
skillDir,
routeId: null,
};
}
if (receipt.expires_at || receipt.expiresAt) {
const expires = Date.parse(receipt.expires_at || receipt.expiresAt);
if (!Number.isFinite(expires) || expires <= now) {
return {
available: false,
reason: "Qualified route receipt has expired",
skillDir,
routeId,
};
}
}
return {
available: true,
reason: "Skill installed; qualified route receipt present",
skillDir,
routeId,
};
}
export function resolveLocalDelegationLaunch({
preference = "off",
availability,
} = {}) {
const pref = normalizeLocalDelegationPreference(preference, "off");
const available = Boolean(availability?.available);
if (pref === "on" && !available) {
return {
preference: pref,
requested: true,
effective: false,
availability,
warning: availability?.reason || "Local Delegation unavailable",
};
}
return {
preference: pref,
requested: pref === "on",
effective: pref === "on" && available,
availability,
warning: null,
};
}
export function parseLocalDelegationState(raw) {
if (!raw || typeof raw !== "object") {
return { preference: "off" };
}
return {
preference: normalizeLocalDelegationPreference(raw.preference, "off"),
};
}
export function serializeLocalDelegationState(state) {
return {
preference: normalizeLocalDelegationPreference(state?.preference, "off"),
updatedAt: new Date().toISOString(),
};
}
@@ -0,0 +1,298 @@
import test from "node:test";
import assert from "node:assert/strict";
import { join, sep } from "node:path";
import {
LOCAL_DELEGATION_ORIENT_LINE,
buildLocalDelegationLaunchEnv,
deskOrientPrompt,
findLocalDelegationSkillDir,
formatLocalDelegationOpenNotice,
isLocalDelegationPreference,
isSafeDeskOrientPrompt,
localDelegationPreferencePath,
localSavingsCredit,
normalizeLocalDelegationPreference,
parseLocalDelegationState,
resolveLocalDelegationAvailability,
resolveLocalDelegationLaunch,
serializeLocalDelegationState,
windowsLocalDelegationCmdPrefix,
} from "./local-delegation.mjs";
import {
buildDeskAgentArgv,
normalizeDeskProfile,
quoteWindowsCmdArgument,
} from "./launch-profile.mjs";
test("normalizes local-delegation preference independently of desk profile", () => {
assert.equal(isLocalDelegationPreference("on"), true);
assert.equal(isLocalDelegationPreference("OFF"), true);
assert.equal(isLocalDelegationPreference("maybe"), false);
assert.equal(normalizeLocalDelegationPreference("ON"), "on");
assert.equal(normalizeLocalDelegationPreference("nope"), "off");
// Profile axis remains orthogonal and untouched.
assert.equal(normalizeDeskProfile("connected"), "connected");
assert.equal(normalizeDeskProfile("repo"), "repo");
});
test("availability is fail-closed without skill or route receipt", () => {
const missingSkill = resolveLocalDelegationAvailability({
env: {},
home: "C:\\home",
findSkill: () => null,
exists: () => false,
});
assert.equal(missingSkill.available, false);
assert.match(missingSkill.reason, /not installed/i);
const skillOnly = resolveLocalDelegationAvailability({
env: {},
home: "C:\\home",
findSkill: () => "C:\\home\\.copilot\\skills\\local-agent-delegation",
exists: () => false,
});
assert.equal(skillOnly.available, false);
assert.match(skillOnly.reason, /No qualified route receipt/i);
});
test("availability accepts env route id or a qualified receipt", () => {
const home = join("home");
const skillDir = join("skills", "local-agent-delegation");
const viaEnv = resolveLocalDelegationAvailability({
env: { WORKSHOP_LOCAL_DELEGATION_ROUTE_ID: "foundry-qwen25-7b-qualified" },
home,
findSkill: () => skillDir,
exists: () => false,
});
assert.equal(viaEnv.available, true);
assert.equal(viaEnv.routeId, "foundry-qwen25-7b-qualified");
// Match host path.join separators so Linux CI exercises the receipt branch.
const receiptPath = join(home, ".copilot", "local-agent-runs", "qualified-route.json");
const viaReceipt = resolveLocalDelegationAvailability({
env: {},
home,
now: Date.parse("2026-08-14T12:00:00Z"),
findSkill: () => skillDir,
exists: (p) => p === receiptPath,
readFile: () => JSON.stringify({
status: "qualified",
route_id: "foundry-qwen25-7b-qualified",
expires_at: "2026-12-01T00:00:00Z",
}),
});
assert.equal(viaReceipt.available, true);
assert.equal(viaReceipt.routeId, "foundry-qwen25-7b-qualified");
const expired = resolveLocalDelegationAvailability({
env: {},
home,
now: Date.parse("2027-01-01T00:00:00Z"),
findSkill: () => skillDir,
exists: (p) => p === receiptPath,
readFile: () => JSON.stringify({
status: "qualified",
route_id: "foundry-qwen25-7b-qualified",
expires_at: "2026-12-01T00:00:00Z",
}),
});
assert.equal(expired.available, false);
assert.match(expired.reason, /expired/i);
});
test("requested on + unavailable stays ineffective with a warning", () => {
const launch = resolveLocalDelegationLaunch({
preference: "on",
availability: {
available: false,
reason: "local-agent-delegation skill is not installed",
},
});
assert.equal(launch.requested, true);
assert.equal(launch.effective, false);
assert.match(launch.warning, /not installed/i);
});
test("launch env enables when effective; -i prompt stays short and quote-free", () => {
const base = {
PATH: "/usr/bin",
WORKSHOP_LOCAL_DELEGATION: "enabled",
workshop_local_delegation: "enabled",
};
const offEnv = buildLocalDelegationLaunchEnv(base, { localDelegationEffective: false });
assert.equal(Object.hasOwn(offEnv, "WORKSHOP_LOCAL_DELEGATION"), false);
assert.equal(Object.hasOwn(offEnv, "workshop_local_delegation"), false);
assert.equal(offEnv.PATH, "/usr/bin");
const onEnv = buildLocalDelegationLaunchEnv(base, { localDelegationEffective: true });
assert.equal(onEnv.WORKSHOP_LOCAL_DELEGATION, "enabled");
assert.equal(Object.hasOwn(onEnv, "workshop_local_delegation"), false);
const offPrompt = deskOrientPrompt("cost-desk", { localDelegationEffective: false });
const onPrompt = deskOrientPrompt("cost-desk", { localDelegationEffective: true });
assert.match(offPrompt, /cost-desk/);
assert.equal(offPrompt.includes("Local Delegation"), false);
// One short ASCII line only — never the long policy appendix.
assert.equal(onPrompt, offPrompt + LOCAL_DELEGATION_ORIENT_LINE);
assert.match(onPrompt, /Local Delegation env is enabled\./);
assert.equal(onPrompt.includes("do not delegate"), false);
assert.equal(isSafeDeskOrientPrompt(onPrompt), true);
assert.equal(isSafeDeskOrientPrompt('bad "quote"'), false);
assert.equal(isSafeDeskOrientPrompt("em dash — bad"), false);
assert.match(windowsLocalDelegationCmdPrefix(true), /enabled/);
assert.match(windowsLocalDelegationCmdPrefix(false), /WORKSHOP_LOCAL_DELEGATION="&&/);
const run = ["C:\\tools\\copilot.exe", "-i", onPrompt];
const cmdLine = windowsLocalDelegationCmdPrefix(true) + run.map(quoteWindowsCmdArgument).join(" ");
assert.match(cmdLine, /^set "WORKSHOP_LOCAL_DELEGATION=enabled"&& /);
assert.match(cmdLine, /copilot\.exe/);
});
test("open notice reports effective route without savings claims", () => {
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: true,
requested: true,
availability: { available: true, routeId: "foundry-qwen25-7b-qualified" },
}), {
titleSuffix: " · Local Delegation effective",
detail: "Local Delegation effective · route foundry-qwen25-7b-qualified",
});
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: false,
requested: true,
warning: "local-agent-delegation skill is not installed",
availability: { available: false, reason: "local-agent-delegation skill is not installed" },
}).detail, "local-agent-delegation skill is not installed");
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: false,
requested: false,
}), { titleSuffix: "", detail: "" });
});
test("repo/connected argv stays orthogonal to local-delegation preference", () => {
const repo = buildDeskAgentArgv({
deskName: "cost-desk",
workshopDir: "/workshop",
useAgency: false,
copilotCommand: "copilot",
profile: "repo",
pluginMcpNames: ["teams"],
});
const connected = buildDeskAgentArgv({
deskName: "cost-desk",
workshopDir: "/workshop",
useAgency: false,
copilotCommand: "copilot",
profile: "connected",
pluginMcpNames: ["teams"],
});
assert.deepEqual(repo, [
"copilot", "--name", "cost-desk",
"--disable-mcp-server", "teams",
"--add-dir", "/workshop",
]);
assert.deepEqual(connected, [
"copilot", "--name", "cost-desk",
"--add-dir", "/workshop",
]);
// Local delegation never injects into argv — only env/prompt.
assert.equal(repo.includes("local"), false);
assert.equal(connected.includes("local"), false);
});
test("failed or unaccepted local work earns zero savings credit", () => {
assert.equal(localSavingsCredit({ attempted: false }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: false }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: true, redone: true }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: true, escalated: true }).credit, 0);
const accepted = localSavingsCredit({ attempted: true, gateAccepted: true });
assert.equal(accepted.credit, 0);
assert.equal(accepted.utilization, "handled_locally_accepted");
});
test("state parse/serialize defaults to off", () => {
assert.deepEqual(parseLocalDelegationState(null), { preference: "off" });
assert.equal(parseLocalDelegationState({ preference: "ON" }).preference, "on");
const serialized = serializeLocalDelegationState({ preference: "on" });
assert.equal(serialized.preference, "on");
assert.equal(typeof serialized.updatedAt, "string");
});
test("preference path is user-local and keyed by workshop path, not the repo root", () => {
const home = join("user-home");
const a = localDelegationPreferencePath(join("repos", "workshop-a"), {
home,
resolvePath: (p) => p,
});
const b = localDelegationPreferencePath(join("repos", "workshop-b"), {
home,
resolvePath: (p) => p,
});
const again = localDelegationPreferencePath(join("repos", "workshop-a"), {
home,
resolvePath: (p) => p,
});
assert.match(a, /workshop-local-delegation/);
assert.equal(a.startsWith(home), true);
assert.equal(a.includes(`${sep}repos${sep}`), false);
assert.notEqual(a, b);
assert.equal(a, again);
// Repo-shipped .local-delegation.json is never the preference path.
assert.equal(a.endsWith(".local-delegation.json"), false);
// Case-sensitive filesystems: Foo and foo must not share permission state.
const upper = localDelegationPreferencePath("/work/Foo", {
home,
resolvePath: (p) => p,
});
const lower = localDelegationPreferencePath("/work/foo", {
home,
resolvePath: (p) => p,
});
assert.notEqual(upper, lower);
});
test("skill discovery respects explicit dir and common install roots", () => {
const found = findLocalDelegationSkillDir({
env: { WORKSHOP_LOCAL_DELEGATION_SKILL_DIR: "D:\\sealed\\.github\\skills\\local-agent-delegation" },
home: "C:\\home",
exists: () => false,
isSkillDir: (p) => p === "D:\\sealed\\.github\\skills\\local-agent-delegation",
});
assert.equal(found, "D:\\sealed\\.github\\skills\\local-agent-delegation");
const missingExplicit = findLocalDelegationSkillDir({
env: { WORKSHOP_LOCAL_DELEGATION_SKILL_DIR: "D:\\missing" },
home: "C:\\home",
exists: () => false,
isSkillDir: () => false,
});
assert.equal(missingExplicit, null);
});
test("skill discovery walks marketplace/plugin and _direct install layouts", async () => {
const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import("node:fs");
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const home = mkdtempSync(join(tmpdir(), "ld-skill-"));
try {
const skillDir = join(
home, ".copilot", "installed-plugins", "awesome-copilot", "sealed-delegation",
".github", "skills", "local-agent-delegation");
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), "# local-agent-delegation\n");
const found = findLocalDelegationSkillDir({ home, env: {} });
assert.equal(found, skillDir);
rmSync(join(home, ".copilot", "installed-plugins", "awesome-copilot"), { recursive: true, force: true });
const direct = join(
home, ".copilot", "installed-plugins", "_direct", "sealed-delegation",
"skills", "local-agent-delegation");
mkdirSync(direct, { recursive: true });
writeFileSync(join(direct, "SKILL.md"), "# local-agent-delegation\n");
const foundDirect = findLocalDelegationSkillDir({ home, env: {} });
assert.equal(foundDirect, direct);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
@@ -1,15 +1,15 @@
{
"name": "signals-dashboard",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"main": "extension.mjs",
"scripts": {
"test": "node --test launch-profile.test.mjs"
"test": "node --test launch-profile.test.mjs local-delegation.test.mjs"
},
"dependencies": {
"@github/copilot-sdk": "latest"
},
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, and cost-aware repo or connected desk launch profiles.",
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, cost-aware desk profiles, and fail-closed Local Delegation.",
"keywords": [
"agent-signals",
"dashboard",
+4 -3
View File
@@ -1,8 +1,8 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "signals-dashboard",
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, and cost-aware repo or connected desk launch profiles.",
"version": "0.2.0",
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, cost-aware desk profiles, and fail-closed Local Delegation.",
"version": "0.3.0",
"author": {
"name": "jennyf19",
"url": "https://github.com/jennyf19"
@@ -12,7 +12,8 @@
"dashboard",
"multi-agent",
"coordination",
"canvas"
"canvas",
"local-delegation"
],
"extensions": {
"com.github.copilot": {
+7
View File
@@ -45,6 +45,13 @@ Each desk card has two launch choices:
- **connected** — the full configured tool surface for work that needs external
systems such as issue trackers, mail, or service APIs.
The summary bar also has a **Local** toggle. It is orthogonal to repo/connected:
when available and on, the frontier desk may use
[Sealed Delegation](https://github.com/jennyf19/sealed-delegation)'s
`local-agent-delegation` skill for bounded read/evidence work. Availability is
fail-closed (installed skill + qualified route receipt). Local output is a
proposal and never earns savings credit on its own.
Set `WORKSHOP_DESK_PROFILE=connected` to make the main **open** button use the
full tool surface; in that mode the separate **connected** button is omitted.
@@ -0,0 +1,145 @@
# 🪨 Cairn — Signals Dashboard
A live GitHub Copilot CLI **canvas extension** that shows the pulse of every
desk in your Workshop. It reads the agent signals your desks emit and renders
them as a compact, always-current dashboard in a side panel — so you can direct
the work instead of polling each desk by hand.
It replaces the old Blazor **WorkshopRoom** dashboard (`src/WorkshopRoom/`) with
a native canvas that runs inside GHCP, with no separate web app to launch.
## What it is
Each desk in the Workshop leaves signals behind — small stones on the trail —
by writing JSON files into its `.signals/` folder. Cairn scans those folders,
picks the most recent signal per desk, and renders:
- **Score bars** for the desk's self-assessment (intent, confidence, accuracy,
completeness).
- **Patterns** the desk reported: what worked ✓, what was hard △, and skill
gaps ✗.
- **Escalations** — desks that raised their hand, with what they're blocked on
and their recommendation, pinned to the top and pulsing red.
## How to open it
The dashboard is registered as the **🪨 Cairn** canvas (`signals-dashboard`).
Ask Copilot to open it and pass your workshop root as `workshopDir`:
> Open the 🪨 Cairn canvas with `workshopDir` set to the folder that contains
> my `desks/` directory.
`workshopDir` must be the **absolute path to the workshop root** — the folder
that holds `desks/` (and optionally `classroom/`). If omitted, it falls back to
the current working directory.
## Features
- **Signal scanning** — walks `desks/*/.signals/` and `classroom/*/.signals/`,
reading the newest `*.json` per desk (mirrors `SignalReader.cs`).
- **Score bars** — color-coded intent / confidence / accuracy / completeness,
scored out of 5.
- **Escalation alerts** — escalation signals sort to the very top, render with a
pulsing red border, and surface the blocker + recommendation.
- **Active desks first** — sorted escalations → recent signals → desks with no
signal yet, then by recency.
- **Stash / restore** — pause a workstream by stashing its desk. Stashed desks
drop off the active view and auto-expire after a **48-hour TTL**; restore any
time before then. Stash state lives in `.desk-stash.json` at the workshop
root.
- **Auto-refresh** — the panel refreshes every 5 seconds using a background
fetch (no full page reload), so scores and escalations stay current smoothly.
- **Summary bar** — desk count, how many are reporting vs. awaiting, an
escalation badge, and average scores across the room.
- **Cost-aware desk launch****open** starts a repo-profile desk that keeps the
verified Workshop root available while suppressing ambient plugin MCPs.
**connected** preserves every configured MCP for work that needs external
systems. Agency remains the preferred wrapper when installed.
- **Local Delegation** — orthogonal off/on control. When available and on, the
frontier desk may use the installed [`local-agent-delegation`](https://github.com/jennyf19/sealed-delegation)
skill for bounded, independently gated read/evidence work. Fail-closed: no
skill or no qualified route receipt means the toggle cannot take effect, and
no local-savings credit is awarded.
## Agent actions
The canvas also exposes actions Copilot can invoke directly:
- `refresh` — force a rescan and return current signal data as JSON.
- `stash` — stash a desk by `deskName`.
- `restore` — restore a stashed desk by `deskName`.
- `open_desk` — open a desk with optional `profile: "repo" | "connected"` and
optional `localDelegation: "off" | "on"`.
## Desk launch profiles
`repo` is the default. At launch, Cairn asks Copilot for the enabled
plugin-scoped MCP inventory and disables those ambient servers for the topic
desk. User-, workspace-, organization-, and built-in resources are left alone.
If discovery fails, Copilot plugin MCP suppression fails open; Agency repo mode
still omits Agency's own default MCPs.
When Agency is installed, Cairn keeps the existing `agency copilot` launch and
adds Agency's `--no-default-mcps` in repo mode. Outside Agency, the same profile
is applied directly to Copilot CLI.
Both profiles pass `--add-dir <workshop-root>` so a desk can intentionally read
another desk's journal or artifact without receiving access outside the room.
Set `WORKSHOP_DESK_PROFILE=connected` to retain the historical default for the
main **open** button. The separate **connected** button is always available when
repo mode is the default.
## Local Delegation
Local Delegation is **not** a third desk profile and does not replace the frontier
model. It is a separate permission bit:
```text
repo / connected = which MCPs and tools the frontier desk can see
Local Delegation off/on = whether the frontier desk may invoke a bounded local worker
```
Availability is fail-closed. Cairn enables the lane only when:
1. the `local-agent-delegation` skill is installed, and
2. a qualified route is declared via `WORKSHOP_LOCAL_DELEGATION_ROUTE_ID` or a
receipt at `~/.copilot/local-agent-runs/qualified-route.json`
(`status: "qualified"`, safe `route_id`).
Operator preference is stored **user-locally** under
`~/.copilot/workshop-local-delegation/` (keyed by the canonical workshop path),
never in the cloned workshop — a repo cannot ship `preference: on`.
When the preference is on but availability fails, opens still launch as frontier
desks and surface the reason — they never silently fall back with savings credit.
When effective, Cairn sets `WORKSHOP_LOCAL_DELEGATION=enabled` on the launched
process, shows an open toast/badge (`Local Delegation effective · route …`), and
may append one short ASCII line to `-i` (`Local Delegation env is enabled.`) when
that combined prompt stays quote-free and under the length guard. Full policy
still lives in the env flag plus the installed `local-agent-delegation` skill —
never a long multi-sentence `-i` appendix (Windows Terminal reparse).
The runtime, launcher, and gates remain owned by
[Sealed Delegation](https://github.com/jennyf19/sealed-delegation).
## Signal shape
Cairn reads the agent-signals protocol used across the Workshop:
```json
{
"signal_type": "execution",
"agent_name": "desk-name",
"self_assessment": { "intent": 5, "confidence": 4, "accuracy": 4, "completeness": 3 },
"patterns": { "what_worked": "...", "what_was_hard": "...", "skill_gap": "..." },
"escalation": { "reason": "...", "blocked_on": "...", "recommendation": "..." }
}
```
`escalation` is only present on `signal_type: "escalation"` signals.
## Replaces the Blazor WorkshopRoom
This canvas supersedes the standalone Blazor dashboard in `src/WorkshopRoom/`.
The data is the truth and the UI is just a view — Cairn renders the same signal
data natively inside GHCP, so there's no separate server to run.
@@ -5,20 +5,33 @@
import { createServer } from "node:http";
import { statSync, accessSync, realpathSync, constants as fsConstants } from "node:fs";
import { readdir, readFile, writeFile, stat } from "node:fs/promises";
import { join, delimiter, isAbsolute, sep } from "node:path";
import { readdir, readFile, writeFile, stat, rename, unlink, mkdir } from "node:fs/promises";
import { join, delimiter, isAbsolute, sep, dirname } from "node:path";
import { spawn } from "node:child_process";
import { randomBytes } from "node:crypto";
import { joinSession, createCanvas } from "@github/copilot-sdk/extension";
import {
buildDeskAgentArgv,
isDeskProfile,
isSafeQuotedWindowsCmdArg,
isSafeWindowsCmdShim,
isWindowsAppExecutionAlias,
normalizeDeskProfile,
parsePluginMcpNames,
quoteWindowsCmdArgument,
} from "./launch-profile.mjs";
import {
buildLocalDelegationLaunchEnv,
deskOrientPrompt,
formatLocalDelegationOpenNotice,
localDelegationPreferencePath,
normalizeLocalDelegationPreference,
parseLocalDelegationState,
resolveLocalDelegationAvailability,
resolveLocalDelegationLaunch,
serializeLocalDelegationState,
windowsLocalDelegationCmdPrefix,
} from "./local-delegation.mjs";
const servers = new Map();
const STASH_TTL_MS = 48 * 60 * 60 * 1000;
@@ -60,11 +73,10 @@ function isValidDeskName(name) {
// path is then only ever passed as a spawn cwd, an argv element, or a
// single-quoted literal inside the macOS Terminal command — never concatenated
// raw onto a command line — so no character filtering of the path is required.
function deskOrientPrompt(deskName) {
return `You are sitting down at the ${deskName} desk in this workshop. ` +
`Read journal.md in this folder first to pick up where the last session ` +
`left off, then continue the desk's work. Write your journal before you stop.`;
}
//
// Local Delegation is orthogonal to repo/connected: it never changes the tool
// surface. When effective, only the orientation prompt and child env mark that
// the frontier desk may use the installed local-agent-delegation skill.
// Spawn detached and resolve true only once the OS confirms the process
// started ('spawn'), false on failure ('error', e.g. the binary is missing) so
@@ -354,7 +366,62 @@ function isInsideRoot(root, target) {
} catch { return false; }
}
async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFAULT_DESK_PROFILE) {
function preferenceStatePath(workshopDir) {
return localDelegationPreferencePath(workshopDir, {
resolvePath: (p) => {
try { return realpathSync(p); } catch { return p; }
},
});
}
async function readLocalDelegationPreference(workshopDir) {
// Never read preference from the workshop repo — a clone can ship
// preference:on. Only user-local state (or explicit env) counts.
try {
const raw = JSON.parse(await readFile(preferenceStatePath(workshopDir), "utf8"));
return parseLocalDelegationState(raw).preference;
} catch {
return normalizeLocalDelegationPreference(
process.env.WORKSHOP_LOCAL_DELEGATION_PREFERENCE, "off");
}
}
async function writeLocalDelegationPreference(workshopDir, preference) {
const state = serializeLocalDelegationState({ preference });
const target = preferenceStatePath(workshopDir);
await mkdir(dirname(target), { recursive: true });
// Atomic replace in the user-local dir (temp + rename).
const tmp = join(
dirname(target),
`.pref.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
const body = JSON.stringify(state, null, 2) + "\n";
try {
await writeFile(tmp, body, { encoding: "utf8", flag: "wx" });
try {
await rename(tmp, target);
} catch {
await unlink(target).catch(() => {});
await rename(tmp, target);
}
} catch (err) {
await unlink(tmp).catch(() => {});
throw err;
}
return state;
}
function currentLocalDelegationLaunch(preference) {
const availability = resolveLocalDelegationAvailability();
return resolveLocalDelegationLaunch({ preference, availability });
}
async function launchDeskConsole(
deskPath,
deskName,
workshopDir,
profile = DEFAULT_DESK_PROFILE,
localDelegation = { effective: false },
) {
// deskName must be a plain slug so it is safe on every command line and shell
// below, and the resolved desk must still live inside the workshop root
// (which defeats a symlinked desk that escapes the repo). deskPath itself is
@@ -367,24 +434,27 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
if (!isInsideRoot(workshopDir, deskPath)) return false;
const agent = await deskAgentArgv(deskName, workshopDir, profile);
if (!agent) return false;
const run = [...agent, "-i", deskOrientPrompt(deskName)];
const effective = Boolean(localDelegation?.effective);
const run = [...agent, "-i", deskOrientPrompt(deskName, { localDelegationEffective: effective })];
const env = buildLocalDelegationLaunchEnv(process.env, { localDelegationEffective: effective });
if (process.platform === "win32") {
const wt = resolveOnPath("wt", { directOnly: true, excludedRoot: workshopDir });
const cmd = resolveSystem32Executable("cmd.exe");
const direct = /\.(exe|com)$/i.test(run[0]);
if (direct && wt && await trySpawn(wt, ["-d", deskPath, ...run])) return true;
// Older installs can expose .cmd/.bat shims. Only use cmd.exe when every
// argument is free of cmd metacharacters; otherwise fail closed and let
// the UI copy the desk path rather than reparse an unsafe workshop path.
const cmdSafe = run.every((arg) => !/[&|<>^%!()\r\n]/.test(arg));
if (cmdSafe && wt && cmd &&
await trySpawn(wt, ["-d", deskPath, cmd, "/k", ...run])) return true;
// Fallback when wt.exe is absent: a fresh console window via `start`,
// still through cmd /k only when the arguments are safe for reparsing.
return cmdSafe && cmd
? await trySpawn(cmd, ["/c", "start", "", cmd, "/k", ...run], { cwd: deskPath })
: false;
// wt.exe does not reliably forward Node's spawn env into a new tab when
// Windows Terminal is already running. Always start through cmd.exe and
// set/clear WORKSHOP_LOCAL_DELEGATION in the command string itself.
// Args are quoteWindowsCmdArgument'd, so only block expanders that still
// fire inside quotes (% and !) — allow parentheses in workshop paths.
const cmdSafe = run.every((arg) => isSafeQuotedWindowsCmdArg(arg));
if (!cmdSafe || !cmd) return false;
const inner = windowsLocalDelegationCmdPrefix(effective)
+ run.map(quoteWindowsCmdArgument).join(" ");
if (wt && await trySpawn(wt, ["-d", deskPath, cmd, "/d", "/s", "/k", inner], { env })) {
return true;
}
// Fallback when wt.exe is absent: a fresh console window via `start`.
return await trySpawn(
cmd, ["/c", "start", "", cmd, "/d", "/s", "/k", inner], { cwd: deskPath, env });
}
if (process.platform === "darwin") {
const osascript = "/usr/bin/osascript";
@@ -393,13 +463,18 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
// to cd into the desk and exec the agent. Each argv element is POSIX
// single-quoted so the shell can't reinterpret it, and osascript itself
// is spawned via argv (no shell).
const line = "cd " + shSingleQuote(deskPath) + " && exec " +
// Local-delegation env is exported in-line so the Terminal session sees it
// without inheriting a polluted parent shell forever.
const envPrefix = effective
? "export WORKSHOP_LOCAL_DELEGATION=enabled; "
: "unset WORKSHOP_LOCAL_DELEGATION; ";
const line = "cd " + shSingleQuote(deskPath) + " && " + envPrefix + "exec " +
run.map(shSingleQuote).join(" ");
const script = 'tell application "Terminal"\n' +
" activate\n" +
" do script " + osaStringLiteral(line) + "\n" +
"end tell";
return await trySpawn(osascript, ["-e", script]);
return await trySpawn(osascript, ["-e", script], { env });
}
// Linux/other: best-effort across common terminal emulators. Each is spawned
// via argv (no shell) with the agent command after the emulator's exec flag,
@@ -412,7 +487,7 @@ async function launchDeskConsole(deskPath, deskName, workshopDir, profile = DEFA
];
for (const [term, args] of linuxTerms) {
const executable = resolveOnPath(term, { excludedRoot: workshopDir });
if (executable && await trySpawn(executable, args, { cwd: deskPath })) return true;
if (executable && await trySpawn(executable, args, { cwd: deskPath, env })) return true;
}
return false;
}
@@ -728,7 +803,42 @@ function avgScore(signals) {
return { confidence: avg("confidence"), accuracy: avg("accuracy"), completeness: avg("completeness"), intent: avg("intentScore") };
}
function renderSummaryBar(activeSignals) {
function renderLocalDelegationControl(localDelegation) {
const pref = localDelegation?.preference || "off";
const available = Boolean(localDelegation?.availability?.available);
const effective = Boolean(localDelegation?.effective);
const reason = localDelegation?.availability?.reason || "Local Delegation unavailable";
const routeId = localDelegation?.availability?.routeId || null;
const next = pref === "on" ? "off" : "on";
const label = effective ? "On" : (pref === "on" ? "On*" : "Off");
const color = effective ? "#86efac" : (pref === "on" ? "#fbbf24" : "#94a3b8");
const border = effective ? "#166534" : (pref === "on" ? "#854d0e" : "#334155");
const title = available
? (effective
? `Local Delegation effective${routeId ? ` · route ${routeId}` : ""}`
: "Local Delegation available but currently off")
: reason;
const note = effective && routeId
? `<span style="font-size:10px;color:#86efac;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(title)}">effective · ${esc(truncate(routeId, 28))}</span>`
: !available
? `<span style="font-size:10px;color:#64748b;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(reason)}">${esc(truncate(reason, 48))}</span>`
: (pref === "on" && !effective
? `<span style="font-size:10px;color:#fbbf24;">requested, unavailable</span>`
: "");
return `
<div style="display:flex;align-items:center;gap:6px;" title="${esc(title)}">
<span style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:.04em;">Local</span>
<button data-act="local-delegation" data-preference="${esc(next)}"
aria-label="Local Delegation ${esc(label)}${effective && routeId ? ` route ${esc(routeId)}` : ""}"
aria-pressed="${pref === "on" ? "true" : "false"}"
style="background:#020617;border:1px solid ${border};color:${color};padding:2px 8px;border-radius:999px;
font-size:11px;cursor:pointer;font-weight:600;min-width:42px;"
title="${esc(title)}">${esc(label)}</button>
${note}
</div>`;
}
function renderSummaryBar(activeSignals, localDelegation) {
const escalations = activeSignals.filter(s => s.signalType === "escalation").length;
const withSignals = activeSignals.filter(s => s.signalType !== "none").length;
const awaiting = activeSignals.filter(s => s.signalType === "none").length;
@@ -763,13 +873,14 @@ function renderSummaryBar(activeSignals) {
return `
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 14px;
background:#0f172a;border:1px solid #1e293b;border-radius:8px;margin-bottom:14px;">
<div style="display:flex;align-items:center;gap:12px;">
background:#0f172a;border:1px solid #1e293b;border-radius:8px;margin-bottom:14px;gap:12px;flex-wrap:wrap;">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<span style="font-size:13px;color:#cbd5e1;"><b style="color:#f1f5f9;">${activeSignals.length}</b> desk${activeSignals.length !== 1 ? "s" : ""}</span>
<span style="font-size:11px;color:#475569;">${withSignals} reporting · ${awaiting} awaiting</span>
${tokenBadge}
${calibrationBadge}
${escBadge}
${renderLocalDelegationControl(localDelegation)}
</div>
${avgBlock}
</div>`;
@@ -945,8 +1056,9 @@ function renderStashedCard(entry) {
</div>`;
}
function renderDashboard(signals, stashed, capabilityToken) {
function renderDashboard(signals, stashed, capabilityToken, localDelegation) {
const activeSignals = sortSignals(signals.filter(s => !stashed.some(e => e.name === s.deskName)));
const localDelegationState = localDelegation || currentLocalDelegationLaunch("off");
const cards = activeSignals.length > 0
? activeSignals.map(renderSignalCard).join("")
@@ -975,7 +1087,9 @@ function renderDashboard(signals, stashed, capabilityToken) {
</div>
</div>`;
const summaryBar = activeSignals.length > 0 ? renderSummaryBar(activeSignals) : "";
// Always show the Local Delegation control so operators can see availability
// even before the first desk signal arrives.
const summaryBar = renderSummaryBar(activeSignals, localDelegationState);
const stashedSection = stashed.length > 0 ? `
<div style="margin-top:20px;padding-top:12px;border-top:1px solid #1a1a1a;">
@@ -1058,27 +1172,55 @@ function renderDashboard(signals, stashed, capabilityToken) {
const data = await res.json();
if (data.ok) {
const path = data.deskPath || name;
const notice = data.localDelegationNotice || {};
const localTitle = notice.titleSuffix || '';
const localDetail = notice.detail || '';
if (data.launched) {
// A successful open shouldn't hijack the user's clipboard.
showToast('opening ' + name + ' desk (' + selectedProfile + ')…', path);
// Surface LD state in the toast — operators cannot rely on -i alone.
showToast('opening ' + name + ' desk (' + selectedProfile + localTitle + ')…',
localDetail || path);
} else {
// No terminal launched from here, so copy the path as the
// fallback handle, but only claim the copy when it actually
// succeeded. The path shows in the toast either way.
let copied = false;
try { await navigator.clipboard.writeText(path); copied = true; } catch {}
showToast(copied ? (name + ' · path copied') : (name + ' · copy this path'), path);
const copyTitle = copied ? (name + ' · path copied') : (name + ' · copy this path');
showToast(copyTitle + localTitle, localDetail || path);
}
} else {
showToast(name + ' · not found', '');
}
}
async function setLocalDelegation(preference) {
const res = await fetch('/api/local-delegation?preference=' +
encodeURIComponent(preference || 'off'), POST_OPTS);
const data = await res.json();
if (data.ok) {
const ld = data.localDelegation || {};
const routeId = ld.availability && ld.availability.routeId;
const label = ld.effective
? ('Local Delegation effective' + (routeId ? (' · route ' + routeId) : ''))
: (ld.preference === 'on'
? 'Local Delegation requested (unavailable)'
: 'Local Delegation off');
showToast(label, ld.availability?.reason || '');
refresh();
} else {
showToast('Local Delegation · not updated', data.error || '');
}
}
document.addEventListener('click', (e) => {
const btn = e.target.closest('button[data-act]');
if (!btn) return;
const act = btn.getAttribute('data-act');
if (act === 'local-delegation') {
setLocalDelegation(btn.getAttribute('data-preference') || 'off');
return;
}
const name = btn.getAttribute('data-desk');
if (!name) return;
const act = btn.getAttribute('data-act');
const profile = btn.getAttribute('data-profile');
if (act === 'stash') stashDesk(name);
else if (act === 'restore') restoreDesk(name);
@@ -1102,19 +1244,26 @@ function renderDashboard(signals, stashed, capabilityToken) {
active.getAttribute('data-act'),
active.getAttribute('data-desk'),
active.getAttribute('data-profile') || '',
active.getAttribute('data-preference') || '',
]);
}
content.innerHTML = newContent.innerHTML;
if (focusKey) {
const [act, desk, profile] = JSON.parse(focusKey);
const escDesk = (window.CSS && CSS.escape) ? CSS.escape(desk) : desk;
const profileSelector = profile
? '[data-profile="' + profile + '"]'
: ':not([data-profile])';
const target = content.querySelector(
'button[data-act="' + act + '"][data-desk="' + escDesk + '"]' +
profileSelector);
const [act, desk, profile, preference] = JSON.parse(focusKey);
let target = null;
if (act === 'local-delegation') {
target = content.querySelector('button[data-act="local-delegation"]');
} else {
const escDesk = (window.CSS && CSS.escape) ? CSS.escape(desk) : desk;
const profileSelector = profile
? '[data-profile="' + profile + '"]'
: ':not([data-profile])';
target = content.querySelector(
'button[data-act="' + act + '"][data-desk="' + escDesk + '"]' +
profileSelector);
}
if (target) target.focus();
void preference;
}
}
} catch {}
@@ -1180,6 +1329,20 @@ async function startServer(instanceId, workshopDir) {
res.end(JSON.stringify({ ok: true }));
return;
}
if (req.method === "POST" && url.pathname === "/api/local-delegation") {
const preferenceInput = url.searchParams.get("preference") || "off";
if (!["off", "on"].includes(String(preferenceInput).toLowerCase())) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: false, error: "Invalid local delegation preference" }));
return;
}
const preference = normalizeLocalDelegationPreference(preferenceInput, "off");
await writeLocalDelegationPreference(workshopDir, preference);
const localDelegation = currentLocalDelegationLaunch(preference);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, localDelegation }));
return;
}
if (req.method === "POST" && url.pathname.startsWith("/api/open/")) {
const deskName = decodeURIComponent(url.pathname.split("/api/open/")[1]);
const profileInput = url.searchParams.get("profile") || DEFAULT_DESK_PROFILE;
@@ -1194,14 +1357,25 @@ async function startServer(instanceId, workshopDir) {
return;
}
const profile = normalizeDeskProfile(profileInput);
const preference = await readLocalDelegationPreference(workshopDir);
const localDelegation = currentLocalDelegationLaunch(preference);
for (const subdir of ["desks", "classroom"]) {
const deskPath = join(workshopDir, subdir, deskName);
try {
const s = await stat(deskPath);
if (s.isDirectory()) {
const launched = await launchDeskConsole(deskPath, deskName, workshopDir, profile);
const launched = await launchDeskConsole(
deskPath, deskName, workshopDir, profile, localDelegation);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, deskName, deskPath, launched, profile }));
res.end(JSON.stringify({
ok: true,
deskName,
deskPath,
launched,
profile,
localDelegation,
localDelegationNotice: formatLocalDelegationOpenNotice(localDelegation),
}));
return;
}
} catch {}
@@ -1213,8 +1387,10 @@ async function startServer(instanceId, workshopDir) {
const signals = await scanSignals(workshopDir);
const stashed = await readStash(workshopDir);
const preference = await readLocalDelegationPreference(workshopDir);
const localDelegation = currentLocalDelegationLaunch(preference);
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(renderDashboard(signals, stashed, capabilityToken));
res.end(renderDashboard(signals, stashed, capabilityToken, localDelegation));
} catch (err) {
// Top-level boundary: never leave a request hanging or let a
// rejection become an unhandled crash — e.g. malformed %-encoding
@@ -1327,7 +1503,7 @@ const session = await joinSession({
},
{
name: "open_desk",
description: "Open a desk as an in-place Copilot CLI session. Repo profile suppresses ambient plugin MCPs; connected keeps every configured tool. Returns the desk path, profile, and whether a terminal was launched.",
description: "Open a desk as an in-place Copilot CLI session. Repo profile suppresses ambient plugin MCPs; connected keeps every configured tool. Local Delegation is orthogonal and fail-closed: when available and preferred on, the frontier desk may use sealed local-agent-delegation for bounded read/evidence work. Returns the desk path, profile, localDelegation state, and whether a terminal was launched.",
inputSchema: {
type: "object",
properties: {
@@ -1337,6 +1513,11 @@ const session = await joinSession({
enum: ["repo", "connected"],
description: `Tool profile. Defaults to ${DEFAULT_DESK_PROFILE}.`,
},
localDelegation: {
type: "string",
enum: ["off", "on"],
description: "Optional Local Delegation preference for this launch. Defaults to the workshop Cairn toggle (.local-delegation.json).",
},
},
required: ["deskName"],
},
@@ -1347,13 +1528,21 @@ const session = await joinSession({
const profileInput = ctx.input.profile || DEFAULT_DESK_PROFILE;
if (!isDeskProfile(profileInput)) return { error: "Invalid desk profile" };
const profile = normalizeDeskProfile(profileInput);
const preferenceInput = ctx.input.localDelegation
?? await readLocalDelegationPreference(entry.workshopDir);
const preference = normalizeLocalDelegationPreference(preferenceInput, "off");
const localDelegation = currentLocalDelegationLaunch(preference);
for (const subdir of ["desks", "classroom"]) {
const deskPath = join(entry.workshopDir, subdir, ctx.input.deskName);
try {
const s = await stat(deskPath);
if (s.isDirectory()) {
const launched = await launchDeskConsole(
deskPath, ctx.input.deskName, entry.workshopDir, profile);
deskPath,
ctx.input.deskName,
entry.workshopDir,
profile,
localDelegation);
return {
ok: true,
deskName: ctx.input.deskName,
@@ -1361,6 +1550,8 @@ const session = await joinSession({
launched,
workshopDir: entry.workshopDir,
profile,
localDelegation,
localDelegationNotice: formatLocalDelegationOpenNotice(localDelegation),
};
}
} catch {}
@@ -25,6 +25,16 @@ 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); }
@@ -7,6 +7,7 @@ import { join } from "node:path";
import {
buildDeskAgentArgv,
isDeskProfile,
isSafeQuotedWindowsCmdArg,
isSafeWindowsCmdShim,
isWindowsAppExecutionAlias,
normalizeDeskProfile,
@@ -41,6 +42,10 @@ test("quotes trusted cmd shim arguments and rejects percent-bearing paths", () =
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", {
@@ -0,0 +1,401 @@
import { createHash } from "node:crypto";
import { accessSync, constants as fsConstants, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const PREFERENCES = new Set(["off", "on"]);
const SAFE_ROUTE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
/** @deprecated Repo-root state is rejected; kept only for docs/migration mentions. */
export const LOCAL_DELEGATION_STATE_FILE = ".local-delegation.json";
export const LOCAL_DELEGATION_ENV = "WORKSHOP_LOCAL_DELEGATION";
export const LOCAL_DELEGATION_SKILL_NAME = "local-agent-delegation";
export const LOCAL_DELEGATION_USER_STATE_DIR = join(".copilot", "workshop-local-delegation");
/**
* Stable user-local preference path for a workshop root.
* Permission state must NOT live in the cloned workshop (a repo can ship
* preference:on). Key by a hash of the canonical workshop path under ~/.copilot.
*/
export function localDelegationPreferencePath(workshopDir, {
home = homedir(),
resolvePath = (p) => p,
} = {}) {
if (typeof workshopDir !== "string" || !workshopDir) {
throw new Error("workshopDir is required");
}
let canonical = workshopDir;
try { canonical = resolvePath(workshopDir); } catch { /* keep input */ }
// Normalize separators only. Do not lowercase: on case-sensitive filesystems
// /work/Foo and /work/foo are distinct workshops and must not share state.
const key = createHash("sha256")
.update(String(canonical).replaceAll("\\", "/"))
.digest("hex")
.slice(0, 32);
return join(home, LOCAL_DELEGATION_USER_STATE_DIR, `${key}.json`);
}
export function isLocalDelegationPreference(value) {
return typeof value === "string" && PREFERENCES.has(value.toLowerCase());
}
export function normalizeLocalDelegationPreference(value, fallback = "off") {
return isLocalDelegationPreference(value) ? value.toLowerCase() : fallback;
}
// Windows Terminal / cmd reparse cannot safely carry long multi-space -i strings.
// Keep the orientation prompt short, ASCII, and quote-free. Local Delegation
// policy lives in WORKSHOP_LOCAL_DELEGATION=enabled + the installed skill — not
// on the CLI.
const MAX_ORIENT_PROMPT_CHARS = 280;
// Allow apostrophes (desk's). Ban double quotes, backticks, dashes that WT/cmd
// have split on, and classic cmd metacharacters.
const UNSAFE_ORIENT_CHARS = /["`—–|&<>^%!()\r\n]/;
export function isSafeDeskOrientPrompt(prompt) {
return typeof prompt === "string"
&& prompt.length > 0
&& prompt.length <= MAX_ORIENT_PROMPT_CHARS
&& !UNSAFE_ORIENT_CHARS.test(prompt);
}
/** One short ASCII notice operators can see in the session start prompt. */
export const LOCAL_DELEGATION_ORIENT_LINE = " Local Delegation env is enabled.";
export function deskOrientPrompt(deskName, { localDelegationEffective = false } = {}) {
// deskName is already constrained to a slug by the launcher; still keep the
// prompt free of punctuation that cmd/wt have historically mis-parsed.
let prompt = `You are sitting down at the ${deskName} desk in this workshop. ` +
`Read journal.md in this folder first to pick up where the last session ` +
`left off, then continue the desk's work. Write your journal before you stop.`;
// Only a single short ASCII line may ride on -i. Full policy stays in env + skill.
if (localDelegationEffective) {
const withNotice = prompt + LOCAL_DELEGATION_ORIENT_LINE;
if (isSafeDeskOrientPrompt(withNotice)) prompt = withNotice;
}
return prompt;
}
/**
* Operator-visible summary for open toasts and badges.
* Never claims savings; only reports effective state + route id when known.
*/
export function formatLocalDelegationOpenNotice(localDelegation) {
if (!localDelegation || typeof localDelegation !== "object") {
return { titleSuffix: "", detail: "" };
}
const routeId = localDelegation.availability?.routeId || null;
if (localDelegation.effective) {
const routePart = routeId ? ` · route ${routeId}` : "";
return {
titleSuffix: " · Local Delegation effective",
detail: `Local Delegation effective${routePart}`,
};
}
if (localDelegation.requested) {
return {
titleSuffix: " · local unavailable",
detail: localDelegation.warning
|| localDelegation.availability?.reason
|| "Local Delegation requested but unavailable",
};
}
return { titleSuffix: "", detail: "" };
}
export function buildLocalDelegationLaunchEnv(baseEnv = {}, { localDelegationEffective = false } = {}) {
const env = { ...baseEnv };
// Windows env names are case-insensitive; spreading process.env yields a
// case-sensitive object, so clear every spelling before optionally setting.
const target = LOCAL_DELEGATION_ENV.toLowerCase();
for (const key of Object.keys(env)) {
if (key.toLowerCase() === target) delete env[key];
}
if (localDelegationEffective) {
env[LOCAL_DELEGATION_ENV] = "enabled";
}
return env;
}
/**
* cmd.exe prefix that forces WORKSHOP_LOCAL_DELEGATION on or off inside a new
* Windows Terminal / console session. wt.exe does not reliably forward the
* caller's process env into a new tab when Terminal is already running.
*/
export function windowsLocalDelegationCmdPrefix(localDelegationEffective = false) {
return localDelegationEffective
? 'set "WORKSHOP_LOCAL_DELEGATION=enabled"&& '
: 'set "WORKSHOP_LOCAL_DELEGATION="&& ';
}
/**
* Savings credit is utilization accounting, not a price claim.
* Failed, unaccepted, redone, or escalated local work earns zero.
*/
export function localSavingsCredit({
attempted = false,
gateAccepted = false,
redone = false,
escalated = false,
} = {}) {
if (!attempted || !gateAccepted || redone || escalated) {
return {
credit: 0,
utilization: attempted ? "handled_locally_unaccepted" : "not_attempted",
reason: !attempted
? "not_attempted"
: escalated
? "escalated"
: redone
? "redone"
: "gate_not_accepted",
};
}
return {
credit: 0, // dollar savings are never claimed by Cairn
utilization: "handled_locally_accepted",
reason: "accepted_utilization_only",
};
}
function isReadableFile(path) {
try {
if (!statSync(path).isFile()) return false;
accessSync(path, fsConstants.R_OK);
return true;
} catch {
return false;
}
}
function skillMarkerPath(dir) {
return join(dir, "SKILL.md");
}
function looksLikeSkillDir(dir) {
return isReadableFile(skillMarkerPath(dir));
}
/**
* Discover the installed local-agent-delegation skill.
* Injectable probes keep unit tests filesystem-free.
*/
export function findLocalDelegationSkillDir({
env = process.env,
home = homedir(),
exists = existsSync,
isSkillDir = looksLikeSkillDir,
} = {}) {
const explicit = (env.WORKSHOP_LOCAL_DELEGATION_SKILL_DIR || "").trim();
if (explicit) {
return isSkillDir(explicit) ? explicit : null;
}
const candidates = [
join(home, ".copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(home, ".agents", "skills", LOCAL_DELEGATION_SKILL_NAME),
];
for (const candidate of candidates) {
if (isSkillDir(candidate)) return candidate;
}
// Marketplace: ~/.copilot/installed-plugins/<marketplace>/<plugin>/
// Direct: ~/.copilot/installed-plugins/_direct/<plugin>/
// Skill dirs may live at skills/, .github/skills/, or com.github.copilot/skills/.
const pluginsRoot = join(home, ".copilot", "installed-plugins");
if (exists(pluginsRoot)) {
try {
for (const market of readdirSync(pluginsRoot, { withFileTypes: true })) {
if (!market.isDirectory()) continue;
const marketRoot = join(pluginsRoot, market.name);
let pluginEntries;
try {
pluginEntries = readdirSync(marketRoot, { withFileTypes: true });
} catch {
continue;
}
for (const plugin of pluginEntries) {
if (!plugin.isDirectory()) continue;
const pluginRoot = join(marketRoot, plugin.name);
const nestedCandidates = [
join(pluginRoot, "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, ".github", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, "com.github.copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
join(pluginRoot, "com.github.awesome-copilot", "skills", LOCAL_DELEGATION_SKILL_NAME),
];
for (const nested of nestedCandidates) {
if (isSkillDir(nested)) return nested;
}
}
}
} catch {
// fail closed on scan errors
}
}
return null;
}
function readJsonFile(path, readFile = readFileSync) {
try {
return JSON.parse(readFile(path, "utf8"));
} catch {
return null;
}
}
function isRouteId(value) {
return typeof value === "string" && SAFE_ROUTE_ID.test(value);
}
/**
* Fail-closed availability. Enable only when skill + qualified route receipt
* (or explicit env route id) are present. Never invent availability.
*/
export function resolveLocalDelegationAvailability({
env = process.env,
home = homedir(),
now = Date.now(),
findSkill = findLocalDelegationSkillDir,
readFile = readFileSync,
exists = existsSync,
} = {}) {
const forced = (env.WORKSHOP_LOCAL_DELEGATION_AVAILABLE || "").trim().toLowerCase();
if (forced === "0" || forced === "false" || forced === "unavailable") {
return {
available: false,
reason: "Forced unavailable by WORKSHOP_LOCAL_DELEGATION_AVAILABLE",
skillDir: null,
routeId: null,
};
}
const skillDir = findSkill({ env, home, exists });
if (!skillDir) {
return {
available: false,
reason: "local-agent-delegation skill is not installed",
skillDir: null,
routeId: null,
};
}
const envRoute = (env.WORKSHOP_LOCAL_DELEGATION_ROUTE_ID || "").trim();
if (envRoute) {
if (!isRouteId(envRoute)) {
return {
available: false,
reason: "WORKSHOP_LOCAL_DELEGATION_ROUTE_ID is not a safe route id",
skillDir,
routeId: null,
};
}
return {
available: true,
reason: "Skill installed; route id provided by environment",
skillDir,
routeId: envRoute,
};
}
const receiptPath = (env.WORKSHOP_LOCAL_DELEGATION_RECEIPT || "").trim()
|| join(home, ".copilot", "local-agent-runs", "qualified-route.json");
if (!exists(receiptPath)) {
return {
available: false,
reason: "No qualified route receipt (set WORKSHOP_LOCAL_DELEGATION_ROUTE_ID or write ~/.copilot/local-agent-runs/qualified-route.json)",
skillDir,
routeId: null,
};
}
const receipt = readJsonFile(receiptPath, readFile);
if (!receipt || typeof receipt !== "object") {
return {
available: false,
reason: "Qualified route receipt is unreadable",
skillDir,
routeId: null,
};
}
const status = String(receipt.status || "").toLowerCase();
if (status !== "qualified") {
return {
available: false,
reason: `Route receipt status is '${receipt.status || "missing"}', not qualified`,
skillDir,
routeId: isRouteId(receipt.route_id || receipt.routeId) ? (receipt.route_id || receipt.routeId) : null,
};
}
const routeId = receipt.route_id || receipt.routeId || null;
if (!isRouteId(routeId)) {
return {
available: false,
reason: "Route receipt is missing a safe route_id",
skillDir,
routeId: null,
};
}
if (receipt.expires_at || receipt.expiresAt) {
const expires = Date.parse(receipt.expires_at || receipt.expiresAt);
if (!Number.isFinite(expires) || expires <= now) {
return {
available: false,
reason: "Qualified route receipt has expired",
skillDir,
routeId,
};
}
}
return {
available: true,
reason: "Skill installed; qualified route receipt present",
skillDir,
routeId,
};
}
export function resolveLocalDelegationLaunch({
preference = "off",
availability,
} = {}) {
const pref = normalizeLocalDelegationPreference(preference, "off");
const available = Boolean(availability?.available);
if (pref === "on" && !available) {
return {
preference: pref,
requested: true,
effective: false,
availability,
warning: availability?.reason || "Local Delegation unavailable",
};
}
return {
preference: pref,
requested: pref === "on",
effective: pref === "on" && available,
availability,
warning: null,
};
}
export function parseLocalDelegationState(raw) {
if (!raw || typeof raw !== "object") {
return { preference: "off" };
}
return {
preference: normalizeLocalDelegationPreference(raw.preference, "off"),
};
}
export function serializeLocalDelegationState(state) {
return {
preference: normalizeLocalDelegationPreference(state?.preference, "off"),
updatedAt: new Date().toISOString(),
};
}
@@ -0,0 +1,298 @@
import test from "node:test";
import assert from "node:assert/strict";
import { join, sep } from "node:path";
import {
LOCAL_DELEGATION_ORIENT_LINE,
buildLocalDelegationLaunchEnv,
deskOrientPrompt,
findLocalDelegationSkillDir,
formatLocalDelegationOpenNotice,
isLocalDelegationPreference,
isSafeDeskOrientPrompt,
localDelegationPreferencePath,
localSavingsCredit,
normalizeLocalDelegationPreference,
parseLocalDelegationState,
resolveLocalDelegationAvailability,
resolveLocalDelegationLaunch,
serializeLocalDelegationState,
windowsLocalDelegationCmdPrefix,
} from "./local-delegation.mjs";
import {
buildDeskAgentArgv,
normalizeDeskProfile,
quoteWindowsCmdArgument,
} from "./launch-profile.mjs";
test("normalizes local-delegation preference independently of desk profile", () => {
assert.equal(isLocalDelegationPreference("on"), true);
assert.equal(isLocalDelegationPreference("OFF"), true);
assert.equal(isLocalDelegationPreference("maybe"), false);
assert.equal(normalizeLocalDelegationPreference("ON"), "on");
assert.equal(normalizeLocalDelegationPreference("nope"), "off");
// Profile axis remains orthogonal and untouched.
assert.equal(normalizeDeskProfile("connected"), "connected");
assert.equal(normalizeDeskProfile("repo"), "repo");
});
test("availability is fail-closed without skill or route receipt", () => {
const missingSkill = resolveLocalDelegationAvailability({
env: {},
home: "C:\\home",
findSkill: () => null,
exists: () => false,
});
assert.equal(missingSkill.available, false);
assert.match(missingSkill.reason, /not installed/i);
const skillOnly = resolveLocalDelegationAvailability({
env: {},
home: "C:\\home",
findSkill: () => "C:\\home\\.copilot\\skills\\local-agent-delegation",
exists: () => false,
});
assert.equal(skillOnly.available, false);
assert.match(skillOnly.reason, /No qualified route receipt/i);
});
test("availability accepts env route id or a qualified receipt", () => {
const home = join("home");
const skillDir = join("skills", "local-agent-delegation");
const viaEnv = resolveLocalDelegationAvailability({
env: { WORKSHOP_LOCAL_DELEGATION_ROUTE_ID: "foundry-qwen25-7b-qualified" },
home,
findSkill: () => skillDir,
exists: () => false,
});
assert.equal(viaEnv.available, true);
assert.equal(viaEnv.routeId, "foundry-qwen25-7b-qualified");
// Match host path.join separators so Linux CI exercises the receipt branch.
const receiptPath = join(home, ".copilot", "local-agent-runs", "qualified-route.json");
const viaReceipt = resolveLocalDelegationAvailability({
env: {},
home,
now: Date.parse("2026-08-14T12:00:00Z"),
findSkill: () => skillDir,
exists: (p) => p === receiptPath,
readFile: () => JSON.stringify({
status: "qualified",
route_id: "foundry-qwen25-7b-qualified",
expires_at: "2026-12-01T00:00:00Z",
}),
});
assert.equal(viaReceipt.available, true);
assert.equal(viaReceipt.routeId, "foundry-qwen25-7b-qualified");
const expired = resolveLocalDelegationAvailability({
env: {},
home,
now: Date.parse("2027-01-01T00:00:00Z"),
findSkill: () => skillDir,
exists: (p) => p === receiptPath,
readFile: () => JSON.stringify({
status: "qualified",
route_id: "foundry-qwen25-7b-qualified",
expires_at: "2026-12-01T00:00:00Z",
}),
});
assert.equal(expired.available, false);
assert.match(expired.reason, /expired/i);
});
test("requested on + unavailable stays ineffective with a warning", () => {
const launch = resolveLocalDelegationLaunch({
preference: "on",
availability: {
available: false,
reason: "local-agent-delegation skill is not installed",
},
});
assert.equal(launch.requested, true);
assert.equal(launch.effective, false);
assert.match(launch.warning, /not installed/i);
});
test("launch env enables when effective; -i prompt stays short and quote-free", () => {
const base = {
PATH: "/usr/bin",
WORKSHOP_LOCAL_DELEGATION: "enabled",
workshop_local_delegation: "enabled",
};
const offEnv = buildLocalDelegationLaunchEnv(base, { localDelegationEffective: false });
assert.equal(Object.hasOwn(offEnv, "WORKSHOP_LOCAL_DELEGATION"), false);
assert.equal(Object.hasOwn(offEnv, "workshop_local_delegation"), false);
assert.equal(offEnv.PATH, "/usr/bin");
const onEnv = buildLocalDelegationLaunchEnv(base, { localDelegationEffective: true });
assert.equal(onEnv.WORKSHOP_LOCAL_DELEGATION, "enabled");
assert.equal(Object.hasOwn(onEnv, "workshop_local_delegation"), false);
const offPrompt = deskOrientPrompt("cost-desk", { localDelegationEffective: false });
const onPrompt = deskOrientPrompt("cost-desk", { localDelegationEffective: true });
assert.match(offPrompt, /cost-desk/);
assert.equal(offPrompt.includes("Local Delegation"), false);
// One short ASCII line only — never the long policy appendix.
assert.equal(onPrompt, offPrompt + LOCAL_DELEGATION_ORIENT_LINE);
assert.match(onPrompt, /Local Delegation env is enabled\./);
assert.equal(onPrompt.includes("do not delegate"), false);
assert.equal(isSafeDeskOrientPrompt(onPrompt), true);
assert.equal(isSafeDeskOrientPrompt('bad "quote"'), false);
assert.equal(isSafeDeskOrientPrompt("em dash — bad"), false);
assert.match(windowsLocalDelegationCmdPrefix(true), /enabled/);
assert.match(windowsLocalDelegationCmdPrefix(false), /WORKSHOP_LOCAL_DELEGATION="&&/);
const run = ["C:\\tools\\copilot.exe", "-i", onPrompt];
const cmdLine = windowsLocalDelegationCmdPrefix(true) + run.map(quoteWindowsCmdArgument).join(" ");
assert.match(cmdLine, /^set "WORKSHOP_LOCAL_DELEGATION=enabled"&& /);
assert.match(cmdLine, /copilot\.exe/);
});
test("open notice reports effective route without savings claims", () => {
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: true,
requested: true,
availability: { available: true, routeId: "foundry-qwen25-7b-qualified" },
}), {
titleSuffix: " · Local Delegation effective",
detail: "Local Delegation effective · route foundry-qwen25-7b-qualified",
});
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: false,
requested: true,
warning: "local-agent-delegation skill is not installed",
availability: { available: false, reason: "local-agent-delegation skill is not installed" },
}).detail, "local-agent-delegation skill is not installed");
assert.deepEqual(formatLocalDelegationOpenNotice({
effective: false,
requested: false,
}), { titleSuffix: "", detail: "" });
});
test("repo/connected argv stays orthogonal to local-delegation preference", () => {
const repo = buildDeskAgentArgv({
deskName: "cost-desk",
workshopDir: "/workshop",
useAgency: false,
copilotCommand: "copilot",
profile: "repo",
pluginMcpNames: ["teams"],
});
const connected = buildDeskAgentArgv({
deskName: "cost-desk",
workshopDir: "/workshop",
useAgency: false,
copilotCommand: "copilot",
profile: "connected",
pluginMcpNames: ["teams"],
});
assert.deepEqual(repo, [
"copilot", "--name", "cost-desk",
"--disable-mcp-server", "teams",
"--add-dir", "/workshop",
]);
assert.deepEqual(connected, [
"copilot", "--name", "cost-desk",
"--add-dir", "/workshop",
]);
// Local delegation never injects into argv — only env/prompt.
assert.equal(repo.includes("local"), false);
assert.equal(connected.includes("local"), false);
});
test("failed or unaccepted local work earns zero savings credit", () => {
assert.equal(localSavingsCredit({ attempted: false }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: false }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: true, redone: true }).credit, 0);
assert.equal(localSavingsCredit({ attempted: true, gateAccepted: true, escalated: true }).credit, 0);
const accepted = localSavingsCredit({ attempted: true, gateAccepted: true });
assert.equal(accepted.credit, 0);
assert.equal(accepted.utilization, "handled_locally_accepted");
});
test("state parse/serialize defaults to off", () => {
assert.deepEqual(parseLocalDelegationState(null), { preference: "off" });
assert.equal(parseLocalDelegationState({ preference: "ON" }).preference, "on");
const serialized = serializeLocalDelegationState({ preference: "on" });
assert.equal(serialized.preference, "on");
assert.equal(typeof serialized.updatedAt, "string");
});
test("preference path is user-local and keyed by workshop path, not the repo root", () => {
const home = join("user-home");
const a = localDelegationPreferencePath(join("repos", "workshop-a"), {
home,
resolvePath: (p) => p,
});
const b = localDelegationPreferencePath(join("repos", "workshop-b"), {
home,
resolvePath: (p) => p,
});
const again = localDelegationPreferencePath(join("repos", "workshop-a"), {
home,
resolvePath: (p) => p,
});
assert.match(a, /workshop-local-delegation/);
assert.equal(a.startsWith(home), true);
assert.equal(a.includes(`${sep}repos${sep}`), false);
assert.notEqual(a, b);
assert.equal(a, again);
// Repo-shipped .local-delegation.json is never the preference path.
assert.equal(a.endsWith(".local-delegation.json"), false);
// Case-sensitive filesystems: Foo and foo must not share permission state.
const upper = localDelegationPreferencePath("/work/Foo", {
home,
resolvePath: (p) => p,
});
const lower = localDelegationPreferencePath("/work/foo", {
home,
resolvePath: (p) => p,
});
assert.notEqual(upper, lower);
});
test("skill discovery respects explicit dir and common install roots", () => {
const found = findLocalDelegationSkillDir({
env: { WORKSHOP_LOCAL_DELEGATION_SKILL_DIR: "D:\\sealed\\.github\\skills\\local-agent-delegation" },
home: "C:\\home",
exists: () => false,
isSkillDir: (p) => p === "D:\\sealed\\.github\\skills\\local-agent-delegation",
});
assert.equal(found, "D:\\sealed\\.github\\skills\\local-agent-delegation");
const missingExplicit = findLocalDelegationSkillDir({
env: { WORKSHOP_LOCAL_DELEGATION_SKILL_DIR: "D:\\missing" },
home: "C:\\home",
exists: () => false,
isSkillDir: () => false,
});
assert.equal(missingExplicit, null);
});
test("skill discovery walks marketplace/plugin and _direct install layouts", async () => {
const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import("node:fs");
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const home = mkdtempSync(join(tmpdir(), "ld-skill-"));
try {
const skillDir = join(
home, ".copilot", "installed-plugins", "awesome-copilot", "sealed-delegation",
".github", "skills", "local-agent-delegation");
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), "# local-agent-delegation\n");
const found = findLocalDelegationSkillDir({ home, env: {} });
assert.equal(found, skillDir);
rmSync(join(home, ".copilot", "installed-plugins", "awesome-copilot"), { recursive: true, force: true });
const direct = join(
home, ".copilot", "installed-plugins", "_direct", "sealed-delegation",
"skills", "local-agent-delegation");
mkdirSync(direct, { recursive: true });
writeFileSync(join(direct, "SKILL.md"), "# local-agent-delegation\n");
const foundDirect = findLocalDelegationSkillDir({ home, env: {} });
assert.equal(foundDirect, direct);
} finally {
rmSync(home, { recursive: true, force: true });
}
});
@@ -1,15 +1,15 @@
{
"name": "signals-dashboard",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"main": "extension.mjs",
"scripts": {
"test": "node --test launch-profile.test.mjs"
"test": "node --test launch-profile.test.mjs local-delegation.test.mjs"
},
"dependencies": {
"@github/copilot-sdk": "latest"
},
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, and cost-aware repo or connected desk launch profiles.",
"description": "Real-time Workshop dashboard with agent signals, honesty calibration, cost-aware desk profiles, and fail-closed Local Delegation.",
"keywords": [
"agent-signals",
"dashboard",