Add browser-automation/desktop-remote-engine-sync
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user