diff --git a/apps/desktop/codex-link-control.schema.json b/apps/desktop/codex-link-control.schema.json deleted file mode 100644 index 342d4d6..0000000 --- a/apps/desktop/codex-link-control.schema.json +++ /dev/null @@ -1,1096 +0,0 @@ -{ - "$defs": { - "catalog_sync": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "catalog_sync" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - } - } - }, - "context_compact_view": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id", - "view_handle", - "expected_checkpoint_revision" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "context_compact_view" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "view_handle": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "expected_checkpoint_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - } - } - }, - "context_view": { - "type": "object", - "additionalProperties": false, - "required": [ - "view_handle", - "can_compact", - "label", - "checkpoint_revision", - "current_model_label", - "limiting_target_id", - "target_budgets", - "active_summary_count", - "summary_tokens", - "summary_token_measurement", - "last_operation_id", - "last_operation_state", - "last_operation_error", - "compact_attempts" - ], - "properties": { - "view_handle": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "can_compact": { - "type": "boolean" - }, - "label": { - "type": "string", - "minLength": 1 - }, - "checkpoint_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "current_model_label": { - "type": "string", - "minLength": 1 - }, - "limiting_target_id": { - "anyOf": [ - { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - { - "type": "null" - } - ] - }, - "target_budgets": { - "anyOf": [ - { - "type": "array", - "items": { - "$ref": "#/$defs/target_budget" - }, - "minItems": 1 - }, - { - "type": "null" - } - ] - }, - "active_summary_count": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "summary_tokens": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - { - "type": "null" - } - ] - }, - "summary_token_measurement": { - "anyOf": [ - { - "type": "object", - "additionalProperties": false, - "required": [ - "target_id", - "tokenizer_id", - "tokenizer_revision", - "count_method" - ], - "properties": { - "target_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "tokenizer_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "tokenizer_revision": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "count_method": { - "enum": [ - "exact_tokenizer", - "verified_upper_bound" - ] - } - } - }, - { - "type": "null" - } - ] - }, - "last_operation_id": { - "anyOf": [ - { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - { - "type": "null" - } - ] - }, - "last_operation_state": { - "anyOf": [ - { - "enum": [ - "accepted", - "running", - "completed", - "failed", - "indeterminate" - ] - }, - { - "type": "null" - } - ] - }, - "last_operation_error": { - "anyOf": [ - { - "$ref": "#/$defs/error" - }, - { - "type": "null" - } - ] - }, - "compact_attempts": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "attempt_id", - "member_label", - "state", - "usage" - ], - "properties": { - "attempt_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "member_label": { - "type": "string", - "minLength": 1 - }, - "state": { - "enum": [ - "prepared", - "running", - "completed", - "failed", - "indeterminate" - ] - }, - "usage": { - "$ref": "#/$defs/usage" - } - } - }, - "minItems": 0 - } - } - }, - "context_views_get": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "context_views_get" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - } - } - }, - "context_views_result": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id", - "settings_revision", - "views" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "context_views_result" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "settings_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "views": { - "type": "array", - "items": { - "$ref": "#/$defs/context_view" - }, - "minItems": 0 - } - } - }, - "control_accepted": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id", - "settings_revision", - "operation_state", - "error" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "control_accepted" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "settings_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "operation_state": { - "enum": [ - "accepted", - "running", - "completed", - "failed", - "indeterminate" - ] - }, - "error": { - "anyOf": [ - { - "$ref": "#/$defs/error" - }, - { - "type": "null" - } - ] - } - } - }, - "control_error": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id", - "settings_revision", - "error" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "control_error" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "settings_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "error": { - "$ref": "#/$defs/error" - } - } - }, - "control_request": { - "oneOf": [ - { - "$ref": "#/$defs/settings_get" - }, - { - "$ref": "#/$defs/settings_update" - }, - { - "$ref": "#/$defs/status_get" - }, - { - "$ref": "#/$defs/catalog_sync" - }, - { - "$ref": "#/$defs/context_views_get" - }, - { - "$ref": "#/$defs/context_compact_view" - } - ] - }, - "control_response": { - "oneOf": [ - { - "$ref": "#/$defs/settings_result" - }, - { - "$ref": "#/$defs/status_result" - }, - { - "$ref": "#/$defs/context_views_result" - }, - { - "$ref": "#/$defs/control_accepted" - }, - { - "$ref": "#/$defs/control_error" - } - ] - }, - "error": { - "type": "object", - "additionalProperties": false, - "required": [ - "code", - "message", - "recovery_action" - ], - "properties": { - "code": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "message": { - "type": "string", - "minLength": 1 - }, - "recovery_action": { - "enum": [ - "retry_control_read", - "reconcile_only", - "repair_required", - "select_route", - "reconfigure", - "none" - ] - } - } - }, - "output_cap_enforcement": { - "oneOf": [ - { - "type": "object", - "additionalProperties": false, - "required": [ - "method", - "field", - "value", - "proof_revision", - "verified" - ], - "properties": { - "method": { - "const": "request_field" - }, - "field": { - "enum": [ - "max_output_tokens", - "max_tokens", - "max_completion_tokens", - "num_predict" - ] - }, - "value": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "proof_revision": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "verified": { - "const": true - } - } - }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "method", - "value", - "proof_revision", - "verified" - ], - "properties": { - "method": { - "const": "provider_fixed_maximum" - }, - "value": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "proof_revision": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "verified": { - "const": true - } - } - } - ] - }, - "settings": { - "type": "object", - "additionalProperties": false, - "required": [ - "settings_revision", - "enabled", - "compression_enabled", - "threshold_tokens", - "source_binding_handle", - "compact_route_id", - "legacy_intent_imported", - "ownership_manifest_revision", - "activation_state" - ], - "properties": { - "settings_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "enabled": { - "type": "boolean" - }, - "compression_enabled": { - "type": "boolean" - }, - "threshold_tokens": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "source_binding_handle": { - "anyOf": [ - { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - { - "type": "null" - } - ] - }, - "compact_route_id": { - "anyOf": [ - { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - { - "type": "null" - } - ] - }, - "legacy_intent_imported": { - "type": "boolean" - }, - "ownership_manifest_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "activation_state": { - "enum": [ - "disabled", - "pending_runtime_acceptance", - "ready", - "restart_required", - "conflict", - "recovery_pending" - ] - } - } - }, - "settings_get": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "settings_get" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - } - } - }, - "settings_result": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id", - "settings", - "available_sources" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "settings_result" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "settings": { - "$ref": "#/$defs/settings" - }, - "available_sources": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "source_binding_handle", - "label", - "authorized" - ], - "properties": { - "source_binding_handle": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "label": { - "type": "string", - "minLength": 1 - }, - "authorized": { - "type": "boolean" - } - } - }, - "minItems": 0 - } - } - }, - "settings_update": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id", - "expected_settings_revision", - "patch" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "settings_update" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "expected_settings_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "patch": { - "type": "object", - "additionalProperties": false, - "required": [], - "properties": { - "enabled": { - "type": "boolean" - }, - "compression_enabled": { - "type": "boolean" - }, - "threshold_tokens": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "source_binding_handle": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - } - }, - "minProperties": 1 - } - } - }, - "status_get": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "status_get" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - } - } - }, - "status_result": { - "type": "object", - "additionalProperties": false, - "required": [ - "contract_version", - "kind", - "request_id", - "operation_id", - "status" - ], - "properties": { - "contract_version": { - "const": "codex-link/1" - }, - "kind": { - "const": "status_result" - }, - "request_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "operation_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "status": { - "$ref": "#/$defs/status_snapshot" - } - } - }, - "status_snapshot": { - "type": "object", - "additionalProperties": false, - "required": [ - "settings_revision", - "runtime_mode", - "native_available", - "external_admission", - "needs_restart", - "catalog_state", - "active_turns", - "uncertain_operations", - "last_operation_id", - "error" - ], - "properties": { - "settings_revision": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "runtime_mode": { - "enum": [ - "native", - "managed", - "unknown" - ] - }, - "native_available": { - "type": "boolean" - }, - "external_admission": { - "enum": [ - "open", - "closed", - "draining", - "recovery_required" - ] - }, - "needs_restart": { - "type": "boolean" - }, - "catalog_state": { - "enum": [ - "unknown", - "fresh", - "stale", - "unauthorized", - "empty" - ] - }, - "active_turns": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "uncertain_operations": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "last_operation_id": { - "anyOf": [ - { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - { - "type": "null" - } - ] - }, - "error": { - "anyOf": [ - { - "$ref": "#/$defs/error" - }, - { - "type": "null" - } - ] - } - } - }, - "target_budget": { - "type": "object", - "additionalProperties": false, - "required": [ - "target_id", - "canonical_leaf_id", - "limits_source", - "limits_revision", - "tokenizer_id", - "tokenizer_revision", - "count_method", - "serialization_revision", - "input_tokens", - "context_window_tokens", - "output_cap_tokens", - "safety_margin_tokens", - "max_input_tokens", - "max_output_tokens", - "request_bytes", - "max_request_bytes", - "output_cap_enforcement" - ], - "properties": { - "target_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "canonical_leaf_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "limits_source": { - "enum": [ - "provider_contract", - "provider_api", - "verified_adapter", - "verified_measurement" - ] - }, - "limits_revision": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "tokenizer_id": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "tokenizer_revision": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "count_method": { - "enum": [ - "exact_tokenizer", - "verified_upper_bound" - ] - }, - "serialization_revision": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - "input_tokens": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "context_window_tokens": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "output_cap_tokens": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "safety_margin_tokens": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "max_input_tokens": { - "anyOf": [ - { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - { - "type": "null" - } - ] - }, - "max_output_tokens": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "request_bytes": { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - "max_request_bytes": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "output_cap_enforcement": { - "$ref": "#/$defs/output_cap_enforcement" - } - } - }, - "usage": { - "type": "object", - "additionalProperties": false, - "required": [ - "input_tokens", - "output_tokens", - "measurement", - "cost_microunits", - "currency", - "cost_source" - ], - "properties": { - "input_tokens": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - { - "type": "null" - } - ] - }, - "output_tokens": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - { - "type": "null" - } - ] - }, - "measurement": { - "enum": [ - "measured", - "estimated", - "unknown" - ] - }, - "cost_microunits": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, - { - "type": "null" - } - ] - }, - "currency": { - "anyOf": [ - { - "const": "USD" - }, - { - "type": "null" - } - ] - }, - "cost_source": { - "anyOf": [ - { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" - }, - { - "type": "null" - } - ] - } - } - } - } -} diff --git a/apps/desktop/codex-link-control.ts b/apps/desktop/codex-link-control.ts deleted file mode 100644 index a4c8a77..0000000 --- a/apps/desktop/codex-link-control.ts +++ /dev/null @@ -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 -const definitions: Record = 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 -} diff --git a/apps/desktop/codex-link-control.types.ts b/apps/desktop/codex-link-control.types.ts deleted file mode 100644 index 4640651..0000000 --- a/apps/desktop/codex-link-control.types.ts +++ /dev/null @@ -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 | 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 } -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) } diff --git a/apps/desktop/codex-link-json.ts b/apps/desktop/codex-link-json.ts deleted file mode 100644 index a0e502d..0000000 --- a/apps/desktop/codex-link-json.ts +++ /dev/null @@ -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 - -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() - - 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 -} diff --git a/apps/desktop/electron/codex-link-client.test.ts b/apps/desktop/electron/codex-link-client.test.ts deleted file mode 100644 index 19f00d7..0000000 --- a/apps/desktop/electron/codex-link-client.test.ts +++ /dev/null @@ -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 = {}) { - 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) - }) -}) diff --git a/apps/desktop/electron/codex-link-client.ts b/apps/desktop/electron/codex-link-client.ts deleted file mode 100644 index d1e1fdc..0000000 --- a/apps/desktop/electron/codex-link-client.ts +++ /dev/null @@ -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)) - - return async function control(raw: unknown): Promise { - 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')) - } - }) - } -} diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index 3975059..a9817fe 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -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), diff --git a/apps/desktop/electron/mobile-lan-proxy.test.ts b/apps/desktop/electron/mobile-lan-proxy.test.ts index 4323088..db78da4 100644 --- a/apps/desktop/electron/mobile-lan-proxy.test.ts +++ b/apps/desktop/electron/mobile-lan-proxy.test.ts @@ -271,63 +271,6 @@ async function startProxy(over: Record = {}) { 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() diff --git a/apps/desktop/electron/mobile-lan-proxy.ts b/apps/desktop/electron/mobile-lan-proxy.ts index cb81d40..e10fc44 100644 --- a/apps/desktop/electron/mobile-lan-proxy.ts +++ b/apps/desktop/electron/mobile-lan-proxy.ts @@ -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 tls: { key: string; cert: string } /** Must resolve the LOCAL backend or throw (remote mode, backend down). */ resolveBackend: (profile: string) => Promise @@ -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 vault: MobileLanVault store: MobileLinkStore resolveBackend: (profile: string) => Promise @@ -1854,7 +1820,6 @@ export function createMobileLink(deps: MobileLinkDeps) { } proxy = createLanProxy({ - codexLinkControl: deps.codexLinkControl, tls: identity, resolveBackend: async profile => { if (deps.isRemoteMode()) { diff --git a/apps/desktop/electron/preload.ts b/apps/desktop/electron/preload.ts index 0729e16..a01fec0 100644 --- a/apps/desktop/electron/preload.ts +++ b/apps/desktop/electron/preload.ts @@ -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, diff --git a/apps/desktop/scripts/check-codex-link-layout.mjs b/apps/desktop/scripts/check-codex-link-layout.mjs deleted file mode 100644 index cb20b4a..0000000 --- a/apps/desktop/scripts/check-codex-link-layout.mjs +++ /dev/null @@ -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) -} diff --git a/apps/desktop/src/app/settings/codex-link-settings.test.tsx b/apps/desktop/src/app/settings/codex-link-settings.test.tsx deleted file mode 100644 index 94f6e91..0000000 --- a/apps/desktop/src/app/settings/codex-link-settings.test.tsx +++ /dev/null @@ -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 => { - 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( - - - - ) - -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, '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') - }) -}) diff --git a/apps/desktop/src/app/settings/codex-link-settings.tsx b/apps/desktop/src/app/settings/codex-link-settings.tsx deleted file mode 100644 index 66d41fb..0000000 --- a/apps/desktop/src/app/settings/codex-link-settings.tsx +++ /dev/null @@ -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 ( - - ) -} - -export function CodexLinkSettings() { - const copy = useCodexLinkCopy() - const [settings, setSettings] = useState(null) - const [status, setStatus] = useState(null) - const [views, setViews] = useState([]) - 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 ( - -
- -

{copy.intro}

- {!bridge ? ( -

{copy.unavailable}

- ) : !settings ? ( -

{error || copy.loading}

- ) : ( - <> - update({ enabled })} - /> - update({ compression_enabled })} - /> -
- -
- setThreshold(event.target.value)} - value={threshold} - /> - -
- -
-
- - {settings.available_sources.length ? ( - - ) : ( -

{copy.noSource}

- )} -
-

- {copy.activation[settings.settings.activation_state]} - {status ? ` · ${copy.admission[status.external_admission]}` : ''} -

- {status?.needs_restart &&

{copy.restart}

} - {status?.error && ( -

- {status.error.message} -

- )} -
- - -
- - )} - {pending && ( -
-

- {copy.unknown} -

- -
- )} - {error && settings && ( -

- {error} -

- )} - {notice && ( -

- {notice} -

- )} -
- {settings && ( - - {!views.length &&

{copy.empty}

} - {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 ( -
-

{view.label}

-

{view.current_model_label}

-
-
{copy.current}
-
{number(target?.input_tokens ?? null)}
-
{copy.limit}
-
{number(limit)}
-
{copy.summaries}
-
- {view.active_summary_count} · {number(view.summary_tokens)}{' '} - {view.summary_tokens !== null ? copy.tokens : ''} -
-
- {view.last_operation_state && ( -

- {copy.operation[view.last_operation_state]} -

- )} - {view.last_operation_error && ( -

- {view.last_operation_error.message} -

- )} - {view.compact_attempts.map(attempt => ( -

- {attempt.member_label} · {copy.operation[attempt.state]} -

- ))} - -
- ) - })} -
- )} -
-
- ) -} diff --git a/apps/desktop/src/app/settings/index.tsx b/apps/desktop/src/app/settings/index.tsx index 6e192c6..ea36d7f 100644 --- a/apps/desktop/src/app/settings/index.tsx +++ b/apps/desktop/src/app/settings/index.tsx @@ -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. - ) : activeView === 'codex-link' ? ( - ) : activeView === 'keybinds' ? ( ) : activeView.startsWith('config:') ? ( diff --git a/apps/desktop/src/app/settings/types.ts b/apps/desktop/src/app/settings/types.ts index 01e3e52..5ecdf27 100644 --- a/apps/desktop/src/app/settings/types.ts +++ b/apps/desktop/src/app/settings/types.ts @@ -8,7 +8,6 @@ export type SettingsView = | 'about' | 'billing' | 'connections' - | 'codex-link' | 'gateway' | 'keybinds' | 'keys' diff --git a/apps/desktop/src/app/starmap/share-controls.test.tsx b/apps/desktop/src/app/starmap/share-controls.test.tsx index e0d77de..5c90841 100644 --- a/apps/desktop/src/app/starmap/share-controls.test.tsx +++ b/apps/desktop/src/app/starmap/share-controls.test.tsx @@ -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 !!!') }) diff --git a/apps/desktop/src/global.d.ts b/apps/desktop/src/global.d.ts index c7e0c58..db4ac71 100644 --- a/apps/desktop/src/global.d.ts +++ b/apps/desktop/src/global.d.ts @@ -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 } 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 diff --git a/apps/desktop/src/i18n/codex-link.ts b/apps/desktop/src/i18n/codex-link.ts deleted file mode 100644 index 9f5e9f1..0000000 --- a/apps/desktop/src/i18n/codex-link.ts +++ /dev/null @@ -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 -} diff --git a/apps/desktop/tsconfig.electron.json b/apps/desktop/tsconfig.electron.json index 38d3bf1..058f132 100644 --- a/apps/desktop/tsconfig.electron.json +++ b/apps/desktop/tsconfig.electron.json @@ -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"] } diff --git a/apps/desktop/visual-fixtures/codex-link-settings.html b/apps/desktop/visual-fixtures/codex-link-settings.html deleted file mode 100644 index e5a7279..0000000 --- a/apps/desktop/visual-fixtures/codex-link-settings.html +++ /dev/null @@ -1,2 +0,0 @@ - -Codex Bağlama kabul görünümü
diff --git a/scripts/generate-codex-link-control.py b/scripts/generate-codex-link-control.py deleted file mode 100644 index 5e686d1..0000000 --- a/scripts/generate-codex-link-control.py +++ /dev/null @@ -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)