@tx5dr/contracts 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/CLAUDE.md +41 -0
  2. package/dist/index.d.ts +55 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +86 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/schema/__tests__/plugin-runtime-log.schema.test.d.ts +2 -0
  7. package/dist/schema/__tests__/plugin-runtime-log.schema.test.d.ts.map +1 -0
  8. package/dist/schema/__tests__/plugin-runtime-log.schema.test.js +78 -0
  9. package/dist/schema/__tests__/plugin-runtime-log.schema.test.js.map +1 -0
  10. package/dist/schema/ability.d.ts +85 -0
  11. package/dist/schema/ability.d.ts.map +1 -0
  12. package/dist/schema/ability.js +119 -0
  13. package/dist/schema/ability.js.map +1 -0
  14. package/dist/schema/api-response.schema.d.ts +1017 -0
  15. package/dist/schema/api-response.schema.d.ts.map +1 -0
  16. package/dist/schema/api-response.schema.js +137 -0
  17. package/dist/schema/api-response.schema.js.map +1 -0
  18. package/dist/schema/audio-sidecar.schema.d.ts +106 -0
  19. package/dist/schema/audio-sidecar.schema.d.ts.map +1 -0
  20. package/dist/schema/audio-sidecar.schema.js +47 -0
  21. package/dist/schema/audio-sidecar.schema.js.map +1 -0
  22. package/dist/schema/audio.schema.d.ts +227 -0
  23. package/dist/schema/audio.schema.d.ts.map +1 -0
  24. package/dist/schema/audio.schema.js +56 -0
  25. package/dist/schema/audio.schema.js.map +1 -0
  26. package/dist/schema/auth.schema.d.ts +738 -0
  27. package/dist/schema/auth.schema.d.ts.map +1 -0
  28. package/dist/schema/auth.schema.js +161 -0
  29. package/dist/schema/auth.schema.js.map +1 -0
  30. package/dist/schema/cycle.schema.d.ts +33 -0
  31. package/dist/schema/cycle.schema.d.ts.map +1 -0
  32. package/dist/schema/cycle.schema.js +23 -0
  33. package/dist/schema/cycle.schema.js.map +1 -0
  34. package/dist/schema/desktop-https.schema.d.ts +146 -0
  35. package/dist/schema/desktop-https.schema.d.ts.map +1 -0
  36. package/dist/schema/desktop-https.schema.js +31 -0
  37. package/dist/schema/desktop-https.schema.js.map +1 -0
  38. package/dist/schema/ft8.schema.d.ts +1107 -0
  39. package/dist/schema/ft8.schema.d.ts.map +1 -0
  40. package/dist/schema/ft8.schema.js +268 -0
  41. package/dist/schema/ft8.schema.js.map +1 -0
  42. package/dist/schema/hello.schema.d.ts +10 -0
  43. package/dist/schema/hello.schema.d.ts.map +1 -0
  44. package/dist/schema/hello.schema.js +5 -0
  45. package/dist/schema/hello.schema.js.map +1 -0
  46. package/dist/schema/logbook.schema.d.ts +1472 -0
  47. package/dist/schema/logbook.schema.d.ts.map +1 -0
  48. package/dist/schema/logbook.schema.js +256 -0
  49. package/dist/schema/logbook.schema.js.map +1 -0
  50. package/dist/schema/mode.schema.d.ts +130 -0
  51. package/dist/schema/mode.schema.d.ts.map +1 -0
  52. package/dist/schema/mode.schema.js +141 -0
  53. package/dist/schema/mode.schema.js.map +1 -0
  54. package/dist/schema/openwebrx.schema.d.ts +352 -0
  55. package/dist/schema/openwebrx.schema.d.ts.map +1 -0
  56. package/dist/schema/openwebrx.schema.js +139 -0
  57. package/dist/schema/openwebrx.schema.js.map +1 -0
  58. package/dist/schema/operator.schema.d.ts +883 -0
  59. package/dist/schema/operator.schema.d.ts.map +1 -0
  60. package/dist/schema/operator.schema.js +97 -0
  61. package/dist/schema/operator.schema.js.map +1 -0
  62. package/dist/schema/plugin.schema.d.ts +1579 -0
  63. package/dist/schema/plugin.schema.d.ts.map +1 -0
  64. package/dist/schema/plugin.schema.js +336 -0
  65. package/dist/schema/plugin.schema.js.map +1 -0
  66. package/dist/schema/process-monitor.schema.d.ts +282 -0
  67. package/dist/schema/process-monitor.schema.d.ts.map +1 -0
  68. package/dist/schema/process-monitor.schema.js +31 -0
  69. package/dist/schema/process-monitor.schema.js.map +1 -0
  70. package/dist/schema/pskreporter.schema.d.ts +185 -0
  71. package/dist/schema/pskreporter.schema.d.ts.map +1 -0
  72. package/dist/schema/pskreporter.schema.js +80 -0
  73. package/dist/schema/pskreporter.schema.js.map +1 -0
  74. package/dist/schema/qso.schema.d.ts +419 -0
  75. package/dist/schema/qso.schema.d.ts.map +1 -0
  76. package/dist/schema/qso.schema.js +137 -0
  77. package/dist/schema/qso.schema.js.map +1 -0
  78. package/dist/schema/radio-capability.schema.d.ts +625 -0
  79. package/dist/schema/radio-capability.schema.d.ts.map +1 -0
  80. package/dist/schema/radio-capability.schema.js +191 -0
  81. package/dist/schema/radio-capability.schema.js.map +1 -0
  82. package/dist/schema/radio-power-support.d.ts +45 -0
  83. package/dist/schema/radio-power-support.d.ts.map +1 -0
  84. package/dist/schema/radio-power-support.js +64 -0
  85. package/dist/schema/radio-power-support.js.map +1 -0
  86. package/dist/schema/radio-power.schema.d.ts +319 -0
  87. package/dist/schema/radio-power.schema.d.ts.map +1 -0
  88. package/dist/schema/radio-power.schema.js +87 -0
  89. package/dist/schema/radio-power.schema.js.map +1 -0
  90. package/dist/schema/radio-profile.schema.d.ts +2694 -0
  91. package/dist/schema/radio-profile.schema.d.ts.map +1 -0
  92. package/dist/schema/radio-profile.schema.js +71 -0
  93. package/dist/schema/radio-profile.schema.js.map +1 -0
  94. package/dist/schema/radio.schema.d.ts +1438 -0
  95. package/dist/schema/radio.schema.d.ts.map +1 -0
  96. package/dist/schema/radio.schema.js +314 -0
  97. package/dist/schema/radio.schema.js.map +1 -0
  98. package/dist/schema/realtime.schema.d.ts +1299 -0
  99. package/dist/schema/realtime.schema.d.ts.map +1 -0
  100. package/dist/schema/realtime.schema.js +184 -0
  101. package/dist/schema/realtime.schema.js.map +1 -0
  102. package/dist/schema/rigctld.schema.d.ts +151 -0
  103. package/dist/schema/rigctld.schema.d.ts.map +1 -0
  104. package/dist/schema/rigctld.schema.js +42 -0
  105. package/dist/schema/rigctld.schema.js.map +1 -0
  106. package/dist/schema/server-message-keys.d.ts +21 -0
  107. package/dist/schema/server-message-keys.d.ts.map +1 -0
  108. package/dist/schema/server-message-keys.js +22 -0
  109. package/dist/schema/server-message-keys.js.map +1 -0
  110. package/dist/schema/slot-info.schema.d.ts +696 -0
  111. package/dist/schema/slot-info.schema.d.ts.map +1 -0
  112. package/dist/schema/slot-info.schema.js +144 -0
  113. package/dist/schema/slot-info.schema.js.map +1 -0
  114. package/dist/schema/spectrum.schema.d.ts +716 -0
  115. package/dist/schema/spectrum.schema.d.ts.map +1 -0
  116. package/dist/schema/spectrum.schema.js +128 -0
  117. package/dist/schema/spectrum.schema.js.map +1 -0
  118. package/dist/schema/station-info.schema.d.ts +202 -0
  119. package/dist/schema/station-info.schema.d.ts.map +1 -0
  120. package/dist/schema/station-info.schema.js +32 -0
  121. package/dist/schema/station-info.schema.js.map +1 -0
  122. package/dist/schema/system.schema.d.ts +113 -0
  123. package/dist/schema/system.schema.d.ts.map +1 -0
  124. package/dist/schema/system.schema.js +95 -0
  125. package/dist/schema/system.schema.js.map +1 -0
  126. package/dist/schema/transmission.schema.d.ts +413 -0
  127. package/dist/schema/transmission.schema.d.ts.map +1 -0
  128. package/dist/schema/transmission.schema.js +78 -0
  129. package/dist/schema/transmission.schema.js.map +1 -0
  130. package/dist/schema/voice.schema.d.ts +38 -0
  131. package/dist/schema/voice.schema.d.ts.map +1 -0
  132. package/dist/schema/voice.schema.js +24 -0
  133. package/dist/schema/voice.schema.js.map +1 -0
  134. package/dist/schema/websocket.schema.d.ts +16916 -0
  135. package/dist/schema/websocket.schema.d.ts.map +1 -0
  136. package/dist/schema/websocket.schema.js +1050 -0
  137. package/dist/schema/websocket.schema.js.map +1 -0
  138. package/dist/utils/grid.d.ts +13 -0
  139. package/dist/utils/grid.d.ts.map +1 -0
  140. package/dist/utils/grid.js +71 -0
  141. package/dist/utils/grid.js.map +1 -0
  142. package/eslint.config.js +15 -0
  143. package/package.json +28 -0
  144. package/src/index.ts +152 -0
  145. package/src/schema/__tests__/plugin-runtime-log.schema.test.ts +90 -0
  146. package/src/schema/ability.ts +205 -0
  147. package/src/schema/api-response.schema.ts +187 -0
  148. package/src/schema/audio-sidecar.schema.ts +53 -0
  149. package/src/schema/audio.schema.ts +73 -0
  150. package/src/schema/auth.schema.ts +224 -0
  151. package/src/schema/cycle.schema.ts +25 -0
  152. package/src/schema/desktop-https.schema.ts +43 -0
  153. package/src/schema/ft8.schema.ts +362 -0
  154. package/src/schema/hello.schema.ts +7 -0
  155. package/src/schema/logbook.schema.ts +310 -0
  156. package/src/schema/mode.schema.ts +158 -0
  157. package/src/schema/openwebrx.schema.ts +160 -0
  158. package/src/schema/operator.schema.ts +112 -0
  159. package/src/schema/plugin.schema.ts +503 -0
  160. package/src/schema/process-monitor.schema.ts +41 -0
  161. package/src/schema/pskreporter.schema.ts +102 -0
  162. package/src/schema/qso.schema.ts +173 -0
  163. package/src/schema/radio-capability.schema.ts +244 -0
  164. package/src/schema/radio-power-support.ts +107 -0
  165. package/src/schema/radio-power.schema.ts +101 -0
  166. package/src/schema/radio-profile.schema.ts +91 -0
  167. package/src/schema/radio.schema.ts +385 -0
  168. package/src/schema/realtime.schema.ts +262 -0
  169. package/src/schema/rigctld.schema.ts +51 -0
  170. package/src/schema/server-message-keys.ts +26 -0
  171. package/src/schema/slot-info.schema.ts +162 -0
  172. package/src/schema/spectrum.schema.ts +166 -0
  173. package/src/schema/station-info.schema.ts +40 -0
  174. package/src/schema/system.schema.ts +121 -0
  175. package/src/schema/transmission.schema.ts +105 -0
  176. package/src/schema/voice.schema.ts +28 -0
  177. package/src/schema/websocket.schema.ts +1480 -0
  178. package/src/utils/grid.ts +94 -0
  179. package/test/mode-schema.test.ts +48 -0
  180. package/tsconfig.json +9 -0
