@tx5dr/contracts 1.7.2 → 1.7.4

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 (142) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +7 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/schema/__tests__/ability.test.d.ts +2 -0
  6. package/dist/schema/__tests__/ability.test.d.ts.map +1 -0
  7. package/dist/schema/__tests__/ability.test.js +32 -0
  8. package/dist/schema/__tests__/ability.test.js.map +1 -0
  9. package/dist/schema/__tests__/audio.schema.test.js +4 -0
  10. package/dist/schema/__tests__/audio.schema.test.js.map +1 -1
  11. package/dist/schema/__tests__/cw-decoder.schema.test.d.ts +2 -0
  12. package/dist/schema/__tests__/cw-decoder.schema.test.d.ts.map +1 -0
  13. package/dist/schema/__tests__/cw-decoder.schema.test.js +114 -0
  14. package/dist/schema/__tests__/cw-decoder.schema.test.js.map +1 -0
  15. package/dist/schema/__tests__/device-ui.schema.test.d.ts +2 -0
  16. package/dist/schema/__tests__/device-ui.schema.test.d.ts.map +1 -0
  17. package/dist/schema/__tests__/device-ui.schema.test.js +111 -0
  18. package/dist/schema/__tests__/device-ui.schema.test.js.map +1 -0
  19. package/dist/schema/__tests__/operator.schema.test.d.ts +2 -0
  20. package/dist/schema/__tests__/operator.schema.test.d.ts.map +1 -0
  21. package/dist/schema/__tests__/operator.schema.test.js +23 -0
  22. package/dist/schema/__tests__/operator.schema.test.js.map +1 -0
  23. package/dist/schema/__tests__/process-monitor.schema.test.d.ts +2 -0
  24. package/dist/schema/__tests__/process-monitor.schema.test.d.ts.map +1 -0
  25. package/dist/schema/__tests__/process-monitor.schema.test.js +235 -0
  26. package/dist/schema/__tests__/process-monitor.schema.test.js.map +1 -0
  27. package/dist/schema/__tests__/radio.schema.test.d.ts +2 -0
  28. package/dist/schema/__tests__/radio.schema.test.d.ts.map +1 -0
  29. package/dist/schema/__tests__/radio.schema.test.js +143 -0
  30. package/dist/schema/__tests__/radio.schema.test.js.map +1 -0
  31. package/dist/schema/ability.d.ts +3 -1
  32. package/dist/schema/ability.d.ts.map +1 -1
  33. package/dist/schema/ability.js +11 -0
  34. package/dist/schema/ability.js.map +1 -1
  35. package/dist/schema/api-response.schema.d.ts +170 -3
  36. package/dist/schema/api-response.schema.d.ts.map +1 -1
  37. package/dist/schema/api-response.schema.js +22 -0
  38. package/dist/schema/api-response.schema.js.map +1 -1
  39. package/dist/schema/audio.schema.d.ts +369 -0
  40. package/dist/schema/audio.schema.d.ts.map +1 -1
  41. package/dist/schema/audio.schema.js +3 -0
  42. package/dist/schema/audio.schema.js.map +1 -1
  43. package/dist/schema/cpu-profile.schema.d.ts +6 -6
  44. package/dist/schema/cw-decoder.schema.d.ts +2501 -0
  45. package/dist/schema/cw-decoder.schema.d.ts.map +1 -0
  46. package/dist/schema/cw-decoder.schema.js +196 -0
  47. package/dist/schema/cw-decoder.schema.js.map +1 -0
  48. package/dist/schema/cw-keyer.schema.d.ts +368 -0
  49. package/dist/schema/cw-keyer.schema.d.ts.map +1 -0
  50. package/dist/schema/cw-keyer.schema.js +143 -0
  51. package/dist/schema/cw-keyer.schema.js.map +1 -0
  52. package/dist/schema/desktop-https.schema.js +1 -1
  53. package/dist/schema/desktop-https.schema.js.map +1 -1
  54. package/dist/schema/device-ui.schema.d.ts +1782 -0
  55. package/dist/schema/device-ui.schema.d.ts.map +1 -0
  56. package/dist/schema/device-ui.schema.js +156 -0
  57. package/dist/schema/device-ui.schema.js.map +1 -0
  58. package/dist/schema/logbook.schema.d.ts +2 -2
  59. package/dist/schema/mode.schema.d.ts +1 -0
  60. package/dist/schema/mode.schema.d.ts.map +1 -1
  61. package/dist/schema/mode.schema.js +8 -0
  62. package/dist/schema/mode.schema.js.map +1 -1
  63. package/dist/schema/operator.schema.d.ts +10 -10
  64. package/dist/schema/operator.schema.d.ts.map +1 -1
  65. package/dist/schema/operator.schema.js +6 -1
  66. package/dist/schema/operator.schema.js.map +1 -1
  67. package/dist/schema/plugin.schema.d.ts +653 -70
  68. package/dist/schema/plugin.schema.d.ts.map +1 -1
  69. package/dist/schema/plugin.schema.js +22 -2
  70. package/dist/schema/plugin.schema.js.map +1 -1
  71. package/dist/schema/process-monitor.schema.d.ts +3102 -42
  72. package/dist/schema/process-monitor.schema.d.ts.map +1 -1
  73. package/dist/schema/process-monitor.schema.js +54 -0
  74. package/dist/schema/process-monitor.schema.js.map +1 -1
  75. package/dist/schema/radio-capability.schema.d.ts +7 -7
  76. package/dist/schema/radio-capability.schema.d.ts.map +1 -1
  77. package/dist/schema/radio-capability.schema.js +41 -0
  78. package/dist/schema/radio-capability.schema.js.map +1 -1
  79. package/dist/schema/radio-power.schema.d.ts +6 -0
  80. package/dist/schema/radio-power.schema.d.ts.map +1 -1
  81. package/dist/schema/radio-profile.schema.d.ts +86 -0
  82. package/dist/schema/radio-profile.schema.d.ts.map +1 -1
  83. package/dist/schema/radio.schema.d.ts +166 -9
  84. package/dist/schema/radio.schema.d.ts.map +1 -1
  85. package/dist/schema/radio.schema.js +82 -0
  86. package/dist/schema/radio.schema.js.map +1 -1
  87. package/dist/schema/realtime.schema.d.ts +6 -6
  88. package/dist/schema/slot-info.schema.d.ts +136 -0
  89. package/dist/schema/slot-info.schema.d.ts.map +1 -1
  90. package/dist/schema/slot-info.schema.js +27 -2
  91. package/dist/schema/slot-info.schema.js.map +1 -1
  92. package/dist/schema/spectrum.schema.d.ts +24 -24
  93. package/dist/schema/voice-keyer.schema.d.ts +2 -2
  94. package/dist/schema/voice.schema.d.ts +1 -1
  95. package/dist/schema/voice.schema.d.ts.map +1 -1
  96. package/dist/schema/voice.schema.js +1 -1
  97. package/dist/schema/voice.schema.js.map +1 -1
  98. package/dist/schema/websocket.schema.d.ts +6066 -639
  99. package/dist/schema/websocket.schema.d.ts.map +1 -1
  100. package/dist/schema/websocket.schema.js +106 -4
  101. package/dist/schema/websocket.schema.js.map +1 -1
  102. package/dist/utils/__tests__/cwTiming.test.d.ts +2 -0
  103. package/dist/utils/__tests__/cwTiming.test.d.ts.map +1 -0
  104. package/dist/utils/__tests__/cwTiming.test.js +74 -0
  105. package/dist/utils/__tests__/cwTiming.test.js.map +1 -0
  106. package/dist/utils/callsign.d.ts +2 -0
  107. package/dist/utils/callsign.d.ts.map +1 -0
  108. package/dist/utils/callsign.js +8 -0
  109. package/dist/utils/callsign.js.map +1 -0
  110. package/dist/utils/cwTiming.d.ts +36 -0
  111. package/dist/utils/cwTiming.d.ts.map +1 -0
  112. package/dist/utils/cwTiming.js +155 -0
  113. package/dist/utils/cwTiming.js.map +1 -0
  114. package/package.json +1 -1
  115. package/src/index.ts +9 -0
  116. package/src/schema/__tests__/ability.test.ts +40 -0
  117. package/src/schema/__tests__/audio.schema.test.ts +4 -0
  118. package/src/schema/__tests__/cw-decoder.schema.test.ts +127 -0
  119. package/src/schema/__tests__/device-ui.schema.test.ts +122 -0
  120. package/src/schema/__tests__/operator.schema.test.ts +29 -0
  121. package/src/schema/__tests__/process-monitor.schema.test.ts +245 -0
  122. package/src/schema/__tests__/radio.schema.test.ts +160 -0
  123. package/src/schema/ability.ts +13 -0
  124. package/src/schema/api-response.schema.ts +22 -0
  125. package/src/schema/audio.schema.ts +3 -0
  126. package/src/schema/cw-decoder.schema.ts +234 -0
  127. package/src/schema/cw-keyer.schema.ts +177 -0
  128. package/src/schema/desktop-https.schema.ts +1 -1
  129. package/src/schema/device-ui.schema.ts +182 -0
  130. package/src/schema/mode.schema.ts +8 -0
  131. package/src/schema/operator.schema.ts +7 -1
  132. package/src/schema/plugin.schema.ts +28 -2
  133. package/src/schema/process-monitor.schema.ts +69 -0
  134. package/src/schema/radio-capability.schema.ts +41 -0
  135. package/src/schema/radio.schema.ts +97 -0
  136. package/src/schema/slot-info.schema.ts +33 -2
  137. package/src/schema/voice.schema.ts +1 -1
  138. package/src/schema/websocket.schema.ts +147 -4
  139. package/src/utils/__tests__/cwTiming.test.ts +91 -0
  140. package/src/utils/callsign.ts +9 -0
  141. package/src/utils/cwTiming.ts +203 -0
  142. package/test/decode-request-schema.test.ts +48 -0
