Commit Graph
16 Commits
Author SHA1 Message Date
yetone f22218a092 gui: after an update the window shows what changed since the version last run (a Discord user: 点更新后能弹个窗显示更新了啥吗, to see whether their issue was fixed) — magpie updated itself silently, and its release notes were only on GitHub. The version last run is now kept in magpie's own folder (last-version); started on a newer release, the window or magpie web's page asks usemagpie.ai's new /api/notes (beside /api/latest; site/worker.js lists the releases' notes from GitHub, cached as the feed is) for every release since, newest first, without their Install section, and shows them once in a What's new dialog. A fresh install, the same version, a downgrade or a build from source shows nothing; a magpie from before the version was kept, already in use, shows its own version's notes; offline nothing is shown and nothing waits on it; until the site is deployed, the update feed's newest notes stand in when they are the current version's. The tray's panel shows no dialog. The notes are drawn as text, never as HTML: headings, bullets, bold, code, http(s) links, and #NNN links to the issue, opened in the browser as the app's other links are. Settings has a What's new row under Version that opens them again, with a waiting update's notes first, marked Not installed yet; /api/update's notes leave out the Install section too. TestShowNotes, TestStripInstall, TestBetween, TestNotesBetween, TestWhatsNewAfterUpgrade, TestWhatsNewFromBefore, TestUpdateNotesWithoutInstall and whats-new.test.cjs (Chromium, WebKit, en, zh) fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; click-scroll, old-webkit, update-check, update-hide pass. 2026-10-02 03:57:55 +08:00
AronnaxandClaude Opus 5.5 ec4ee5a3a4 fix(proc): end what a CLI probe started, on its timeout and when magpie exits (#414)
* fix(proc): end what a CLI probe started, on its timeout and when magpie exits

The probes that ask an agent's CLI something (claude auth status,
cursor-agent about/models/status, the --version checks) ran through
proc.CommandContext, which on its timeout kills only the command itself.
A CLI that is a script (a version manager's wrapper, a shim) left what it
had started running, adopted by init; and a probe still asking when magpie
exited (a one-shot command, an update, a restart) was left whole, with
nothing to end it. With Omarchy's mise wrappers stuck in their exec loop
(omacom/omarchy#7234) that was five processes holding about half a core
each for up to six hours.

proc.ProbeContext gives such a probe a session of its own on Unix, as
StartTree does, and its context ending kills the whole group. Probes still
running are listed, and proc.EndProbes kills them when magpie exits (after
run in main) or puts a new version in its place (update.Reexec). On
Windows a probe is killed alone, as before, a job being joined only once
the command has started.

TestProbeTimeoutEndsWhatItStarted and TestEndProbes fail without the
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(proc): end probes on a terminal's signals and on a Mac's quit

Review of the first change: a probe leading its own session no longer
gets the Ctrl+C, hang-up or SIGTERM sent to magpie's terminal or service,
and magpie ending on the signal never reached EndProbes, so the probe was
left to init where before it died with magpie. main now catches SIGINT,
SIGTERM and SIGHUP, ends the probes and exits as the signal would have
(128+n). A signal magpie was started ignoring (nohup, a background job)
stays ignored. The account sign-in and plugin commands, which end
themselves on Ctrl+C, go through interruptContext and are left to do so.

On a Mac the desktop app's quit ([NSApp terminate:], also the restart to
update) exits without returning to main, so OnShutdown ends the probes.

TestProbeTimeoutEndsWhatItStarted ended its probe on a 500ms timeout,
which on a Mac could come before the script had written its child's pid
(6 of 10 runs failed in review). It now ends the context once that pid is
read, the same Cancel path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: let the app and the TUI quit on a signal their own way

Review of the signal handling: endProbesOnSignal exited ahead of Wails
and bubbletea, which quit on SIGINT and SIGTERM themselves. `magpie tray`
exited 143 without OnShutdown (no downloaded update installed, and
sessions.Saved skipped in main), and `magpie tui` left the terminal on its
alternate screen with the cursor hidden. The app's and the TUI's entry
points now hand the signals back (ownSignals): the app's OnShutdown ends
the probes, and the TUI comes back to main, which ends them too. A
one-shot command keeps the handler.

TestOwnSignalsHandsThemBack fails without the change (magpie exited 143
before the command could quit its own way); TestSignalExits keeps the
one-shot behavior.

childPid waited 3s for a script just written to start its child, which a
busy Mac can exceed (2 of 3 full runs failed in review). It waits 30s now
and, should the probe end first, says the probe's error rather than "the
script started nothing".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep SIGHUP for the app and the TUI, and die of the signal

ownSignals handed SIGHUP back along with SIGINT and SIGTERM, but neither
Wails nor bubbletea handles it: the terminal closing under the TUI or an
app started from a shell ended magpie on the spot, and its probes, in a
session of their own, out of the hang-up's reach, kept running (before
this PR they were in the terminal's group and ended with it). SIGHUP now
stays with endProbesOnSignal. A tray under a pty whose terminal closes
left 2 processes before, none now.

The handler asked whether a command was ending itself on Ctrl+C only
after EndProbes, which can take up to a second; a sign-in done in the
meantime got magpie ended in the middle of its own cleanup. It is asked
first.

It exited 128+n, which a shell takes for a child that handled the signal:
a loop running magpie went on to the next run on Ctrl+C. It now dies of
the signal (handling reset, the signal sent again); Windows, which can't
send one, still exits with the code.

agentProbe and agentCommand share withProxy; the Windows probeGroup
comment no longer says EndProbes ends what a shim started.

TestOwnSignalsKeepsHangUp and TestInterruptWhileEndingProbes fail without
the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: hand signals to the TUI and the app only once they listen

Review: runTUI handed SIGINT and SIGTERM back before tui.Run, whose
newModel asks agents' CLIs (claude auth status, up to 5s) before
bubbletea listens for signals. Ctrl+C, SIGTERM or SIGINT while it loaded
ended magpie by Go's default and left the probe and its child running.
tui.Run now takes ready, called once the model is made, and runTUI passes
ownSignals.

The app had the same window: runGUI handed them back before gui.Run,
which starts the backend (probes included) before Wails' Run starts
listening. ownSignals is now gui.Started, called on ApplicationStarted,
which Wails emits after its signal handler is set up. A tray sent SIGTERM
0.05s after starting, a probe under way, left 2 processes before and
none now; one sent it once started still quits through OnShutdown
(exit 0).

TestTUIKeepsSignalsWhileLoading fails without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:42:48 +08:00
yetone bd529a555e netproxy: a host whose IPv6 takes the connection and drops it is reached over IPv4 (tony on Discord: models.dev sync and the update check failed with a bare EOF, direct or auto, while curl got through) — behind a TUN (xray) without IPv6 out, a connection to a site's IPv6 address is accepted, so the dialer's own fallback to IPv4 never starts, and closed once TLS begins. A connection to an IPv6 address that ends before a byte comes back now marks its host:port to be dialed over IPv4 for 30 minutes, and the request that met it is sent once more over IPv4 when nothing of it can have reached the site (HTTPS, or an idempotent method) and its body can be had again; proxied requests and IPv4 connections are left as they are, and TLS, ALPN/HTTP/2, headers and bodies are unchanged. Every transport made with Dispatch (http.DefaultClient, the gateway's) and the update client get it. TestIPv4Fallback fails without the change (Get "https://example.com/": EOF). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestFXInState flaked on TempDir cleanup under load, passes alone). 2026-10-01 23:20:58 +08:00
yetone 0361189f9e test: every package's tests pass on Windows, and none writes outside its sandbox (the user: 把 Windows 上那几个失败的测试修掉) — on a Windows box the suite failed in most packages, and the root package hung for 25 minutes. Tests set HOME, which os.UserHomeDir ignores on Windows (it reads USERPROFILE), so they read and wrote the runner's own home; each now sets USERPROFILE beside HOME, and Devin's, Goose's, Crush's and Claude Desktop's tests set APPDATA/LOCALAPPDATA, where those keep their files there. said() read the command's output only once it returned, and a pipe holds a few KB on Windows, so magpie model --help blocked forever; it reads while the command writes. Unix permission bits (the gateway key store's 0600 too), exec bits and symlinks Windows lacks are checked only off Windows; TOML strings hold Windows paths as literal strings, JSON as marshalled ones; a URL's path is compared as one. The provider and usage packages run in a home of their own (TestMain, as the gateway's already does), since a CLI's identity kept by a refresh still going when its test ended landed in the runner's ~/.config/magpie — and on a Mac in the user's own. Two product fixes came out of it: imagemcp took a file:///C:/… root or image for the path /C:/…, which doesn't exist on Windows, and now makes a Windows path of it (a host for a UNC share); and session discovery could keep a new Cowork session hidden on Linux, where a folder's mtime is a kernel tick and its size doesn't change, so an entry added within the tick of the last listing left both as they were — a listing made within 2s of its folder's last change is read again, as git does for racy files. TestFilePath and TestRacyDirectoryListedAgain fail without the change. On Windows 11 every package passes (32 of 32) and a run of each package in a fresh home leaves nothing in home, APPDATA or LOCALAPPDATA; on Linux (WSL) and macOS go test -tags nogui ./... passes. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. 2026-10-01 23:15:07 +08:00
yetone 30236bf035 migrate: moving Kiro back with an API key no longer hangs magpie, a move's lock left by a killed magpie frees itself, and plugins changed in a terminal reach the running app (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Kiro's back put the plugin's API key onto the Kiro provider while holding logins.json's lock; saving the provider syncs the agents, which read the accounts and waited on that same lock, so the move back never returned and every request after it hung. The key is now put back once the lock is let go (a mover's settle). A move killed while hung left migrations.json.lock, refusing every move for 15 minutes as "another magpie is moving"; the lock now holds its magpie's pid and one whose magpie is gone is taken. And magpie plugin add, remove or move in a terminal left a running app with the plugins it had loaded (a moved Kiro gone until restart, "magpie knows no model kiro/auto"): a plugins.json changed by another magpie now restarts the host and takes the providers that magpie was told meanwhile. Found walking Qoder, Kiro and Cursor through sandbox moves. TestKiroMover (hangs), TestMoveLockOfDeadMagpie and TestPluginsChangedElsewhere fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 06:53:29 +08:00
yetone ee8a371cf4 gui, update: saving the Settings page keeps the models hidden from each agent, and update checks go through magpie's proxy and say so (#294: 设置代理之后只有模型代理生效了,代理没有用于检查 magpie 是否有更新) — POST /api/settings carried over the models' names, levels and images but not HiddenModels, which the page never sends, so changing the theme, the language or anything else there showed every model hidden one by one on the Agents page again; it is carried over with the others. The update feed and the release came through http.DefaultTransport, proxied only because main installs netproxy first; the update client now takes netproxy.Func on a transport of its own (Settings' Proxy, else *_PROXY, else the system's). A check that failed at start-up, before a proxy was set, stayed failed for six hours: saving a new proxy now checks again through it. And the Settings row read "requests to vendors go through {proxy}", which says update checks don't: it now names both. TestSettingsSaveKeepsWhatItDoesNotSend (every field prefsKeep doesn't send survives a save of the theme alone; fails on main with HiddenModels emptied), TestUpdateGoesThroughTheProxy (feed and asset at hosts only a local proxy answers; fails without the transport), TestSettingsProxyRechecksAFailedUpdate (fails without the re-check). account-proxy, provider-proxy and click-scroll GUI tests pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, known flakes under load, pass alone). 2026-09-30 16:03:52 +08:00
yetone 831df8579e update: a restart to update from the open window (Windows, Linux) opens the window again, on the tab it was asked from, instead of the tray icon alone (#275 Aimer779: 设置页更新重启后主窗口消失,只剩托盘) — RelaunchBinary always started magpie tray, autostart's argument, so the new version came up as a tray icon however the update was asked for; the Mac reopens its .app and was fine. The window's Update pill and Settings' "Restart to update" send the tab they're on, the new process starts as magpie gui <tab>; the tray menu's Restart to Update brings the window back if it was open and keeps to the tray if not; autostart stays tray. magpie gui <tab> / magpie app <tab> open the window on that tab, and hand it to a magpie already running. The MAGPIE_REPLACES handover is unchanged: the new process waits for the old one before its single-instance lock. Tested with TestRelaunchArgs and TestRelaunchBinaryArgs (a script in magpie's place is started with gui settings <pid> / tray <pid>); go vet, go build (GUI and -tags nogui, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. Not tried: a real restart to update on Windows or Linux. 2026-09-30 12:33:36 +08:00
yetone 4e83dc6be8 web: magpie web knows its version and updates itself as the app does — the badge's restart puts the new version in and runs it in the old one's place with the same arguments and key, so the open page reloads onto it (#111); routing: a gateway served by magpie serve, which has no window, is told apart from another magpie's window, and the routing view says to stop it so this magpie serves the gateway and shows routing live (#110) 2026-09-27 19:39:45 +08:00
yetone 06dddf7f68 update: magpie update in a Homebrew install (brew install magpie, from the new tap in magpie-releases) says to brew upgrade magpie instead of replacing the binary in brew's Cellar, which would leave brew thinking the old version is there 2026-09-26 20:50:26 +08:00
yetone cb609d2cb4 update: after an update on Windows one magpie is left, not two — a magpie running when another updated the binary (it runs on from magpie.exe.old) no longer downloads the update again to magpie.exe.new and swaps it in when it quits; it says a restart is all that is left. A magpie.exe.new that is magpie.exe over again is removed at start. The tray that found the gateway taken serves it once the magpie that had it is gone, checked every 15 s 2026-09-26 14:30:16 +08:00
yetone 7c3fa7d19e update: on Windows an update goes in while what the last one moved aside still runs — a magpie serve started before an update runs from magpie.exe.old, which then can't be removed, and moving the running exe onto it failed with "Access is denied"; it now goes beside it (magpie.exe.old-2…), the leftovers are removed once free, the download is kept for another try, and the Update button says Update failed (the error in its tooltip) instead of seeming to do nothing. Reproduced on Windows 11 with a running .old: the old swap fails, the new one passes 2026-09-26 01:38:37 +08:00
yetone e8b3be572c windows: the desktop app no longer opens a console window for every command it runs (#54) — it is linked as a GUI program and has no console, so each console CLI it started (devin auth status and models list, devin acp for every Devin turn, claude, codex, cursor-agent and grok probes and runs, grok login) was given a new console by Windows, and a Windows Terminal window with it, some every few seconds while the app sat idle. Every command magpie runs is now made through internal/proc, which starts it with CREATE_NO_WINDOW when magpie has no console of its own: the child gets a console without a window, and its output, which magpie always pipes back, arrives as before. A magpie run in a terminal (magpie serve, the TUI, the desktop app started from one with a command) has one, and its children share it as they did, so Ctrl+C there still reaches them. The relaunch after an update keeps its DETACHED_PROCESS, added to rather than replacing the flags it was made with, and a test fails on any command made with os/exec directly, so a new one can't bring the windows back 2026-09-25 14:04:44 +08:00
yetone 90580ca7e1 update: Update updates magpie in place — a click downloads the new version and restarts into it, asking for the password when magpie can't replace itself, instead of opening the release page; only a magpie running from its disk image or a temporary copy is sent to the download page, with why 2026-09-25 01:35:24 +08:00
yetone 65fdcb1a14 update: the download shows how far along it is — percent and megabytes in Settings — a dropped connection is tried twice more, and a failure says why instead of only that it failed (#21) 2026-09-24 15:07:35 +08:00
yetone 75cb192647 Desktop app for Windows and Linux
The Windows app cross-compiles (WebView2, no cgo) as a GUI program with an
icon, version info and a DPI-aware manifest; run from a terminal with a
command it borrows the console. The Linux app links GTK 3 and WebKitGTK
4.1. Off the Mac the app updates its own binary from its own build:
downloaded in the background, swapped in on restart or quit, and the
relaunched process waits for the old one before taking the gateway. The
window leaves room for traffic lights only on a Mac.
2026-09-24 03:22:02 +08:00
yetone 73ef0e5276 usemagpie.ai, and a magpie that updates itself
site/ is the website: a static page plus a worker that turns the newest
release of yetone/magpie-releases into an update feed (/api/latest, with
every file's SHA-256), stable download links and an install script.

The app asks the feed at start-up and every six hours, downloads a newer
version in the background, and installs it on Restart to Update or on
quit. A download must match its checksum and be signed by the same team
as the app it replaces. `magpie update` does the same from a terminal and
replaces a bare binary in place.
2026-09-24 01:57:50 +08:00