@tx5dr/contracts 1.7.12 → 2.0.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 (195) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/schema/__tests__/android-operator-audio.schema.test.d.ts +2 -0
  6. package/dist/schema/__tests__/android-operator-audio.schema.test.d.ts.map +1 -0
  7. package/dist/schema/__tests__/android-operator-audio.schema.test.js +27 -0
  8. package/dist/schema/__tests__/android-operator-audio.schema.test.js.map +1 -0
  9. package/dist/schema/__tests__/assisted-queue.schema.test.d.ts +2 -0
  10. package/dist/schema/__tests__/assisted-queue.schema.test.d.ts.map +1 -0
  11. package/dist/schema/__tests__/assisted-queue.schema.test.js +113 -0
  12. package/dist/schema/__tests__/assisted-queue.schema.test.js.map +1 -0
  13. package/dist/schema/__tests__/audio-sidecar.schema.test.d.ts +2 -0
  14. package/dist/schema/__tests__/audio-sidecar.schema.test.d.ts.map +1 -0
  15. package/dist/schema/__tests__/audio-sidecar.schema.test.js +42 -0
  16. package/dist/schema/__tests__/audio-sidecar.schema.test.js.map +1 -0
  17. package/dist/schema/__tests__/audio.schema.test.js +92 -1
  18. package/dist/schema/__tests__/audio.schema.test.js.map +1 -1
  19. package/dist/schema/__tests__/diagnostics.schema.test.d.ts +2 -0
  20. package/dist/schema/__tests__/diagnostics.schema.test.d.ts.map +1 -0
  21. package/dist/schema/__tests__/diagnostics.schema.test.js +31 -0
  22. package/dist/schema/__tests__/diagnostics.schema.test.js.map +1 -0
  23. package/dist/schema/__tests__/logbook-health.schema.test.d.ts +2 -0
  24. package/dist/schema/__tests__/logbook-health.schema.test.d.ts.map +1 -0
  25. package/dist/schema/__tests__/logbook-health.schema.test.js +104 -0
  26. package/dist/schema/__tests__/logbook-health.schema.test.js.map +1 -0
  27. package/dist/schema/__tests__/observability.schema.test.d.ts +2 -0
  28. package/dist/schema/__tests__/observability.schema.test.d.ts.map +1 -0
  29. package/dist/schema/__tests__/observability.schema.test.js +12 -0
  30. package/dist/schema/__tests__/observability.schema.test.js.map +1 -0
  31. package/dist/schema/__tests__/operator.schema.test.js +9 -3
  32. package/dist/schema/__tests__/operator.schema.test.js.map +1 -1
  33. package/dist/schema/__tests__/plugin-market.schema.test.js +43 -1
  34. package/dist/schema/__tests__/plugin-market.schema.test.js.map +1 -1
  35. package/dist/schema/__tests__/radio-power-support.test.js +12 -0
  36. package/dist/schema/__tests__/radio-power-support.test.js.map +1 -1
  37. package/dist/schema/__tests__/radio-profile.schema.test.d.ts +2 -0
  38. package/dist/schema/__tests__/radio-profile.schema.test.d.ts.map +1 -0
  39. package/dist/schema/__tests__/radio-profile.schema.test.js +36 -0
  40. package/dist/schema/__tests__/radio-profile.schema.test.js.map +1 -0
  41. package/dist/schema/__tests__/radio.schema.test.js +41 -1
  42. package/dist/schema/__tests__/radio.schema.test.js.map +1 -1
  43. package/dist/schema/__tests__/realtime-settings.schema.test.js +2 -1
  44. package/dist/schema/__tests__/realtime-settings.schema.test.js.map +1 -1
  45. package/dist/schema/__tests__/remote-access.schema.test.d.ts +2 -0
  46. package/dist/schema/__tests__/remote-access.schema.test.d.ts.map +1 -0
  47. package/dist/schema/__tests__/remote-access.schema.test.js +18 -0
  48. package/dist/schema/__tests__/remote-access.schema.test.js.map +1 -0
  49. package/dist/schema/android-operator-audio.schema.d.ts +174 -0
  50. package/dist/schema/android-operator-audio.schema.d.ts.map +1 -0
  51. package/dist/schema/android-operator-audio.schema.js +38 -0
  52. package/dist/schema/android-operator-audio.schema.js.map +1 -0
  53. package/dist/schema/api-response.schema.d.ts +268 -44
  54. package/dist/schema/api-response.schema.d.ts.map +1 -1
  55. package/dist/schema/audio-sidecar.schema.d.ts +75 -0
  56. package/dist/schema/audio-sidecar.schema.d.ts.map +1 -1
  57. package/dist/schema/audio-sidecar.schema.js +32 -0
  58. package/dist/schema/audio-sidecar.schema.js.map +1 -1
  59. package/dist/schema/audio.schema.d.ts +4749 -3
  60. package/dist/schema/audio.schema.d.ts.map +1 -1
  61. package/dist/schema/audio.schema.js +101 -2
  62. package/dist/schema/audio.schema.js.map +1 -1
  63. package/dist/schema/auth.schema.d.ts +171 -12
  64. package/dist/schema/auth.schema.d.ts.map +1 -1
  65. package/dist/schema/auth.schema.js +65 -1
  66. package/dist/schema/auth.schema.js.map +1 -1
  67. package/dist/schema/cpu-profile.schema.d.ts +2 -2
  68. package/dist/schema/cw-decoder.schema.d.ts +74 -74
  69. package/dist/schema/cw-keyer.schema.d.ts +20 -14
  70. package/dist/schema/cw-keyer.schema.d.ts.map +1 -1
  71. package/dist/schema/cw-keyer.schema.js +3 -0
  72. package/dist/schema/cw-keyer.schema.js.map +1 -1
  73. package/dist/schema/desktop-https.schema.d.ts +6 -6
  74. package/dist/schema/device-ui.schema.d.ts +6 -6
  75. package/dist/schema/diagnostics.schema.d.ts +163 -0
  76. package/dist/schema/diagnostics.schema.d.ts.map +1 -0
  77. package/dist/schema/diagnostics.schema.js +46 -0
  78. package/dist/schema/diagnostics.schema.js.map +1 -0
  79. package/dist/schema/ft8.schema.d.ts +12 -2
  80. package/dist/schema/ft8.schema.d.ts.map +1 -1
  81. package/dist/schema/ft8.schema.js +8 -0
  82. package/dist/schema/ft8.schema.js.map +1 -1
  83. package/dist/schema/logbook.schema.d.ts +1940 -18
  84. package/dist/schema/logbook.schema.d.ts.map +1 -1
  85. package/dist/schema/logbook.schema.js +158 -1
  86. package/dist/schema/logbook.schema.js.map +1 -1
  87. package/dist/schema/mode.schema.d.ts +2 -6
  88. package/dist/schema/mode.schema.d.ts.map +1 -1
  89. package/dist/schema/mode.schema.js +2 -6
  90. package/dist/schema/mode.schema.js.map +1 -1
  91. package/dist/schema/observability.schema.d.ts +66 -0
  92. package/dist/schema/observability.schema.d.ts.map +1 -0
  93. package/dist/schema/observability.schema.js +20 -0
  94. package/dist/schema/observability.schema.js.map +1 -0
  95. package/dist/schema/operator.schema.d.ts +6 -6
  96. package/dist/schema/operator.schema.d.ts.map +1 -1
  97. package/dist/schema/operator.schema.js +6 -1
  98. package/dist/schema/operator.schema.js.map +1 -1
  99. package/dist/schema/plugin.schema.d.ts +258 -78
  100. package/dist/schema/plugin.schema.d.ts.map +1 -1
  101. package/dist/schema/plugin.schema.js +57 -2
  102. package/dist/schema/plugin.schema.js.map +1 -1
  103. package/dist/schema/process-monitor.schema.d.ts +132 -132
  104. package/dist/schema/pskreporter.schema.d.ts +1 -3
  105. package/dist/schema/pskreporter.schema.d.ts.map +1 -1
  106. package/dist/schema/pskreporter.schema.js +1 -3
  107. package/dist/schema/pskreporter.schema.js.map +1 -1
  108. package/dist/schema/qso.schema.d.ts +7 -0
  109. package/dist/schema/qso.schema.d.ts.map +1 -1
  110. package/dist/schema/qso.schema.js +7 -0
  111. package/dist/schema/qso.schema.js.map +1 -1
  112. package/dist/schema/radio-capability.schema.d.ts +31 -40
  113. package/dist/schema/radio-capability.schema.d.ts.map +1 -1
  114. package/dist/schema/radio-capability.schema.js +7 -15
  115. package/dist/schema/radio-capability.schema.js.map +1 -1
  116. package/dist/schema/radio-power-support.d.ts.map +1 -1
  117. package/dist/schema/radio-power-support.js +3 -0
  118. package/dist/schema/radio-power-support.js.map +1 -1
  119. package/dist/schema/radio-power.schema.d.ts +87 -13
  120. package/dist/schema/radio-power.schema.d.ts.map +1 -1
  121. package/dist/schema/radio-power.schema.js +15 -8
  122. package/dist/schema/radio-power.schema.js.map +1 -1
  123. package/dist/schema/radio-profile.schema.d.ts +1240 -97
  124. package/dist/schema/radio-profile.schema.d.ts.map +1 -1
  125. package/dist/schema/radio.schema.d.ts +271 -48
  126. package/dist/schema/radio.schema.d.ts.map +1 -1
  127. package/dist/schema/radio.schema.js +44 -5
  128. package/dist/schema/radio.schema.js.map +1 -1
  129. package/dist/schema/realtime.schema.d.ts +60 -52
  130. package/dist/schema/realtime.schema.d.ts.map +1 -1
  131. package/dist/schema/realtime.schema.js +9 -1
  132. package/dist/schema/realtime.schema.js.map +1 -1
  133. package/dist/schema/slot-info.schema.d.ts +15 -9
  134. package/dist/schema/slot-info.schema.d.ts.map +1 -1
  135. package/dist/schema/slot-info.schema.js +15 -9
  136. package/dist/schema/slot-info.schema.js.map +1 -1
  137. package/dist/schema/spectrum.schema.d.ts +12 -12
  138. package/dist/schema/station-info.schema.d.ts +5 -0
  139. package/dist/schema/station-info.schema.d.ts.map +1 -1
  140. package/dist/schema/station-info.schema.js +5 -0
  141. package/dist/schema/station-info.schema.js.map +1 -1
  142. package/dist/schema/system.schema.d.ts +56 -6
  143. package/dist/schema/system.schema.d.ts.map +1 -1
  144. package/dist/schema/system.schema.js +22 -0
  145. package/dist/schema/system.schema.js.map +1 -1
  146. package/dist/schema/transmission.schema.d.ts +1 -7
  147. package/dist/schema/transmission.schema.d.ts.map +1 -1
  148. package/dist/schema/transmission.schema.js +2 -7
  149. package/dist/schema/transmission.schema.js.map +1 -1
  150. package/dist/schema/voice-keyer.schema.d.ts +16 -16
  151. package/dist/schema/websocket.schema.d.ts +3582 -555
  152. package/dist/schema/websocket.schema.d.ts.map +1 -1
  153. package/dist/schema/websocket.schema.js +160 -3
  154. package/dist/schema/websocket.schema.js.map +1 -1
  155. package/package.json +6 -1
  156. package/src/index.ts +3 -0
  157. package/src/schema/__tests__/android-operator-audio.schema.test.ts +31 -0
  158. package/src/schema/__tests__/assisted-queue.schema.test.ts +121 -0
  159. package/src/schema/__tests__/audio-sidecar.schema.test.ts +48 -0
  160. package/src/schema/__tests__/audio.schema.test.ts +102 -0
  161. package/src/schema/__tests__/diagnostics.schema.test.ts +32 -0
  162. package/src/schema/__tests__/logbook-health.schema.test.ts +117 -0
  163. package/src/schema/__tests__/observability.schema.test.ts +15 -0
  164. package/src/schema/__tests__/operator.schema.test.ts +11 -3
  165. package/src/schema/__tests__/plugin-market.schema.test.ts +49 -0
  166. package/src/schema/__tests__/radio-power-support.test.ts +14 -0
  167. package/src/schema/__tests__/radio-profile.schema.test.ts +44 -0
  168. package/src/schema/__tests__/radio.schema.test.ts +49 -1
  169. package/src/schema/__tests__/realtime-settings.schema.test.ts +2 -1
  170. package/src/schema/__tests__/remote-access.schema.test.ts +22 -0
  171. package/src/schema/android-operator-audio.schema.ts +49 -0
  172. package/src/schema/audio-sidecar.schema.ts +35 -0
  173. package/src/schema/audio.schema.ts +115 -2
  174. package/src/schema/auth.schema.ts +79 -1
  175. package/src/schema/cw-keyer.schema.ts +5 -0
  176. package/src/schema/diagnostics.schema.ts +55 -0
  177. package/src/schema/ft8.schema.ts +8 -0
  178. package/src/schema/logbook.schema.ts +204 -1
  179. package/src/schema/mode.schema.ts +2 -6
  180. package/src/schema/observability.schema.ts +26 -0
  181. package/src/schema/operator.schema.ts +7 -1
  182. package/src/schema/plugin.schema.ts +67 -2
  183. package/src/schema/pskreporter.schema.ts +1 -3
  184. package/src/schema/qso.schema.ts +7 -0
  185. package/src/schema/radio-capability.schema.ts +7 -15
  186. package/src/schema/radio-power-support.ts +3 -0
  187. package/src/schema/radio-power.schema.ts +15 -8
  188. package/src/schema/radio.schema.ts +55 -5
  189. package/src/schema/realtime.schema.ts +9 -1
  190. package/src/schema/slot-info.schema.ts +16 -10
  191. package/src/schema/station-info.schema.ts +5 -0
  192. package/src/schema/system.schema.ts +29 -0
  193. package/src/schema/transmission.schema.ts +2 -7
  194. package/src/schema/websocket.schema.ts +223 -6
  195. package/test/radio-capability-schema.test.ts +44 -0
