Files

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
bridge
engine
remote-mode
version
update
fix
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:

# ── 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

  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.