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