@tx5dr/contracts 2.0.0 → 2.5.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 (138) hide show
  1. package/dist/index.d.ts +4 -1
  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__/assisted-queue.schema.test.js +1 -0
  6. package/dist/schema/__tests__/assisted-queue.schema.test.js.map +1 -1
  7. package/dist/schema/__tests__/meter.schema.test.d.ts +2 -0
  8. package/dist/schema/__tests__/meter.schema.test.d.ts.map +1 -0
  9. package/dist/schema/__tests__/meter.schema.test.js +27 -0
  10. package/dist/schema/__tests__/meter.schema.test.js.map +1 -0
  11. package/dist/schema/__tests__/operator-context-report.schema.test.d.ts +2 -0
  12. package/dist/schema/__tests__/operator-context-report.schema.test.d.ts.map +1 -0
  13. package/dist/schema/__tests__/operator-context-report.schema.test.js +70 -0
  14. package/dist/schema/__tests__/operator-context-report.schema.test.js.map +1 -0
  15. package/dist/schema/__tests__/plugin-market.schema.test.js +107 -3
  16. package/dist/schema/__tests__/plugin-market.schema.test.js.map +1 -1
  17. package/dist/schema/__tests__/qso.schema.test.d.ts +2 -0
  18. package/dist/schema/__tests__/qso.schema.test.d.ts.map +1 -0
  19. package/dist/schema/__tests__/qso.schema.test.js +70 -0
  20. package/dist/schema/__tests__/qso.schema.test.js.map +1 -0
  21. package/dist/schema/__tests__/radio-power-support.test.js +4 -1
  22. package/dist/schema/__tests__/radio-power-support.test.js.map +1 -1
  23. package/dist/schema/__tests__/radio.schema.test.js +52 -1
  24. package/dist/schema/__tests__/radio.schema.test.js.map +1 -1
  25. package/dist/schema/__tests__/remote-access.schema.test.js +1 -1
  26. package/dist/schema/__tests__/remote-access.schema.test.js.map +1 -1
  27. package/dist/schema/__tests__/spectrum.schema.test.d.ts +2 -0
  28. package/dist/schema/__tests__/spectrum.schema.test.d.ts.map +1 -0
  29. package/dist/schema/__tests__/spectrum.schema.test.js +107 -0
  30. package/dist/schema/__tests__/spectrum.schema.test.js.map +1 -0
  31. package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts +2 -0
  32. package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts.map +1 -0
  33. package/dist/schema/__tests__/strategy-transmission.schema.test.js +95 -0
  34. package/dist/schema/__tests__/strategy-transmission.schema.test.js.map +1 -0
  35. package/dist/schema/api-response.schema.d.ts +235 -0
  36. package/dist/schema/api-response.schema.d.ts.map +1 -1
  37. package/dist/schema/api-response.schema.js +9 -0
  38. package/dist/schema/api-response.schema.js.map +1 -1
  39. package/dist/schema/auth.schema.d.ts +2 -2
  40. package/dist/schema/cpu-profile.schema.d.ts +2 -2
  41. package/dist/schema/device-ui.schema.d.ts +10 -10
  42. package/dist/schema/image-radio.schema.d.ts +2597 -0
  43. package/dist/schema/image-radio.schema.d.ts.map +1 -0
  44. package/dist/schema/image-radio.schema.js +283 -0
  45. package/dist/schema/image-radio.schema.js.map +1 -0
  46. package/dist/schema/logbook.schema.d.ts +203 -15
  47. package/dist/schema/logbook.schema.d.ts.map +1 -1
  48. package/dist/schema/logbook.schema.js +1 -0
  49. package/dist/schema/logbook.schema.js.map +1 -1
  50. package/dist/schema/mode.schema.d.ts +2 -0
  51. package/dist/schema/mode.schema.d.ts.map +1 -1
  52. package/dist/schema/mode.schema.js +16 -0
  53. package/dist/schema/mode.schema.js.map +1 -1
  54. package/dist/schema/openwebrx.schema.d.ts +2 -2
  55. package/dist/schema/operator.schema.d.ts +2676 -0
  56. package/dist/schema/operator.schema.d.ts.map +1 -1
  57. package/dist/schema/operator.schema.js +4 -19
  58. package/dist/schema/operator.schema.js.map +1 -1
  59. package/dist/schema/plugin.schema.d.ts +1780 -475
  60. package/dist/schema/plugin.schema.d.ts.map +1 -1
  61. package/dist/schema/plugin.schema.js +108 -12
  62. package/dist/schema/plugin.schema.js.map +1 -1
  63. package/dist/schema/qso.schema.d.ts +239 -1
  64. package/dist/schema/qso.schema.d.ts.map +1 -1
  65. package/dist/schema/qso.schema.js +61 -0
  66. package/dist/schema/qso.schema.js.map +1 -1
  67. package/dist/schema/radio-capability.schema.d.ts +1 -1
  68. package/dist/schema/radio-capability.schema.d.ts.map +1 -1
  69. package/dist/schema/radio-capability.schema.js +1 -0
  70. package/dist/schema/radio-capability.schema.js.map +1 -1
  71. package/dist/schema/radio-power.schema.d.ts +15 -0
  72. package/dist/schema/radio-power.schema.d.ts.map +1 -1
  73. package/dist/schema/radio-profile.schema.d.ts +251 -0
  74. package/dist/schema/radio-profile.schema.d.ts.map +1 -1
  75. package/dist/schema/radio-profile.schema.js +7 -0
  76. package/dist/schema/radio-profile.schema.js.map +1 -1
  77. package/dist/schema/radio.schema.d.ts +174 -3
  78. package/dist/schema/radio.schema.d.ts.map +1 -1
  79. package/dist/schema/radio.schema.js +31 -2
  80. package/dist/schema/radio.schema.js.map +1 -1
  81. package/dist/schema/rigctld.schema.d.ts +6 -6
  82. package/dist/schema/slot-info.schema.d.ts +13 -0
  83. package/dist/schema/slot-info.schema.d.ts.map +1 -1
  84. package/dist/schema/slot-info.schema.js +2 -1
  85. package/dist/schema/slot-info.schema.js.map +1 -1
  86. package/dist/schema/spectrum-config.schema.d.ts +638 -0
  87. package/dist/schema/spectrum-config.schema.d.ts.map +1 -0
  88. package/dist/schema/spectrum-config.schema.js +129 -0
  89. package/dist/schema/spectrum-config.schema.js.map +1 -0
  90. package/dist/schema/spectrum.schema.d.ts +1313 -20
  91. package/dist/schema/spectrum.schema.d.ts.map +1 -1
  92. package/dist/schema/spectrum.schema.js +72 -0
  93. package/dist/schema/spectrum.schema.js.map +1 -1
  94. package/dist/schema/transmission.schema.d.ts +7 -0
  95. package/dist/schema/transmission.schema.d.ts.map +1 -1
  96. package/dist/schema/tx-audio-input.schema.d.ts +8 -0
  97. package/dist/schema/tx-audio-input.schema.d.ts.map +1 -0
  98. package/dist/schema/tx-audio-input.schema.js +19 -0
  99. package/dist/schema/tx-audio-input.schema.js.map +1 -0
  100. package/dist/schema/voice.schema.d.ts +1 -1
  101. package/dist/schema/voice.schema.d.ts.map +1 -1
  102. package/dist/schema/voice.schema.js +1 -1
  103. package/dist/schema/voice.schema.js.map +1 -1
  104. package/dist/schema/websocket.schema.d.ts +27480 -2525
  105. package/dist/schema/websocket.schema.d.ts.map +1 -1
  106. package/dist/schema/websocket.schema.js +298 -11
  107. package/dist/schema/websocket.schema.js.map +1 -1
  108. package/package.json +1 -1
  109. package/src/index.ts +4 -0
  110. package/src/schema/__tests__/assisted-queue.schema.test.ts +1 -0
  111. package/src/schema/__tests__/meter.schema.test.ts +28 -0
  112. package/src/schema/__tests__/operator-context-report.schema.test.ts +77 -0
  113. package/src/schema/__tests__/plugin-market.schema.test.ts +116 -2
  114. package/src/schema/__tests__/qso.schema.test.ts +91 -0
  115. package/src/schema/__tests__/radio-power-support.test.ts +4 -1
  116. package/src/schema/__tests__/radio.schema.test.ts +60 -1
  117. package/src/schema/__tests__/remote-access.schema.test.ts +1 -1
  118. package/src/schema/__tests__/spectrum.schema.test.ts +119 -0
  119. package/src/schema/__tests__/strategy-transmission.schema.test.ts +106 -0
  120. package/src/schema/api-response.schema.ts +11 -0
  121. package/src/schema/image-radio.schema.ts +342 -0
  122. package/src/schema/logbook.schema.ts +1 -0
  123. package/src/schema/mode.schema.ts +16 -0
  124. package/src/schema/operator.schema.ts +4 -19
  125. package/src/schema/plugin.schema.ts +119 -13
  126. package/src/schema/qso.schema.ts +71 -0
  127. package/src/schema/radio-capability.schema.ts +1 -0
  128. package/src/schema/radio-profile.schema.ts +7 -0
  129. package/src/schema/radio.schema.ts +35 -2
  130. package/src/schema/slot-info.schema.ts +2 -1
  131. package/src/schema/spectrum-config.schema.ts +156 -0
  132. package/src/schema/spectrum.schema.ts +89 -0
  133. package/src/schema/tx-audio-input.schema.ts +22 -0
  134. package/src/schema/voice.schema.ts +1 -1
  135. package/src/schema/websocket.schema.ts +383 -12
  136. package/test/image-radio-schema.test.ts +107 -0
  137. package/test/plugin-slot-schema.test.ts +31 -0
  138. package/CLAUDE.md +0 -41
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { SlotPackSchema, SlotInfoSchema, SlotPackFrequencyContextSchema } from './slot-info.schema.js';
3
3
  import { PluginDataPayloadSchema, PluginLogEntrySchema, PluginRuntimeLogEntrySchema, PluginRuntimeLogHistoryPayloadSchema, PluginSystemSnapshotSchema, PluginStatusSchema, PluginUserActionPayloadSchema, } from './plugin.schema.js';
