@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.
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/schema/__tests__/android-operator-audio.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/android-operator-audio.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/android-operator-audio.schema.test.js +27 -0
- package/dist/schema/__tests__/android-operator-audio.schema.test.js.map +1 -0
- package/dist/schema/__tests__/assisted-queue.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/assisted-queue.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/assisted-queue.schema.test.js +114 -0
- package/dist/schema/__tests__/assisted-queue.schema.test.js.map +1 -0
- package/dist/schema/__tests__/audio-sidecar.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/audio-sidecar.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/audio-sidecar.schema.test.js +42 -0
- package/dist/schema/__tests__/audio-sidecar.schema.test.js.map +1 -0
- package/dist/schema/__tests__/audio.schema.test.js +92 -1
- package/dist/schema/__tests__/audio.schema.test.js.map +1 -1
- package/dist/schema/__tests__/diagnostics.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/diagnostics.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/diagnostics.schema.test.js +31 -0
- package/dist/schema/__tests__/diagnostics.schema.test.js.map +1 -0
- package/dist/schema/__tests__/logbook-health.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/logbook-health.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/logbook-health.schema.test.js +104 -0
- package/dist/schema/__tests__/logbook-health.schema.test.js.map +1 -0
- package/dist/schema/__tests__/meter.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/meter.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/meter.schema.test.js +27 -0
- package/dist/schema/__tests__/meter.schema.test.js.map +1 -0
- package/dist/schema/__tests__/observability.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/observability.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/observability.schema.test.js +12 -0
- package/dist/schema/__tests__/observability.schema.test.js.map +1 -0
- package/dist/schema/__tests__/operator-context-report.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/operator-context-report.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/operator-context-report.schema.test.js +70 -0
- package/dist/schema/__tests__/operator-context-report.schema.test.js.map +1 -0
- package/dist/schema/__tests__/operator.schema.test.js +9 -3
- package/dist/schema/__tests__/operator.schema.test.js.map +1 -1
- package/dist/schema/__tests__/plugin-market.schema.test.js +149 -3
- package/dist/schema/__tests__/plugin-market.schema.test.js.map +1 -1
- package/dist/schema/__tests__/qso.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/qso.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/qso.schema.test.js +70 -0
- package/dist/schema/__tests__/qso.schema.test.js.map +1 -0
- package/dist/schema/__tests__/radio-power-support.test.js +15 -0
- package/dist/schema/__tests__/radio-power-support.test.js.map +1 -1
- package/dist/schema/__tests__/radio-profile.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/radio-profile.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/radio-profile.schema.test.js +36 -0
- package/dist/schema/__tests__/radio-profile.schema.test.js.map +1 -0
- package/dist/schema/__tests__/radio.schema.test.js +92 -1
- package/dist/schema/__tests__/radio.schema.test.js.map +1 -1
- package/dist/schema/__tests__/realtime-settings.schema.test.js +2 -1
- package/dist/schema/__tests__/realtime-settings.schema.test.js.map +1 -1
- package/dist/schema/__tests__/remote-access.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/remote-access.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/remote-access.schema.test.js +18 -0
- package/dist/schema/__tests__/remote-access.schema.test.js.map +1 -0
- package/dist/schema/__tests__/spectrum.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/spectrum.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/spectrum.schema.test.js +107 -0
- package/dist/schema/__tests__/spectrum.schema.test.js.map +1 -0
- package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts +2 -0
- package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts.map +1 -0
- package/dist/schema/__tests__/strategy-transmission.schema.test.js +95 -0
- package/dist/schema/__tests__/strategy-transmission.schema.test.js.map +1 -0
- package/dist/schema/android-operator-audio.schema.d.ts +174 -0
- package/dist/schema/android-operator-audio.schema.d.ts.map +1 -0
- package/dist/schema/android-operator-audio.schema.js +38 -0
- package/dist/schema/android-operator-audio.schema.js.map +1 -0
- package/dist/schema/api-response.schema.d.ts +503 -44
- package/dist/schema/api-response.schema.d.ts.map +1 -1
- package/dist/schema/api-response.schema.js +9 -0
- package/dist/schema/api-response.schema.js.map +1 -1
- package/dist/schema/audio-sidecar.schema.d.ts +75 -0
- package/dist/schema/audio-sidecar.schema.d.ts.map +1 -1
- package/dist/schema/audio-sidecar.schema.js +32 -0
- package/dist/schema/audio-sidecar.schema.js.map +1 -1
- package/dist/schema/audio.schema.d.ts +4749 -3
- package/dist/schema/audio.schema.d.ts.map +1 -1
- package/dist/schema/audio.schema.js +101 -2
- package/dist/schema/audio.schema.js.map +1 -1
- package/dist/schema/auth.schema.d.ts +173 -14
- package/dist/schema/auth.schema.d.ts.map +1 -1
- package/dist/schema/auth.schema.js +65 -1
- package/dist/schema/auth.schema.js.map +1 -1
- package/dist/schema/cpu-profile.schema.d.ts +2 -2
- package/dist/schema/cw-decoder.schema.d.ts +74 -74
- package/dist/schema/cw-keyer.schema.d.ts +20 -14
- package/dist/schema/cw-keyer.schema.d.ts.map +1 -1
- package/dist/schema/cw-keyer.schema.js +3 -0
- package/dist/schema/cw-keyer.schema.js.map +1 -1
- package/dist/schema/desktop-https.schema.d.ts +6 -6
- package/dist/schema/device-ui.schema.d.ts +16 -16
- package/dist/schema/diagnostics.schema.d.ts +163 -0
- package/dist/schema/diagnostics.schema.d.ts.map +1 -0
- package/dist/schema/diagnostics.schema.js +46 -0
- package/dist/schema/diagnostics.schema.js.map +1 -0
- package/dist/schema/ft8.schema.d.ts +12 -2
- package/dist/schema/ft8.schema.d.ts.map +1 -1
- package/dist/schema/ft8.schema.js +8 -0
- package/dist/schema/ft8.schema.js.map +1 -1
- package/dist/schema/image-radio.schema.d.ts +2597 -0
- package/dist/schema/image-radio.schema.d.ts.map +1 -0
- package/dist/schema/image-radio.schema.js +283 -0
- package/dist/schema/image-radio.schema.js.map +1 -0
- package/dist/schema/logbook.schema.d.ts +2128 -18
- package/dist/schema/logbook.schema.d.ts.map +1 -1
- package/dist/schema/logbook.schema.js +159 -1
- package/dist/schema/logbook.schema.js.map +1 -1
- package/dist/schema/mode.schema.d.ts +4 -6
- package/dist/schema/mode.schema.d.ts.map +1 -1
- package/dist/schema/mode.schema.js +18 -6
- package/dist/schema/mode.schema.js.map +1 -1
- package/dist/schema/observability.schema.d.ts +66 -0
- package/dist/schema/observability.schema.d.ts.map +1 -0
- package/dist/schema/observability.schema.js +20 -0
- package/dist/schema/observability.schema.js.map +1 -0
- package/dist/schema/openwebrx.schema.d.ts +2 -2
- package/dist/schema/operator.schema.d.ts +2682 -6
- package/dist/schema/operator.schema.d.ts.map +1 -1
- package/dist/schema/operator.schema.js +10 -20
- package/dist/schema/operator.schema.js.map +1 -1
- package/dist/schema/plugin.schema.d.ts +1977 -492
- package/dist/schema/plugin.schema.d.ts.map +1 -1
- package/dist/schema/plugin.schema.js +165 -14
- package/dist/schema/plugin.schema.js.map +1 -1
- package/dist/schema/process-monitor.schema.d.ts +132 -132
- package/dist/schema/pskreporter.schema.d.ts +1 -3
- package/dist/schema/pskreporter.schema.d.ts.map +1 -1
- package/dist/schema/pskreporter.schema.js +1 -3
- package/dist/schema/pskreporter.schema.js.map +1 -1
- package/dist/schema/qso.schema.d.ts +246 -1
- package/dist/schema/qso.schema.d.ts.map +1 -1
- package/dist/schema/qso.schema.js +68 -0
- package/dist/schema/qso.schema.js.map +1 -1
- package/dist/schema/radio-capability.schema.d.ts +31 -40
- package/dist/schema/radio-capability.schema.d.ts.map +1 -1
- package/dist/schema/radio-capability.schema.js +8 -15
- package/dist/schema/radio-capability.schema.js.map +1 -1
- package/dist/schema/radio-power-support.d.ts.map +1 -1
- package/dist/schema/radio-power-support.js +3 -0
- package/dist/schema/radio-power-support.js.map +1 -1
- package/dist/schema/radio-power.schema.d.ts +102 -13
- package/dist/schema/radio-power.schema.d.ts.map +1 -1
- package/dist/schema/radio-power.schema.js +15 -8
- package/dist/schema/radio-power.schema.js.map +1 -1
- package/dist/schema/radio-profile.schema.d.ts +1491 -97
- package/dist/schema/radio-profile.schema.d.ts.map +1 -1
- package/dist/schema/radio-profile.schema.js +7 -0
- package/dist/schema/radio-profile.schema.js.map +1 -1
- package/dist/schema/radio.schema.d.ts +442 -48
- package/dist/schema/radio.schema.d.ts.map +1 -1
- package/dist/schema/radio.schema.js +75 -7
- package/dist/schema/radio.schema.js.map +1 -1
- package/dist/schema/realtime.schema.d.ts +60 -52
- package/dist/schema/realtime.schema.d.ts.map +1 -1
- package/dist/schema/realtime.schema.js +9 -1
- package/dist/schema/realtime.schema.js.map +1 -1
- package/dist/schema/rigctld.schema.d.ts +6 -6
- package/dist/schema/slot-info.schema.d.ts +28 -9
- package/dist/schema/slot-info.schema.d.ts.map +1 -1
- package/dist/schema/slot-info.schema.js +17 -10
- package/dist/schema/slot-info.schema.js.map +1 -1
- package/dist/schema/spectrum-config.schema.d.ts +638 -0
- package/dist/schema/spectrum-config.schema.d.ts.map +1 -0
- package/dist/schema/spectrum-config.schema.js +129 -0
- package/dist/schema/spectrum-config.schema.js.map +1 -0
- package/dist/schema/spectrum.schema.d.ts +1323 -30
- package/dist/schema/spectrum.schema.d.ts.map +1 -1
- package/dist/schema/spectrum.schema.js +72 -0
- package/dist/schema/spectrum.schema.js.map +1 -1
- package/dist/schema/station-info.schema.d.ts +5 -0
- package/dist/schema/station-info.schema.d.ts.map +1 -1
- package/dist/schema/station-info.schema.js +5 -0
- package/dist/schema/station-info.schema.js.map +1 -1
- package/dist/schema/system.schema.d.ts +56 -6
- package/dist/schema/system.schema.d.ts.map +1 -1
- package/dist/schema/system.schema.js +22 -0
- package/dist/schema/system.schema.js.map +1 -1
- package/dist/schema/transmission.schema.d.ts +8 -7
- package/dist/schema/transmission.schema.d.ts.map +1 -1
- package/dist/schema/transmission.schema.js +2 -7
- package/dist/schema/transmission.schema.js.map +1 -1
- package/dist/schema/tx-audio-input.schema.d.ts +8 -0
- package/dist/schema/tx-audio-input.schema.d.ts.map +1 -0
- package/dist/schema/tx-audio-input.schema.js +19 -0
- package/dist/schema/tx-audio-input.schema.js.map +1 -0
- package/dist/schema/voice-keyer.schema.d.ts +16 -16
- package/dist/schema/voice.schema.d.ts +1 -1
- package/dist/schema/voice.schema.d.ts.map +1 -1
- package/dist/schema/voice.schema.js +1 -1
- package/dist/schema/voice.schema.js.map +1 -1
- package/dist/schema/websocket.schema.d.ts +35811 -7829
- package/dist/schema/websocket.schema.d.ts.map +1 -1
- package/dist/schema/websocket.schema.js +453 -9
- package/dist/schema/websocket.schema.js.map +1 -1
- package/package.json +6 -1
- package/src/index.ts +7 -0
- package/src/schema/__tests__/android-operator-audio.schema.test.ts +31 -0
- package/src/schema/__tests__/assisted-queue.schema.test.ts +122 -0
- package/src/schema/__tests__/audio-sidecar.schema.test.ts +48 -0
- package/src/schema/__tests__/audio.schema.test.ts +102 -0
- package/src/schema/__tests__/diagnostics.schema.test.ts +32 -0
- package/src/schema/__tests__/logbook-health.schema.test.ts +117 -0
- package/src/schema/__tests__/meter.schema.test.ts +28 -0
- package/src/schema/__tests__/observability.schema.test.ts +15 -0
- package/src/schema/__tests__/operator-context-report.schema.test.ts +77 -0
- package/src/schema/__tests__/operator.schema.test.ts +11 -3
- package/src/schema/__tests__/plugin-market.schema.test.ts +165 -2
- package/src/schema/__tests__/qso.schema.test.ts +91 -0
- package/src/schema/__tests__/radio-power-support.test.ts +17 -0
- package/src/schema/__tests__/radio-profile.schema.test.ts +44 -0
- package/src/schema/__tests__/radio.schema.test.ts +108 -1
- package/src/schema/__tests__/realtime-settings.schema.test.ts +2 -1
- package/src/schema/__tests__/remote-access.schema.test.ts +22 -0
- package/src/schema/__tests__/spectrum.schema.test.ts +119 -0
- package/src/schema/__tests__/strategy-transmission.schema.test.ts +106 -0
- package/src/schema/android-operator-audio.schema.ts +49 -0
- package/src/schema/api-response.schema.ts +11 -0
- package/src/schema/audio-sidecar.schema.ts +35 -0
- package/src/schema/audio.schema.ts +115 -2
- package/src/schema/auth.schema.ts +79 -1
- package/src/schema/cw-keyer.schema.ts +5 -0
- package/src/schema/diagnostics.schema.ts +55 -0
- package/src/schema/ft8.schema.ts +8 -0
- package/src/schema/image-radio.schema.ts +342 -0
- package/src/schema/logbook.schema.ts +205 -1
- package/src/schema/mode.schema.ts +18 -6
- package/src/schema/observability.schema.ts +26 -0
- package/src/schema/operator.schema.ts +11 -20
- package/src/schema/plugin.schema.ts +186 -15
- package/src/schema/pskreporter.schema.ts +1 -3
- package/src/schema/qso.schema.ts +78 -0
- package/src/schema/radio-capability.schema.ts +8 -15
- package/src/schema/radio-power-support.ts +3 -0
- package/src/schema/radio-power.schema.ts +15 -8
- package/src/schema/radio-profile.schema.ts +7 -0
- package/src/schema/radio.schema.ts +90 -7
- package/src/schema/realtime.schema.ts +9 -1
- package/src/schema/slot-info.schema.ts +18 -11
- package/src/schema/spectrum-config.schema.ts +156 -0
- package/src/schema/spectrum.schema.ts +89 -0
- package/src/schema/station-info.schema.ts +5 -0
- package/src/schema/system.schema.ts +29 -0
- package/src/schema/transmission.schema.ts +2 -7
- package/src/schema/tx-audio-input.schema.ts +22 -0
- package/src/schema/voice.schema.ts +1 -1
- package/src/schema/websocket.schema.ts +601 -13
- package/test/image-radio-schema.test.ts +107 -0
- package/test/plugin-slot-schema.test.ts +31 -0
- package/test/radio-capability-schema.test.ts +44 -0
- 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:
|
|
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:
|
|
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([
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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),
|
package/src/schema/qso.schema.ts
CHANGED
|
@@ -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
|
-
*
|
|
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' };
|