consortium
Version:
Remote control and session sharing CLI for AI coding agents
88 lines (77 loc) • 4.05 kB
text/typescript
/**
* 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);