4
4
  import { ModeDescriptorSchema } from './mode.schema.js';
5
- import { QSORecordSchema } from './qso.schema.js';
5
+ import { QSORecordSchema, } from './qso.schema.js';
6
6
  import { LogBookStatisticsSchema, LogbookHealthSchema, LogbookOperationErrorCodeSchema, } from './logbook.schema.js';
7
7
  import { RadioInfoSchema, HamlibConfigSchema, TunerCapabilitiesSchema, RadioConnectionStatusSchema, ReconnectProgressSchema, CoreRadioCapabilitiesSchema, CoreCapabilityDiagnosticsSchema } from './radio.schema.js';
8
8
  import { UserRole } from './auth.schema.js';
@@ -10,8 +10,9 @@ import { CWDecoderEventSchema, CWDecoderStatusSchema } from './cw-decoder.schema
10
10
  import { CapabilityListSchema, CapabilityStateSchema, WriteCapabilityPayloadSchema } from './radio-capability.schema.js';
11
11
  import { RadioPowerStateEventSchema } from './radio-power.schema.js';
12
12
  import { AudioSidecarStatusPayloadSchema } from './audio-sidecar.schema.js';
13
- import { SpectrumCapabilitiesSchema, SpectrumFrameSchema, SpectrumKindSchema, SpectrumSessionControlActionSchema, SpectrumSessionControlIdSchema, SpectrumSessionStateSchema } from './spectrum.schema.js';
13
+ import { SpectrumCapabilitiesSchema, SpectrumFrameSchema, SpectrumKindSchema, SpectrumSessionControlActionSchema, SpectrumSessionControlIdSchema, SpectrumSessionStateSchema, SpectrumViewportSchema } from './spectrum.schema.js';
14
14
  import { ClockStatusSummarySchema } from './system.schema.js';