@@ -0,0 +1,1050 @@
1
+ import { z } from 'zod';
2
+ import { SlotPackSchema, SlotInfoSchema } from './slot-info.schema.js';
3
+ import { PluginDataPayloadSchema, PluginLogEntrySchema, PluginRuntimeLogEntrySchema, PluginRuntimeLogHistoryPayloadSchema, PluginSystemSnapshotSchema, PluginStatusSchema, PluginUserActionPayloadSchema, } from './plugin.schema.js';
4
+ import { ModeDescriptorSchema } from './mode.schema.js';
5
+ import { QSORecordSchema } from './qso.schema.js';
6
+ import { LogBookStatisticsSchema } from './logbook.schema.js';
7
+ import { RadioInfoSchema, HamlibConfigSchema, TunerCapabilitiesSchema, RadioConnectionStatusSchema, ReconnectProgressSchema, CoreRadioCapabilitiesSchema, CoreCapabilityDiagnosticsSchema } from './radio.schema.js';
8
+ import { UserRole } from './auth.schema.js';
9
+ import { CapabilityListSchema, CapabilityStateSchema, WriteCapabilityPayloadSchema } from './radio-capability.schema.js';
10
+ import { RadioPowerStateEventSchema } from './radio-power.schema.js';
11
+ import { AudioSidecarStatusPayloadSchema } from './audio-sidecar.schema.js';
12
+ import { SpectrumCapabilitiesSchema, SpectrumFrameSchema, SpectrumKindSchema, SpectrumSessionControlActionSchema, SpectrumSessionControlIdSchema, SpectrumSessionStateSchema } from './spectrum.schema.js';
13
+ import { ClockStatusSummarySchema } from './system.schema.js';
14
+ // WebSocket消息类型枚举
15
+ export var WSMessageType;
16
+ (function (WSMessageType) {
17
+ // ===== 基础连接管理 =====
18
+ WSMessageType["PING"] = "ping";
19
+ WSMessageType["PONG"] = "pong";
20
+ WSMessageType["ERROR"] = "error";
21
+ /** 服务端通知客户端:此连接已被同一 clientInstance 的新连接替换,客户端不应重连 */
22
+ WSMessageType["CONNECTION_REPLACED"] = "connectionReplaced";
23
+ // ===== 引擎控制 =====
24
+ WSMessageType["START_ENGINE"] = "startEngine";
25
+ WSMessageType["STOP_ENGINE"] = "stopEngine";
26
+ WSMessageType["GET_STATUS"] = "getStatus";
27
+ WSMessageType["SET_MODE"] = "setMode";
28
+ // ===== 引擎事件 =====
29
+ WSMessageType["MODE_CHANGED"] = "modeChanged";
30
+ WSMessageType["SLOT_START"] = "slotStart";
31
+ WSMessageType["SUB_WINDOW"] = "subWindow";
32
+ WSMessageType["SLOT_PACK_UPDATED"] = "slotPackUpdated";
33
+ WSMessageType["SPECTRUM_CAPABILITIES"] = "spectrumCapabilities";
34
+ WSMessageType["SUBSCRIBE_SPECTRUM"] = "subscribeSpectrum";
35
+ WSMessageType["SPECTRUM_FRAME"] = "spectrumFrame";
36
+ WSMessageType["SPECTRUM_SESSION_STATE_CHANGED"] = "spectrumSessionStateChanged";
37
+ WSMessageType["INVOKE_SPECTRUM_CONTROL"] = "invokeSpectrumControl";
38
+ WSMessageType["DECODE_ERROR"] = "decodeError";
39
+ WSMessageType["SYSTEM_STATUS"] = "systemStatus";
40
+ WSMessageType["CLIENT_COUNT_CHANGED"] = "clientCountChanged";
41
+ WSMessageType["CLOCK_STATUS_CHANGED"] = "clockStatusChanged";
42
+ WSMessageType["RIGCTLD_STATUS"] = "rigctldStatus";
43
+ // ===== 电台操作员管理 =====
44
+ WSMessageType["GET_OPERATORS"] = "getOperators";
45
+ WSMessageType["OPERATORS_LIST"] = "operatorsList";
46
+ WSMessageType["OPERATOR_STATUS_UPDATE"] = "operatorStatusUpdate";
47
+ WSMessageType["SET_OPERATOR_CONTEXT"] = "setOperatorContext";
48
+ WSMessageType["SET_OPERATOR_RUNTIME_STATE"] = "setOperatorRuntimeState";
49
+ WSMessageType["SET_OPERATOR_RUNTIME_SLOT_CONTENT"] = "setOperatorRuntimeSlotContent";
50
+ WSMessageType["SET_OPERATOR_TRANSMIT_CYCLES"] = "setOperatorTransmitCycles";
51
+ WSMessageType["START_OPERATOR"] = "startOperator";
52
+ WSMessageType["STOP_OPERATOR"] = "stopOperator";
53
+ WSMessageType["OPERATOR_REQUEST_CALL"] = "operatorRequestCall";
54
+ // ===== 客户端操作员过滤 =====
55
+ WSMessageType["SET_CLIENT_ENABLED_OPERATORS"] = "setClientEnabledOperators";
56
+ WSMessageType["SET_CLIENT_SELECTED_OPERATOR"] = "setClientSelectedOperator";
57
+ // ===== 握手协议 =====
58
+ WSMessageType["CLIENT_HANDSHAKE"] = "clientHandshake";
59
+ WSMessageType["SERVER_HANDSHAKE_COMPLETE"] = "serverHandshakeComplete";
60
+ WSMessageType["SLOT_PACKS_RESET"] = "slotPacksReset";
61
+ // ===== 发射日志 =====
62
+ WSMessageType["TRANSMISSION_LOG"] = "transmissionLog";
63
+ // ===== 音量控制 =====
64
+ WSMessageType["SET_VOLUME_GAIN"] = "setVolumeGain";
65
+ WSMessageType["SET_VOLUME_GAIN_DB"] = "setVolumeGainDb";
66
+ WSMessageType["VOLUME_GAIN_CHANGED"] = "volumeGainChanged";
67
+ // ===== 通联日志 =====
68
+ WSMessageType["QSO_RECORD_ADDED"] = "qsoRecordAdded";
69
+ WSMessageType["QSO_RECORD_UPDATED"] = "qsoRecordUpdated";
70
+ WSMessageType["LOGBOOK_UPDATED"] = "logbookUpdated";
71
+ // 仅通知的日志本变更事件(专用于日志本WS)
72
+ WSMessageType["LOGBOOK_CHANGE_NOTICE"] = "logbookChangeNotice";
73
+ // ===== 电台连接管理 =====
74
+ WSMessageType["RADIO_STATUS_CHANGED"] = "radioStatusChanged";
75
+ WSMessageType["RADIO_ERROR"] = "radioError";
76
+ WSMessageType["RADIO_MANUAL_RECONNECT"] = "radioManualReconnect";
77
+ WSMessageType["RADIO_DISCONNECTED_DURING_TRANSMISSION"] = "radioDisconnectedDuringTransmission";
78
+ // ===== 频率管理 =====
79
+ WSMessageType["FREQUENCY_CHANGED"] = "frequencyChanged";
80
+ // ===== PTT状态管理 =====
81
+ WSMessageType["PTT_STATUS_CHANGED"] = "pttStatusChanged";
82
+ WSMessageType["FORCE_STOP_TRANSMISSION"] = "forceStopTransmission";
83
+ WSMessageType["REMOVE_OPERATOR_FROM_TRANSMISSION"] = "removeOperatorFromTransmission";
84
+ // ===== 电台数值表 =====
85
+ WSMessageType["METER_DATA"] = "meterData";
86
+ // ===== 极简文本消息 =====
87
+ WSMessageType["TEXT_MESSAGE"] = "textMessage";
88
+ // ===== 天线调谐器(已迁移至统一能力系统,枚举值保留以避免运行时错误,不再广播)=====
89
+ /** @deprecated Use RADIO_CAPABILITY_CHANGED with id='tuner_switch' instead */
90
+ WSMessageType["TUNER_STATUS_CHANGED"] = "tunerStatusChanged";
91
+ // ===== 统一电台控制能力系统 =====
92
+ /** 连接时下发能力快照(server → client) */
93
+ WSMessageType["RADIO_CAPABILITY_LIST"] = "radioCapabilityList";
94
+ /** 单个能力值变化(server → client) */
95
+ WSMessageType["RADIO_CAPABILITY_CHANGED"] = "radioCapabilityChanged";
96
+ /** 客户端写入能力值(client → server) */
97
+ WSMessageType["WRITE_RADIO_CAPABILITY"] = "writeRadioCapability";
98
+ /** 客户端请求刷新所有能力值(client → server) */
99
+ WSMessageType["REFRESH_RADIO_CAPABILITIES"] = "refreshRadioCapabilities";
100
+ // ===== 电台重连控制 =====
101
+ WSMessageType["RADIO_STOP_RECONNECT"] = "radioStopReconnect";
102
+ // ===== 音频旁路(sidecar) =====
103
+ /** 音频 sidecar 状态变化(server → client) */
104
+ WSMessageType["AUDIO_SIDECAR_STATUS_CHANGED"] = "audioSidecarStatusChanged";
105
+ /** 客户端请求立即重试音频启动(client → server) */
106
+ WSMessageType["AUDIO_RETRY_NOW"] = "audioRetryNow";
107
+ // ===== 电台电源管理 =====
108
+ /** 电源操作进度事件(server → client) */
109
+ WSMessageType["RADIO_POWER_STATE"] = "radioPowerState";
110
+ // ===== Profile 管理 =====
111
+ WSMessageType["PROFILE_CHANGED"] = "profileChanged";
112
+ WSMessageType["PROFILE_LIST_UPDATED"] = "profileListUpdated";
113
+ WSMessageType["REALTIME_SETTINGS_CHANGED"] = "realtimeSettingsChanged";
114
+ // ===== 认证 =====
115
+ WSMessageType["AUTH_REQUIRED"] = "authRequired";
116
+ WSMessageType["AUTH_TOKEN"] = "authToken";
117
+ WSMessageType["AUTH_PUBLIC_VIEWER"] = "authPublicViewer";
118
+ WSMessageType["AUTH_RESULT"] = "authResult";
119
+ WSMessageType["AUTH_EXPIRED"] = "authExpired";
120
+ // ===== 语音模式 =====
121
+ WSMessageType["VOICE_PTT_REQUEST"] = "voicePttRequest";
122
+ WSMessageType["VOICE_PTT_RELEASE"] = "voicePttRelease";
123
+ WSMessageType["VOICE_PTT_LOCK_CHANGED"] = "voicePttLockChanged";
124
+ WSMessageType["VOICE_SET_RADIO_MODE"] = "voiceSetRadioMode";
125
+ WSMessageType["VOICE_RADIO_MODE_CHANGED"] = "voiceRadioModeChanged";
126
+ // ===== 进程监控 =====
127
+ WSMessageType["PROCESS_SNAPSHOT"] = "processSnapshot";
128
+ WSMessageType["PROCESS_SNAPSHOT_HISTORY"] = "processSnapshotHistory";
129
+ // ===== 插件系统 =====
130
+ WSMessageType["PLUGIN_LIST"] = "pluginList";
131
+ WSMessageType["PLUGIN_STATUS_CHANGED"] = "pluginStatusChanged";
132
+ WSMessageType["PLUGIN_DATA"] = "pluginData";
133
+ WSMessageType["PLUGIN_LOG"] = "pluginLog";
134
+ WSMessageType["PLUGIN_RUNTIME_LOG"] = "pluginRuntimeLog";
135
+ WSMessageType["GET_PLUGIN_RUNTIME_LOG_HISTORY"] = "getPluginRuntimeLogHistory";
136
+ WSMessageType["PLUGIN_RUNTIME_LOG_HISTORY"] = "pluginRuntimeLogHistory";
137
+ WSMessageType["PLUGIN_USER_ACTION"] = "pluginUserAction";
138
+ WSMessageType["PLUGIN_PAGE_PUSH"] = "pluginPagePush";
139
+ // ===== OpenWebRX SDR =====
140
+ WSMessageType["OPENWEBRX_LISTEN_STATUS"] = "openwebrxListenStatus";
141
+ WSMessageType["OPENWEBRX_PROFILE_SELECT_REQUEST"] = "openwebrxProfileSelectRequest";
142
+ WSMessageType["OPENWEBRX_PROFILE_SELECT_RESPONSE"] = "openwebrxProfileSelectResponse";
143
+ WSMessageType["OPENWEBRX_PROFILE_VERIFY_RESULT"] = "openwebrxProfileVerifyResult";
144
+ WSMessageType["OPENWEBRX_CLIENT_COUNT"] = "openwebrxClientCount";
145
+ WSMessageType["OPENWEBRX_COOLDOWN_NOTICE"] = "openwebrxCooldownNotice";
146
+ })(WSMessageType || (WSMessageType = {}));
147
+ // ===== 共享数据类型Schema定义 =====
148
+ // 系统状态数据结构
149
+ export const SystemStatusSchema = z.object({
150
+ isRunning: z.boolean(),
151
+ isDecoding: z.boolean(),
152
+ currentMode: ModeDescriptorSchema,
153
+ currentTime: z.number(),
154
+ nextSlotIn: z.number(),
155
+ audioStarted: z.boolean(),
156
+ radioConnected: z.boolean().optional(),
157
+ radioConnectionHealth: z.object({
158
+ connectionHealthy: z.boolean(),
159
+ }).optional(),
160
+ /** 引擎模式:digital(FT8/FT4)或 voice(语音通联) */
161
+ engineMode: z.enum(['digital', 'voice']).default('digital'),
162
+ /** 当前电台调制模式(语音模式下使用,如 USB/LSB/FM/AM) */
163
+ currentRadioMode: z.string().optional(),
164
+ /** 引擎状态机当前状态 */
165
+ engineState: z.enum(['idle', 'waking', 'starting', 'running', 'stopping']).optional(),
166
+ /** 引擎状态机上下文的精简投影 */
167
+ engineContext: z.object({
168
+ error: z.string().optional(),
169
+ startedResources: z.array(z.string()).optional(),
170
+ forcedStop: z.boolean().optional(),
171
+ }).nullable().optional(),
172
+ });
173
+ // 子窗口信息数据结构
174
+ export const SubWindowInfoSchema = z.object({
175
+ slotInfo: SlotInfoSchema,
176
+ windowIdx: z.number(),
177
+ });
178
+ // 解码错误信息数据结构
179
+ export const DecodeErrorInfoSchema = z.object({
180
+ error: z.object({
181
+ message: z.string(),
182
+ stack: z.string().optional(),
183
+ }),
184
+ request: z.object({
185
+ slotId: z.string(),
186
+ windowIdx: z.number(),
187
+ }),
188
+ });
189
+ // 频率状态数据结构
190
+ export const FrequencyStateSchema = z.object({
191
+ frequency: z.number(),
192
+ mode: z.string(),
193
+ band: z.string(),
194
+ description: z.string(),
195
+ radioMode: z.string().optional(),
196
+ radioConnected: z.boolean(),
197
+ source: z.enum(['program', 'radio']).optional(),
198
+ });
199
+ export const SpectrumControlInvocationSchema = z.object({
200
+ id: SpectrumSessionControlIdSchema,
201
+ action: SpectrumSessionControlActionSchema,
202
+ });
203
+ // PTT状态数据结构
204
+ export const PTTStatusSchema = z.object({
205
+ isTransmitting: z.boolean(),
206
+ operatorIds: z.array(z.string()),
207
+ });
208
+ export const OperatorRuntimeSlotSchema = z.enum(['TX1', 'TX2', 'TX3', 'TX4', 'TX5', 'TX6']);
209
+ export const StrategyRuntimeContextSchema = z.object({
210
+ targetCallsign: z.string().optional(),
211
+ targetGrid: z.string().optional(),
212
+ reportSent: z.number().optional(),
213
+ reportReceived: z.number().optional(),
214
+ actualFrequency: z.number().optional(),
215
+ });
216
+ export const StrategyRuntimeSnapshotSchema = z.object({
217
+ currentState: z.string(),
218
+ slots: z.object({
219
+ TX1: z.string().optional(),
220
+ TX2: z.string().optional(),
221
+ TX3: z.string().optional(),
222
+ TX4: z.string().optional(),
223
+ TX5: z.string().optional(),
224
+ TX6: z.string().optional(),
225
+ }).optional(),
226
+ context: StrategyRuntimeContextSchema.optional(),
227
+ availableSlots: z.array(z.string()).optional(),
228
+ });
229
+ export const LevelMeterDisplayStyleSchema = z.enum([
230
+ 's-meter-dbm',
231
+ 's-meter',
232
+ 'db-over-s9',
233
+ ]);
234
+ /**
235
+ * Meter level reading
236
+ *
237
+ * Depending on the radio/vendor strategy, the same structure can represent:
238
+ * - branded S-meter output with optional dBm estimate
239
+ * - branded S-meter output without dBm display
240
+ * - generic Hamlib STRENGTH output shown as dB relative to S9
241
+ */
242
+ export const LevelMeterReadingSchema = z.object({
243
+ /** Raw meter reading from the radio or normalized backend value */
244
+ raw: z.number(),
245
+ /** Percentage (0-100%) */
246
+ percent: z.number(),
247
+ /** S-unit value (0-9+), supports decimal (e.g., 4.5 = S4.5) */
248
+ sUnits: z.number(),
249
+ /** dB above S9 (only when >S9, e.g., 20 means S9+20dB) */
250
+ dbAboveS9: z.number().optional(),
251
+ /** Estimated absolute signal level in dBm (may be approximate depending on strategy) */
252
+ dBm: z.number(),
253
+ /** Human-readable formatted string (e.g., "S4", "S9+20dB", "-24 dB@S9") */
254
+ formatted: z.string(),
255
+ /** Preferred frontend presentation style for this meter reading */
256
+ displayStyle: LevelMeterDisplayStyleSchema,
257
+ });
258
+ // 电台数值表能力检测
259
+ export const MeterCapabilitiesSchema = z.object({
260
+ strength: z.boolean(), // 接收信号强度 (STRENGTH)
261
+ swr: z.boolean(), // 驻波比 (SWR)
262
+ alc: z.boolean(), // 自动电平控制 (ALC)
263
+ power: z.boolean(), // 射频功率 (RFPOWER_METER / RFPOWER_METER_WATTS)
264
+ powerWatts: z.boolean(), // 是否支持绝对瓦数读取 (RFPOWER_METER_WATTS)
265
+ });
266
+ // 电台数值表数据结构
267
+ export const MeterDataSchema = z.object({
268
+ swr: z.object({
269
+ raw: z.number(),
270
+ swr: z.number(),
271
+ alert: z.boolean(),
272
+ }).nullable(),
273
+ alc: z.object({
274
+ raw: z.number(),
275
+ percent: z.number(),
276
+ alert: z.boolean(),
277
+ }).nullable(),
278
+ level: LevelMeterReadingSchema.nullable(),
279
+ power: z.object({
280
+ raw: z.number(),
281
+ percent: z.number(),
282
+ watts: z.number().nullable(), // 绝对瓦数(仅当电台支持 RFPOWER_METER_WATTS 时非 null)
283
+ maxWatts: z.number().nullable(), // 当前频率+模式对应的量程瓦数
284
+ }).nullable(),
285
+ });
286
+ // ===== WebSocket消息Schema定义 =====
287
+ // WebSocket基础消息结构
288
+ export const WSBaseMessageSchema = z.object({
289
+ type: z.nativeEnum(WSMessageType),
290
+ timestamp: z.string(),
291
+ id: z.string().optional(),
292
+ });
293
+ // 通用消息
294
+ export const WSPingMessageSchema = WSBaseMessageSchema.extend({
295
+ type: z.literal(WSMessageType.PING),
296
+ data: z.object({}).optional(),
297
+ });
298
+ export const WSPongMessageSchema = WSBaseMessageSchema.extend({
299
+ type: z.literal(WSMessageType.PONG),
300
+ data: z.object({}).optional(),
301
+ });
302
+ // 服务端到客户端消息
303
+ export const WSModeChangedMessageSchema = WSBaseMessageSchema.extend({
304
+ type: z.literal(WSMessageType.MODE_CHANGED),
305
+ data: ModeDescriptorSchema,
306
+ });
307
+ export const WSSlotStartMessageSchema = WSBaseMessageSchema.extend({
308
+ type: z.literal(WSMessageType.SLOT_START),
309
+ data: SlotInfoSchema,
310
+ });
311
+ export const WSSubWindowMessageSchema = WSBaseMessageSchema.extend({
312
+ type: z.literal(WSMessageType.SUB_WINDOW),
313
+ data: SubWindowInfoSchema,
314
+ });
315
+ export const WSSlotPackUpdatedMessageSchema = WSBaseMessageSchema.extend({
316
+ type: z.literal(WSMessageType.SLOT_PACK_UPDATED),
317
+ data: SlotPackSchema,
318
+ });
319
+ export const WSSpectrumCapabilitiesMessageSchema = WSBaseMessageSchema.extend({
320
+ type: z.literal(WSMessageType.SPECTRUM_CAPABILITIES),
321
+ data: SpectrumCapabilitiesSchema,
322
+ });
323
+ export const WSSubscribeSpectrumMessageSchema = WSBaseMessageSchema.extend({
324
+ type: z.literal(WSMessageType.SUBSCRIBE_SPECTRUM),
325
+ data: z.object({
326
+ kind: SpectrumKindSchema.nullable(),
327
+ }),
328
+ });
329
+ export const WSSpectrumFrameMessageSchema = WSBaseMessageSchema.extend({
330
+ type: z.literal(WSMessageType.SPECTRUM_FRAME),
331
+ data: SpectrumFrameSchema,
332
+ });
333
+ export const WSSpectrumSessionStateChangedMessageSchema = WSBaseMessageSchema.extend({
334
+ type: z.literal(WSMessageType.SPECTRUM_SESSION_STATE_CHANGED),
335
+ data: SpectrumSessionStateSchema,
336
+ });
337
+ export const WSInvokeSpectrumControlMessageSchema = WSBaseMessageSchema.extend({
338
+ type: z.literal(WSMessageType.INVOKE_SPECTRUM_CONTROL),
339
+ data: SpectrumControlInvocationSchema,
340
+ });
341
+ export const WSDecodeErrorMessageSchema = WSBaseMessageSchema.extend({
342
+ type: z.literal(WSMessageType.DECODE_ERROR),
343
+ data: DecodeErrorInfoSchema,
344
+ });
345
+ export const WSSystemStatusMessageSchema = WSBaseMessageSchema.extend({
346
+ type: z.literal(WSMessageType.SYSTEM_STATUS),
347
+ data: SystemStatusSchema,
348
+ });
349
+ export const WSClientCountChangedMessageSchema = WSBaseMessageSchema.extend({
350
+ type: z.literal(WSMessageType.CLIENT_COUNT_CHANGED),
351
+ data: z.object({
352
+ count: z.number(),
353
+ timestamp: z.number(),
354
+ }),
355
+ });
356
+ export const WSClockStatusChangedMessageSchema = WSBaseMessageSchema.extend({
357
+ type: z.literal(WSMessageType.CLOCK_STATUS_CHANGED),
358
+ data: ClockStatusSummarySchema,
359
+ });
360
+ export const WSErrorMessageSchema = WSBaseMessageSchema.extend({
361
+ type: z.literal(WSMessageType.ERROR),
362
+ data: z.object({
363
+ message: z.string(),
364
+ userMessage: z.string().optional(),
365
+ /** 前端 i18n 翻译键(优先用于 Toast 副标题本地化) */
366
+ userMessageKey: z.string().optional(),
367
+ userMessageParams: z.record(z.union([z.string(), z.number()])).optional(),
368
+ code: z.string().optional(),
369
+ severity: z.enum(['info', 'warning', 'error', 'critical']).optional(),
370
+ suggestions: z.array(z.string()).optional(),
371
+ timestamp: z.union([z.string(), z.number()]).optional(),
372
+ context: z.record(z.unknown()).optional(),
373
+ details: z.any().optional(),
374
+ }),
375
+ });
376
+ // 日志本轻量变更通知(仅包含标识信息)
377
+ export const WSLogbookChangeNoticeMessageSchema = WSBaseMessageSchema.extend({
378
+ type: z.literal(WSMessageType.LOGBOOK_CHANGE_NOTICE),
379
+ data: z.object({
380
+ logBookId: z.string(),
381
+ operatorId: z.string().optional(),
382
+ }),
383
+ });
384
+ // 客户端到服务端消息
385
+ export const WSStartEngineMessageSchema = WSBaseMessageSchema.extend({
386
+ type: z.literal(WSMessageType.START_ENGINE),
387
+ data: z.object({}).optional(),
388
+ });
389
+ export const WSStopEngineMessageSchema = WSBaseMessageSchema.extend({
390
+ type: z.literal(WSMessageType.STOP_ENGINE),
391
+ data: z.object({}).optional(),
392
+ });
393
+ export const WSSetModeMessageSchema = WSBaseMessageSchema.extend({
394
+ type: z.literal(WSMessageType.SET_MODE),
395
+ data: z.object({
396
+ mode: ModeDescriptorSchema,
397
+ }),
398
+ });
399
+ export const WSGetStatusMessageSchema = WSBaseMessageSchema.extend({
400
+ type: z.literal(WSMessageType.GET_STATUS),
401
+ data: z.object({}).optional(),
402
+ });
403
+ // ===== 电台操作员相关Schema =====
404
+ /**
405
+ * 电台操作员状态信息
406
+ */
407
+ export const OperatorStatusSchema = z.object({
408
+ id: z.string(),
409
+ isActive: z.boolean(),
410
+ isTransmitting: z.boolean(), // 是否正在发射(发射开关状态)
411
+ isInActivePTT: z.boolean().optional(), // 该操作员的音频是否正在被实际播放
412
+ currentSlot: z.string().optional(),
413
+ context: z.object({
414
+ myCall: z.string(),
415
+ myGrid: z.string(),
416
+ targetCall: z.string(),
417
+ targetGrid: z.string().optional(),
418
+ frequency: z.number().optional(),
419
+ reportSent: z.number().optional(), // 改为number类型
420
+ reportReceived: z.number().optional(), // 改为number类型
421
+ }),
422
+ strategy: z.object({
423
+ name: z.string(),
424
+ state: z.string(),
425
+ availableSlots: z.array(z.string()),
426
+ }),
427
+ runtime: StrategyRuntimeSnapshotSchema.optional(),
428
+ cycleInfo: z.object({
429
+ currentCycle: z.number(),
430
+ isTransmitCycle: z.boolean(),
431
+ cycleProgress: z.number().min(0).max(1), // 0-1 表示周期进度百分比
432
+ }).optional(),
433
+ // TX1-TX6 时隙内容
434
+ slots: z.object({
435
+ TX1: z.string().optional(),
436
+ TX2: z.string().optional(),
437
+ TX3: z.string().optional(),
438
+ TX4: z.string().optional(),
439
+ TX5: z.string().optional(),
440
+ TX6: z.string().optional(),
441
+ }).optional(),
442
+ // 发射周期配置
443
+ transmitCycles: z.array(z.number()).optional(),
444
+ });
445
+ /**
446
+ * 获取操作员列表消息
447
+ */
448
+ export const WSGetOperatorsMessageSchema = WSBaseMessageSchema.extend({
449
+ type: z.literal(WSMessageType.GET_OPERATORS),
450
+ });
451
+ /**
452
+ * 操作员列表响应消息
453
+ */
454
+ export const WSOperatorsListMessageSchema = WSBaseMessageSchema.extend({
455
+ type: z.literal(WSMessageType.OPERATORS_LIST),
456
+ data: z.object({
457
+ operators: z.array(OperatorStatusSchema),
458
+ }),
459
+ });
460
+ /**
461
+ * 操作员状态更新消息
462
+ */
463
+ export const WSOperatorStatusUpdateMessageSchema = WSBaseMessageSchema.extend({
464
+ type: z.literal(WSMessageType.OPERATOR_STATUS_UPDATE),
465
+ data: OperatorStatusSchema,
466
+ });
467
+ /**
468
+ * 设置操作员上下文消息
469
+ */
470
+ export const WSSetOperatorContextMessageSchema = WSBaseMessageSchema.extend({
471
+ type: z.literal(WSMessageType.SET_OPERATOR_CONTEXT),
472
+ data: z.object({
473
+ operatorId: z.string(),
474
+ context: z.object({
475
+ myCall: z.string().optional(),
476
+ myGrid: z.string().optional(),
477
+ targetCallsign: z.string().optional(),
478
+ targetGrid: z.string().optional(),
479
+ frequency: z.number().optional(),
480
+ reportSent: z.number().optional(),
481
+ reportReceived: z.number().optional(),
482
+ }),
483
+ }),
484
+ });
485
+ /**
486
+ * 设置操作员运行时状态消息
487
+ */
488
+ export const WSSetOperatorRuntimeStateMessageSchema = WSBaseMessageSchema.extend({
489
+ type: z.literal(WSMessageType.SET_OPERATOR_RUNTIME_STATE),
490
+ data: z.object({
491
+ operatorId: z.string(),
492
+ state: OperatorRuntimeSlotSchema,
493
+ }),
494
+ });
495
+ /**
496
+ * 设置操作员运行时槽位内容消息
497
+ */
498
+ export const WSSetOperatorRuntimeSlotContentMessageSchema = WSBaseMessageSchema.extend({
499
+ type: z.literal(WSMessageType.SET_OPERATOR_RUNTIME_SLOT_CONTENT),
500
+ data: z.object({
501
+ operatorId: z.string(),
502
+ slot: OperatorRuntimeSlotSchema,
503
+ content: z.string(),
504
+ }),
505
+ });
506
+ /**
507
+ * 设置操作员发射周期消息
508
+ */
509
+ export const WSSetOperatorTransmitCyclesMessageSchema = WSBaseMessageSchema.extend({
510
+ type: z.literal(WSMessageType.SET_OPERATOR_TRANSMIT_CYCLES),
511
+ data: z.object({
512
+ operatorId: z.string(),
513
+ transmitCycles: z.array(z.number().min(0).max(1)),
514
+ }),
515
+ });
516
+ /**
517
+ * 启动操作员消息
518
+ */
519
+ export const WSStartOperatorMessageSchema = WSBaseMessageSchema.extend({
520
+ type: z.literal(WSMessageType.START_OPERATOR),
521
+ data: z.object({
522
+ operatorId: z.string(),
523
+ }),
524
+ });
525
+ /**
526
+ * 停止操作员消息
527
+ */
528
+ export const WSStopOperatorMessageSchema = WSBaseMessageSchema.extend({
529
+ type: z.literal(WSMessageType.STOP_OPERATOR),
530
+ data: z.object({
531
+ operatorId: z.string(),
532
+ }),
533
+ });
534
+ /**
535
+ * 手动双击消息行时附带的原始解码快照
536
+ */
537
+ export const WSSelectedFrameSchema = z.object({
538
+ message: z.string(),
539
+ snr: z.number(),
540
+ dt: z.number(),
541
+ freq: z.number(),
542
+ slotStartMs: z.number(),
543
+ });
544
+ /**
545
+ * 操作员请求手动呼叫消息
546
+ */
547
+ export const WSOperatorRequestCallMessageSchema = WSBaseMessageSchema.extend({
548
+ type: z.literal(WSMessageType.OPERATOR_REQUEST_CALL),
549
+ data: z.object({
550
+ operatorId: z.string(),
551
+ callsign: z.string(),
552
+ selectedFrame: WSSelectedFrameSchema.optional(),
553
+ }),
554
+ });
555
+ /**
556
+ * 设置客户端启用的操作员列表消息
557
+ */
558
+ export const WSSetClientEnabledOperatorsMessageSchema = WSBaseMessageSchema.extend({
559
+ type: z.literal(WSMessageType.SET_CLIENT_ENABLED_OPERATORS),
560
+ data: z.object({
561
+ enabledOperatorIds: z.array(z.string()),
562
+ }),
563
+ });
564
+ /**
565
+ * 设置客户端当前选中操作员消息
566
+ */
567
+ export const WSSetClientSelectedOperatorMessageSchema = WSBaseMessageSchema.extend({
568
+ type: z.literal(WSMessageType.SET_CLIENT_SELECTED_OPERATOR),
569
+ data: z.object({
570
+ selectedOperatorId: z.string().nullable(),
571
+ }),
572
+ });
573
+ /**
574
+ * 客户端握手消息
575
+ */
576
+ export const WSClientHandshakeMessageSchema = WSBaseMessageSchema.extend({
577
+ type: z.literal(WSMessageType.CLIENT_HANDSHAKE),
578
+ data: z.object({
579
+ enabledOperatorIds: z.array(z.string()).nullable(), // null表示新客户端,数组表示已配置的偏好
580
+ selectedOperatorId: z.string().nullable().optional(),
581
+ clientInstanceId: z.string().min(1),
582
+ clientVersion: z.string().optional(),
583
+ clientCapabilities: z.array(z.string()).optional(),
584
+ }),
585
+ });
586
+ /**
587
+ * 服务器握手完成消息
588
+ */
589
+ export const WSServerHandshakeCompleteMessageSchema = WSBaseMessageSchema.extend({
590
+ type: z.literal(WSMessageType.SERVER_HANDSHAKE_COMPLETE),
591
+ data: z.object({
592
+ serverVersion: z.string().optional(),
593
+ supportedFeatures: z.array(z.string()).optional(),
594
+ finalEnabledOperatorIds: z.array(z.string()).optional(),
595
+ finalSelectedOperatorId: z.string().nullable().optional(),
596
+ }),
597
+ });
598
+ export const WSSlotPacksResetMessageSchema = WSBaseMessageSchema.extend({
599
+ type: z.literal(WSMessageType.SLOT_PACKS_RESET),
600
+ data: z.object({
601
+ phase: z.enum(['start', 'complete']),
602
+ }),
603
+ });
604
+ /**
605
+ * 发射日志消息
606
+ */
607
+ export const WSTransmissionLogMessageSchema = WSBaseMessageSchema.extend({
608
+ type: z.literal(WSMessageType.TRANSMISSION_LOG),
609
+ data: z.object({
610
+ operatorId: z.string(),
611
+ time: z.string(),
612
+ message: z.string(),
613
+ frequency: z.number(),
614
+ slotStartMs: z.number()
615
+ }),
616
+ });
617
+ /**
618
+ * 设置音量增益消息(线性单位)
619
+ */
620
+ export const WSSetVolumeGainMessageSchema = WSBaseMessageSchema.extend({
621
+ type: z.literal(WSMessageType.SET_VOLUME_GAIN),
622
+ data: z.object({
623
+ gain: z.number().min(0.001).max(10),
624
+ }),
625
+ });
626
+ /**
627
+ * 设置音量增益消息(dB单位)
628
+ */
629
+ export const WSSetVolumeGainDbMessageSchema = WSBaseMessageSchema.extend({
630
+ type: z.literal(WSMessageType.SET_VOLUME_GAIN_DB),
631
+ data: z.object({
632
+ gainDb: z.number().min(-60).max(20),
633
+ }),
634
+ });
635
+ /**
636
+ * QSO记录添加消息(服务端到客户端)
637
+ */
638
+ export const WSQSORecordAddedMessageSchema = WSBaseMessageSchema.extend({
639
+ type: z.literal(WSMessageType.QSO_RECORD_ADDED),
640
+ data: z.object({
641
+ operatorId: z.string(),
642
+ logBookId: z.string(),
643
+ qsoRecord: QSORecordSchema,
644
+ }),
645
+ });
646
+ /**
647
+ * QSO记录更新消息(服务端到客户端)
648
+ */
649
+ export const WSQSORecordUpdatedMessageSchema = WSBaseMessageSchema.extend({
650
+ type: z.literal(WSMessageType.QSO_RECORD_UPDATED),
651
+ data: z.object({
652
+ operatorId: z.string(),
653
+ logBookId: z.string(),
654
+ qsoRecord: QSORecordSchema,
655
+ }),
656
+ });
657
+ /**
658
+ * 日志本更新消息(服务端到客户端)
659
+ */
660
+ export const WSLogbookUpdatedMessageSchema = WSBaseMessageSchema.extend({
661
+ type: z.literal(WSMessageType.LOGBOOK_UPDATED),
662
+ data: z.object({
663
+ logBookId: z.string(),
664
+ statistics: LogBookStatisticsSchema,
665
+ }),
666
+ });
667
+ /**
668
+ * 电台状态变化消息
669
+ */
670
+ export const WSRadioStatusChangedMessageSchema = WSBaseMessageSchema.extend({
671
+ type: z.literal(WSMessageType.RADIO_STATUS_CHANGED),
672
+ data: z.object({
673
+ connected: z.boolean(),
674
+ status: RadioConnectionStatusSchema, // 精细化连接状态(必填)
675
+ radioInfo: RadioInfoSchema.nullable(), // 电台信息(连接时有值,断开时为null)
676
+ radioConfig: HamlibConfigSchema.optional(), // 电台配置(保持当前配置)
677
+ reason: z.string().optional(),
678
+ message: z.string().optional(), // 用户友好的消息
679
+ recommendation: z.string().optional(), // 操作建议
680
+ reconnectProgress: ReconnectProgressSchema.optional(), // 重连进度
681
+ connectionHealth: z.object({
682
+ connectionHealthy: z.boolean(),
683
+ }).optional(),
684
+ coreCapabilities: CoreRadioCapabilitiesSchema.optional(),
685
+ coreCapabilityDiagnostics: CoreCapabilityDiagnosticsSchema.optional(),
686
+ meterCapabilities: MeterCapabilitiesSchema.optional(), // 电台数值表能力(connected=true 时应始终携带)
687
+ /** @deprecated Tuner capability is now in radioCapabilityList event. Kept for backward compat. */
688
+ tunerCapabilities: TunerCapabilitiesSchema.optional(),
689
+ }),
690
+ });
691
+ /**
692
+ * 电台错误事件数据(专用错误频道)
693
+ * 包含完整的错误信息、解决建议、Profile 关联等
694
+ */
695
+ export const RadioErrorEventDataSchema = z.object({
696
+ /** 技术错误消息(精简摘要;完整原始内容在 context.rawHamlibTrace) */
697
+ message: z.string(),
698
+ /** 用户友好的错误消息(兜底英文文案) */
699
+ userMessage: z.string(),
700
+ /** 前端 i18n 翻译键,格式如 'radio:error.xxx';存在时前端优先使用 t() */
701
+ userMessageKey: z.string().optional(),
702
+ /** i18n 参数(与 userMessageKey 配合) */
703
+ userMessageParams: z.record(z.union([z.string(), z.number()])).optional(),
704
+ /** 解决建议列表 */
705
+ suggestions: z.array(z.string()).default([]),
706
+ /** 错误代码(RadioErrorCode) */
707
+ code: z.string().optional(),
708
+ /** 严重程度 */
709
+ severity: z.enum(['info', 'warning', 'error', 'critical']).default('error'),
710
+ /** ISO 时间戳(服务端生成) */
711
+ timestamp: z.string(),
712
+ /** 错误堆栈(仅非生产环境) */
713
+ stack: z.string().optional(),
714
+ /** 错误上下文 */
715
+ context: z.record(z.unknown()).optional(),
716
+ /** 连接健康状态 */
717
+ connectionHealth: z.object({
718
+ connectionHealthy: z.boolean(),
719
+ }).optional(),
720
+ /** 关联的 Profile ID */
721
+ profileId: z.string().nullable(),
722
+ /** 关联的 Profile 名称 */
723
+ profileName: z.string().nullable(),
724
+ });
725
+ /**
726
+ * 电台错误消息
727
+ */
728
+ export const WSRadioErrorMessageSchema = WSBaseMessageSchema.extend({
729
+ type: z.literal(WSMessageType.RADIO_ERROR),
730
+ data: RadioErrorEventDataSchema,
731
+ });
732
+ /**
733
+ * 手动重连电台消息(客户端到服务端)
734
+ */
735
+ export const WSRadioManualReconnectMessageSchema = WSBaseMessageSchema.extend({
736
+ type: z.literal(WSMessageType.RADIO_MANUAL_RECONNECT),
737
+ data: z.object({}).optional(),
738
+ });
739
+ /**
740
+ * 电台发射中断开连接消息
741
+ */
742
+ export const WSRadioDisconnectedDuringTransmissionMessageSchema = WSBaseMessageSchema.extend({
743
+ type: z.literal(WSMessageType.RADIO_DISCONNECTED_DURING_TRANSMISSION),
744
+ data: z.object({
745
+ reason: z.string(),
746
+ message: z.string(),
747
+ recommendation: z.string(),
748
+ }),
749
+ });
750
+ /**
751
+ * 停止电台重连消息(客户端到服务端)
752
+ */
753
+ export const WSRadioStopReconnectMessageSchema = WSBaseMessageSchema.extend({
754
+ type: z.literal(WSMessageType.RADIO_STOP_RECONNECT),
755
+ data: z.object({}).optional(),
756
+ });
757
+ /**
758
+ * 音频 sidecar 状态变化消息(服务端→客户端)
759
+ */
760
+ export const WSAudioSidecarStatusChangedMessageSchema = WSBaseMessageSchema.extend({
761
+ type: z.literal(WSMessageType.AUDIO_SIDECAR_STATUS_CHANGED),
762
+ data: AudioSidecarStatusPayloadSchema,
763
+ });
764
+ /**
765
+ * 音频立即重试命令(客户端→服务端)
766
+ */
767
+ export const WSAudioRetryNowMessageSchema = WSBaseMessageSchema.extend({
768
+ type: z.literal(WSMessageType.AUDIO_RETRY_NOW),
769
+ data: z.object({}).optional(),
770
+ });
771
+ /**
772
+ * 电源操作进度事件(服务端→客户端)
773
+ */
774
+ export const WSRadioPowerStateMessageSchema = WSBaseMessageSchema.extend({
775
+ type: z.literal(WSMessageType.RADIO_POWER_STATE),
776
+ data: RadioPowerStateEventSchema,
777
+ });
778
+ /**
779
+ * 频率变化消息(服务端到客户端)
780
+ */
781
+ export const WSFrequencyChangedMessageSchema = WSBaseMessageSchema.extend({
782
+ type: z.literal(WSMessageType.FREQUENCY_CHANGED),
783
+ data: FrequencyStateSchema,
784
+ });
785
+ export const WSSpectrumSessionStateChangedOutboundMessageSchema = WSBaseMessageSchema.extend({
786
+ type: z.literal(WSMessageType.SPECTRUM_SESSION_STATE_CHANGED),
787
+ data: SpectrumSessionStateSchema,
788
+ });
789
+ /**
790
+ * PTT状态变化消息(服务端到客户端)
791
+ */
792
+ export const WSPTTStatusChangedMessageSchema = WSBaseMessageSchema.extend({
793
+ type: z.literal(WSMessageType.PTT_STATUS_CHANGED),
794
+ data: PTTStatusSchema,
795
+ });
796
+ /**
797
+ * 强制停止发射消息(客户端到服务端)
798
+ * 立即停止PTT并清空音频播放队列
799
+ */
800
+ export const WSForceStopTransmissionMessageSchema = WSBaseMessageSchema.extend({
801
+ type: z.literal(WSMessageType.FORCE_STOP_TRANSMISSION),
802
+ data: z.object({}).optional(),
803
+ });
804
+ /**
805
+ * 从当前发射中移除单个操作员消息(客户端到服务端)
806
+ * 移除该操作员的音频并重混音,如果是最后一个操作员则停止PTT
807
+ */
808
+ export const WSRemoveOperatorFromTransmissionMessageSchema = WSBaseMessageSchema.extend({
809
+ type: z.literal(WSMessageType.REMOVE_OPERATOR_FROM_TRANSMISSION),
810
+ data: z.object({
811
+ operatorId: z.string(),
812
+ }),
813
+ });
814
+ /**
815
+ * 电台数值表数据消息(服务端到客户端)
816
+ */
817
+ export const WSMeterDataMessageSchema = WSBaseMessageSchema.extend({
818
+ type: z.literal(WSMessageType.METER_DATA),
819
+ data: MeterDataSchema,
820
+ });
821
+ /**
822
+ * 文本消息(Toast通知)
823
+ * 用于向客户端推送提示信息
824
+ */
825
+ export const WSTextMessageSchema = WSBaseMessageSchema.extend({
826
+ type: z.literal(WSMessageType.TEXT_MESSAGE),
827
+ data: z.object({
828
+ title: z.string(),
829
+ text: z.string(),
830
+ color: z.enum(['success', 'warning', 'danger', 'default']).optional(),
831
+ timeout: z.number().nullable().optional(), // null 表示需要手动关闭,number 表示自动关闭的毫秒数
832
+ /** i18n key,前端有此字段时优先翻译显示 */
833
+ key: z.string().optional(),
834
+ /** i18n 插值参数 */
835
+ params: z.record(z.string()).optional(),
836
+ }),
837
+ });
838
+ // ===== 统一电台控制能力消息 =====
839
+ /**
840
+ * 能力列表快照(连接成功后推送,server → client)
841
+ */
842
+ export const WSRadioCapabilityListMessageSchema = WSBaseMessageSchema.extend({
843
+ type: z.literal(WSMessageType.RADIO_CAPABILITY_LIST),
844
+ data: CapabilityListSchema,
845
+ });
846
+ /**
847
+ * 单个能力值变化通知(server → client)
848
+ */
849
+ export const WSRadioCapabilityChangedMessageSchema = WSBaseMessageSchema.extend({
850
+ type: z.literal(WSMessageType.RADIO_CAPABILITY_CHANGED),
851
+ data: CapabilityStateSchema,
852
+ });
853
+ /**
854
+ * 客户端写入能力值命令(client → server)
855
+ * 权限:execute:RadioControl
856
+ */
857
+ export const WSWriteRadioCapabilityMessageSchema = WSBaseMessageSchema.extend({
858
+ type: z.literal(WSMessageType.WRITE_RADIO_CAPABILITY),
859
+ data: WriteCapabilityPayloadSchema,
860
+ });
861
+ // ===== 认证相关消息 =====
862
+ /**
863
+ * 服务端要求认证(连接建立后发送)
864
+ */
865
+ export const WSAuthRequiredMessageSchema = WSBaseMessageSchema.extend({
866
+ type: z.literal(WSMessageType.AUTH_REQUIRED),
867
+ data: z.object({
868
+ allowPublicViewing: z.boolean(),
869
+ }),
870
+ });
871
+ /**
872
+ * 客户端发送 JWT 进行认证(登录或权限升级)
873
+ */
874
+ export const WSAuthTokenMessageSchema = WSBaseMessageSchema.extend({
875
+ type: z.literal(WSMessageType.AUTH_TOKEN),
876
+ data: z.object({
877
+ jwt: z.string(),
878
+ }),
879
+ });
880
+ /**
881
+ * 客户端选择公开观察者模式
882
+ */
883
+ export const WSAuthPublicViewerMessageSchema = WSBaseMessageSchema.extend({
884
+ type: z.literal(WSMessageType.AUTH_PUBLIC_VIEWER),
885
+ data: z.object({}).optional(),
886
+ });
887
+ /**
888
+ * 认证结果(服务端到客户端)
889
+ */
890
+ export const WSAuthResultMessageSchema = WSBaseMessageSchema.extend({
891
+ type: z.literal(WSMessageType.AUTH_RESULT),
892
+ data: z.object({
893
+ success: z.boolean(),
894
+ role: z.nativeEnum(UserRole).optional(),
895
+ label: z.string().optional(),
896
+ operatorIds: z.array(z.string()).optional(),
897
+ error: z.string().optional(),
898
+ }),
899
+ });
900
+ /**
901
+ * JWT 过期通知(服务端到客户端)
902
+ */
903
+ export const WSAuthExpiredMessageSchema = WSBaseMessageSchema.extend({
904
+ type: z.literal(WSMessageType.AUTH_EXPIRED),
905
+ data: z.object({
906
+ reason: z.string().optional(),
907
+ }),
908
+ });
909
+ export const WSPluginListMessageSchema = WSBaseMessageSchema.extend({
910
+ type: z.literal(WSMessageType.PLUGIN_LIST),
911
+ data: PluginSystemSnapshotSchema,
912
+ });
913
+ export const WSPluginStatusChangedMessageSchema = WSBaseMessageSchema.extend({
914
+ type: z.literal(WSMessageType.PLUGIN_STATUS_CHANGED),
915
+ data: z.object({
916
+ generation: z.number().int().nonnegative(),
917
+ plugin: PluginStatusSchema,
918
+ }),
919
+ });
920
+ export const WSPluginDataMessageSchema = WSBaseMessageSchema.extend({
921
+ type: z.literal(WSMessageType.PLUGIN_DATA),
922
+ data: PluginDataPayloadSchema,
923
+ });
924
+ export const WSPluginLogMessageSchema = WSBaseMessageSchema.extend({
925
+ type: z.literal(WSMessageType.PLUGIN_LOG),
926
+ data: PluginLogEntrySchema,
927
+ });
928
+ export const WSPluginRuntimeLogMessageSchema = WSBaseMessageSchema.extend({
929
+ type: z.literal(WSMessageType.PLUGIN_RUNTIME_LOG),
930
+ data: PluginRuntimeLogEntrySchema,
931
+ });
932
+ export const WSGetPluginRuntimeLogHistoryMessageSchema = WSBaseMessageSchema.extend({
933
+ type: z.literal(WSMessageType.GET_PLUGIN_RUNTIME_LOG_HISTORY),
934
+ data: z.object({
935
+ limit: z.number().int().positive().max(5000).optional(),
936
+ }).optional(),
937
+ });
938
+ export const WSPluginRuntimeLogHistoryMessageSchema = WSBaseMessageSchema.extend({
939
+ type: z.literal(WSMessageType.PLUGIN_RUNTIME_LOG_HISTORY),
940
+ data: PluginRuntimeLogHistoryPayloadSchema,
941
+ });
942
+ export const WSPluginUserActionMessageSchema = WSBaseMessageSchema.extend({
943
+ type: z.literal(WSMessageType.PLUGIN_USER_ACTION),
944
+ data: PluginUserActionPayloadSchema,
945
+ });
946
+ // 联合所有WebSocket消息类型
947
+ export const WSMessageSchema = z.discriminatedUnion('type', [
948
+ WSPingMessageSchema,
949
+ WSPongMessageSchema,
950
+ // 服务端到客户端
951
+ WSModeChangedMessageSchema,
952
+ WSSlotStartMessageSchema,
953
+ WSSubWindowMessageSchema,
954
+ WSSlotPackUpdatedMessageSchema,
955
+ WSSpectrumCapabilitiesMessageSchema,
956
+ WSSubscribeSpectrumMessageSchema,
957
+ WSSpectrumFrameMessageSchema,
958
+ WSSpectrumSessionStateChangedMessageSchema,
959
+ WSInvokeSpectrumControlMessageSchema,
960
+ WSDecodeErrorMessageSchema,
961
+ WSSystemStatusMessageSchema,
962
+ WSClientCountChangedMessageSchema,
963
+ WSClockStatusChangedMessageSchema,
964
+ WSErrorMessageSchema,
965
+ WSLogbookChangeNoticeMessageSchema,
966
+ WSTransmissionLogMessageSchema,
967
+ // 客户端到服务端
968
+ WSStartEngineMessageSchema,
969
+ WSStopEngineMessageSchema,
970
+ WSSetModeMessageSchema,
971
+ WSGetStatusMessageSchema,
972
+ // 操作员相关消息
973
+ WSGetOperatorsMessageSchema,
974
+ WSOperatorsListMessageSchema,
975
+ WSOperatorStatusUpdateMessageSchema,
976
+ WSSetOperatorContextMessageSchema,
977
+ WSSetOperatorRuntimeStateMessageSchema,
978
+ WSSetOperatorRuntimeSlotContentMessageSchema,
979
+ WSSetOperatorTransmitCyclesMessageSchema,
980
+ WSStartOperatorMessageSchema,
981
+ WSStopOperatorMessageSchema,
982
+ WSOperatorRequestCallMessageSchema,
983
+ // 音量控制消息
984
+ WSSetVolumeGainMessageSchema,
985
+ WSSetVolumeGainDbMessageSchema,
986
+ // 通联日志消息
987
+ WSQSORecordAddedMessageSchema,
988
+ WSQSORecordUpdatedMessageSchema,
989
+ WSLogbookUpdatedMessageSchema,
990
+ // 客户端启用操作员列表消息
991
+ WSSetClientEnabledOperatorsMessageSchema,
992
+ WSSetClientSelectedOperatorMessageSchema,
993
+ // 握手消息
994
+ WSClientHandshakeMessageSchema,
995
+ WSServerHandshakeCompleteMessageSchema,
996
+ WSSlotPacksResetMessageSchema,
997
+ // 电台连接管理消息
998
+ WSRadioStatusChangedMessageSchema,
999
+ WSRadioErrorMessageSchema,
1000
+ WSRadioManualReconnectMessageSchema,
1001
+ WSRadioStopReconnectMessageSchema,
1002
+ WSRadioDisconnectedDuringTransmissionMessageSchema,
1003
+ WSRadioPowerStateMessageSchema,
1004
+ // 音频 sidecar 消息
1005
+ WSAudioSidecarStatusChangedMessageSchema,
1006
+ WSAudioRetryNowMessageSchema,
1007
+ // 频率管理消息
1008
+ WSFrequencyChangedMessageSchema,
1009
+ // PTT状态管理消息
1010
+ WSPTTStatusChangedMessageSchema,
1011
+ WSForceStopTransmissionMessageSchema,
1012
+ WSRemoveOperatorFromTransmissionMessageSchema,
1013
+ // 电台数值表消息
1014
+ WSMeterDataMessageSchema,
1015
+ // 文本消息(Toast通知)
1016
+ WSTextMessageSchema,
1017
+ // 认证消息
1018
+ WSAuthRequiredMessageSchema,
1019
+ WSAuthTokenMessageSchema,
1020
+ WSAuthPublicViewerMessageSchema,
1021
+ WSAuthResultMessageSchema,
1022
+ WSAuthExpiredMessageSchema,
1023
+ // 插件系统消息
1024
+ WSPluginListMessageSchema,
1025
+ WSPluginStatusChangedMessageSchema,
1026
+ WSPluginDataMessageSchema,
1027
+ WSPluginLogMessageSchema,
1028
+ WSPluginRuntimeLogMessageSchema,
1029
+ WSGetPluginRuntimeLogHistoryMessageSchema,
1030
+ WSPluginRuntimeLogHistoryMessageSchema,
1031
+ WSPluginUserActionMessageSchema,
1032
+ ]);
1033
+ export const TransmitRequestSchema = z.object({
1034
+ operatorId: z.string(),
1035
+ transmission: z.string(),
1036
+ /** 是否覆盖同一操作员在同一时隙的现有 TX 帧(自动重决策时为 true) */
1037
+ replaceExisting: z.boolean().optional(),
1038
+ });
1039
+ // ===== 前端应用事件接口 =====
1040
+ /**
1041
+ * 发射完成事件信息
1042
+ */
1043
+ export const TransmissionCompleteInfoSchema = z.object({
1044
+ operatorId: z.string(),
1045
+ success: z.boolean(),
1046
+ duration: z.number().optional(),
1047
+ error: z.string().optional(),
1048
+ mixedWith: z.array(z.string()).optional(), // 与其他操作员混音的ID列表
1049
+ });
1050
+ //# sourceMappingURL=websocket.schema.js.map