@tx5dr/contracts 1.7.12 → 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 (255) hide show
  1. package/dist/index.d.ts +7 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/schema/__tests__/android-operator-audio.schema.test.d.ts +2 -0
  6. package/dist/schema/__tests__/android-operator-audio.schema.test.d.ts.map +1 -0
  7. package/dist/schema/__tests__/android-operator-audio.schema.test.js +27 -0
  8. package/dist/schema/__tests__/android-operator-audio.schema.test.js.map +1 -0
  9. package/dist/schema/__tests__/assisted-queue.schema.test.d.ts +2 -0
  10. package/dist/schema/__tests__/assisted-queue.schema.test.d.ts.map +1 -0
  11. package/dist/schema/__tests__/assisted-queue.schema.test.js +114 -0
  12. package/dist/schema/__tests__/assisted-queue.schema.test.js.map +1 -0
  13. package/dist/schema/__tests__/audio-sidecar.schema.test.d.ts +2 -0
  14. package/dist/schema/__tests__/audio-sidecar.schema.test.d.ts.map +1 -0
  15. package/dist/schema/__tests__/audio-sidecar.schema.test.js +42 -0
  16. package/dist/schema/__tests__/audio-sidecar.schema.test.js.map +1 -0
  17. package/dist/schema/__tests__/audio.schema.test.js +92 -1
  18. package/dist/schema/__tests__/audio.schema.test.js.map +1 -1
  19. package/dist/schema/__tests__/diagnostics.schema.test.d.ts +2 -0
  20. package/dist/schema/__tests__/diagnostics.schema.test.d.ts.map +1 -0
  21. package/dist/schema/__tests__/diagnostics.schema.test.js +31 -0
  22. package/dist/schema/__tests__/diagnostics.schema.test.js.map +1 -0
  23. package/dist/schema/__tests__/logbook-health.schema.test.d.ts +2 -0
  24. package/dist/schema/__tests__/logbook-health.schema.test.d.ts.map +1 -0
  25. package/dist/schema/__tests__/logbook-health.schema.test.js +104 -0
  26. package/dist/schema/__tests__/logbook-health.schema.test.js.map +1 -0
  27. package/dist/schema/__tests__/meter.schema.test.d.ts +2 -0
  28. package/dist/schema/__tests__/meter.schema.test.d.ts.map +1 -0
  29. package/dist/schema/__tests__/meter.schema.test.js +27 -0
  30. package/dist/schema/__tests__/meter.schema.test.js.map +1 -0
  31. package/dist/schema/__tests__/observability.schema.test.d.ts +2 -0
  32. package/dist/schema/__tests__/observability.schema.test.d.ts.map +1 -0
  33. package/dist/schema/__tests__/observability.schema.test.js +12 -0
  34. package/dist/schema/__tests__/observability.schema.test.js.map +1 -0
  35. package/dist/schema/__tests__/operator-context-report.schema.test.d.ts +2 -0
  36. package/dist/schema/__tests__/operator-context-report.schema.test.d.ts.map +1 -0
  37. package/dist/schema/__tests__/operator-context-report.schema.test.js +70 -0
  38. package/dist/schema/__tests__/operator-context-report.schema.test.js.map +1 -0
  39. package/dist/schema/__tests__/operator.schema.test.js +9 -3
  40. package/dist/schema/__tests__/operator.schema.test.js.map +1 -1
  41. package/dist/schema/__tests__/plugin-market.schema.test.js +149 -3
  42. package/dist/schema/__tests__/plugin-market.schema.test.js.map +1 -1
  43. package/dist/schema/__tests__/qso.schema.test.d.ts +2 -0
  44. package/dist/schema/__tests__/qso.schema.test.d.ts.map +1 -0
  45. package/dist/schema/__tests__/qso.schema.test.js +70 -0
  46. package/dist/schema/__tests__/qso.schema.test.js.map +1 -0
  47. package/dist/schema/__tests__/radio-power-support.test.js +15 -0
  48. package/dist/schema/__tests__/radio-power-support.test.js.map +1 -1
  49. package/dist/schema/__tests__/radio-profile.schema.test.d.ts +2 -0
  50. package/dist/schema/__tests__/radio-profile.schema.test.d.ts.map +1 -0
  51. package/dist/schema/__tests__/radio-profile.schema.test.js +36 -0
  52. package/dist/schema/__tests__/radio-profile.schema.test.js.map +1 -0
  53. package/dist/schema/__tests__/radio.schema.test.js +92 -1
  54. package/dist/schema/__tests__/radio.schema.test.js.map +1 -1
  55. package/dist/schema/__tests__/realtime-settings.schema.test.js +2 -1
  56. package/dist/schema/__tests__/realtime-settings.schema.test.js.map +1 -1
  57. package/dist/schema/__tests__/remote-access.schema.test.d.ts +2 -0
  58. package/dist/schema/__tests__/remote-access.schema.test.d.ts.map +1 -0
  59. package/dist/schema/__tests__/remote-access.schema.test.js +18 -0
  60. package/dist/schema/__tests__/remote-access.schema.test.js.map +1 -0
  61. package/dist/schema/__tests__/spectrum.schema.test.d.ts +2 -0
  62. package/dist/schema/__tests__/spectrum.schema.test.d.ts.map +1 -0
  63. package/dist/schema/__tests__/spectrum.schema.test.js +107 -0
  64. package/dist/schema/__tests__/spectrum.schema.test.js.map +1 -0
  65. package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts +2 -0
  66. package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts.map +1 -0
  67. package/dist/schema/__tests__/strategy-transmission.schema.test.js +95 -0
  68. package/dist/schema/__tests__/strategy-transmission.schema.test.js.map +1 -0
  69. package/dist/schema/android-operator-audio.schema.d.ts +174 -0
  70. package/dist/schema/android-operator-audio.schema.d.ts.map +1 -0
  71. package/dist/schema/android-operator-audio.schema.js +38 -0
  72. package/dist/schema/android-operator-audio.schema.js.map +1 -0
  73. package/dist/schema/api-response.schema.d.ts +503 -44
  74. package/dist/schema/api-response.schema.d.ts.map +1 -1
  75. package/dist/schema/api-response.schema.js +9 -0
  76. package/dist/schema/api-response.schema.js.map +1 -1
  77. package/dist/schema/audio-sidecar.schema.d.ts +75 -0
  78. package/dist/schema/audio-sidecar.schema.d.ts.map +1 -1
  79. package/dist/schema/audio-sidecar.schema.js +32 -0
  80. package/dist/schema/audio-sidecar.schema.js.map +1 -1
  81. package/dist/schema/audio.schema.d.ts +4749 -3
  82. package/dist/schema/audio.schema.d.ts.map +1 -1
  83. package/dist/schema/audio.schema.js +101 -2
  84. package/dist/schema/audio.schema.js.map +1 -1
  85. package/dist/schema/auth.schema.d.ts +173 -14
  86. package/dist/schema/auth.schema.d.ts.map +1 -1
  87. package/dist/schema/auth.schema.js +65 -1
  88. package/dist/schema/auth.schema.js.map +1 -1
  89. package/dist/schema/cpu-profile.schema.d.ts +2 -2
  90. package/dist/schema/cw-decoder.schema.d.ts +74 -74
  91. package/dist/schema/cw-keyer.schema.d.ts +20 -14
  92. package/dist/schema/cw-keyer.schema.d.ts.map +1 -1
  93. package/dist/schema/cw-keyer.schema.js +3 -0
  94. package/dist/schema/cw-keyer.schema.js.map +1 -1
  95. package/dist/schema/desktop-https.schema.d.ts +6 -6
  96. package/dist/schema/device-ui.schema.d.ts +16 -16
  97. package/dist/schema/diagnostics.schema.d.ts +163 -0
  98. package/dist/schema/diagnostics.schema.d.ts.map +1 -0
  99. package/dist/schema/diagnostics.schema.js +46 -0
  100. package/dist/schema/diagnostics.schema.js.map +1 -0
  101. package/dist/schema/ft8.schema.d.ts +12 -2
  102. package/dist/schema/ft8.schema.d.ts.map +1 -1
  103. package/dist/schema/ft8.schema.js +8 -0
  104. package/dist/schema/ft8.schema.js.map +1 -1
  105. package/dist/schema/image-radio.schema.d.ts +2597 -0
  106. package/dist/schema/image-radio.schema.d.ts.map +1 -0
  107. package/dist/schema/image-radio.schema.js +283 -0
  108. package/dist/schema/image-radio.schema.js.map +1 -0
  109. package/dist/schema/logbook.schema.d.ts +2128 -18
  110. package/dist/schema/logbook.schema.d.ts.map +1 -1
  111. package/dist/schema/logbook.schema.js +159 -1
  112. package/dist/schema/logbook.schema.js.map +1 -1
  113. package/dist/schema/mode.schema.d.ts +4 -6
  114. package/dist/schema/mode.schema.d.ts.map +1 -1
  115. package/dist/schema/mode.schema.js +18 -6
  116. package/dist/schema/mode.schema.js.map +1 -1
  117. package/dist/schema/observability.schema.d.ts +66 -0
  118. package/dist/schema/observability.schema.d.ts.map +1 -0
  119. package/dist/schema/observability.schema.js +20 -0
  120. package/dist/schema/observability.schema.js.map +1 -0
  121. package/dist/schema/openwebrx.schema.d.ts +2 -2
  122. package/dist/schema/operator.schema.d.ts +2682 -6
  123. package/dist/schema/operator.schema.d.ts.map +1 -1
  124. package/dist/schema/operator.schema.js +10 -20
  125. package/dist/schema/operator.schema.js.map +1 -1
  126. package/dist/schema/plugin.schema.d.ts +1977 -492
  127. package/dist/schema/plugin.schema.d.ts.map +1 -1
  128. package/dist/schema/plugin.schema.js +165 -14
  129. package/dist/schema/plugin.schema.js.map +1 -1
  130. package/dist/schema/process-monitor.schema.d.ts +132 -132
  131. package/dist/schema/pskreporter.schema.d.ts +1 -3
  132. package/dist/schema/pskreporter.schema.d.ts.map +1 -1
  133. package/dist/schema/pskreporter.schema.js +1 -3
  134. package/dist/schema/pskreporter.schema.js.map +1 -1
  135. package/dist/schema/qso.schema.d.ts +246 -1
  136. package/dist/schema/qso.schema.d.ts.map +1 -1
  137. package/dist/schema/qso.schema.js +68 -0
  138. package/dist/schema/qso.schema.js.map +1 -1
  139. package/dist/schema/radio-capability.schema.d.ts +31 -40
  140. package/dist/schema/radio-capability.schema.d.ts.map +1 -1
  141. package/dist/schema/radio-capability.schema.js +8 -15
  142. package/dist/schema/radio-capability.schema.js.map +1 -1
  143. package/dist/schema/radio-power-support.d.ts.map +1 -1
  144. package/dist/schema/radio-power-support.js +3 -0
  145. package/dist/schema/radio-power-support.js.map +1 -1
  146. package/dist/schema/radio-power.schema.d.ts +102 -13
  147. package/dist/schema/radio-power.schema.d.ts.map +1 -1
  148. package/dist/schema/radio-power.schema.js +15 -8
  149. package/dist/schema/radio-power.schema.js.map +1 -1
  150. package/dist/schema/radio-profile.schema.d.ts +1491 -97
  151. package/dist/schema/radio-profile.schema.d.ts.map +1 -1
  152. package/dist/schema/radio-profile.schema.js +7 -0
  153. package/dist/schema/radio-profile.schema.js.map +1 -1
  154. package/dist/schema/radio.schema.d.ts +442 -48
  155. package/dist/schema/radio.schema.d.ts.map +1 -1
  156. package/dist/schema/radio.schema.js +75 -7
  157. package/dist/schema/radio.schema.js.map +1 -1
  158. package/dist/schema/realtime.schema.d.ts +60 -52
  159. package/dist/schema/realtime.schema.d.ts.map +1 -1
  160. package/dist/schema/realtime.schema.js +9 -1
  161. package/dist/schema/realtime.schema.js.map +1 -1
  162. package/dist/schema/rigctld.schema.d.ts +6 -6
  163. package/dist/schema/slot-info.schema.d.ts +28 -9
  164. package/dist/schema/slot-info.schema.d.ts.map +1 -1
  165. package/dist/schema/slot-info.schema.js +17 -10
  166. package/dist/schema/slot-info.schema.js.map +1 -1
  167. package/dist/schema/spectrum-config.schema.d.ts +638 -0
  168. package/dist/schema/spectrum-config.schema.d.ts.map +1 -0
  169. package/dist/schema/spectrum-config.schema.js +129 -0
  170. package/dist/schema/spectrum-config.schema.js.map +1 -0
  171. package/dist/schema/spectrum.schema.d.ts +1323 -30
  172. package/dist/schema/spectrum.schema.d.ts.map +1 -1
  173. package/dist/schema/spectrum.schema.js +72 -0
  174. package/dist/schema/spectrum.schema.js.map +1 -1
  175. package/dist/schema/station-info.schema.d.ts +5 -0
  176. package/dist/schema/station-info.schema.d.ts.map +1 -1
  177. package/dist/schema/station-info.schema.js +5 -0
  178. package/dist/schema/station-info.schema.js.map +1 -1
  179. package/dist/schema/system.schema.d.ts +56 -6
  180. package/dist/schema/system.schema.d.ts.map +1 -1
  181. package/dist/schema/system.schema.js +22 -0
  182. package/dist/schema/system.schema.js.map +1 -1
  183. package/dist/schema/transmission.schema.d.ts +8 -7
  184. package/dist/schema/transmission.schema.d.ts.map +1 -1
  185. package/dist/schema/transmission.schema.js +2 -7
  186. package/dist/schema/transmission.schema.js.map +1 -1
  187. package/dist/schema/tx-audio-input.schema.d.ts +8 -0
  188. package/dist/schema/tx-audio-input.schema.d.ts.map +1 -0
  189. package/dist/schema/tx-audio-input.schema.js +19 -0
  190. package/dist/schema/tx-audio-input.schema.js.map +1 -0
  191. package/dist/schema/voice-keyer.schema.d.ts +16 -16
  192. package/dist/schema/voice.schema.d.ts +1 -1
  193. package/dist/schema/voice.schema.d.ts.map +1 -1
  194. package/dist/schema/voice.schema.js +1 -1
  195. package/dist/schema/voice.schema.js.map +1 -1
  196. package/dist/schema/websocket.schema.d.ts +35811 -7829
  197. package/dist/schema/websocket.schema.d.ts.map +1 -1
  198. package/dist/schema/websocket.schema.js +453 -9
  199. package/dist/schema/websocket.schema.js.map +1 -1
  200. package/package.json +6 -1
  201. package/src/index.ts +7 -0
  202. package/src/schema/__tests__/android-operator-audio.schema.test.ts +31 -0
  203. package/src/schema/__tests__/assisted-queue.schema.test.ts +122 -0
  204. package/src/schema/__tests__/audio-sidecar.schema.test.ts +48 -0
  205. package/src/schema/__tests__/audio.schema.test.ts +102 -0
  206. package/src/schema/__tests__/diagnostics.schema.test.ts +32 -0
  207. package/src/schema/__tests__/logbook-health.schema.test.ts +117 -0
  208. package/src/schema/__tests__/meter.schema.test.ts +28 -0
  209. package/src/schema/__tests__/observability.schema.test.ts +15 -0
  210. package/src/schema/__tests__/operator-context-report.schema.test.ts +77 -0
  211. package/src/schema/__tests__/operator.schema.test.ts +11 -3
  212. package/src/schema/__tests__/plugin-market.schema.test.ts +165 -2
  213. package/src/schema/__tests__/qso.schema.test.ts +91 -0
  214. package/src/schema/__tests__/radio-power-support.test.ts +17 -0
  215. package/src/schema/__tests__/radio-profile.schema.test.ts +44 -0
  216. package/src/schema/__tests__/radio.schema.test.ts +108 -1
  217. package/src/schema/__tests__/realtime-settings.schema.test.ts +2 -1
  218. package/src/schema/__tests__/remote-access.schema.test.ts +22 -0
  219. package/src/schema/__tests__/spectrum.schema.test.ts +119 -0
  220. package/src/schema/__tests__/strategy-transmission.schema.test.ts +106 -0
  221. package/src/schema/android-operator-audio.schema.ts +49 -0
  222. package/src/schema/api-response.schema.ts +11 -0
  223. package/src/schema/audio-sidecar.schema.ts +35 -0
  224. package/src/schema/audio.schema.ts +115 -2
  225. package/src/schema/auth.schema.ts +79 -1
  226. package/src/schema/cw-keyer.schema.ts +5 -0
  227. package/src/schema/diagnostics.schema.ts +55 -0
  228. package/src/schema/ft8.schema.ts +8 -0
  229. package/src/schema/image-radio.schema.ts +342 -0
  230. package/src/schema/logbook.schema.ts +205 -1
  231. package/src/schema/mode.schema.ts +18 -6
  232. package/src/schema/observability.schema.ts +26 -0
  233. package/src/schema/operator.schema.ts +11 -20
  234. package/src/schema/plugin.schema.ts +186 -15
  235. package/src/schema/pskreporter.schema.ts +1 -3
  236. package/src/schema/qso.schema.ts +78 -0
  237. package/src/schema/radio-capability.schema.ts +8 -15
  238. package/src/schema/radio-power-support.ts +3 -0
  239. package/src/schema/radio-power.schema.ts +15 -8
  240. package/src/schema/radio-profile.schema.ts +7 -0
  241. package/src/schema/radio.schema.ts +90 -7
  242. package/src/schema/realtime.schema.ts +9 -1
  243. package/src/schema/slot-info.schema.ts +18 -11
  244. package/src/schema/spectrum-config.schema.ts +156 -0
  245. package/src/schema/spectrum.schema.ts +89 -0
  246. package/src/schema/station-info.schema.ts +5 -0
  247. package/src/schema/system.schema.ts +29 -0
  248. package/src/schema/transmission.schema.ts +2 -7
  249. package/src/schema/tx-audio-input.schema.ts +22 -0
  250. package/src/schema/voice.schema.ts +1 -1
  251. package/src/schema/websocket.schema.ts +601 -13
  252. package/test/image-radio-schema.test.ts +107 -0
  253. package/test/plugin-slot-schema.test.ts +31 -0
  254. package/test/radio-capability-schema.test.ts +44 -0
  255. package/CLAUDE.md +0 -41