15
+ import { ImageRadioStatusSchema, ImageRxEventSchema, ImageRxSubscriptionSchema, FaxCalibrationCommandResultSchema, FaxCalibrationResetCommandSchema, FaxCalibrationSetCommandSchema, SstvTxCancelCommandSchema, SstvTxCommandResultSchema, SstvTxStartCommandSchema, SstvTxStatusSchema, } from './image-radio.schema.js';
15
16
  // WebSocket消息类型枚举
16
17
  export var WSMessageType;
17
18
  (function (WSMessageType) {
@@ -26,6 +27,16 @@ export var WSMessageType;
26
27
  WSMessageType["STOP_ENGINE"] = "stopEngine";
27
28
  WSMessageType["GET_STATUS"] = "getStatus";
28
29
  WSMessageType["SET_MODE"] = "setMode";
30
+ WSMessageType["SUBSCRIBE_IMAGE_RX"] = "subscribeImageRx";
31
+ WSMessageType["IMAGE_RADIO_STATUS"] = "imageRadioStatus";
32
+ WSMessageType["IMAGE_RX_EVENT"] = "imageRxEvent";
33
+ WSMessageType["SSTV_TX_START"] = "sstvTxStart";
34
+ WSMessageType["SSTV_TX_CANCEL"] = "sstvTxCancel";
35
+ WSMessageType["SSTV_TX_STATUS"] = "sstvTxStatus";
36
+ WSMessageType["SSTV_TX_COMMAND_RESULT"] = "sstvTxCommandResult";
37
+ WSMessageType["FAX_CALIBRATION_SET"] = "faxCalibrationSet";
38
+ WSMessageType["FAX_CALIBRATION_RESET"] = "faxCalibrationReset";
39
+ WSMessageType["FAX_CALIBRATION_COMMAND_RESULT"] = "faxCalibrationCommandResult";
29
40
  // ===== 引擎事件 =====
30
41
  WSMessageType["MODE_CHANGED"] = "modeChanged";
31
42
  WSMessageType["SLOT_START"] = "slotStart";
@@ -37,6 +48,7 @@ export var WSMessageType;
37
48
  WSMessageType["SPECTRUM_FRAME"] = "spectrumFrame";
38
49
  WSMessageType["SPECTRUM_SESSION_STATE_CHANGED"] = "spectrumSessionStateChanged";
39
50
  WSMessageType["INVOKE_SPECTRUM_CONTROL"] = "invokeSpectrumControl";
51
+ WSMessageType["SPECTRUM_VIEWPORT_CHANGED"] = "spectrumViewportChanged";
40
52
  WSMessageType["DECODE_ERROR"] = "decodeError";
41
53
  WSMessageType["SYSTEM_STATUS"] = "systemStatus";
42
54
  WSMessageType["BOOTSTRAP_STATUS_CHANGED"] = "bootstrapStatusChanged";
@@ -49,6 +61,8 @@ export var WSMessageType;
49
61
  WSMessageType["OPERATOR_STATUS_UPDATE"] = "operatorStatusUpdate";
50
62
  WSMessageType["SET_OPERATOR_CONTEXT"] = "setOperatorContext";
51
63
  WSMessageType["SET_OPERATOR_RUNTIME_STATE"] = "setOperatorRuntimeState";
64
+ WSMessageType["SET_OPERATOR_STREAM_STATE"] = "setOperatorStreamState";
65
+ WSMessageType["INVOKE_OPERATOR_STRATEGY_ACTION"] = "invokeOperatorStrategyAction";
52
66
  WSMessageType["SET_OPERATOR_RUNTIME_SLOT_CONTENT"] = "setOperatorRuntimeSlotContent";
53
67
  WSMessageType["SET_OPERATOR_TRANSMIT_CYCLES"] = "setOperatorTransmitCycles";
54
68
  WSMessageType["START_OPERATOR"] = "startOperator";
@@ -199,7 +213,7 @@ export const SystemStatusSchema = z.object({
199
213
  connectionHealthy: z.boolean(),
200
214
  }).optional(),
201
215
  /** 引擎模式:digital(FT8/FT4)、voice(语音通联)或 cw(莫尔斯码) */
202
- engineMode: z.enum(['digital', 'voice', 'cw']).default('digital'),
216
+ engineMode: z.enum(['digital', 'voice', 'cw', 'image']).default('digital'),
203
217
  /** 当前电台调制模式(语音模式下使用,如 USB/LSB/FM/AM) */
204
218
  currentRadioMode: z.string().optional(),
205
219
  /** 引擎状态机当前状态 */
@@ -283,10 +297,11 @@ export const DecodeErrorInfoSchema = z.object({
283
297
  /**
284
298
  * Current dial-frequency projection broadcast to plugins and clients.
285
299
  *
286
- * `frequency` is RF dial frequency in hertz; `mode` is the TX-5DR engine mode;
287
- * `band` and `description` are display labels; `radioMode` is the underlying
288
- * modulation when known. `source` distinguishes application changes from
289
- * updates reported by the radio.
300
+ * `frequency` is the logical RF dial target in hertz; `mode` is the TX-5DR
301
+ * engine mode; `band` and `description` are display labels; `radioMode` is the
302
+ * underlying modulation when known. `observedFrequency` is the latest physical
303
+ * readback. `source` distinguishes application changes from radio observations;
304
+ * `revision` and `connectionGeneration` reject stale events.
290
305
  */
291
306
  export const FrequencyStateSchema = z.object({
292
307
  frequency: z.number(),
@@ -296,6 +311,20 @@ export const FrequencyStateSchema = z.object({
296
311
  radioMode: z.string().optional(),
297
312
  radioConnected: z.boolean(),
298
313
  source: z.enum(['program', 'radio']).optional(),
314
+ /** Monotonic operating-state revision for stale-event suppression. */
315
+ revision: z.number().int().nonnegative().optional(),
316
+ /** Physical CAT session identity associated with this snapshot. */
317
+ connectionGeneration: z.number().int().nonnegative().optional(),
318
+ /** Physical confirmation status for the requested operating state. */
319
+ confirmation: z.enum(['confirmed', 'pending', 'mismatch', 'offline']).optional(),
320
+ /** Last frequency read back from the physical radio, when available. */
321
+ observedFrequency: z.number().optional(),
322
+ /** Requested logical frequency when it differs from the observed value. */
323
+ requestedFrequency: z.number().optional(),
324
+ /** Correlates a snapshot with one programmatic operating-state mutation. */
325
+ operationId: z.string().optional(),
326
+ /** Physical radio-mode confirmation status. */
327
+ modeConfirmation: z.enum(['confirmed', 'unconfirmed', 'unknown']).optional(),
299
328
  });
300
329
  export const SpectrumControlInvocationSchema = z.object({
301
330
  id: SpectrumSessionControlIdSchema,
@@ -307,7 +336,7 @@ export const PTTStatusSchema = z.object({
307
336
  operatorIds: z.array(z.string()),
308
337
  phase: z.enum(['idle', 'starting', 'on_air', 'draining', 'stopping', 'unknown']).optional(),
309
338
  frameId: z.string().optional(),
310
- source: z.enum(['digital', 'voice', 'voice-keyer', 'cw', 'tune-tone', 'manual', 'test']).optional(),
339
+ source: z.enum(['digital', 'voice', 'voice-keyer', 'cw', 'sstv', 'tune-tone', 'manual', 'test']).optional(),
311
340
  });
312
341
  export const TuneToneStartPayloadSchema = z.object({
313
342
  operatorId: z.string().optional(),
@@ -335,8 +364,90 @@ export const StrategyRuntimeContextSchema = z.object({
335
364
  reportReceived: z.number().optional(),
336
365
  actualFrequency: z.number().optional(),
337
366
  });
367
+ export const StrategyStateOptionSchema = z.object({
368
+ id: z.string().min(1),
369
+ label: z.string().min(1).optional(),
370
+ transmitText: z.string().optional(),
371
+ });
372
+ export const StrategyActionInputSchema = z.discriminatedUnion('kind', [
373
+ z.object({
374
+ kind: z.literal('text'),
375
+ label: z.string().optional(),
376
+ value: z.string().optional(),
377
+ placeholder: z.string().optional(),
378
+ maxLength: z.number().int().positive().optional(),
379
+ }),
380
+ z.object({
381
+ kind: z.enum(['number', 'audio-frequency']),
382
+ label: z.string().optional(),
383
+ value: z.number().optional(),
384
+ min: z.number().optional(),
385
+ max: z.number().optional(),
386
+ step: z.number().positive().optional(),
387
+ unit: z.string().optional(),
388
+ spectrumPick: z.boolean().optional(),
389
+ }),
390
+ ]);
391
+ export const StrategyActionDescriptorSchema = z.object({
392
+ id: z.string().min(1),
393
+ label: z.string().min(1),
394
+ description: z.string().optional(),
395
+ icon: z.string().optional(),
396
+ tone: z.enum(['default', 'primary', 'success', 'warning', 'danger']).optional(),
397
+ presentation: z.enum(['primary', 'secondary', 'menu', 'segmented']).optional(),
398
+ groupId: z.string().optional(),
399
+ selected: z.boolean().optional(),
400
+ disabledReason: z.string().optional(),
401
+ previewText: z.string().optional(),
402
+ confirmation: z.object({
403
+ title: z.string().min(1),
404
+ description: z.string().optional(),
405
+ confirmLabel: z.string().optional(),
406
+ cancelLabel: z.string().optional(),
407
+ }).optional(),
408
+ input: StrategyActionInputSchema.optional(),
409
+ navigation: z.object({
410
+ kind: z.literal('plugin-page'),
411
+ pageId: z.string().min(1),
412
+ }).optional(),
413
+ });
414
+ export const StrategyAttentionSchema = z.object({
415
+ id: z.string().min(1),
416
+ tone: z.enum(['info', 'warning', 'danger', 'success']),
417
+ title: z.string().min(1),
418
+ description: z.string().optional(),
419
+ params: z.record(z.string(), z.union([z.string(), z.number()])).optional(),
420
+ notify: z.boolean().optional(),
421
+ expiresAt: z.number().optional(),
422
+ actionIds: z.array(z.string()).optional(),
423
+ });
424
+ export const StrategyCompletionProjectionSchema = z.object({
425
+ state: z.enum(['not-ready', 'ready', 'committing', 'committed', 'failed']),
426
+ label: z.string().optional(),
427
+ recordId: z.string().optional(),
428
+ });
429
+ export const StrategyStreamSnapshotSchema = z.object({
430
+ streamId: z.string().min(1),
431
+ currentState: z.string(),
432
+ targetCallsign: z.string().optional(),
433
+ targetGrid: z.string().optional(),
434
+ audioFrequencyHz: z.number().min(0).max(5000),
435
+ qsoLifecycleEpoch: z.number().int().nonnegative(),
436
+ stateOptions: z.array(StrategyStateOptionSchema).optional(),
437
+ actions: z.array(StrategyActionDescriptorSchema).optional(),
438
+ attentions: z.array(StrategyAttentionSchema).optional(),
439
+ completion: StrategyCompletionProjectionSchema.optional(),
440
+ lastReceivedText: z.string().optional(),
441
+ nextTransmitText: z.string().optional(),
442
+ });
443
+ export const StrategyTransmissionSchema = z.object({
444
+ streamId: z.string().min(1),
445
+ text: z.string().min(1),
446
+ audioFrequencyHz: z.number().min(0).max(5000),
447
+ });
338
448
  export const AssistedQueueDisplayStateSchema = z.enum([
339
449
  'TX1', 'TX2', 'TX3', 'TX4', 'TX5', 'engaged', 'closing', 'paused', 'no-response', 'later', 'review',
450
+ 'candidate', 'authorized', 'dupe',
340
451
  ]);
341
452
  export const AssistedQueuePauseReasonSchema = z.enum(['target-busy', 'stale']);
342
453
  export const AssistedQueueToneSchema = z.enum(['neutral', 'active', 'success', 'warning', 'danger']);
@@ -356,12 +467,69 @@ export const AssistedQueueRowSchema = z.object({
356
467
  targetGrid: z.string().optional(),
357
468
  lastSnr: z.number().optional(),
358
469
  lastHeardCyclesAgo: z.number().int().nonnegative().optional(),
470
+ lastHeardCycle: z.union([z.literal(0), z.literal(1)]).optional(),
471
+ streamId: z.string().optional(),
472
+ audioFrequencyHz: z.number().optional(),
473
+ authorizationId: z.string().optional(),
474
+ actions: z.array(StrategyActionDescriptorSchema).optional(),
359
475
  });
360
476
  export const AssistedQueueSnapshotSchema = z.object({
361
477
  version: z.number().int().nonnegative(),
362
478
  activeEntryId: z.string().optional(),
479
+ activeEntryIds: z.array(z.string()).optional(),
480
+ maxActiveStreams: z.number().int().min(1).max(5).optional(),
481
+ requestedMaxActiveStreams: z.number().int().min(1).max(5).optional(),
363
482
  rows: z.array(AssistedQueueRowSchema),
364
483
  });
484
+ /** Null clears an unsettable runtime report field over JSON/WebSocket. */
485
+ export const OperatorContextReportFieldSchema = z.number().nullish();
486
+ export const StrategyMessagePresentationToneSchema = z.enum([
487
+ 'neutral', 'primary', 'secondary', 'success', 'warning', 'danger',
488
+ ]);
489
+ export const StrategyMessagePresentationBadgeSchema = z.object({
490
+ label: z.string().min(1),
491
+ tone: StrategyMessagePresentationToneSchema,
492
+ });
493
+ export const StrategyMessagePresentationTokenMatchSchema = z.object({
494
+ firstTokenIn: z.array(z.string().min(1)).optional(),
495
+ anyTokenIn: z.array(z.string().min(1)).optional(),
496
+ });
497
+ export const StrategyMessagePresentationClassSchema = z.object({
498
+ badge: StrategyMessagePresentationBadgeSchema.optional(),
499
+ badges: z.array(StrategyMessagePresentationBadgeSchema).optional(),
500
+ row: z.object({
501
+ tone: StrategyMessagePresentationToneSchema,
502
+ background: z.enum(['none', 'soft']).optional(),
503
+ accent: z.boolean().optional(),
504
+ }).optional(),
505
+ emphasisWhen: z.array(StrategyMessagePresentationTokenMatchSchema).min(1).optional(),
506
+ textDecoration: z.literal('line-through').optional(),
507
+ opacity: z.enum(['normal', 'muted']).optional(),
508
+ });
509
+ export const StrategyMessagePresentationProjectionSchema = z.object({
510
+ revision: z.number().int().nonnegative(),
511
+ mode: z.enum(['replace-logbook', 'augment']),
512
+ subject: z.literal('sender-callsign'),
513
+ partitionBy: z.enum(['band', 'mode', 'none']),
514
+ eligiblePartitions: z.array(z.string()).optional(),
515
+ defaultClass: z.string().optional(),
516
+ classes: z.record(z.string(), StrategyMessagePresentationClassSchema),
517
+ assignments: z.array(z.object({
518
+ subject: z.string().min(1),
519
+ partition: z.string().optional(),
520
+ classId: z.string().min(1),
521
+ })),
522
+ noveltyRules: z.array(z.object({
523
+ fact: z.literal('grid-field-2'),
524
+ knownValuesByPartition: z.record(z.string(), z.array(z.string().min(1))),
525
+ classId: z.string().min(1),
526
+ })).optional(),
527
+ tagRules: z.array(z.object({
528
+ id: z.string().min(1),
529
+ match: StrategyMessagePresentationTokenMatchSchema,
530
+ badge: StrategyMessagePresentationBadgeSchema,
531
+ })).optional(),
532
+ });
365
533
  export const StrategyRuntimeSnapshotSchema = z.object({
366
534
  currentState: z.string(),
367
535
  slots: z.object({
@@ -375,8 +543,30 @@ export const StrategyRuntimeSnapshotSchema = z.object({
375
543
  context: StrategyRuntimeContextSchema.optional(),
376
544
  availableSlots: z.array(z.string()).optional(),
377
545
  qsoLifecycleEpoch: z.number().int().nonnegative().optional(),
546
+ streams: z.array(StrategyStreamSnapshotSchema).optional(),
378
547
  queue: AssistedQueueSnapshotSchema.optional(),
548
+ actions: z.array(StrategyActionDescriptorSchema).optional(),
549
+ attentions: z.array(StrategyAttentionSchema).optional(),
550
+ messagePresentation: StrategyMessagePresentationProjectionSchema.optional(),
551
+ transmitGate: z.object({
552
+ allowed: z.literal(false),
553
+ reason: z.string().min(1),
554
+ actionId: z.string().optional(),
555
+ }).optional(),
379
556
  });
557
+ export const StrategyActionTargetSchema = z.discriminatedUnion('kind', [
558
+ z.object({ kind: z.literal('runtime') }),
559
+ z.object({
560
+ kind: z.literal('stream'),
561
+ streamId: z.string().min(1),
562
+ lifecycleEpoch: z.number().int().nonnegative(),
563
+ }),
564
+ z.object({
565
+ kind: z.literal('queue-entry'),
566
+ entryId: z.string().min(1),
567
+ queueVersion: z.number().int().nonnegative(),
568
+ }),
569
+ ]);
380
570
  export const LevelMeterDisplayStyleSchema = z.enum([
381
571
  's-meter-dbm',
382
572
  's-meter',
@@ -425,11 +615,12 @@ export const MeterDataSchema = z.object({
425
615
  raw: z.number(),
426
616
  percent: z.number(),
427
617
  alert: z.boolean(),
618
+ unit: z.enum(['percent', 'dbfs']).optional(),
428
619
  }).nullable(),
429
620
  level: LevelMeterReadingSchema.nullable(),
430
621
  power: z.object({
431
622
  raw: z.number(),
432
- percent: z.number(),
623
+ percent: z.number().nullable(),
433
624
  watts: z.number().nullable(), // 绝对瓦数(仅当电台支持 RFPOWER_METER_WATTS 时非 null)
434
625
  maxWatts: z.number().nullable(), // 当前频率+模式对应的量程瓦数
435
626
  }).nullable(),
@@ -455,6 +646,46 @@ export const WSModeChangedMessageSchema = WSBaseMessageSchema.extend({
455
646
  type: z.literal(WSMessageType.MODE_CHANGED),
456
647
  data: ModeDescriptorSchema,
457
648
  });
649
+ export const WSSubscribeImageRxMessageSchema = WSBaseMessageSchema.extend({
650
+ type: z.literal(WSMessageType.SUBSCRIBE_IMAGE_RX),
651
+ data: ImageRxSubscriptionSchema,
652
+ });
653
+ export const WSImageRadioStatusMessageSchema = WSBaseMessageSchema.extend({
654
+ type: z.literal(WSMessageType.IMAGE_RADIO_STATUS),
655
+ data: ImageRadioStatusSchema,
656
+ });
657
+ export const WSImageRxEventMessageSchema = WSBaseMessageSchema.extend({
658
+ type: z.literal(WSMessageType.IMAGE_RX_EVENT),
659
+ data: ImageRxEventSchema,
660
+ });
661
+ export const WSSstvTxStartMessageSchema = WSBaseMessageSchema.extend({
662
+ type: z.literal(WSMessageType.SSTV_TX_START),
663
+ data: SstvTxStartCommandSchema,
664
+ });
665
+ export const WSSstvTxCancelMessageSchema = WSBaseMessageSchema.extend({
666
+ type: z.literal(WSMessageType.SSTV_TX_CANCEL),
667
+ data: SstvTxCancelCommandSchema,
668
+ });
669
+ export const WSSstvTxStatusMessageSchema = WSBaseMessageSchema.extend({
670
+ type: z.literal(WSMessageType.SSTV_TX_STATUS),
671
+ data: SstvTxStatusSchema,
672
+ });
673
+ export const WSSstvTxCommandResultMessageSchema = WSBaseMessageSchema.extend({
674
+ type: z.literal(WSMessageType.SSTV_TX_COMMAND_RESULT),
675
+ data: SstvTxCommandResultSchema,
676
+ });
677
+ export const WSFaxCalibrationSetMessageSchema = WSBaseMessageSchema.extend({
678
+ type: z.literal(WSMessageType.FAX_CALIBRATION_SET),
679
+ data: FaxCalibrationSetCommandSchema,
680
+ });
681
+ export const WSFaxCalibrationResetMessageSchema = WSBaseMessageSchema.extend({
682
+ type: z.literal(WSMessageType.FAX_CALIBRATION_RESET),
683
+ data: FaxCalibrationResetCommandSchema,
684
+ });
685
+ export const WSFaxCalibrationCommandResultMessageSchema = WSBaseMessageSchema.extend({
686
+ type: z.literal(WSMessageType.FAX_CALIBRATION_COMMAND_RESULT),
687
+ data: FaxCalibrationCommandResultSchema,
688
+ });
458
689
  export const WSSlotStartMessageSchema = WSBaseMessageSchema.extend({
459
690
  type: z.literal(WSMessageType.SLOT_START),
460
691
  data: SlotInfoSchema,
@@ -475,8 +706,13 @@ export const WSSubscribeSpectrumMessageSchema = WSBaseMessageSchema.extend({
475
706
  type: z.literal(WSMessageType.SUBSCRIBE_SPECTRUM),
476
707
  data: z.object({
477
708
  kind: SpectrumKindSchema.nullable(),
709
+ viewport: SpectrumViewportSchema.optional(),
478
710
  }),
479
711
  });
712
+ export const WSSpectrumViewportChangedMessageSchema = WSBaseMessageSchema.extend({
713
+ type: z.literal(WSMessageType.SPECTRUM_VIEWPORT_CHANGED),
714
+ data: SpectrumViewportSchema.nullable(),
715
+ });
480
716
  export const WSSpectrumSubscriptionChangedMessageSchema = WSBaseMessageSchema.extend({
481
717
  type: z.literal(WSMessageType.SPECTRUM_SUBSCRIPTION_CHANGED),
482
718
  data: z.object({
@@ -575,10 +811,13 @@ export const OperatorStatusSchema = z.object({
575
811
  isTransmitting: z.boolean(), // 是否已授予策略自动发射许可(发射开关状态)
576
812
  isInActivePTT: z.boolean().optional(), // 该操作员的音频是否正在被实际播放
577
813
  hasTransmitIntent: z.boolean().optional(), // 当前策略是否有可在合法周期发射的有效内容
814
+ currentTransmissions: z.array(StrategyTransmissionSchema).optional(),
578
815
  currentSlot: z.string().optional(),
579
816
  context: z.object({
580
817
  myCall: z.string(),
581
818
  myGrid: z.string(),
819
+ /** Canonical active target set; `targetCall` remains the primary compatibility field. */
820
+ targetCalls: z.array(z.string()).optional(),
582
821
  targetCall: z.string(),
583
822
  targetGrid: z.string().optional(),
584
823
  frequency: z.number().optional(),
@@ -638,8 +877,8 @@ export const WSSetOperatorContextMessageSchema = WSBaseMessageSchema.extend({
638
877
  targetCallsign: z.string().optional(),
639
878
  targetGrid: z.string().optional(),
640
879
  frequency: z.number().optional(),
641
- reportSent: z.number().optional(),
642
- reportReceived: z.number().optional(),
880
+ reportSent: OperatorContextReportFieldSchema,
881
+ reportReceived: OperatorContextReportFieldSchema,
643
882
  }),
644
883
  }),
645
884
  });
@@ -653,6 +892,25 @@ export const WSSetOperatorRuntimeStateMessageSchema = WSBaseMessageSchema.extend
653
892
  state: OperatorRuntimeSlotSchema,
654
893
  }),
655
894
  });
895
+ /** Sets one strategy-owned stream to a protocol-approved state. */
896
+ export const WSSetOperatorStreamStateMessageSchema = WSBaseMessageSchema.extend({
897
+ type: z.literal(WSMessageType.SET_OPERATOR_STREAM_STATE),
898
+ data: z.object({
899
+ operatorId: z.string(),
900
+ streamId: z.string().min(1),
901
+ stateId: z.string().min(1),
902
+ expectedLifecycleEpoch: z.number().int().nonnegative(),
903
+ }),
904
+ });
905
+ export const WSInvokeOperatorStrategyActionMessageSchema = WSBaseMessageSchema.extend({
906
+ type: z.literal(WSMessageType.INVOKE_OPERATOR_STRATEGY_ACTION),
907
+ data: z.object({
908
+ operatorId: z.string().min(1),
909
+ target: StrategyActionTargetSchema,
910
+ actionId: z.string().min(1),
911
+ payload: z.unknown().optional(),
912
+ }),
913
+ });
656
914
  /**
657
915
  * 设置操作员运行时槽位内容消息
658
916
  */
@@ -1236,12 +1494,23 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1236
1494
  WSPongMessageSchema,
1237
1495
  // 服务端到客户端
1238
1496
  WSModeChangedMessageSchema,
1497
+ WSSubscribeImageRxMessageSchema,
1498
+ WSImageRadioStatusMessageSchema,
1499
+ WSImageRxEventMessageSchema,
1500
+ WSSstvTxStartMessageSchema,
1501
+ WSSstvTxCancelMessageSchema,
1502
+ WSSstvTxStatusMessageSchema,
1503
+ WSSstvTxCommandResultMessageSchema,
1504
+ WSFaxCalibrationSetMessageSchema,
1505
+ WSFaxCalibrationResetMessageSchema,
1506
+ WSFaxCalibrationCommandResultMessageSchema,
1239
1507
  WSSlotStartMessageSchema,
1240
1508
  WSSubWindowMessageSchema,
1241
1509
  WSSlotPackUpdatedMessageSchema,
1242
1510
  WSSpectrumCapabilitiesMessageSchema,
1243
1511
  WSSubscribeSpectrumMessageSchema,
1244
1512
  WSSpectrumSubscriptionChangedMessageSchema,
1513
+ WSSpectrumViewportChangedMessageSchema,
1245
1514
  WSSpectrumFrameMessageSchema,
1246
1515
  WSSpectrumSessionStateChangedMessageSchema,
1247
1516
  WSInvokeSpectrumControlMessageSchema,
@@ -1264,6 +1533,8 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1264
1533
  WSOperatorStatusUpdateMessageSchema,
1265
1534
  WSSetOperatorContextMessageSchema,
1266
1535
  WSSetOperatorRuntimeStateMessageSchema,
1536
+ WSSetOperatorStreamStateMessageSchema,
1537
+ WSInvokeOperatorStrategyActionMessageSchema,
1267
1538
  WSSetOperatorRuntimeSlotContentMessageSchema,
1268
1539
  WSSetOperatorTransmitCyclesMessageSchema,
1269
1540
  WSStartOperatorMessageSchema,
@@ -1336,7 +1607,9 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1336
1607
  ]);
1337
1608
  export const TransmitRequestSchema = z.object({
1338
1609
  operatorId: z.string(),
1610
+ streamId: z.string().min(1).optional(),
1339
1611
  transmission: z.string(),
1612
+ audioFrequencyHz: z.number().min(0).max(5000).optional(),
1340
1613
  /** 是否覆盖同一操作员在同一时隙的现有 TX 帧(自动重决策时为 true) */
1341
1614
  replaceExisting: z.boolean().optional(),
1342
1615
  source: z.enum(['standard-qso', 'plugin', 'late-decode', 'operator-edit', 'manual']).optional(),
@@ -1344,12 +1617,26 @@ export const TransmitRequestSchema = z.object({
1344
1617
  /** Host command epoch used to tombstone stale encode/mix callbacks. */
1345
1618
  decisionEpoch: z.number().int().nonnegative().optional(),
1346
1619
  });
1620
+ export const TransmitBatchRequestSchema = z.object({
1621
+ operatorId: z.string(),
1622
+ transmissions: z.array(z.object({
1623
+ streamId: z.string().min(1),
1624
+ transmission: z.string(),
1625
+ audioFrequencyHz: z.number().min(0).max(5000),
1626
+ })).max(5),
1627
+ replaceExisting: z.boolean().optional(),
1628
+ source: z.enum(['standard-qso', 'plugin', 'late-decode', 'operator-edit', 'manual']).optional(),
1629
+ reason: z.string().optional(),
1630
+ decisionEpoch: z.number().int().nonnegative().optional(),
1631
+ });
1347
1632
  // ===== 前端应用事件接口 =====
1348
1633
  /**
1349
1634
  * 发射完成事件信息
1350
1635
  */
1351
1636
  export const TransmissionCompleteInfoSchema = z.object({
1352
1637
  operatorId: z.string(),
1638
+ streamId: z.string().optional(),
1639
+ trackId: z.string().optional(),
1353
1640
  success: z.boolean(),
1354
1641
  frameId: z.string().optional(),
1355
1642
  physicalConfirmed: z.boolean().optional(),