import { randomBytes } from 'node:crypto'; import { mkdir, open, readFile, unlink } from 'node:fs/promises'; import { dirname, join } from 'node:path'; export interface RemoteControlLockInfo { readonly pid: number; readonly nonce: string; readonly localOrigin: string; readonly deviceId: string; readonly url: string; readonly startedAt: number; } interface RemoteControlLockDisk { readonly pid: number; readonly nonce: string; readonly local_origin: string; readonly device_id: string; readonly url: string; readonly started_at: number; } export class RemoteControlAlreadyRunningError extends Error { readonly holder: RemoteControlLockInfo; constructor(holder: RemoteControlLockInfo) { super(formatRemoteControlAlreadyRunning(holder)); this.name = 'RemoteControlAlreadyRunningError'; this.holder = holder; } } export function formatRemoteControlAlreadyRunning(holder: RemoteControlLockInfo): string { return [ `Remote Control is already running on this machine (pid ${holder.pid}, ${holder.localOrigin}, since ${new Date(holder.startedAt).toLocaleString()}).`, `Use the existing link: ${holder.url}`, 'To start a new one here, stop the other `kimi web --remote-control` process first.', ].join('\n'); } export function remoteControlLockPath(homeDir: string): string { return join(homeDir, 'server', 'rc.json'); } export interface RemoteControlLock { release(): Promise; } const MAX_ACQUIRE_ATTEMPTS = 3; export async function acquireRemoteControlLock( homeDir: string, details: { localOrigin: string; deviceId: string; url: string }, ): Promise { const lockPath = remoteControlLockPath(homeDir); await mkdir(dirname(lockPath), { recursive: true }); const info: RemoteControlLockInfo = { pid: process.pid, nonce: randomBytes(8).toString('hex'), localOrigin: details.localOrigin, deviceId: details.deviceId, url: details.url, startedAt: Date.now(), }; for (let attempt = 0; ; attempt += 1) { try { const handle = await open(lockPath, 'wx'); try { await handle.writeFile(encodeLock(info)); } finally { await handle.close(); } return { release: () => releaseRemoteControlLock(lockPath, info.nonce) }; } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'EEXIST' || attempt >= MAX_ACQUIRE_ATTEMPTS) { throw error; } const holder = await readRemoteControlLock(lockPath); if (holder !== undefined && pidAlive(holder.pid)) { throw new RemoteControlAlreadyRunningError(holder); } await removeFile(lockPath); } } } export async function inspectRemoteControlLock( homeDir: string, ): Promise { const lockPath = remoteControlLockPath(homeDir); const info = await readRemoteControlLock(lockPath); if (info === undefined) return undefined; if (!pidAlive(info.pid)) { await removeFile(lockPath); return undefined; } return info; } async function releaseRemoteControlLock(lockPath: string, nonce: string): Promise { const info = await readRemoteControlLock(lockPath); if (info === undefined || info.nonce !== nonce) return; await removeFile(lockPath); } async function readRemoteControlLock(lockPath: string): Promise { let raw: string; try { raw = await readFile(lockPath, 'utf8'); } catch { return undefined; } return decodeLock(raw); } function encodeLock(info: RemoteControlLockInfo): string { const disk: RemoteControlLockDisk = { pid: info.pid, nonce: info.nonce, local_origin: info.localOrigin, device_id: info.deviceId, url: info.url, started_at: info.startedAt, }; return JSON.stringify(disk); } function decodeLock(raw: string): RemoteControlLockInfo | undefined { try { const parsed = JSON.parse(raw) as Partial; if ( typeof parsed.pid === 'number' && typeof parsed.nonce === 'string' && typeof parsed.local_origin === 'string' && typeof parsed.device_id === 'string' && typeof parsed.url === 'string' && typeof parsed.started_at === 'number' ) { return { pid: parsed.pid, nonce: parsed.nonce, localOrigin: parsed.local_origin, deviceId: parsed.device_id, url: parsed.url, startedAt: parsed.started_at, }; } return undefined; } catch { return undefined; } } async function removeFile(lockPath: string): Promise { try { await unlink(lockPath); } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; } } function pidAlive(pid: number): boolean { try { process.kill(pid, 0); return true; } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false; return true; } }