@@ -28,6 +28,65 @@ export const EventLoopDelaySchema = z.object({
28
28
  p99: z.number(),
29
29
  });
30
30
 
31
+ export const DecodeWorkerCurrentJobSchema = z.object({
32
+ jobId: z.number(),
33
+ slotId: z.string(),
34
+ windowIdx: z.number(),
35
+ mode: z.string(),
36
+ startedAt: z.number(),
37
+ elapsedMs: z.number(),
38
+ requestAudioDurationMs: z.number().optional(),
39
+ });
40
+
41
+ export const DecodeWorkerTelemetryWorkerSchema = z.object({
42
+ workerId: z.number(),
43
+ pid: z.number().optional(),
44
+ ready: z.boolean(),
45
+ busy: z.boolean(),
46
+ nativeThreads: z.number().int().positive(),
47
+ uptimeSeconds: z.number(),
48
+ memory: ProcessMemorySchema,
49
+ cpu: ProcessCpuSchema,
50
+ currentJob: DecodeWorkerCurrentJobSchema.optional(),
51
+ lastSeenAt: z.number(),
52
+ });
53
+
54
+ export const DecodeWorkerTelemetrySummarySchema = z.object({
55
+ status: z.enum(['stopped', 'starting', 'ready', 'degraded', 'unavailable']).optional(),
56
+ workerCount: z.number().int().nonnegative(),
57
+ desiredWorkers: z.number().int().nonnegative().optional(),
58
+ readyCount: z.number().int().nonnegative(),
59
+ busyCount: z.number().int().nonnegative(),
60
+ totalRss: z.number().nonnegative(),
61
+ totalCpu: z.number().nonnegative(),
62
+ nativeThreadsPerWorker: z.number().int().positive(),
63
+ pendingJobs: z.number().int().nonnegative(),
64
+ activeJobs: z.number().int().nonnegative(),
65
+ lastError: z.string().optional(),
66
+ lastFailureAt: z.number().optional(),
67
+ restartAttempts: z.number().int().nonnegative().optional(),
68
+ workerEntry: z.string().optional(),
69
+ workerMode: z.enum(['development', 'production']).optional(),
70
+ });
71
+
72
+ export const DecodeWorkerTelemetrySnapshotSchema = z.object({
73
+ summary: DecodeWorkerTelemetrySummarySchema,
74
+ workers: z.array(DecodeWorkerTelemetryWorkerSchema),
75
+ });
76
+
77
+ export const WorkerPoolTelemetryWorkerSchema = DecodeWorkerTelemetryWorkerSchema;
78
+ export const WorkerPoolTelemetrySummarySchema = DecodeWorkerTelemetrySummarySchema;
79
+
80
+ export const WorkerPoolTelemetrySnapshotSchema = z.object({
81
+ id: z.string().min(1),
82
+ name: z.string().min(1),
83
+ kind: z.string().min(1),
84
+ summary: WorkerPoolTelemetrySummarySchema,
85
+ workers: z.array(WorkerPoolTelemetryWorkerSchema),
86
+ });
87
+
88
+ export const WorkerPoolTelemetryListSchema = z.array(WorkerPoolTelemetrySnapshotSchema);
89
+
31
90
  export const ProcessSnapshotSchema = z.object({
32
91
  timestamp: z.number(),
33
92
  uptimeSeconds: z.number(),
@@ -35,6 +94,8 @@ export const ProcessSnapshotSchema = z.object({
35
94
  cpu: ProcessCpuSchema,
36
95
  hostCpu: HostCpuSchema.optional(),
37
96
  eventLoop: EventLoopDelaySchema,
97
+ decodeWorkers: DecodeWorkerTelemetrySnapshotSchema.optional(),
98
+ workerPools: WorkerPoolTelemetryListSchema.optional(),
38
99
  });
39
100
 
40
101
  export const ProcessSnapshotHistorySchema = z.object({
@@ -47,5 +108,13 @@ export type ProcessMemory = z.infer<typeof ProcessMemorySchema>;
47
108
  export type ProcessCpu = z.infer<typeof ProcessCpuSchema>;
48
109
  export type HostCpu = z.infer<typeof HostCpuSchema>;
49
110
  export type EventLoopDelay = z.infer<typeof EventLoopDelaySchema>;
111
+ export type DecodeWorkerCurrentJob = z.infer<typeof DecodeWorkerCurrentJobSchema>;
112
+ export type DecodeWorkerTelemetryWorker = z.infer<typeof DecodeWorkerTelemetryWorkerSchema>;
113
+ export type DecodeWorkerTelemetrySummary = z.infer<typeof DecodeWorkerTelemetrySummarySchema>;
114
+ export type DecodeWorkerTelemetrySnapshot = z.infer<typeof DecodeWorkerTelemetrySnapshotSchema>;
115
+ export type WorkerPoolTelemetryWorker = z.infer<typeof WorkerPoolTelemetryWorkerSchema>;
116
+ export type WorkerPoolTelemetrySummary = z.infer<typeof WorkerPoolTelemetrySummarySchema>;
117
+ export type WorkerPoolTelemetrySnapshot = z.infer<typeof WorkerPoolTelemetrySnapshotSchema>;
118
+ export type WorkerPoolTelemetryList = z.infer<typeof WorkerPoolTelemetryListSchema>;
50
119
  export type ProcessSnapshot = z.infer<typeof ProcessSnapshotSchema>;
51
120
  export type ProcessSnapshotHistory = z.infer<typeof ProcessSnapshotHistorySchema>;
@@ -247,21 +247,62 @@ export const CAPABILITY_IDS = [
247
247
  'compressor',
248
248
  'compressor_level',
249
249
  'monitor_gain',
250
+ 'monitor_enabled',
251
+ 'apf_enabled',
252
+ 'apf_level',
250
253
  'nb',
254
+ 'nb_level',
251
255
  'nr',
256
+ 'nr_level',
257
+ 'rf_gain',
258
+ 'if_shift',
259
+ 'pbt_in',
260
+ 'pbt_out',
261
+ 'cw_pitch',
262
+ 'key_speed',
263
+ 'notch_raw',
264
+ 'agc_time',
265
+ 'balance',
266
+ 'drive_gain',
267
+ 'digi_sel_enabled',
268
+ 'digi_sel_level',
252
269
  'lock_mode',
253
270
  'mute',
254
271
  'vox',
272
+ 'vox_gain',
273
+ 'anti_vox',
274
+ 'vox_delay',
275
+ 'break_in_delay',
276
+ 'auto_notch',
277
+ 'manual_notch',
278
+ 'rit_enabled',
279
+ 'xit_enabled',
280
+ 'tone_enabled',
281
+ 'tone_squelch_enabled',
282
+ 'beep_enabled',
283
+ 'break_in_mode',
255
284
  'agc_mode',
256
285
  'preamp',
257
286
  'attenuator',
258
287
  'mode_bandwidth',
288
+ 'split_enabled',
289
+ 'vfo_select',
290
+ 'audio_if_mode',
259
291
  'rit_offset',
260
292
  'xit_offset',
261
293
  'tuning_step',
262
294
  'repeater_shift',
263
295
  'repeater_offset',
264
296
  'ctcss_tone',
297
+ 'spectrum_data_output',
298
+ 'spectrum_hold',
299
+ 'spectrum_speed',
300
+ 'spectrum_ref',
301
+ 'spectrum_average',
302
+ 'spectrum_vbw',
303
+ 'spectrum_rbw',
304
+ 'spectrum_during_tx',
305
+ 'spectrum_center_type',
265
306
  'dcs_code',
266
307
  ] as const;
267
308
 
@@ -1,5 +1,15 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ /**
4
+ * 中继差频方向
5
+ */
6
+ export const RepeaterShiftSchema = z.enum(['none', 'minus', 'plus']);
7
+
8
+ /**
9
+ * 哑音类型
10
+ */
11
+ export const ToneSquelchModeSchema = z.enum(['none', 'ctcss', 'dcs']);
12
+
3
13
  /**
4
14
  * 预设频率Schema
5
15
  */
@@ -9,6 +19,85 @@ export const PresetFrequencySchema = z.object({
9
19
  radioMode: z.string().optional(), // 电台调制模式,如 USB, LSB, AM, FM
10
20
  frequency: z.number(),
11
21
  description: z.string().optional(),
22
+ repeaterShift: RepeaterShiftSchema.optional(), // 中继差频方向,默认 none
23
+ repeaterOffsetHz: z.number().int().positive().optional(), // 中继偏移,单位 Hz
24
+ toneMode: ToneSquelchModeSchema.optional(), // 哑音类型,默认 none
25
+ ctcssToneTenthsHz: z.number().int().positive().optional(), // CTCSS,单位 0.1Hz
26
+ dcsCode: z.number().int().positive().optional(), // DCS 码
27
+ }).superRefine((preset, ctx) => {
28
+ const isVoiceFmPreset = preset.mode === 'VOICE' && preset.radioMode?.toUpperCase() === 'FM';
29
+ const hasRepeaterDuplex = preset.repeaterShift === 'minus' || preset.repeaterShift === 'plus';
30
+
31
+ if (hasRepeaterDuplex && !isVoiceFmPreset) {
32
+ ctx.addIssue({
33
+ code: z.ZodIssueCode.custom,
34
+ path: ['repeaterShift'],
35
+ message: 'repeater duplex is only supported for VOICE FM presets',
36
+ });
37
+ }
38
+
39
+ if (
40
+ hasRepeaterDuplex
41
+ && preset.repeaterOffsetHz === undefined
42
+ ) {
43
+ ctx.addIssue({
44
+ code: z.ZodIssueCode.custom,
45
+ path: ['repeaterOffsetHz'],
46
+ message: 'repeaterOffsetHz is required when repeaterShift is plus or minus',
47
+ });
48
+ }
49
+
50
+ if (!isVoiceFmPreset && preset.toneMode && preset.toneMode !== 'none') {
51
+ ctx.addIssue({
52
+ code: z.ZodIssueCode.custom,
53
+ path: ['toneMode'],
54
+ message: 'tone squelch is only supported for VOICE FM presets',
55
+ });
56
+ }
57
+
58
+ if (preset.toneMode === 'ctcss') {
59
+ if (preset.ctcssToneTenthsHz === undefined) {
60
+ ctx.addIssue({
61
+ code: z.ZodIssueCode.custom,
62
+ path: ['ctcssToneTenthsHz'],
63
+ message: 'ctcssToneTenthsHz is required when toneMode is ctcss',
64
+ });
65
+ }
66
+ if (preset.dcsCode !== undefined) {
67
+ ctx.addIssue({
68
+ code: z.ZodIssueCode.custom,
69
+ path: ['dcsCode'],
70
+ message: 'dcsCode cannot be set when toneMode is ctcss',
71
+ });
72
+ }
73
+ }
74
+
75
+ if (preset.toneMode === 'dcs') {
76
+ if (preset.dcsCode === undefined) {
77
+ ctx.addIssue({
78
+ code: z.ZodIssueCode.custom,
79
+ path: ['dcsCode'],
80
+ message: 'dcsCode is required when toneMode is dcs',
81
+ });
82
+ }
83
+ if (preset.ctcssToneTenthsHz !== undefined) {
84
+ ctx.addIssue({
85
+ code: z.ZodIssueCode.custom,
86
+ path: ['ctcssToneTenthsHz'],
87
+ message: 'ctcssToneTenthsHz cannot be set when toneMode is dcs',
88
+ });
89
+ }
90
+ }
91
+
92
+ if ((preset.toneMode === undefined || preset.toneMode === 'none')
93
+ && (preset.ctcssToneTenthsHz !== undefined || preset.dcsCode !== undefined)
94
+ ) {
95
+ ctx.addIssue({
96
+ code: z.ZodIssueCode.custom,
97
+ path: ['toneMode'],
98
+ message: 'toneMode must be ctcss or dcs when tone values are set',
99
+ });
100
+ }
12
101
  });
13
102
 
14
103
  /**
@@ -163,6 +252,12 @@ export const HamlibConfigSchema = z.object({
163
252
  // PTT 独立串口路径(仅当 pttMethod 为 dtr/rts 时有效)
164
253
  // 留空则复用 CAT 同一串口
165
254
  pttPort: z.string().optional(),
255
+
256
+ // CW 键控串口路径(用于 DTR/RTS 引脚驱动电台 CW KEY 输入)
257
+ cwKeyPort: z.string().optional(),
258
+
259
+ // CW 键控引脚类型(dtr 或 rts)
260
+ cwKeyMethod: z.enum(['dtr', 'rts']).optional(),
166
261
  });
167
262
 
168
263
  /**
@@ -357,6 +452,8 @@ export const CustomFrequencyPresetsSchema = z.object({
357
452
  // 导出类型
358
453
  export type CustomFrequencyPresets = z.infer<typeof CustomFrequencyPresetsSchema>;
359
454
  export type PresetFrequency = z.infer<typeof PresetFrequencySchema>;
455
+ export type RepeaterShift = z.infer<typeof RepeaterShiftSchema>;
456
+ export type ToneSquelchMode = z.infer<typeof ToneSquelchModeSchema>;
360
457
  export type FrequencyListResponse = z.infer<typeof FrequencyListResponseSchema>;
361
458
  export type SerialConfig = z.infer<typeof SerialConfigSchema>;
362
459
  export type HamlibBackendConfig = z.infer<typeof HamlibBackendConfigSchema>;
@@ -1,6 +1,20 @@
1
1
  import { z } from 'zod';
2
2
  import { DxccStatusSchema, SubdivisionConfidenceSchema } from './qso.schema.js';
3
3
 
4
+ /**
5
+ * Frequency context captured when a slot pack is created.
6
+ * Fields are optional so older persisted slot packs remain valid.
7
+ */
8
+ export const SlotPackFrequencyContextSchema = z.object({
9
+ frequency: z.number().optional(),
10
+ mode: z.string().optional(),
11
+ band: z.string().optional(),
12
+ radioMode: z.string().optional(),
13
+ description: z.string().optional(),
14
+ });
15
+
16
+ export type SlotPackFrequencyContext = z.infer<typeof SlotPackFrequencyContextSchema>;
17
+
4
18
  /**
5
19
  * 时隙周期(偶数奇数)
6
20
  */
@@ -83,6 +97,19 @@ export const FrameMessageSchema = z.object({
83
97
 
84
98
  export type FrameMessage = z.infer<typeof FrameMessageSchema>;
85
99
 
100
+ export const DecodeApContextSchema = z.object({
101
+ operatorId: z.string(),
102
+ myCall: z.string(),
103
+ myGrid: z.string().optional(),
104
+ dxCall: z.string(),
105
+ dxGrid: z.string().optional(),
106
+ frequencyHz: z.number(),
107
+ qsoProgress: z.number(),
108
+ currentSlot: z.string(),
109
+ });
110
+
111
+ export type DecodeApContext = z.infer<typeof DecodeApContextSchema>;
112
+
86
113
  /**
87
114
  * 解码请求,用于SlotScheduler内部
88
115
  */
@@ -100,7 +127,9 @@ export const DecodeRequestSchema = z.object({
100
127
  /** 请求时间戳 */
101
128
  timestamp: z.number().default(() => Date.now()),
102
129
  /** 窗口时间偏移(毫秒) */
103
- windowOffsetMs: z.number().default(0)
130
+ windowOffsetMs: z.number().default(0),
131
+ /** 可选的单操作员 AP decode 上下文 */
132
+ apContext: DecodeApContextSchema.optional()
104
133
  });
105
134
 
106
135
  export type DecodeRequest = z.infer<typeof DecodeRequestSchema>;
@@ -160,7 +189,9 @@ export const SlotPackSchema = z.object({
160
189
  timestamp: z.number(),
161
190
  frameCount: z.number(),
162
191
  processingTimeMs: z.number()
163
- })).default([])
192
+ })).default([]),
193
+ /** Dial/band context active when this slot was created. */
194
+ frequencyContext: SlotPackFrequencyContextSchema.optional()
164
195
  });