@@ -1,19 +1,27 @@
1
1
  import { z } from 'zod';
2
2
  import { ModeDescriptorSchema } from './mode.schema.js';
3
3
  import { sanitizeCallsignInput } from '../utils/callsign.js';
4
+ import { sanitizeGridInput } from '../utils/grid.js';
5
+ import { StrategyRuntimeSnapshotSchema } from './websocket.schema.js';
4
6
 
5
7
  const OperatorCallsignSchema = z
6
8
  .string()
7
9
  .transform(sanitizeCallsignInput)
8
10
  .pipe(z.string().min(1, '呼号不能为空').max(10, '呼号不能超过10个字符'));
9
11
 
12
+ const OperatorGridSchema = z
13
+ .string()
14
+ .transform(sanitizeGridInput)
15
+ .pipe(z.string().min(4, '网格坐标至少4位').max(8, '网格坐标不能超过8位'));
16
+
10
17
  // 操作员配置 Schema (重命名以避免冲突)
11
18
  export const RadioOperatorConfigSchema = z.object({
12
19
  id: z.string().min(1, '操作员ID不能为空'),
13
20
  createdByTokenId: z.string().optional(), // 创建者 Token ID(仅用于审计,不参与权限判断)
14
21
  myCallsign: OperatorCallsignSchema,
15
- myGrid: z.string().min(4, '网格坐标至少4位').max(8, '网格坐标不能超过8位').optional(),
22
+ myGrid: OperatorGridSchema.optional(),
16
23
  frequency: z.number().min(0).max(1000000000, '频率必须在0-1GHz之间').default(1000), // 音频偏移频率(Hz),默认1000Hz,创建时自动分配不重复值
24
+ maxConcurrentStreams: z.number().int().min(1).max(5).optional(),
17
25
  transmitCycles: z.array(z.number().min(0).max(1)).default([0]), // 0=偶数周期,1=奇数周期
18
26
  mode: ModeDescriptorSchema.optional(),
19
27
  logBookId: z.string().optional(), // 连接的日志本ID,如果未指定则使用默认日志本
@@ -61,6 +69,7 @@ export const RadioOperatorStatusResponseSchema = z.object({
61
69
  context: z.object({
62
70
  myCall: z.string(),
63
71
  myGrid: z.string(),
72
+ targetCalls: z.array(z.string()).optional(),
64
73
  targetCall: z.string(),
65
74
  targetGrid: z.string().optional(),
66
75
  frequency: z.number().optional(),
@@ -72,25 +81,7 @@ export const RadioOperatorStatusResponseSchema = z.object({
72
81
  state: z.string(),
73
82
  availableSlots: z.array(z.string()),
74
83
  }),
75
- runtime: z.object({
76
- currentState: z.string(),
77
- slots: z.object({
78
- TX1: z.string().optional(),
79
- TX2: z.string().optional(),
80
- TX3: z.string().optional(),
81
- TX4: z.string().optional(),
82
- TX5: z.string().optional(),
83
- TX6: z.string().optional(),
84
- }).optional(),
85
- context: z.object({
86
- targetCallsign: z.string().optional(),
87
- targetGrid: z.string().optional(),
88
- reportSent: z.number().optional(),
89
- reportReceived: z.number().optional(),
90
- actualFrequency: z.number().optional(),
91
- }).optional(),
92
- availableSlots: z.array(z.string()).optional(),
93
- }).optional(),
84
+ runtime: StrategyRuntimeSnapshotSchema.optional(),
94
85
  slots: z.object({
95
86
  TX1: z.string().optional(),
96
87
  TX2: z.string().optional(),
@@ -17,6 +17,16 @@ export const PluginTypeSchema = z.enum(['strategy', 'utility']);
17
17
  */
18
18
  export type PluginType = z.infer<typeof PluginTypeSchema>;
19
19
 
20
+ export const StrategyFeaturesSchema = z.object({
21
+ targetQueue: z.literal(1).optional(),
22
+ parallelTargetQueue: z.literal(1).optional(),
23
+ queueActivation: z.enum(['immediate', 'operator-toggle']).optional(),
24
+ manualInitiation: z.literal(1).optional(),
25
+ maxConcurrentStreams: z.number().int().min(1).max(5).optional(),
26
+ maxSimultaneousSignals: z.number().int().min(1).max(5).optional(),
27
+ }).optional();
28
+ export type StrategyFeatures = z.infer<typeof StrategyFeaturesSchema>;
29
+
20
30
  /**
21
31
  * Runtime instance scope for a plugin.
22
32
  *
@@ -38,10 +48,17 @@ export type PluginInstanceScope = z.infer<typeof PluginInstanceScopeSchema>;
38
48
  */
39
49
  export const PluginPermissionSchema = z.enum([
40
50
  'network',
51
+ 'plugin:event-bus',
41
52
  'host:hamlib',
53
+ 'operator:transmit-control',
42
54
  'radio:read',
43
55
  'radio:control',
56
+ 'radio:tuner-control',
44
57
  'radio:power',
58
+ 'logbook:read',
59
+ 'logbook:write',
60
+ 'logbook:session',
61
+ 'logbook:sync',
45
62
  'settings:ft8',
46
63
  'settings:decode-windows',
47
64
  'settings:realtime',
@@ -74,7 +91,17 @@ export type PluginPanelComponent = z.infer<typeof PluginPanelComponentSchema>;
74
91
  * These values control both validation expectations and default frontend
75
92
  * rendering in plugin settings UIs.
76
93
  */
77
- export const PluginSettingTypeSchema = z.enum(['boolean', 'number', 'string', 'string[]', 'object[]', 'keyedStringArrays', 'info']);
94
+ export const PluginSettingTypeSchema = z.enum([
95
+ 'boolean',
96
+ 'number',
97
+ 'string',
98
+ 'string[]',
99
+ 'object[]',
100
+ 'keyedStringArrays',
101
+ 'keyedObjectArrays',
102
+ 'keyedObjects',
103
+ 'info',
104
+ ]);
78
105
 
79
106
  /**
80
107
  * Supported generated-form field types for plugin settings.
@@ -94,6 +121,11 @@ export const PluginSettingOptionSchema = z.object({
94
121
  */
95
122
  export type PluginSettingOption = z.infer<typeof PluginSettingOptionSchema>;
96
123
 
124
+ /**
125
+ * Field descriptor used for each item in an `object[]` or `keyedObjectArrays`
126
+ * setting editor. `key` becomes the object property; `type` controls the Host
127
+ * input; `required` and `default` are applied per item.
128
+ */
97
129
  export const PluginObjectArrayFieldSchema = z.object({
98
130
  key: z.string(),
99
131
  type: z.enum(['string', 'number', 'boolean']).optional().default('string'),
@@ -101,9 +133,14 @@ export const PluginObjectArrayFieldSchema = z.object({
101
133
  description: z.string().optional(),
102
134
  placeholder: z.string().optional(),
103
135
  required: z.boolean().optional(),
136
+ default: z.unknown().optional(),
104
137
  });
105
138
  export type PluginObjectArrayField = z.infer<typeof PluginObjectArrayFieldSchema>;
106
139
 
140
+ /**
141
+ * Fixed key exposed by keyed setting editors. `key` is persisted in the value;
142
+ * `label` and `description` are literal text or plugin locale keys.
143
+ */
107
144
  export const PluginKeyedStringArrayKeySchema = z.object({
108
145
  key: z.string(),
109
146
  label: z.string(),
@@ -111,10 +148,18 @@ export const PluginKeyedStringArrayKeySchema = z.object({
111
148
  });
112
149
  export type PluginKeyedStringArrayKey = z.infer<typeof PluginKeyedStringArrayKeySchema>;
113
150
 
151
+ /**
152
+ * Declarative condition evaluated against values in the same settings form.
153
+ * A leaf compares one `setting` with `equals` or `notEquals`; `allOf` requires
154
+ * every nested condition and `anyOf` requires at least one. Nested groups may
155
+ * be combined recursively.
156
+ */
114
157
  export interface PluginSettingCondition {
115
158
  /** Single setting key to compare. Preserves the original condition shape. */
116
159
  setting?: string;
160
+ /** Match when the referenced value is strictly equal to this value. */
117
161
  equals?: unknown;
162
+ /** Match when the referenced value is not strictly equal to this value. */
118
163
  notEquals?: unknown;
119
164
  /** All nested conditions must match. */
120
165
  allOf?: PluginSettingCondition[];
@@ -130,6 +175,7 @@ export const PluginSettingConditionSchema: z.ZodType<PluginSettingCondition> = z
130
175
  anyOf: z.array(PluginSettingConditionSchema).optional(),
131
176
  }));
132
177
 
178
+ /** Description override selected when its condition matches. */
133
179
  export const PluginSettingConditionalDescriptionSchema = z.object({
134
180
  when: PluginSettingConditionSchema,
135
181
  description: z.string(),
@@ -156,6 +202,17 @@ export type PluginSettingScope = z.infer<typeof PluginSettingScopeSchema>;
156
202
  *
157
203
  * The host uses this schema to generate configuration forms, validate updates
158
204
  * and resolve default values before injecting them into `ctx.config`.
205
+ *
206
+ * Collection defaults must match the selected renderer:
207
+ * - `string[]` -> `string[]`
208
+ * - `object[]` -> `Array<Record<string, unknown>>` described by `itemFields`
209
+ * - `keyedStringArrays` -> `Record<string, string[]>`
210
+ * - `keyedObjectArrays` -> `Record<string, Array<Record<string, unknown>>>`
211
+ * - `keyedObjects` -> `Record<string, Record<string, unknown>>`
212
+ *
213
+ * Keyed editors use `keys` for their fixed outer keys; object-based editors use
214
+ * `itemFields` for the nested shape. `info` is display-only and cannot be used
215
+ * by `quickSettings`.
159
216
  */
160
217
  export const PluginSettingDescriptorSchema = z.object({
161
218
  type: PluginSettingTypeSchema,
@@ -167,7 +224,7 @@ export const PluginSettingDescriptorSchema = z.object({
167
224
  options: z.array(PluginSettingOptionSchema).optional(),
168
225
  /** Field schema used by generated editors for `object[]` settings. */
169
226
  itemFields: z.array(PluginObjectArrayFieldSchema).optional(),
170
- /** Fixed key list used by generated editors for `keyedStringArrays` settings. */
227
+ /** Fixed key list used by generated keyed setting editors. */
171
228
  keys: z.array(PluginKeyedStringArrayKeySchema).optional(),
172
229
  /** Conditionally show the field based on another setting in the same form. */
173
230
  visibleWhen: PluginSettingConditionSchema.optional(),
@@ -231,6 +288,7 @@ export type PluginQuickSetting = z.infer<typeof PluginQuickSettingSchema>;
231
288
  * about plugin roles without hard-coding specific plugin names.
232
289
  */
233
290
  export const PluginCapabilitySchema = z.enum([
291
+ 'auto_call_control',
234
292
  'auto_call_candidate',
235
293
  'auto_call_execution',
236
294
  ]);
@@ -247,6 +305,7 @@ export type PluginCapability = z.infer<typeof PluginCapabilitySchema>;
247
305
  *
248
306
  * - `operator`: shown in the expanded operator card's live-panel area (default).
249
307
  * - `automation`: shown inside the top-right automation quick-action popover.
308
+ * - `operator-action`: shown beside the built-in logbook action on an operator card.
250
309
  * - `main-right`: shown in the optional main layout plugin pane on the far right.
251
310
  * - `voice-left-top`: shown above the voice frequency control card.
252
311
  * - `voice-right-top`: shown in the tabbed top area of the voice right panel.
@@ -256,6 +315,7 @@ export type PluginCapability = z.infer<typeof PluginCapabilitySchema>;
256
315
  export const PluginPanelSlotSchema = z.enum([
257
316
  'operator',
258
317
  'automation',
318
+ 'operator-action',
259
319
  'main-right',
260
320
  'voice-left-top',
261
321
  'voice-right-top',
@@ -284,12 +344,15 @@ export const PluginPanelWidthSchema = z.enum(['half', 'full']);
284
344
  export type PluginPanelWidth = z.infer<typeof PluginPanelWidthSchema>;
285
345
 
286
346
  /**
287
- * How an iframe panel is opened when rendered as a toolbar entry.
347
+ * How an iframe panel is opened when rendered as an action entry.
348
+ *
349
+ * `page` opens the referenced custom UI in the host's standalone plugin-page
350
+ * route instead of embedding it in the radio workspace.
288
351
  */
289
- export const PluginPanelOpenModeSchema = z.enum(['popover', 'modal']);
352
+ export const PluginPanelOpenModeSchema = z.enum(['popover', 'modal', 'page']);
290
353
 
291
354
  /**
292
- * How an iframe panel is opened when rendered as a toolbar entry.
355
+ * How an iframe panel is opened when rendered as an action entry.
293
356
  */
294
357
  export type PluginPanelOpenMode = z.infer<typeof PluginPanelOpenModeSchema>;
295
358
 
@@ -330,21 +393,28 @@ export const PluginPanelDescriptorSchema = z.object({
330
393
  /** Optional controlled UI size hint for toolbar popovers/modals. */
331
394
  uiSize: PluginPanelUISizeSchema.optional(),
332
395
  }).superRefine((panel, ctx) => {
333
- if (panel.slot !== 'radio-control-toolbar') {
396
+ if (panel.slot !== 'radio-control-toolbar' && panel.slot !== 'operator-action') {
334
397
  return;
335
398
  }
336
399
  if (panel.component !== 'iframe') {
337
400
  ctx.addIssue({
338
401
  code: z.ZodIssueCode.custom,
339
402
  path: ['component'],
340
- message: 'radio-control-toolbar panels must use iframe component',
403
+ message: `${panel.slot} panels must use iframe component`,
341
404
  });
342
405
  }
343
406
  if (!panel.pageId) {
344
407
  ctx.addIssue({
345
408
  code: z.ZodIssueCode.custom,
346
409
  path: ['pageId'],
347
- message: 'radio-control-toolbar panels must declare pageId',
410
+ message: `${panel.slot} panels must declare pageId`,
411
+ });
412
+ }
413
+ if (panel.slot === 'operator-action' && panel.openMode !== 'page') {
414
+ ctx.addIssue({
415
+ code: z.ZodIssueCode.custom,
416
+ path: ['openMode'],
417
+ message: 'operator-action panels must use page openMode',
348
418
  });
349
419
  }
350
420
  });
@@ -354,6 +424,10 @@ export const PluginPanelDescriptorSchema = z.object({
354
424
  */
355
425
  export type PluginPanelDescriptor = z.infer<typeof PluginPanelDescriptorSchema>;
356
426
 
427
+ /**
428
+ * Plugin instance that owns a runtime panel contribution: the single global
429
+ * instance or one explicitly identified operator instance.
430
+ */
357
431
  export const PluginUIPanelContributionTargetSchema = z.discriminatedUnion('kind', [
358
432
  z.object({ kind: z.literal('global') }),
359
433
  z.object({ kind: z.literal('operator'), operatorId: z.string() }),
@@ -448,6 +522,11 @@ export type PluginStorageConfig = z.infer<typeof PluginStorageConfigSchema>;
448
522
 
449
523
  // ===== 插件清单 =====
450
524
 
525
+ const SemanticVersionSchema = z.string().regex(
526
+ /^v?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/,
527
+ 'must be a semantic version',
528
+ );
529
+
451
530
  /**
452
531
  * Normalized manifest describing a plugin's static metadata and declarations.
453
532
  *
@@ -455,9 +534,12 @@ export type PluginStorageConfig = z.infer<typeof PluginStorageConfigSchema>;
455
534
  * host can expose to management UI and diagnostics.
456
535
  */
457
536
  export const PluginManifestSchema = z.object({
537
+ apiVersion: z.literal(2).optional(),
458
538
  name: z.string(),
459
539
  version: z.string(),
540
+ minPluginApiVersion: SemanticVersionSchema.optional(),
460
541
  type: PluginTypeSchema,
542
+ strategyFeatures: StrategyFeaturesSchema,
461
543
  instanceScope: PluginInstanceScopeSchema.optional().default('operator'),
462
544
  description: z.string().optional(),
463
545
  permissions: z.array(PluginPermissionSchema).optional(),
@@ -503,6 +585,7 @@ export type PluginLocales = z.infer<typeof PluginLocalesSchema>;
503
585
  export const PluginStatusSchema = z.object({
504
586
  name: z.string(),
505
587
  type: PluginTypeSchema,
588
+ strategyFeatures: StrategyFeaturesSchema,
506
589
  instanceScope: PluginInstanceScopeSchema.optional().default('operator'),
507
590
  version: z.string(),
508
591
  description: z.string().optional(),
@@ -521,6 +604,8 @@ export const PluginStatusSchema = z.object({
521
604
  panels: z.array(PluginPanelDescriptorSchema).optional(),
522
605
  permissions: z.array(PluginPermissionSchema).optional(),
523
606
  capabilities: z.array(PluginCapabilitySchema).optional(),
607
+ autoCallEnabledOperatorIds: z.array(z.string()).optional(),
608
+ pausedOperatorIds: z.array(z.string()).optional(),
524
609
  ui: PluginUIConfigSchema.optional(),
525
610
  locales: PluginLocalesSchema.optional(),
526
611
  source: PluginSourceSchema.optional(),
@@ -586,6 +671,8 @@ export const PluginDistributionSchema = z.enum([
586
671
  export type PluginDistribution = z.infer<typeof PluginDistributionSchema>;
587
672
 
588
673
  export const PluginRuntimeInfoSchema = z.object({
674
+ hostVersion: z.string(),
675
+ pluginApiVersion: SemanticVersionSchema,
589
676
  pluginDir: z.string(),
590
677
  pluginDataDir: z.string(),
591
678
  dataDir: z.string(),
@@ -616,13 +703,17 @@ export const PluginMarketScreenshotSchema = z.object({
616
703
  });
617
704
  export type PluginMarketScreenshot = z.infer<typeof PluginMarketScreenshotSchema>;
618
705
 
619
- export const PluginMarketCatalogEntrySchema = z.object({
706
+ const PluginMarketCatalogEntryInputSchema = z.object({
620
707
  name: z.string(),
621
708
  title: z.string(),
622
709
  description: z.string(),
710
+ readmeMarkdown: z.string().max(100 * 1024).optional(),
711
+ readmeSourceUrl: z.string().url().optional(),
623
712
  locales: PluginLocalesSchema.optional(),
624
713
  latestVersion: z.string(),
625
- minHostVersion: z.string(),
714
+ minPluginApiVersion: SemanticVersionSchema.optional(),
715
+ /** Catalog schema v1 compatibility alias; normalized on read. */
716
+ minHostVersion: SemanticVersionSchema.optional(),
626
717
  author: z.string().optional(),
627
718
  license: z.string().optional(),
628
719
  repository: z.string().url().optional(),
@@ -635,15 +726,93 @@ export const PluginMarketCatalogEntrySchema = z.object({
635
726
  sha256: z.string().regex(/^[a-f0-9]{64}$/i),
636
727
  size: z.number().int().nonnegative(),
637
728
  publishedAt: z.string(),
729
+ artifactManifestVersion: z.literal(1).optional(),
730
+ }).superRefine((value, context) => {
731
+ if (!value.minPluginApiVersion && !value.minHostVersion) {
732
+ context.addIssue({
733
+ code: z.ZodIssueCode.custom,
734
+ path: ['minPluginApiVersion'],
735
+ message: 'minPluginApiVersion is required',
736
+ });
737
+ }
738
+ if (value.minPluginApiVersion && value.minHostVersion
739
+ && value.minPluginApiVersion !== value.minHostVersion) {
740
+ context.addIssue({
741
+ code: z.ZodIssueCode.custom,
742
+ path: ['minPluginApiVersion'],
743
+ message: 'minPluginApiVersion conflicts with legacy minHostVersion',
744
+ });
745
+ }
746
+ });
747
+
748
+ export const PluginMarketCatalogEntrySchema = PluginMarketCatalogEntryInputSchema.transform((value) => {
749
+ const { minHostVersion: _legacyMinimum, ...entry } = value;
750
+ return {
751
+ ...entry,
752
+ minPluginApiVersion: value.minPluginApiVersion ?? value.minHostVersion!,
753
+ };
638
754
  });
639
755
  export type PluginMarketCatalogEntry = z.infer<typeof PluginMarketCatalogEntrySchema>;
640
756
 
641
- export const PluginMarketCatalogSchema = z.object({
642
- schemaVersion: z.number().int().positive(),
643
- generatedAt: z.string(),
644
- channel: PluginMarketChannelSchema,
645
- plugins: z.array(PluginMarketCatalogEntrySchema),
757
+ const PluginMarketCatalogV2EntrySchema = PluginMarketCatalogEntryInputSchema.superRefine(
758
+ (value, context) => {
759
+ if (!value.minPluginApiVersion) {
760
+ context.addIssue({
761
+ code: z.ZodIssueCode.custom,
762
+ path: ['minPluginApiVersion'],
763
+ message: 'catalog schema v2 requires minPluginApiVersion',
764
+ });
765
+ }
766
+ if (value.minHostVersion) {
767
+ context.addIssue({
768
+ code: z.ZodIssueCode.custom,
769
+ path: ['minHostVersion'],
770
+ message: 'catalog schema v2 does not support legacy minHostVersion',
771
+ });
772
+ }
773
+ if (value.artifactManifestVersion !== 1) {
774
+ context.addIssue({
775
+ code: z.ZodIssueCode.custom,
776
+ path: ['artifactManifestVersion'],
777
+ message: 'catalog schema v2 requires artifactManifestVersion 1',
778
+ });
779
+ }
780
+ },
781
+ ).transform((value) => {
782
+ const { minHostVersion: _legacyMinimum, ...entry } = value;
783
+ return {
784
+ ...entry,
785
+ minPluginApiVersion: value.minPluginApiVersion!,
786
+ };
787
+ });
788
+
789
+ export const PluginArtifactManifestSchema = z.object({
790
+ schemaVersion: z.literal(1),
791
+ name: z.string(),
792
+ version: z.string(),
793
+ minPluginApiVersion: SemanticVersionSchema,
794
+ apiVersion: z.literal(2).optional(),
795
+ type: PluginTypeSchema,
796
+ instanceScope: PluginInstanceScopeSchema.optional().default('operator'),
797
+ permissions: z.array(PluginPermissionSchema).optional().default([]),
798
+ strategyFeatures: StrategyFeaturesSchema,
646
799
  });
800
+ export type PluginArtifactManifest = z.infer<typeof PluginArtifactManifestSchema>;
801
+
802
+ export const PluginMarketCatalogSchema = z.discriminatedUnion('schemaVersion', [
803
+ z.object({
804
+ schemaVersion: z.literal(1),
805
+ generatedAt: z.string(),
806
+ channel: PluginMarketChannelSchema,
807
+ plugins: z.array(PluginMarketCatalogEntrySchema),
808
+ }),
809
+ z.object({
810
+ schemaVersion: z.literal(2),
811
+ generatedAt: z.string(),
812
+ channel: PluginMarketChannelSchema,
813
+ plugins: z.array(PluginMarketCatalogV2EntrySchema),
814
+ }),
815
+ ]);
647
816
  export type PluginMarketCatalog = z.infer<typeof PluginMarketCatalogSchema>;
648
817
 
649
818
  export const PluginMarketCatalogResponseSchema = z.object({
@@ -686,6 +855,8 @@ export const PluginsConfigSchema = z.object({
686
855
  z.string(),
687
856
  z.record(z.string(), z.record(z.string(), z.unknown()))
688
857
  ).optional().default({}),
858
+ /** 每操作员暂停的 transmit-control 插件:operatorId → pluginName[] */
859
+ operatorPluginPauses: z.record(z.string(), z.array(z.string())).optional().default({}),
689
860
  });
690
861
  export type PluginsConfig = z.infer<typeof PluginsConfigSchema>;
691
862
 
@@ -16,9 +16,7 @@ export const PSKReporterStatsSchema = z.object({
16
16
  consecutiveFailures: z.number().default(0),
17
17
  });
18
18
 
19
- /**
20
- * PSKReporter 配置 Schema
21
- */
19
+ /** PSK Reporter publishing identity, antenna metadata, interval and counters. */
22
20
  export const PSKReporterConfigSchema = z.object({
23
21
  /** 是否启用 PSKReporter 上报 */
24
22
  enabled: z.boolean().default(false),
@@ -49,6 +49,61 @@ export const SubdivisionConfidenceSchema = z.enum([
49
49
  'low',
50
50
  ]);
51
51
 
52
+ /** Maximum UTF-8 JSON size of contest-owned data persisted with one QSO. */
53
+ export const CONTEST_QSO_ENVELOPE_MAX_BYTES = 8 * 1024;
54
+
55
+ const ContestQsoExchangeSchema = z.record(z.string(), z.string());
56
+ const ContestQsoAnnotationValueSchema = z.union([z.string(), z.number().finite(), z.boolean()]);
57
+
58
+ function durableContestQsoJson(value: unknown): string {
59
+ return JSON.stringify(value).replace(/[<>\u007f-\uffff]/g, character => (
60
+ `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`
61
+ ));
62
+ }
63
+
64
+ /**
65
+ * Versioned contest facts that must be committed atomically with their QSO.
66
+ *
67
+ * The shape is deliberately shallow and bounded. Contest plugins may choose
68
+ * their exchange and annotation keys, but cannot persist arbitrary object
69
+ * graphs or binary payloads in the logbook record.
70
+ */
71
+ export const ContestQsoEnvelopeSchema = z.object({
72
+ schemaVersion: z.literal(1),
73
+ contestId: z.string().min(1),
74
+ editionId: z.string().min(1),
75
+ rulesetVersion: z.string().min(1),
76
+ sent: ContestQsoExchangeSchema,
77
+ received: ContestQsoExchangeSchema,
78
+ annotations: z.record(z.string(), ContestQsoAnnotationValueSchema).optional(),
79
+ }).strict().superRefine((value, context) => {
80
+ if (new TextEncoder().encode(durableContestQsoJson(value)).byteLength > CONTEST_QSO_ENVELOPE_MAX_BYTES) {
81
+ context.addIssue({
82
+ code: z.ZodIssueCode.custom,
83
+ message: `Contest QSO envelope must not exceed ${CONTEST_QSO_ENVELOPE_MAX_BYTES} UTF-8 JSON bytes`,
84
+ });
85
+ }
86
+ });
87
+
88
+ /**
89
+ * Serialize a validated contest envelope as ASCII-only JSON for durable ADIF
90
+ * private fields. Escaping non-ASCII and angle brackets keeps legacy string
91
+ * ADIF readers byte-safe without changing the decoded data.
92
+ */
93
+ export function serializeContestQsoEnvelope(value: ContestQsoEnvelope): string {
94
+ return durableContestQsoJson(ContestQsoEnvelopeSchema.parse(value));
95
+ }
96
+
97
+ /** Parse and validate an envelope read from a durable private field. */
98
+ export function parseContestQsoEnvelope(value: string): ContestQsoEnvelope | undefined {
99
+ try {
100
+ const parsed = ContestQsoEnvelopeSchema.safeParse(JSON.parse(value));
101
+ return parsed.success ? parsed.data : undefined;
102
+ } catch {
103
+ return undefined;
104
+ }
105
+ }
106
+
52
107
  // 操作配置
53
108
  export const OperatorConfigSchema = z.object({
54
109
  id: z.string(),
@@ -60,6 +115,7 @@ export const OperatorConfigSchema = z.object({
60
115
  maxQSOTimeoutCycles: z.number(),
61
116
  maxCallAttempts: z.number(),
62
117
  frequency: z.number(),
118
+ maxConcurrentStreams: z.number().int().min(1).max(5).optional(),
63
119
  autoResumeCQAfterFail: z.boolean().default(false),
64
120
  autoResumeCQAfterSuccess: z.boolean().default(false),
65
121
  replyToWorkedStations: z.boolean().default(false),
@@ -91,6 +147,13 @@ export const QslSimpleStatusSchema = z.enum(['Y', 'N']).optional();
91
147
  * - DXCC enrichment and review metadata;
92
148
  * - LoTW / QRZ confirmation state;
93
149
  * - station-location metadata used when exporting ADIF-like records.
150
+ *
151
+ * `frequency` is the RF frequency in hertz; `startTime`/`endTime` and QSL dates
152
+ * are Unix epoch milliseconds. `mode` is the ADIF main mode and `submode` is the
153
+ * optional ADIF submode. `messageHistory` contains the raw digital-mode exchange.
154
+ * Sync providers update the service-specific LoTW/QRZ fields using ADIF status
155
+ * codes (`Y`, `N`, `R`, `Q`, `I`, and received-only `V`) rather than a generic
156
+ * `qslStatus` field.
94
157
  */
95
158
  export const QSORecordSchema = z.object({
96
159
  id: z.string(),
@@ -105,6 +168,8 @@ export const QSORecordSchema = z.object({
105
168
  reportReceived: z.string().optional(), // 接收的信号报告
106
169
  messageHistory: z.array(z.string()), // 内部消息历史(FT8 等数字模式)
107
170
  comment: z.string().optional(), // 标准 ADIF COMMENT 字段
171
+ contestId: z.string().optional(), // ADIF CONTEST_ID
172
+ contestEntry: ContestQsoEnvelopeSchema.optional(), // TX-5DR atomic contest facts
108
173
  myCallsign: z.string().optional(), // 我的呼号(操作员呼号)
109
174
  myGrid: z.string().optional(), // 我的网格定位(操作员网格)
110
175
  qth: z.string().optional(), // 对方 QTH(地点,语音通联常用)
@@ -141,8 +206,19 @@ export const QSORecordSchema = z.object({
141
206
 
142
207
  // 附注(对应 ADIF NOTES 字段)
143
208
  notes: z.string().optional(),
209
+ }).superRefine((value, context) => {
210
+ if (value.contestId && value.contestEntry && value.contestId !== value.contestEntry.contestId) {
211
+ context.addIssue({
212
+ code: z.ZodIssueCode.custom,
213
+ path: ['contestEntry', 'contestId'],
214
+ message: 'contestEntry.contestId must match contestId',
215
+ });
216
+ }
144
217
  });
145
218
 
219
+ /** Host persistence behavior for an automatically completed QSO. */
220
+ export const QSOPersistencePolicySchema = z.enum(['merge-nearby', 'preserve-distinct']);
221
+
146
222
  // 策略执行结果
147
223
  export const StrategiesResultSchema = z.object({
148
224
  stop: z.boolean().optional(),
@@ -162,6 +238,7 @@ export type DxccStatus = z.infer<typeof DxccStatusSchema>;
162
238
  export type DxccSource = z.infer<typeof DxccSourceSchema>;
163
239
  export type DxccConfidence = z.infer<typeof DxccConfidenceSchema>;
164
240
  export type QSOContext = z.infer<typeof QSOContextSchema>;
241
+ export type ContestQsoEnvelope = z.infer<typeof ContestQsoEnvelopeSchema>;
165
242
 
166
243
  /**
167
244
  * Canonical persisted QSO record used by TX-5DR logbooks and plugin hooks.
@@ -170,4 +247,5 @@ export type QSOContext = z.infer<typeof QSOContextSchema>;
170
247
  * logbook queries.
171
248
  */
172
249
  export type QSORecord = z.infer<typeof QSORecordSchema>;
250
+ export type QSOPersistencePolicy = z.infer<typeof QSOPersistencePolicySchema>;
173
251
  export type StrategiesResult = z.infer<typeof StrategiesResultSchema>;
@@ -36,9 +36,7 @@ export type CapabilityUpdateMode = z.infer<typeof CapabilityUpdateModeSchema>;
36
36
  export const CapabilityOptionValueSchema = z.union([z.string(), z.number()]);
37
37
  export type CapabilityOptionValue = z.infer<typeof CapabilityOptionValueSchema>;
38
38
 
39
- /**
40
- * 运行时能力值。
41
- */
39
+ /** Runtime value carried by a radio capability state. */
42
40
  export const CapabilityValueSchema = z.union([z.boolean(), z.number(), z.string()]);
43
41
  export type CapabilityValue = z.infer<typeof CapabilityValueSchema>;
44
42
 
@@ -84,10 +82,7 @@ export const CapabilityDisplaySchema = z.object({
84
82
  });
85
83
  export type CapabilityDisplay = z.infer<typeof CapabilityDisplaySchema>;
86
84
 
87
- /**
88
- * 能力描述符
89
- * 由服务端在运行时下发,作为当前连接会话的真源。
90
- */
85
+ /** Host-provided descriptor that defines one capability for the current session. */
91
86
  export const CapabilityDescriptorSchema = z.object({
92
87
  /** 全局唯一能力 ID,如 'tuner_switch', 'rf_power', 'lock_mode' */
93
88
  id: z.string(),
@@ -165,10 +160,7 @@ export const CapabilityDescriptorSchema = z.object({
165
160
 
166
161
  export type CapabilityDescriptor = z.infer<typeof CapabilityDescriptorSchema>;
167
162
 
168
- /**
169
- * 能力运行时状态
170
- * 动态数据,通过 WebSocket 实时同步到前端。
171
- */
163
+ /** Dynamic support, availability and value state for one capability. */
172
164
  export const CapabilityStateSchema = z.object({
173
165
  /** 能力 ID,与 CapabilityDescriptor.id 对应 */
174
166
  id: z.string(),
@@ -209,9 +201,7 @@ export const CapabilityStateSchema = z.object({
209
201
 
210
202
  export type CapabilityState = z.infer<typeof CapabilityStateSchema>;
211
203
 
212
- /**
213
- * 能力列表快照(radioCapabilityList WS 消息 / REST 响应的 data 部分)
214
- */
204
+ /** Complete descriptor/state snapshot returned by radio capability APIs. */
215
205
  export const CapabilityListSchema = z.object({
216
206
  descriptors: z.array(CapabilityDescriptorSchema),
217
207
  capabilities: z.array(CapabilityStateSchema),
@@ -220,7 +210,8 @@ export const CapabilityListSchema = z.object({
220
210
  export type CapabilityList = z.infer<typeof CapabilityListSchema>;
221
211
 
222
212
  /**
223
- * 写命令负载(writeRadioCapability WS 命令的 data 部分)
213
+ * Legacy WebSocket payload for writing a generic radio capability.
214
+ * Plugin API v2 uses explicit radio/tuner command ports instead.
224
215
  */
225
216
  export const WriteCapabilityPayloadSchema = z.object({
226
217
  /** 能力 ID */
@@ -284,6 +275,7 @@ export const CAPABILITY_IDS = [
284
275
  'agc_mode',
285
276
  'preamp',
286
277
  'attenuator',
278
+ 'radio_mode',
287
279
  'mode_bandwidth',
288
280
  'split_enabled',
289
281
  'vfo_select',
@@ -303,6 +295,7 @@ export const CAPABILITY_IDS = [
303
295
  'spectrum_rbw',
304
296
  'spectrum_during_tx',
305
297
  'spectrum_center_type',
298
+ 'tci_iq_sample_rate',
306
299
  'dcs_code',
307
300
  ] as const;
308
301
 
@@ -111,6 +111,9 @@ export function decidePowerSupport(
111
111
  // ICOM WLAN 的 CI-V-over-UDP 通道在电台关机后无法维持;即便已连接
112
112
  // 发送 powerstat(off) 也不能可靠恢复。整体不暴露电源控制。
113
113
  return { canPowerOn: false, canPowerOff: false, supportedStates: [], reason: 'model-unsupported' };
114
+ case 'tci':
115
+ // TCI/ExpertSDR does not expose a safe physical power-on path here.
116
+ return { canPowerOn: false, canPowerOff: false, supportedStates: [], reason: 'model-unsupported' };
114
117
  case 'serial': {
115
118
  if (!rigInfo) {
116
119
  return { canPowerOn: false, canPowerOff: false, supportedStates: [], reason: 'model-unsupported' };