Commit Graph
23 Commits
Author SHA1 Message Date
2cc663d0a2 gui: protect unsaved edits and confirm destructive actions (#820)
* gui: edited forms ask before they are discarded, removals ask before they take effect, and the header keeps its first layout — Cancel, Escape, the backdrop and a page change could throw away a provider's draft, and several account, key, plugin and library removals took effect at once. Modified provider, settings, library and routing forms now ask before discarding; unmodified forms still close without asking, and refresh or closing the browser tab uses its leave confirmation. Removal and discard confirmations are in-app dialogs over the intact editor, centered, with Cancel focused first, Tab kept inside, Escape canceling and focus returned afterwards; destructive buttons have readable foregrounds in light and dark themes. Text and connection settings have Save/Cancel rather than saving on blur; simple switches still save at once. Editors associate labels and help with fields, make the background inert while open and keep focus through redraws; navigation keeps browser back/forward history and its current-page state, errors remain until dismissed or replaced, and focus and validation can scroll into view. Mobile settings tabs have larger targets, fields use 16px text and page zoom stays available. layout.js applies mode and platform before the header paints, theme.js applies the saved text size before styles, and mode-specific window controls stay hidden while app.js downloads. zh/ja/de strings added. routing-shape's global .rt-day selector selected the hidden By session button on WebKit; its history click now stays in .rt-days. ux-safety covers unchanged and modified closes, canceled and confirmed removals, Escape, backdrop, modal focus, light/dark centering and button contrast, explicit saves, browser history and sync-form discard. Rebased onto upstream/main 2f183c4c. Chromium and WebKit each pass 31 related browser tests in en and zh; gui-ja and gui-de pass all 6 browser and translation-parity tests. go vet -tags nogui ./..., go build -tags nogui, go test -tags nogui ./internal/gui, JavaScript syntax checks and git diff --check pass.

Co-authored-by: XYenon <register@xyenon.bid>

* gui: edited providers, routing groups and library forms ask before their drafts are discarded, and only immediate removals ask before taking effect — Settings keep saving on Enter and blur, switches save at once, and the reader's scroll position is restored after an unasked-for scroll. Header rows, fallback models, group members, patterns and rules stay ordinary reversible draft edits. The confirmation dialog keeps its own readable destructive colors; the window's text selection, muted and faint colors, timed status messages, replaceState navigation and iOS viewport policy stay as before. The keyboard scroll assertion is restored, and click-scroll's date buttons are scoped to .rt-days .rt-day so WebKit cannot pick a hidden request-grouping button. The safety tests cover dirty editor closes and page changes, immediate provider deletion, labels, modal focus, Escape and backdrop cancellation, with the withdrawn settings, history and persistent-error assertions removed. First-paint layout and the saved theme/text size remain early.

Co-authored-by: XYenon <register@xyenon.bid>

* gui: old WebKit gets its layout before the header paints, and browser tests answer the new discard and removal questions — compat.js runs before layout.js, whose viewport setup uses ES5; the confirmation uses vh rather than Safari 15.4 dynamic viewport units. Opening a provider from its address keeps the editor pending until its page loads, and fields keep their own nested labels when Names & levels is reopened. An empty model preference map is compared as Save sends it, so opening model settings without changing anything closes without a question. Removal tests assert nothing is sent before confirmation; draft tests answer Discard and retain their Save and Cancel assertions. Plugin migration uses the reader’s wheel rather than a scripted scroll, keeping every scroll assertion. Targeted WebKit regressions pass 40/40, Chromium regressions pass 41/41, and the model-setting and editor-safety checks pass in both engines. go vet -tags nogui ./..., go build -tags nogui, JavaScript syntax checks and git diff --check pass.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui tests: the upstream model list keeps folding with the new discard question — models-fold answers Discard only after picking another model; the short list and an unchanged folded list still close without asking. ux-safety waits for its Relay row rather than matching loading placeholders. Rebased onto upstream/main c353f7bd, including the new model-list expansion. models-fold passes in WebKit and Chromium, as do the English and Chinese editor-safety checks; the related model drafts and first-paint phone tests pass on WebKit.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui: an editor redraw keeps its removal confirmation and menus interactive — Only a fresh modal makes the existing background inert, and closing restores exactly the elements it changed. A redraw no longer disables a confirmation or popup appended over the editor; elements already inert and overlays added later retain their own state. The original background is retained if a new editor interrupts the closing animation. ux-safety redraws Relay twice under its Remove confirmation, clicks Cancel, and verifies no deletion was sent; it also picks a menu item after an editor redraw and checks background restoration. The regression reproduced on WebKit before the fix. All 8 English and Chinese safety checks pass on WebKit and Chromium. The related WebKit removal, draft, routing and phone checks pass, with account-arrange passing separately after a detached-element race in the concurrent run. go vet -tags nogui ./..., go test -tags nogui ./internal/gui, go build -tags nogui, JavaScript syntax and git diff --check pass.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui tests: editor safety uses the page's boot preferences, not its settings — gateway in boot.js is now the gateway-mode flag, whereas settings.gateway is the gateway's URL. Spreading settings into the fixture boot data mistook the URL for gateway mode and redirected a dirty editor's attempted page change back to Providers. The fixture now emits the same language, theme, text size and web fields as the server. The 8 English and Chinese editor-safety tests pass on Chromium and WebKit; gateway-mode, plugin middleware and plugin-market pass on WebKit, and phone-web passes separately after a scroll race under concurrent load. Rebased onto upstream/main 2f135ed4 with gateway-mode navigation and initialization retained. go vet -tags nogui ./..., go test -tags nogui ./internal/gui, JavaScript syntax and git diff --check pass.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui: upstream's new removals and fields follow the editor-safety rules too — Audited GUI changes from the original PR base 2f183c4c through upstream/main 9debc583, including earlier rebases. Bulk group removal uses the in-app consequence dialog rather than a second click, and clearing middleware options asks before deleting saved options. Cancel sends nothing and keeps the selection or editor. Custom group context, Settings' compact threshold, the inline key-weight input and middleware JSON have accessible names; the JSON note/error and group context hint are linked, and invalid input is exposed. The copy-out-of-date skill action is a keyboard button supporting Enter and Space. A skill's immediately saved per-agent transfer choices update the modal baseline, so Done, Escape and navigation no longer ask to discard already saved changes. Settings save behavior, draft-only removals, scroll guard, viewport and themes stay intact. Chinese, Japanese and German consequence strings added; obsolete bulk-removal armed styles and strings removed. The six affected browser files pass across Chromium and WebKit in English and Chinese (54 tests after scoping compact-at to its provider rather than skeletons). Additional provider price, key import/weight, group member/first-token, caller-key, purpose and gateway-mode regressions pass on WebKit; key-weight also passes in Chromium, with a concurrent status-message race passing separately. go vet -tags nogui ./..., go test -tags nogui ./internal/gui, go build -tags nogui, JavaScript syntax and git diff --check pass. The Chinese bulk confirmation was rendered and inspected on WebKit.

Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847
Co-authored-by: XYenon <register@xyenon.bid>

* gui: new upstream key removals and nested group jumps keep the editor-safety rules — Bulk deletion of failing, off and matching keys uses the app's consequence dialog, with Cancel and Escape sending nothing and a redraw leaving its buttons interactive. The shared group-editor entry asks before replacing a dirty draft, and nested navigation waits for acceptance before upstream's explicit scroll. Codex's Sign out uses its existing confirmation once. Group and key filters have accessible names. A focused model-price input is included in leave protection before its change/blur parser runs, and reverting the raw value is clean. Settings saving, scroll protection, draft-only removal, viewport and themes retain their upstream behavior. The affected flows pass in Chromium and WebKit, English and Chinese; Names & levels also passes in Japanese and German. The price fixture focuses before filling because the previous blur normalizes sibling fields, a race reproduced on upstream. go vet -tags nogui ./..., go test -tags nogui ./internal/gui, go build -tags nogui, JavaScript syntax and git diff --check pass. The Chinese bulk-key dialog was rendered and inspected on WebKit.

Co-authored-by: XYenon <register@xyenon.bid>
Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847

* gui tests: Codex's newly removable active account waits for the consequence question (#820) — The latest upstream multiaccount Remove path uses accountAction's immediate-removal dialog. Its test now verifies that the first click sends nothing, Cancel keeps the account, and accepting sends exactly one login/forget request. The original account, status and translation assertions remain. Rebased onto upstream/main 8d3cf5f8; the final Codex, nested-group, bulk-key, focused-price and editor-safety tests pass 48/48 across Chromium and WebKit in English and Chinese. Upstream's new quota, skill-group, hit-rate and translation checks pass; search-first's detached-element failure reproduces on isolated upstream. The complete WebKit suite ran 1413 tests before this final upstream delta, with 1289 passes and 124 failures; failing files were compared with upstream or rerun separately. Syntax and diff checks, go vet and the nogui build pass.

Co-authored-by: XYenon <register@xyenon.bid>
Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847

* gui: the Sessions agent strip reports which reused button is selected (#820, #929) — Rebased onto upstream/main 1330b966, retaining the strip's own scrolling, visible selected agent and preserved buttons. Each redraw updates aria-pressed for the selected agent and clears it for the previous selection. The regression checks All initially, the unselected alternatives, then Zed selected with All cleared; on unmodified upstream it fails with null instead of true. Both strip files pass 8/8 across Chromium and WebKit in English and Chinese, including narrow and wide layout and scroll assertions. The latest ChatGPT API, Codex and editor-safety browser checks pass 32/32. Final GUI, Agent, Provider and Gateway Go checks, go vet -tags nogui ./..., Linux nogui and Windows builds, syntax and diff checks pass. Linux native GUI build is blocked by the orb's GTK 4.8 lacking APIs used by Wails beta.24. The complete WebKit run finished with 1289 passes and 124 failures; all failing files were checked against upstream or rerun separately, with details retained in the review artifacts. The full Go suite's sole sessions rewrite failure also reproduces on upstream.

Co-authored-by: XYenon <register@xyenon.bid>
Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847

* gui: closing editors immediately give clicks back to the page (#820, #944) — Restore the background when the close animation starts and protect it again when an editor reopens. Both New group buttons and the model entry ask before replacing a dirty routing draft; creating a group from a provider waits for navigation to be accepted. The GUI shell reference describes these transitions.

The real mouse click during closing fails without the change in all four engine/language combinations, with null instead of received. The unguarded New group and model entries fail waiting for the discard question; the old model navigation creates a routing draft even after Cancel. ux-safety, provider-remove, click-scroll and group-new-head pass 50/50 in Chromium and WebKit, English and Chinese. Closing/reopening also passes five additional rounds, 20/20. After the final upstream translation and TUI additions, i18n-keys, Japanese/German, ux-safety and group-new-head pass 23/23. Ten Chromium update-wait runs have no no-click-scroll failure; two fail on the Restarting status assertion, also reproduced on upstream.

The complete WebKit run finished with 1,307 passes and 141 failures across 1,448 tests, with no skips or cancellations. It is not green: failures in 63 of its 70 failing files also occur on upstream; the other seven files pass separate PR reruns. The complete suite preceded the final upstream translation deduplication, whose resolved keys and values are unchanged, and the TUI/docs additions. go vet and the Linux GUI build with the Makefile's gtk3 tag, Linux nogui and Windows builds, GUI/TUI Go tests, syntax and diff checks pass. Full nogui Go tests are not green: the sessions same-size rewrite check and the final run's WebDAV slow-transfer stall both reproduce on upstream with -race -count=20 under an isolated HOME with pinned caches.

Co-authored-by: XYenon <register@xyenon.bid>
Amp-Thread-ID: https://ampcode.com/threads/T-01a108b0-c6a0-768d-ad6a-9a571a10a847

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-10-06 06:57:49 +08:00
yetone 2f135ed4d5 plugin, gui, docs: Plugins says which plugins are providers and which are middleware, lists the community's ready-made middleware, and edits a middleware's options in its row (the owner) — the Plugins page still read as if every plugin were a subscription, and nothing showed what a middleware does or let you set its options without editing plugins.json. Every Discover card and Installed row now carries a Provider or Middleware chip (both for a plugin that is both), also for one that is off (isMiddleware/middlewareOnly from the server, read from its files); Discover splits into Subscriptions and Gateway middleware ("runs in magpie's gateway on what every agent sends and gets back, whichever provider serves it"), the latter from market entries with "kind": "middleware" and no providers or icon (the built-in market.json lists the five new @magpie-community/middleware-* packages: param-override, model-map, system-prompt, word-guard, think-tags, mostly New API's channel settings with the same JSON). A middleware row has Options: a JSON editor in the row, starting from the package's magpie.options example when none are set, which validates it is an object, saves (POST /api/plugins/options) for the next request, clears, and cancels with Esc. magpie plugin options takes a short name (model-map for @magpie-community/middleware-model-map; plugin.ShortName, also in SetOptions). en/zh/ja/de. Docs: usemagpie.ai/docs/plugins (en/zh/ja) gains Ready-made middleware and Options and the registry's kind; reference.md, both READMEs and docs/subsystems/gateway-middleware.md likewise. Tested with a real DeepSeek account and real Claude Code through magpie web --gateway in a sandbox HOME, with all five installed and options set by short name: a mapped model named back, max_tokens capped, a prompt appended for claude only, word-guard's 400 and param-override's 403 in the agent's shape, <think> stripped from Chat and Responses streams and a whole Anthropic reply, reasoning_content moved into the text; /api/plugins showed the kinds and the example. TestSetOptionsShortName is new; TestBuiltinMarket checks a middleware entry has no providers or icon and no other kind. plugin-middleware.test.cjs (Chromium and WebKit, en and zh: the chips, Discover's two sections, the options editor with its errors, no select, no scroll, no left border) fails without the change; the other plugin tests and click-scroll pass in WebKit (plugin-market's section name updated; plugin-move fails only under parallel load and passes alone). go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. 2026-10-05 20:50:09 +08:00
yetone eebd0b4222 plugin, gateway, gui: a plugin can be gateway middleware — onRequest, onEvent and onResponse run in magpie's gateway on what agents send and get back (moejs) — a plugin could only be a subscription provider, run in the Bun host, a process away from the gateway. A plugin may now declare middleware (package.json magpie.middleware, or a *.middleware.js / .mjs file), run in-process by moejs (pure-Go JS engine): compiled once, one runtime per request from a pool, so every hook of a request shares ctx.state. onRequest(body, ctx) sees the agent's body in its own API before routing; what it returns is the body the next middleware and the gateway get, undefined leaves the bytes as they were, ctx.reject(status, msg) answers with an error in the agent's API's shape. onEvent(ev, ctx) sees each SSE event's JSON (export const events = [...] filters by name; an object changes it, null drops it, undefined passes the bytes through untouched; data: [DONE] is no JSON and passes by). onResponse(body, ctx) sees a whole JSON reply, with ctx.status. ctx has protocol, model, stream, path, agent, the plugin's options and state. A hook that throws, returns a non-object or runs past its limit (250ms request/response, 50ms an event, 1s load; interrupted) leaves the request or reply as it was and is counted and logged (60 lines a minute); a compressed reply passes as-is. Files are reloaded within a second of a change. Only agents' requests run through it (serveAgent at the gateway's agent entry points and the Codex backend), never magpie's own titles, classifier or stand-ins; it sees bodies before redaction. A middleware-only package is skipped by the Bun host. The Plugins page marks a middleware with a chip, its hooks, calls and µs per call (JSON in and out counted), failures in red with the last error, and a load error. en/zh/ja/de; docs/reference.md and usemagpie.ai/docs/plugins#middleware (en/zh/ja) with an alias example. Tested with a real DeepSeek account through magpie web --gateway in a sandbox HOME: an alias middleware served "fast" and named it in message_start, a reject came back as 403 permission_error, a Chat stream uppercased; real Claude Code (sandbox CLAUDE_CONFIG_DIR) did Read, Bash and Glob tool turns through three middlewares with 0 failures: ≈1–1.6µs an event, 0.2–0.7ms an onRequest that parses and rewrites Claude Code's ≈80KB body. Real use found two bugs fixed here: [DONE] parsed as JSON (SyntaxError on every Chat stream) and stats leaving out the JSON time. internal/middleware tests (chain, undefined keeps bytes, reject, fail-open on throw/spin/string with the runtime reused, async hook, load errors, off and Bun skip, reload, split/CRLF/filtered/dropped stream events, [DONE] untouched, whole reply and gzip, 64 in parallel; BenchmarkEvent ≈0.9µs, 11 allocs) and TestMiddlewareThroughGateway (alias routed, events by protocol, redaction still upstream, reject in Anthropic's shape; fails without the change) are new; plugin-middleware.test.cjs (Chromium and WebKit, en and zh: chip, hooks and stats, failures with last error, broken and off, no left border) fails without it. The other plugin GUI tests and click-scroll pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (internal/provider failed once under load and passes alone). 2026-10-05 19:14:31 +08:00
tobitege 21702d01ff Add German translation; harden paths against unset env vars (#770)
* Add German translation and harden paths against unset env vars

Add a German locale to the GUI, tray menu, usage alerts and website home page, and refuse to start when the home folder is unset or relative so nothing is written under the working folder; the pi plugin no longer adopts the user's PI_CODING_AGENT_DIR, and an inherited APPIMAGE no longer selects portable mode. Scripts refuse to run from the wrong folder or empty a shared out folder, and a new internal/testenv sandboxes every test package from the user's real files, which also fixes the Claude picker and Termux installer tests on Windows.

* Makefile: know magpie's folder by its go.mod, so paths with spaces work

The check compared $(CURDIR) with $(dir $(abspath …)), which make splits on spaces, so a checkout under 'My Projects' or 'John Doe' refused every target. It now reads go.mod relative to the working folder and wants magpie's module there: make -C and a symlinked Makefile work, make -f from another folder still stops before any recipe runs.

* i18n: German for the strings main added since, with a parity test

75 strings main added (group order, patterns, slow-start wait, usage caps, RTK savings …) get their German, and two that main dropped go. gui-de.test.cjs checks, as gui-ja does, that de has every zh string with its placeholders and tags, and that the window reads in German when picked or on a German system.

* appdir: ignore relative folder variables when reading, keep them for children

CheckEnv no longer unsets anything. magpie reads the XDG folders, APPDATA/LOCALAPPDATA and every agent's folder variable through appdir.Getenv/LookupEnv, which pass over a relative value; the environment stays as it is, so the agents magpie starts get the variable as the user set it. That covers what proc.UserPath lends from the login shell too, which is set after main's check and reached the desktop app unchecked. On Windows a path rooted on the current drive (\Users\x, Git Bash's /c/x) counts as usable, and magpie version and help answer without a home, before run makes any folder.

* Sandbox the tests main added since

internal/wslrun gets testenv's TestMain, and four tests main added that set HOME alone set their home with testenv.SetHome, so on Windows they don't read the runner's profile through USERPROFILE.

* agentenv: PI_CONFIG_DIR and OPENCODE_DB take relative names on purpose

omp puts $PI_CONFIG_DIR under the home and OpenCode a relative $OPENCODE_DB in its data folder, so neither depends on the working folder: appdir no longer passes them over.

* i18n: German for the Library's multi-select and System One's decision API

The 19 strings main added since (#791's skill multi-select, the decision API snippets) get their German; the one main replaced (four APIs → five APIs) goes.

* appdir: tidy the import into magpie's group; say what Go makes of /c/x

The appdir import joins the group of magpie's own imports where a file has one. rooted's comment now says that Go takes a Git Bash path like /c/x as \c\x on the current drive, not C:\x.
2026-10-04 23:30:56 +08:00
yetone f8a3f16dc6 plugin: the community's plugins are looked for updates every hour, not every six (#681, ChenstarMr on v0.1.808: told TraeCN 0.1.5 would come by itself, installed it by hand eleven minutes after it was out) — KeepUpdated ran CheckUpdates 30 s after the gateway's magpie started and then every updateEvery, six hours, so a fix published to a @magpie-community plugin and announced on its issue could take most of a working day to reach the reporter. updateEvery is now an hour: one look at npm's metadata for the plugins installed (no download unless a community plugin, unpinned and switched on, has a newer version; someone else's still waits for a click), cheap at that pace. Bun's own check (KeepBunUpdated, a GitHub release lookup with a two-day hold) keeps six hours under its own bunEvery. The Update button's tooltip on a community plugin says "within the hour" / 一小时内 where it said within a few hours. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass (TestProvidersAnswerWhileListsComeIn, TestUserPath and TestClaudeSignedOut failed once under load with four other test runs going and pass on their own); the plugin-*.test.cjs GUI tests pass in webkit. 2026-10-04 11:12:15 +08:00
yetone c8278a8691 docs: plugins are OpenCode's provider plugins or pi's packages — the Plugins page's intro, magpie plugin's help, the README and the plugin docs (en, zh, ja) said OpenCode only, though pi packages that register a provider run since v0.1.772; the docs gain a pi packages section (what makes a package pi's, what is loaded and left alone, sign-in through onPrompt/onAuth and ctx.ui, pi's files under <config>/pi or PI_CODING_AGENT_DIR, Anthropic Messages requests), the pi-package search and a FAQ entry 2026-10-04 03:10:09 +08:00
yetone 1baad0d2d4 plugin: pi packages are plugins as OpenCode's are — a pi coding agent package (its extensions) adds providers to magpie: their models, their sign-in, renewals and requests (owner: 我希望全面兼容 pi 插件!!!!!!!!!!) — a package is pi's when its package.json has a "pi" manifest, the "pi-package" keyword, or pi-coding-agent among its dependencies (IsPi, mirrored in host.js). Adding or updating one installs @earendil-works/pi-coding-agent beside it, and the plugin host loads every pi package through pi's own loader (pi.js: discoverAndLoadExtensions and ModelRuntime, with the credentials kept in plugin-auth.json), so an extension's registerCommand, registerTool and pi.on are taken and left unused, and its registerProvider calls become magpie providers. A provider's models keep their context, output, images and the reasoning levels their thinkingLevelMap allows; its OAuth login runs as pi runs it, each onPrompt asked in magpie's sign-in in turn, onAuth's page opened, a later prompt being the code pasted back; a provider with apiKey takes a key; magpie renews through refreshToken and saves the result. Requests arrive as Anthropic Messages and go to pi's stream: system and tools, images, tool results, thinking budgets and effort as the nearest level the model has (and the least it allows when it can't turn thinking off: Antigravity's gpt-oss refused requests without one), max_tokens held to the model's; the answer comes back as Messages SSE or JSON with thinking and its signature, redacted thinking, tool calls (their thought signatures kept for the next turn), usage, and a failure before anything is said as the vendor's status with Retry-After. The Plugins page's search also finds pi packages that bring a provider. Tried with the owner's real Antigravity account through pi-antigravity, next to the Grok OpenCode plugin in the same host: 14 models listed, Gemini streaming with usage, Claude Sonnet thinking with signatures, gpt-oss, tool round trips on gemini-3.8-flash, gemini-3.1-pro and claude-sonnet-4-6, a 400 and Antigravity's 429 passed on with their status. A Claude Code turn didn't get through: Antigravity answers 429 to any system prompt with Claude Code's billing header or its "Claude agent" line, through magpie's built-in Antigravity just the same. TestPiPackage (a fake pi package: models, an OAuth login asking twice with a wrong code then a right one, a key, streaming and JSON answers, thinking and tool signatures back, a 429, a renewal) and TestSearchFindsPiPackages pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeRunEndsWhenTheClientLeaves, TestProvidersAnswerWhileListsComeIn and TestSavedButSignedOutClaudeServed, timing tests that pass alone. 2026-10-03 19:12:19 +08:00
yetone a345af91e5 gui: a plugin card's Update chip updates the plugin (莫 on Discord: 显示更新但是好像没有更新按钮) — on Plugins › Discover a card whose plugin has a newer version showed an "Update" chip that looked like a button and did nothing (a click opened the plugin's page); the Update button was only on the Installed tab. The chip is now that button: it names the version ("Update to v0.0.9" / "更新到 v0.0.9"), updates the plugin without opening its page, shows Updating… meanwhile and goes once it is current, and is disabled while another plugin action runs or the plugin is off. plugin-card-update.test.cjs (en, zh) fails on the old code (no button) and passes; plugin-updates, plugin-market and plugin-check-updates still pass (WebKit). 2026-10-03 18:04:41 +08:00
yetone 860ed6618d plugins: a 「国内镜像」 switch on the Plugins page downloads the list, plugins and Bun from mirrors in China first (Discord, akic404: GitHub/npm are slow from the mainland; asked for ghproxy) — Plugins › Mirrors in China, off by default, kept in settings (chinaMirror). On, source asks a copy of the very file before its official address: npm's registry (packages, versions, search) at registry.npmmirror.com, Bun's release zips at registry.npmmirror.com/-/binary/bun, and the community registry.json at cdn.jsdelivr.net/gh/magpie-community/plugins@<branch>; bun add gets BUN_CONFIG_REGISTRY=npmmirror unless the environment names a registry. No proxy: gh-proxy (MAGPIE_MIRRORS' GitHub fallback) handles nothing the switch sends, since a proxy could hand back other code. A mirror asked first that can't answer (404/403/5xx, e.g. a version it hasn't synced) falls through to the official address; npm's weekly downloads stay api.npmjs.org's (npmmirror counts its own). Checked by hand: npmmirror's bun-v1.4.2 darwin-aarch64 zip has the SHA-256 pinned in bun.go, jsDelivr's registry.json is byte-identical to raw.githubusercontent's, and bun 1.4.2 add through npmmirror locks the same sha512 integrity npmjs gives. TestChinaFirst, TestChinaComesFirst, TestChinaMirrorAnswers, TestChinaMirrorsMissIsAskedOfficially, TestDoFaithful, TestBunInstallsFromChinaMirror and TestPluginsMirrorSwitch cover it. A live run with the switch on fetched the list from jsDelivr, npm info from npmmirror and Bun from npmmirror (checksum passed); a sandbox magpie web showed the switch in the Plugins head, saved it, kept it after a reload, and a click didn't move the page. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried from inside mainland China. 2026-10-03 15:14:45 +08:00
yetone 94bb3e2ca6 plugins: Check for updates asks npm now for each installed plugin's newest version, and each row says what it found (the owner: 插件的检查更新按钮还是得做) — the community's plugins update by themselves a little after magpie starts and every 6 hours, and the page knew of anyone else's new version only from what npm said within the hour (Info's cache), so a reader who heard a plugin had a new version had no way to have magpie look. The foot of Installed (beside Update all) now has Check for updates, shown when a plugin is npm's (a folder's or a git one's has no versions there; a git one keeps its own Update, which fetches it again): it says Checking… with a spinner while POST /api/plugins/check asks npm, a few at a time, past the hour's cache (plugin.CheckNow, through npmAsk, which is npmInfo telling why npm said nothing), and nothing is installed. Each row then says it: "v0.0.9 out" titled "v0.0.7 installed, v0.0.9 on npm" with its Update (the existing upgrade path, Upgrade → Add), "Up to date", or an amber "Couldn't check" chip with a dot, titled why — npm not reached (check your connection or proxy), npm turning requests away (429, try again in a few minutes), npm answering with an error, or npm not having the package (fetchJSON's answers other than 200/404 are now a statusError, so a 429 is told apart). The status line says how many have updates, or that every plugin is up to date, and how many couldn't be checked and why; npm not reached at all is "Couldn't check for updates: npm couldn't be reached — check your connection or proxy". A community plugin's Update is titled that magpie would update it by itself within a few hours and Update does it now. What npm said is kept as Info keeps it, so the rows and the market read it, and an update found for the reader (someone else's, or pinned) waits as the background look's do, putting the dot on Plugins; one npm didn't answer for keeps what waited before. Every new string has its Chinese; the click never scrolls the page. TestCheckNow (an httptest npm registry: the community's and someone else's updates found past an hour-old cache saying otherwise, up to date, 404 missing, 429 limited, 500 registry, git and folder not asked, nothing installed, the cache and the waiting list updated) and TestCheckNowOffline (npm not reached: offline, what waited still waits) are new, as is TestPluginsCheckRoute; plugin-check-updates.test.cjs (WebKit and Chromium, en and zh: Checking… while npm is asked, the rows' out / up to date / couldn't check chips and titles, the status line, Update after the check, offline said plainly, the view's scroll unmoved) fails without the change and passes with it. plugin-updates, plugin-market, plugin-progressive, plugin-git, plugin-local and click-scroll pass in WebKit and Chromium. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass. 2026-10-02 21:58:22 +08:00
yetone 7ae1c344b3 plugins: a plugin signed in to one of its subscriptions no longer reads as signed out (#556: 供应商这边显示登录了,插件那边掉登录了) — the Qoder plugin signs in to Qoder and Qoder CN, the WorkBuddy one to WorkBuddy AI and WorkBuddy; the reporter was signed in to Qoder and WorkBuddy AI, which Providers showed, and the Installed row showed "Qoder: signed in · Qoder CN: not signed in" beside a blue Sign in, the row's primary button, which read as the plugin having lost its sign-in. Nothing was lost: the second one is a subscription of its own that was never signed in to. Now, when one of a plugin's subscriptions is signed in, the row's button names the one it signs in to ("Sign in to Qoder CN", 登录 Qoder CN) and is a plain one, not the primary; the not-signed-in one's tooltip says it is a subscription of its own that the signed-in one works without; and Discover's card says Signed in (opening the signed-in one in Providers) rather than Sign in. A plugin signed in to nothing keeps its blue Sign in. plugin-two-subs.test.cjs (WebKit; en, zh) fails without the change and passes with it; plugin-market, plugin-move, plugin-moved-signin, plugin-local, plugin-updates and click-scroll (one flake of the trace's "picking request", then 2/2) pass. 2026-10-02 20:22:00 +08:00
yetone ec10d3bd98 gui: the header tabs lose their grey track, a Plugins card's move is one word beside the name, and an agent row's grip shows only under the pointer (the user: 这个 tab 栏的背景灰色条有点丑 · 这里的紫色按钮也有点丑,而且还有的变成了两行 · 排序 icon 只在 hover 到这个条目的时候显示,logo hover 的排序 icon 可以删掉)
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.
2026-10-02 10:31:43 +08:00
yetone cfd67225c8 plugins: the Plugins page is drawn in parts, Installed at once and npm's figures after (#488: the page showed skeletons for seconds, even on Installed) — the page waited on /api/plugins/market, which answered only once the plugin host, the registry fetch and an npm request for every package (two each, 8s apiece, all at once, kept an hour in memory only) had all come back, so the slowest package decided when anything was drawn. The page now asks for each part on its own, as the Library page does: Installed is drawn from GET /api/plugins as soon as it answers; Discover's cards from the new GET /api/plugins/listings (the registry, with its cached and built-in copies), each with what npm said last; and versions, weekly downloads and an installed plugin's "v… out" fill in from the new GET /api/plugins/npm?names=…, never waited on (a card's button waits, disabled, until npm has said whether it has the package). npm is asked at most six packages at a time and for no more than 8s in all; a package it answers late is what it said last, and its answer is kept for the next time. What npm said is kept on disk (~/.cache/magpie/plugin-npm.json) beside the registry's copy, so a magpie just started shows it before asking, and GET /api/plugins gives each plugin's newest version from it. /api/plugins/market still answers the whole market as before. Once loaded the page shows what it did. TestNPMInfoKeptOnDisk, TestNPMInfoDeadline, TestNPMInfoAtOnce, TestPluginMarketInParts and plugin-progressive.test.cjs (Chromium, WebKit; en, zh; Installed while the market is held pending) fail without the change; plugin-local, plugin-updates, plugin-market, plugin-move, plugin-move-overflow, plugin-release, plugin-git, plugin-key-hint, plugin-plan, list-sort, click-scroll, old-webkit pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 08:20:32 +08:00
yetone 03ab46dda3 library, plugins: the installed lists are sorted as the reader picks, and remember it (#481: the installed MCP servers, skills and plugins had no order the user could choose; the plugins came in the order they were installed) — the Library's MCP servers and the installed plugins now have A→Z and Z→A beside their list, A→Z until one is picked, so the plugins are no longer in the order they were installed in; the Library's skills have By source (the groups by repository and their parts, as before, and still the default), A→Z and Z→A, the last two one flat list of every skill by name, the filter still finding a skill by its repository. Each list keeps its own pick in the browser's storage (magpie.sort.<list>, read and written in try/catch), and a pick redraws only its list, the control left where it was on the screen. The skills' heading, the fullest, goes on to a second line in a narrow window rather than past the page's side. Sorting by use or by when a thing was installed waits for the library to keep those. list-sort (Chromium, WebKit; en, zh) fails without the change; library-items, library-all, own-skill, every-skill, the plugin tests, click-scroll and old-webkit pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-02 06:33:46 +08:00
yetone 456208945b plugins: a plugin can be installed from a git repository — github:owner/repo, owner/repo, a GitHub URL or a git+https:// one (Lemon on Discord: can plugins be installed directly from git?) — magpie plugin add and the Plugins field took only an npm name or a path, and turned a git spec away as "isn't an npm package name". A git spec now goes to bun add as given and is kept as given in plugins.json; the package it installs is named by its repository's package.json, which bun records as the dependency whose spec it is, so the plugin is known (list, on/off, rm, its version, its README) by that name or by the spec. update and the row's Update run bun update on it, which fetches the repository's commit now where bun add keeps the one bun.lock has, the spec unchanged; npm is never asked for a newer version of it, nor auto-updates it. Adding the same package from another URL, or from npm, takes its entry's place. The Plugins field says it takes a GitHub repo (en+zh); an installed git plugin shows its package's name, an Update button, and a page with its README, no npm link and a Source link to the repository. Nothing that installed before is turned away. TestGitPlugin (a fake bun fetching from local bare repositories, the real one loading the plugin when on PATH) and plugin-git.test.cjs fail without the change; TestIsGit checks what counts as a git spec. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; plugin-git, plugin-local, plugin-market, plugin-updates, click-scroll, old-webkit pass. 2026-10-02 02:24:49 +08:00
volcanic fc248e381f plugins: the folder a plugin lives in is picked, not typed, and its page reads that folder's README (#411)
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.
2026-10-01 18:46:56 +08:00
yetone b7037c101e plugin: plugins keep up to date as magpie does (the user: 插件的升级是怎么做的?我们有插件的升级提醒吗?) — a plugin updated only when the reader opened Plugins and clicked Update, save the built-ins' moved plugins held at their least version, so most users stayed on the version they first installed, and updating cut any reply streaming through a plugin. Now the magpie serving the gateway asks npm 30s after it starts and every 6 hours: the community's plugins (@magpie-community/*) update by themselves, and their row says Auto-updated (from which version, when) for three days; someone else's, or one pinned to a version, waits for the reader with a dot on Plugins naming it, gone once it's updated. A host being replaced finishes the calls it is serving (up to 10 minutes) while new ones go to the new host. TestCheckUpdates, TestPinned, TestRestartFinishesTheReply (fails without the change: the plugin host quit: signal: killed) and plugin-updates.test.cjs (Chromium, WebKit, en/zh). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; internal/plugin passes on Windows, internal/plugin and internal/provider on Linux; plugin-market, plugin-signin, plugin-move, plugin-release, click-scroll, old-webkit pass. 2026-10-01 16:27:43 +08:00
GinWU db0a3344f4 gui: a Plugins card's move button has a row of its own and says how many accounts it moves — on the Plugins tab, a card that offers a built-in's accounts put "Move my {name} accounts ({n})" in its top row beside the plugin's name; the button doesn't shrink (.pm-act is flex: none, nowrap), so in a two- or three-column window "Move my Grok (SuperGrok) accounts (1)" ran past the card's right edge, and Kiro's shorter one squeezed its name to "K…" and cut "magpie community". The move button (and its "Moving…") now has a row of its own at the card's foot, full width, wrapping if it must; Install, Sign in and the rest stay in the top row. Its words say what the number is: "Move my 1 Grok (SuperGrok) account", "Move my 2 Zed accounts", 「迁移我的 1 个 Grok (SuperGrok) 账号」. plugin-move-overflow.test.cjs (Chromium, WebKit, en/zh, 920/600/480px with the column count checked) fails without the change at 920 and 600px; plugin-move, plugin-market, market-have, old-webkit, plugin-moved-signin, plugin-release, plugin-signin pass (plugin-market's "typing keeps the field" fails now and then on main here too). Go not run: only CSS, JS and strings changed. (#398) 2026-10-01 14:56:25 +08:00
yetone 93c3cde024 gui: moving a built-in onto its plugin says what it is doing and why it didn't go, where the user is (记得把整套迁移逻辑做得丝滑优雅精致优美简单方便易用 / 要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — the Plugins tab opened as the page (?view=plugins) stayed blank; Move to the plugin greyed out for up to 18 s, then closed the editor, its error far off in the footer in English, and the button never came back. The button now says "Installing the plugin and checking each account…", a failure is said under it in the reader's language (npm unreachable, every account lapsed, models the plugin doesn't serve) with Try again, and a move that goes through turns the field over in the editor still open where it was, with a toast of the accounts and models it took. Runs on reads "magpie's built-in · or the community Zed plugin", the npm package in its tooltip; a quiet line over the list names the built-ins a plugin can run (Take a look · Not now); a plugin's card offers "Move my Zed accounts (2)", its installed row "Move Zed here" and "Zed: on magpie's built-in" rather than a signed-out plugin, and switching it on again offers the move back in the row. Accounts: a lapsed-only provider shows Signed out, not a green pill; plan chips read "Zed Pro", not "Zed Zed Pro", and a long one isn't cut; the "{agent} itself stays signed in" line is kept for a moved built-in whose agent keeps its own sign-in, named as the agent, and dropped where none is its own. The vendor list's age is said in the reader's language ("8m ago" in the zh UI). plugin-move.test.cjs (Chromium, WebKit, en/zh) fails without each of: the Plugins tab loading on ?view=plugins, Try again, the inline reason, the editor staying open, the progress text, the card's move. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; plugin-move, click-scroll, old-webkit, plugin-market, plugin-signin, nokey-editor pass. 2026-10-01 11:15:25 +08:00
yetone 2ca1373db5 plugins: removing or switching off the plugin a built-in is moved onto moves the built-in back first (插件版…保持一模一样) — the provider went with the plugin, its accounts out of sight in plugin-auth.json; now its accounts go back to the built-in, then the plugin is removed or switched off, in the app and with magpie plugin rm|off. Installed marks such a plugin ("Serves Zed", a dot, no stripe) and asks in its row, not a dialog, naming what goes back; only a yes sends it. TestReleasePlugin and plugin-release.test.cjs (Chromium, WebKit, en/zh) fail without the change; plugin-market, click-scroll, old-webkit pass. 2026-10-01 04:04:23 +08:00
yetone e332451169 plugins: the market lists magpie's community's plugins alone (来自 OpenCode 社区的插件可以删掉了,因为质量不行) — the section of the OpenCode community's plugins is gone, from the list built in, the community repo's registry and any copy fetched before; one is still a search away on npm, and one installed keeps its vendor's icon. TestBuiltinMarket, TestMarketSources, TestMarketIcon and plugin-market.test.cjs (Chromium, WebKit, en/zh) fail without the change. 2026-10-01 03:27:48 +08:00
yetone 8a4283256d gui: a plugin's README reads as it does on GitHub (这里的Markdown你没有渲染) — a link inside bold (**Curated by [Numman Ali](…)**) was shown as its source, and a badge, a picture in a link ([![npm version](…)](…)), as ![npm version](…) text; bold and italics now hold links and code, a badge goes whole, a link written as HTML (<a href>) is a link, and a link to one of the README's headings ([Install](#-quick-start), GitHub's anchors) goes to it in the page instead of nowhere. The publisher line under the name ends in an ellipsis rather than cut, and a box drawn in │ and ─ joins up. plugin-market.test.cjs (Chromium, WebKit, en/zh) fails without the change; old-webkit, click-scroll pass. go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test ./internal/gui ./internal/plugin pass. 2026-09-30 19:07:34 +08:00
yetone 9ec237d4e6 gui: a Plugins tab, a market to find and install provider plugins in (在产品里要有插件市场功能,整个插件市场要美观易用), and a way to it from the providers list (供应商列表里有个精致优雅的按钮) — Discover lists the magpie community's plugins, then OpenCode's community's by weekly downloads, each card with its logo, publisher, summary, downloads, version and one button that takes it all the way: Install (Bun fetched first if need be), Sign in, Signed in; a package not on npm yet says Coming soon. Typing searches the list and, after a pause, npm, with the field keeping its focus. A card opens the plugin's page: version, downloads, licence, updated, what it signs in to, npm/source/homepage links, and its README (no images, HTML or scripts; only http(s) links, opened outside). Installed lists each plugin with its providers' sign-ins, a newer version to update to, Update all, switch off and remove; Settings' Plugins section moves here. The list comes from registry.json in magpie-community/plugins, kept for 6 hours, falling back to the copy fetched last and then the one built in (MAGPIE_PLUGIN_MARKET overrides, "off" never asks). Under Add provider a "More subscriptions in Plugins" pill opens it; the add sheet ends in a Plugins tile, and a search that finds nothing offers to look for a plugin. The header, seven tabs now, draws them in at the narrowest window. plugin-market.test.cjs (Chromium, WebKit, en/zh) and market_test.go fail without the change; add-button, plugin-signin, usage-ledger, text-size, old-webkit, click-scroll pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-09-30 17:57:46 +08:00