165
196
 
166
197
  export type SlotPack = z.infer<typeof SlotPackSchema>;
@@ -24,5 +24,5 @@ export type VoicePTTLock = z.infer<typeof VoicePTTLockSchema>;
24
24
  * digital: FT8/FT4 等数字模式
25
25
  * voice: 语音通联模式(SSB/FM/AM)
26
26
  */
27
- export const EngineModeSchema = z.enum(['digital', 'voice']);
27
+ export const EngineModeSchema = z.enum(['digital', 'voice', 'cw']);
28
28
  export type EngineMode = z.infer<typeof EngineModeSchema>;
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { FT8DecodeSchema } from './ft8.schema.js';
3
- import { SlotPackSchema, SlotInfoSchema } from './slot-info.schema.js';
3
+ import { SlotPackSchema, SlotInfoSchema, SlotPackFrequencyContextSchema } from './slot-info.schema.js';
4
4
  import {
5
5
  PluginDataPayloadSchema,
6
6
  PluginLogEntrySchema,
@@ -27,6 +27,8 @@ import { RadioProfileSchema, ProfileChangedEventSchema } from './radio-profile.s
27
27
  import { UserRole } from './auth.schema.js';
28
28
  import type { VoicePTTLock } from './voice.schema.js';
29
29
  import type { VoiceKeyerStatus } from './voice-keyer.schema.js';
30
+ import type { CWKeyerStatus, CWKeyerConfig } from './cw-keyer.schema.js';
31
+ import { CWDecoderEventSchema, CWDecoderStatusSchema, type CWDecoderEvent, type CWDecoderStatus } from './cw-decoder.schema.js';
30
32
  import { CapabilityListSchema, CapabilityStateSchema, WriteCapabilityPayloadSchema } from './radio-capability.schema.js';
31
33
  import { RadioPowerStateEventSchema } from './radio-power.schema.js';
32
34
  import { AudioSidecarStatusPayloadSchema } from './audio-sidecar.schema.js';
@@ -56,11 +58,13 @@ export enum WSMessageType {
56
58
  SLOT_PACK_UPDATED = 'slotPackUpdated',
57
59
  SPECTRUM_CAPABILITIES = 'spectrumCapabilities',
58
60
  SUBSCRIBE_SPECTRUM = 'subscribeSpectrum',
61
+ SPECTRUM_SUBSCRIPTION_CHANGED = 'spectrumSubscriptionChanged',
59
62
  SPECTRUM_FRAME = 'spectrumFrame',
60
63
  SPECTRUM_SESSION_STATE_CHANGED = 'spectrumSessionStateChanged',
61
64
  INVOKE_SPECTRUM_CONTROL = 'invokeSpectrumControl',
62
65
  DECODE_ERROR = 'decodeError',
63
66
  SYSTEM_STATUS = 'systemStatus',
67
+ BOOTSTRAP_STATUS_CHANGED = 'bootstrapStatusChanged',
64
68
  CLIENT_COUNT_CHANGED = 'clientCountChanged',
65
69
  CLOCK_STATUS_CHANGED = 'clockStatusChanged',
66
70
  RIGCTLD_STATUS = 'rigctldStatus',
@@ -175,6 +179,24 @@ export enum WSMessageType {
175
179
  VOICE_KEYER_STOP = 'voiceKeyerStop',
176
180
  VOICE_KEYER_STATUS_CHANGED = 'voiceKeyerStatusChanged',
177
181
 
182
+ // ===== CW 模式 =====
183
+ /** CW 键控器状态变化(server → client) */
184
+ CW_KEYER_STATUS = 'cwKeyerStatus',
185
+ /** CW 手键动作(client → server) */
186
+ CW_KEY_ACTION = 'cwKeyAction',
187
+ /** CW 文字输入(client → server) */
188
+ CW_TEXT_INPUT = 'cwTextInput',
189
+ /** CW 播放预设报文(client → server) */
190
+ CW_PLAY_MESSAGE = 'cwPlayMessage',
191
+ /** CW 停止播放(client → server) */
192
+ CW_STOP_MESSAGE = 'cwStopMessage',
193
+ /** CW 配置变更通知(server → client) */
194
+ CW_CONFIG_CHANGED = 'cwConfigChanged',
195
+ /** CW 解码器状态快照(server → client) */
196
+ CW_DECODER_STATUS = 'cwDecoderStatus',
197
+ /** CW 解码器增量事件(server → client) */
198
+ CW_DECODER_EVENT = 'cwDecoderEvent',
199
+
178
200
  // ===== 进程监控 =====
179
201
  PROCESS_SNAPSHOT = 'processSnapshot',
180
202
  PROCESS_SNAPSHOT_HISTORY = 'processSnapshotHistory',
@@ -215,8 +237,8 @@ export const SystemStatusSchema = z.object({
215
237
  radioConnectionHealth: z.object({
216
238
  connectionHealthy: z.boolean(),
217
239
  }).optional(),
218
- /** 引擎模式:digital(FT8/FT4)或 voice(语音通联) */
219
- engineMode: z.enum(['digital', 'voice']).default('digital'),
240
+ /** 引擎模式:digital(FT8/FT4)、voice(语音通联)或 cw(莫尔斯码) */
241
+ engineMode: z.enum(['digital', 'voice', 'cw']).default('digital'),
220
242
  /** 当前电台调制模式(语音模式下使用,如 USB/LSB/FM/AM) */
221
243
  currentRadioMode: z.string().optional(),
222
244
  /** 引擎状态机当前状态 */
@@ -229,6 +251,62 @@ export const SystemStatusSchema = z.object({
229
251
  }).nullable().optional(),
230
252
  });
231
253
 
254
+ export const BootstrapLifecycleSchema = z.enum(['booting', 'completed', 'degraded', 'failed', 'dismissed']);
255
+ export const BootstrapPhaseStateSchema = z.enum([
256
+ 'pending',
257
+ 'running',
258
+ 'ready',
259
+ 'skipped',
260
+ 'warning',
261
+ 'failed',
262
+ 'timed_out',
263
+ ]);
264
+ export const BootstrapPhaseIdSchema = z.enum([
265
+ 'config-auth',
266
+ 'core-http',
267
+ 'engine-bootstrap',
268
+ 'audio-device-discovery',
269
+ 'logbook-prewarm',
270
+ 'plugin-bootstrap',
271
+ 'ntp-initial-check',
272
+ 'active-profile-autostart',
273
+ ]);
274
+
275
+ export const BootstrapPhaseStatusSchema = z.object({
276
+ id: BootstrapPhaseIdSchema,
277
+ label: z.string(),
278
+ description: z.string().optional(),
279
+ state: BootstrapPhaseStateSchema,
280
+ startedAt: z.number().optional(),
281
+ completedAt: z.number().optional(),
282
+ updatedAt: z.number(),
283
+ durationMs: z.number().optional(),
284
+ message: z.string().optional(),
285
+ retryable: z.boolean().optional(),
286
+ userVisible: z.boolean().optional(),
287
+ });
288
+
289
+ export const BootstrapStatusSchema = z.object({
290
+ bootSessionId: z.string(),
291
+ lifecycle: BootstrapLifecycleSchema,
292
+ startedAt: z.number(),
293
+ updatedAt: z.number(),
294
+ completedAt: z.number().optional(),
295
+ durationMs: z.number().optional(),
296
+ blockingReady: z.boolean().default(false),
297
+ phases: z.array(BootstrapPhaseStatusSchema),
298
+ summary: z.object({
299
+ total: z.number(),
300
+ pending: z.number(),
301
+ running: z.number(),
302
+ ready: z.number(),
303
+ skipped: z.number(),
304
+ warning: z.number(),
305
+ failed: z.number(),
306
+ timedOut: z.number(),
307
+ }),
308
+ });
309
+
232
310
  // 子窗口信息数据结构
233
311
  export const SubWindowInfoSchema = z.object({
234
312
  slotInfo: SlotInfoSchema,
@@ -387,6 +465,11 @@ export const MeterDataSchema = z.object({
387
465
 
388
466
  // ===== 导出共享类型 =====
389
467
  export type SystemStatus = z.infer<typeof SystemStatusSchema>;
468
+ export type BootstrapLifecycle = z.infer<typeof BootstrapLifecycleSchema>;
469
+ export type BootstrapPhaseState = z.infer<typeof BootstrapPhaseStateSchema>;
470
+ export type BootstrapPhaseId = z.infer<typeof BootstrapPhaseIdSchema>;
471
+ export type BootstrapPhaseStatus = z.infer<typeof BootstrapPhaseStatusSchema>;
472
+ export type BootstrapStatus = z.infer<typeof BootstrapStatusSchema>;
390
473
  export type SubWindowInfo = z.infer<typeof SubWindowInfoSchema>;
391
474
  export type DecodeErrorInfo = z.infer<typeof DecodeErrorInfoSchema>;
392
475
  export type FrequencyState = z.infer<typeof FrequencyStateSchema>;
@@ -448,6 +531,17 @@ export const WSSubscribeSpectrumMessageSchema = WSBaseMessageSchema.extend({
448
531
  }),
449
532
  });
450
533
 
534
+ export const WSSpectrumSubscriptionChangedMessageSchema = WSBaseMessageSchema.extend({
535
+ type: z.literal(WSMessageType.SPECTRUM_SUBSCRIPTION_CHANGED),
536
+ data: z.object({
537
+ requestedKind: SpectrumKindSchema.nullable(),
538
+ effectiveKind: SpectrumKindSchema.nullable(),
539
+ ok: z.boolean(),
540
+ reason: z.string().optional(),
541
+ capabilities: SpectrumCapabilitiesSchema.optional(),
542
+ }),
543
+ });
544
+
451
545
  export const WSSpectrumFrameMessageSchema = WSBaseMessageSchema.extend({
452
546
  type: z.literal(WSMessageType.SPECTRUM_FRAME),
453
547
  data: SpectrumFrameSchema,
@@ -473,6 +567,11 @@ export const WSSystemStatusMessageSchema = WSBaseMessageSchema.extend({
473
567
  data: SystemStatusSchema,
474
568
  });
475
569
 
570
+ export const WSBootstrapStatusChangedMessageSchema = WSBaseMessageSchema.extend({
571
+ type: z.literal(WSMessageType.BOOTSTRAP_STATUS_CHANGED),
572
+ data: BootstrapStatusSchema,
573
+ });
574
+
476
575
  export const WSClientCountChangedMessageSchema = WSBaseMessageSchema.extend({
477
576
  type: z.literal(WSMessageType.CLIENT_COUNT_CHANGED),
478
577
  data: z.object({
@@ -779,7 +878,9 @@ export const WSTransmissionLogMessageSchema = WSBaseMessageSchema.extend({
779
878
  time: z.string(),
780
879
  message: z.string(),
781
880
  frequency: z.number(),
782
- slotStartMs: z.number()
881
+ slotStartMs: z.number(),
882
+ replaceExisting: z.boolean().optional(),
883
+ frequencyContext: SlotPackFrequencyContextSchema.optional(),
783
884
  }),
784
885
  });
785
886
 
@@ -1107,6 +1208,20 @@ export const WSTextMessageSchema = WSBaseMessageSchema.extend({
1107
1208
 
1108
1209
  export type WSTextMessage = z.infer<typeof WSTextMessageSchema>;
1109
1210
 
1211
+ export const WSCWDecoderStatusMessageSchema = WSBaseMessageSchema.extend({
1212
+ type: z.literal(WSMessageType.CW_DECODER_STATUS),
1213
+ data: CWDecoderStatusSchema,
1214
+ });
1215
+
1216
+ export type WSCWDecoderStatusMessage = z.infer<typeof WSCWDecoderStatusMessageSchema>;
1217
+
1218
+ export const WSCWDecoderEventMessageSchema = WSBaseMessageSchema.extend({
1219
+ type: z.literal(WSMessageType.CW_DECODER_EVENT),
1220
+ data: CWDecoderEventSchema,
1221
+ });
1222
+
1223
+ export type WSCWDecoderEventMessage = z.infer<typeof WSCWDecoderEventMessageSchema>;
1224
+
1110
1225
  // ===== 统一电台控制能力消息 =====
1111
1226
 
1112
1227
  /**
@@ -1268,11 +1383,13 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1268
1383
  WSSlotPackUpdatedMessageSchema,
1269
1384
  WSSpectrumCapabilitiesMessageSchema,
1270
1385
  WSSubscribeSpectrumMessageSchema,
1386
+ WSSpectrumSubscriptionChangedMessageSchema,
1271
1387
  WSSpectrumFrameMessageSchema,
1272
1388
  WSSpectrumSessionStateChangedMessageSchema,
1273
1389
  WSInvokeSpectrumControlMessageSchema,
1274
1390
  WSDecodeErrorMessageSchema,
1275
1391
  WSSystemStatusMessageSchema,
1392
+ WSBootstrapStatusChangedMessageSchema,
1276
1393
  WSClientCountChangedMessageSchema,
1277
1394
  WSClockStatusChangedMessageSchema,
1278
1395
  WSErrorMessageSchema,
@@ -1345,6 +1462,10 @@ export const WSMessageSchema = z.discriminatedUnion('type', [
1345
1462
  // 文本消息(Toast通知)
1346
1463
  WSTextMessageSchema,
1347
1464
 
1465
+ // CW 解码器消息
1466
+ WSCWDecoderStatusMessageSchema,
1467
+ WSCWDecoderEventMessageSchema,
1468
+
1348
1469
  // 认证消息
1349
1470
  WSAuthRequiredMessageSchema,
1350
1471
  WSAuthTokenMessageSchema,
@@ -1376,11 +1497,13 @@ export type WSSubWindowMessage = z.infer<typeof WSSubWindowMessageSchema>;
1376
1497
  export type WSSlotPackUpdatedMessage = z.infer<typeof WSSlotPackUpdatedMessageSchema>;
1377
1498
  export type WSSpectrumCapabilitiesMessage = z.infer<typeof WSSpectrumCapabilitiesMessageSchema>;
1378
1499
  export type WSSubscribeSpectrumMessage = z.infer<typeof WSSubscribeSpectrumMessageSchema>;
1500
+ export type WSSpectrumSubscriptionChangedMessage = z.infer<typeof WSSpectrumSubscriptionChangedMessageSchema>;
1379
1501
  export type WSSpectrumFrameMessage = z.infer<typeof WSSpectrumFrameMessageSchema>;
1380
1502
  export type WSSpectrumSessionStateChangedMessage = z.infer<typeof WSSpectrumSessionStateChangedMessageSchema>;
1381
1503
  export type WSInvokeSpectrumControlMessage = z.infer<typeof WSInvokeSpectrumControlMessageSchema>;
1382
1504
  export type WSDecodeErrorMessage = z.infer<typeof WSDecodeErrorMessageSchema>;
1383
1505
  export type WSSystemStatusMessage = z.infer<typeof WSSystemStatusMessageSchema>;
1506
+ export type WSBootstrapStatusChangedMessage = z.infer<typeof WSBootstrapStatusChangedMessageSchema>;
1384
1507
  export type WSClientCountChangedMessage = z.infer<typeof WSClientCountChangedMessageSchema>;
1385
1508
  export type WSClockStatusChangedMessage = z.infer<typeof WSClockStatusChangedMessageSchema>;
1386
1509
  export type WSErrorMessage = z.infer<typeof WSErrorMessageSchema>;
@@ -1430,6 +1553,7 @@ export interface DigitalRadioEngineEvents {
1430
1553
  slotPackUpdated: (slotPack: z.infer<typeof SlotPackSchema>) => void;
1431
1554
  slotPacksReset: (data: z.infer<typeof WSSlotPacksResetMessageSchema>['data']) => void;
1432
1555
  spectrumCapabilities: (data: z.infer<typeof SpectrumCapabilitiesSchema>) => void;
1556
+ spectrumSubscriptionChanged: (data: z.infer<typeof WSSpectrumSubscriptionChangedMessageSchema>['data']) => void;
1433
1557
  spectrumFrame: (data: z.infer<typeof SpectrumFrameSchema>) => void;
1434
1558
  spectrumSessionStateChanged: (data: z.infer<typeof SpectrumSessionStateSchema>) => void;
1435
1559
 
@@ -1443,6 +1567,7 @@ export interface DigitalRadioEngineEvents {
1443
1567
  frequency: number;
1444
1568
  slotStartMs: number;
1445
1569
  replaceExisting?: boolean;
1570
+ frequencyContext?: z.infer<typeof SlotPackFrequencyContextSchema>;
1446
1571
  }) => void;
1447
1572
 
1448
1573
  // 操作员事件
@@ -1452,6 +1577,7 @@ export interface DigitalRadioEngineEvents {
1452
1577
  // 错误和状态事件
1453
1578
  decodeError: (errorInfo: DecodeErrorInfo) => void;
1454
1579
  systemStatus: (status: SystemStatus) => void;
1580
+ bootstrapStatusChanged: (status: BootstrapStatus) => void;
1455
1581
  clientCountChanged: (data: { count: number; timestamp: number }) => void;
1456
1582
  clockStatusChanged: (data: import('./system.schema.js').ClockStatusSummary) => void;
1457
1583
 
@@ -1516,6 +1642,12 @@ export interface DigitalRadioEngineEvents {
1516
1642
  voiceRadioModeChanged: (data: { radioMode: string }) => void;
1517
1643
  voiceKeyerStatusChanged: (data: VoiceKeyerStatus) => void;
1518
1644
 
1645
+ // CW 模式事件
1646
+ cwKeyerStatusChanged: (data: CWKeyerStatus) => void;
1647
+ cwConfigChanged: (data: CWKeyerConfig) => void;
1648
+ cwDecoderStatusChanged: (data: CWDecoderStatus) => void;
1649
+ cwDecoderEvent: (data: CWDecoderEvent) => void;
1650
+
1519
1651
  // 进程监控事件
1520
1652
  processSnapshot: (data: import('./process-monitor.schema.js').ProcessSnapshot) => void;
1521
1653
  processSnapshotHistory: (data: import('./process-monitor.schema.js').ProcessSnapshotHistory) => void;
@@ -1544,6 +1676,17 @@ export interface DigitalRadioEngineEvents {
1544
1676
  }) => void;
1545
1677
  pluginPanelMeta: (data: import('./plugin.schema.js').PluginPanelMetaPayload) => void;
1546
1678
  pluginPanelContributionsChanged: (data: import('./plugin.schema.js').PluginUIPanelContributionGroup) => void;
1679
+ pluginRemoteReplyToDecode: (data: { operatorId: string; callsign: string; modifiers?: number }) => void;
1680
+ pluginRemoteClearDecodes: (data: { operatorId: string; window?: number }) => void;
1681
+ pluginRemoteFreeText: (data: { operatorId: string; text?: string; send: boolean }) => void;
1682
+ pluginRemoteLocation: (data: { operatorId: string; location: string }) => void;
1683
+ pluginRemoteHighlightCallsign: (data: {
1684
+ operatorId: string;
1685
+ callsign: string;
1686
+ background?: string | null;
1687
+ foreground?: string | null;
1688
+ lastOnly?: boolean;
1689
+ }) => void;
1547
1690
 
1548
1691
  // 内部子系统间通信事件(不通过 WebSocket 广播,用于 engine emitter 内部编排)
1549
1692
  encodeStart: (slotInfo: z.infer<typeof SlotInfoSchema>) => void;