@algosuite/vo-mcp 0.2.0-beta.11 → 0.2.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/cloud/pairing.ts", "../src/cloud/credential-store.ts", "../src/cloud/keychain.ts", "../src/pair-cli.ts"],
4
- "sourcesContent": ["/**\n * Device-code pairing for the BYO runner (M1) \u2014 the no-terminal-token onboarding.\n *\n * The runner calls the control-plane `pair/initiate`, shows the friend a short\n * `code` to type at `<dashboard>/pair`, then polls `pair/poll` with its SECRET\n * `poll_token` until the friend authorizes \u2014 at which point it receives a freshly\n * minted `vocred_` and stores it in the OS keychain (via writeStoredCredential).\n *\n * The friend's AI key NEVER flows through here \u2014 this only obtains the\n * control-plane credential the runner needs to claim THEIR tasks.\n */\nimport { hostname, platform } from 'node:os';\n\nimport { writeStoredCredential, type StoredCredential } from './credential-store.js';\n\nexport const DEFAULT_CONTROL_PLANE_URL = 'https://vo-control-plane-bzjphrajaq-uc.a.run.app';\nexport const DEFAULT_DASHBOARD_URL = 'https://algosuite.ai';\n\nexport interface RunPairingDeps {\n readonly env?: Record<string, string | undefined>;\n readonly log?: (message: string) => void;\n readonly fetchImpl?: typeof fetch;\n readonly sleep?: (ms: number) => Promise<void>;\n readonly now?: () => Date;\n /** Test seam: capture the stored credential instead of touching the real keychain. */\n readonly store?: (cred: StoredCredential, storedAtIso: string) => string;\n}\n\nexport interface PairingResult {\n readonly credentialPath: string;\n readonly expires_at: string;\n}\n\n/** ABCD-EFGH grouping for an 8-char code (easier to read aloud / type). */\nexport function formatPairingCode(code: string): string {\n return code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;\n}\n\nasync function readJson(res: { json: () => Promise<unknown> }): Promise<Record<string, unknown>> {\n const body = await res.json().catch(() => ({}));\n return body && typeof body === 'object' ? (body as Record<string, unknown>) : {};\n}\n\n/**\n * Run the full pairing handshake. Resolves once a credential is stored; rejects\n * with a friendly message on expiry / consumption / fatal transport error.\n */\nexport async function runPairing(deps: RunPairingDeps = {}): Promise<PairingResult> {\n const env = deps.env ?? process.env;\n const log = deps.log ?? ((m: string) => console.error(m));\n const fetchImpl = deps.fetchImpl ?? fetch;\n const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n const now = deps.now ?? (() => new Date());\n const store = deps.store ?? ((cred: StoredCredential, iso: string) => writeStoredCredential(cred, iso, env));\n\n const controlPlaneUrl = env['VO_CONTROL_PLANE_URL']?.trim() || DEFAULT_CONTROL_PLANE_URL;\n const dashboardUrl = env['VO_DASHBOARD_URL']?.trim() || DEFAULT_DASHBOARD_URL;\n const deviceLabel = `${platform()} on ${hostname()}`.slice(0, 120);\n\n // 1. initiate \u2014 get a display code + a secret poll token.\n const initRes = await fetchImpl(`${controlPlaneUrl}/api/v1/pair/initiate`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ device_label: deviceLabel }),\n });\n if (!initRes.ok) throw new Error(`Could not start pairing (server ${initRes.status}).`);\n const init = await readJson(initRes);\n const code = String(init['code'] ?? '');\n const pollToken = String(init['poll_token'] ?? '');\n if (!code || !pollToken) throw new Error('Pairing service returned an incomplete response.');\n const intervalMs = (Number(init['poll_interval_seconds']) || 5) * 1000;\n const expiresAtMs = new Date(String(init['expires_at'] ?? '')).getTime();\n\n // 2. show the friend what to do.\n log('');\n log(' To connect this runner, open this page in your browser:');\n log(` ${dashboardUrl}/pair`);\n log(' and enter this code:');\n log('');\n log(` ${formatPairingCode(code)}`);\n log('');\n log(' Your Anthropic key never leaves this computer. Waiting for you to authorize\u2026');\n\n // 3. poll with the SECRET token until authorized / expired.\n for (;;) {\n if (Number.isFinite(expiresAtMs) && now().getTime() >= expiresAtMs) {\n throw new Error('The pairing code expired before it was authorized. Run `vo-mcp pair` again.');\n }\n await sleep(intervalMs);\n const pollRes = await fetchImpl(`${controlPlaneUrl}/api/v1/pair/poll`, {\n method: 'GET',\n headers: { 'x-vo-poll-token': pollToken },\n });\n if (pollRes.status === 404) {\n throw new Error('The pairing expired. Run `vo-mcp pair` again.');\n }\n if (pollRes.status === 410) {\n throw new Error('This code was already used. Run `vo-mcp pair` again.');\n }\n if (!pollRes.ok) {\n // Transient (rate limit / blip) \u2014 keep waiting.\n continue;\n }\n const body = await readJson(pollRes);\n if (body['status'] === 'pending') continue;\n if (body['status'] === 'authorized' && typeof body['vo_credential'] === 'string') {\n const credentialPath = store(\n {\n vo_credential: body['vo_credential'] as string,\n ...(typeof body['expires_at'] === 'string'\n ? { vo_credential_expires_at: body['expires_at'] as string }\n : {}),\n },\n now().toISOString(),\n );\n return { credentialPath, expires_at: String(body['expires_at'] ?? '') };\n }\n throw new Error('Unexpected response from the pairing service.');\n }\n}\n", "/**\n * Local credential store for the thin-client `vo-mcp login` flow (Increment 3b,\n * Option A \u2014 `docs/vo/vo-command-center-inc3b-login-design-2026-06-05.md`).\n *\n * Persists the per-user Firebase refresh token (the user's OWN credential, never\n * the god-token, never model keys) captured by `login`, so the auto-refreshing\n * token source (Inc 3a) can mint fresh ID tokens across MCP restarts.\n *\n * Storage precedence (Inc 3b.3):\n * 1. **OS keychain** (Windows Credential Manager / macOS Keychain / libsecret)\n * via the optional `@napi-rs/keyring` backend (`keychain.ts`). The DEFAULT\n * when available \u2014 the secret never lands in plaintext on disk.\n * 2. **0600 file** at `$VO_MCP_CREDENTIALS_PATH` or `~/.config/vo-mcp/credentials.json`.\n * The fallback when the keychain is unavailable or disabled\n * (`VO_MCP_DISABLE_KEYCHAIN`). `VO_MCP_CREDENTIALS_PATH` only sets the file\n * LOCATION; force file storage with `VO_MCP_DISABLE_KEYCHAIN`.\n *\n * `env`-supplied tokens (`VO_USER_REFRESH_TOKEN`, etc.) still win over BOTH\n * stores \u2014 that precedence lives upstream in `auth-token-source.ts`.\n *\n * **Single source of truth.** The credential lives in EITHER the keychain OR the\n * file, never both: a write to one store CLEARS the other, so a stale entry can\n * never shadow the current credential on read, and the secret never lingers in\n * plaintext after a migration to the keychain.\n *\n * **Keychain durability.** A keychain-stored credential is only readable while\n * the `@napi-rs/keyring` native module loads. If the module later becomes\n * unavailable (an ABI break across a Node upgrade, a corrupted install), the\n * credential can't be read and the user re-runs `vo-mcp login` \u2014 the same\n * behaviour as `gh` / `gcloud` / `firebase` keychain storage. We deliberately do\n * NOT mirror the secret to a plaintext file as a fallback: that would defeat the\n * entire point of keychain storage (keeping the secret off plaintext disk).\n */\nimport { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n chmodSync,\n rmSync,\n} from 'node:fs';\n\nimport { keychainAvailable, keychainGet, keychainSet, keychainDelete } from './keychain.js';\n\nexport interface StoredCredential {\n /**\n * Firebase refresh token (long-lived; exchanged for short-lived ID tokens).\n * OPTIONAL since Inc 3b.4b: once a scoped `vo_credential` is minted, the raw\n * refresh token is dropped, so a stored credential may carry ONLY the vocred_.\n */\n readonly refresh_token?: string;\n /** Firebase Web API key (PUBLIC) needed for the securetoken refresh exchange. */\n readonly api_key?: string;\n /**\n * Scoped, revocable VO credential (`vocred_`) minted by the control-plane\n * (Inc 3b.4b). Preferred over the raw refresh token; lets the client present a\n * revocable, server-side credential instead of the Firebase refresh token.\n */\n readonly vo_credential?: string;\n /** ISO-8601 expiry of `vo_credential` (the client re-logs-in past this). */\n readonly vo_credential_expires_at?: string;\n /** The signed-in operator email (diagnostics only). */\n readonly email?: string;\n /** ISO timestamp the credential was stored. */\n readonly stored_at?: string;\n}\n\n/**\n * Pluggable OS-keychain backend. Defaults to the real `@napi-rs/keyring` wrapper;\n * tests inject a deterministic fake so they never touch the host keychain.\n */\nexport interface KeychainBackend {\n available(): boolean;\n get(): string | null;\n set(secret: string): boolean;\n delete(): boolean;\n}\n\nconst realKeychain: KeychainBackend = {\n available: keychainAvailable,\n get: keychainGet,\n set: keychainSet,\n delete: keychainDelete,\n};\n\n/** Human-readable \"location\" returned when the credential was stored in the OS keychain. */\nexport const KEYCHAIN_LOCATION = 'OS keychain (service \"vo-mcp\")';\n\n/** Resolve the credentials file path (env override \u2192 XDG-ish default under home). */\nexport function credentialPath(env: Readonly<Record<string, string | undefined>> = process.env): string {\n const override = env['VO_MCP_CREDENTIALS_PATH']?.trim();\n if (override) return override;\n return join(homedir(), '.config', 'vo-mcp', 'credentials.json');\n}\n\n/**\n * Whether the keychain should be consulted at all (read OR write). False when the\n * native backend is unavailable or `VO_MCP_DISABLE_KEYCHAIN` is set (CI/headless).\n */\nfunction keychainEnabled(\n env: Readonly<Record<string, string | undefined>>,\n keychain: KeychainBackend,\n): boolean {\n const disabled = (env['VO_MCP_DISABLE_KEYCHAIN'] ?? '').trim().toLowerCase();\n if (disabled === '1' || disabled === 'true' || disabled === 'yes') return false;\n return keychain.available();\n}\n\n/** Parse + validate a stored credential blob. Returns null on any problem (never throws). */\nfunction deserialize(raw: string): StoredCredential | null {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredCredential>;\n const refresh = typeof parsed.refresh_token === 'string' ? parsed.refresh_token.trim() : '';\n const apiKey = typeof parsed.api_key === 'string' ? parsed.api_key.trim() : '';\n const voCred = typeof parsed.vo_credential === 'string' ? parsed.vo_credential.trim() : '';\n // Valid if it carries a scoped vocred_ OR a full Firebase refresh pair.\n if (!voCred && (!refresh || !apiKey)) return null;\n return {\n ...(refresh ? { refresh_token: refresh } : {}),\n ...(apiKey ? { api_key: apiKey } : {}),\n ...(voCred ? { vo_credential: voCred } : {}),\n ...(typeof parsed.vo_credential_expires_at === 'string' ? { vo_credential_expires_at: parsed.vo_credential_expires_at } : {}),\n ...(typeof parsed.email === 'string' ? { email: parsed.email } : {}),\n ...(typeof parsed.stored_at === 'string' ? { stored_at: parsed.stored_at } : {}),\n };\n } catch {\n return null;\n }\n}\n\nfunction readFromFile(env: Readonly<Record<string, string | undefined>>): StoredCredential | null {\n try {\n const p = credentialPath(env);\n if (!existsSync(p)) return null;\n return deserialize(readFileSync(p, 'utf8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Read the stored credential, or `null` if absent/unreadable/invalid (never\n * throws). Consults an ENABLED keychain first (regardless of the write-target\n * flags, so a credential written to the keychain is found even if\n * `VO_MCP_CREDENTIALS_PATH` is later set), then the 0600 file.\n */\nexport function readStoredCredential(\n env: Readonly<Record<string, string | undefined>> = process.env,\n keychain: KeychainBackend = realKeychain,\n): StoredCredential | null {\n if (keychainEnabled(env, keychain)) {\n const raw = keychain.get();\n const fromKeychain = raw ? deserialize(raw) : null;\n if (fromKeychain) return fromKeychain;\n }\n return readFromFile(env);\n}\n\nfunction deleteFile(env: Readonly<Record<string, string | undefined>>): void {\n try {\n rmSync(credentialPath(env), { force: true });\n } catch {\n /* best-effort */\n }\n}\n\nfunction writeToFile(\n payload: StoredCredential,\n env: Readonly<Record<string, string | undefined>>,\n): string {\n const p = credentialPath(env);\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, `${JSON.stringify(payload, null, 2)}\\n`, { mode: 0o600 });\n // Best-effort tighten (no-op / throws on some Windows filesystems \u2014 ignore).\n try {\n chmodSync(p, 0o600);\n } catch {\n /* best-effort */\n }\n return p;\n}\n\n/**\n * Persist the credential. Prefers the OS keychain (secret never hits plaintext\n * disk); otherwise writes the 0600 file. Writing to one store CLEARS the other\n * (single source of truth \u2014 no stale shadow, no lingering plaintext). Returns the\n * location it was stored (`KEYCHAIN_LOCATION` or the file path).\n */\nexport function writeStoredCredential(\n cred: StoredCredential,\n storedAt: string,\n env: Readonly<Record<string, string | undefined>> = process.env,\n keychain: KeychainBackend = realKeychain,\n): string {\n const payload: StoredCredential = {\n ...(cred.refresh_token ? { refresh_token: cred.refresh_token } : {}),\n ...(cred.api_key ? { api_key: cred.api_key } : {}),\n ...(cred.vo_credential ? { vo_credential: cred.vo_credential } : {}),\n ...(cred.vo_credential_expires_at ? { vo_credential_expires_at: cred.vo_credential_expires_at } : {}),\n ...(cred.email ? { email: cred.email } : {}),\n stored_at: cred.stored_at ?? storedAt,\n };\n if (keychainEnabled(env, keychain) && keychain.set(JSON.stringify(payload))) {\n // Stored in the keychain \u2192 clear any stale plaintext file so the secret\n // doesn't linger on disk and can't shadow the keychain on read.\n deleteFile(env);\n return KEYCHAIN_LOCATION;\n }\n const p = writeToFile(payload, env);\n // Stored in the file \u2192 clear any stale keychain entry so it can't shadow the\n // newer file credential on read.\n if (keychainEnabled(env, keychain)) keychain.delete();\n return p;\n}\n", "/**\n * Optional OS-keychain backend for the thin-client credential store (Increment\n * 3b.3 \u2014 `docs/vo/vo-command-center-inc3b-login-design-2026-06-05.md` \u00A75/\u00A76).\n *\n * Loads `@napi-rs/keyring` at runtime via `createRequire`, so it is a TRUE\n * optional dependency: if the native module is absent or fails to load\n * (unsupported platform, prebuilt binary missing, headless CI), every function\n * degrades to a no-op and the caller (`credential-store.ts`) falls back to the\n * 0600 file store. `@napi-rs/keyring`'s `Entry` API is SYNCHRONOUS, so the\n * credential store stays synchronous \u2014 no async ripple into the Inc-3a token\n * source that reads it.\n *\n * Why `createRequire` and not a static/dynamic `import`: a static import would\n * make the native module a HARD dependency (a missing prebuilt would crash the\n * MCP at startup); a dynamic `import()` is async (would force the whole read\n * path async). `createRequire(...)` inside a try/catch loads it lazily and\n * synchronously, and a load failure is just \"keychain unavailable\".\n */\nimport { createRequire } from 'node:module';\n\n/** Keychain service + account the single refresh credential is stored under. */\nconst SERVICE = 'vo-mcp';\nconst ACCOUNT = 'refresh-credential';\n\ninterface KeyringEntry {\n getPassword(): string | null;\n setPassword(password: string): void;\n deletePassword(): boolean;\n}\ninterface KeyringModule {\n Entry: new (service: string, account: string) => KeyringEntry;\n}\n\n// undefined = not yet attempted; null = attempted and unavailable.\nlet cached: KeyringModule | null | undefined;\n\nfunction loadKeyring(): KeyringModule | null {\n if (cached !== undefined) return cached;\n try {\n const req = createRequire(import.meta.url);\n const mod = req('@napi-rs/keyring') as Partial<KeyringModule>;\n cached = mod && typeof mod.Entry === 'function' ? (mod as KeyringModule) : null;\n } catch {\n cached = null;\n }\n return cached;\n}\n\n/** True when the OS keychain backend is usable in this runtime. */\nexport function keychainAvailable(): boolean {\n return loadKeyring() !== null;\n}\n\n/** Read the raw stored secret string from the OS keychain, or null. Never throws. */\nexport function keychainGet(): string | null {\n const k = loadKeyring();\n if (!k) return null;\n try {\n return new k.Entry(SERVICE, ACCOUNT).getPassword();\n } catch {\n return null;\n }\n}\n\n/** Store the raw secret string in the OS keychain. Returns true on success. Never throws. */\nexport function keychainSet(secret: string): boolean {\n const k = loadKeyring();\n if (!k) return false;\n try {\n new k.Entry(SERVICE, ACCOUNT).setPassword(secret);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Delete the stored secret from the OS keychain. Returns true if an entry was\n * removed. Never throws \u2014 a no-op (and `false`) when the backend is unavailable\n * or the entry is absent. Used to keep ONE source of truth: when the credential\n * is (re)written to the file, any stale keychain entry is cleared so it can't\n * shadow the newer file credential on read (and vice-versa).\n */\nexport function keychainDelete(): boolean {\n const k = loadKeyring();\n if (!k) return false;\n try {\n return new k.Entry(SERVICE, ACCOUNT).deletePassword();\n } catch {\n return false;\n }\n}\n\n/** Test-only seam to reset the memoised module load. */\nexport function __resetKeychainCache(): void {\n cached = undefined;\n}\n", "#!/usr/bin/env node\n/**\n * `vo-mcp pair` CLI entry point.\n *\n * Device-code pairing \u2014 shows a short code to enter at <dashboard>/pair, waits\n * for the friend to authorize in their browser, then stores the minted\n * credential. No browser loopback, no token paste.\n */\nimport { runPairing } from './cloud/pairing.js';\n\nasync function main(): Promise<void> {\n const log = (m: string): void => console.error(m);\n try {\n const result = await runPairing({ env: process.env, log });\n log('');\n log(`\u2713 Paired! Credential stored at: ${result.credentialPath}`);\n log('');\n log('Next steps:');\n log(' 1. Start your runner: vo-mcp runner');\n log(' 2. Dispatch agents from: https://algosuite.ai/algohq');\n } catch (err: unknown) {\n log(`\u2717 Pairing failed: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n}\n\nmain().catch((err: unknown) => {\n console.error('[vo-mcp pair] fatal:', err);\n process.exit(1);\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Device-code pairing for the BYO runner (M1) \u2014 the no-terminal-token onboarding.\n *\n * The runner calls the control-plane `pair/initiate`, shows the friend a short\n * `code` to type at `<dashboard>/pair`, then polls `pair/poll` with its SECRET\n * `poll_token` until the friend authorizes \u2014 at which point it receives a freshly\n * minted `vocred_` and stores it in the OS keychain (via writeStoredCredential).\n *\n * The friend's AI key NEVER flows through here \u2014 this only obtains the\n * control-plane credential the runner needs to claim THEIR tasks.\n */\nimport { hostname, platform } from 'node:os';\n\nimport { writeStoredCredential, type StoredCredential } from './credential-store.js';\n\nexport const DEFAULT_CONTROL_PLANE_URL = 'https://vo-control-plane-bzjphrajaq-uc.a.run.app';\nexport const DEFAULT_DASHBOARD_URL = 'https://algosuite.ai';\n\nexport interface RunPairingDeps {\n readonly env?: Record<string, string | undefined>;\n readonly log?: (message: string) => void;\n readonly fetchImpl?: typeof fetch;\n readonly sleep?: (ms: number) => Promise<void>;\n readonly now?: () => Date;\n /** Test seam: capture the stored credential instead of touching the real keychain. */\n readonly store?: (cred: StoredCredential, storedAtIso: string) => string;\n}\n\nexport interface PairingResult {\n readonly credentialPath: string;\n readonly expires_at: string;\n}\n\n/** ABCD-EFGH grouping for an 8-char code (easier to read aloud / type). */\nexport function formatPairingCode(code: string): string {\n return code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;\n}\n\nasync function readJson(res: { json: () => Promise<unknown> }): Promise<Record<string, unknown>> {\n const body = await res.json().catch(() => ({}));\n return body && typeof body === 'object' ? (body as Record<string, unknown>) : {};\n}\n\n/**\n * Run the full pairing handshake. Resolves once a credential is stored; rejects\n * with a friendly message on expiry / consumption / fatal transport error.\n */\nexport async function runPairing(deps: RunPairingDeps = {}): Promise<PairingResult> {\n const env = deps.env ?? process.env;\n const log = deps.log ?? ((m: string) => console.error(m));\n const fetchImpl = deps.fetchImpl ?? fetch;\n const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n const now = deps.now ?? (() => new Date());\n const store = deps.store ?? ((cred: StoredCredential, iso: string) => writeStoredCredential(cred, iso, env));\n\n const controlPlaneUrl = env['VO_CONTROL_PLANE_URL']?.trim() || DEFAULT_CONTROL_PLANE_URL;\n const dashboardUrl = env['VO_DASHBOARD_URL']?.trim() || DEFAULT_DASHBOARD_URL;\n const deviceLabel = `${platform()} on ${hostname()}`.slice(0, 120);\n\n // 1. initiate \u2014 get a display code + a secret poll token.\n const initRes = await fetchImpl(`${controlPlaneUrl}/api/v1/pair/initiate`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ device_label: deviceLabel }),\n });\n if (!initRes.ok) throw new Error(`Could not start pairing (server ${initRes.status}).`);\n const init = await readJson(initRes);\n const code = String(init['code'] ?? '');\n const pollToken = String(init['poll_token'] ?? '');\n if (!code || !pollToken) throw new Error('Pairing service returned an incomplete response.');\n const intervalMs = (Number(init['poll_interval_seconds']) || 5) * 1000;\n const expiresAtMs = new Date(String(init['expires_at'] ?? '')).getTime();\n\n // 2. show the friend what to do.\n log('');\n log(' To connect this runner, open this page in your browser:');\n log(` ${dashboardUrl}/pair`);\n log(' and enter this code:');\n log('');\n log(` ${formatPairingCode(code)}`);\n log('');\n log(' Your Anthropic key never leaves this computer. Waiting for you to authorize\u2026');\n\n // 3. poll with the SECRET token until authorized / expired.\n for (;;) {\n if (Number.isFinite(expiresAtMs) && now().getTime() >= expiresAtMs) {\n throw new Error('The pairing code expired before it was authorized. Run `vo-mcp pair` again.');\n }\n await sleep(intervalMs);\n const pollRes = await fetchImpl(`${controlPlaneUrl}/api/v1/pair/poll`, {\n method: 'GET',\n headers: { 'x-vo-poll-token': pollToken },\n });\n if (pollRes.status === 404) {\n throw new Error('The pairing expired. Run `vo-mcp pair` again.');\n }\n if (pollRes.status === 410) {\n throw new Error('This code was already used. Run `vo-mcp pair` again.');\n }\n if (!pollRes.ok) {\n // Transient (rate limit / blip) \u2014 keep waiting.\n continue;\n }\n const body = await readJson(pollRes);\n if (body['status'] === 'pending') continue;\n if (body['status'] === 'authorized' && typeof body['vo_credential'] === 'string') {\n const credentialPath = store(\n {\n vo_credential: body['vo_credential'] as string,\n ...(typeof body['expires_at'] === 'string'\n ? { vo_credential_expires_at: body['expires_at'] as string }\n : {}),\n },\n now().toISOString(),\n );\n return { credentialPath, expires_at: String(body['expires_at'] ?? '') };\n }\n throw new Error('Unexpected response from the pairing service.');\n }\n}\n", "/**\n * Local credential store for the thin-client `vo-mcp login` flow (Increment 3b,\n * Option A \u2014 `docs/vo/vo-command-center-inc3b-login-design-2026-06-05.md`).\n *\n * Persists the per-user Firebase refresh token (the user's OWN credential, never\n * the god-token, never model keys) captured by `login`, so the auto-refreshing\n * token source (Inc 3a) can mint fresh ID tokens across MCP restarts.\n *\n * Storage precedence (Inc 3b.3):\n * 1. **OS keychain** (Windows Credential Manager / macOS Keychain / libsecret)\n * via the optional `@napi-rs/keyring` backend (`keychain.ts`). The DEFAULT\n * when available \u2014 the secret never lands in plaintext on disk.\n * 2. **0600 file** at `$VO_MCP_CREDENTIALS_PATH` or `~/.config/vo-mcp/credentials.json`.\n * The fallback when the keychain is unavailable or disabled\n * (`VO_MCP_DISABLE_KEYCHAIN`). `VO_MCP_CREDENTIALS_PATH` only sets the file\n * LOCATION; force file storage with `VO_MCP_DISABLE_KEYCHAIN`.\n *\n * `env`-supplied tokens (`VO_USER_REFRESH_TOKEN`, etc.) still win over BOTH\n * stores \u2014 that precedence lives upstream in `auth-token-source.ts`.\n *\n * **Single source of truth.** The credential lives in EITHER the keychain OR the\n * file, never both: a write to one store CLEARS the other, so a stale entry can\n * never shadow the current credential on read, and the secret never lingers in\n * plaintext after a migration to the keychain.\n *\n * **Keychain durability.** A keychain-stored credential is only readable while\n * the `@napi-rs/keyring` native module loads. If the module later becomes\n * unavailable (an ABI break across a Node upgrade, a corrupted install), the\n * credential can't be read and the user re-runs `vo-mcp login` \u2014 the same\n * behaviour as `gh` / `gcloud` / `firebase` keychain storage. We deliberately do\n * NOT mirror the secret to a plaintext file as a fallback: that would defeat the\n * entire point of keychain storage (keeping the secret off plaintext disk).\n */\nimport { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n chmodSync,\n rmSync,\n} from 'node:fs';\n\nimport { keychainAvailable, keychainGet, keychainSet, keychainDelete } from './keychain.js';\n\nexport interface StoredCredential {\n /**\n * Firebase refresh token (long-lived; exchanged for short-lived ID tokens).\n * OPTIONAL since Inc 3b.4b: once a scoped `vo_credential` is minted, the raw\n * refresh token is dropped, so a stored credential may carry ONLY the vocred_.\n */\n readonly refresh_token?: string;\n /** Firebase Web API key (PUBLIC) needed for the securetoken refresh exchange. */\n readonly api_key?: string;\n /**\n * Scoped, revocable VO credential (`vocred_`) minted by the control-plane\n * (Inc 3b.4b). Preferred over the raw refresh token; lets the client present a\n * revocable, server-side credential instead of the Firebase refresh token.\n */\n readonly vo_credential?: string;\n /** ISO-8601 expiry of `vo_credential` (the client re-logs-in past this). */\n readonly vo_credential_expires_at?: string;\n /** The signed-in operator email (diagnostics only). */\n readonly email?: string;\n /** ISO timestamp the credential was stored. */\n readonly stored_at?: string;\n}\n\n/**\n * Pluggable OS-keychain backend. Defaults to the real `@napi-rs/keyring` wrapper;\n * tests inject a deterministic fake so they never touch the host keychain.\n */\nexport interface KeychainBackend {\n available(): boolean;\n get(): string | null;\n set(secret: string): boolean;\n delete(): boolean;\n}\n\nconst realKeychain: KeychainBackend = {\n available: keychainAvailable,\n get: keychainGet,\n set: keychainSet,\n delete: keychainDelete,\n};\n\n/** Human-readable \"location\" returned when the credential was stored in the OS keychain. */\nexport const KEYCHAIN_LOCATION = 'OS keychain (service \"vo-mcp\")';\n\n/** Resolve the credentials file path (env override \u2192 XDG-ish default under home). */\nexport function credentialPath(env: Readonly<Record<string, string | undefined>> = process.env): string {\n const override = env['VO_MCP_CREDENTIALS_PATH']?.trim();\n if (override) return override;\n return join(homedir(), '.config', 'vo-mcp', 'credentials.json');\n}\n\n/**\n * Whether the keychain should be consulted at all (read OR write). False when the\n * native backend is unavailable or `VO_MCP_DISABLE_KEYCHAIN` is set (CI/headless).\n */\nfunction keychainEnabled(\n env: Readonly<Record<string, string | undefined>>,\n keychain: KeychainBackend,\n): boolean {\n const disabled = (env['VO_MCP_DISABLE_KEYCHAIN'] ?? '').trim().toLowerCase();\n if (disabled === '1' || disabled === 'true' || disabled === 'yes') return false;\n return keychain.available();\n}\n\n/** Parse + validate a stored credential blob. Returns null on any problem (never throws). */\nfunction deserialize(raw: string): StoredCredential | null {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredCredential>;\n const refresh = typeof parsed.refresh_token === 'string' ? parsed.refresh_token.trim() : '';\n const apiKey = typeof parsed.api_key === 'string' ? parsed.api_key.trim() : '';\n const voCred = typeof parsed.vo_credential === 'string' ? parsed.vo_credential.trim() : '';\n // Valid if it carries a scoped vocred_ OR a full Firebase refresh pair.\n if (!voCred && (!refresh || !apiKey)) return null;\n return {\n ...(refresh ? { refresh_token: refresh } : {}),\n ...(apiKey ? { api_key: apiKey } : {}),\n ...(voCred ? { vo_credential: voCred } : {}),\n ...(typeof parsed.vo_credential_expires_at === 'string' ? { vo_credential_expires_at: parsed.vo_credential_expires_at } : {}),\n ...(typeof parsed.email === 'string' ? { email: parsed.email } : {}),\n ...(typeof parsed.stored_at === 'string' ? { stored_at: parsed.stored_at } : {}),\n };\n } catch {\n return null;\n }\n}\n\nfunction readFromFile(env: Readonly<Record<string, string | undefined>>): StoredCredential | null {\n try {\n const p = credentialPath(env);\n if (!existsSync(p)) return null;\n return deserialize(readFileSync(p, 'utf8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Read the stored credential, or `null` if absent/unreadable/invalid (never\n * throws). Consults an ENABLED keychain first (regardless of the write-target\n * flags, so a credential written to the keychain is found even if\n * `VO_MCP_CREDENTIALS_PATH` is later set), then the 0600 file.\n */\nexport function readStoredCredential(\n env: Readonly<Record<string, string | undefined>> = process.env,\n keychain: KeychainBackend = realKeychain,\n): StoredCredential | null {\n if (keychainEnabled(env, keychain)) {\n const raw = keychain.get();\n const fromKeychain = raw ? deserialize(raw) : null;\n if (fromKeychain) return fromKeychain;\n }\n return readFromFile(env);\n}\n\n/** Read only the OS keychain; never fall back to env-selected plaintext files. */\nexport function readStoredCredentialKeychainOnly(\n env: Readonly<Record<string, string | undefined>> = process.env,\n keychain: KeychainBackend = realKeychain,\n): StoredCredential | null {\n if (!keychainEnabled(env, keychain)) return null;\n const raw = keychain.get();\n return raw ? deserialize(raw) : null;\n}\n\nfunction deleteFile(env: Readonly<Record<string, string | undefined>>): void {\n try {\n rmSync(credentialPath(env), { force: true });\n } catch {\n /* best-effort */\n }\n}\n\nfunction writeToFile(\n payload: StoredCredential,\n env: Readonly<Record<string, string | undefined>>,\n): string {\n const p = credentialPath(env);\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, `${JSON.stringify(payload, null, 2)}\\n`, { mode: 0o600 });\n // Best-effort tighten (no-op / throws on some Windows filesystems \u2014 ignore).\n try {\n chmodSync(p, 0o600);\n } catch {\n /* best-effort */\n }\n return p;\n}\n\n/**\n * Persist the credential. Prefers the OS keychain (secret never hits plaintext\n * disk); otherwise writes the 0600 file. Writing to one store CLEARS the other\n * (single source of truth \u2014 no stale shadow, no lingering plaintext). Returns the\n * location it was stored (`KEYCHAIN_LOCATION` or the file path).\n */\nexport function writeStoredCredential(\n cred: StoredCredential,\n storedAt: string,\n env: Readonly<Record<string, string | undefined>> = process.env,\n keychain: KeychainBackend = realKeychain,\n): string {\n const payload: StoredCredential = {\n ...(cred.refresh_token ? { refresh_token: cred.refresh_token } : {}),\n ...(cred.api_key ? { api_key: cred.api_key } : {}),\n ...(cred.vo_credential ? { vo_credential: cred.vo_credential } : {}),\n ...(cred.vo_credential_expires_at ? { vo_credential_expires_at: cred.vo_credential_expires_at } : {}),\n ...(cred.email ? { email: cred.email } : {}),\n stored_at: cred.stored_at ?? storedAt,\n };\n if (keychainEnabled(env, keychain) && keychain.set(JSON.stringify(payload))) {\n // Stored in the keychain \u2192 clear any stale plaintext file so the secret\n // doesn't linger on disk and can't shadow the keychain on read.\n deleteFile(env);\n return KEYCHAIN_LOCATION;\n }\n const p = writeToFile(payload, env);\n // Stored in the file \u2192 clear any stale keychain entry so it can't shadow the\n // newer file credential on read.\n if (keychainEnabled(env, keychain)) keychain.delete();\n return p;\n}\n", "/**\n * Optional OS-keychain backend for the thin-client credential store (Increment\n * 3b.3 \u2014 `docs/vo/vo-command-center-inc3b-login-design-2026-06-05.md` \u00A75/\u00A76).\n *\n * Loads `@napi-rs/keyring` at runtime via `createRequire`, so it is a TRUE\n * optional dependency: if the native module is absent or fails to load\n * (unsupported platform, prebuilt binary missing, headless CI), every function\n * degrades to a no-op and the caller (`credential-store.ts`) falls back to the\n * 0600 file store. `@napi-rs/keyring`'s `Entry` API is SYNCHRONOUS, so the\n * credential store stays synchronous \u2014 no async ripple into the Inc-3a token\n * source that reads it.\n *\n * Why `createRequire` and not a static/dynamic `import`: a static import would\n * make the native module a HARD dependency (a missing prebuilt would crash the\n * MCP at startup); a dynamic `import()` is async (would force the whole read\n * path async). `createRequire(...)` inside a try/catch loads it lazily and\n * synchronously, and a load failure is just \"keychain unavailable\".\n */\nimport { createRequire } from 'node:module';\n\n/** Keychain service + account the single refresh credential is stored under. */\nconst SERVICE = 'vo-mcp';\nconst ACCOUNT = 'refresh-credential';\n\ninterface KeyringEntry {\n getPassword(): string | null;\n setPassword(password: string): void;\n deletePassword(): boolean;\n}\ninterface KeyringModule {\n Entry: new (service: string, account: string) => KeyringEntry;\n}\n\n// undefined = not yet attempted; null = attempted and unavailable.\nlet cached: KeyringModule | null | undefined;\n\nfunction loadKeyring(): KeyringModule | null {\n if (cached !== undefined) return cached;\n try {\n const req = createRequire(import.meta.url);\n const mod = req('@napi-rs/keyring') as Partial<KeyringModule>;\n cached = mod && typeof mod.Entry === 'function' ? (mod as KeyringModule) : null;\n } catch {\n cached = null;\n }\n return cached;\n}\n\n/** True when the OS keychain backend is usable in this runtime. */\nexport function keychainAvailable(): boolean {\n return loadKeyring() !== null;\n}\n\n/** Read the raw stored secret string from the OS keychain, or null. Never throws. */\nexport function keychainGet(): string | null {\n const k = loadKeyring();\n if (!k) return null;\n try {\n return new k.Entry(SERVICE, ACCOUNT).getPassword();\n } catch {\n return null;\n }\n}\n\n/** Store the raw secret string in the OS keychain. Returns true on success. Never throws. */\nexport function keychainSet(secret: string): boolean {\n const k = loadKeyring();\n if (!k) return false;\n try {\n new k.Entry(SERVICE, ACCOUNT).setPassword(secret);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Delete the stored secret from the OS keychain. Returns true if an entry was\n * removed. Never throws \u2014 a no-op (and `false`) when the backend is unavailable\n * or the entry is absent. Used to keep ONE source of truth: when the credential\n * is (re)written to the file, any stale keychain entry is cleared so it can't\n * shadow the newer file credential on read (and vice-versa).\n */\nexport function keychainDelete(): boolean {\n const k = loadKeyring();\n if (!k) return false;\n try {\n return new k.Entry(SERVICE, ACCOUNT).deletePassword();\n } catch {\n return false;\n }\n}\n\n/** Test-only seam to reset the memoised module load. */\nexport function __resetKeychainCache(): void {\n cached = undefined;\n}\n", "#!/usr/bin/env node\n/**\n * `vo-mcp pair` CLI entry point.\n *\n * Device-code pairing \u2014 shows a short code to enter at <dashboard>/pair, waits\n * for the friend to authorize in their browser, then stores the minted\n * credential. No browser loopback, no token paste.\n */\nimport { runPairing } from './cloud/pairing.js';\n\nasync function main(): Promise<void> {\n const log = (m: string): void => console.error(m);\n try {\n const result = await runPairing({ env: process.env, log });\n log('');\n log(`\u2713 Paired! Credential stored at: ${result.credentialPath}`);\n log('');\n log('Next steps:');\n log(' 1. Start your runner: vo-mcp runner');\n log(' 2. Dispatch agents from: https://algosuite.ai/algohq');\n } catch (err: unknown) {\n log(`\u2717 Pairing failed: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n}\n\nmain().catch((err: unknown) => {\n console.error('[vo-mcp pair] fatal:', err);\n process.exit(1);\n});\n"],
5
+ "mappings": "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",
6
6
  "names": ["credentialPath"]
