feat(electron): in-app install, R2 auto-update feed, gaia:// deep links#2196
Conversation
…p links Three Agent-UI shell surfaces that share the Electron main/preload layer. #1721 — the agent:install / agent:uninstall IPC handlers were dead stubs that threw "not yet implemented", so the UI could never import a published agent. They now proxy to the backend install runtime (POST /api/agents/install, poll install-status, DELETE /api/agents/{id}) — one tested install path — and stream progress to the renderer over agent:install-progress. No backend port resolves to a loud, actionable error rather than a silent failure. #1724 — the forward auto-update channel moves off GitHub to a configurable generic (R2) feed with a mutable "latest" channel pointer. The feed URL is resolved at runtime from GAIA_UPDATE_FEED_URL or update-config.json; with none configured the updater enters a loud no-channel state instead of silently checking a dead feed. electron-builder.yml switches provider github -> generic. The R2 publish feed + channel pointer are milestone-52 work (#1719); until then point the env var at any generic/mock feed to exercise the path. #1725 — register the gaia:// protocol and handle gaia://hub/install/<id> deep links (macOS open-url, Win/Linux argv at cold start + second-instance), dispatching the agent id to the install runtime. Malformed links surface an actionable error dialog, never a silent drop. Tests: deep-link parsing, install/uninstall IPC redirect + loud no-backend error, and feed resolution / no-channel state under tests/electron.
…ds pass
electron-builder aborts config load with "cannot expand pattern
${env.GAIA_UPDATE_FEED_URL}: env ... is not defined" when the var is unset,
which it is in the installer-build CI jobs. Replace the macro with a static
generic feed base under hub.amd-gaia.ai. The value is inert at runtime —
auto-updater.cjs always re-resolves the feed and calls setFeedURL, or enters
the loud no-channel state — it exists only so electron-builder can emit
app-update.yml at build time.
|
🔒 SECURITY CONCERN — @kovtcharov-amd The deep-link install path performs an agent install without any user confirmation: Separate note for the tracking issue rather than this PR: until code signing lands (#1719), auto-update integrity rests solely on HTTPS + same-origin checksum — fine as a documented accepted risk, but it should stay a hard blocker for enabling a production feed. Otherwise the PR is in good shape — deep-link parser, feed resolution, and install/uninstall IPC proxy are all unit-tested (the |
…onfirmation A gaia://hub/install/<id> link comes from an untrusted web page, but dispatch called installAgent() directly — so any non-native catalog agent could be downloaded + pip-installed + hot-registered off a single generic OS "Open GAIA?" prompt, with the trust gate only guarding native packages. That bypasses the per-agent confirmation the in-app install requires (#2201). Deep-link installs now require an explicit per-agent confirmation dialog (naming the specific agent, defaulting to Cancel) before any download happens. The dispatch flow moves into deep-link.cjs with confirm/installAgent/focusWindow injected, so the security-critical gate — no confirm, no install — is unit tested; main.cjs supplies the native confirmation dialog.
|
🟡 When the app starts in NO_CHANNEL (no feed configured) and a feed is later set, periodic background checks are never scheduled — only manual
The test Since the R2 feed isn't live yet this won't affect users today, but it contradicts the intent of the late-configuration path the tests document. 🔍 Technical details
initialized = true;
if (!feedReady && !savedPin) {
// …log…
return; // ← exits before the block below
}
initialCheckTimeout = setTimeout(async () => {
await checkForUpdates();
scheduleNextCheck(); // ← never reached when NO_CHANNEL at startup
}, CHECK_DELAY_MS);Fix: when if (!forwardFeedUrl && !state.pinnedVersion) {
if (!_applyForwardFeed()) return;
// Feed was just configured; start the periodic cadence if nothing else has.
if (!initialCheckTimeout && !scheduledTimeout) scheduleNextCheck();
}This mirrors the |
kovtcharov
left a comment
There was a problem hiding this comment.
The deep-link concern is fully addressed: installs from gaia:// now require an explicit per-agent confirmation (default Cancel), the gate lives in deep-link.cjs with injected deps, and the security-critical control flow is unit-tested — confirm-before-install ordering, decline, and dismissed-dialog (non-true) all covered. Note: the currently red build/Vitest checks are main's AgentHubGrid breakage (missing module after the #2190/#2201 merge ordering), not this PR — re-run after main is fixed and they should go green. The auto-update feed integrity note stands as an accepted risk tracked in #1719.
Why this matters
The Agent UI shell had three broken/missing install-and-update surfaces, all in the Electron main/preload layer:
agent:installIPC stub that threw "not yet implemented" — the UI could never actually import an agent. After: the IPC handlers proxy to the real backend install runtime (POST /api/agents/install→ pollinstall-status→DELETEfor uninstall), streaming progress to the renderer. One tested install path, no duplicated download/verify/uvlogic in the main process. (Closes In-app install runtime: remove/redirect the staleagent:installElectron IPC stub (backend runtime landed) #1721)latestchannel pointer; the URL resolves fromGAIA_UPDATE_FEED_URL/update-config.json, and an unconfigured app enters a loud "no channel configured" state instead of silently checking a dead feed. (Closes R2-primary auto-update: generic electron-updater feed + mutable latest channel #1724)gaia://protocol and handlesgaia://hub/install/<id>(macOSopen-url, Win/Linux argv +second-instance), dispatching the agent id to the install runtime; malformed links raise an actionable dialog. (Closes gaia:// deep-link install bridge (website → app) + protocol registration #1725)No silent fallbacks: a missing backend port, missing update feed, or malformed deep link each surface a loud, actionable error.
Part of #2014.
Dependency note (#1724)
The actual R2 publish feed + Worker
latest*.ymlchannel pointer are milestone-52 work (#1719) and not live yet. This PR ships the app-side integration + config so it works against any configurable feed URL (a local/mock feed exercises the full path);electron-builder.ymlswitchesprovider: github → generic. Rollback (#1336) still lists/installs from GitHub Releases — independent of the forward feed, migrates with #1719.Test plan
cd tests/electron && npm install && npx jest— full suite green (659 passed, 1 skipped, 20 suites)tests/electron/deep-link.test.cjs—gaia://parse: valid, bad scheme, missing/invalid id, extra segments, argv extractiontests/electron/auto-updater-feed.test.cjs— feed from env + config file, env precedence, loudNO_CHANNELwhen unset,checkForUpdatespicks up a later-configured feedtests/electron/test_agent_process_manager.js— install/uninstall IPC redirect (POST/DELETE shape +X-Gaia-UIheader), failed-install surfaces, loud no-backend errortests/electron/auto-updater.test.cjs—clearPinrestores the R2 forward feedcd src/gaia/apps/webui && npm run build— TypeScript + Vite build compilesGAIA_UPDATE_FEED_URL=<mock>end-to-end update against a local feed (blocked on a real R2 feed / Publish Agent UI to R2 as anapp(manifest + catalog entry + signed release workflow) #1719 for the production path)