6.4 KiB
6.4 KiB
name, description, category, tags, related_skills
| name | description | category | tags | related_skills | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| desktop-remote-engine-sync | Fix Desktop remote mode showing stale local engine version — sync engine version display and update to Bridge server instead of reading local install. | browser-automation |
|
|
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:
# ── 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:
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():
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
// 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:
# 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
git fetch origin main && git reset --hard origin/main(both repos)sudo systemctl restart cloud-bridge.service(Bridge changes take effect immediately)- Desktop needs rebuild for user to get the fix
Pitfalls
sudomay not work: usesystemctl restartdirectly if running as root- Bridge restart can hang: use
kill -9 <pid>thensystemctl start - Gitea API 405 on merge: sleep 3-5s after PR creation, retry
- Protected main: never push directly, always use release branch + PR
_HERMES_BINpath: defaults to~/hermes-agent/hermes, overridable viaHERMES_AGENT_DIRenv- Version handler returns plain text (not JSON) to match
hermes --versionformat that Desktop'sparsedVersionregex expects - hermes.ts unicode escapes: do NOT use
patchtool on hermes.ts — use Pythonrb/wbbinary mode. Seeatomk-desktop-developmentpitfall #55. - Prompt hygiene:
buildCloudBridgePrompt()must never hardcode platform-specific URLs, menu structures, or account info. Seeatomk-desktop-developmentpitfall #59.