UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

88 lines (77 loc) • 4.05 kB
/** * Headless E2E: the REAL daemon device path against a REAL emulator. * real DeviceHost (seal/throttle/route) → real AndroidBackend → real scrcpy * → transport captures SEALED frames → client decrypts with the machine key * → verify H.264 keyframe → seal a tap → handleInput → real injection. * The Socket.IO server relay is a proven blind passthrough (relay spec 10/10), * so this in-process transport faithfully covers the daemon-side integration. */ import { randomBytes } from 'node:crypto'; import { getCryptoEnv } from '@/agent/attachments/cryptoEnv'; import { DeviceHost } from '@/daemon/deviceHost'; import { AndroidBackend, TangoAdbScrcpyClient } from '@/daemon/deviceHost/androidBackend'; import { sealInputEnvelope, openFrameSealed, SECRETBOX_NONCE_BYTES, type SecretBox, type DeviceFrame, type DeviceInfo, type DeviceControlResult, type DeviceLifecycleEvent, type DeviceInputEvent, } from '@consortium/device-protocol'; const log = (...a: unknown[]) => console.log('[e2e]', ...a); const env = await getCryptoEnv(); const dek = new Uint8Array(randomBytes(32)); // shared machine key: daemon seals, client opens const box: SecretBox = { seal: (m, n, k) => env.sodium.crypto_secretbox_easy(m, n, k), open: (c, n, k) => env.sodium.crypto_secretbox_open_easy(c, n, k), randomNonce: () => env.getRandomBytes(SECRETBOX_NONCE_BYTES), toBase64: (b) => Buffer.from(b).toString('base64'), fromBase64: (s) => new Uint8Array(Buffer.from(s, 'base64')), }; let frames = 0, keyframes = 0, bytes = 0; let firstLogged = false; const results: DeviceControlResult[] = []; const events: DeviceLifecycleEvent[] = []; const transport = { emitFrame: (f: DeviceFrame) => { try { const raw = openFrameSealed(box, f, dek); // CLIENT decrypt with machine key frames++; bytes += raw.length; if (f.keyframe) keyframes++; if (!firstLogged) { firstLogged = true; log(`decoded frame#1 keyframe=${!!f.keyframe} codec=${f.codec} ${raw.length}B seq=${f.seq}`); } } catch (e) { log('DECRYPT FAILED ❌', String(e)); } }, emitEvent: (e: DeviceLifecycleEvent) => { events.push(e); log('event:', e.event, e.deviceId); }, emitControlResult: (r: DeviceControlResult) => { results.push(r); }, }; const host = new DeviceHost({ env, backends: { android: new AndroidBackend(new TangoAdbScrcpyClient()) }, transport, dek: () => dek, policy: { mobileToolsDisabled: () => false, ensureConsent: () => {} }, }); await host.handleControl({ type: 'control', id: 'c1', method: 'listDevices', params: { platform: 'android' } }); const r1 = results.find((r) => r.id === 'c1'); const devices = (r1 && 'result' in r1 ? (r1.result as DeviceInfo[]) : []) ?? []; log('devices:', devices.map((d) => `${d.id} ${d.screen.w}x${d.screen.h}`)); const dev = devices.find((d) => d.id === 'emulator-5554') ?? devices[0]; if (!dev) { log('no device ❌'); process.exit(1); } host.registerDevice(dev.id, 'android'); log('attaching (real scrcpy)...'); await host.handleControl({ type: 'control', id: 'c2', method: 'attach', params: { deviceId: dev.id, opts: { codec: 'h264', maxFps: 15, maxLongEdge: 1024, quality: 0.6 } } }); const r2 = results.find((r) => r.id === 'c2'); if (r2 && 'ok' in r2 && !r2.ok) { log('attach failed ❌', (r2 as { error: string }).error); process.exit(1); } await new Promise((res) => setTimeout(res, 8000)); log(`FRAMES: ${frames} decoded & auth'd, ${keyframes} keyframes, ${bytes}B total`); const tap = (phase: 'down' | 'up'): DeviceInputEvent => ({ kind: 'touch', phase, x: 0.5, y: 0.5 }); let nonce = 0; await host.handleInput(sealInputEnvelope(box, dev.id, tap('down'), ++nonce, dek)); await host.handleInput(sealInputEnvelope(box, dev.id, tap('up'), ++nonce, dek)); log(`TAP sealed → handleInput → injected at center of ${dev.screen.w}x${dev.screen.h}`); await host.close(); const pass = frames > 0 && keyframes > 0; log(pass ? 'E2E PASS ✅' : 'E2E FAIL ❌'); process.exit(pass ? 0 : 1);