feat(settings): add Codex link controls and authenticated phone bridge

This commit is contained in:
yilsem committed 2026-10-09 11:55:16 +03:00
1 parent 1acb5dd90f
commit 55a1beaf9d
22 files changed
+2857 -3

No files matched your search

+35
View File
@@ -42,6 +42,9 @@ import http from 'node:http'
import https from 'node:https'
import net from 'node:net'
import type { Duplex } from 'node:stream'
import { CODEX_LINK_REQUEST_BYTES, validateControlRequest } from '../codex-link-control'
import { parseStrictJson } from '../codex-link-json'
import type { CodexLinkControlRequest, CodexLinkControlResponse } from '../codex-link-control.types'
import { loadOrCreateLanIdentity, type MobileLanIdentity, type MobileLanVault } from './mobile-lan-cert'
import {
@@ -706,6 +709,7 @@ export interface MobileDeviceView {
export type PairingEndReason = 'claimed' | 'locked' | 'expired' | 'cancelled'
export interface LanProxyOptions {
codexLinkControl?: (request: CodexLinkControlRequest) => Promise<CodexLinkControlResponse>
tls: { key: string; cert: string }
/** Must resolve the LOCAL backend or throw (remote mode, backend down). */
resolveBackend: (profile: string) => Promise<MobileBackend>
@@ -1108,6 +1112,35 @@ export function createLanProxy(options: LanProxyOptions) {
return reply(req, res, path, 403, { error: 'forbidden' })
}
if (path === '/api/aiturk/codex-link/control') {
if (req.method !== 'POST') return reply(req, res, path, 405, { error: 'method_not_allowed' })
if (!options.codexLinkControl) return reply(req, res, path, 503, { error: 'codex_link_unavailable' })
if (!/^application\/json(?:;|$)/i.test(String(req.headers['content-type'] ?? ''))) {
return reply(req, res, path, 400, { error: 'invalid_request' })
}
let request: CodexLinkControlRequest
try {
if (Number(req.headers['content-length'] ?? 0) > CODEX_LINK_REQUEST_BYTES) throw new Error('large_body')
let size = 0
const chunks: Buffer[] = []
for await (const chunk of req) {
size += chunk.length
if (size > CODEX_LINK_REQUEST_BYTES) throw new Error('large_body')
chunks.push(Buffer.from(chunk))
}
const parsed = parseStrictJson(Buffer.concat(chunks), CODEX_LINK_REQUEST_BYTES)
validateControlRequest(parsed)
request = parsed
} catch { return reply(req, res, path, 400, { error: 'invalid_request' }) }
// Pairing can be revoked while a slow body is being read.
if (!devices.has(device.tokenHash)) return reply(req, res, path, 401, { error: 'unauthorized' })
try {
const result = await options.codexLinkControl(request)
if (!devices.has(device.tokenHash)) return reply(req, res, path, 401, { error: 'unauthorized' })
return reply(req, res, path, 200, result)
} catch { return reply(req, res, path, 503, { error: 'codex_link_outcome_unknown' }) }
}
const backend = await localBackend(url)
if (res.destroyed) return
@@ -1510,6 +1543,7 @@ export type MobileLinkEvent =
{ type: 'state'; state: MobileLinkState } | { type: 'paired'; name: string; state: MobileLinkState }
export interface MobileLinkDeps {
codexLinkControl?: (request: CodexLinkControlRequest) => Promise<CodexLinkControlResponse>
vault: MobileLanVault
store: MobileLinkStore
resolveBackend: (profile: string) => Promise<MobileBackend>
@@ -1820,6 +1854,7 @@ export function createMobileLink(deps: MobileLinkDeps) {
}
proxy = createLanProxy({
codexLinkControl: deps.codexLinkControl,
tls: identity,
resolveBackend: async profile => {
if (deps.isRemoteMode()) {