--- name: desktop-remote-engine-sync description: Fix Desktop remote mode showing stale local engine version — sync engine version display and update to Bridge server instead of reading local install. category: browser-automation tags: [desktop, bridge, engine, remote-mode, version, update, fix] related_skills: [atomk-desktop-development] --- # Desktop Remote Mode — Engine Version Sync ## Trigger - User reports "engine N commits behind" after installing new Desktop build - Settings page shows stale version when Desktop is in remote/Bridge mode - Engine update button doesn't affect the Bridge server's engine ## Root Cause Desktop v4.0+ uses remote mode exclusively (atomlisting login → Bridge server). The `get-hermes-version` and `run-hermes-update` IPC handlers fall through to local `getHermesVersion()` / `runHermesUpdate()` — reading/writing `~/.hermes/hermes-agent/` which is a stale leftover from a previous Desktop install. Compare `run-hermes-doctor` which correctly routes to Bridge in remote mode. ## Fix Pattern (Two Repos) ### 1. Bridge Server — New Endpoints Add to `cloud-bridge/server.py`: ```python # ── Hermes Engine Version & Update ────────────────────────── _HERMES_AGENT_DIR = Path(os.environ.get('HERMES_AGENT_DIR', os.path.expanduser('~/hermes-agent'))) _HERMES_BIN = str(_HERMES_AGENT_DIR / 'hermes') async def hermes_version_handler(request: web.Request) -> web.Response: """GET /api/hermes/version — runs hermes --version.""" proc = await asyncio.create_subprocess_exec( _HERMES_BIN, '--version', stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=str(_HERMES_AGENT_DIR), ) stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=15) if proc.returncode != 0: return web.json_response({'error': ...}, status=502) return web.Response(text=stdout.decode().strip(), content_type='text/plain; charset=utf-8') async def hermes_update_handler(request: web.Request) -> web.Response: """POST /api/hermes/update — runs hermes update.""" proc = await asyncio.create_subprocess_exec( _HERMES_BIN, 'update', stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, cwd=str(_HERMES_AGENT_DIR), ) stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=120) return web.json_response({'ok': proc.returncode == 0, 'output': ...}) ``` Register on both `ws_app` and `http_app` with `auth_wrapper`: ```python ws_app.router.add_get('/api/hermes/version', auth_wrapper(hermes_version_handler)) ws_app.router.add_post('/api/hermes/update', auth_wrapper(hermes_update_handler)) http_app.router.add_get('/api/hermes/version', auth_wrapper(hermes_version_handler)) http_app.router.add_post('/api/hermes/update', auth_wrapper(hermes_update_handler)) ``` ### 2. Desktop — Bridge Helper Functions Add to `src/main/index.ts`, near `runBridgeDoctor()`: ```typescript async function bridgeGetHermesVersion( cloudBridgeUrl: string, apiKey: string, ): Promise { const httpUrl = wsUrlToHttp(cloudBridgeUrl); const resp = await fetch(`${httpUrl}/api/hermes/version`, { headers: { Authorization: `Bearer ${apiKey}` }, signal: AbortSignal.timeout(15000), }); if (!resp.ok) return null; return (await resp.text()).trim() || null; } async function bridgeRunHermesUpdate( cloudBridgeUrl: string, apiKey: string, ): Promise<{ success: boolean; error?: string }> { const httpUrl = wsUrlToHttp(cloudBridgeUrl); const resp = await fetch(`${httpUrl}/api/hermes/update`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}` }, signal: AbortSignal.timeout(130000), }); const data = await resp.json(); if (data.ok) return { success: true }; return { success: false, error: data.error || ... }; } ``` ### 3. Desktop — Update IPC Handlers ```typescript // get-hermes-version ipcMain.handle("get-hermes-version", async () => { const conn = getConnectionConfig(); if (conn.mode === "ssh" && conn.ssh) return sshGetHermesVersion(conn.ssh); if (conn.mode === "remote" && conn.cloudBridgeUrl && conn.apiKey) // ← ADD return bridgeGetHermesVersion(conn.cloudBridgeUrl, conn.apiKey); return getHermesVersion(); }); // refresh-hermes-version — same pattern // run-hermes-update ipcMain.handle("run-hermes-update", async (event) => { const conn = getConnectionConfig(); if (conn.mode === "ssh" && conn.ssh) { ... } if (conn.mode === "remote" && conn.cloudBridgeUrl && conn.apiKey) { // ← ADD return bridgeRunHermesUpdate(conn.cloudBridgeUrl, conn.apiKey); } await runHermesUpdate(...); }); ``` ## Auth Desktop uses `conn.apiKey` from atomlisting login → sent as `Bearer` token. Bridge endpoints use `auth_wrapper` (same as `/v1/*`, `/cdp/*`). No separate API key provisioning needed. ## Branch / PR Flow Both repos have protected `main`: ```bash # Bridge cd /home/ubuntu/atomk-page-bridge git checkout -b release/vX.Y.Z # ... edit server.py ... git add cloud-bridge/server.py && git commit -m "feat: ..." git push origin release/vX.Y.Z # → Gitea API: create PR + merge # Desktop cd /home/ubuntu/AtomK-Desktop git checkout -b release/vX.Y.Z # ... edit src/main/index.ts ... git add src/main/index.ts && git commit -m "fix: ..." git push origin release/vX.Y.Z # → Gitea API: create PR + merge ``` ## After Merge 1. `git fetch origin main && git reset --hard origin/main` (both repos) 2. `sudo systemctl restart cloud-bridge.service` (Bridge changes take effect immediately) 3. Desktop needs rebuild for user to get the fix ## Pitfalls - **`sudo` may not work**: use `systemctl restart` directly if running as root - **Bridge restart can hang**: use `kill -9 ` then `systemctl start` - **Gitea API 405 on merge**: sleep 3-5s after PR creation, retry - **Protected main**: never push directly, always use release branch + PR - **`_HERMES_BIN` path**: defaults to `~/hermes-agent/hermes`, overridable via `HERMES_AGENT_DIR` env - **Version handler returns plain text** (not JSON) to match `hermes --version` format that Desktop's `parsedVersion` regex expects - **hermes.ts unicode escapes**: do NOT use `patch` tool on hermes.ts — use Python `rb`/`wb` binary mode. See `atomk-desktop-development` pitfall #55. - **Prompt hygiene**: `buildCloudBridgePrompt()` must never hardcode platform-specific URLs, menu structures, or account info. See `atomk-desktop-development` pitfall #59.