@@ -40,16 +40,11 @@ export const CallRequestSchema = z.object({
40
40
 
41
41
  // ========== 传输请求类型 ==========
42
42
 
43
- /**
44
- * 传输请求(已在 websocket.schema.ts 中定义)
45
- * TransmitRequestSchema
46
- */
43
+ // TransmitRequestSchema is declared in websocket.schema.ts.
47
44
 
48
45
  // ========== 时隙映射类型 ==========
49
46
 
50
- /**
51
- * 操作员时隙配置
52
- */
47
+ /** Transmit-message templates for the six logical slots of one operator. */
53
48
  export const OperatorSlotsSchema = z.object({
54
49
  TX1: z.string().optional(),
55
50
  TX2: z.string().optional(),
@@ -21,7 +21,11 @@ import type {
21
21
  } from './plugin.schema.js';
22
22
  import { ModeDescriptorSchema } from './mode.schema.js';
23
23
  import { QSORecordSchema, TargetSelectionPriorityModeSchema } from './qso.schema.js';
24
- import { LogBookStatisticsSchema } from './logbook.schema.js';
24
+ import {
25
+ LogBookStatisticsSchema,
26
+ LogbookHealthSchema,
27
+ LogbookOperationErrorCodeSchema,
28
+ } from './logbook.schema.js';
25
29
  import { RadioInfoSchema, HamlibConfigSchema, TunerStatusSchema, TunerCapabilitiesSchema, RadioConnectionStatusSchema, ReconnectProgressSchema, CoreRadioCapabilitiesSchema, CoreCapabilityDiagnosticsSchema } from './radio.schema.js';
26
30
  import { RadioProfileSchema, ProfileChangedEventSchema } from './radio-profile.schema.js';
27
31
  import { UserRole } from './auth.schema.js';
@@ -34,6 +38,7 @@ import { RadioPowerStateEventSchema } from './radio-power.schema.js';
34
38
  import { AudioSidecarStatusPayloadSchema } from './audio-sidecar.schema.js';
35
39
  import { SpectrumCapabilitiesSchema, SpectrumFrameSchema, SpectrumKindSchema, SpectrumSessionControlActionSchema, SpectrumSessionControlIdSchema, SpectrumSessionStateSchema } from './spectrum.schema.js';
36
40
  import type { RealtimeSettingsResponseData } from './realtime.schema.js';
41
+ import type { AndroidOperatorAudioStatus } from './android-operator-audio.schema.js';
37
42
  import { ClockStatusSummarySchema } from './system.schema.js';
38
43
 
39
44
  // WebSocket消息类型枚举
@@ -80,6 +85,11 @@ export enum WSMessageType {
80
85
  START_OPERATOR = 'startOperator',
81
86
  STOP_OPERATOR = 'stopOperator',
82
87
  OPERATOR_REQUEST_CALL = 'operatorRequestCall',
88
+ OPERATOR_QUEUE_ENQUEUE = 'operatorQueueEnqueue',
89
+ OPERATOR_QUEUE_REORDER = 'operatorQueueReorder',
90
+ OPERATOR_QUEUE_RETRY = 'operatorQueueRetry',
91
+ OPERATOR_QUEUE_REMOVE = 'operatorQueueRemove',
92
+ OPERATOR_QUEUE_CLEAR = 'operatorQueueClear',
83
93
 
84
94
  // ===== 客户端操作员过滤 =====
85
95
  SET_CLIENT_ENABLED_OPERATORS = 'setClientEnabledOperators',
@@ -102,6 +112,8 @@ export enum WSMessageType {
102
112
  QSO_RECORD_ADDED = 'qsoRecordAdded',
103
113
  QSO_RECORD_UPDATED = 'qsoRecordUpdated',
104
114
  LOGBOOK_UPDATED = 'logbookUpdated',
115
+ LOGBOOK_HEALTH_CHANGED = 'logbookHealthChanged',
116
+ LOGBOOK_WRITE_FAILED = 'logbookWriteFailed',
105
117
  // 仅通知的日志本变更事件(专用于日志本WS)
106
118
  LOGBOOK_CHANGE_NOTICE = 'logbookChangeNotice',
107
119
 
@@ -143,6 +155,8 @@ export enum WSMessageType {
143
155
  WRITE_RADIO_CAPABILITY = 'writeRadioCapability',
144
156
  /** 客户端请求刷新所有能力值(client → server) */
145
157
  REFRESH_RADIO_CAPABILITIES = 'refreshRadioCapabilities',
158
+ /** 客户端设置 Split TX 频率(client → server) */
159
+ SET_SPLIT_FREQUENCY = 'setSplitFrequency',
146
160
 
147
161
  // ===== 电台重连控制 =====
148
162
  RADIO_STOP_RECONNECT = 'radioStopReconnect',
@@ -168,6 +182,7 @@ export enum WSMessageType {
168
182
  AUTH_PUBLIC_VIEWER = 'authPublicViewer',
169
183
  AUTH_RESULT = 'authResult',
170
184
  AUTH_EXPIRED = 'authExpired',
185
+ ACCESS_DENIED = 'accessDenied',
171
186
 
172
187
  // ===== 语音模式 =====
173
188
  VOICE_PTT_REQUEST = 'voicePttRequest',
@@ -178,6 +193,7 @@ export enum WSMessageType {
178
193
  VOICE_KEYER_PLAY = 'voiceKeyerPlay',
179
194
  VOICE_KEYER_STOP = 'voiceKeyerStop',
180
195
  VOICE_KEYER_STATUS_CHANGED = 'voiceKeyerStatusChanged',
196
+ ANDROID_OPERATOR_AUDIO_STATUS_CHANGED = 'androidOperatorAudioStatusChanged',
181
197
 
182
198
  // ===== CW 模式 =====
183
199
  /** CW 键控器状态变化(server → client) */
@@ -325,7 +341,14 @@ export const DecodeErrorInfoSchema = z.object({
325
341
  }),
326
342
  });
327
343
 
328
- // 频率状态数据结构
344
+ /**
345
+ * Current dial-frequency projection broadcast to plugins and clients.
346
+ *
347
+ * `frequency` is RF dial frequency in hertz; `mode` is the TX-5DR engine mode;
348
+ * `band` and `description` are display labels; `radioMode` is the underlying
349
+ * modulation when known. `source` distinguishes application changes from
350
+ * updates reported by the radio.
351
+ */
329
352
  export const FrequencyStateSchema = z.object({
330
353
  frequency: z.number(),
331
354
  mode: z.string(),
@@ -346,6 +369,9 @@ export type SpectrumControlInvocation = z.infer<typeof SpectrumControlInvocation
346
369
  export const PTTStatusSchema = z.object({
347
370
  isTransmitting: z.boolean(),
348
371
  operatorIds: z.array(z.string()),
372
+ phase: z.enum(['idle', 'starting', 'on_air', 'draining', 'stopping', 'unknown']).optional(),
373
+ frameId: z.string().optional(),
374
+ source: z.enum(['digital', 'voice', 'voice-keyer', 'cw', 'tune-tone', 'manual', 'test']).optional(),
349
375
  });
350
376
 
351
377
  export const TuneToneStartPayloadSchema = z.object({
@@ -383,6 +409,40 @@ export const StrategyRuntimeContextSchema = z.object({
383
409
  });
384
410
  export type StrategyRuntimeContext = z.infer<typeof StrategyRuntimeContextSchema>;
385
411
 
412
+ export const AssistedQueueDisplayStateSchema = z.enum([
413
+ 'TX1', 'TX2', 'TX3', 'TX4', 'TX5', 'engaged', 'closing', 'paused', 'no-response', 'later', 'review',
414
+ ]);
415
+ export const AssistedQueuePauseReasonSchema = z.enum(['target-busy', 'stale']);
416
+ export const AssistedQueueToneSchema = z.enum(['neutral', 'active', 'success', 'warning', 'danger']);
417
+ export const AssistedQueueIconSchema = z.enum([
418
+ 'circle', 'radio', 'check-circle', 'loader-circle', 'clock', 'pause', 'triangle-alert',
419
+ ]);
420
+ export const AssistedQueueRowSchema = z.object({
421
+ entryId: z.string(),
422
+ callsign: z.string(),
423
+ order: z.number().int().nonnegative(),
424
+ draggable: z.boolean(),
425
+ displayState: AssistedQueueDisplayStateSchema,
426
+ tone: AssistedQueueToneSchema,
427
+ icon: AssistedQueueIconSchema,
428
+ pauseReason: AssistedQueuePauseReasonSchema.optional(),
429
+ noResponseCycles: z.number().int().nonnegative().optional(),
430
+ targetGrid: z.string().optional(),
431
+ lastSnr: z.number().optional(),
432
+ lastHeardCyclesAgo: z.number().int().nonnegative().optional(),
433
+ });
434
+ export const AssistedQueueSnapshotSchema = z.object({
435
+ version: z.number().int().nonnegative(),
436
+ activeEntryId: z.string().optional(),
437
+ rows: z.array(AssistedQueueRowSchema),
438
+ });
439
+ export type AssistedQueueDisplayState = z.infer<typeof AssistedQueueDisplayStateSchema>;
440
+ export type AssistedQueuePauseReason = z.infer<typeof AssistedQueuePauseReasonSchema>;
441
+ export type AssistedQueueTone = z.infer<typeof AssistedQueueToneSchema>;
442
+ export type AssistedQueueIcon = z.infer<typeof AssistedQueueIconSchema>;
443
+ export type AssistedQueueRow = z.infer<typeof AssistedQueueRowSchema>;
444
+ export type AssistedQueueSnapshot = z.infer<typeof AssistedQueueSnapshotSchema>;
445
+
386
446
  export const StrategyRuntimeSnapshotSchema = z.object({
387
447
  currentState: z.string(),
388
448
  slots: z.object({
@@ -395,6 +455,8 @@ export const StrategyRuntimeSnapshotSchema = z.object({
395
455
  }).optional(),
396
456
  context: StrategyRuntimeContextSchema.optional(),
397
457
  availableSlots: z.array(z.string()).optional(),
458
+ qsoLifecycleEpoch: z.number().int().nonnegative().optional(),
459
+ queue: AssistedQueueSnapshotSchema.optional(),
398
460
  });
399
461
  export type StrategyRuntimeSnapshot = z.infer<typeof StrategyRuntimeSnapshotSchema>;
400
462
 
@@ -642,8 +704,9 @@ export const WSGetStatusMessageSchema = WSBaseMessageSchema.extend({
642
704
  export const OperatorStatusSchema = z.object({
643
705
  id: z.string(),
644
706
  isActive: z.boolean(),
645
- isTransmitting: z.boolean(), // 是否正在发射(发射开关状态)
707
+ isTransmitting: z.boolean(), // 是否已授予策略自动发射许可(发射开关状态)
646
708
  isInActivePTT: z.boolean().optional(), // 该操作员的音频是否正在被实际播放
709
+ hasTransmitIntent: z.boolean().optional(), // 当前策略是否有可在合法周期发射的有效内容
647
710
  currentSlot: z.string().optional(),
648
711
  context: z.object({
649
712
  myCall: z.string(),
@@ -796,6 +859,52 @@ export const WSOperatorRequestCallMessageSchema = WSBaseMessageSchema.extend({
796
859
  }),
797
860
  });
798
861
 
862
+ export const WSOperatorQueueEnqueueMessageSchema = WSBaseMessageSchema.extend({
863
+ type: z.literal(WSMessageType.OPERATOR_QUEUE_ENQUEUE),
864
+ data: z.object({
865
+ operatorId: z.string(),
866
+ callsign: z.string(),
867
+ selectedFrame: WSSelectedFrameSchema.optional(),
868
+ startIfIdle: z.boolean().optional(),
869
+ }),
870
+ });
871
+
872
+ export const WSOperatorQueueReorderMessageSchema = WSBaseMessageSchema.extend({
873
+ type: z.literal(WSMessageType.OPERATOR_QUEUE_REORDER),
874
+ data: z.object({
875
+ operatorId: z.string(),
876
+ entryId: z.string(),
877
+ beforeEntryId: z.string().nullable(),
878
+ expectedVersion: z.number().int().nonnegative(),
879
+ }),
880
+ });
881
+
882
+ export const WSOperatorQueueRetryMessageSchema = WSBaseMessageSchema.extend({
883
+ type: z.literal(WSMessageType.OPERATOR_QUEUE_RETRY),
884
+ data: z.object({
885
+ operatorId: z.string(),
886
+ entryId: z.string(),
887
+ expectedVersion: z.number().int().nonnegative(),
888
+ }),
889
+ });
890
+
891
+ export const WSOperatorQueueRemoveMessageSchema = WSBaseMessageSchema.extend({
892
+ type: z.literal(WSMessageType.OPERATOR_QUEUE_REMOVE),
893
+ data: z.object({
894
+ operatorId: z.string(),
895
+ entryId: z.string(),
896
+ expectedVersion: z.number().int().nonnegative(),
897
+ }),
898
+ });
899
+
900
+ export const WSOperatorQueueClearMessageSchema = WSBaseMessageSchema.extend({
901
+ type: z.literal(WSMessageType.OPERATOR_QUEUE_CLEAR),
902
+ data: z.object({
903
+ operatorId: z.string(),
904
+ expectedVersion: z.number().int().nonnegative(),
905
+ }),
906
+ });
907
+
799
908
  /**
800
909
  * 设置客户端启用的操作员列表消息
801
910
  */
@@ -875,6 +984,11 @@ export const WSTransmissionLogMessageSchema = WSBaseMessageSchema.extend({
875
984
  type: z.literal(WSMessageType.TRANSMISSION_LOG),
876
985
  data: z.object({
877
986
  operatorId: z.string(),
987
+ frameId: z.string(),
988
+ revision: z.number().int().nonnegative(),
989
+ playbackGeneration: z.number().int().positive(),
990
+ phase: z.literal('on_air'),
991
+ physicalConfirmed: z.literal(true),
878
992
  time: z.string(),
879
993
  message: z.string(),
880
994
  frequency: z.number(),
@@ -951,6 +1065,34 @@ export const WSLogbookUpdatedMessageSchema = WSBaseMessageSchema.extend({
951
1065
 
952
1066
  export type WSLogbookUpdatedMessage = z.infer<typeof WSLogbookUpdatedMessageSchema>;
953
1067
 
1068
+ export const WSLogbookHealthChangedMessageSchema = WSBaseMessageSchema.extend({
1069
+ type: z.literal(WSMessageType.LOGBOOK_HEALTH_CHANGED),
1070
+ data: z.object({
1071
+ logBookId: z.string(),
1072
+ health: LogbookHealthSchema,
1073
+ }),
1074
+ });
1075
+
1076
+ export type WSLogbookHealthChangedMessage = z.infer<typeof WSLogbookHealthChangedMessageSchema>;
1077
+
1078
+ export const WSLogbookWriteFailedMessageSchema = WSBaseMessageSchema.extend({
1079
+ type: z.literal(WSMessageType.LOGBOOK_WRITE_FAILED),
1080
+ data: z.object({
1081
+ logBookId: z.string(),
1082
+ operatorId: z.string().optional(),
1083
+ attemptId: z.string().min(8).max(128).regex(/^[A-Za-z0-9._:-]+$/).optional(),
1084
+ unsavedCount: z.number().int().positive().optional(),
1085
+ error: z.object({
1086
+ code: LogbookOperationErrorCodeSchema,
1087
+ message: z.string(),
1088
+ systemCode: z.string().optional(),
1089
+ occurredAt: z.number(),
1090
+ }),
1091
+ }).strict(),
1092
+ });
1093
+
1094
+ export type WSLogbookWriteFailedMessage = z.infer<typeof WSLogbookWriteFailedMessageSchema>;
1095
+
954
1096
  /**
955
1097
  * 电台状态变化消息
956
1098
  */
@@ -973,6 +1115,8 @@ export const WSRadioStatusChangedMessageSchema = WSBaseMessageSchema.extend({
973
1115
  meterCapabilities: MeterCapabilitiesSchema.optional(), // 电台数值表能力(connected=true 时应始终携带)
974
1116
  /** @deprecated Tuner capability is now in radioCapabilityList event. Kept for backward compat. */
975
1117
  tunerCapabilities: TunerCapabilitiesSchema.optional(),
1118
+ /** 虚拟频差实际生效状态(多op发射时为 false) */
1119
+ fakeFrequencyEffective: z.boolean().optional(),
976
1120
  }),
977
1121
  });
978
1122
 
@@ -1199,6 +1343,8 @@ export const WSTextMessageSchema = WSBaseMessageSchema.extend({
1199
1343
  text: z.string(),
1200
1344
  color: z.enum(['success', 'warning', 'danger', 'default']).optional(),
1201
1345
  timeout: z.number().nullable().optional(), // null 表示需要手动关闭,number 表示自动关闭的毫秒数
1346
+ /** 服务端创建消息的时间戳,用于前端识别后台积压消息 */
1347
+ createdAtMs: z.number().optional(),
1202
1348
  /** i18n key,前端有此字段时优先翻译显示 */
1203
1349
  key: z.string().optional(),
1204
1350
  /** i18n 插值参数 */
@@ -1255,6 +1401,19 @@ export const WSWriteRadioCapabilityMessageSchema = WSBaseMessageSchema.extend({
1255
1401
 
1256
1402
  export type WSWriteRadioCapabilityMessage = z.infer<typeof WSWriteRadioCapabilityMessageSchema>;
1257
1403
 
1404
+ export const SetSplitFrequencyPayloadSchema = z.object({
1405
+ txFrequency: z.number().finite().int().min(1_000_000).max(1_000_000_000),
1406
+ });
1407
+
1408
+ export type SetSplitFrequencyPayload = z.infer<typeof SetSplitFrequencyPayloadSchema>;
1409
+
1410
+ export const WSSetSplitFrequencyMessageSchema = WSBaseMessageSchema.extend({
1411
+ type: z.literal(WSMessageType.SET_SPLIT_FREQUENCY),
1412
+ data: SetSplitFrequencyPayloadSchema,
1413
+ });
1414
+
1415
+ export type WSSetSplitFrequencyMessage = z.infer<typeof WSSetSplitFrequencyMessageSchema>;
1416
+
1258
1417
  // ===== 认证相关消息 =====
1259
1418
 
1260
1419
  /**
@@ -1291,6 +1450,20 @@ export const WSAuthPublicViewerMessageSchema = WSBaseMessageSchema.extend({
1291
1450
 
1292
1451
  export type WSAuthPublicViewerMessage = z.infer<typeof WSAuthPublicViewerMessageSchema>;
1293
1452
 
1453
+ export const WSAccessDeniedDataSchema = z.object({
1454
+ reason: z.enum([
1455
+ 'capacity_reached',
1456
+ 'origin_not_allowed',
1457
+ 'authentication_timeout',
1458
+ 'handshake_timeout',
1459
+ 'ip_limit_reached',
1460
+ ]),
1461
+ current: z.number().int().nonnegative().optional(),
1462
+ limit: z.number().int().positive().optional(),
1463
+ retryAfterMs: z.number().int().nonnegative().optional(),
1464
+ });
1465
+ export type WSAccessDeniedData = z.infer<typeof WSAccessDeniedDataSchema>;
1466
+
1294
1467
  /**
1295
1468
  * 认证结果(服务端到客户端)
1296
1469
  */
@@ -1413,6 +1586,11 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1413
1586
  WSStartOperatorMessageSchema,
1414
1587
  WSStopOperatorMessageSchema,
1415
1588
  WSOperatorRequestCallMessageSchema,
1589
+ WSOperatorQueueEnqueueMessageSchema,
1590
+ WSOperatorQueueReorderMessageSchema,
1591
+ WSOperatorQueueRetryMessageSchema,
1592
+ WSOperatorQueueRemoveMessageSchema,
1593
+ WSOperatorQueueClearMessageSchema,
1416
1594
 
1417
1595
  // 音量控制消息
1418
1596
  WSSetVolumeGainMessageSchema,
@@ -1422,6 +1600,8 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1422
1600
  WSQSORecordAddedMessageSchema,
1423
1601
  WSQSORecordUpdatedMessageSchema,
1424
1602
  WSLogbookUpdatedMessageSchema,
1603
+ WSLogbookHealthChangedMessageSchema,
1604
+ WSLogbookWriteFailedMessageSchema,
1425
1605
 
1426
1606
  // 客户端启用操作员列表消息
1427
1607
  WSSetClientEnabledOperatorsMessageSchema,
@@ -1446,6 +1626,7 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1446
1626
 
1447
1627
  // 频率管理消息
1448
1628
  WSFrequencyChangedMessageSchema,
1629
+ WSSetSplitFrequencyMessageSchema,
1449
1630
 
1450
1631
  // PTT状态管理消息
1451
1632
  WSPTTStatusChangedMessageSchema,
@@ -1518,6 +1699,10 @@ export const TransmitRequestSchema = z.object({
1518
1699
  transmission: z.string(),
1519
1700
  /** 是否覆盖同一操作员在同一时隙的现有 TX 帧(自动重决策时为 true) */
1520
1701
  replaceExisting: z.boolean().optional(),
1702
+ source: z.enum(['standard-qso', 'plugin', 'late-decode', 'operator-edit', 'manual']).optional(),
1703
+ reason: z.string().optional(),
1704
+ /** Host command epoch used to tombstone stale encode/mix callbacks. */
1705
+ decisionEpoch: z.number().int().nonnegative().optional(),
1521
1706
  });
1522
1707
 
1523
1708
  export type TransmitRequest = z.infer<typeof TransmitRequestSchema>;
@@ -1530,6 +1715,9 @@ export type TransmitRequest = z.infer<typeof TransmitRequestSchema>;
1530
1715
  export const TransmissionCompleteInfoSchema = z.object({
1531
1716
  operatorId: z.string(),
1532
1717
  success: z.boolean(),
1718
+ frameId: z.string().optional(),
1719
+ physicalConfirmed: z.boolean().optional(),
1720
+ terminalReason: z.string().optional(),
1533
1721
  duration: z.number().optional(),
1534
1722
  error: z.string().optional(),
1535
1723
  mixedWith: z.array(z.string()).optional(), // 与其他操作员混音的ID列表
@@ -1562,6 +1750,11 @@ export interface DigitalRadioEngineEvents {
1562
1750
  transmissionComplete: (info: TransmissionCompleteInfo) => void;
1563
1751
  transmissionLog: (data: {
1564
1752
  operatorId: string;
1753
+ frameId: string;
1754
+ revision: number;
1755
+ playbackGeneration: number;
1756
+ phase: 'on_air';
1757
+ physicalConfirmed: true;
1565
1758
  time: string;
1566
1759
  message: string;
1567
1760
  frequency: number;
@@ -1584,7 +1777,7 @@ export interface DigitalRadioEngineEvents {
1584
1777
  // 连接事件
1585
1778
  connected: () => void;
1586
1779
  reconnecting: (data: { attempt: number; delayMs: number }) => void;
1587
- disconnected: () => void;
1780
+ disconnected: (data?: WSAccessDeniedData) => void;
1588
1781
  handshakeComplete: (data: z.infer<typeof WSServerHandshakeCompleteMessageSchema>['data']) => void;
1589
1782
  connectionReplaced: (data: { reason: string }) => void;
1590
1783
  error: (error: Error) => void;
@@ -1609,6 +1802,8 @@ export interface DigitalRadioEngineEvents {
1609
1802
  qsoRecordAdded: (data: { operatorId: string; logBookId: string; qsoRecord: z.infer<typeof QSORecordSchema> }) => void;
1610
1803
  qsoRecordUpdated: (data: { operatorId: string; logBookId: string; qsoRecord: z.infer<typeof QSORecordSchema> }) => void;
1611
1804
  logbookUpdated: (data: { logBookId: string; statistics: z.infer<typeof LogBookStatisticsSchema>; operatorId?: string }) => void;
1805
+ logbookHealthChanged: (data: { logBookId: string; health: z.infer<typeof LogbookHealthSchema> }) => void;
1806
+ logbookWriteFailed: (data: z.infer<typeof WSLogbookWriteFailedMessageSchema>['data']) => void;
1612
1807
  textMessage: (data: z.infer<typeof WSTextMessageSchema>['data']) => void;
1613
1808
 
1614
1809
  /** @deprecated Tuner state is now delivered via radioCapabilityChanged event (id='tuner_switch') */
@@ -1636,11 +1831,13 @@ export interface DigitalRadioEngineEvents {
1636
1831
  authRequired: (data: { allowPublicViewing: boolean }) => void;
1637
1832
  authResult: (data: { success: boolean; role?: UserRole; label?: string; operatorIds?: string[]; error?: string }) => void;
1638
1833
  authExpired: (data: { reason?: string }) => void;
1834
+ accessDenied: (data: WSAccessDeniedData) => void;
1639
1835
 
1640
1836
  // 语音模式事件
1641
1837
  voicePttLockChanged: (data: VoicePTTLock) => void;
1642
1838
  voiceRadioModeChanged: (data: { radioMode: string }) => void;
1643
1839
  voiceKeyerStatusChanged: (data: VoiceKeyerStatus) => void;
1840
+ androidOperatorAudioStatusChanged: (data: AndroidOperatorAudioStatus) => void;
1644
1841
 
1645
1842
  // CW 模式事件
1646
1843
  cwKeyerStatusChanged: (data: CWKeyerStatus) => void;
@@ -1695,8 +1892,28 @@ export interface DigitalRadioEngineEvents {
1695
1892
  operatorSlotChanged: (data: { operatorId: string; slot: string }) => void;
1696
1893
  operatorSlotContentChanged: (data: { operatorId: string; slot: string; content: string }) => void;
1697
1894
  operatorFrequencyChanged: (data: { operatorId: string; frequency: number }) => void;
1698
- operatorTransmitCyclesChanged: (data: { operatorId: string; transmitCycles: number[] }) => void;
1699
- recordQSO: (data: { operatorId: string; qsoRecord: z.infer<typeof QSORecordSchema> }) => void;
1895
+ operatorTransmitCyclesChanged: (data: {
1896
+ operatorId: string;
1897
+ transmitCycles: number[];
1898
+ commandEpoch?: number;
1899
+ source?: 'manual' | 'plugin' | 'late-decode' | 'slot-auto';
1900
+ reason?: string;
1901
+ }) => void;
1902
+ qsoLifecycleChanged: (data: {
1903
+ operatorId: string;
1904
+ lifecycleEpoch: number;
1905
+ runtimeGeneration?: number;
1906
+ }) => void;
1907
+ recordQSO: (data: {
1908
+ operatorId: string;
1909
+ qsoLifecycleId?: string;
1910
+ qsoLifecycleEpoch?: number;
1911
+ qsoRuntimeGeneration?: number;
1912
+ qsoRecord: z.infer<typeof QSORecordSchema>;
1913
+ retryAttemptId?: string;
1914
+ resolve?: (record: z.infer<typeof QSORecordSchema>) => void;
1915
+ reject?: (error: unknown) => void;
1916
+ }) => void;
1700
1917
  checkHasWorkedCallsign: (data: { operatorId: string; callsign: string; requestId: string }) => void;
1701
1918
  hasWorkedCallsignResponse: (data: { requestId: string; hasWorked: boolean }) => void;
1702
1919
  }
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ CAPABILITY_IDS,
4
+ CapabilityDescriptorSchema,
5
+ CapabilityStateSchema,
6
+ type CapabilityId,
7
+ } from '../src/schema/radio-capability.schema';
8
+
9
+ describe('CAPABILITY_IDS', () => {
10
+ it('includes radio_mode for dynamic voice radio mode discovery', () => {
11
+ const id: CapabilityId = 'radio_mode';
12
+
13
+ expect(CAPABILITY_IDS).toContain(id);
14
+ });
15
+
16
+ it('accepts radio_mode as an enum capability descriptor and state', () => {
17
+ expect(CapabilityDescriptorSchema.parse({
18
+ id: 'radio_mode',
19
+ category: 'operation',
20
+ valueType: 'enum',
21
+ options: [{ value: 'USB' }, { value: 'WFM' }],
22
+ readable: true,
23
+ writable: false,
24
+ updateMode: 'polling',
25
+ labelI18nKey: 'radio:capability.radio_mode.label',
26
+ hasSurfaceControl: false,
27
+ })).toMatchObject({
28
+ id: 'radio_mode',
29
+ valueType: 'enum',
30
+ options: [{ value: 'USB' }, { value: 'WFM' }],
31
+ });
32
+
33
+ expect(CapabilityStateSchema.parse({
34
+ id: 'radio_mode',
35
+ supported: true,
36
+ value: 'WFM',
37
+ updatedAt: 1,
38
+ })).toMatchObject({
39
+ id: 'radio_mode',
40
+ supported: true,
41
+ value: 'WFM',
42
+ });
43
+ });
44
+ });