@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
@@ -20,7 +20,11 @@ import type {
20
20
  PluginSystemSnapshot,
21
21
  } from './plugin.schema.js';
22
22
  import { ModeDescriptorSchema } from './mode.schema.js';
23
- import { QSORecordSchema, TargetSelectionPriorityModeSchema } from './qso.schema.js';
23
+ import {
24
+ QSORecordSchema,
25
+ QSOPersistencePolicySchema,
26
+ TargetSelectionPriorityModeSchema,
27
+ } from './qso.schema.js';
24
28
  import {
25
29
  LogBookStatisticsSchema,
26
30
  LogbookHealthSchema,
@@ -36,10 +40,27 @@ import { CWDecoderEventSchema, CWDecoderStatusSchema, type CWDecoderEvent, type
36
40
  import { CapabilityListSchema, CapabilityStateSchema, WriteCapabilityPayloadSchema } from './radio-capability.schema.js';
37
41
  import { RadioPowerStateEventSchema } from './radio-power.schema.js';
38
42
  import { AudioSidecarStatusPayloadSchema } from './audio-sidecar.schema.js';
39
- import { SpectrumCapabilitiesSchema, SpectrumFrameSchema, SpectrumKindSchema, SpectrumSessionControlActionSchema, SpectrumSessionControlIdSchema, SpectrumSessionStateSchema } from './spectrum.schema.js';
43
+ import { SpectrumCapabilitiesSchema, SpectrumFrameSchema, SpectrumKindSchema, SpectrumSessionControlActionSchema, SpectrumSessionControlIdSchema, SpectrumSessionStateSchema, SpectrumViewportSchema } from './spectrum.schema.js';
40
44
  import type { RealtimeSettingsResponseData } from './realtime.schema.js';
41
45
  import type { AndroidOperatorAudioStatus } from './android-operator-audio.schema.js';
42
46
  import { ClockStatusSummarySchema } from './system.schema.js';
47
+ import {
48
+ ImageRadioStatusSchema,
49
+ ImageRxEventSchema,
50
+ ImageRxSubscriptionSchema,
51
+ FaxCalibrationCommandResultSchema,
52
+ FaxCalibrationResetCommandSchema,
53
+ FaxCalibrationSetCommandSchema,
54
+ SstvTxCancelCommandSchema,
55
+ SstvTxCommandResultSchema,
56
+ SstvTxStartCommandSchema,
57
+ SstvTxStatusSchema,
58
+ type ImageRadioStatus,
59
+ type ImageRxEvent,
60
+ type FaxCalibrationCommandResult,
61
+ type SstvTxCommandResult,
62
+ type SstvTxStatus,
63
+ } from './image-radio.schema.js';
43
64
 
44
65
  // WebSocket消息类型枚举
45
66
  export enum WSMessageType {
@@ -55,6 +76,16 @@ export enum WSMessageType {
55
76
  STOP_ENGINE = 'stopEngine',
56
77
  GET_STATUS = 'getStatus',
57
78
  SET_MODE = 'setMode',
79
+ SUBSCRIBE_IMAGE_RX = 'subscribeImageRx',
80
+ IMAGE_RADIO_STATUS = 'imageRadioStatus',
81
+ IMAGE_RX_EVENT = 'imageRxEvent',
82
+ SSTV_TX_START = 'sstvTxStart',
83
+ SSTV_TX_CANCEL = 'sstvTxCancel',
84
+ SSTV_TX_STATUS = 'sstvTxStatus',
85
+ SSTV_TX_COMMAND_RESULT = 'sstvTxCommandResult',
86
+ FAX_CALIBRATION_SET = 'faxCalibrationSet',
87
+ FAX_CALIBRATION_RESET = 'faxCalibrationReset',
88
+ FAX_CALIBRATION_COMMAND_RESULT = 'faxCalibrationCommandResult',
58
89
 
59
90
  // ===== 引擎事件 =====
60
91
  MODE_CHANGED = 'modeChanged',
@@ -67,6 +98,7 @@ export enum WSMessageType {
67
98
  SPECTRUM_FRAME = 'spectrumFrame',
68
99
  SPECTRUM_SESSION_STATE_CHANGED = 'spectrumSessionStateChanged',
69
100
  INVOKE_SPECTRUM_CONTROL = 'invokeSpectrumControl',
101
+ SPECTRUM_VIEWPORT_CHANGED = 'spectrumViewportChanged',
70
102
  DECODE_ERROR = 'decodeError',
71
103
  SYSTEM_STATUS = 'systemStatus',
72
104
  BOOTSTRAP_STATUS_CHANGED = 'bootstrapStatusChanged',
@@ -80,6 +112,8 @@ export enum WSMessageType {
80
112
  OPERATOR_STATUS_UPDATE = 'operatorStatusUpdate',
81
113
  SET_OPERATOR_CONTEXT = 'setOperatorContext',
82
114
  SET_OPERATOR_RUNTIME_STATE = 'setOperatorRuntimeState',
115
+ SET_OPERATOR_STREAM_STATE = 'setOperatorStreamState',
116
+ INVOKE_OPERATOR_STRATEGY_ACTION = 'invokeOperatorStrategyAction',
83
117
  SET_OPERATOR_RUNTIME_SLOT_CONTENT = 'setOperatorRuntimeSlotContent',
84
118
  SET_OPERATOR_TRANSMIT_CYCLES = 'setOperatorTransmitCycles',
85
119
  START_OPERATOR = 'startOperator',
@@ -254,7 +288,7 @@ export const SystemStatusSchema = z.object({
254
288
  connectionHealthy: z.boolean(),
255
289
  }).optional(),
256
290
  /** 引擎模式:digital(FT8/FT4)、voice(语音通联)或 cw(莫尔斯码) */
257
- engineMode: z.enum(['digital', 'voice', 'cw']).default('digital'),
291
+ engineMode: z.enum(['digital', 'voice', 'cw', 'image']).default('digital'),
258
292
  /** 当前电台调制模式(语音模式下使用,如 USB/LSB/FM/AM) */
259
293
  currentRadioMode: z.string().optional(),
260
294
  /** 引擎状态机当前状态 */
@@ -344,10 +378,11 @@ export const DecodeErrorInfoSchema = z.object({
344
378
  /**
345
379
  * Current dial-frequency projection broadcast to plugins and clients.
346
380
  *
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.
381
+ * `frequency` is the logical RF dial target in hertz; `mode` is the TX-5DR
382
+ * engine mode; `band` and `description` are display labels; `radioMode` is the
383
+ * underlying modulation when known. `observedFrequency` is the latest physical
384
+ * readback. `source` distinguishes application changes from radio observations;
385
+ * `revision` and `connectionGeneration` reject stale events.
351
386
  */
352
387
  export const FrequencyStateSchema = z.object({
353
388
  frequency: z.number(),
@@ -357,6 +392,20 @@ export const FrequencyStateSchema = z.object({
357
392
  radioMode: z.string().optional(),
358
393
  radioConnected: z.boolean(),
359
394
  source: z.enum(['program', 'radio']).optional(),
395
+ /** Monotonic operating-state revision for stale-event suppression. */
396
+ revision: z.number().int().nonnegative().optional(),
397
+ /** Physical CAT session identity associated with this snapshot. */
398
+ connectionGeneration: z.number().int().nonnegative().optional(),
399
+ /** Physical confirmation status for the requested operating state. */
400
+ confirmation: z.enum(['confirmed', 'pending', 'mismatch', 'offline']).optional(),
401
+ /** Last frequency read back from the physical radio, when available. */
402
+ observedFrequency: z.number().optional(),
403
+ /** Requested logical frequency when it differs from the observed value. */
404
+ requestedFrequency: z.number().optional(),
405
+ /** Correlates a snapshot with one programmatic operating-state mutation. */
406
+ operationId: z.string().optional(),
407
+ /** Physical radio-mode confirmation status. */
408
+ modeConfirmation: z.enum(['confirmed', 'unconfirmed', 'unknown']).optional(),
360
409
  });
361
410
 
362
411
  export const SpectrumControlInvocationSchema = z.object({
@@ -371,7 +420,7 @@ export const PTTStatusSchema = z.object({
371
420
  operatorIds: z.array(z.string()),
372
421
  phase: z.enum(['idle', 'starting', 'on_air', 'draining', 'stopping', 'unknown']).optional(),
373
422
  frameId: z.string().optional(),
374
- source: z.enum(['digital', 'voice', 'voice-keyer', 'cw', 'tune-tone', 'manual', 'test']).optional(),
423
+ source: z.enum(['digital', 'voice', 'voice-keyer', 'cw', 'sstv', 'tune-tone', 'manual', 'test']).optional(),
375
424
  });
376
425
 
377
426
  export const TuneToneStartPayloadSchema = z.object({
@@ -409,8 +458,103 @@ export const StrategyRuntimeContextSchema = z.object({
409
458
  });
410
459
  export type StrategyRuntimeContext = z.infer<typeof StrategyRuntimeContextSchema>;
411
460
 
461
+ export const StrategyStateOptionSchema = z.object({
462
+ id: z.string().min(1),
463
+ label: z.string().min(1).optional(),
464
+ transmitText: z.string().optional(),
465
+ });
466
+ export type StrategyStateOption = z.infer<typeof StrategyStateOptionSchema>;
467
+
468
+ export const StrategyActionInputSchema = z.discriminatedUnion('kind', [
469
+ z.object({
470
+ kind: z.literal('text'),
471
+ label: z.string().optional(),
472
+ value: z.string().optional(),
473
+ placeholder: z.string().optional(),
474
+ maxLength: z.number().int().positive().optional(),
475
+ }),
476
+ z.object({
477
+ kind: z.enum(['number', 'audio-frequency']),
478
+ label: z.string().optional(),
479
+ value: z.number().optional(),
480
+ min: z.number().optional(),
481
+ max: z.number().optional(),
482
+ step: z.number().positive().optional(),
483
+ unit: z.string().optional(),
484
+ spectrumPick: z.boolean().optional(),
485
+ }),
486
+ ]);
487
+
488
+ export const StrategyActionDescriptorSchema = z.object({
489
+ id: z.string().min(1),
490
+ label: z.string().min(1),
491
+ description: z.string().optional(),
492
+ icon: z.string().optional(),
493
+ tone: z.enum(['default', 'primary', 'success', 'warning', 'danger']).optional(),
494
+ presentation: z.enum(['primary', 'secondary', 'menu', 'segmented']).optional(),
495
+ groupId: z.string().optional(),
496
+ selected: z.boolean().optional(),
497
+ disabledReason: z.string().optional(),
498
+ previewText: z.string().optional(),
499
+ confirmation: z.object({
500
+ title: z.string().min(1),
501
+ description: z.string().optional(),
502
+ confirmLabel: z.string().optional(),
503
+ cancelLabel: z.string().optional(),
504
+ }).optional(),
505
+ input: StrategyActionInputSchema.optional(),
506
+ navigation: z.object({
507
+ kind: z.literal('plugin-page'),
508
+ pageId: z.string().min(1),
509
+ }).optional(),
510
+ });
511
+ export type StrategyActionDescriptor = z.infer<typeof StrategyActionDescriptorSchema>;
512
+
513
+ export const StrategyAttentionSchema = z.object({
514
+ id: z.string().min(1),
515
+ tone: z.enum(['info', 'warning', 'danger', 'success']),
516
+ title: z.string().min(1),
517
+ description: z.string().optional(),
518
+ params: z.record(z.string(), z.union([z.string(), z.number()])).optional(),
519
+ notify: z.boolean().optional(),
520
+ expiresAt: z.number().optional(),
521
+ actionIds: z.array(z.string()).optional(),
522
+ });
523
+ export type StrategyAttention = z.infer<typeof StrategyAttentionSchema>;
524
+
525
+ export const StrategyCompletionProjectionSchema = z.object({
526
+ state: z.enum(['not-ready', 'ready', 'committing', 'committed', 'failed']),
527
+ label: z.string().optional(),
528
+ recordId: z.string().optional(),
529
+ });
530
+ export type StrategyCompletionProjection = z.infer<typeof StrategyCompletionProjectionSchema>;
531
+
532
+ export const StrategyStreamSnapshotSchema = z.object({
533
+ streamId: z.string().min(1),
534
+ currentState: z.string(),
535
+ targetCallsign: z.string().optional(),
536
+ targetGrid: z.string().optional(),
537
+ audioFrequencyHz: z.number().min(0).max(5000),
538
+ qsoLifecycleEpoch: z.number().int().nonnegative(),
539
+ stateOptions: z.array(StrategyStateOptionSchema).optional(),
540
+ actions: z.array(StrategyActionDescriptorSchema).optional(),
541
+ attentions: z.array(StrategyAttentionSchema).optional(),
542
+ completion: StrategyCompletionProjectionSchema.optional(),
543
+ lastReceivedText: z.string().optional(),
544
+ nextTransmitText: z.string().optional(),
545
+ });
546
+ export type StrategyStreamSnapshot = z.infer<typeof StrategyStreamSnapshotSchema>;
547
+
548
+ export const StrategyTransmissionSchema = z.object({
549
+ streamId: z.string().min(1),
550
+ text: z.string().min(1),
551
+ audioFrequencyHz: z.number().min(0).max(5000),
552
+ });
553
+ export type StrategyTransmission = z.infer<typeof StrategyTransmissionSchema>;
554
+
412
555
  export const AssistedQueueDisplayStateSchema = z.enum([
413
556
  'TX1', 'TX2', 'TX3', 'TX4', 'TX5', 'engaged', 'closing', 'paused', 'no-response', 'later', 'review',
557
+ 'candidate', 'authorized', 'dupe',
414
558
  ]);
415
559
  export const AssistedQueuePauseReasonSchema = z.enum(['target-busy', 'stale']);
416
560
  export const AssistedQueueToneSchema = z.enum(['neutral', 'active', 'success', 'warning', 'danger']);
@@ -430,10 +574,18 @@ export const AssistedQueueRowSchema = z.object({
430
574
  targetGrid: z.string().optional(),
431
575
  lastSnr: z.number().optional(),
432
576
  lastHeardCyclesAgo: z.number().int().nonnegative().optional(),
577
+ lastHeardCycle: z.union([z.literal(0), z.literal(1)]).optional(),
578
+ streamId: z.string().optional(),
579
+ audioFrequencyHz: z.number().optional(),
580
+ authorizationId: z.string().optional(),
581
+ actions: z.array(StrategyActionDescriptorSchema).optional(),
433
582
  });
434
583
  export const AssistedQueueSnapshotSchema = z.object({
435
584
  version: z.number().int().nonnegative(),
436
585
  activeEntryId: z.string().optional(),
586
+ activeEntryIds: z.array(z.string()).optional(),
587
+ maxActiveStreams: z.number().int().min(1).max(5).optional(),
588
+ requestedMaxActiveStreams: z.number().int().min(1).max(5).optional(),
437
589
  rows: z.array(AssistedQueueRowSchema),
438
590
  });
439
591
  export type AssistedQueueDisplayState = z.infer<typeof AssistedQueueDisplayStateSchema>;
@@ -443,6 +595,63 @@ export type AssistedQueueIcon = z.infer<typeof AssistedQueueIconSchema>;
443
595
  export type AssistedQueueRow = z.infer<typeof AssistedQueueRowSchema>;
444
596
  export type AssistedQueueSnapshot = z.infer<typeof AssistedQueueSnapshotSchema>;
445
597
 
598
+ /** Null clears an unsettable runtime report field over JSON/WebSocket. */
599
+ export const OperatorContextReportFieldSchema = z.number().nullish();
600
+
601
+ export const StrategyMessagePresentationToneSchema = z.enum([
602
+ 'neutral', 'primary', 'secondary', 'success', 'warning', 'danger',
603
+ ]);
604
+ export const StrategyMessagePresentationBadgeSchema = z.object({
605
+ label: z.string().min(1),
606
+ tone: StrategyMessagePresentationToneSchema,
607
+ });
608
+ export const StrategyMessagePresentationTokenMatchSchema = z.object({
609
+ firstTokenIn: z.array(z.string().min(1)).optional(),
610
+ anyTokenIn: z.array(z.string().min(1)).optional(),
611
+ });
612
+ export const StrategyMessagePresentationClassSchema = z.object({
613
+ badge: StrategyMessagePresentationBadgeSchema.optional(),
614
+ badges: z.array(StrategyMessagePresentationBadgeSchema).optional(),
615
+ row: z.object({
616
+ tone: StrategyMessagePresentationToneSchema,
617
+ background: z.enum(['none', 'soft']).optional(),
618
+ accent: z.boolean().optional(),
619
+ }).optional(),
620
+ emphasisWhen: z.array(StrategyMessagePresentationTokenMatchSchema).min(1).optional(),
621
+ textDecoration: z.literal('line-through').optional(),
622
+ opacity: z.enum(['normal', 'muted']).optional(),
623
+ });
624
+ export const StrategyMessagePresentationProjectionSchema = z.object({
625
+ revision: z.number().int().nonnegative(),
626
+ mode: z.enum(['replace-logbook', 'augment']),
627
+ subject: z.literal('sender-callsign'),
628
+ partitionBy: z.enum(['band', 'mode', 'none']),
629
+ eligiblePartitions: z.array(z.string()).optional(),
630
+ defaultClass: z.string().optional(),
631
+ classes: z.record(z.string(), StrategyMessagePresentationClassSchema),
632
+ assignments: z.array(z.object({
633
+ subject: z.string().min(1),
634
+ partition: z.string().optional(),
635
+ classId: z.string().min(1),
636
+ })),
637
+ noveltyRules: z.array(z.object({
638
+ fact: z.literal('grid-field-2'),
639
+ knownValuesByPartition: z.record(z.string(), z.array(z.string().min(1))),
640
+ classId: z.string().min(1),
641
+ })).optional(),
642
+ tagRules: z.array(z.object({
643
+ id: z.string().min(1),
644
+ match: StrategyMessagePresentationTokenMatchSchema,
645
+ badge: StrategyMessagePresentationBadgeSchema,
646
+ })).optional(),
647
+ });
648
+
649
+ export type StrategyMessagePresentationTone = z.infer<typeof StrategyMessagePresentationToneSchema>;
650
+ export type StrategyMessagePresentationBadge = z.infer<typeof StrategyMessagePresentationBadgeSchema>;
651
+ export type StrategyMessagePresentationTokenMatch = z.infer<typeof StrategyMessagePresentationTokenMatchSchema>;
652
+ export type StrategyMessagePresentationClass = z.infer<typeof StrategyMessagePresentationClassSchema>;
653
+ export type StrategyMessagePresentationProjection = z.infer<typeof StrategyMessagePresentationProjectionSchema>;
654
+
446
655
  export const StrategyRuntimeSnapshotSchema = z.object({
447
656
  currentState: z.string(),
448
657
  slots: z.object({
@@ -456,8 +665,32 @@ export const StrategyRuntimeSnapshotSchema = z.object({
456
665
  context: StrategyRuntimeContextSchema.optional(),
457
666
  availableSlots: z.array(z.string()).optional(),
458
667
  qsoLifecycleEpoch: z.number().int().nonnegative().optional(),
668
+ streams: z.array(StrategyStreamSnapshotSchema).optional(),
459
669
  queue: AssistedQueueSnapshotSchema.optional(),
670
+ actions: z.array(StrategyActionDescriptorSchema).optional(),
671
+ attentions: z.array(StrategyAttentionSchema).optional(),
672
+ messagePresentation: StrategyMessagePresentationProjectionSchema.optional(),
673
+ transmitGate: z.object({
674
+ allowed: z.literal(false),
675
+ reason: z.string().min(1),
676
+ actionId: z.string().optional(),
677
+ }).optional(),
460
678
  });
679
+
680
+ export const StrategyActionTargetSchema = z.discriminatedUnion('kind', [
681
+ z.object({ kind: z.literal('runtime') }),
682
+ z.object({
683
+ kind: z.literal('stream'),
684
+ streamId: z.string().min(1),
685
+ lifecycleEpoch: z.number().int().nonnegative(),
686
+ }),
687
+ z.object({
688
+ kind: z.literal('queue-entry'),
689
+ entryId: z.string().min(1),
690
+ queueVersion: z.number().int().nonnegative(),
691
+ }),
692
+ ]);
693
+ export type StrategyActionTarget = z.infer<typeof StrategyActionTargetSchema>;
461
694
  export type StrategyRuntimeSnapshot = z.infer<typeof StrategyRuntimeSnapshotSchema>;
462
695
 
463
696
  export const LevelMeterDisplayStyleSchema = z.enum([
@@ -515,11 +748,12 @@ export const MeterDataSchema = z.object({
515
748
  raw: z.number(),
516
749
  percent: z.number(),
517
750
  alert: z.boolean(),
751
+ unit: z.enum(['percent', 'dbfs']).optional(),
518
752
  }).nullable(),
519
753
  level: LevelMeterReadingSchema.nullable(),
520
754
  power: z.object({
521
755
  raw: z.number(),
522
- percent: z.number(),
756
+ percent: z.number().nullable(),
523
757
  watts: z.number().nullable(), // 绝对瓦数(仅当电台支持 RFPOWER_METER_WATTS 时非 null)
524
758
  maxWatts: z.number().nullable(), // 当前频率+模式对应的量程瓦数
525
759
  }).nullable(),
@@ -566,6 +800,56 @@ export const WSModeChangedMessageSchema = WSBaseMessageSchema.extend({
566
800
  data: ModeDescriptorSchema,
567
801
  });
568
802
 
803
+ export const WSSubscribeImageRxMessageSchema = WSBaseMessageSchema.extend({
804
+ type: z.literal(WSMessageType.SUBSCRIBE_IMAGE_RX),
805
+ data: ImageRxSubscriptionSchema,
806
+ });
807
+
808
+ export const WSImageRadioStatusMessageSchema = WSBaseMessageSchema.extend({
809
+ type: z.literal(WSMessageType.IMAGE_RADIO_STATUS),
810
+ data: ImageRadioStatusSchema,
811
+ });
812
+
813
+ export const WSImageRxEventMessageSchema = WSBaseMessageSchema.extend({
814
+ type: z.literal(WSMessageType.IMAGE_RX_EVENT),
815
+ data: ImageRxEventSchema,
816
+ });
817
+
818
+ export const WSSstvTxStartMessageSchema = WSBaseMessageSchema.extend({
819
+ type: z.literal(WSMessageType.SSTV_TX_START),
820
+ data: SstvTxStartCommandSchema,
821
+ });
822
+
823
+ export const WSSstvTxCancelMessageSchema = WSBaseMessageSchema.extend({
824
+ type: z.literal(WSMessageType.SSTV_TX_CANCEL),
825
+ data: SstvTxCancelCommandSchema,
826
+ });
827
+
828
+ export const WSSstvTxStatusMessageSchema = WSBaseMessageSchema.extend({
829
+ type: z.literal(WSMessageType.SSTV_TX_STATUS),
830
+ data: SstvTxStatusSchema,
831
+ });
832
+
833
+ export const WSSstvTxCommandResultMessageSchema = WSBaseMessageSchema.extend({
834
+ type: z.literal(WSMessageType.SSTV_TX_COMMAND_RESULT),
835
+ data: SstvTxCommandResultSchema,
836
+ });
837
+
838
+ export const WSFaxCalibrationSetMessageSchema = WSBaseMessageSchema.extend({
839
+ type: z.literal(WSMessageType.FAX_CALIBRATION_SET),
840
+ data: FaxCalibrationSetCommandSchema,
841
+ });
842
+
843
+ export const WSFaxCalibrationResetMessageSchema = WSBaseMessageSchema.extend({
844
+ type: z.literal(WSMessageType.FAX_CALIBRATION_RESET),
845
+ data: FaxCalibrationResetCommandSchema,
846
+ });
847
+
848
+ export const WSFaxCalibrationCommandResultMessageSchema = WSBaseMessageSchema.extend({
849
+ type: z.literal(WSMessageType.FAX_CALIBRATION_COMMAND_RESULT),
850
+ data: FaxCalibrationCommandResultSchema,
851
+ });
852
+
569
853
  export const WSSlotStartMessageSchema = WSBaseMessageSchema.extend({
570
854
  type: z.literal(WSMessageType.SLOT_START),
571
855
  data: SlotInfoSchema,
@@ -590,9 +874,15 @@ export const WSSubscribeSpectrumMessageSchema = WSBaseMessageSchema.extend({
590
874
  type: z.literal(WSMessageType.SUBSCRIBE_SPECTRUM),
591
875
  data: z.object({
592
876
  kind: SpectrumKindSchema.nullable(),
877
+ viewport: SpectrumViewportSchema.optional(),
593
878
  }),
594
879
  });
595
880
 
881
+ export const WSSpectrumViewportChangedMessageSchema = WSBaseMessageSchema.extend({
882
+ type: z.literal(WSMessageType.SPECTRUM_VIEWPORT_CHANGED),
883
+ data: SpectrumViewportSchema.nullable(),
884
+ });
885
+
596
886
  export const WSSpectrumSubscriptionChangedMessageSchema = WSBaseMessageSchema.extend({
597
887
  type: z.literal(WSMessageType.SPECTRUM_SUBSCRIPTION_CHANGED),
598
888
  data: z.object({
@@ -707,10 +997,13 @@ export const OperatorStatusSchema = z.object({
707
997
  isTransmitting: z.boolean(), // 是否已授予策略自动发射许可(发射开关状态)
708
998
  isInActivePTT: z.boolean().optional(), // 该操作员的音频是否正在被实际播放
709
999
  hasTransmitIntent: z.boolean().optional(), // 当前策略是否有可在合法周期发射的有效内容
1000
+ currentTransmissions: z.array(StrategyTransmissionSchema).optional(),
710
1001
  currentSlot: z.string().optional(),
711
1002
  context: z.object({
712
1003
  myCall: z.string(),
713
1004
  myGrid: z.string(),
1005
+ /** Canonical active target set; `targetCall` remains the primary compatibility field. */
1006
+ targetCalls: z.array(z.string()).optional(),
714
1007
  targetCall: z.string(),
715
1008
  targetGrid: z.string().optional(),
716
1009
  frequency: z.number().optional(),
@@ -776,8 +1069,8 @@ export const WSSetOperatorContextMessageSchema = WSBaseMessageSchema.extend({
776
1069
  targetCallsign: z.string().optional(),
777
1070
  targetGrid: z.string().optional(),
778
1071
  frequency: z.number().optional(),
779
- reportSent: z.number().optional(),
780
- reportReceived: z.number().optional(),
1072
+ reportSent: OperatorContextReportFieldSchema,
1073
+ reportReceived: OperatorContextReportFieldSchema,
781
1074
  }),
782
1075
  }),
783
1076
  });
@@ -793,6 +1086,27 @@ export const WSSetOperatorRuntimeStateMessageSchema = WSBaseMessageSchema.extend
793
1086
  }),
794
1087
  });
795
1088
 
1089
+ /** Sets one strategy-owned stream to a protocol-approved state. */
1090
+ export const WSSetOperatorStreamStateMessageSchema = WSBaseMessageSchema.extend({
1091
+ type: z.literal(WSMessageType.SET_OPERATOR_STREAM_STATE),
1092
+ data: z.object({
1093
+ operatorId: z.string(),
1094
+ streamId: z.string().min(1),
1095
+ stateId: z.string().min(1),
1096
+ expectedLifecycleEpoch: z.number().int().nonnegative(),
1097
+ }),
1098
+ });
1099
+
1100
+ export const WSInvokeOperatorStrategyActionMessageSchema = WSBaseMessageSchema.extend({
1101
+ type: z.literal(WSMessageType.INVOKE_OPERATOR_STRATEGY_ACTION),
1102
+ data: z.object({
1103
+ operatorId: z.string().min(1),
1104
+ target: StrategyActionTargetSchema,
1105
+ actionId: z.string().min(1),
1106
+ payload: z.unknown().optional(),
1107
+ }),
1108
+ });
1109
+
796
1110
  /**
797
1111
  * 设置操作员运行时槽位内容消息
798
1112
  */
@@ -965,6 +1279,8 @@ export type WSOperatorsListMessage = z.infer<typeof WSOperatorsListMessageSchema
965
1279
  export type WSOperatorStatusUpdateMessage = z.infer<typeof WSOperatorStatusUpdateMessageSchema>;
966
1280
  export type WSSetOperatorContextMessage = z.infer<typeof WSSetOperatorContextMessageSchema>;
967
1281
  export type WSSetOperatorRuntimeStateMessage = z.infer<typeof WSSetOperatorRuntimeStateMessageSchema>;
1282
+ export type WSSetOperatorStreamStateMessage = z.infer<typeof WSSetOperatorStreamStateMessageSchema>;
1283
+ export type WSInvokeOperatorStrategyActionMessage = z.infer<typeof WSInvokeOperatorStrategyActionMessageSchema>;
968
1284
  export type WSSetOperatorRuntimeSlotContentMessage = z.infer<typeof WSSetOperatorRuntimeSlotContentMessageSchema>;
969
1285
  export type WSSetOperatorTransmitCyclesMessage = z.infer<typeof WSSetOperatorTransmitCyclesMessageSchema>;
970
1286
  export type WSStartOperatorMessage = z.infer<typeof WSStartOperatorMessageSchema>;
@@ -1551,12 +1867,23 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1551
1867
  WSPongMessageSchema,
1552
1868
  // 服务端到客户端
1553
1869
  WSModeChangedMessageSchema,
1870
+ WSSubscribeImageRxMessageSchema,
1871
+ WSImageRadioStatusMessageSchema,
1872
+ WSImageRxEventMessageSchema,
1873
+ WSSstvTxStartMessageSchema,
1874
+ WSSstvTxCancelMessageSchema,
1875
+ WSSstvTxStatusMessageSchema,
1876
+ WSSstvTxCommandResultMessageSchema,
1877
+ WSFaxCalibrationSetMessageSchema,
1878
+ WSFaxCalibrationResetMessageSchema,
1879
+ WSFaxCalibrationCommandResultMessageSchema,
1554
1880
  WSSlotStartMessageSchema,
1555
1881
  WSSubWindowMessageSchema,
1556
1882
  WSSlotPackUpdatedMessageSchema,
1557
1883
  WSSpectrumCapabilitiesMessageSchema,
1558
1884
  WSSubscribeSpectrumMessageSchema,
1559
1885
  WSSpectrumSubscriptionChangedMessageSchema,
1886
+ WSSpectrumViewportChangedMessageSchema,
1560
1887
  WSSpectrumFrameMessageSchema,
1561
1888
  WSSpectrumSessionStateChangedMessageSchema,
1562
1889
  WSInvokeSpectrumControlMessageSchema,
@@ -1581,6 +1908,8 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1581
1908
  WSOperatorStatusUpdateMessageSchema,
1582
1909
  WSSetOperatorContextMessageSchema,
1583
1910
  WSSetOperatorRuntimeStateMessageSchema,
1911
+ WSSetOperatorStreamStateMessageSchema,
1912
+ WSInvokeOperatorStrategyActionMessageSchema,
1584
1913
  WSSetOperatorRuntimeSlotContentMessageSchema,
1585
1914
  WSSetOperatorTransmitCyclesMessageSchema,
1586
1915
  WSStartOperatorMessageSchema,
@@ -1679,6 +2008,7 @@ export type WSSlotPackUpdatedMessage = z.infer<typeof WSSlotPackUpdatedMessageSc
1679
2008
  export type WSSpectrumCapabilitiesMessage = z.infer<typeof WSSpectrumCapabilitiesMessageSchema>;
1680
2009
  export type WSSubscribeSpectrumMessage = z.infer<typeof WSSubscribeSpectrumMessageSchema>;
1681
2010
  export type WSSpectrumSubscriptionChangedMessage = z.infer<typeof WSSpectrumSubscriptionChangedMessageSchema>;
2011
+ export type WSSpectrumViewportChangedMessage = z.infer<typeof WSSpectrumViewportChangedMessageSchema>;
1682
2012
  export type WSSpectrumFrameMessage = z.infer<typeof WSSpectrumFrameMessageSchema>;
1683
2013
  export type WSSpectrumSessionStateChangedMessage = z.infer<typeof WSSpectrumSessionStateChangedMessageSchema>;
1684
2014
  export type WSInvokeSpectrumControlMessage = z.infer<typeof WSInvokeSpectrumControlMessageSchema>;
@@ -1696,7 +2026,9 @@ export type WSGetStatusMessage = z.infer<typeof WSGetStatusMessageSchema>;
1696
2026
 
1697
2027
  export const TransmitRequestSchema = z.object({
1698
2028
  operatorId: z.string(),
2029
+ streamId: z.string().min(1).optional(),
1699
2030
  transmission: z.string(),
2031
+ audioFrequencyHz: z.number().min(0).max(5000).optional(),
1700
2032
  /** 是否覆盖同一操作员在同一时隙的现有 TX 帧(自动重决策时为 true) */
1701
2033
  replaceExisting: z.boolean().optional(),
1702
2034
  source: z.enum(['standard-qso', 'plugin', 'late-decode', 'operator-edit', 'manual']).optional(),
@@ -1707,6 +2039,20 @@ export const TransmitRequestSchema = z.object({
1707
2039
 
1708
2040
  export type TransmitRequest = z.infer<typeof TransmitRequestSchema>;
1709
2041
 
2042
+ export const TransmitBatchRequestSchema = z.object({
2043
+ operatorId: z.string(),
2044
+ transmissions: z.array(z.object({
2045
+ streamId: z.string().min(1),
2046
+ transmission: z.string(),
2047
+ audioFrequencyHz: z.number().min(0).max(5000),
2048
+ })).max(5),
2049
+ replaceExisting: z.boolean().optional(),
2050
+ source: z.enum(['standard-qso', 'plugin', 'late-decode', 'operator-edit', 'manual']).optional(),
2051
+ reason: z.string().optional(),
2052
+ decisionEpoch: z.number().int().nonnegative().optional(),
2053
+ });
2054
+ export type TransmitBatchRequest = z.infer<typeof TransmitBatchRequestSchema>;
2055
+
1710
2056
  // ===== 前端应用事件接口 =====
1711
2057
 
1712
2058
  /**
@@ -1714,6 +2060,8 @@ export type TransmitRequest = z.infer<typeof TransmitRequestSchema>;
1714
2060
  */
1715
2061
  export const TransmissionCompleteInfoSchema = z.object({
1716
2062
  operatorId: z.string(),
2063
+ streamId: z.string().optional(),
2064
+ trackId: z.string().optional(),
1717
2065
  success: z.boolean(),
1718
2066
  frameId: z.string().optional(),
1719
2067
  physicalConfirmed: z.boolean().optional(),
@@ -1747,9 +2095,11 @@ export interface DigitalRadioEngineEvents {
1747
2095
 
1748
2096
  // 发射相关事件
1749
2097
  requestTransmit: (request: TransmitRequest) => void;
2098
+ requestTransmitBatch: (request: TransmitBatchRequest) => void;
1750
2099
  transmissionComplete: (info: TransmissionCompleteInfo) => void;
1751
2100
  transmissionLog: (data: {
1752
2101
  operatorId: string;
2102
+ streamId: string;
1753
2103
  frameId: string;
1754
2104
  revision: number;
1755
2105
  playbackGeneration: number;
@@ -1791,6 +2141,11 @@ export interface DigitalRadioEngineEvents {
1791
2141
  // PTT状态控制事件
1792
2142
  pttStatusChanged: (data: PTTStatus) => void;
1793
2143
  tuneToneStatusChanged: (data: TuneToneStatus) => void;
2144
+ imageRadioStatus: (data: ImageRadioStatus) => void;
2145
+ imageRxEvent: (data: ImageRxEvent) => void;
2146
+ sstvTxStatus: (data: SstvTxStatus) => void;
2147
+ sstvTxCommandResult: (data: SstvTxCommandResult) => void;
2148
+ faxCalibrationCommandResult: (data: FaxCalibrationCommandResult) => void;
1794
2149
 
1795
2150
  // 电台数值表事件
1796
2151
  meterData: (data: MeterData) => void;
@@ -1824,7 +2179,7 @@ export interface DigitalRadioEngineEvents {
1824
2179
 
1825
2180
  // Profile 管理事件
1826
2181
  profileChanged: (data: z.infer<typeof ProfileChangedEventSchema>) => void;
1827
- profileListUpdated: (data: { profiles: z.infer<typeof RadioProfileSchema>[]; activeProfileId: string | null }) => void;
2182
+ profileListUpdated: (data: { profiles: z.infer<typeof RadioProfileSchema>[]; activeProfileId: string | null; hasConfiguredProfiles?: boolean }) => void;
1828
2183
  realtimeSettingsChanged: (data: RealtimeSettingsResponseData) => void;
1829
2184
 
1830
2185
  // 认证事件
@@ -1890,10 +2245,18 @@ export interface DigitalRadioEngineEvents {
1890
2245
  transmitStart: (slotInfo: z.infer<typeof SlotInfoSchema>) => void;
1891
2246
  timingWarning: (data: { title: string; text: string }) => void;
1892
2247
  operatorSlotChanged: (data: { operatorId: string; slot: string }) => void;
2248
+ operatorStreamStateChanged: (data: {
2249
+ operatorId: string;
2250
+ streamId: string;
2251
+ state: string;
2252
+ commandEpoch?: number;
2253
+ source?: 'manual' | 'plugin' | 'late-decode' | 'slot-auto';
2254
+ }) => void;
1893
2255
  operatorSlotContentChanged: (data: { operatorId: string; slot: string; content: string }) => void;
1894
2256
  operatorFrequencyChanged: (data: { operatorId: string; frequency: number }) => void;
1895
2257
  operatorTransmitCyclesChanged: (data: {
1896
2258
  operatorId: string;
2259
+ previousTransmitCycles?: number[];
1897
2260
  transmitCycles: number[];
1898
2261
  commandEpoch?: number;
1899
2262
  source?: 'manual' | 'plugin' | 'late-decode' | 'slot-auto';
@@ -1901,15 +2264,23 @@ export interface DigitalRadioEngineEvents {
1901
2264
  }) => void;
1902
2265
  qsoLifecycleChanged: (data: {
1903
2266
  operatorId: string;
2267
+ streamId?: string;
1904
2268
  lifecycleEpoch: number;
1905
2269
  runtimeGeneration?: number;
1906
2270
  }) => void;
1907
2271
  recordQSO: (data: {
1908
2272
  operatorId: string;
2273
+ streamId?: string;
1909
2274
  qsoLifecycleId?: string;
1910
2275
  qsoLifecycleEpoch?: number;
1911
2276
  qsoRuntimeGeneration?: number;
1912
2277
  qsoRecord: z.infer<typeof QSORecordSchema>;
2278
+ persistencePolicy?: z.infer<typeof QSOPersistencePolicySchema>;
2279
+ destination?:
2280
+ | { kind: 'plugin-session'; sessionId: string }
2281
+ | { kind: 'plugin-session-key'; sessionKey: string };
2282
+ sourcePluginName?: string;
2283
+ metadata?: Record<string, unknown>;
1913
2284
  retryAttemptId?: string;
1914
2285
  resolve?: (record: z.infer<typeof QSORecordSchema>) => void;
1915
2286
  reject?: (error: unknown) => void;