7
7
  }
@@ -74,6 +74,7 @@ __export(credential_store_exports, {
74
74
  KEYCHAIN_LOCATION: () => KEYCHAIN_LOCATION,
75
75
  credentialPath: () => credentialPath,
76
76
  readStoredCredential: () => readStoredCredential,
77
+ readStoredCredentialKeychainOnly: () => readStoredCredentialKeychainOnly,
77
78
  writeStoredCredential: () => writeStoredCredential
78
79
  });
79
80
  import { homedir } from "node:os";
@@ -132,6 +133,11 @@ function readStoredCredential(env2 = process.env, keychain = realKeychain) {
132
133
  }
133
134
  return readFromFile(env2);
134
135
  }
136
+ function readStoredCredentialKeychainOnly(env2 = process.env, keychain = realKeychain) {
137
+ if (!keychainEnabled(env2, keychain)) return null;
138
+ const raw = keychain.get();
139
+ return raw ? deserialize(raw) : null;
140
+ }
135
141
  function deleteFile(env2) {
136
142
  try {
137
143
  rmSync(credentialPath(env2), { force: true });
@@ -2125,22 +2131,41 @@ async function resolveBearer(env2) {
2125
2131
  function createControlPlaneClient({
2126
2132
  baseUrl = process.env.VO_CONTROL_PLANE_URL || "",
2127
2133
  env: env2 = process.env,
2128
- fetchImpl = fetch
2134
+ fetchImpl = fetch,
2135
+ heartbeatTimeoutMs = Math.min(
2136
+ Math.max(Number(env2.VO_CODE_RUNNER_HEARTBEAT_TIMEOUT_MS) || 15e3, 1e3),
2137
+ 6e4
2138
+ )
2129
2139
  } = {}) {
2130
2140
  if (!baseUrl) {
2131
2141
  throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
2132
2142
  }
2133
2143
  const root = baseUrl.replace(/\/+$/, "");
2134
- async function req(method, path16, body) {
2144
+ async function req(method, path16, body, { timeoutMs } = {}) {
2135
2145
  const bearer = await resolveBearer(env2);
2136
- return fetchImpl(`${root}${path16}`, {
2146
+ const controller = timeoutMs ? new AbortController() : null;
2147
+ let timeoutId;
2148
+ const request = Promise.resolve(fetchImpl(`${root}${path16}`, {
2137
2149
  method,
2138
2150
  headers: {
2139
2151
  "content-type": "application/json",
2140
2152
  authorization: `Bearer ${bearer}`
2141
2153
  },
2142
- body: body === void 0 ? void 0 : JSON.stringify(body)
2154
+ body: body === void 0 ? void 0 : JSON.stringify(body),
2155
+ ...controller ? { signal: controller.signal } : {}
2156
+ }));
2157
+ if (!timeoutMs) return request;
2158
+ const timeout = new Promise((_, reject) => {
2159
+ timeoutId = setTimeout(() => {
2160
+ controller.abort();
2161
+ reject(new Error(`control-plane ${path16} timed out after ${timeoutMs}ms`));
2162
+ }, timeoutMs);
2143
2163
  });
2164
+ try {
2165
+ return await Promise.race([request, timeout]);
2166
+ } finally {
2167
+ clearTimeout(timeoutId);
2168
+ }
2144
2169
  }
2145
2170
  return {
2146
2171
  /**
@@ -2156,6 +2181,10 @@ function createControlPlaneClient({
2156
2181
  if (Array.isArray(operatorIds) && operatorIds.length > 0) body.operator_ids = operatorIds;
2157
2182
  if (session.runnerInstanceId) body.runner_instance_id = session.runnerInstanceId;
2158
2183
  if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
2184
+ if (session.defaultAgent) body.default_agent = session.defaultAgent;
2185
+ if (Array.isArray(session.availableAgents)) {
2186
+ body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
2187
+ }
2159
2188
  const res = await req("POST", "/api/v1/code-task/claim", body);
2160
2189
  if (res.status === 401) {
2161
2190
  cachedFirebaseToken = null;
@@ -2299,13 +2328,20 @@ function createControlPlaneClient({
2299
2328
  * authenticated operator so the web shows a TRUE "runner online" signal.
2300
2329
  * Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
2301
2330
  */
2302
- async postHeartbeat({ runnerId, operatorId, uptimeSec, activeTasks, version, daemonVersion, servedRepos, servedOperators, availableAgents, accountUsage }) {
2331
+ async postHeartbeat({ runnerId, runnerInstanceId, operatorId, uptimeSec, activeTasks, version, daemonVersion, defaultAgent, supervisorInstanceId, supervisorVersion, supervisorCapabilities, servedRepos, servedOperators, availableAgents, accountUsage }) {
2303
2332
  const body = { runner_id: runnerId };
2333
+ if (runnerInstanceId) body.runner_instance_id = runnerInstanceId;
2304
2334
  if (operatorId) body.operator_id = operatorId;
2305
2335
  if (typeof uptimeSec === "number") body.uptime_sec = uptimeSec;
2306
2336
  if (typeof activeTasks === "number") body.active_tasks = activeTasks;
2307
2337
  if (version) body.version = version;
2308
2338
  if (daemonVersion) body.daemon_version = daemonVersion;
2339
+ if (defaultAgent) body.default_agent = defaultAgent;
2340
+ if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
2341
+ if (supervisorVersion) body.supervisor_version = supervisorVersion;
2342
+ if (Array.isArray(supervisorCapabilities) && supervisorCapabilities.length > 0) {
2343
+ body.supervisor_capabilities = supervisorCapabilities;
2344
+ }
2309
2345
  if (Array.isArray(servedRepos) && servedRepos.length > 0) body.served_repos = servedRepos;
2310
2346
  if (Array.isArray(servedOperators) && servedOperators.length > 0) {
2311
2347
  body.served_operator_ids = servedOperators;
@@ -2316,7 +2352,9 @@ function createControlPlaneClient({
2316
2352
  if (Array.isArray(accountUsage) && accountUsage.length > 0) {
2317
2353
  body.account_usage = accountUsage;
2318
2354
  }
2319
- const res = await req("POST", "/api/v1/runner/heartbeat", body);
2355
+ const res = await req("POST", "/api/v1/runner/heartbeat", body, {
2356
+ timeoutMs: heartbeatTimeoutMs
2357
+ });
2320
2358
  if (res.status === 401) {
2321
2359
  cachedFirebaseToken = null;
2322
2360
  throw new Error("heartbeat unauthorized (401)");
@@ -2324,10 +2362,27 @@ function createControlPlaneClient({
2324
2362
  if (!res.ok) throw new Error(`heartbeat failed: HTTP ${res.status}`);
2325
2363
  return true;
2326
2364
  },
2365
+ /** Read the server-authoritative heartbeat ledger without mutating it. */
2366
+ async getRunnerStatus({ operatorId } = {}) {
2367
+ const query = operatorId ? `?operator_id=${encodeURIComponent(operatorId)}` : "";
2368
+ const res = await req("GET", `/api/v1/runner/status${query}`, void 0, {
2369
+ timeoutMs: heartbeatTimeoutMs
2370
+ });
2371
+ if (res.status === 401) {
2372
+ cachedFirebaseToken = null;
2373
+ throw new Error("runner status unauthorized (401)");
2374
+ }
2375
+ if (!res.ok) throw new Error(`runner status failed: HTTP ${res.status}`);
2376
+ const body = await res.json();
2377
+ return Array.isArray(body?.runners) ? body.runners : [];
2378
+ },
2327
2379
  /** Poll one authenticated runner's durable Mission Control action queue. */
2328
- async pollRunnerControl({ runnerId, operatorId }) {
2380
+ async pollRunnerControl({ runnerId, operatorId, supervisorInstanceId, supervisorVersion, capabilities }) {
2329
2381
  const body = { runner_id: runnerId };
2330
2382
  if (operatorId) body.operator_id = operatorId;
2383
+ if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
2384
+ if (supervisorVersion) body.supervisor_version = supervisorVersion;
2385
+ if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
2331
2386
  const res = await req("POST", "/api/v1/runner/control/poll", body);
2332
2387
  if (res.status === 401) {
2333
2388
  cachedFirebaseToken = null;
@@ -2339,9 +2394,12 @@ function createControlPlaneClient({
2339
2394
  return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
2340
2395
  },
2341
2396
  /** Acknowledge a maintenance action after the host has restarted the child. */
2342
- async completeRunnerControl(actionId, { runnerId, operatorId, status, detail }) {
2397
+ async completeRunnerControl(actionId, { runnerId, operatorId, supervisorInstanceId, supervisorVersion, capabilities, status, detail }) {
2343
2398
  const body = { runner_id: runnerId, status };
2344
2399
  if (operatorId) body.operator_id = operatorId;
2400
+ if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
2401
+ if (supervisorVersion) body.supervisor_version = supervisorVersion;
2402
+ if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
2345
2403
  if (detail) body.detail = detail;
2346
2404
  const res = await req("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
2347
2405
  if (res.status === 401) {
@@ -2423,17 +2481,55 @@ function cleanPathSegment(value) {
2423
2481
  const trimmed = String(value || "").trim();
2424
2482
  return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
2425
2483
  }
2484
+ function envValue(env2, name) {
2485
+ const exact = env2?.[name];
2486
+ if (typeof exact === "string") return exact.trim();
2487
+ const key = Object.keys(env2 || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
2488
+ return typeof env2?.[key] === "string" ? env2[key].trim() : "";
2489
+ }
2490
+ function userClaudeCandidates(bin, env2) {
2491
+ if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
2492
+ const userProfile = envValue(env2, "USERPROFILE");
2493
+ const appData = envValue(env2, "APPDATA") || (userProfile ? path9.join(userProfile, "AppData", "Roaming") : "");
2494
+ const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ? path9.join(userProfile, "AppData", "Local") : "");
2495
+ const candidates = [];
2496
+ if (appData) {
2497
+ const npmBin = path9.join(appData, "npm");
2498
+ candidates.push(
2499
+ path9.join(npmBin, "claude.exe"),
2500
+ path9.join(npmBin, "claude.cmd"),
2501
+ path9.join(npmBin, "claude.ps1"),
2502
+ path9.join(npmBin, "claude"),
2503
+ path9.join(npmBin, ...NATIVE_CLAUDE_PARTS)
2504
+ );
2505
+ }
2506
+ if (userProfile) candidates.push(path9.join(userProfile, ".local", "bin", "claude.exe"));
2507
+ if (localAppData) {
2508
+ candidates.push(
2509
+ path9.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
2510
+ path9.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
2511
+ );
2512
+ }
2513
+ return candidates;
2514
+ }
2426
2515
  function pathCandidates(bin, env2) {
2427
2516
  if (path9.isAbsolute(bin) || /[\\/]/u.test(bin)) {
2428
2517
  return [path9.resolve(bin)];
2429
2518
  }
2430
2519
  const extension = path9.extname(bin);
2431
- return pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path9.join(directory, bin)] : [
2520
+ const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path9.join(directory, bin)] : [
2432
2521
  path9.join(directory, `${bin}.exe`),
2433
2522
  path9.join(directory, `${bin}.cmd`),
2434
2523
  path9.join(directory, `${bin}.ps1`),
2435
2524
  path9.join(directory, bin)
2436
2525
  ]);
2526
+ const seen = /* @__PURE__ */ new Set();
2527
+ return [...fromPath, ...userClaudeCandidates(bin, env2)].filter((candidate) => {
2528
+ const key = candidate.toLowerCase();
2529
+ if (seen.has(key)) return false;
2530
+ seen.add(key);
2531
+ return true;
2532
+ });
2437
2533
  }
2438
2534
  function canonicalExistingPath(candidate, exists, canonicalize) {
2439
2535
  if (!exists(candidate)) return null;
@@ -2462,7 +2558,7 @@ function resolveWindowsClaudeExecutable({
2462
2558
  if (resolvedNative) return resolvedNative;
2463
2559
  }
2464
2560
  const error = new Error(
2465
- `Could not resolve a native claude.exe for "${requested}". Update Claude Code with npm install -g @anthropic-ai/claude-code; the HQ runner will not execute a shell-only .cmd/.ps1 shim.`
2561
+ `Could not resolve a native claude.exe for "${requested}". Install or update Claude Code with the native Windows installer (recommended) or npm install -g @anthropic-ai/claude-code; the HQ runner will not execute a shell-only .cmd/.ps1 shim.`
2466
2562
  );
2467
2563
  error.code = "ENOENT";
2468
2564
  throw error;
@@ -4779,7 +4875,7 @@ var init_dispatch_onboarding = __esm({
4779
4875
  "docs/current/virtual-office-operating-model.md",
4780
4876
  "docs/current/virtual-office-test-architect.md",
4781
4877
  "docs/current/evidence-grounded-consensus-testing.md",
4782
- "docs/vo/ADR-001-verification-oracle-not-orchestrator-2026-05-29.md (VO verifies + signs; human approves merges; NO autonomous bot-merge / headless triggers)",
4878
+ "docs/vo/ADR-001-verification-oracle-not-orchestrator-2026-05-29.md (AlgoHQ verifies + signs; human approves merges; NO autonomous bot-merge / headless triggers)",
4783
4879
  "docs/vo/vo-adr-002-two-plane-moat.md (fat secret server / thin dumb client)",
4784
4880
  "docs/vo/vo-roadmap-2026-05-26.md (the live roadmap \u2014 read its Change log tail for current state)",
4785
4881
  "the nearest scoped CLAUDE.md for any directory you edit",
@@ -5240,6 +5336,7 @@ function makeLoopTicks({
5240
5336
  env: env2,
5241
5337
  log: log3,
5242
5338
  getActive,
5339
+ runnerInstanceId,
5243
5340
  // Cached agent-availability provider (agent-availability.mjs); returns null
5244
5341
  // until the first probe completes — the heartbeat simply omits the field.
5245
5342
  getAgentAvailability = () => null,
@@ -5251,9 +5348,49 @@ function makeLoopTicks({
5251
5348
  }) {
5252
5349
  let lastSessionForward = 0;
5253
5350
  let lastHeartbeat = 0;
5351
+ let availabilityWasReady = false;
5352
+ const heartbeatState = /* @__PURE__ */ new Map();
5254
5353
  let lastResumeSchedule = 0;
5255
5354
  let resumeRunning = false;
5355
+ function enqueueHeartbeat(payload) {
5356
+ const key = payload.operatorId || "";
5357
+ let state = heartbeatState.get(key);
5358
+ if (!state) {
5359
+ state = { running: false, pending: null };
5360
+ heartbeatState.set(key, state);
5361
+ }
5362
+ return new Promise((resolve2) => {
5363
+ if (state.running) {
5364
+ if (state.pending) state.pending.waiters.push(resolve2);
5365
+ else state.pending = { payload, waiters: [resolve2] };
5366
+ state.pending.payload = payload;
5367
+ return;
5368
+ }
5369
+ const launch = (nextPayload, waiters) => {
5370
+ state.running = true;
5371
+ let request;
5372
+ try {
5373
+ request = Promise.resolve(client.postHeartbeat(nextPayload));
5374
+ } catch (error) {
5375
+ request = Promise.reject(error);
5376
+ }
5377
+ request.catch((e) => log3(`heartbeat failed: ${e.message}`)).finally(() => {
5378
+ for (const done of waiters) done();
5379
+ if (state.pending) {
5380
+ const pending = state.pending;
5381
+ state.pending = null;
5382
+ launch(pending.payload, pending.waiters);
5383
+ } else {
5384
+ state.running = false;
5385
+ heartbeatState.delete(key);
5386
+ }
5387
+ });
5388
+ };
5389
+ launch(payload, [resolve2]);
5390
+ });
5391
+ }
5256
5392
  return function tick() {
5393
+ const heartbeatCompletions = [];
5257
5394
  const now = nowFn();
5258
5395
  if (cfg.sessionForwardSec > 0 && now - lastSessionForward >= cfg.sessionForwardSec * 1e3) {
5259
5396
  lastSessionForward = now;
@@ -5264,18 +5401,29 @@ function makeLoopTicks({
5264
5401
  }).catch(() => {
5265
5402
  });
5266
5403
  }
5267
- if (now - lastHeartbeat >= HEARTBEAT_MS) {
5404
+ const availableAgents = getAgentAvailability();
5405
+ const availabilityReady = Array.isArray(availableAgents);
5406
+ const availabilityJustBecameReady = availabilityReady && !availabilityWasReady;
5407
+ availabilityWasReady = availabilityReady;
5408
+ if (now - lastHeartbeat >= HEARTBEAT_MS || availabilityJustBecameReady) {
5268
5409
  lastHeartbeat = now;
5269
5410
  const servedRepos = Array.isArray(cfg.servedRepos) ? cfg.servedRepos.slice(0, 100) : [];
5270
5411
  const servedOperators = Array.isArray(cfg.servedOperators) ? cfg.servedOperators.slice(0, 100) : [];
5271
- const availableAgents = getAgentAvailability();
5272
5412
  const accountUsage = getAccountUsage();
5273
5413
  const version = String(env2.VO_CODE_RUNNER_VERSION || "").trim().slice(0, 40);
5274
5414
  const daemonVersion = String(env2.VO_CODE_RUNNER_DAEMON_VERSION || "").trim().slice(0, 40);
5415
+ const supervisorInstanceId = String(env2.VO_RUNNER_SUPERVISOR_INSTANCE_ID || "").trim();
5416
+ const supervisorVersion = String(env2.VO_RUNNER_SUPERVISOR_VERSION || "").trim().slice(0, 40);
5417
+ const supervisorCapabilities = String(env2.VO_RUNNER_SUPERVISOR_CAPABILITIES || "").split(",").map((value) => value.trim()).filter(Boolean).slice(0, 8);
5275
5418
  const baseHeartbeat = {
5276
5419
  runnerId: cfg.runnerId,
5420
+ ...runnerInstanceId ? { runnerInstanceId } : {},
5277
5421
  ...version ? { version } : {},
5278
5422
  ...daemonVersion ? { daemonVersion } : {},
5423
+ ...cfg.agent ? { defaultAgent: cfg.agent } : {},
5424
+ ...supervisorInstanceId ? { supervisorInstanceId } : {},
5425
+ ...supervisorVersion ? { supervisorVersion } : {},
5426
+ ...supervisorCapabilities.length > 0 ? { supervisorCapabilities } : {},
5279
5427
  ...servedRepos.length > 0 ? { servedRepos } : {},
5280
5428
  ...servedOperators.length > 0 ? { servedOperators } : {},
5281
5429
  ...Array.isArray(availableAgents) && availableAgents.length > 0 ? { availableAgents } : {},
@@ -5285,7 +5433,10 @@ function makeLoopTicks({
5285
5433
  };
5286
5434
  const operatorIds = servedOperators.length > 0 ? servedOperators : [void 0];
5287
5435
  for (const operatorId of operatorIds) {
5288
- client.postHeartbeat({ ...baseHeartbeat, ...operatorId ? { operatorId } : {} }).catch((e) => log3(`heartbeat failed: ${e.message}`));
5436
+ heartbeatCompletions.push(enqueueHeartbeat({
5437
+ ...baseHeartbeat,
5438
+ ...operatorId ? { operatorId } : {}
5439
+ }));
5289
5440
  }
5290
5441
  }
5291
5442
  const resumeSec = Number(env2.VO_RESUME_SCHEDULE_SEC) > 0 ? Number(env2.VO_RESUME_SCHEDULE_SEC) : DEFAULT_RESUME_SCHEDULE_SEC;
@@ -5296,6 +5447,7 @@ function makeLoopTicks({
5296
5447
  resumeRunning = false;
5297
5448
  });
5298
5449
  }
5450
+ return Promise.all(heartbeatCompletions).then(() => void 0);
5299
5451
  };
5300
5452
  }
5301
5453
  var HEARTBEAT_MS, DEFAULT_RESUME_SCHEDULE_SEC;
@@ -5310,6 +5462,10 @@ var init_loop_ticks = __esm({
5310
5462
  });
5311
5463
 
5312
5464
  // ../../scripts/virtual-office/code-runner/agent-availability.mjs
5465
+ function resolveAgentClaimContext(provider, defaultAgent) {
5466
+ const availableAgents = provider.get();
5467
+ return Array.isArray(availableAgents) ? { availableAgents, defaultAgent } : null;
5468
+ }
5313
5469
  async function collectAgentAvailability({
5314
5470
  agents = listAgents(),
5315
5471
  runnerFor = (agent) => resolveRunner({ VO_CODE_RUNNER_AGENT: agent }).runner
@@ -6085,7 +6241,7 @@ function startControlServer({ port, getStatus, requestStop, allowedOrigin, log:
6085
6241
  server.unref?.();
6086
6242
  return server;
6087
6243
  }
6088
- function startDaemonControl({ cfg, requestStop, getActiveCount, isRunning, startedAt, log: log3 = () => {
6244
+ function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount, isRunning, startedAt, log: log3 = () => {
6089
6245
  }, onDuplicate = null }) {
6090
6246
  if (!cfg.controlEnabled) return null;
6091
6247
  return startControlServer({
@@ -6097,6 +6253,7 @@ function startDaemonControl({ cfg, requestStop, getActiveCount, isRunning, start
6097
6253
  running: isRunning(),
6098
6254
  pid: process.pid,
6099
6255
  runnerId: cfg.runnerId,
6256
+ runnerInstanceId,
6100
6257
  servedRepos: cfg.servedRepos,
6101
6258
  servedOperators: cfg.servedOperators,
6102
6259
  watchEnabled: cfg.watchEnabled,
@@ -7626,7 +7783,7 @@ function reportsBlocker(summary) {
7626
7783
  const text = String(summary || "");
7627
7784
  const explicit = explicitTaskOutcome(text);
7628
7785
  if (explicit) return explicit === "BLOCKED" || explicit === "FAILED";
7629
- return /\btask remains\s+(?:\*\*)?BLOCKED\b/i.test(text) || /(?:^|\n)\s*(?:#{1,6}\s*)?(?:host-recovery\s+)?(?:result|outcome)\s*:\s*(?:\*\*)?BLOCKED\b/im.test(text);
7786
+ return /\btask remains\s+(?:\*\*)?BLOCKED\b/i.test(text) || /(?:^|\n)\s*(?:#{1,6}\s*)?(?:\*\*)?(?:host-recovery\s+)?(?:result|outcome|status)\s*:\s*(?:\*\*)?BLOCKED\b/im.test(text);
7630
7787
  }
7631
7788
  function isMaxTurnExhaustion(run = {}, maxTurns) {
7632
7789
  const summary = String(run.summary || "").trim().toLowerCase();
@@ -7902,6 +8059,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
7902
8059
  const startedAt = Date.now();
7903
8060
  const controlServer = startDaemonControl({
7904
8061
  cfg,
8062
+ runnerInstanceId,
7905
8063
  requestStop: () => stop("web-control"),
7906
8064
  getActiveCount: () => active,
7907
8065
  isRunning: () => !stopping,
@@ -7929,18 +8087,24 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
7929
8087
  const watchCoordinator = makeWatchCycleCoordinator({ runWatch, log: log2, intervalMs: cfg.watchIntervalSec * 1e3 });
7930
8088
  const agentAvailability = makeAgentAvailabilityProvider({ onError: (e) => log2(`agent probe failed: ${e.message}`) });
7931
8089
  const accountUsage = makeAccountUsageProvider();
7932
- const loopTick = makeLoopTicks({ client, cfg, env: env2, log: log2, getActive: () => active, getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get() });
8090
+ const loopTick = makeLoopTicks({ client, cfg, env: env2, log: log2, getActive: () => active, runnerInstanceId, getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get() });
7933
8091
  const backoff = makeReconnectBackoff({ baseMs: cfg.pollSec * 1e3, log: log2 });
7934
8092
  while (!stopping) {
7935
- loopTick();
8093
+ const heartbeatCompletion = loopTick();
7936
8094
  if (cfg.watchEnabled) watchCoordinator.start();
7937
8095
  if (active >= cfg.maxConcurrency) {
7938
8096
  await sleep2(cfg.pollSec * 1e3);
7939
8097
  continue;
7940
8098
  }
8099
+ const claimAgents = resolveAgentClaimContext(agentAvailability, cfg.agent);
8100
+ if (!claimAgents) {
8101
+ await sleep2(cfg.pollSec * 1e3);
8102
+ continue;
8103
+ }
8104
+ await heartbeatCompletion;
7941
8105
  let task;
7942
8106
  try {
7943
- task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale });
8107
+ task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents });
7944
8108
  reconcileStale = false;
7945
8109
  backoff.onSuccess();
7946
8110
  } catch (err) {