Files

176 lines
6.4 KiB
Markdown

---
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<string | null> {
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 <pid>` 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.