-
Notifications
You must be signed in to change notification settings - Fork 233
Experimental canvas: support --global install to ~/.copilot/extensions/ (user scope) #1691
Copy link
Copy link
Open
Labels
area/distributionInstallers (curl/PowerShell/Brew/Scoop), self-update, devcontainer, codespaces.Installers (curl/PowerShell/Brew/Scoop), self-update, devcontainer, codespaces.area/docs-sitedocs/src/content (Starlight), README, doc generation.docs/src/content (Starlight), README, doc generation.enhancementDeprecated: use type/feature. Kept for issue history; will be removed in milestone 0.10.0.Deprecated: use type/feature. Kept for issue history; will be removed in milestone 0.10.0.status/needs-designDirection approved, design discussion required before code.Direction approved, design discussion required before code.status/triagedInitial agentic triage complete; pending maintainer ratification (silence = approval).Initial agentic triage complete; pending maintainer ratification (silence = approval).theme/securitySecure by default. Content scanning, lockfile integrity, MCP trust boundaries.Secure by default. Content scanning, lockfile integrity, MCP trust boundaries.type/featureNew capability, new flag, new primitive.New capability, new flag, new primitive.
Metadata
Metadata
Assignees
Labels
area/distributionInstallers (curl/PowerShell/Brew/Scoop), self-update, devcontainer, codespaces.Installers (curl/PowerShell/Brew/Scoop), self-update, devcontainer, codespaces.area/docs-sitedocs/src/content (Starlight), README, doc generation.docs/src/content (Starlight), README, doc generation.enhancementDeprecated: use type/feature. Kept for issue history; will be removed in milestone 0.10.0.Deprecated: use type/feature. Kept for issue history; will be removed in milestone 0.10.0.status/needs-designDirection approved, design discussion required before code.Direction approved, design discussion required before code.status/triagedInitial agentic triage complete; pending maintainer ratification (silence = approval).Initial agentic triage complete; pending maintainer ratification (silence = approval).theme/securitySecure by default. Content scanning, lockfile integrity, MCP trust boundaries.Secure by default. Content scanning, lockfile integrity, MCP trust boundaries.type/featureNew capability, new flag, new primitive.New capability, new flag, new primitive.
Type
Fields
Give feedbackNo fields configured for issues without a type.
Projects
StatusShow more project fields
In Progress
Summary
The experimental Copilot canvas primitive (shipped in #1689) is deliberately project-scope-only —
apm installdeploys a canvas to<git-root>/.github/extensions/<name>/. This issue tracks adding--global/ user-scope support so a canvas can be deployed to~/.copilot/extensions/<name>/and become available in every Copilot session, not just the one repo.Confirmation: Copilot DOES scan the global folder
Verified against the GitHub Copilot CLI runtime contract (bundled with the installed app):
copilot-sdk/generated/rpc.d.tsExtensionSource = "project" | "user"; "Extension discovered from the user's~/.copilot/extensionsdirectory."copilot-sdk/docs/extensions.md.github/extensions/(project) and the user's copilot config extensions directory"create-canvasskill.github/extensions/(git root) and$COPILOT_HOME/extensions/(default~/.copilot).So global install is technically supportable and useful.
What's already in place
APM's user-scope machinery already maps the
copilotTargetProfile(user_root_dir=".copilot", anchored atPath.home()) + the canvasPrimitiveMapping(subdir="extensions")to~/.copilot/extensions/— the exact path Copilot discovers. Canvas is blocked at user scope only by:unsupported_user_primitives=("canvas",)on the copilot profile (src/apm_cli/integration/targets.py:480), andif scope is InstallScope.USER: return empty(src/apm_cli/integration/canvas_integrator.py:173-177).Blocking implementation work (not just a flag flip)
A safe implementation must address these, because a canvas is arbitrary executable Node.js loaded into every future session:
post_deps_local.pyskips user scope (if ctx.scope is not InstallScope.PROJECT: return, lines 23-25 / 48-50). A first-party canvas deployed globally would not be recorded inlocal_deployed_files, soapm uninstall -g/ stale-sync would not prune it — install works but uninstall leaks executable code globally. Fixing this touches the user-scope local-file tracking chokepoint for all user-scope primitives.$COPILOT_HOMEis unhandled. It is read nowhere in APM (grep COPILOT_HOME src/→ none); APM hardcodes.copilotunderPath.home(). If a user sets$COPILOT_HOMEoutside home, APM would deploy to the wrong place relative to where Copilot scans. Honouring it correctly interacts with the lockfile URI scheme —_deployed_path_entry(src/apm_cli/install/services.py:90-96) would mis-encode a dynamic Copilot root ascowork://.cleanup.py:170-177resolves deployed files against the current root. If$COPILOT_HOMEdiffers between install and uninstall, the original global executable could be orphaned. Needs install-root identity persisted in the lockfile.CanvasIntegratorcomputes paths manually (canvas_integrator.py:192-195) instead of viaTargetProfile.deploy_path(...), so it won't honour a dynamic resolver without rework.services.py:712-741) filters canvas paths itself with hardcoded.github/extensions/messaging — needs scope-aware deploy root + diagnostics.Security recommendation: scope-aware trust gate
Today first-party canvases deploy freely (flag only); dependency canvases need
--trust-canvas-extensions. At global scope the blast radius is the whole user account (runs in every session), so:--trust-canvas-extensions(new)npm/pip precedent (global binaries without a trust flag) is weak here — a global Copilot extension is a persistent plugin, not a PATH binary. Update the flag help (
install.py:924-927) which currently says "provided by dependencies".Other notes
.github/extensions/<name>/shadows a user~/.copilot/extensions/<name>/at Copilot discovery time. On global install, if the current repo has a same-named project canvas, emit an informational warning.canvasfromunsupported_user_primitivesshould not affectapm compile(driven bycompile_family), but add a narrow regression test.Acceptance criteria
apm install --globaldeploys a first-party canvas to$COPILOT_HOME/extensions/(default~/.copilot/extensions/), gated on the experimental flag and--trust-canvas-extensions.apm uninstall -g(and stale-sync) reliably removes the global canvas — no orphaned executable files, including the$COPILOT_HOME-changed case (or a loud warning if unremovable).$COPILOT_HOMEhonoured for both deploy and cleanup.$COPILOT_HOME; compile-unchanged regression), docs (docs/src/content/docscanvas page +packages/apm-guide/.apm/skills/apm-usage/), CHANGELOG.Discovered while validating #1689 end-to-end. Evidence and design critique captured in that work.