neiko on Discord installed a third-party plugin beside a plugin of their
own, added by its folder. Only the third-party one could be seen. Both
plugins' auth hooks name the same provider id. The host's auths() keeps
the last plugin in plugins.json for an id, as OpenCode does, so the
user's own plugin silently lost it:
- its row read "Signs in to nothing magpie can use";
- its provider was gone from the Add sheet;
- keepUnloaded could list the id a second time, from the
plugin-providers.json kept before.
A package-name clash and a display-name clash were tried as well. Both
plugins stay visible in those cases, because a folder plugin's name is
its path.
What changed:
- host.js:
- init gives each loaded plugin the ids it signs in to (provides) and,
for each id another plugin serves, that plugin's spec (servedBy).
- auths() honours plugins.json's new prefer map (id -> spec).
- Only the serving plugin's provider.models hook runs for the id.
- plugin:
- Clashes() turns provides and servedBy into {id, by, with} per spec.
- Prefer() records a pick and restarts the host.
- Remove drops the removed plugin's picks.
- Re-adding a plugin under a new spec carries its picks over.
- remove, off, options and prefer find a plugin by its exact spec
before its package name (indexOf), so acting on one plugin never
reaches another of the same package.
- keepUnloaded no longer re-adds a provider from before when another
plugin serves its id now.
- GUI:
- /api/plugins rows carry clashes, with the provider's listed name.
- POST /api/plugins/prefer {spec, provider}.
- The serving row says "{other} signs in to {name} too; this one
serves it".
- The other row says, in amber, "{name} is served by {other}, which
signs in to it too", with a "Use for {name}" button.
- New strings in zh, zh-TW, ja and de.
- CLI: `magpie plugin` prints the same lines, with the command that
fixes it, and `magpie plugin use <name> <provider>` makes the pick.
- Docs: docs/subsystems/provider-plugins.md, docs/reference.md and
site/public/docs/{,zh/,ja/}plugins.html.
What was run:
- Reproduced under a sandbox HOME with magpie web on 3562: a fake npm
plugin and a folder plugin, both on id "acme".
- Old binary: the local row had providers [] and /api/providers
listed acme twice.
- New binary: both rows carry the clash; prefer moves acme to the
folder plugin; remove clears the pick and leaves the other plugin
serving acme.
- New Go tests:
- TestTwoPluginsOneProvider, a real Bun host: Prefer, restart,
off/on and Remove.
- TestClashes, TestPluginNamedExactlyFirst and
TestKeepUnloadedLeavesAServedID.
- TestPluginsClashRows (gui) and TestPluginListSaysClash (CLI).
- Each fails when its piece is broken by hand: prefer ignored in
host.js, the keepUnloaded id check removed, name before exact spec,
Remove keeping picks, no provides, no clashes in the rows, no
prefer route.
- plugin-clash.test.cjs, in Chromium and WebKit, in en, zh, zh-TW, ja
and de, at 1100 and 560px: 22/22 pass. It times out on the old
plugins.js.
- gui-de, gui-ja, gui-zh-tw, i18n-keys and every plugin-*.test.cjs:
248 tests.
- 245 passed in the 4-wide run.
- plugin-moved-signin (webkit) failed once in that run, then passed
3/3 alone and 26/26 in a smaller 4-wide rerun.
- gofmt -l; go vet ./...; GOOS=linux and GOOS=windows go vet -tags
nogui ./...; go build ./...
- The full go test -tags nogui ./... under a temp HOME with the caches
pinned: 44 packages ok, none failed.
- After rebasing onto 05917100 (plugins.css conflict with a3a8e257's
chip rules, both kept): plugin, gui and the root plugin tests, vet on
darwin, linux and windows, and the 248 GUI tests above, all green
(248/248 this time).
Not tried: a real third-party plugin, neiko's own plugins, and Windows
beyond vet.
Unofficial is a hairline with a small amber dot; what a plugin is reads as
its glyph and word, periwinkle for a provider and teal for middleware,
apart from the community badge's green.
Ran: plugin-github, plugin-middleware and plugin-check-updates GUI tests in
Chromium and WebKit (en/zh/ja/de, 980 and 440), light and dark looked at.
GitHub's search for topic:magpie-plugin (not archived, not a fork, not
magpie-community's own, which the market lists) is asked at most every six
hours and kept on disk, so a restart or GitHub being down shows what it said
last. Each repository is read from its package.json on the default branch.
When npm has that package published from the same repository, it is
installed from npm, because a repository often builds its dist only to
publish and Bun runs no build for a git install. Otherwise it is installed as
github:owner/repo, and only if the file it loads (exports, main or
magpie.middleware) is in the repository. Its page warns that it is
unofficial, says whether npm or the repository is what gets installed, and
shows the repository's README until it is installed. MAGPIE_PLUGIN_MARKET=off
asks GitHub nothing.
Checked against real GitHub with the topic set to opencode-plugin in a
sandbox: 43 repositories came back, and 32 were kept (30 from npm, 2 from
git). github:ndom91/open-plan-annotator, one of the git ones, really
installed and loaded. alfaoz/opencode-see-image, whose dist isn't in its
repository, failed to load before the entry check and is now left out. The
test fixtures are GitHub's and npm's real answers.
Ran: go test -race -count=3 ./internal/plugin, go test ./internal/gui, go
vet, and plugin-github.test.cjs (Chromium and WebKit, 980/440, en/zh/ja/de,
18/18). The full GUI suite passed 3343 of 3348. The 5 failures were WebKit
number-units, purpose-filter and routing-failed-open, which pass 3/3 on
their own and don't touch the Plugins page.
* gui: choose installed interface and code fonts and styles
Discover installed styles through DirectWrite, CoreText and Fontconfig. Keep choices per computer, apply them before paint and notify both desktop webviews after saving; defaults retain platform and Omarchy fallbacks.
Verified seven focused Go tests, including real Windows discovery (90 families, 276 styles), 14 font browser cases in Chromium/WebKit, 68 settings/zoom/Omarchy/web cases, go vet, Windows amd64/arm64 and Linux nogui builds. Against the original assets, the new UI test fails with Settings must offer an interface font picker (0 != 1). Full Windows suites have separately reproduced upstream failures; macOS/Linux native discovery is covered by the added CI step.
* gui: retain bundled Mac fonts and variable style precision
Only filter an undownloaded CoreText face when it is explicitly downloadable; bundled fonts are installed without being downloaded. Use resolved font names when descriptors omit them. Preserve fractional weights and nonnegative CSS widths for named variable styles.
* gui: restore the authored font cascade when returning to defaults
Apply family-only trait overrides only while a custom face is active. The browser regression catches code under a bold parent becoming 700 instead of its original 400. Keep the compatibility shim first, and apply boot fonts before visible content.
* gui: keep font picker review separate from CI integration
Leave the native test sources and manual verification command with the feature. Move the added workflow step to a follow-up so the font picker can take the normal review path requested on PR #1007.
The header's .seg drew a grey track behind its tabs; now only the open tab's white thumb marks it (the account-mode .am-seg keeps its own). A Plugins card offering a built-in's accounts had a solid purple bar of its own at the card's foot, "Move my 2 Command Code Plan accounts", which broke onto two lines; it is now the tinted pill beside the name that Install is, reading "Move" / "迁移", its title saying whose accounts and how many and what moving does. In the Agents list the grip in a row's left margin (#479) was drawn on every row all the time, and hovering the logo turned it into a second grip; the margin grip now shows only for the row under the pointer, the one being dragged, or a handle with its menu open or the keyboard on it (kept in a rule of its own, for Safari 15), and the logo stays the logo. Dragging or clicking the grip still does what the logo does.
agent-grip now checks no grip is drawn with the pointer away, the hovered row's alone is, and the logo has no grip of its own; plugin-move-overflow checks the move button sits in the card's top row on one line. agent-grip, account-arrange, agent-layout, the agent-models tests, brand, header-centre, header-zoom-fit, phone-web, strip-scrollbar, old-webkit and the plugin tests pass in Chromium and WebKit, except plugin-move's known Chromium flake and click-scroll, which fail the same way on unchanged main on this loaded machine. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and go test -tags nogui ./internal/gui/... pass.
Settings → Plugins adds a plugin by npm package name or by a path, and the
path had to be typed in full: the market's field was the only way in, with
nothing to pick the folder from. And a plugin added from a folder had no page
of its own — its README comes from plugin.Readme, which answers only for an
npm package name ("%q isn't an npm package name"), so opening one said "npm
didn't answer" and offered an npm link for a folder that npm has never heard
of.
The field now has the system's folder picker beside it, and a folder plugin's
page shows the README that folder carries.
- POST /api/plugins/choose asks Windows.ChooseFolder, which the app answers
with the system's panel — the Library's "Choose a project" already used it,
and a page served by another magpie forwards it to the magpie it is served
by. pluginsState carries "picker" beside "bun", false for `magpie web`: a
browser has no such panel (webHost.ChooseFolder refuses it), and the page is
then left with the field to type into, as before.
- plugin.Readme takes a path: folderReadme reads README.md whatever its case,
else a markdown or a text one named beside it, and says so when the folder
has none rather than answering with npm's silence. Updated is that file's
mtime, and the text is capped at 200 KB as npm's answer is.
- A folder plugin's page drops what only npm can say: no npm link, no version,
downloads or license, and a failure names the folder rather than npm. Its
row and its title name the folder it was added from (opencode-mine, not
/Users/me/plugins/opencode-mine).
TestReadmeFromFolder (internal/plugin) fails without the change: with a
README.txt and an unsuffixed README beside it, the README.md is still the one
read; ReadMe.MD is too, and through file:// and a trailing .; a folder with
none is an error, as is one that isn't there.
TestPluginsPickerWhereItCan (internal/gui) fails without the change: the
picker is offered for the app, not for `magpie web` nor for no host at all.
plugin-local.test.cjs (new; Chromium and WebKit, en and zh) fails without the
change: the picker is beside the field and titled as such, a click asks for it
and what it answers is what the field holds and takes the focus, what the
field holds is what Install posts, the Installed row names the folder rather
than its path, its page renders that folder's README with no npm link, and a
market that offers no picker draws no button (english and Chinese).
Not only the faked page: a sandbox `magpie web` in a HOME of its own, with a
plugin added by path, read that folder's README.md — its mtime as Updated —
and listed it by the folder's name.
go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows
go build, go test -tags nogui ./internal/gui/ pass; plugin-market,
plugin-signin and plugin-local pass in Chromium and WebKit, en and zh.
TestPluginProxy (internal/plugin) and the hermes tests (internal/agent,
internal/profile) fail on main on this machine too — its own proxy and a
hermes install it finds — and are unrelated.
Not done: the panel itself hasn't been clicked in the app. It is the system's
own, and the call under it is the one the Library's folder chooser already
uses, but the screenshots here are the page with a faked backend underneath
(the app's own picker is offered, `magpie web` has none), plus the real
`magpie web` for the README.