🤖 feat: add Mux Extension Platform v1#3255
Conversation
Capture the extension platform glossary, ADRs, and PRD generated from the design grilling session.
---
_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `high` • Cost: `729323{MUX_COSTS_USD:-0}`_
<!-- mux-attribution: model=openai:gpt-5.5 thinking=high costs=103.38 -->
Apply six P0 fixes from advisor review and capture supporting language in CONTEXT.md and a new ADR-0005:
- Add v1 Contribution Activation Matrix (separates schema-supported from capability-consumed; clarifies that themes/layouts/runtime presets/commands are inspection-only in v1).
- Mark inspection-only contribution types as Provisional Descriptors so their schemas can evolve without bumping descriptor version.
- Tighten telemetry boundary to Provenance-gated Telemetry (reserved-prefix regex AND bundled-root provenance), with defense-in-depth.
- Make Snapshot Cache feed only the Inspection Path; Capability Path uses the live Snapshot, and cache invalidation includes Global/Project-local Extension State mtimes.
- Clarify Grant Records store the normalized granted set (inferred registration + operational); enablement does not grant registration.
- Drop the manifest icon field; inspection UI uses generic icons.
- Add P0 Acceptance Criteria section before Implementation Decisions.
- Make IPC mutators identify with { rootId, extensionId } and add BundledExtensionRootResolver as a supporting module.
- Replace bun install --no-save assembly with deterministic offline copy/pack.
- Pre-trust project-local discovery is existence-only.
- Add explicit security tests (reserved prefix, provenance-gated telemetry, capability-vs-cache separation, drift across new contribution types) and screenshot/video evidence requirements to dogfood checklist.
- Add ADR-0005 capturing the aggregate security boundary.
---
_Generated with `mux` • Model: `anthropic:claude-opus-4-7` • Thinking: `max` • Cost: `881861{MUX_COSTS_USD:-0}`_
<!-- mux-attribution: model=anthropic:claude-opus-4-7 thinking=max costs=130.54 -->
…r kill switch ServiceContainer now resolves the bundled extension root via detectBundledExtensionRoot() (preferring the assembled tree at build/extensions in dev) and kicks off an initial extensions.reload() so the Settings UI paints on cold start without a hang. SettingsPage gates the Extensions tab on the EXTENSION_PLATFORM experiment so the kill switch hides the section without unmounting downstream sections. Adds the e2e Electron smoke that asserts the Demo Extension card surfaces on first paint, disappears with the kill switch, survives a renderer reload, and reappears on re-enable without a fresh trust/grant prompt.
…cklist) Adds the v1 authoring quickstart + manifest reference, the full v1 telemetry events catalog with provenance-gating notes, and the pre-release dogfood checklist with screenshot/video evidence requirements. Wires the new pages into docs.json navigation and regenerates the built-in mux-docs index so the docs skill surfaces them.
Formatting-only normalization (collapse multi-line argument lists, consistent quoting) across the extension layer plus minor test cleanup left over from US-026/27/28 integration. Marks US-026/27/28 passes:true in tasks/prd.json.
PRD §Permission Model defines bundled Extensions as policy-granted (not user-consented). Before this fix the Demo Extension shipped with `granted: false`, surfaced `Pending re-grant` on the card, and the `mux-extensions` skill never reached `Available` — breaking the "fresh-install, no manual setup" promise of P0 #2. Discovery now treats `isBundled` as activation-granted, and the Registry synthesizes a matching policy Grant Record at permission-calculation time (never persisted, recomputed on every reload, distribution-identity-aligned so drift stays `null` across version bumps). The discovery test that asserted `activated:false` on a bundled root without a grant has been updated to reflect the new contract.
…ry disabled Renderer's useExperimentValue falls back to `enabledByDefault` when PostHog returns no assignment, but the backend's isExperimentEnabled returned `false` in the same scenario — so dev-server / MUX_E2E builds shipped with EXTENSION_PLATFORM (default-on) effectively off. Frontend showed the Extensions tab while the backend reported "No extension roots configured". isExperimentEnabled now uses EXPERIMENTS[id].enabledByDefault as the fallback. Existing tests for default-off experiments still assert false because their definitions are enabledByDefault:false.
…ively The renderer can't reproduce permissionCalculator's canonical SHA-256 without a Node `crypto` import, so both Consent Shortcut paths sent `requestedPermissionsHash: ""`. After persistence the very next reload read `hash !== ""` → driftStatus `permissions-changed` for an already-fresh grant. setGrant now overwrites the hash with hashRequestedPermissions(<live manifest's requestedPermissions>) before persistence; falls back to hashing grantedPermissions when no live snapshot is available (equivalent under v1's all-or-nothing grants). The frontend-side comments are updated to point at the canonical recompute site instead of misleading future readers.
#2) agentSkillsService had zero references to extensionRegistry. Even with the Demo Extension fully Available in the Registry snapshot, its `mux-extensions` skill never reached the slash menu — directly contradicting PRD P0 #2 ("Demo Extension visible end-to-end … exposes the mux-extensions skill via the existing slash menu"). Changes: - Adds 'extension' as a fourth AgentSkillScope value (project > global > extension > built-in precedence so user-authored skills always shadow extension-provided ones). - ExtensionRegistry.getSkillSources() returns the resolved list of Available skill contributions (absolute body path + display metadata) for agentSkillsService to consume. - discoverAgentSkills / discoverAgentSkillsDiagnostics / readAgentSkill accept an optional extensionSkills array; the agentSkills router supplies it from context.extensionRegistry.getSkillSources() on every call so kill-switch flips and reload events take effect immediately. - readAgentSkill reads the extension body via node:fs (extensions live on the host, not the workspace runtime) and returns it with scope:'extension'. - SkillIndicator gains an "Extension" scope group; MuxMessageMetadata scope union widens to include 'extension' so /skill messages from extension skills render correctly. - Adds YAML frontmatter to packages/mux-extension-platform-demo/SKILL.md so parseSkillMarkdown accepts it (the Manifest Validator only needs the body file to exist; the agent-skill consumer needs frontmatter).
The 'Why?' link in the Inferred Registration Permissions header pointed at docs.mux.dev (not our domain) with an anchor that didn't exist. Repoints to https://mux.coder.com/extensions/authoring#permissions where the v1 permissions reference actually lives.
The Shift+? hotkey hint at the top of the Extensions Settings Section was rendered as low-contrast plain text on a transparent ghost button — hard to read against the section background. Wraps the keybind in the same <kbd> styling already used by ExtensionsCheatSheetModal so the trigger visually echoes the cheat sheet itself instead of looking like a comment.
The modal panel was styled with `bg-background-primary`, but globals.css only defines `--color-background` and `--color-background-secondary`. The non-existent token resolved to nothing, so the panel rendered fully transparent — only the dimmed backdrop was visible behind it, and the shortcut list bled through into the section content. Switches to `bg-background-secondary` to match ConsentShortcutModal.
…atform-demo The package shipped under `@mux/extension-platform-demo`, but `@mux` is not Coder's npm scope. Renames the distribution identity to `@coder/mux-extension-platform-demo` (Extension Identity `mux.platformdemo` is unchanged — it lives under the reserved `mux.*` prefix and survives package renames per ADR-0003). Touches: package.json + README + SKILL.md inside the workspace, the docs/extensions/authoring.mdx quickstart, bundled-extensions.ts comment, test fixtures (the assemble + discovery + registry suites all rebuilt against the new path), and the PRD/CONTEXT/prd.json planning artifacts that referenced the old name. Regenerates the embedded mux-docs builtin skill index so `agent_skill_read` surfaces the renamed link.
agentSkillsService.list (the slash menu) merged extension-contributed skills correctly, but the dispatch path that actually invokes a skill (`agentSession.ts` slash resolver, `agent_skill_read`, `agent_skill_read_file`) called readAgentSkill without the extensionSkills source list. Result: the slash menu showed `mux-extensions` but `/mux-extensions` failed with `Agent skill not found: mux-extensions` — the user-reported bug. Wires a single getExtensionSkillSources provider from ExtensionRegistry.getSkillSources() through three layers: - ServiceContainer registers the provider with both WorkspaceService (for AgentSession.sendMessage slash resolution) and AIService (for the tool layer). - WorkspaceService passes the provider into every AgentSession via a new option. - AgentSession reads the live source list per slash invocation; fixes agentSession.ts:5253 to pass extensionSkills to readAgentSkill. - AIService injects extensionSkills into ToolConfiguration on every stream so agent_skill_read / agent_skill_read_file resolve extension skills the same way they resolve project / global / built-in skills. Adds a regression test in agentSession.agentSkillSnapshot.test.ts that exercises a slash invocation against an extension-contributed skill — fails on the pre-fix codepath, passes here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 26dd821cb1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the latest follow-up review threads:
Local validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 53ee297929
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the project lock sync fail-closed finding: when trusted lock sync fails, the roots provider no longer points discovery at a stale materialized active root and falls back to the project Local validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 160628d447
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the Settings root rendering finding: the Extensions section now renders every user-global root returned by the registry snapshot, including fetched installed roots such as Local validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 436bb38e24
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the sync-failure fallback activation finding: project lock sync failures now mark the returned project-local root as untrusted/non-activating, so discovery cannot publish stale active-view code or repo-controlled Local validation: |
|
@codex review Addressed the arbitrary skill body watcher finding: module directory watchers now trigger reload for contained body file changes regardless of basename, with regression coverage for a custom Local validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c582725b2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the bundled extension validation finding: bundled packages that contain Local validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e8f84fc45d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the malformed bundled package follow-up: a packages/ directory that contains Local validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6aa52b2f24
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the extension reference file TOCTOU finding: extension skill reference reads now revalidate realpath containment after preflight, then open the validated path with no-follow before reading through the file handle. Added regression coverage that swaps an extension reference file to an escaping symlink between containment and read. Local validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da074d189a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the opened-parent-directory TOCTOU follow-up: extension reference reads now validate the realpath of the opened file handle (via Local validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eef3fcfab9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // neither fd namespace is available (for example on Windows). | ||
| } | ||
| } | ||
| return fs.realpath(fallbackPath); |
There was a problem hiding this comment.
Fail closed when opened-handle realpath is unavailable
When neither /proc/self/fd nor /dev/fd is available, this falls back to resolving fallbackPath rather than the already-open handle. In that environment, e.g. Windows, a writable extension root can still swap an intermediate parent to an escaping link for fs.open() and swap it back before this fallback realpath, so the check validates a different path than the file handle that is later read. For the TOCTOU hardening to hold, this path should reject when the opened handle cannot be canonicalized, or use another handle-bound validation.
Useful? React with 👍 / 👎.
| throw new Error("Opened file resolves outside the extension skill directory."); | ||
| } | ||
| const stat = await handle.stat(); | ||
| const content = stat.isDirectory() ? "" : await handle.readFile("utf8"); |
There was a problem hiding this comment.
Reject extension directories instead of returning empty content
For extension skills, a filePath that points at a directory now succeeds with empty content because directories skip readFile here and validateFileSize does not reject directories. The non-extension path below returns Path is a directory, not a file, so this new extension branch gives agents a misleading successful read for inputs like filePath: "refs" instead of the expected error.
Useful? React with 👍 / 👎.
Summary
Adds the Mux Extension Platform v1 behind the
EXTENSION_PLATFORMexperiment and documents the platform's architectural pivot to Extension Modules: extension folders with a singleextension.ts, statically extractable manifests, QuickJS-based discovery/activation, skill-style root precedence, source locks, and Mux-owned trust/capability state. The implementation is now partially refactored toward that model: trusted roots can discover direct child Extension Module folders via staticextension.tsmanifest extraction, local authoring roots use~/.mux/extensions/local, and source-lock schemas now model git/vendored extension sources without carrying trust state.Background
This PR grew out of the need to consolidate Mux's extension surfaces across skills, tools, agents, policies, themes, and future runtime contributions. During review, the design moved away from npm-package identity and repo-stored project approvals toward a Go-modules-like Extension Module model. The updated docs capture that decision, the code hardens the current scaffold so repositories cannot provide security authority, and the latest slices begin moving discovery/root layout/source metadata from package manifests to static Extension Module manifests and locks.
Implementation
extension.tsmanifest extraction forexport const manifest = defineManifest({ ... })or a literal object export, rejecting dynamic manifest values without executing extension code.extension.ts, including folder-name validation,manifest.namemismatch diagnostics, project-local pre-trust no-read behavior, and static capability validation.~/.mux/extensions/localand updatesinitializeUserRootto create that folder instead of a package-rootpackage.json.bun run debug extensions.agent_skill_*tool reads, including hardened skill-body reads that reject symlinks and TOCTOU path swaps.extension.ts) as the target architecture.~/.mux/extensions/project-state/<project-hash>/, not inside the target repository.Validation
make static-checkmake test -j1bun test src/node/extensions/extensionRoots.test.ts src/node/orpc/extensionsRouter.test.ts src/common/extensions/sourceLocks.test.ts src/node/extensions/staticManifestExtractor.test.ts src/node/extensions/extensionDiscoveryService.test.tsbun test src/common/extensions/conflictResolver.test.ts src/common/extensions/permissionCalculator.test.tsbun test src/node/extensions/bundledExtensionsAssemble.test.tsbun test src/node/extensions/projectExtensionStateService.test.tsbun test src/node/orpc/extensionsRouter.test.tsbun test src/cli/debug/extensions.test.tsbun test src/browser/features/Settings/Sections/ExtensionCard.test.tsx src/browser/features/Settings/Sections/ExtensionsSection.test.tsxRisks
This is a large additive subsystem touching startup wiring, settings UI, package assembly, telemetry, and skill discovery. The primary rollback lever is the default-on
EXTENSION_PLATFORMexperiment. The highest remaining architectural risk is that full QuickJS Registration Discovery/Full Activation and git install/store materialization are still follow-up work; the current module-discovery slice intentionally publishes no module-registered skills until that runtime path exists.Pains
This PR required several review and merge cycles: resolving older security findings, integrating concurrent
mainchanges around heartbeat/image-generation skill filtering, aligning extension skill IDs with agent skill schemas, moving project extension state out of repositories after review identified the trust-injection vulnerability, and beginning the package-to-Extension-Module refactor while preserving transitional compatibility.Generated with
mux• Model:openai:gpt-5.5• Thinking:off• Cost:$916.09