import type { ViewerSettings } from "@mission-core/plugin-sdk"; import { xgridsK1Actions, xgridsK1Manifest } from "./manifest"; const PLUGIN_ID = xgridsK1Manifest.metadata.id; export interface BleDevice { device_id: string; name?: string | null; rssi?: number | null; address?: string | null; connectable?: boolean | null; likely_k1?: boolean | null; } export type SourceMode = "idle" | "live" | "replay"; export const XGRIDS_ACTIVE_STREAM_RECOVERY_STATES = [ "inactive", "reconnecting", "blocked", "recovered", "standby", "fault", "force-finishing", "force-finished", ] as const; export type XgridsActiveStreamRecoveryState = typeof XGRIDS_ACTIVE_STREAM_RECOVERY_STATES[number]; export const XGRIDS_CAMERA_RECOVERY_STATES = [ "inactive", "owned", "blocked", ] as const; export type XgridsCameraRecoveryState = typeof XGRIDS_CAMERA_RECOVERY_STATES[number]; export const XGRIDS_CAMERA_MEDIA_STATES = [ "inactive", "pending-epoch", "pending-init", "pending-first-media", "ready", ] as const; export type XgridsCameraMediaState = typeof XGRIDS_CAMERA_MEDIA_STATES[number]; export const XGRIDS_CONNECTION_MODES = [ "bridge", "quick-connect", "direct-connect", ] as const; export type XgridsConnectionMode = typeof XGRIDS_CONNECTION_MODES[number]; export const XGRIDS_HOST_DIAGNOSTIC_CODES = [ "host.bluetooth.permission-denied", "host.bluetooth.adapter-powered-off", "host.bluetooth.adapter-unavailable", "host.bluetooth.runtime-unavailable", "host.bluetooth.operation-timeout", "host.wifi.permission-denied", "host.wifi.adapter-powered-off", "host.wifi.interface-unavailable", "host.wifi.ssid-unavailable", "host.wifi.operation-timeout", "host.wifi.association-failed", "host.keychain.interaction-required", "host.keychain.permission-denied", "host.keychain.unavailable", "host.route.unavailable", "host.tcp.connection-refused", "host.tcp.connection-timeout", "host.tcp.endpoint-unavailable", "host.mqtt.connection-timeout", "host.mqtt.connection-refused", "host.mqtt.transport-unavailable", "host.filesystem.permission-denied", "host.filesystem.ledger-unavailable", ] as const; export const XGRIDS_HOST_DIAGNOSTIC_DOMAINS = [ "corebluetooth", "corewlan", "keychain", "route", "tcp", "mqtt", "filesystem", ] as const; export const XGRIDS_HOST_DIAGNOSTIC_IMPACTS = [ "discovery", "host-network", "control", "durable-safety", ] as const; export const XGRIDS_HOST_DIAGNOSTIC_ACTIONS = [ "grant-bluetooth-permission", "power-on-bluetooth", "restore-bluetooth-adapter", "grant-wifi-permission", "power-on-wifi", "restore-wifi-interface", "unlock-or-authorize-keychain", "review-keychain-access", "join-expected-network", "inspect-host-route", "verify-broker-endpoint", "inspect-local-storage", "restart-local-service", "explicit-retry", ] as const; export type XgridsHostDiagnosticCode = typeof XGRIDS_HOST_DIAGNOSTIC_CODES[number]; export type XgridsHostDiagnosticDomain = typeof XGRIDS_HOST_DIAGNOSTIC_DOMAINS[number]; export type XgridsHostDiagnosticImpact = typeof XGRIDS_HOST_DIAGNOSTIC_IMPACTS[number]; export type XgridsHostDiagnosticAction = typeof XGRIDS_HOST_DIAGNOSTIC_ACTIONS[number]; export interface XgridsHostFailureDiagnostic { schema_version: "missioncore.host-failure-diagnostic/v1"; code: XgridsHostDiagnosticCode; domain: XgridsHostDiagnosticDomain; impact: XgridsHostDiagnosticImpact; operator_action: XgridsHostDiagnosticAction; automatic_retry: false; redacted: true; } export type AcquisitionState = | "preparing" | "prepared" | "awaiting_external_start" | "starting" | "acquiring" | "awaiting_external_stop" | "stopping" | "finalizing" | "completed" | "failed" | "aborted" | "interrupted"; export type OperationStatus = | "accepted" | "running" | "operator_action_required" | "succeeded" | "failed" | "cancelled" | "timed_out" | "interrupted"; export interface XgridsDeviceRef { device_id: string; model_id: string; identity_stability: "stable" | "provisional"; identity_basis: | "hardware-identifier" | "plugin-derived" | "operator-assigned" | "transport-local"; transport_alias?: string | null; } export interface XgridsDeviceSession { device_session_id: string; device_id: string; opened_at?: string | null; compatibility_profile_id?: string | null; connectivity?: "unknown" | "offline" | "connecting" | "connected" | "degraded"; } export const XGRIDS_CONNECTION_VERIFICATION_STATUSES = [ "not-probed", "device-network-applied", "device-network-applied-host-failed", "adopted", "host-route-mismatch", "endpoint-unreachable", "tcp-reachable-device-info-unverified", "reachable", "recovered", "control-transport-lost", "unreachable", ] as const; export const XGRIDS_CONNECTION_VERIFICATION_LEASE_STATES = [ "disconnected", "configured-unverified", "reachable", ] as const; export const XGRIDS_CONNECTION_NETWORK_REACHABILITY = [ "unknown", "reachable", "unreachable", ] as const; export type XgridsConnectionVerificationStatus = typeof XGRIDS_CONNECTION_VERIFICATION_STATUSES[number]; export type XgridsConnectionVerificationLeaseState = typeof XGRIDS_CONNECTION_VERIFICATION_LEASE_STATES[number]; export type XgridsConnectionNetworkReachability = typeof XGRIDS_CONNECTION_NETWORK_REACHABILITY[number]; export interface XgridsConnectionVerification { status: XgridsConnectionVerificationStatus; lease_state: XgridsConnectionVerificationLeaseState; lease_generation: number; supervisor_revision: number; endpoint_validation: string | null; network_reachability: XgridsConnectionNetworkReachability; host_route_class?: string | null; address_source?: string | null; connection_origin?: string | null; admission_source?: string | null; address_changed?: boolean; previous_address_present?: boolean; write_performed?: boolean; observed_at: string | null; last_known_reachable_at?: string | null; reason_code?: string | null; } export interface XgridsConfiguredEndpointProbe { schema_version: "missioncore.xgrids-k1-configured-endpoint-probe/v1"; status: "not-probed" | "reachable" | "endpoint-unreachable" | "host-route-unavailable"; target_source: "current-supervisor" | "durable-semantic-topology" | null; connection_mode: XgridsConnectionMode | null; endpoint: string | null; transport_ref: string | null; intent_id: string | null; semantic_revision: number | null; host_route_available: boolean | null; host_route_class: string | null; tcp_reachable: boolean | null; identity_validation: "not-performed"; control_authority_granted: false; ble_operation_performed: false; network_mutation_performed: false; automatic_retry: false; observed_at: string | null; reason_code: string | null; } export interface XgridsNetworkWriteReconciliation { status: | "prepared-before-dispatch" | "device-state-unknown-after-write" | "durable-ledger-corrupt"; operation_id: string; transport_ref: string; connection_mode: XgridsConnectionMode; operation_stage: "prepared" | "dispatching" | "observing" | "ledger-corrupt"; reason_code: string; device_write_confirmed: boolean; required_action: | "restart-service-to-resolve-prepared" | "explicit-read-only-ble-status-observation" | "operator-ledger-diagnosis"; scope: "durable-ledger"; ledger_revision: number | null; observed_at: string | null; } export interface XgridsNetworkMutationLedger { status: "empty" | "unresolved" | "resolved" | "corrupt"; mutation_allowed: boolean; reason_code: string | null; operation_id: string | null; transport_ref: string | null; intended_mode: XgridsConnectionMode | null; stage: "prepared" | "dispatching" | "observing" | "resolved" | null; revision: number | null; resolution: | "not-dispatched" | "target-observed" | "interrupted" | "superseded" | null; updated_at_utc: string | null; diagnostic?: XgridsHostFailureDiagnostic | null; } export interface XgridsCurrentDeviceRecovery { transport_ref?: string | null; connection_mode?: XgridsConnectionMode | null; handle_available?: boolean; handle_retained?: boolean; advertised_now?: boolean; gatt_validated_recently?: boolean; observed_at?: string | null; } export const XGRIDS_CONNECTION_POLICY_ACTIONS = [ "scan-ble", "provision-fresh-device", "prepare-select-device", "prepare-change-network", "cancel-reconfiguration", "recover-current-device-network", "observe-fresh-device-network", "observe-current-device-network", "observe-configured-device-network", "inspect-configured-endpoint", "inspect-host-network", "probe-endpoint", "verify-control-device-info", "start-acquisition", "stop-acquisition", "stop-local-receiver", "retire-unavailable-physical-target", "acknowledge-data-loss", ] as const; export type XgridsConnectionPolicyAction = typeof XGRIDS_CONNECTION_POLICY_ACTIONS[number]; export const XGRIDS_CONNECTION_POLICY_TARGET_SOURCES = [ "none", "fresh-scan", "retained-current-process", "durable-configured-state", "configured-topology", "connection-supervisor", "local-runtime", "local-prestart-handoff", "local-reconfiguration-intent", "durable-physical-command", ] as const; export type XgridsConnectionPolicyTargetSource = typeof XGRIDS_CONNECTION_POLICY_TARGET_SOURCES[number]; export interface XgridsConnectionPolicyDecision { allowed: boolean; reason_codes: string[]; target_source: XgridsConnectionPolicyTargetSource; required_transport_ref: string | null; required_connection_mode?: XgridsConnectionMode | null; requires_live_gatt_validation: boolean; automatic_retry: false; execution_mode?: "capture-only"; physical_command_allowed?: false; physical_outcome?: "unknown"; operator_follow_up?: "manual-device-stop-required"; device_write_performed?: false; } export interface XgridsConnectionPolicy { schema_version: "missioncore.xgrids-k1-connection-policy/v1"; supervisor_revision: number; network_ledger_revision: number | null; recommended_action: string; allowed_actions: XgridsConnectionPolicyAction[]; actions: Partial>; facts: { fresh_transport_refs: string[]; retained_transport_ref: string | null; retained_context_is_presence: false; network_mutation_status: "empty" | "unresolved" | "resolved" | "corrupt"; network_provisioning_idempotency_status: "empty" | "ready" | "blocked" | "corrupt"; network_provisioning_idempotency_available: boolean; network_provisioning_active_operation_id: string | null; network_provisioning_active_operation_matches_ledger: boolean; semantic_topology_store_status: "empty" | "available" | "corrupt"; device_identity_pin_store_status: "empty" | "available" | "corrupt"; physical_command_status: "empty" | "unresolved" | "resolved" | "corrupt"; physical_command_requires_reconciliation: boolean; retired_transport_refs?: string[]; eligible_fresh_transport_refs?: string[]; ble_runtime: { active_operation_kind: string | null; cleanup_pending: boolean; poisoned: boolean; }; lifecycle_process_lease_holders: Array<"control" | "network">; control_plane_state: "idle" | "healthy" | "stalled" | "lost"; data_plane_state: "idle" | "healthy" | "stalled" | "lost"; physical_network_state: "unknown" | "not-disputed"; }; } export const XGRIDS_CONNECTION_RECONFIGURATION_INTENTS = [ "select-device", "change-network", ] as const; export type XgridsConnectionReconfigurationIntent = typeof XGRIDS_CONNECTION_RECONFIGURATION_INTENTS[number]; export const XGRIDS_CONNECTION_RECONFIGURATION_STATUSES = [ "idle", "awaiting-fresh-scan", "fresh-scan-completed", ] as const; export type XgridsConnectionReconfigurationStatus = typeof XGRIDS_CONNECTION_RECONFIGURATION_STATUSES[number]; export interface XgridsConnectionReconfiguration { schema_version: "missioncore.xgrids-k1-connection-reconfiguration/v1"; revision: number; intent_id: string | null; intent: XgridsConnectionReconfigurationIntent | null; status: XgridsConnectionReconfigurationStatus; required_transport_ref: string | null; required_connection_mode: XgridsConnectionMode | null; minimum_discovery_generation: number | null; fresh_discovery_generation: number | null; required_transport_observed: boolean | null; prepared_at: string | null; automatic_retry: false; } export interface XgridsConnectionSupervisorTarget { ipv4: string; port: number; } export interface XgridsConnectionSupervisorIntent { intent_id: string; requested_mode: XgridsConnectionMode; expected_device_id: string | null; requested_at: string; } export interface XgridsConnectionSupervisorHostPath { epoch: number; available: boolean; fingerprint: string | null; interface: string | null; source_ipv4: string | null; route_class: "direct" | "default" | "tunnel" | "unavailable" | "unknown"; reason_code: string | null; observed_at: string; } export interface XgridsConnectionSupervisorEndpoint { target: XgridsConnectionSupervisorTarget | null; tcp_state: "unknown" | "reachable" | "unreachable"; intent_id: string | null; host_path_epoch: number | null; reason_code: string | null; observed_at: string | null; } export interface XgridsConnectionSupervisorDeviceNetwork { state: "unconfigured" | "applied"; intent_id: string | null; transport_ref: string | null; connection_mode: XgridsConnectionMode | null; target: XgridsConnectionSupervisorTarget | null; source: "ble-post-write-status" | "ble-read-only-status" | null; observed_at: string | null; } export interface XgridsConnectionSupervisorDeviceIdentity { state: "unverified" | "verified" | "stale" | "mismatch"; intent_id: string | null; logical_device_id: string | null; compatibility_profile_id: string | null; connection_mode: XgridsConnectionMode | null; source: "mqtt-device-info" | null; host_path_epoch: number | null; observed_at: string | null; } export interface XgridsConnectionSupervisorPlane { state: "idle" | "healthy" | "stalled" | "lost"; session_id: string | null; host_path_epoch: number | null; reason_code: string | null; observed_at: string | null; } export interface XgridsConnectionSupervisorLease { state: "absent" | "configured-unverified" | "reachable" | "lost"; generation: number; intent_id: string | null; host_path_epoch: number | null; connection_mode: XgridsConnectionMode | null; target: XgridsConnectionSupervisorTarget | null; logical_device_id: string | null; reason_code: string | null; observed_at: string | null; } export interface XgridsConnectionSupervisorAuthority { network_mutation_allowed: boolean; control_allowed: boolean; acquisition_start_allowed: boolean; data_ingest_authoritative: boolean; physical_motion_allowed: false; reason_codes: string[]; } export interface XgridsConnectionSupervisorLastKnown { connection_mode: XgridsConnectionMode; target: XgridsConnectionSupervisorTarget; logical_device_id: string; compatibility_profile_id: string; verified_at: string; } export type XgridsConnectionSupervisorAllowedAction = | "select-connection-intent" | "inspect-host-network" | "probe-endpoint" | "verify-control-device-info" | "start-acquisition" | "stop-acquisition" | "stop-local-receiver" | "acknowledge-data-loss"; export interface XgridsConnectionSupervisor { schema_version: "missioncore.k1-connection-supervisor/v1"; revision: number; closed: boolean; intent: XgridsConnectionSupervisorIntent | null; observed: { device_network: XgridsConnectionSupervisorDeviceNetwork; host_path: XgridsConnectionSupervisorHostPath; endpoint: XgridsConnectionSupervisorEndpoint; device_identity: XgridsConnectionSupervisorDeviceIdentity; control_plane: XgridsConnectionSupervisorPlane; data_plane: XgridsConnectionSupervisorPlane; }; lease: XgridsConnectionSupervisorLease; authority: XgridsConnectionSupervisorAuthority; last_known: XgridsConnectionSupervisorLastKnown | null; diagnostics?: XgridsHostFailureDiagnostic[]; allowed_actions: XgridsConnectionSupervisorAllowedAction[]; } export interface XgridsSemanticTopologyRecord { schema_version: "missioncore.xgrids-k1-semantic-topology/v1"; revision: number; transport_ref: string; connection_mode: XgridsConnectionMode; ipv4: string; compatibility_profile_id: string; firmware_version: string; source: "ble-post-write-status" | "ble-read-only-status"; observed_at_utc: string; } export interface XgridsSemanticTopologyStore { status: "empty" | "available" | "corrupt"; configured_offline_evidence: boolean; live_connection_authority: false; reason_code: string | null; record: XgridsSemanticTopologyRecord | null; } export interface XgridsCompatibilityState { profile_id?: string | null; decision?: "compatible" | "limited" | "unknown" | "incompatible"; permitted_mode?: "blocked" | "evidence-only" | "read-only" | "active-control"; firmware_claim?: string | null; vendor_writes_enabled?: boolean; camera_preview?: string | null; attestation?: CompatibilityAttestation | null; } export interface XgridsModelingControlSafety { mode: "shadow-only"; status_reports_observed: number; decode_errors: number; vendor_identity_observed: boolean; device_serial_observed: boolean; identity_conflict: boolean; session_state?: string | null; project_bound: boolean; ready_for_start_shadow: boolean; ready_for_stop_shadow: boolean; vendor_writes_enabled: false; publisher_installed: false; } export interface XgridsApplicationControlExecution { mode: "dormant-write-disabled"; state: "disarmed" | "armed-shadow-only" | "expired" | "closed"; lease?: { state: "armed" | "expired" | "closed"; remaining_seconds: number; authority_cached: boolean; exportable: false; } | null; orchestrator?: Record | null; publisher: { mode: "write-disabled"; denied_attempts: number; transport_calls: 0; vendor_writes_enabled: false; publisher_armed: false; automatic_retry: false; }; live_transport_installed: false; can_emit_requests: false; } export interface XgridsPhysicalCommandState { status: "empty" | "unresolved" | "resolved" | "corrupt"; reason_code: string | null; requires_reconciliation: boolean; resolved_active_recovery_required?: boolean; automatic_replay_allowed: false; normal_session_recovery_supported: false; recovery_requirement?: string | null; runtime_bound: boolean; reconciliation_ready: boolean; observed_session_state?: "ready" | "scanning" | string | null; active_operation_id?: string | null; operator_retirement?: { allowed: boolean; reason_codes: string[]; expected_operation_id: string | null; expected_revision: number | null; expected_transport_ref: string | null; physical_outcome: "unknown"; device_io_performed: false; automatic_retry: false; } | null; operator_retirement_allowed?: boolean; operator_retirement_reason_codes?: string[]; operator_reconciliation_reopen?: { allowed: boolean; reason_codes: string[]; expected_revision: number | null; expected_retirement_id: string | null; expected_transport_ref: string | null; expected_discovery_generation: number | null; expected_desired_mode: XgridsConnectionMode; expected_desired_mode_revision: number; device_io_performed: false; automatic_retry: false; } | null; record?: (Record & { action?: "start" | "stop"; stage?: string; resolution?: string | null; }) | null; } export type XgridsApplicationControlPhase = | "idle" | "connecting" | "connection-ready" | "active-recovery-requested" | "workspace-requested" | "workspace-ready" | "project-requested" | "project-ready" | "start-requested" | "initializing" | "scanning" | "stop-requested" | "stopping" | "awaiting-standby-confirmation" | "completed" | "closed" | "failed"; export interface XgridsApplicationControlSession { mode: "interactive-canonical"; inspection_only?: boolean; inspection_promotion_allowed?: boolean; session_generation: number; state_revision: number; state: XgridsApplicationControlPhase; control_socket_open: boolean; can_open: boolean; can_enter_workspace: boolean; can_prepare_project: boolean; can_start: boolean; can_stop: boolean; can_confirm_standby: boolean; pending_operator_action?: string | null; scripted_transitions: false; automatic_retry: false; outcome_unknown: boolean; failure?: { code?: string; reason_code?: string; message?: string; failed_phase?: string | null; dialogue_stage?: string | null; transport_state?: string | null; publish_attempts?: number | null; qos2_completions?: number | null; correlated_responses?: number | null; ignored_known_responses?: number | null; late_known_responses?: number | null; modeling_command_attempted?: boolean | null; diagnostic_snapshot_unavailable?: string[]; diagnostic_evidence_unavailable?: string[]; correlation_failure?: { phase?: string; operation_key?: string; response_topic?: string; reason_code?: string; reason?: string; } | null; compatibility_failure?: { phase?: string; operation_key?: string; response_topic?: string; reason_code?: string; expected?: Record; observed?: Record; } | null; status_reconciliation?: { device_session_state?: string; device_project_bound?: boolean; system_error_code?: number | null; decision?: "safe-explicit-prestart-retry"; automatic_retry?: false; } | null; network_change_admissible?: boolean; network_change_reconciliation?: { device_session_state?: "scan_stopping"; device_project_bound?: true; system_error_code?: null; stop_complete?: true; standby_confirmed?: false; decision?: "explicit-network-change-only-after-acknowledged-stop"; automatic_retry?: false; } | null; safe_to_retry?: boolean; host_diagnostic?: XgridsHostFailureDiagnostic; } | null; dialogue?: Record | null; transport?: Record | null; verified_control?: { logical_device_id: string; compatibility_profile_id: string; control_session_id: string; source: "mqtt-device-info"; intent_id: string; transport_ref: string; host_path_epoch: number; target_ipv4: string; target_port: number; connection_mode: XgridsConnectionMode; control_proof_revision: number; control_proof_source: string; control_proof_fresh: boolean; control_proof_age_seconds?: number | null; } | null; physical_command?: XgridsPhysicalCommandState | null; } export interface XgridsAcquisition { schema_version?: string; acquisition_id: string; device_id: string; device_session_id: string; compatibility_profile_id: string; control_mode: "operator-manual" | "plugin-commanded" | "observe-only"; project_name?: string | null; mount_type?: "handheld" | null; gnss_mode?: "none" | null; cleanup_pending?: boolean; requested_streams: string[]; target_host: string; duration_seconds: number | null; evidence_policy: "required" | "best-effort" | "disabled"; state: AcquisitionState; state_revision: number; created_at?: string | null; updated_at?: string | null; message_code?: string | null; operator_instructions?: string[]; result?: Record | null; } export interface XgridsOperation { schema_version?: string; operation_id: string; action: string; status: OperationStatus; accepted_at?: string | null; completed_at?: string | null; deadline_at?: string | null; device_id?: string | null; device_session_id?: string | null; idempotency_key?: string | null; stage_code?: string | null; message_code?: string | null; sequence?: number; state_revision?: number; cancellable?: boolean; cancel_requested?: boolean; result?: Record | null; error?: (Record & { host_diagnostic?: XgridsHostFailureDiagnostic; }) | null; evidence_refs?: string[]; context?: Record; events?: XgridsOperationEvent[]; } export interface XgridsOperationEvent { schema_version: "missioncore.operation-event/v1"; sequence: number; status: OperationStatus; stage_code: string; message_code: string; observed_at: string; side_effect_status: string | null; error_code: string | null; safe_to_retry: boolean | null; automatic_retry: false; } export interface XgridsConnectionDiagnosticBundle { schema_version: "missioncore.xgrids-k1-connection-diagnostic/v1"; redacted: true; generated_at_utc: string; snapshot_runtime_id: string; attempt: Omit; network_mutation_ledger: XgridsNetworkMutationLedger; connection_supervisor: Record; automatic_retry: false; } export const XGRIDS_CONNECTION_ATTEMPT_PHASES = [ "network_applied", "network_not_applied", "network_outcome_unknown", ] as const; export type XgridsConnectionAttemptPhase = typeof XGRIDS_CONNECTION_ATTEMPT_PHASES[number]; export interface XgridsConnectionAttempt { schema_version: "missioncore.xgrids-k1-connection-attempt/v1"; attempt_id: string; connection_mode: XgridsConnectionMode; status: OperationStatus; stage: string; public_error_code: string | null; side_effect_status: string; phase: XgridsConnectionAttemptPhase; control_state: "ready" | "control_not_ready" | "unknown"; safe_next_action: | "wait-for-current-attempt" | "continue-with-control-verification" | "verify-control-read-only" | "start-acquisition" | "stop-local-receiver" | "retire-unavailable-physical-target" | "scan-select-connect" | "manual-recovery-required"; automatic_retry: false; accepted_at: string | null; completed_at: string | null; timeline: XgridsOperationEvent[]; diagnostic_bundle?: XgridsConnectionDiagnosticBundle; } export interface XgridsK1Metrics { pcl_frames?: number | null; pose_frames?: number | null; mqtt_to_decode_ms?: number | null; decode_ms?: number | null; publish_ms?: number | null; pipeline_ms?: number | null; end_to_end_ms?: number | null; frame_rate?: number | null; frame_rate_hz?: number | null; point_count?: number | null; dropped_preview_frames?: number | null; ai_end_to_end_ms?: number | null; ai_end_to_end_p95_ms?: number | null; ai_frame_rate_hz?: number | null; ai_dropped_frames?: number | null; ai_stale_ms?: number | null; device_elapsed_seconds?: number | null; device_route_distance_meters?: number | null; device_speed_meters_per_second?: number | null; device_speed_mps?: number | null; elapsed_seconds?: number | null; route_distance_meters?: number | null; speed_meters_per_second?: number | null; [key: string]: number | null | undefined; } export interface XgridsCameraPreviewDelivery { id: string; kind: "mse-fmp4-websocket"; url: string; media_type: string; } export interface XgridsCameraPreviewActivation { group_id: string; max_active: number; selected: boolean; controllable: boolean; } export interface XgridsCameraPreviewState { phase?: string | null; revision?: number | null; generation?: number | null; active_source_id?: string | null; delivery?: XgridsCameraPreviewDelivery | null; } export interface XgridsSensorCatalogStream { stream_id: string; source_id?: string | null; semantic_channel_id?: string | null; label?: string | null; sensor_kind?: string | null; modality?: string | null; availability?: string | null; endpoint_label?: string | null; activation?: XgridsCameraPreviewActivation | null; delivery?: XgridsCameraPreviewDelivery | null; decode_status?: string | null; frame_id?: string | null; coordinate_convention?: string | null; } export interface XgridsSensorCatalog { schema_version?: string | null; revision?: string | null; streams?: XgridsSensorCatalogStream[]; } export interface XgridsConnectionLifecycleBinding { binding_key: string; intent_id: string; transport_ref: string; connection_mode: XgridsConnectionMode; target_ipv4: string; target_port: number; host_path_epoch: number; control_session_id: string; logical_device_id?: string | null; compatibility_profile_id?: string | null; control_proof_source?: string | null; control_proof_revision?: number | null; } export interface XgridsConnectionLifecycle { schema_version: "missioncore.xgrids-k1-connection-lifecycle/v1"; revision: number; desired_mode: XgridsConnectionMode; configured_mode: XgridsConnectionMode | null; active_mode: XgridsConnectionMode | null; mode_change: { state: | "disconnected" | "awaiting-control" | "ready" | "switch-selected" | "connecting"; from: XgridsConnectionMode | null; to: XgridsConnectionMode; }; mode_selection: { allowed: boolean; reason_codes: string[]; automatic_retry: false; }; active_binding_key: string | null; active_binding: XgridsConnectionLifecycleBinding | null; connection_ready: boolean; ready_to_start: boolean; operation: XgridsConnectionAttempt | null; allowed_actions: string[]; automatic_retry: false; } export interface XgridsActiveStreamRecovery { schema_version: "missioncore.xgrids-k1-active-stream-recovery/v1"; state: XgridsActiveStreamRecoveryState; generation: number; acquisition_id: string | null; attempt: number; started_at_utc: string | null; elapsed_ms: number | null; reason_code: string | null; force_finish_allowed: boolean; automatic_read_only_rebind: boolean; automatic_command_retry: false; start_performed: false; stop_performed: false; ble_operation_performed: false; network_mutation_performed: false; runtime_producer_generation: number | null; camera_recovery: XgridsCameraRecoveryState; camera_media_state: XgridsCameraMediaState; camera_media_ready: boolean; camera_epoch: XgridsCameraMediaEpoch | null; } export interface XgridsCameraMediaEpoch { generation: number; init_committed: boolean; init_committed_age_ms: number | null; first_media_committed: boolean; first_media_committed_age_ms: number | null; committed_media_segment_count: number; last_media_segment_age_ms: number | null; } export interface XgridsK1State { contract_version?: string | null; snapshot_runtime_started_at_utc?: string | null; snapshot_runtime_started_monotonic_ns?: string | null; snapshot_runtime_id?: string | null; snapshot_revision?: number | null; producer_generation?: number | null; phase?: string | null; message?: string | null; devices?: BleDevice[]; selected_device_id?: string | null; ble_discovery_generation?: number; k1_ip?: string | null; /** Legacy alias for the last configured mode. */ connection_mode?: XgridsConnectionMode | null; configured_connection_mode?: XgridsConnectionMode | null; active_connection_mode?: XgridsConnectionMode | null; desired_connection_mode?: XgridsConnectionMode; desired_connection_mode_revision?: number; connection_scenario_reset?: { reset_id: string; request_revision: number; revision: number; desired_mode: XgridsConnectionMode; /** Missing is treated as active while rolling out the marker lifecycle. */ active?: boolean; settled_by_discovery_generation?: number | null; local_session_closed: true; previous_device_may_continue_scanning: boolean; physical_disposition: string; network_disposition: string | null; device_command_performed: false; network_write_performed: false; automatic_scan: false; operation_sequence: number; timing?: { schema_version: "missioncore.xgrids-k1-connection-scenario-reset-timing/v1"; started_at_utc: string; completed_at_utc: string; total_ms: number; lifecycle_boundary_wait_ms: number; monitor_quiescence_wait_ms: number; local_retirement_ms: number; intent_commit_ms: number; }; } | null; connection_scenario_reset_pending?: { reset_id: string; desired_mode: XgridsConnectionMode; expected_revision: number; status: "waiting-for-local-lifecycle" | "retiring-local-session"; device_command_performed: false; network_write_performed: false; automatic_scan: false; } | null; foxglove_ws_url?: string | null; foxglove_viewer_url?: string | null; rerun_grpc_url?: string | null; viewer_settings?: ViewerSettings | null; source_mode?: SourceMode | null; metrics?: XgridsK1Metrics; compatibility?: XgridsCompatibilityState | null; modeling_control_safety?: XgridsModelingControlSafety | null; application_control_execution?: XgridsApplicationControlExecution | null; application_control_session?: XgridsApplicationControlSession | null; physical_command?: XgridsPhysicalCommandState | null; device_ref?: XgridsDeviceRef | null; device_session?: XgridsDeviceSession | null; connection_verification?: XgridsConnectionVerification | null; configured_endpoint_probe?: XgridsConfiguredEndpointProbe | null; network_write_reconciliation?: XgridsNetworkWriteReconciliation | null; network_mutation_ledger?: XgridsNetworkMutationLedger | null; current_device_recovery?: XgridsCurrentDeviceRecovery | null; connection_supervisor?: XgridsConnectionSupervisor | null; connection_lifecycle?: XgridsConnectionLifecycle | null; connection_recovery?: XgridsActiveStreamRecovery | null; connection_reconfiguration?: XgridsConnectionReconfiguration | null; connection_policy?: XgridsConnectionPolicy | null; semantic_topology_store?: XgridsSemanticTopologyStore | null; acquisition?: XgridsAcquisition | null; operations?: XgridsOperation[]; last_operation?: XgridsOperation | null; connection_attempt?: XgridsConnectionAttempt | null; sensor_catalog?: XgridsSensorCatalog | null; camera_preview?: XgridsCameraPreviewState | null; } export interface HealthResponse { ok?: boolean; status?: string; service?: string; version?: string; } export interface ScanRequest { duration_seconds?: number; operation_id?: string; } export interface CompatibilityAttestation { firmware_version: "3.0.2"; topology: "direct-lan" | "device-ap" | "controller-hotspot"; verification: "live-device-info"; } export interface ConnectRequest { device_id: string; ssid?: string; password?: string; connection_mode: "bridge" | "quick-connect" | "direct-connect"; compatibility_attestation: CompatibilityAttestation; operation_id?: string; idempotency_key: string; expected_mode_revision: number; expected_discovery_generation: number; expected_reconfiguration_revision: number; expected_reconfiguration_intent_id?: string; } export interface SelectConnectionModeRequest { connection_mode: XgridsConnectionMode; expected_revision: number; reset_scenario?: true; reset_id?: string; } export interface PrepareConnectionReconfigurationRequest { intent: XgridsConnectionReconfigurationIntent | "cancel"; expected_reconfiguration_revision: number; expected_reconfiguration_intent_id: string | null; expected_desired_mode_revision: number; expected_active_binding_key: string | null; } interface ConnectionVerifyRequestBase { device_id: string; compatibility_attestation: CompatibilityAttestation; operation_id?: string; expected_mode_revision: number; } export type ConnectionVerifyRequest = | (ConnectionVerifyRequestBase & { source: "fresh-scan"; expected_discovery_generation: number; expected_reconfiguration_revision: number; expected_reconfiguration_intent_id?: string; }) | (ConnectionVerifyRequestBase & { source: Extract< XgridsConnectionPolicyTargetSource, "retained-current-process" | "durable-configured-state" >; expected_discovery_generation?: never; }); export interface ConfiguredEndpointProbeRequest { operation_id?: string; } export interface RetireUnavailablePhysicalCommandRequest { retirement_id: string; expected_operation_id: string; expected_revision: number; expected_transport_ref: string; operator_confirmed: true; reason: "device-permanently-unavailable-or-replaced"; } export interface ReopenRetiredPhysicalReconciliationRequest { reopening_id: string; expected_revision: number; expected_retirement_id: string; expected_transport_ref: string; expected_discovery_generation: number; expected_desired_mode: XgridsConnectionMode; expected_desired_mode_revision: number; operator_confirmed: true; reason: "device-returned-for-explicit-reconciliation"; } export interface SnapshotRuntimeFenceRequest { expected_snapshot_runtime_id: string; } export type SnapshotRuntimeFencedRequest = TRequest & SnapshotRuntimeFenceRequest; export interface PrepareAcquisitionRequest { project_name: string; mount_type: "handheld"; gnss_mode: "none"; host?: string; duration_seconds?: number; requested_streams?: RequestedStreamId[]; evidence_policy?: "required" | "best-effort" | "disabled"; compatibility_attestation: CompatibilityAttestation; operation_id?: string; idempotency_key: string; deadline_seconds?: number; expected_control_session_generation?: number; expected_control_state_revision?: number; } export type RequestedStreamId = | "spatial.point-cloud.live" | "spatial.pose.live" | "device.modeling.live" | "device.status.live" | "device.heartbeat.live"; export interface StartAcquisitionRequest { acquisition_id: string; expected_state_revision?: number; expected_control_session_generation: number; expected_control_state_revision: number; operation_id?: string; idempotency_key: string; deadline_seconds?: number; physical_acceptance?: OperatorPresenceConfirmation; } interface StopAcquisitionRequestBase { acquisition_id: string; operator_confirmed?: boolean; operation_id?: string; idempotency_key: string; deadline_seconds?: number; physical_acceptance?: OperatorPresenceConfirmation; } export type StopAcquisitionRequest = | (StopAcquisitionRequestBase & { mode: "capture-only"; expected_control_session_generation?: never; expected_control_state_revision?: never; }) | (StopAcquisitionRequestBase & { mode: "graceful"; expected_control_session_generation: number; expected_control_state_revision: number; }); export interface AbortAcquisitionRequest { acquisition_id: string; operation_id?: string; idempotency_key: string; deadline_seconds?: number; expected_control_session_generation?: number; expected_control_state_revision?: number; } export interface ForceFinishAcquisitionRequest { acquisition_id: string; expected_state_revision: number; expected_recovery_generation: number; operator_confirmed: true; operation_id?: string; idempotency_key: string; deadline_seconds?: number; } export interface CompatibilityLiveRequest { project_name: string; host?: string; duration_seconds?: number; compatibility_attestation: CompatibilityAttestation; } export interface ReplayRequest { path: string; speed?: number; loop?: boolean; } export interface SelectCameraPreviewRequest { source_id: string; device_session_id: string; } export interface StopCameraPreviewRequest { device_session_id: string; generation: number; } export interface ShadowApplicationControlArmRequest { operator_confirmed: true; lease_seconds?: number; timezone_name: string; } export interface OperatorPresenceConfirmation { operator_present: true; owner_controlled_device: true; lixelgo_closed: true; battery_storage_confirmed: true; expected_physical_state_confirmed: true; } export interface OpenApplicationControlSessionRequest extends OperatorPresenceConfirmation { timezone_name: string; } export interface EnterApplicationWorkspaceRequest { operator_confirmed: true; expected_session_generation: number; expected_state_revision: number; } export interface CloseApplicationControlSessionRequest { expected_session_generation: number; expected_state_revision: number; } export interface ReconcilePhysicalCommandRequest { reconciliation_id: string; expected_session_generation: number; expected_state_revision: number; } export class ApiError extends Error { readonly status: number; readonly transportUnavailable: boolean; readonly hostDiagnostic: XgridsHostFailureDiagnostic | null; constructor( message: string, status = 0, transportUnavailable = false, hostDiagnostic: unknown = null, ) { super(message); this.name = "ApiError"; this.status = status; this.transportUnavailable = transportUnavailable; this.hostDiagnostic = isXgridsHostFailureDiagnostic(hostDiagnostic) ? hostDiagnostic : null; } } export class ApiRequestTimeoutError extends ApiError { readonly code = "request_timeout_outcome_unknown"; readonly outcomeUnknown = true; readonly automaticRetry = false; readonly timeoutMs: number; readonly operationLabel: string; constructor(operationLabel: string, timeoutMs: number) { super( `Локальный запрос «${operationLabel}» не завершился за ${Math.ceil(timeoutMs / 1000)} с. Результат операции неизвестен; автоматический повтор запрещён. Обновите состояние перед отдельным ручным действием.`, ); this.name = "ApiRequestTimeoutError"; this.timeoutMs = timeoutMs; this.operationLabel = operationLabel; } } const DEFAULT_REQUEST_TIMEOUT_MS = 8_000; const ACTION_REQUEST_TIMEOUT_MS: Readonly> = { [xgridsK1Actions.stateRead]: 8_000, [xgridsK1Actions.discoveryScan]: 70_000, // Explicit scenario reset queues behind a bounded in-flight lifecycle and // then performs local-only retirement. Keep the browser request alive long // enough to observe that single committed intent. [xgridsK1Actions.connectionModeSelect]: 360_000, [xgridsK1Actions.connectionReconfigurePrepare]: 8_000, // Bridge and Quick are composite operations: exact BLE baseline/write, // bounded post-write observation and, for Quick, a native CoreWLAN handoff. // The backend journals a 240-second operation fence. The browser deadline // must outlive it; aborting the HTTP request earlier cannot cancel the // native/device work and would present an avoidable ambiguous outcome. [xgridsK1Actions.networkProvision]: 300_000, // A canonical verification performs a bounded fresh CoreBluetooth connect // and 7f02 status read before any route/TCP interpretation. The browser // must not abandon that read at the generic eight-second HTTP deadline and // accidentally invite a second operator attempt while native cleanup still // owns the BLE lifecycle. [xgridsK1Actions.connectionVerify]: 150_000, // This is the separate host-only route -> TCP -> route diagnostic. It does // not enter CoreBluetooth and has a much smaller bounded server deadline. [xgridsK1Actions.configuredEndpointProbe]: 15_000, [xgridsK1Actions.applicationControlSessionOpen]: 120_000, [xgridsK1Actions.applicationControlWorkspaceEnter]: 120_000, [xgridsK1Actions.applicationControlSessionClose]: 120_000, [xgridsK1Actions.physicalCommandReconcile]: 120_000, [xgridsK1Actions.physicalCommandRetireUnavailable]: 8_000, [xgridsK1Actions.physicalCommandReopenRetiredReconciliation]: 8_000, [xgridsK1Actions.acquisitionPrepare]: 120_000, [xgridsK1Actions.acquisitionStart]: 120_000, [xgridsK1Actions.acquisitionStop]: 120_000, [xgridsK1Actions.acquisitionForceFinishLocal]: 45_000, }; export function requestTimeoutMsForAction(actionId: string): number { return ACTION_REQUEST_TIMEOUT_MS[actionId] ?? DEFAULT_REQUEST_TIMEOUT_MS; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function isLiteralValue( value: unknown, values: Values, ): value is Values[number] { return typeof value === "string" && values.some((candidate) => candidate === value); } function isOptionalString(value: unknown): value is string | null | undefined { return value === undefined || value === null || typeof value === "string"; } function isOptionalBoolean(value: unknown): value is boolean | undefined { return value === undefined || typeof value === "boolean"; } function isNonNegativeInteger(value: unknown): value is number { return typeof value === "number" && Number.isInteger(value) && value >= 0; } function isStringOrNull(value: unknown): value is string | null { return value === null || typeof value === "string"; } function isNonNegativeIntegerOrNull(value: unknown): value is number | null { return value === null || isNonNegativeInteger(value); } function isStringArray(value: unknown): value is string[] { return Array.isArray(value) && value.every((item) => typeof item === "string"); } export function isXgridsConnectionAttemptPhase( value: unknown, ): value is XgridsConnectionAttemptPhase { return isLiteralValue(value, XGRIDS_CONNECTION_ATTEMPT_PHASES); } export function isXgridsHostFailureDiagnostic( value: unknown, ): value is XgridsHostFailureDiagnostic { return isRecord(value) && value.schema_version === "missioncore.host-failure-diagnostic/v1" && isLiteralValue(value.code, XGRIDS_HOST_DIAGNOSTIC_CODES) && isLiteralValue(value.domain, XGRIDS_HOST_DIAGNOSTIC_DOMAINS) && isLiteralValue(value.impact, XGRIDS_HOST_DIAGNOSTIC_IMPACTS) && isLiteralValue(value.operator_action, XGRIDS_HOST_DIAGNOSTIC_ACTIONS) && value.automatic_retry === false && value.redacted === true; } export function isXgridsActiveStreamRecovery( value: unknown, ): value is XgridsActiveStreamRecovery { if (!isRecord(value)) return false; return value.schema_version === "missioncore.xgrids-k1-active-stream-recovery/v1" && isLiteralValue(value.state, XGRIDS_ACTIVE_STREAM_RECOVERY_STATES) && isNonNegativeInteger(value.generation) && isStringOrNull(value.acquisition_id) && isNonNegativeInteger(value.attempt) && isStringOrNull(value.started_at_utc) && isNonNegativeIntegerOrNull(value.elapsed_ms) && isStringOrNull(value.reason_code) && typeof value.force_finish_allowed === "boolean" && typeof value.automatic_read_only_rebind === "boolean" && value.automatic_command_retry === false && value.start_performed === false && value.stop_performed === false && value.ble_operation_performed === false && value.network_mutation_performed === false && isNonNegativeIntegerOrNull(value.runtime_producer_generation) && isLiteralValue(value.camera_recovery, XGRIDS_CAMERA_RECOVERY_STATES) && isXgridsCameraMediaProjection(value); } function isXgridsCameraMediaEpoch(value: unknown): value is XgridsCameraMediaEpoch { return isRecord(value) && isNonNegativeInteger(value.generation) && value.generation > 0 && typeof value.init_committed === "boolean" && isNonNegativeIntegerOrNull(value.init_committed_age_ms) && typeof value.first_media_committed === "boolean" && isNonNegativeIntegerOrNull(value.first_media_committed_age_ms) && isNonNegativeInteger(value.committed_media_segment_count) && isNonNegativeIntegerOrNull(value.last_media_segment_age_ms); } function isXgridsCameraMediaProjection( value: Record, ): boolean { if ( !isLiteralValue(value.camera_media_state, XGRIDS_CAMERA_MEDIA_STATES) || typeof value.camera_media_ready !== "boolean" ) return false; const state = value.camera_media_state; const epoch = value.camera_epoch; if (state === "inactive" || state === "pending-epoch") { return value.camera_media_ready === false && epoch === null; } if (!isXgridsCameraMediaEpoch(epoch) || value.camera_media_ready !== (state === "ready")) { return false; } if (state === "pending-init") { return epoch.init_committed === false && epoch.init_committed_age_ms === null && epoch.first_media_committed === false && epoch.first_media_committed_age_ms === null && epoch.committed_media_segment_count === 0 && epoch.last_media_segment_age_ms === null; } if (state === "pending-first-media") { return epoch.init_committed === true && isNonNegativeInteger(epoch.init_committed_age_ms) && epoch.first_media_committed === false && epoch.first_media_committed_age_ms === null && epoch.committed_media_segment_count === 0 && epoch.last_media_segment_age_ms === null; } return epoch.init_committed === true && isNonNegativeInteger(epoch.init_committed_age_ms) && epoch.first_media_committed === true && isNonNegativeInteger(epoch.first_media_committed_age_ms) && epoch.committed_media_segment_count > 0 && isNonNegativeInteger(epoch.last_media_segment_age_ms); } export function isXgridsConnectionVerification( value: unknown, ): value is XgridsConnectionVerification { if (!isRecord(value)) return false; return ( isLiteralValue(value.status, XGRIDS_CONNECTION_VERIFICATION_STATUSES) && isLiteralValue( value.lease_state, XGRIDS_CONNECTION_VERIFICATION_LEASE_STATES, ) && isNonNegativeInteger(value.lease_generation) && isNonNegativeInteger(value.supervisor_revision) && isStringOrNull(value.endpoint_validation) && isLiteralValue( value.network_reachability, XGRIDS_CONNECTION_NETWORK_REACHABILITY, ) && isOptionalString(value.host_route_class) && isOptionalString(value.address_source) && isOptionalString(value.connection_origin) && isOptionalString(value.admission_source) && isOptionalBoolean(value.address_changed) && isOptionalBoolean(value.previous_address_present) && isOptionalBoolean(value.write_performed) && isStringOrNull(value.observed_at) && isOptionalString(value.last_known_reachable_at) && isOptionalString(value.reason_code) ); } export function isXgridsConnectionReconfiguration( value: unknown, ): value is XgridsConnectionReconfiguration { if (!isRecord(value)) return false; const intent = value.intent; const intentId = value.intent_id; const status = value.status; const requiredTransportRef = value.required_transport_ref; const requiredMode = value.required_connection_mode; const observed = value.required_transport_observed; return Boolean( value.schema_version === "missioncore.xgrids-k1-connection-reconfiguration/v1" && isNonNegativeInteger(value.revision) && isStringOrNull(intentId) && ( intent === null || isLiteralValue(intent, XGRIDS_CONNECTION_RECONFIGURATION_INTENTS) ) && isLiteralValue( status, XGRIDS_CONNECTION_RECONFIGURATION_STATUSES, ) && isStringOrNull(requiredTransportRef) && ( requiredMode === null || isLiteralValue(requiredMode, XGRIDS_CONNECTION_MODES) ) && isNonNegativeIntegerOrNull(value.minimum_discovery_generation) && isNonNegativeIntegerOrNull(value.fresh_discovery_generation) && (observed === null || typeof observed === "boolean") && isStringOrNull(value.prepared_at) && value.automatic_retry === false && ( status === "idle" ? intent === null && intentId === null && requiredTransportRef === null && requiredMode === null && value.minimum_discovery_generation === null && value.fresh_discovery_generation === null && observed === null && value.prepared_at === null : intent !== null && Boolean(intentId?.trim()) && requiredMode === "bridge" && isNonNegativeInteger(value.minimum_discovery_generation) && Boolean(value.prepared_at?.trim()) && ( intent === "select-device" ? requiredTransportRef === null : Boolean(requiredTransportRef?.trim()) ) && ( status === "awaiting-fresh-scan" ? value.fresh_discovery_generation === null && observed === null : isNonNegativeInteger(value.fresh_discovery_generation) && value.fresh_discovery_generation >= value.minimum_discovery_generation && ( intent === "change-network" ? typeof observed === "boolean" : observed === null ) ) ) ); } export function isXgridsConnectionPolicyDecision( value: unknown, ): value is XgridsConnectionPolicyDecision { if (!isRecord(value)) return false; const requiredMode = value.required_connection_mode; return ( typeof value.allowed === "boolean" && isStringArray(value.reason_codes) && isLiteralValue(value.target_source, XGRIDS_CONNECTION_POLICY_TARGET_SOURCES) && isStringOrNull(value.required_transport_ref) && ( requiredMode === undefined || requiredMode === null || isLiteralValue(requiredMode, XGRIDS_CONNECTION_MODES) ) && typeof value.requires_live_gatt_validation === "boolean" && value.automatic_retry === false && ( value.execution_mode === undefined || value.execution_mode === "capture-only" ) && ( value.physical_command_allowed === undefined || value.physical_command_allowed === false ) && ( value.physical_outcome === undefined || value.physical_outcome === "unknown" ) && ( value.operator_follow_up === undefined || value.operator_follow_up === "manual-device-stop-required" ) && ( value.device_write_performed === undefined || value.device_write_performed === false ) ); } export function isXgridsConnectionPolicy( value: unknown, ): value is XgridsConnectionPolicy { if ( !isRecord(value) || value.schema_version !== "missioncore.xgrids-k1-connection-policy/v1" || !isNonNegativeInteger(value.supervisor_revision) || !isNonNegativeIntegerOrNull(value.network_ledger_revision) || typeof value.recommended_action !== "string" || !Array.isArray(value.allowed_actions) || !isRecord(value.actions) || !isRecord(value.facts) || value.facts.retained_context_is_presence !== false ) { return false; } const allowedActions: XgridsConnectionPolicyAction[] = []; const policyActions = value.actions; for (const action of value.allowed_actions) { if (!isLiteralValue(action, XGRIDS_CONNECTION_POLICY_ACTIONS)) return false; allowedActions.push(action); } for (const [action, decision] of Object.entries(policyActions)) { if ( !isLiteralValue(action, XGRIDS_CONNECTION_POLICY_ACTIONS) || !isXgridsConnectionPolicyDecision(decision) ) { return false; } if (decision.allowed !== allowedActions.includes(action)) return false; if ( action === "observe-current-device-network" && decision.target_source !== "retained-current-process" ) { return false; } if ( action === "observe-configured-device-network" && decision.target_source !== "durable-configured-state" ) { return false; } if ( action === "retire-unavailable-physical-target" && ( decision.target_source !== "durable-physical-command" || decision.physical_command_allowed !== false || decision.physical_outcome !== "unknown" || decision.device_write_performed !== false || decision.requires_live_gatt_validation !== false ) ) { return false; } if ( decision.allowed && (action === "observe-current-device-network" || action === "observe-configured-device-network") && ( !decision.required_transport_ref?.trim() || decision.required_connection_mode == null ) ) { return false; } } return allowedActions.every((action) => { const decision = policyActions[action]; return isRecord(decision) && decision.allowed === true; }); } function hasValidRuntimeContracts( value: unknown, ): value is XgridsK1State { if (!isRecord(value)) return false; const verification = value.connection_verification; const reconfiguration = value.connection_reconfiguration; const policy = value.connection_policy; const attempt = value.connection_attempt; const activeStreamRecovery = value.connection_recovery; const verificationValid = verification === undefined || verification === null || isXgridsConnectionVerification(verification); const policyValid = policy === undefined || policy === null || isXgridsConnectionPolicy(policy); const reconfigurationValid = reconfiguration === undefined || reconfiguration === null || isXgridsConnectionReconfiguration(reconfiguration); const attemptValid = attempt === undefined || attempt === null || ( isRecord(attempt) && attempt.schema_version === "missioncore.xgrids-k1-connection-attempt/v1" && isXgridsConnectionAttemptPhase(attempt.phase) && attempt.automatic_retry === false ); const activeStreamRecoveryValid = activeStreamRecovery === undefined || activeStreamRecovery === null || isXgridsActiveStreamRecovery(activeStreamRecovery); const producerGenerationValid = value.producer_generation === undefined || value.producer_generation === null || isNonNegativeInteger(value.producer_generation); return verificationValid && reconfigurationValid && policyValid && attemptValid && activeStreamRecoveryValid && producerGenerationValid; } function unwrapState(payload: unknown): XgridsK1State { const value = isRecord(payload) && isRecord(payload.state) ? payload.state : payload; if (!hasValidRuntimeContracts(value)) { throw new ApiError("Локальный сервер вернул некорректное состояние."); } return value; } async function requestJson( path: string, init: RequestInit | undefined, operationLabel: string, timeoutMs: number, ): Promise { const controller = new AbortController(); let timedOut = false; const timeout = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs); try { const response = await fetch(path, { ...init, signal: controller.signal, headers: { Accept: "application/json", ...(init?.body ? { "Content-Type": "application/json" } : {}), ...init?.headers, }, }); const bodyText = await response.text(); let body: unknown; if (bodyText) { try { body = JSON.parse(bodyText) as unknown; } catch { body = bodyText; } } if (!response.ok) { const detail = isRecord(body) && typeof body.detail === "string" ? body.detail : typeof body === "string" && body.trim() ? body.trim() : `Запрос к API устройства завершился ошибкой HTTP ${response.status}.`; throw new ApiError( detail || `Запрос к API устройства завершился ошибкой HTTP ${response.status}.`, response.status, ); } return body; } catch (error) { if (error instanceof ApiError) throw error; if (timedOut) { throw new ApiRequestTimeoutError(operationLabel, timeoutMs); } throw new ApiError( "Не удалось подключиться к локальному сервису устройства.", 0, true, ); } finally { clearTimeout(timeout); } } async function postState( path: string, body: object | undefined, operationLabel: string, timeoutMs: number, ): Promise { const payload = await requestJson(path, { method: "POST", body: body ? JSON.stringify(body) : undefined, }, operationLabel, timeoutMs); if (payload === undefined) { return xgridsK1Api.getState(); } return unwrapState(payload); } function invokeState(actionId: string, input: object = {}): Promise { return postState( `/api/v1/device-plugins/${encodeURIComponent(PLUGIN_ID)}/actions/${encodeURIComponent(actionId)}`, { input }, actionId, requestTimeoutMsForAction(actionId), ); } let stateReadInFlight: Promise | null = null; function readStateSingleFlight(): Promise { if (stateReadInFlight) return stateReadInFlight; const request = invokeState(xgridsK1Actions.stateRead); stateReadInFlight = request; const release = () => { if (stateReadInFlight === request) stateReadInFlight = null; }; void request.then(release, release); return request; } export const xgridsK1Api = { async getHealth(): Promise { const payload = await requestJson( "/api/health", undefined, "health.read", DEFAULT_REQUEST_TIMEOUT_MS, ); if (!isRecord(payload)) { throw new ApiError("Локальный сервер вернул некорректный ответ проверки."); } return payload as HealthResponse; }, async getState(): Promise { return readStateSingleFlight(); }, scanBle( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.discoveryScan, body); }, selectConnectionMode( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.connectionModeSelect, body); }, prepareConnectionReconfiguration( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.connectionReconfigurePrepare, body); }, connect( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.networkProvision, body); }, verifyConnection( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.connectionVerify, body); }, probeConfiguredEndpoint( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.configuredEndpointProbe, body); }, retireUnavailablePhysicalCommand( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.physicalCommandRetireUnavailable, body); }, reopenRetiredPhysicalReconciliation( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState( xgridsK1Actions.physicalCommandReopenRetiredReconciliation, body, ); }, prepareAcquisition( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.acquisitionPrepare, body); }, startAcquisition( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.acquisitionStart, body); }, stopAcquisition( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.acquisitionStop, body); }, abortAcquisition( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.acquisitionAbort, body); }, forceFinishAcquisitionLocally( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.acquisitionForceFinishLocal, body); }, startReplay(body: ReplayRequest): Promise { return invokeState(xgridsK1Actions.streamStartReplay, body); }, startLiveCompatibility(body: CompatibilityLiveRequest): Promise { return invokeState(xgridsK1Actions.compatibilityStreamStartLive, body); }, stopSessionCompatibility( body: SnapshotRuntimeFenceRequest, ): Promise { return invokeState(xgridsK1Actions.compatibilityStreamStop, body); }, selectCameraPreview(body: SelectCameraPreviewRequest): Promise { return invokeState(xgridsK1Actions.cameraPreviewSelect, body); }, stopCameraPreview(body: StopCameraPreviewRequest): Promise { return invokeState(xgridsK1Actions.cameraPreviewStop, body); }, updateViewerSettings(body: ViewerSettings): Promise { return invokeState(xgridsK1Actions.viewerSettingsUpdate, body); }, getShadowApplicationControlState(): Promise { return invokeState(xgridsK1Actions.applicationControlShadowState); }, armShadowApplicationControl( body: ShadowApplicationControlArmRequest, ): Promise { return invokeState(xgridsK1Actions.applicationControlShadowArm, body); }, disarmShadowApplicationControl(): Promise { return invokeState(xgridsK1Actions.applicationControlShadowDisarm); }, openApplicationControlSession( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.applicationControlSessionOpen, body); }, enterApplicationWorkspace( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.applicationControlWorkspaceEnter, body); }, closeApplicationControlSession( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.applicationControlSessionClose, body); }, reconcilePhysicalCommand( body: SnapshotRuntimeFencedRequest, ): Promise { return invokeState(xgridsK1Actions.physicalCommandReconcile, body); }, }; export type EventSocketStatus = "connecting" | "open" | "closed" | "error"; function eventSocketUrl(): string { const url = new URL( `/api/v1/device-plugins/${encodeURIComponent(PLUGIN_ID)}/events`, window.location.href, ); url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; return url.toString(); } export function openEventSocket( onState: (state: XgridsK1State) => void, onStatus: (status: EventSocketStatus) => void, ): () => void { onStatus("connecting"); const socket = new WebSocket(eventSocketUrl()); socket.addEventListener("open", () => onStatus("open")); socket.addEventListener("message", (event) => { try { const payload = JSON.parse(String(event.data)) as unknown; onState(unwrapState(payload)); } catch { // The REST poll remains authoritative if an unrelated event is received. } }); socket.addEventListener("error", () => onStatus("error")); socket.addEventListener("close", () => onStatus("closed")); return () => socket.close(1000, "Пункт управления закрыт"); }