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

This reverts commit 55a1beaf9d.
This commit is contained in:
yilsem committed 2026-10-09 15:17:59 +03:00
1 parent 01889d1383
commit 4220d35ebe
21 files changed
+3 -2838

No files matched your search

File diff suppressed because it is too large. Load diff
-123
View File
@@ -1,123 +0,0 @@
import schema from './codex-link-control.schema.json'
import type { CodexLinkControlRequest, CodexLinkControlResponse } from './codex-link-control.types'
import { canonicalJson, CodexLinkValidationError, parseStrictJson } from './codex-link-json'
export const CODEX_LINK_REQUEST_BYTES = 64 * 1024
export const CODEX_LINK_RESPONSE_BYTES = 1024 * 1024
type Rule = Record<string, any>
const definitions: Record<string, Rule> = schema.$defs
function matches(value: any, rule: Rule): boolean {
if (rule.$ref) {
const target = definitions[rule.$ref.replace('#/$defs/', '')]
return !!target && matches(value, target)
}
if (rule.oneOf) {
return rule.oneOf.filter((r: Rule) => matches(value, r)).length === 1
}
if (rule.anyOf) {
return rule.anyOf.some((r: Rule) => matches(value, r))
}
if ('const' in rule && value !== rule.const) {
return false
}
if (rule.enum && !rule.enum.includes(value)) {
return false
}
switch (rule.type) {
case 'object': {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return false
}
const keys = Object.keys(value)
return (
keys.length >= (rule.minProperties ?? 0) &&
rule.required.every((key: string) => Object.hasOwn(value, key)) &&
keys.every(key => Object.hasOwn(rule.properties, key) && matches(value[key], rule.properties[key]))
)
}
case 'array':
return (
Array.isArray(value) &&
value.length >= (rule.minItems ?? 0) &&
(!rule.uniqueItems || new Set(value.map(canonicalJson)).size === value.length) &&
value.every(item => matches(item, rule.items))
)
case 'string':
return (
typeof value === 'string' &&
[...value].length >= (rule.minLength ?? 0) &&
(!rule.pattern || new RegExp(rule.pattern).test(value))
)
case 'integer':
return (
Number.isSafeInteger(value) &&
value >= (rule.minimum ?? -Number.MAX_SAFE_INTEGER) &&
value <= (rule.maximum ?? Number.MAX_SAFE_INTEGER)
)
case 'boolean':
return typeof value === 'boolean'
case 'null':
return value === null
case undefined:
return 'const' in rule || 'enum' in rule
default:
return false
}
}
export function validateControlRequest(value: unknown): asserts value is CodexLinkControlRequest {
const text = canonicalJson(value)
if (
new TextEncoder().encode(text).byteLength > CODEX_LINK_REQUEST_BYTES ||
!matches(value, definitions.control_request)
) {
throw new CodexLinkValidationError('invalid_control_request')
}
}
export function parseControlResponse(
raw: Uint8Array | string,
request: CodexLinkControlRequest
): CodexLinkControlResponse {
const value = parseStrictJson(raw, CODEX_LINK_RESPONSE_BYTES) as CodexLinkControlResponse
if (
!matches(value, definitions.control_response) ||
value.request_id !== request.request_id ||
value.operation_id !== request.operation_id
) {
throw new CodexLinkValidationError('invalid_control_response')
}
const expected = {
settings_get: 'settings_result',
settings_update: 'settings_result',
status_get: 'status_result',
context_views_get: 'context_views_result',
catalog_sync: 'control_accepted',
context_compact_view: 'control_accepted'
}
if (value.kind !== 'control_error' && value.kind !== expected[request.kind]) {
throw new CodexLinkValidationError('unexpected_control_response')
}
return value
}
-22
View File
@@ -1,22 +0,0 @@
// Generated by scripts/generate-codex-link-control.py. Do not edit.
export const CODEX_LINK_CONTROL_SCHEMA_HASH = "sha256:f969be5410af06a168f2934ed1025061b06482901a7421f0034e5e4e86aca238"
export type CodexLinkCatalogSync = { "contract_version": "codex-link/1"; "kind": "catalog_sync"; "request_id": string; "operation_id": string }
export type CodexLinkContextCompactView = { "contract_version": "codex-link/1"; "kind": "context_compact_view"; "request_id": string; "operation_id": string; "view_handle": string; "expected_checkpoint_revision": number }
export type CodexLinkContextView = { "view_handle": string; "can_compact": boolean; "label": string; "checkpoint_revision": number; "current_model_label": string; "limiting_target_id": (string | null); "target_budgets": (Array<CodexLinkTargetBudget> | null); "active_summary_count": number; "summary_tokens": (number | null); "summary_token_measurement": ({ "target_id": string; "tokenizer_id": string; "tokenizer_revision": string; "count_method": "exact_tokenizer" | "verified_upper_bound" } | null); "last_operation_id": (string | null); "last_operation_state": ("accepted" | "running" | "completed" | "failed" | "indeterminate" | null); "last_operation_error": (CodexLinkError | null); "compact_attempts": Array<{ "attempt_id": string; "member_label": string; "state": "prepared" | "running" | "completed" | "failed" | "indeterminate"; "usage": CodexLinkUsage }> }
export type CodexLinkContextViewsGet = { "contract_version": "codex-link/1"; "kind": "context_views_get"; "request_id": string; "operation_id": string }
export type CodexLinkContextViewsResult = { "contract_version": "codex-link/1"; "kind": "context_views_result"; "request_id": string; "operation_id": string; "settings_revision": number; "views": Array<CodexLinkContextView> }
export type CodexLinkControlAccepted = { "contract_version": "codex-link/1"; "kind": "control_accepted"; "request_id": string; "operation_id": string; "settings_revision": number; "operation_state": "accepted" | "running" | "completed" | "failed" | "indeterminate"; "error": (CodexLinkError | null) }
export type CodexLinkControlError = { "contract_version": "codex-link/1"; "kind": "control_error"; "request_id": string; "operation_id": string; "settings_revision": number; "error": CodexLinkError }
export type CodexLinkControlRequest = (CodexLinkSettingsGet | CodexLinkSettingsUpdate | CodexLinkStatusGet | CodexLinkCatalogSync | CodexLinkContextViewsGet | CodexLinkContextCompactView)
export type CodexLinkControlResponse = (CodexLinkSettingsResult | CodexLinkStatusResult | CodexLinkContextViewsResult | CodexLinkControlAccepted | CodexLinkControlError)
export type CodexLinkError = { "code": string; "message": string; "recovery_action": "retry_control_read" | "reconcile_only" | "repair_required" | "select_route" | "reconfigure" | "none" }
export type CodexLinkOutputCapEnforcement = ({ "method": "request_field"; "field": "max_output_tokens" | "max_tokens" | "max_completion_tokens" | "num_predict"; "value": number; "proof_revision": string; "verified": true } | { "method": "provider_fixed_maximum"; "value": number; "proof_revision": string; "verified": true })
export type CodexLinkSettings = { "settings_revision": number; "enabled": boolean; "compression_enabled": boolean; "threshold_tokens": number; "source_binding_handle": (string | null); "compact_route_id": (string | null); "legacy_intent_imported": boolean; "ownership_manifest_revision": number; "activation_state": "disabled" | "pending_runtime_acceptance" | "ready" | "restart_required" | "conflict" | "recovery_pending" }
export type CodexLinkSettingsGet = { "contract_version": "codex-link/1"; "kind": "settings_get"; "request_id": string; "operation_id": string }
export type CodexLinkSettingsResult = { "contract_version": "codex-link/1"; "kind": "settings_result"; "request_id": string; "operation_id": string; "settings": CodexLinkSettings; "available_sources": Array<{ "source_binding_handle": string; "label": string; "authorized": boolean }> }
export type CodexLinkSettingsUpdate = { "contract_version": "codex-link/1"; "kind": "settings_update"; "request_id": string; "operation_id": string; "expected_settings_revision": number; "patch": { "enabled"?: boolean; "compression_enabled"?: boolean; "threshold_tokens"?: number; "source_binding_handle"?: string } }
export type CodexLinkStatusGet = { "contract_version": "codex-link/1"; "kind": "status_get"; "request_id": string; "operation_id": string }
export type CodexLinkStatusResult = { "contract_version": "codex-link/1"; "kind": "status_result"; "request_id": string; "operation_id": string; "status": CodexLinkStatusSnapshot }
export type CodexLinkStatusSnapshot = { "settings_revision": number; "runtime_mode": "native" | "managed" | "unknown"; "native_available": boolean; "external_admission": "open" | "closed" | "draining" | "recovery_required"; "needs_restart": boolean; "catalog_state": "unknown" | "fresh" | "stale" | "unauthorized" | "empty"; "active_turns": number; "uncertain_operations": number; "last_operation_id": (string | null); "error": (CodexLinkError | null) }
export type CodexLinkTargetBudget = { "target_id": string; "canonical_leaf_id": string; "limits_source": "provider_contract" | "provider_api" | "verified_adapter" | "verified_measurement"; "limits_revision": string; "tokenizer_id": string; "tokenizer_revision": string; "count_method": "exact_tokenizer" | "verified_upper_bound"; "serialization_revision": string; "input_tokens": number; "context_window_tokens": number; "output_cap_tokens": number; "safety_margin_tokens": number; "max_input_tokens": (number | null); "max_output_tokens": number; "request_bytes": number; "max_request_bytes": number; "output_cap_enforcement": CodexLinkOutputCapEnforcement }
export type CodexLinkUsage = { "input_tokens": (number | null); "output_tokens": (number | null); "measurement": "measured" | "estimated" | "unknown"; "cost_microunits": (number | null); "currency": ("USD" | null); "cost_source": (string | null) }
-259
View File
@@ -1,259 +0,0 @@
// Strict P1 JSON domain/parser, shared algorithm with router codexLinkValidation.
// Browser-safe; never apply to the opaque native/provider JSON body.
type Data = Record<string, any>
export class CodexLinkValidationError extends Error {
readonly code: string
constructor(code: string) {
super(code)
this.code = code
this.name = 'CodexLinkValidationError'
}
}
function requireValue(condition: unknown, code: string): asserts condition {
if (!condition) {
throw new CodexLinkValidationError(code)
}
}
function object(value: unknown): Data {
requireValue(value !== null && typeof value === 'object' && !Array.isArray(value), 'schema_rejected')
const prototype = Object.getPrototypeOf(value)
requireValue(prototype === Object.prototype || prototype === null, 'schema_rejected')
return value as Data
}
function integer(value: unknown, positive = false): asserts value is number {
requireValue(Number.isSafeInteger(value) && (value as number) >= (positive ? 1 : 0), 'schema_rejected')
}
function id(value: unknown): asserts value is string {
requireValue(typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/.test(value), 'schema_rejected')
}
function exactKeys(value: Data, keys: string[]) {
requireValue(
Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key)),
'schema_rejected'
)
}
function unicode(value: string) {
for (let i = 0; i < value.length; i++) {
const code = value.charCodeAt(i)
if (code >= 0xd800 && code <= 0xdbff) {
const low = value.charCodeAt(++i)
requireValue(low >= 0xdc00 && low <= 0xdfff, 'invalid_unicode')
} else {
requireValue(code < 0xdc00 || code > 0xdfff, 'invalid_unicode')
}
}
}
/** P1 canonical domain: ASCII object keys, safe integers, exact Unicode text. */
export function canonicalJson(value: unknown): string {
const ancestors = new Set<object>()
function encode(item: unknown, depth: number): string {
requireValue(depth <= 256, 'invalid_json')
if (item === null || typeof item === 'boolean') {
return JSON.stringify(item)
}
if (typeof item === 'string') {
unicode(item)
return JSON.stringify(item)
}
if (typeof item === 'number') {
requireValue(Number.isInteger(item), 'floats_or_non_json_type')
requireValue(Number.isSafeInteger(item), 'unsafe_integer')
return JSON.stringify(item)
}
requireValue(typeof item === 'object' && item !== null, 'floats_or_non_json_type')
requireValue(!ancestors.has(item), 'invalid_json')
ancestors.add(item)
let result: string
if (Array.isArray(item)) {
result = '[' + item.map(child => encode(child, depth + 1)).join(',') + ']'
} else {
const data = object(item)
const keys = Object.keys(data).sort()
requireValue(
keys.every(key => [...key].every(char => char.charCodeAt(0) <= 127)),
'non_ascii_key'
)
result = '{' + keys.map(key => JSON.stringify(key) + ':' + encode(data[key], depth + 1)).join(',') + '}'
}
ancestors.delete(item)
return result
}
return encode(value, 0)
}
/** A bounded parser that detects duplicates before JSON.parse can erase them. */
export function parseStrictJson(raw: string | Uint8Array, maxBytes: number): unknown {
integer(maxBytes, true)
const rawBytes = typeof raw === 'string' ? new TextEncoder().encode(raw).byteLength : raw.byteLength
requireValue(rawBytes <= maxBytes, 'transport_bytes_exceeded')
let text: string
try {
text = typeof raw === 'string' ? raw : new TextDecoder('utf-8', { fatal: true }).decode(raw)
} catch {
throw new CodexLinkValidationError('invalid_json')
}
unicode(text)
let position = 0
function whitespace() {
while (/[ \t\r\n]/.test(text[position] ?? '') && position < text.length) {
position++
}
}
function quoted(): string {
const start = position++
while (position < text.length) {
const next = text[position++]
if (next === '\\') {
position++
continue
}
if (next === '"') {
try {
const value = JSON.parse(text.slice(start, position))
unicode(value)
return value
} catch (error) {
if (error instanceof CodexLinkValidationError) {
throw error
}
throw new CodexLinkValidationError('invalid_json')
}
}
}
throw new CodexLinkValidationError('invalid_json')
}
function read(depth: number): unknown {
requireValue(depth <= 256, 'invalid_json')
whitespace()
const token = text[position]
if (token === '"') {
return quoted()
}
if (token === '{') {
position++
whitespace()
const value: Data = Object.create(null)
if (text[position] === '}') {
position++
return value
}
while (position < text.length) {
requireValue(text[position] === '"', 'invalid_json')
const key = quoted()
requireValue(
[...key].every(char => char.charCodeAt(0) <= 127),
'non_ascii_key'
)
requireValue(!Object.hasOwn(value, key), 'duplicate_json_key')
whitespace()
requireValue(text[position++] === ':', 'invalid_json')
value[key] = read(depth + 1)
whitespace()
const separator = text[position++]
if (separator === '}') {
return value
}
requireValue(separator === ',', 'invalid_json')
whitespace()
}
} else if (token === '[') {
position++
whitespace()
const value: unknown[] = []
if (text[position] === ']') {
position++
return value
}
while (position < text.length) {
value.push(read(depth + 1))
whitespace()
const separator = text[position++]
if (separator === ']') {
return value
}
requireValue(separator === ',', 'invalid_json')
}
} else {
for (const [word, value] of [
['true', true],
['false', false],
['null', null]
] as const) {
if (text.startsWith(word, position)) {
position += word.length
return value
}
}
if (/^(?:NaN|[-+]?Infinity)/.test(text.slice(position))) {
throw new CodexLinkValidationError('floats_or_non_json_type')
}
const match = /^-?(?:0|[1-9]\d*)/.exec(text.slice(position))
if (match) {
position += match[0].length
requireValue(!/[.eE]/.test(text[position] ?? ''), 'floats_or_non_json_type')
const number = Number(match[0])
requireValue(Number.isSafeInteger(number), 'unsafe_integer')
return number
}
}
throw new CodexLinkValidationError('invalid_json')
}
const value = read(0)
whitespace()
requireValue(position === text.length, 'invalid_json')
canonicalJson(value)
return value
}
@@ -1,173 +0,0 @@
import { createHash } from 'node:crypto'
import { EventEmitter } from 'node:events'
import { PassThrough } from 'node:stream'
import { describe, expect, it, vi } from 'vitest'
import { parseControlResponse, validateControlRequest } from '../codex-link-control'
import controlSchema from '../codex-link-control.schema.json'
import { CODEX_LINK_CONTROL_SCHEMA_HASH } from '../codex-link-control.types'
import type { CodexLinkControlRequest } from '../codex-link-control.types'
import { canonicalJson } from '../codex-link-json'
import { createCodexLinkClient } from './codex-link-client'
const request: CodexLinkControlRequest = {
contract_version: 'codex-link/1',
kind: 'settings_get',
request_id: 'req-1',
operation_id: 'op-1'
}
const response = {
...request,
kind: 'settings_result',
settings: {
settings_revision: 1,
enabled: true,
compression_enabled: true,
threshold_tokens: 250000,
source_binding_handle: null,
compact_route_id: null,
legacy_intent_imported: false,
ownership_manifest_revision: 0,
activation_state: 'pending_runtime_acceptance'
},
available_sources: []
}
function harness(reply: string = JSON.stringify(response), env: Record<string, string> = {}) {
let stdin = ''
const child = Object.assign(new EventEmitter(), {
stdout: new PassThrough(),
stderr: new PassThrough(),
stdin: new PassThrough(),
kill: vi.fn()
})
child.stdin.on('data', chunk => {
stdin += chunk.toString()
})
child.stdin.on('finish', () =>
queueMicrotask(() => {
const bytes = Buffer.from(reply)
// UTF-8 chunks may end inside a multi-byte character.
for (const byte of bytes) {
child.stdout.write(Buffer.from([byte]))
}
child.stderr.write('diagnostic that must not become a response')
child.emit('close', 0)
})
)
const spawn = vi.fn(() => child)
const control = createCodexLinkClient({
spawn,
existsSync: () => true,
platform: 'win32',
env: { LOCALAPPDATA: 'C:\\Users\\Example User\\AppData\\Local', SystemRoot: 'C:\\Windows', ...env }
})
return { control, spawn, child, stdin: () => stdin }
}
describe('Codex connection control boundary', () => {
it('pins the generated control-only schema', () => {
expect('sha256:' + createHash('sha256').update(canonicalJson(controlSchema)).digest('hex')).toBe(CODEX_LINK_CONTROL_SCHEMA_HASH)
expect(controlSchema.$defs).not.toHaveProperty('runtime_attach')
expect(controlSchema.$defs).not.toHaveProperty('checkpoint_commit')
})
it('uses only fixed command arguments and sends the exact request on stdin with EOF', async () => {
const h = harness()
expect(await h.control(request)).toEqual(response)
expect(h.stdin()).toBe(JSON.stringify(request) + '\n')
const [file, args, options] = h.spawn.mock.calls[0] as unknown as [string, string[], any]
expect(file).toBe('C:\\Windows\\System32\\cmd.exe')
expect(args).toEqual([
'/d',
'/s',
'/c',
'""C:\\Users\\Example User\\AppData\\Local\\TurkServis\\AITURK\\program\\bin\\aiturk.cmd" codex-link control --stdin"'
])
expect(options).toMatchObject({ shell: false, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] })
})
it('rejects runtime and arbitrary path/credential commands before spawning', async () => {
const h = harness()
await expect(h.control({ ...request, kind: 'runtime_attach' })).rejects.toThrow()
await expect(h.control({ ...request, path: 'C:\\private' })).rejects.toThrow()
expect(h.spawn).not.toHaveBeenCalled()
})
it('rejects shell expansion in the entrypoint path', async () => {
const h = harness(undefined, { LOCALAPPDATA: 'C:\\Users\\%PATH%\\Local' })
await expect(h.control(request)).rejects.toThrow('invalid_entrypoint')
expect(h.spawn).not.toHaveBeenCalled()
})
it('binds the response to both request and operation identity', () => {
expect(() =>
parseControlResponse(JSON.stringify({ ...response, operation_id: 'other-chat-op' }), request)
).toThrow()
expect(() => parseControlResponse(JSON.stringify({ ...response, request_id: 'other-request' }), request)).toThrow()
})
it('rejects duplicate keys, floats, trailing JSON and invalid UTF8', () => {
expect(() =>
parseControlResponse(
JSON.stringify(response).replace('"settings_revision":1', '"settings_revision":1,"settings_revision":2'),
request
)
).toThrow('duplicate_json_key')
expect(() => parseControlResponse(JSON.stringify(response).replace('250000', '250000.0'), request)).toThrow(
'floats_or_non_json_type'
)
expect(() => parseControlResponse(JSON.stringify(response) + '{}', request)).toThrow()
expect(() => parseControlResponse(new Uint8Array([0xc3, 0x28]), request)).toThrow()
})
it('rejects oversized requests and unsafe integer settings', () => {
expect(() =>
validateControlRequest({
...request,
kind: 'settings_update',
expected_settings_revision: 1,
patch: { threshold_tokens: Number.MAX_SAFE_INTEGER + 1 }
})
).toThrow()
expect(() => validateControlRequest({ ...request, operation_id: 'x'.repeat(70000) })).toThrow()
})
it('does not retry a write after a broken reply', async () => {
const h = harness('broken')
await expect(h.control({ ...request, kind: 'catalog_sync' })).rejects.toThrow('outcome_unknown')
expect(h.spawn).toHaveBeenCalledTimes(1)
})
it('preserves a structured CLI rejection returned with exit code one', async () => {
const rejection = { ...request, kind: 'control_error', settings_revision: 2, error: { code: 'stale_settings_cas', message: 'Ayarlar değişti.', recovery_action: 'retry_control_read' } }
const h = harness()
h.child.stdin.removeAllListeners('finish')
h.child.stdin.on('finish', () => queueMicrotask(() => {
h.child.stdout.write(JSON.stringify(rejection))
h.child.emit('close', 1)
}))
expect(await h.control(request)).toEqual(rejection)
expect(h.spawn).toHaveBeenCalledTimes(1)
})
it('preserves exact Unicode when response bytes arrive separately', async () => {
const h = harness(
JSON.stringify({
...response,
available_sources: [{ source_binding_handle: 'source-a', label: 'Türkçe é/é 🧪', authorized: true }]
})
)
const result = await h.control(request)
expect(result.kind === 'settings_result' && result.available_sources[0].label).toBe('Türkçe é/é 🧪')
})
it('bounds stdout and terminates only the short proxy', async () => {
const h = harness('x'.repeat(1024 * 1024 + 1))
// Avoid a million small chunks: replace the synthetic finish listener.
h.child.stdin.removeAllListeners('finish')
h.child.stdin.on('finish', () => queueMicrotask(() => h.child.stdout.emit('data', Buffer.alloc(1024 * 1024 + 1))))
await expect(h.control(request)).rejects.toThrow('outcome_unknown')
expect(h.child.kill).toHaveBeenCalledTimes(1)
})
})
-132
View File
@@ -1,132 +0,0 @@
import { CODEX_LINK_RESPONSE_BYTES, parseControlResponse, validateControlRequest } from '../codex-link-control'
import type { CodexLinkControlResponse } from '../codex-link-control.types'
import { type AiturkCliRunnerDeps, resolveAiturkCliPath } from './aiturk-login'
type Dependencies = AiturkCliRunnerDeps
// Only the fixed entrypoint and fixed arguments go through cmd.exe. All values travel on stdin.
// eslint-disable-next-line no-control-regex -- Reject control bytes in executable paths before cmd parsing.
const UNSAFE_PATH = /["%^!\u0000-\u001f]/
const ABSOLUTE_PATH = /^[A-Za-z]:\\/
export function createCodexLinkClient(deps: Dependencies) {
const setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms))
const clearTimer = deps.clearTimer ?? (timer => clearTimeout(timer as ReturnType<typeof setTimeout>))
return async function control(raw: unknown): Promise<CodexLinkControlResponse> {
validateControlRequest(raw)
const cli = resolveAiturkCliPath(deps)
if (!cli) {
throw new Error('codex-link:cli_unavailable')
}
const systemRoot = String(deps.env.SystemRoot || deps.env.SYSTEMROOT || 'C:\\Windows').replace(/[\\/]+$/, '')
if (UNSAFE_PATH.test(cli) || UNSAFE_PATH.test(systemRoot) || !ABSOLUTE_PATH.test(systemRoot)) {
throw new Error('codex-link:invalid_entrypoint')
}
return new Promise((resolve, reject) => {
let settled = false
let timer: unknown
let bytes = 0
const chunks: Buffer[] = []
let child: any
const finish = (error?: Error, response?: CodexLinkControlResponse) => {
if (settled) {
return
}
settled = true
if (timer !== undefined) {
clearTimer(timer)
}
error ? reject(error) : resolve(response!)
}
try {
child = deps.spawn(
`${systemRoot}\\System32\\cmd.exe`,
['/d', '/s', '/c', `""${cli}" codex-link control --stdin"`],
{
shell: false,
windowsHide: true,
windowsVerbatimArguments: true,
stdio: ['pipe', 'pipe', 'pipe']
}
)
} catch {
finish(new Error('codex-link:cli_unavailable'))
return
}
timer = setTimer(() => {
// The durable manager may have accepted a write. Never repeat it under a new ID.
finish(new Error('codex-link:outcome_unknown'))
try {
child.kill()
} catch {
/* proxy already exited; manager is independent */
}
}, deps.timeoutMs ?? 30_000)
child.stdout.on('data', (chunk: Buffer) => {
if (settled) {
return
}
bytes += chunk.length
if (bytes > CODEX_LINK_RESPONSE_BYTES) {
finish(new Error('codex-link:outcome_unknown'))
try {
child.kill()
} catch {
/* no process tree termination */
}
} else {
chunks.push(Buffer.from(chunk))
}
})
// Drain concurrently, with no history, paths, credentials or uncontrolled diagnostics logged.
child.stderr.on('data', () => {})
child.stdin.on('error', () => {})
child.once('error', () => finish(new Error('codex-link:outcome_unknown')))
child.once('close', (code: number | null) => {
if (settled) {
return
}
if (code !== 0 && code !== 1) {
finish(new Error('codex-link:outcome_unknown'))
return
}
try {
const response = parseControlResponse(Buffer.concat(chunks), raw)
if (code === 1 && response.kind !== 'control_error') {
finish(new Error('codex-link:outcome_unknown'))
} else {
finish(undefined, response)
}
} catch {
finish(new Error('codex-link:outcome_unknown'))
}
})
try {
child.stdin.end(JSON.stringify(raw) + '\n')
} catch {
finish(new Error('codex-link:outcome_unknown'))
}
})
}
}
-12
View File
@@ -36,7 +36,6 @@ import { WebSocket as RelayWebSocket } from 'ws'
import { classifyActiveRuntime, needsPackagedRuntimeUpgrade } from './active-runtime-state'
import { createAiturkCliRunner, createAiturkLogin } from './aiturk-login'
import { createCodexLinkClient } from './codex-link-client'
import { createMobileBackendResolver } from './mobile-backend'
import {
createMobileLink,
@@ -17267,16 +17266,6 @@ const aiturkLogin = createAiturkLogin({
now: () => Date.now()
})
const codexLinkControl = createCodexLinkClient({ spawn, existsSync: fs.existsSync, env: process.env })
ipcMain.handle('aiturk:codex-link:control', async (event, request) => {
const window = BrowserWindow.fromWebContents(event.sender)
if (!window || (window !== mainWindow && !instanceWindows.has(window)) ||
event.senderFrame !== event.sender.mainFrame) {
throw new Error('codex-link:unavailable')
}
return codexLinkControl(request)
})
// Only the primary window's top-level frame may drive a sign-in.
function assertAiturkLoginSender(event) {
const contents = mainWindow && !mainWindow.isDestroyed() ? mainWindow.webContents : null
@@ -17874,7 +17863,6 @@ function getMobileLink(): MobileLink {
const directory = path.join(app.getPath('userData'), 'aiturk-mobile', 'mobile')
mobileLink = createMobileLink({
codexLinkControl,
// Same vault file as before (mobile-secrets.bin, safeStorage); older fields are kept.
vault: new MobileVault(directory, safeStorage),
store: createMobileLinkFileStore(directory, path.join(directory, 'lan-link.json'), fs.promises),
@@ -271,63 +271,6 @@ async function startProxy(over: Record<string, unknown> = {}) {
return { proxy, port, identity, clock, logs, saved, ended, pair, ca: identity.cert }
}
describe('LAN proxy: narrow Codex control', () => {
const route = '/api/aiturk/codex-link/control'
const body = { contract_version: 'codex-link/1', kind: 'settings_get', request_id: 'phone-read-1', operation_id: 'phone-op-1' }
it('requires pairing before invoking local control and never starts a backend', async () => {
const calls: unknown[] = []
const t = await startProxy({ codexLinkControl: async request => { calls.push(request); return { ...request, kind: 'control_error', settings_revision: 0, error: { code: 'pending', message: 'Not ready', recovery_action: 'none' } } } })
const before = resolveCalls
try {
expect((await call(t.port, 'POST', route, { ca: t.ca, body })).status).toBe(401)
expect(calls).toHaveLength(0)
const phone = await t.pair()
const result = await call(t.port, 'POST', route, { ca: t.ca, token: phone.token, body })
expect(result.status).toBe(200)
expect(calls).toEqual([body])
expect(resolveCalls).toBe(before)
expect(JSON.stringify(calls)).not.toContain(phone.token)
} finally { await t.proxy.stop() }
})
it('rejects runtime commands, extra paths, duplicate keys and large bodies', async () => {
let calls = 0
const t = await startProxy({ codexLinkControl: async () => { calls++; throw new Error('must not execute') } })
try {
const phone = await t.pair()
for (const rawBody of [
JSON.stringify({ ...body, kind: 'runtime_attach' }),
JSON.stringify({ ...body, path: 'C:\\private' }),
JSON.stringify(body).replace('"kind":"settings_get"', '"kind":"settings_get","kind":"status_get"'),
'x'.repeat(65537)
]) {
expect((await call(t.port, 'POST', route, { ca: t.ca, token: phone.token, rawBody })).status).toBe(400)
}
expect(calls).toBe(0)
} finally { await t.proxy.stop() }
})
it('returns a fixed unknown-outcome code without leaking CLI diagnostics', async () => {
const t = await startProxy({ codexLinkControl: async () => { throw new Error('private-key-and-path') } })
try {
const phone = await t.pair()
const result = await call(t.port, 'POST', route, { ca: t.ca, token: phone.token, body })
expect(result.status).toBe(503)
expect(result.json).toEqual({ error: 'codex_link_outcome_unknown' })
expect(result.text).not.toContain('private-key')
} finally { await t.proxy.stop() }
})
it('rejects revoked phones and unsupported methods', async () => {
let calls = 0
const t = await startProxy({ codexLinkControl: async () => { calls++; return {} } })
try {
const phone = await t.pair()
expect((await call(t.port, 'GET', route, { ca: t.ca, token: phone.token })).status).toBe(405)
await t.proxy.revoke(phone.deviceId)
expect((await call(t.port, 'POST', route, { ca: t.ca, token: phone.token, body })).status).toBe(401)
expect(calls).toBe(0)
} finally { await t.proxy.stop() }
})
})
describe('LAN proxy: pairing (hello / claim)', () => {
it('hello is unauthenticated and reports whether pairing is open', async () => {
const t = await startProxy()
-35
View File
@@ -42,9 +42,6 @@ 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 {
@@ -709,7 +706,6 @@ 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>
@@ -1112,35 +1108,6 @@ 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
@@ -1543,7 +1510,6 @@ 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>
@@ -1854,7 +1820,6 @@ export function createMobileLink(deps: MobileLinkDeps) {
}
proxy = createLanProxy({
codexLinkControl: deps.codexLinkControl,
tls: identity,
resolveBackend: async profile => {
if (deps.isRemoteMode()) {
-1
View File
@@ -13,7 +13,6 @@ const hudNativeDrag = hudWindowing?.nativeDrag === true
const launchFlags = ipcRenderer.sendSync('hermes:launch-flags')
contextBridge.exposeInMainWorld('hermesDesktop', {
codexLink: { control: request => ipcRenderer.invoke('aiturk:codex-link:control', request) },
setUiLanguage: (locale: string) => ipcRenderer.send('hermes:ui-language', locale),
glassSupported: translucencySupport?.glass === true,
translucencySupported: translucencySupport?.translucency === true,
@@ -1,55 +0,0 @@
import { spawn } from 'node:child_process'
import { mkdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { chromium } from '@playwright/test'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const output = process.argv[2]
if (!output || !path.isAbsolute(output)) throw new Error('An absolute evidence output directory is required')
await mkdir(output, { recursive: true })
const port = 5178
const server = spawn(process.execPath, [path.resolve(root, '../../node_modules/vite/bin/vite.js'), '--host', '127.0.0.1', '--port', String(port), '--strictPort'], { cwd: root, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
let log = ''
const capture = chunk => { log = (log + String(chunk)).slice(-12000) }
server.stdout.on('data', capture); server.stderr.on('data', capture)
let browser
try {
let ready = false
for (let i = 0; i < 100; i++) {
if (server.exitCode !== null) throw new Error('Preview server exited: ' + log)
try { ready = (await fetch(`http://127.0.0.1:${port}/visual-fixtures/codex-link-settings.html`)).ok } catch {}
if (ready) break
await new Promise(resolve => setTimeout(resolve, 300))
}
if (!ready) throw new Error('Preview server not ready: ' + log)
browser = await chromium.launch({ channel: 'chrome', headless: true })
const evidence = []
for (const width of [360, 390, 414, 1280]) {
const page = await browser.newPage({ viewport: { width, height: 900 }, reducedMotion: 'reduce' })
const errors = []
page.on('pageerror', error => errors.push(error.message))
await page.goto(`http://127.0.0.1:${port}/visual-fixtures/codex-link-settings.html`, { waitUntil: 'domcontentloaded', timeout: 90000 })
await page.getByText('Proje sohbeti · örnek kayıt').waitFor({ timeout: 90000 })
await page.evaluate(() => document.fonts.ready)
const layout = await page.evaluate(() => ({ width: innerWidth, scroll: document.documentElement.scrollWidth, buttons: [...document.querySelectorAll('button:not([role=switch]), input, select, label[for="codex-link-enabled"], label[for="codex-link-compression"]')].map(element => ({ text: element.textContent || element.id, height: element.getBoundingClientRect().height })) }))
if (layout.scroll > width) throw new Error(`Horizontal overflow at ${width}: ${layout.scroll}`)
await writeFile(path.join(output, `layout-${width}.json`), JSON.stringify(layout, null, 2) + '\n')
await page.screenshot({ path: path.join(output, `codex-link-${width}.png`), fullPage: true })
if (layout.buttons.some(button => button.height < 43.5)) throw new Error(`Small touch target at ${width}: ${JSON.stringify(layout.buttons.filter(button => button.height < 43.5))}`)
if (errors.length) throw new Error(errors.join('\n'))
// Tap the label as a finger would, then verify the manager-confirmed value.
await page.locator('label[for="codex-link-enabled"]').click()
await page.waitForFunction(() => document.querySelector('#codex-link-enabled')?.getAttribute('aria-checked') === 'false')
await page.getByRole('button', { name: 'Şimdi sıkıştır' }).scrollIntoViewIfNeeded()
await page.screenshot({ path: path.join(output, `codex-link-${width}-history.png`), fullPage: true })
evidence.push({ width, horizontal_overflow: false, touch_targets_passed: true, preference_update_passed: true, errors })
await page.close()
}
await writeFile(path.join(output, 'layout-result.json'), JSON.stringify({ synthetic_fixture: true, real_manager: false, checks: evidence }, null, 2) + '\n')
console.log(JSON.stringify({ passed: evidence.length, output }))
} finally {
await browser?.close()
server.kill()
await writeFile(path.join(output, 'preview-server.log'), log)
}
@@ -1,196 +0,0 @@
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { I18nProvider } from '@/i18n/context'
import type {
CodexLinkContextView,
CodexLinkControlRequest,
CodexLinkControlResponse,
CodexLinkSettings as SavedSettings
} from '../../../codex-link-control.types'
import { CodexLinkSettings } from './codex-link-settings'
let settings: SavedSettings
let views: CodexLinkContextView[]
let writes: CodexLinkControlRequest[]
let failWrite: boolean
const control = vi.fn(async (request: CodexLinkControlRequest): Promise<CodexLinkControlResponse> => {
const base = {
contract_version: 'codex-link/1' as const,
request_id: request.request_id,
operation_id: request.operation_id
}
if (request.kind === 'status_get') {
return {
...base,
kind: 'status_result',
status: {
settings_revision: settings.settings_revision,
runtime_mode: 'managed',
native_available: true,
external_admission: 'open',
needs_restart: false,
catalog_state: 'fresh',
active_turns: 0,
uncertain_operations: 0,
last_operation_id: null,
error: null
}
}
}
if (request.kind === 'context_views_get') {
return { ...base, kind: 'context_views_result', settings_revision: settings.settings_revision, views }
}
if (request.kind === 'settings_update') {
writes.push(request)
if (failWrite) {
throw new Error('outcome_unknown')
}
settings = { ...settings, ...request.patch, settings_revision: settings.settings_revision + 1 }
} else if (request.kind !== 'settings_get') {
writes.push(request)
return {
...base,
kind: 'control_accepted',
settings_revision: settings.settings_revision,
operation_state: 'accepted',
error: null
}
}
return { ...base, kind: 'settings_result', settings, available_sources: [] }
})
const mount = () =>
render(
<I18nProvider configClient={null} initialLocale="tr">
<CodexLinkSettings />
</I18nProvider>
)
beforeEach(() => {
sessionStorage.clear()
control.mockClear()
writes = []
views = []
failWrite = false
settings = {
settings_revision: 7,
enabled: false,
compression_enabled: true,
threshold_tokens: 250000,
source_binding_handle: null,
compact_route_id: 'Compact',
legacy_intent_imported: true,
ownership_manifest_revision: 2,
activation_state: 'disabled'
}
vi.stubGlobal('hermesDesktop', { codexLink: { control } } satisfies Pick<NonNullable<typeof window.hermesDesktop>, 'codexLink'>)
})
afterEach(() => {
cleanup()
vi.unstubAllGlobals()
})
describe('Codex connection settings', () => {
it('shows the persisted disabled preference, without turning it on during mount', async () => {
mount()
const toggle = await screen.findByRole('switch', { name: 'Modelleri Codex’e bağla' })
expect(toggle.getAttribute('aria-checked')).toBe('false')
expect(writes).toEqual([])
expect((screen.getByLabelText('Sıkıştırma eşiği (token)') as HTMLInputElement).value).toBe('250000')
})
it('uses a checkpoint-safe settings patch and rejects decimal/unsafe thresholds', async () => {
mount()
const input = await screen.findByLabelText('Sıkıştırma eşiği (token)')
fireEvent.change(input, { target: { value: '1.5' } })
expect((screen.getByRole('button', { name: 'Kaydet' }) as HTMLButtonElement).disabled).toBe(true)
fireEvent.change(input, { target: { value: '9007199254740992' } })
expect((screen.getByRole('button', { name: 'Kaydet' }) as HTMLButtonElement).disabled).toBe(true)
fireEvent.change(input, { target: { value: '125000' } })
fireEvent.click(screen.getByRole('button', { name: 'Kaydet' }))
await waitFor(() => expect(writes).toHaveLength(1))
expect(writes[0]).toMatchObject({
kind: 'settings_update',
expected_settings_revision: 7,
patch: { threshold_tokens: 125000 }
})
})
it('keeps an uncertain operation across remount and reconciles the exact ID/body', async () => {
failWrite = true
const first = mount()
fireEvent.click(await screen.findByRole('switch', { name: 'Modelleri Codex’e bağla' }))
await screen.findByRole('button', { name: 'İşlemi kontrol et' })
expect(writes).toHaveLength(1)
const original = writes[0]
first.unmount()
mount()
failWrite = false
fireEvent.click(await screen.findByRole('button', { name: 'İşlemi kontrol et' }))
await waitFor(() => expect(writes).toHaveLength(2))
expect(writes[1]).toEqual(original)
await waitFor(() => expect(sessionStorage.getItem('aiturk.codex-link.pending-control.v1')).toBeNull())
})
it('does not present unknown token measurement as zero or enable unsafe compaction', async () => {
views = [
{
view_handle: 'view-a',
can_compact: false,
label: 'Ayrı sohbet',
checkpoint_revision: 3,
current_model_label: 'Model A',
limiting_target_id: null,
target_budgets: null,
active_summary_count: 2,
summary_tokens: null,
summary_token_measurement: null,
last_operation_id: null,
last_operation_state: null,
last_operation_error: null,
compact_attempts: []
}
]
mount()
await screen.findByText('Ayrı sohbet')
expect(screen.getAllByText(/Henüz ölçülmedi/).length).toBeGreaterThanOrEqual(3)
expect((screen.getByRole('button', { name: 'Şimdi sıkıştır' }) as HTMLButtonElement).disabled).toBe(true)
})
it('binds manual compaction to a manager view and its exact checkpoint revision', async () => {
views = [
{
view_handle: 'opaque-view-a',
can_compact: true,
label: 'Sohbet A',
checkpoint_revision: 9,
current_model_label: 'Model A',
limiting_target_id: null,
target_budgets: null,
active_summary_count: 0,
summary_tokens: null,
summary_token_measurement: null,
last_operation_id: null,
last_operation_state: null,
last_operation_error: null,
compact_attempts: []
}
]
mount()
fireEvent.click(await screen.findByRole('button', { name: 'Şimdi sıkıştır' }))
await waitFor(() => expect(writes).toHaveLength(1))
expect(writes[0]).toMatchObject({
kind: 'context_compact_view',
view_handle: 'opaque-view-a',
expected_checkpoint_revision: 9
})
expect(writes[0]).not.toHaveProperty('thread_id')
})
})
@@ -1,465 +0,0 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Switch } from '@/components/ui/switch'
import { useCodexLinkCopy } from '@/i18n/codex-link'
import { Cpu } from '@/lib/icons'
import { validateControlRequest } from '../../../codex-link-control'
import type {
CodexLinkContextView,
CodexLinkControlRequest,
CodexLinkControlResponse,
CodexLinkSettingsResult,
CodexLinkSettingsUpdate,
CodexLinkStatusSnapshot
} from '../../../codex-link-control.types'
import { SettingsContent, SettingsSection } from './primitives'
const PENDING_KEY = 'aiturk.codex-link.pending-control.v1'
function envelope() {
return {
contract_version: 'codex-link/1' as const,
request_id: crypto.randomUUID(),
operation_id: crypto.randomUUID()
}
}
function readPending(): CodexLinkControlRequest | null {
try {
const raw = sessionStorage.getItem(PENDING_KEY)
if (!raw) {
return null
}
const value: unknown = JSON.parse(raw)
validateControlRequest(value)
return ['settings_update', 'catalog_sync', 'context_compact_view'].includes(value.kind) ? value : null
} catch {
return null
}
}
function savedPending(value: CodexLinkControlRequest | null) {
if (value) {
sessionStorage.setItem(PENDING_KEY, JSON.stringify(value))
} else {
sessionStorage.removeItem(PENDING_KEY)
}
}
function Preference({
id,
label,
description,
checked,
disabled,
onChange
}: {
id: string
label: string
description: string
checked: boolean
disabled: boolean
onChange: (value: boolean) => void
}) {
return (
<label className="flex min-h-11 cursor-pointer items-center gap-4 py-3" htmlFor={id}>
<span className="min-w-0 flex-1">
<span className="block text-sm font-medium">{label}</span>
<span className="mt-1 block text-sm text-muted-foreground">{description}</span>
</span>
<Switch aria-label={label} checked={checked} disabled={disabled} id={id} onCheckedChange={onChange} />
</label>
)
}
export function CodexLinkSettings() {
const copy = useCodexLinkCopy()
const [settings, setSettings] = useState<CodexLinkSettingsResult | null>(null)
const [status, setStatus] = useState<CodexLinkStatusSnapshot | null>(null)
const [views, setViews] = useState<CodexLinkContextView[]>([])
const [threshold, setThreshold] = useState('')
const [busy, setBusy] = useState(false)
const [pending, setPending] = useState(readPending)
const [error, setError] = useState('')
const [notice, setNotice] = useState('')
const mounted = useRef(true)
const writeLock = useRef(false)
const readLock = useRef(false)
const generation = useRef(0)
const savedRevision = useRef(-1)
const bridge = window.hermesDesktop?.codexLink
const refresh = useCallback(async () => {
if (!bridge || readLock.current || writeLock.current) {
return
}
readLock.current = true
const current = generation.current
try {
const results = await Promise.all(
['settings_get', 'status_get', 'context_views_get'].map(kind =>
bridge.control({ ...envelope(), kind } as CodexLinkControlRequest)
)
)
if (!mounted.current || current !== generation.current) {
return
}
const [saved, live, chats] = results
if (saved.kind !== 'settings_result' || live.kind !== 'status_result' || chats.kind !== 'context_views_result') {
throw new Error('read_failed')
}
// A write in another window may fall between these three reads. Never paint a mixed revision.
if (
saved.settings.settings_revision !== live.status.settings_revision ||
saved.settings.settings_revision !== chats.settings_revision
) {
return
}
if (saved.settings.settings_revision < savedRevision.current) {
return
}
if (savedRevision.current !== saved.settings.settings_revision) {
setThreshold(String(saved.settings.threshold_tokens))
}
savedRevision.current = saved.settings.settings_revision
setSettings(saved)
setStatus(live.status)
setViews(chats.views)
setError('')
} catch {
if (mounted.current && current === generation.current) {
setError(copy.failed)
}
} finally {
readLock.current = false
}
}, [bridge, copy.failed])
// This is a component lifetime flag, not a mirror of a reactive atom/value.
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
mounted.current = true
void refresh()
const timer = setInterval(() => {
if (document.visibilityState === 'visible') {
void refresh()
}
}, 5000)
return () => {
mounted.current = false
// The current numeric generation is invalidated; it is not a captured DOM ref.
// eslint-disable-next-line react-hooks/exhaustive-deps
generation.current++
clearInterval(timer)
}
}, [refresh])
const send = async (request: CodexLinkControlRequest) => {
if (!bridge || writeLock.current) {
return
}
writeLock.current = true
generation.current++
setBusy(true)
setError('')
setNotice('')
// Persist the exact operation before dispatch: an uncertain reply must reuse this ID and body.
try {
savedPending(request)
setPending(request)
} catch {
writeLock.current = false
setBusy(false)
setError(copy.failed)
return
}
try {
const result: CodexLinkControlResponse = await bridge.control(request)
if (!mounted.current) {
return
}
if (
(result.kind === 'control_error' && result.error.recovery_action === 'reconcile_only') ||
(result.kind === 'control_accepted' && result.operation_state === 'indeterminate')
) {
setError(copy.unknown)
return
}
savedPending(null)
setPending(null)
if (result.kind === 'control_error') {
setNotice(
result.error.code.includes('conflict') || result.error.code.includes('cas')
? copy.conflict
: result.error.message
)
} else if (result.kind === 'settings_result') {
savedRevision.current = result.settings.settings_revision
setSettings(result)
setThreshold(String(result.settings.threshold_tokens))
setNotice(copy.saved)
} else if (result.kind === 'control_accepted') {
setNotice(result.error?.message ?? copy.accepted)
}
} catch {
if (mounted.current) {
setError(copy.unknown)
}
} finally {
writeLock.current = false
if (mounted.current) {
setBusy(false)
void refresh()
}
}
}
const update = (patch: CodexLinkSettingsUpdate['patch']) => {
if (settings && !pending) {
void send({
...envelope(),
kind: 'settings_update',
expected_settings_revision: settings.settings.settings_revision,
patch
})
}
}
const thresholdValue = /^[0-9]+$/.test(threshold) ? Number(threshold) : NaN
const validThreshold = Number.isSafeInteger(thresholdValue) && thresholdValue > 0
const locked = busy || pending !== null
const number = (n: number | null) => (n === null ? copy.unmeasured : n.toLocaleString())
return (
<SettingsContent>
<div className="min-w-0 py-4" data-testid="codex-link-settings">
<SettingsSection icon={Cpu} title={copy.title}>
<p className="mb-4 text-sm text-muted-foreground">{copy.intro}</p>
{!bridge ? (
<p role="alert">{copy.unavailable}</p>
) : !settings ? (
<p role="status">{error || copy.loading}</p>
) : (
<>
<Preference
checked={settings.settings.enabled}
description={copy.enabledHelp}
disabled={locked}
id="codex-link-enabled"
label={copy.enabled}
onChange={enabled => update({ enabled })}
/>
<Preference
checked={settings.settings.compression_enabled}
description={copy.compressionHelp}
disabled={locked}
id="codex-link-compression"
label={copy.compression}
onChange={compression_enabled => update({ compression_enabled })}
/>
<div className="my-4 space-y-2">
<label className="block text-sm font-medium" htmlFor="codex-link-threshold">
{copy.threshold}
</label>
<div className="flex flex-wrap gap-2">
<Input
aria-describedby="codex-link-threshold-help"
aria-invalid={!validThreshold}
className="min-h-11 w-full min-w-0 sm:w-48"
disabled={locked}
id="codex-link-threshold"
inputMode="numeric"
onChange={event => setThreshold(event.target.value)}
value={threshold}
/>
<Button
className="min-h-11"
disabled={locked || !validThreshold || thresholdValue === settings.settings.threshold_tokens}
onClick={() => update({ threshold_tokens: thresholdValue })}
>
{copy.save}
</Button>
</div>
<p className="text-sm text-muted-foreground" id="codex-link-threshold-help">
{validThreshold ? copy.thresholdHelp : copy.invalidThreshold}
</p>
</div>
<div className="my-4 space-y-2">
<label className="block text-sm font-medium" htmlFor="codex-link-source">
{copy.source}
</label>
{settings.available_sources.length ? (
<select
className="min-h-11 w-full min-w-0 rounded-md border bg-background px-3 text-sm"
disabled={locked}
id="codex-link-source"
onChange={event => update({ source_binding_handle: event.target.value })}
value={settings.settings.source_binding_handle ?? ''}
>
{!settings.settings.source_binding_handle && (
<option disabled value="">
{copy.noSource}
</option>
)}
{settings.available_sources.map(source => (
<option
disabled={!source.authorized}
key={source.source_binding_handle}
value={source.source_binding_handle}
>
{source.label}
</option>
))}
</select>
) : (
<p className="text-sm text-muted-foreground">{copy.noSource}</p>
)}
</div>
<p className="text-sm" role="status">
{copy.activation[settings.settings.activation_state]}
{status ? ` · ${copy.admission[status.external_admission]}` : ''}
</p>
{status?.needs_restart && <p className="mt-2 text-sm text-muted-foreground">{copy.restart}</p>}
{status?.error && (
<p className="mt-2 text-sm" role="alert">
{status.error.message}
</p>
)}
<div className="mt-4 flex flex-wrap gap-2">
<Button
className="min-h-11"
disabled={locked}
onClick={() => void send({ ...envelope(), kind: 'catalog_sync' })}
variant="outline"
>
{copy.sync}
</Button>
<Button className="min-h-11" disabled={busy} onClick={() => void refresh()} variant="outline">
{copy.refresh}
</Button>
</div>
</>
)}
{pending && (
<div className="mt-4 space-y-2">
<p className="text-sm" role="alert">
{copy.unknown}
</p>
<Button className="min-h-11" disabled={busy} onClick={() => void send(pending)}>
{copy.reconcile}
</Button>
</div>
)}
{error && settings && (
<p className="mt-3 text-sm" role="alert">
{error}
</p>
)}
{notice && (
<p className="mt-3 text-sm" role="status">
{notice}
</p>
)}
</SettingsSection>
{settings && (
<SettingsSection icon={Cpu} title={copy.chats}>
{!views.length && <p className="text-sm text-muted-foreground">{copy.empty}</p>}
{views.map(view => {
const target = view.target_budgets?.find(item => item.target_id === view.limiting_target_id)
const hardLimit = target
? Math.min(
target.max_input_tokens ?? Number.MAX_SAFE_INTEGER,
target.context_window_tokens - target.output_cap_tokens - target.safety_margin_tokens
)
: null
const limit =
hardLimit === null
? null
: settings.settings.compression_enabled
? Math.min(settings.settings.threshold_tokens, hardLimit)
: hardLimit
return (
<article className="min-w-0 border-b py-4 last:border-b-0" key={view.view_handle}>
<h3 className="break-words text-sm font-medium">{view.label}</h3>
<p className="break-words text-sm text-muted-foreground">{view.current_model_label}</p>
<dl className="my-3 grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)] gap-2 text-sm">
<dt>{copy.current}</dt>
<dd className="break-words">{number(target?.input_tokens ?? null)}</dd>
<dt>{copy.limit}</dt>
<dd className="break-words">{number(limit)}</dd>
<dt>{copy.summaries}</dt>
<dd>
{view.active_summary_count} · {number(view.summary_tokens)}{' '}
{view.summary_tokens !== null ? copy.tokens : ''}
</dd>
</dl>
{view.last_operation_state && (
<p className="my-2 text-sm" role="status">
{copy.operation[view.last_operation_state]}
</p>
)}
{view.last_operation_error && (
<p className="my-2 text-sm" role="alert">
{view.last_operation_error.message}
</p>
)}
{view.compact_attempts.map(attempt => (
<p className="break-words text-sm text-muted-foreground" key={attempt.attempt_id}>
{attempt.member_label} · {copy.operation[attempt.state]}
</p>
))}
<Button
className="mt-2 min-h-11"
disabled={locked || !view.can_compact}
onClick={() =>
void send({
...envelope(),
kind: 'context_compact_view',
view_handle: view.view_handle,
expected_checkpoint_revision: view.checkpoint_revision
})
}
variant="outline"
>
{copy.compact}
</Button>
</article>
)
})}
</SettingsSection>
)}
</div>
</SettingsContent>
)
}
+1 -14
View File
@@ -9,7 +9,6 @@ import { Tip } from '@/components/ui/tooltip'
import { getHermesConfigDefaults, getHermesConfigRecord, saveHermesConfig } from '@/hermes'
import { useI18n } from '@/i18n'
import { useAiturkCopy } from '@/i18n/aiturk'
import { useCodexLinkCopy } from '@/i18n/codex-link'
import { triggerHaptic } from '@/lib/haptics'
import {
Archive,
@@ -48,7 +47,6 @@ import { AboutSettings } from './about-settings'
import { AppearanceSettings } from './appearance-settings'
import { BillingSettings } from './billing'
import { ConfigSettings } from './config-settings'
import { CodexLinkSettings } from './codex-link-settings'
import { SECTIONS } from './constants'
import { GatewaySettings } from './gateway-settings'
import { KeybindSettings } from './keybind-settings'
@@ -63,7 +61,6 @@ const SETTINGS_VIEWS: readonly SettingsViewId[] = [
...SECTIONS.map(s => `config:${s.id}` as SettingsViewId),
'providers',
'gateway',
'codex-link',
// Legacy alias: the Connections page merged into Gateways. Kept in the enum
// so saved `?tab=connections` deep links still resolve (redirected below).
'connections',
@@ -79,7 +76,6 @@ const SETTINGS_VIEWS: readonly SettingsViewId[] = [
export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: SettingsPageProps) {
const { t } = useI18n()
const aiturkCopy = useAiturkCopy()
const codexLinkCopy = useCodexLinkCopy()
const navigate = useNavigate()
const { hash, pathname, search } = useLocation()
@@ -256,13 +252,6 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set
label: t.settings.nav.gateway,
onSelect: () => setActiveView('gateway')
},
{
active: activeView === 'codex-link',
icon: Cpu,
id: 'codex-link',
label: codexLinkCopy.title,
onSelect: () => setActiveView('codex-link')
},
// "Telefon bağlantısı" lives in its own small window (QR + code); this
// entry only opens it. Present only where the desktop bridge offers it.
...(window.hermesDesktop?.aiturkMobile
@@ -331,7 +320,7 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set
onSelect: () => setActiveView('about')
}
],
[activeView, keysView, providerView, t, aiturkCopy, codexLinkCopy, setActiveView, openProviderView, openKeysView]
[activeView, keysView, providerView, t, aiturkCopy, setActiveView, openProviderView, openKeysView]
)
// Type-to-search: printable keystrokes on the Settings surface (outside any
@@ -425,8 +414,6 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set
// 'connections' renders the unified page too so the frame before
// the alias redirect lands doesn't flash the fallback view.
<GatewaySettings />
) : activeView === 'codex-link' ? (
<CodexLinkSettings />
) : activeView === 'keybinds' ? (
<KeybindSettings />
) : activeView.startsWith('config:') ? (
-1
View File
@@ -8,7 +8,6 @@ export type SettingsView =
| 'about'
| 'billing'
| 'connections'
| 'codex-link'
| 'gateway'
| 'keybinds'
| 'keys'
@@ -39,7 +39,7 @@ describe('ShareControls', () => {
const dialog = await screen.findByRole('dialog')
const input = within(dialog).getByRole('textbox')
fireEvent.change(input, { target: { value: 'not a valid code !!!' } })
fireEvent.click(within(dialog).getByRole('button', { name: 'Yükle' }))
fireEvent.click(within(dialog).getByRole('button', { name: 'Yükle', exact: true }))
expect(within(dialog).getByText('Bu metin bir harita koduna benzemiyor.')).toBeTruthy()
expect((input as HTMLTextAreaElement).value).toBe('not a valid code !!!')
})
-2
View File
@@ -2,7 +2,6 @@ import type { GatewayWsUrlResult } from '@hermes/shared'
import type { TranslucencyState } from '@hermes/shared/translucency'
import type { PoolLimits } from '../electron/pool-limits'
import type { CodexLinkControlRequest, CodexLinkControlResponse } from '../codex-link-control.types'
import type { WakeIndicatorState } from './lib/wake-indicator'
import type {
@@ -18,7 +17,6 @@ export {}
declare global {
interface Window {
hermesDesktop: {
codexLink?: { control: (request: CodexLinkControlRequest) => Promise<CodexLinkControlResponse> }
setUiLanguage?: (locale: string) => void
// Resolve a backend connection. Omit `profile` (or pass the primary) for
// the window's backend; pass a named profile to lazily spawn/reuse that
-121
View File
@@ -1,121 +0,0 @@
import { useI18n } from './context'
const en = {
title: 'Connect Codex',
intro: 'Use this IDE’s models and combos in Codex. Each chat keeps its own memory.',
enabled: 'Connect models to Codex',
enabledHelp: 'Enabled on first installation. Your choice is saved.',
compression: 'Automatic compaction',
compressionHelp:
'Checks the complete request before sending. Earlier summaries are retained as the available context allows.',
threshold: 'Compaction threshold (tokens)',
thresholdHelp:
'The effective threshold is lower when a model has less available input space. Output space is reserved too.',
invalidThreshold: 'Enter a positive whole number.',
save: 'Save',
source: 'Connected account',
noSource: 'No authorized model source is available.',
sync: 'Refresh models',
refresh: 'Refresh status',
loading: 'Reading saved settings…',
unavailable: 'The local Codex connection service is unavailable. Check the AITURK installation.',
failed: 'Could not read the latest state.',
unknown: 'The result is not confirmed. Check this same operation before making another change.',
reconcile: 'Check operation',
conflict: 'Settings changed elsewhere. The latest saved state has been loaded.',
saved: 'Settings saved.',
accepted: 'Operation accepted. Its progress appears below.',
chats: 'Chat memory',
empty: 'No managed Codex chat is currently visible.',
compact: 'Compact now',
summaries: 'Saved summaries',
tokens: 'tokens',
unmeasured: 'Not measured yet',
current: 'Current input',
limit: 'Effective threshold',
restart: 'The next Codex launch will use the new runtime. Existing chats keep their current process.',
activation: {
disabled: 'Disabled',
pending_runtime_acceptance: 'Runtime verification pending',
ready: 'Ready',
restart_required: 'Restart required',
conflict: 'Configuration conflict',
recovery_pending: 'Recovering the saved state'
},
admission: {
open: 'External models available',
closed: 'External models unavailable',
draining: 'Finishing active work',
recovery_required: 'Operation recovery required'
},
operation: {
accepted: 'Accepted',
prepared: 'Prepared',
running: 'Running',
completed: 'Completed',
failed: 'Failed',
indeterminate: 'Result unknown'
}
}
const tr: typeof en = {
title: 'Codex Bağlama',
intro: 'Bu IDE’deki modelleri ve comboları Codex’te kullanın. Her sohbet kendi hafızasını tutar.',
enabled: 'Modelleri Codex’e bağla',
enabledHelp: 'İlk kurulumda açıktır. Değiştirdiğiniz tercih kaydedilir.',
compression: 'Otomatik sıkıştırma',
compressionHelp:
'Gönderilmeden önce isteğin tamamı kontrol edilir. Önceki özetler kullanılabilir bağlamın izin verdiği ölçüde korunur.',
threshold: 'Sıkıştırma eşiği (token)',
thresholdHelp:
'Modelin kullanılabilir giriş alanı daha küçükse etkin eşik düşer. Yanıt için gereken alan da ayrılır.',
invalidThreshold: 'Pozitif bir tam sayı girin.',
save: 'Kaydet',
source: 'Bağlı hesap',
noSource: 'Yetkili model kaynağı bulunamadı.',
sync: 'Modelleri yenile',
refresh: 'Durumu yenile',
loading: 'Kayıtlı ayarlar okunuyor…',
unavailable: 'Yerel Codex bağlantı hizmetine ulaşılamıyor. AITURK kurulumunu kontrol edin.',
failed: 'Güncel durum okunamadı.',
unknown: 'İşlemin sonucu henüz doğrulanmadı. Yeni değişiklikten önce aynı işlemin durumunu kontrol edin.',
reconcile: 'İşlemi kontrol et',
conflict: 'Ayarlar başka bir yerde değişti. Güncel kayıtlı ayarlar yüklendi.',
saved: 'Ayarlar kaydedildi.',
accepted: 'İşlem kabul edildi. İlerlemesi aşağıda gösterilir.',
chats: 'Sohbet hafızası',
empty: 'Şu anda görünen yönetilen Codex sohbeti yok.',
compact: 'Şimdi sıkıştır',
summaries: 'Kayıtlı özetler',
tokens: 'token',
unmeasured: 'Henüz ölçülmedi',
current: 'Mevcut giriş',
limit: 'Etkin eşik',
restart: 'Yeni runtime sonraki Codex açılışında kullanılacak. Açık sohbetler mevcut süreçleriyle devam eder.',
activation: {
disabled: 'Kapalı',
pending_runtime_acceptance: 'Runtime doğrulaması bekleniyor',
ready: 'Hazır',
restart_required: 'Yeniden açılış gerekli',
conflict: 'Ayar çakışması',
recovery_pending: 'Kayıtlı durum kurtarılıyor'
},
admission: {
open: 'Harici modeller kullanılabilir',
closed: 'Harici modeller kullanılamıyor',
draining: 'Etkin işler bitiriliyor',
recovery_required: 'İşlem durumu uzlaştırılmalı'
},
operation: {
accepted: 'Kabul edildi',
prepared: 'Hazırlandı',
running: 'Çalışıyor',
completed: 'Tamamlandı',
failed: 'Başarısız',
indeterminate: 'Sonuç belirsiz'
}
}
export function useCodexLinkCopy() {
return useI18n().locale === 'tr' ? tr : en
}
+1 -1
View File
@@ -16,6 +16,6 @@
"rootDir": "..",
"outDir": "build/electron-types"
},
"include": ["electron", "codex-link-control.ts", "codex-link-control.types.ts", "codex-link-control.schema.json", "codex-link-json.ts", "../shared/src/data-url-read-max.ts", "../shared/src/translucency.ts"],
"include": ["electron", "../shared/src/data-url-read-max.ts", "../shared/src/translucency.ts"],
"exclude": ["src", "electron/**/*.e2e.mts"]
}
@@ -1,2 +0,0 @@
<!doctype html>
<html lang="tr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Codex Bağlama kabul görünümü</title></head><body><div id="root"></div><script type="module" src="../src/visual-fixtures/codex-link-settings.tsx"></script></body></html>
-70
View File
@@ -1,70 +0,0 @@
"""Generate the IDE's narrow control contract from the reviewed gateway schema.
Usage: python generate-codex-link-control.py PATH_TO_SCHEMA [--check]
No runtime/namespace/archive messages are included in this surface.
"""
import hashlib
import json
from pathlib import Path
import sys
root = Path(__file__).resolve().parents[1]
schema = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
definitions = schema["$defs"]
selected = {}
def collect(name):
if name in selected:
return
selected[name] = definitions[name]
def visit(value):
if isinstance(value, dict):
if "$ref" in value:
collect(value["$ref"].removeprefix("#/$defs/"))
for child in value.values():
visit(child)
elif isinstance(value, list):
for child in value:
visit(child)
visit(selected[name])
for name in ("control_request", "control_response"):
collect(name)
def typename(name):
return "CodexLink" + "".join(part.title() for part in name.split("_"))
def ts(rule):
if "$ref" in rule:
return typename(rule["$ref"].split("/")[-1])
if "const" in rule:
return json.dumps(rule["const"], ensure_ascii=False)
if "enum" in rule:
return " | ".join(json.dumps(x, ensure_ascii=False) for x in rule["enum"])
for key in ("oneOf", "anyOf"):
if key in rule:
return "(" + " | ".join(ts(x) for x in rule[key]) + ")"
kind = rule["type"]
if kind == "object":
return "{ " + "; ".join(json.dumps(k) + ("" if k in rule["required"] else "?") + ": " + ts(v) for k, v in rule["properties"].items()) + " }"
if kind == "array":
return "Array<" + ts(rule["items"]) + ">"
return {"integer": "number", "boolean": "boolean", "string": "string", "null": "null"}[kind]
subset = {"$defs": {k: selected[k] for k in sorted(selected)}}
canonical = json.dumps(subset, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()
digest = "sha256:" + hashlib.sha256(canonical).hexdigest()
types = "// Generated by scripts/generate-codex-link-control.py. Do not edit.\n"
types += "export const CODEX_LINK_CONTROL_SCHEMA_HASH = " + json.dumps(digest) + "\n"
types += "\n".join("export type " + typename(k) + " = " + ts(v) for k, v in sorted(selected.items())) + "\n"
outputs = {
root / "apps/desktop/codex-link-control.schema.json": json.dumps(subset, ensure_ascii=False, indent=2) + "\n",
root / "apps/desktop/codex-link-control.types.ts": types,
}
for path, content in outputs.items():
if "--check" in sys.argv:
if not path.exists() or path.read_text(encoding="utf-8") != content:
raise SystemExit("Generated control contract is stale: " + str(path))
else:
path.write_text(content, encoding="utf-8", newline="\n")
print(digest)