@tx5dr/contracts 1.4.0 → 1.6.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 (63) hide show
  1. package/dist/schema/__tests__/radio-power-support.test.d.ts +2 -0
  2. package/dist/schema/__tests__/radio-power-support.test.d.ts.map +1 -0
  3. package/dist/schema/__tests__/radio-power-support.test.js +19 -0
  4. package/dist/schema/__tests__/radio-power-support.test.js.map +1 -0
  5. package/dist/schema/__tests__/realtime-settings.schema.test.d.ts +2 -0
  6. package/dist/schema/__tests__/realtime-settings.schema.test.d.ts.map +1 -0
  7. package/dist/schema/__tests__/realtime-settings.schema.test.js +122 -0
  8. package/dist/schema/__tests__/realtime-settings.schema.test.js.map +1 -0
  9. package/dist/schema/cpu-profile.schema.d.ts +4 -4
  10. package/dist/schema/desktop-https.schema.d.ts +9 -0
  11. package/dist/schema/desktop-https.schema.d.ts.map +1 -1
  12. package/dist/schema/desktop-https.schema.js +3 -0
  13. package/dist/schema/desktop-https.schema.js.map +1 -1
  14. package/dist/schema/openwebrx.schema.d.ts +1 -1
  15. package/dist/schema/openwebrx.schema.d.ts.map +1 -1
  16. package/dist/schema/openwebrx.schema.js +1 -1
  17. package/dist/schema/openwebrx.schema.js.map +1 -1
  18. package/dist/schema/operator.schema.d.ts +32 -32
  19. package/dist/schema/plugin.schema.d.ts +702 -168
  20. package/dist/schema/plugin.schema.d.ts.map +1 -1
  21. package/dist/schema/plugin.schema.js +38 -3
  22. package/dist/schema/plugin.schema.js.map +1 -1
  23. package/dist/schema/radio-capability.schema.d.ts +42 -2
  24. package/dist/schema/radio-capability.schema.d.ts.map +1 -1
  25. package/dist/schema/radio-capability.schema.js +18 -1
  26. package/dist/schema/radio-capability.schema.js.map +1 -1
  27. package/dist/schema/radio-power-support.d.ts +8 -0
  28. package/dist/schema/radio-power-support.d.ts.map +1 -1
  29. package/dist/schema/radio-power-support.js +38 -26
  30. package/dist/schema/radio-power-support.js.map +1 -1
  31. package/dist/schema/radio-power.schema.d.ts +10 -6
  32. package/dist/schema/radio-power.schema.d.ts.map +1 -1
  33. package/dist/schema/radio-power.schema.js +8 -6
  34. package/dist/schema/radio-power.schema.js.map +1 -1
  35. package/dist/schema/realtime.schema.d.ts +797 -417
  36. package/dist/schema/realtime.schema.d.ts.map +1 -1
  37. package/dist/schema/realtime.schema.js +205 -51
  38. package/dist/schema/realtime.schema.js.map +1 -1
  39. package/dist/schema/server-message-keys.d.ts +2 -2
  40. package/dist/schema/server-message-keys.d.ts.map +1 -1
  41. package/dist/schema/server-message-keys.js +2 -2
  42. package/dist/schema/server-message-keys.js.map +1 -1
  43. package/dist/schema/slot-info.schema.d.ts +6 -0
  44. package/dist/schema/slot-info.schema.d.ts.map +1 -1
  45. package/dist/schema/slot-info.schema.js +3 -1
  46. package/dist/schema/slot-info.schema.js.map +1 -1
  47. package/dist/schema/websocket.schema.d.ts +1436 -640
  48. package/dist/schema/websocket.schema.d.ts.map +1 -1
  49. package/dist/schema/websocket.schema.js +2 -1
  50. package/dist/schema/websocket.schema.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/schema/__tests__/radio-power-support.test.ts +26 -0
  53. package/src/schema/__tests__/realtime-settings.schema.test.ts +142 -0
  54. package/src/schema/desktop-https.schema.ts +3 -1
  55. package/src/schema/openwebrx.schema.ts +1 -1
  56. package/src/schema/plugin.schema.ts +44 -3
  57. package/src/schema/radio-capability.schema.ts +25 -1
  58. package/src/schema/radio-power-support.ts +49 -26
  59. package/src/schema/radio-power.schema.ts +8 -6
  60. package/src/schema/realtime.schema.ts +247 -65
  61. package/src/schema/server-message-keys.ts +2 -2
  62. package/src/schema/slot-info.schema.ts +3 -1
  63. package/src/schema/websocket.schema.ts +3 -1
@@ -36,7 +36,7 @@ export type PluginInstanceScope = z.infer<typeof PluginInstanceScopeSchema>;
36
36
  * Permissions let the host gate sensitive capabilities behind manifest-level
37
37
  * intent. Plugins should request the smallest possible set.
38
38
  */
39
- export const PluginPermissionSchema = z.enum(['network']);
39
+ export const PluginPermissionSchema = z.enum(['network', 'radio:read', 'radio:control', 'radio:power']);
40
40
 
41
41
  /**
42
42
  * Explicit permission declarations requested by a plugin.
@@ -61,7 +61,7 @@ export type PluginPanelComponent = z.infer<typeof PluginPanelComponentSchema>;
61
61
  * These values control both validation expectations and default frontend
62
62
  * rendering in plugin settings UIs.
63
63
  */
64
- export const PluginSettingTypeSchema = z.enum(['boolean', 'number', 'string', 'string[]', 'info']);
64
+ export const PluginSettingTypeSchema = z.enum(['boolean', 'number', 'string', 'string[]', 'object[]', 'info']);
65
65
 
66
66
  /**
67
67
  * Supported generated-form field types for plugin settings.
@@ -81,6 +81,16 @@ export const PluginSettingOptionSchema = z.object({
81
81
  */
82
82
  export type PluginSettingOption = z.infer<typeof PluginSettingOptionSchema>;
83
83
 
84
+ export const PluginObjectArrayFieldSchema = z.object({
85
+ key: z.string(),
86
+ type: z.enum(['string', 'number', 'boolean']).optional().default('string'),
87
+ label: z.string(),
88
+ description: z.string().optional(),
89
+ placeholder: z.string().optional(),
90
+ required: z.boolean().optional(),
91
+ });
92
+ export type PluginObjectArrayField = z.infer<typeof PluginObjectArrayFieldSchema>;
93
+
84
94
  /**
85
95
  * Persistence and UI scope for a plugin setting.
86
96
  *
@@ -110,6 +120,10 @@ export const PluginSettingDescriptorSchema = z.object({
110
120
  min: z.number().optional(),
111
121
  max: z.number().optional(),
112
122
  options: z.array(PluginSettingOptionSchema).optional(),
123
+ /** Field schema used by generated editors for `object[]` settings. */
124
+ itemFields: z.array(PluginObjectArrayFieldSchema).optional(),
125
+ /** Internal settings are persisted/injected but hidden from generated UIs. */
126
+ hidden: z.boolean().optional(),
113
127
  /** 设置作用域:global(所有操作员共享)或 operator(每操作员独立),默认 global */
114
128
  scope: PluginSettingScopeSchema.optional().default('global'),
115
129
  });
@@ -212,7 +226,8 @@ export type PluginPanelWidth = z.infer<typeof PluginPanelWidthSchema>;
212
226
  *
213
227
  * Panels are passive containers rendered by the host. A plugin sends data into
214
228
  * them through `ctx.ui.send(panelId, data)`. When `component` is `'iframe'`,
215
- * the panel renders a custom UI page inside a sandboxed iframe instead.
229
+ * the panel renders a custom UI page inside a sandboxed iframe instead. Static
230
+ * manifest panels and runtime UI contributions use this same descriptor.
216
231
  */
217
232
  export const PluginPanelDescriptorSchema = z.object({
218
233
  id: z.string(),
@@ -220,6 +235,8 @@ export const PluginPanelDescriptorSchema = z.object({
220
235
  component: PluginPanelComponentSchema,
221
236
  /** Required when `component` is `'iframe'`. References a page id from `ui.pages`. */
222
237
  pageId: z.string().optional(),
238
+ /** Optional string params forwarded to iframe panels as URL/init params. */
239
+ params: z.record(z.string(), z.string()).optional(),
223
240
  /** Where the panel renders. Defaults to `'operator'` (operator card live-panel area). */
224
241
  slot: PluginPanelSlotSchema.optional(),
225
242
  /** Preferred width hint. Defaults to `'half'`. */
@@ -231,6 +248,29 @@ export const PluginPanelDescriptorSchema = z.object({
231
248
  */
232
249
  export type PluginPanelDescriptor = z.infer<typeof PluginPanelDescriptorSchema>;
233
250
 
251
+ export const PluginUIPanelContributionTargetSchema = z.discriminatedUnion('kind', [
252
+ z.object({ kind: z.literal('global') }),
253
+ z.object({ kind: z.literal('operator'), operatorId: z.string() }),
254
+ ]);
255
+ export type PluginUIPanelContributionTarget = z.infer<typeof PluginUIPanelContributionTargetSchema>;
256
+
257
+ /**
258
+ * A normalized group of plugin UI panels.
259
+ *
260
+ * Static `PluginDefinition.panels` are emitted by the host as the reserved
261
+ * `manifest` group. Runtime groups are replaced by
262
+ * `ctx.ui.setPanelContributions(groupId, panels)` and cleared by publishing an
263
+ * empty panel list for the same group.
264
+ */
265
+ export const PluginUIPanelContributionGroupSchema = z.object({
266
+ pluginName: z.string(),
267
+ groupId: z.string(),
268
+ source: z.enum(['manifest', 'runtime']),
269
+ instanceTarget: PluginUIPanelContributionTargetSchema.optional(),
270
+ panels: z.array(PluginPanelDescriptorSchema),
271
+ });
272
+ export type PluginUIPanelContributionGroup = z.infer<typeof PluginUIPanelContributionGroupSchema>;
273
+
234
274
  // ===== 自定义 UI 页面 =====
235
275
 
236
276
  /**
@@ -422,6 +462,7 @@ export const PluginSystemSnapshotSchema = z.object({
422
462
  generation: z.number().int().nonnegative(),
423
463
  plugins: z.array(PluginStatusSchema),
424
464
  panelMeta: z.array(PluginPanelMetaPayloadSchema).optional().default([]),
465
+ panelContributions: z.array(PluginUIPanelContributionGroupSchema).optional().default([]),
425
466
  lastError: z.string().optional(),
426
467
  });
427
468
  export type PluginSystemSnapshot = z.infer<typeof PluginSystemSnapshotSchema>;
@@ -42,6 +42,18 @@ export type CapabilityOptionValue = z.infer<typeof CapabilityOptionValueSchema>;
42
42
  export const CapabilityValueSchema = z.union([z.boolean(), z.number(), z.string()]);
43
43
  export type CapabilityValue = z.infer<typeof CapabilityValueSchema>;
44
44
 
45
+ export const CapabilityAvailabilitySchema = z.enum(['available', 'unavailable', 'unknown']);
46
+ export type CapabilityAvailability = z.infer<typeof CapabilityAvailabilitySchema>;
47
+
48
+ export const CapabilityAvailabilityReasonSchema = z.enum([
49
+ 'runtime_error',
50
+ 'busy',
51
+ 'unsupported_by_current_mode',
52
+ 'radio_reported_unavailable',
53
+ 'unknown',
54
+ ]);
55
+ export type CapabilityAvailabilityReason = z.infer<typeof CapabilityAvailabilityReasonSchema>;
56
+
45
57
  /**
46
58
  * 枚举项定义。
47
59
  */
@@ -161,9 +173,21 @@ export const CapabilityStateSchema = z.object({
161
173
  /** 能力 ID,与 CapabilityDescriptor.id 对应 */
162
174
  id: z.string(),
163
175
 
164
- /** 当前连接的电台是否支持此能力(探测结果) */
176
+ /** 当前连接的电台型号/后端是否声明支持此能力 */
165
177
  supported: z.boolean(),
166
178
 
179
+ /**
180
+ * 当前运行时是否可用。
181
+ * 兼容旧客户端:缺省时应按 supported=true 视为 available,supported=false 视为 unknown。
182
+ */
183
+ availability: CapabilityAvailabilitySchema.optional(),
184
+
185
+ /** 当前不可用的机器可读原因 */
186
+ availabilityReason: CapabilityAvailabilityReasonSchema.optional(),
187
+
188
+ /** 最近一次运行时读写错误摘要 */
189
+ lastError: z.string().optional(),
190
+
167
191
  /**
168
192
  * 当前值
169
193
  * - boolean 类能力:true/false
@@ -17,32 +17,40 @@ import type { HamlibConfig } from './radio.schema.js';
17
17
  export interface PowerCapableRigEntry {
18
18
  mfg: string | RegExp;
19
19
  model: string | RegExp;
20
+ /**
21
+ * Physical powerstat targets that are safe to expose while CAT is connected.
22
+ * `on` is represented by canPowerOn and is not part of this connected-state list.
23
+ */
24
+ supportedStates?: ReadonlyArray<'operate' | 'standby' | 'off'>;
20
25
  }
21
26
 
22
27
  export const POWER_CAPABLE_RIGS: ReadonlyArray<PowerCapableRigEntry> = [
23
28
  // Icom modern transceivers (CI-V 0x18 command)
24
- { mfg: /^icom$/i, model: /^IC-?705$/i },
25
- { mfg: /^icom$/i, model: /^IC-?7300$/i },
26
- { mfg: /^icom$/i, model: /^IC-?7610$/i },
27
- { mfg: /^icom$/i, model: /^IC-?7100$/i },
28
- { mfg: /^icom$/i, model: /^IC-?7851$/i },
29
- { mfg: /^icom$/i, model: /^IC-?9700$/i },
30
- { mfg: /^icom$/i, model: /^IC-?R8600$/i },
29
+ { mfg: /^icom$/i, model: /^IC-?705$/i, supportedStates: ['off', 'standby', 'operate'] },
30
+ { mfg: /^icom$/i, model: /^IC-?7300$/i, supportedStates: ['off', 'standby', 'operate'] },
31
+ { mfg: /^icom$/i, model: /^IC-?7610$/i, supportedStates: ['off', 'standby', 'operate'] },
32
+ { mfg: /^icom$/i, model: /^IC-?7100$/i, supportedStates: ['off', 'standby', 'operate'] },
33
+ { mfg: /^icom$/i, model: /^IC-?7851$/i, supportedStates: ['off', 'standby', 'operate'] },
34
+ { mfg: /^icom$/i, model: /^IC-?9700$/i, supportedStates: ['off', 'standby', 'operate'] },
35
+ { mfg: /^icom$/i, model: /^IC-?R8600$/i, supportedStates: ['off', 'standby', 'operate'] },
31
36
 
32
37
  // Kenwood
33
- { mfg: /^kenwood$/i, model: /^TS-590SG?$/i },
34
- { mfg: /^kenwood$/i, model: /^TS-890$/i },
35
- { mfg: /^kenwood$/i, model: /^TS-990$/i },
38
+ { mfg: /^kenwood$/i, model: /^TS-590SG?$/i, supportedStates: ['off', 'standby'] },
39
+ { mfg: /^kenwood$/i, model: /^TS-890$/i, supportedStates: ['off', 'standby'] },
40
+ { mfg: /^kenwood$/i, model: /^TS-990$/i, supportedStates: ['off', 'standby'] },
36
41
 
37
- // Yaesu
38
- { mfg: /^yaesu$/i, model: /^FT-991A?$/i },
39
- { mfg: /^yaesu$/i, model: /^FTDX-?10$/i },
40
- { mfg: /^yaesu$/i, model: /^FTDX-?101(MP|D)?$/i },
41
- { mfg: /^yaesu$/i, model: /^FT-?710$/i },
42
+ // Yaesu: FT-710 rejects powerstat(operate), so do not expose it by default.
43
+ { mfg: /^yaesu$/i, model: /^FT-991A?$/i, supportedStates: ['off', 'standby'] },
44
+ { mfg: /^yaesu$/i, model: /^FTDX-?10$/i, supportedStates: ['off', 'standby'] },
45
+ { mfg: /^yaesu$/i, model: /^FTDX-?101(MP|D)?$/i, supportedStates: ['off', 'standby'] },
46
+ { mfg: /^yaesu$/i, model: /^FT-?710$/i, supportedStates: ['off'] },
42
47
  ];
43
48
 
44
- export function isRigModelPowerCapable(mfgName: string, modelName: string): boolean {
45
- return POWER_CAPABLE_RIGS.some((entry) => {
49
+ export function findPowerCapableRigEntry(
50
+ mfgName: string,
51
+ modelName: string
52
+ ): PowerCapableRigEntry | undefined {
53
+ return POWER_CAPABLE_RIGS.find((entry) => {
46
54
  const mfgOk =
47
55
  typeof entry.mfg === 'string'
48
56
  ? entry.mfg.toLowerCase() === mfgName.toLowerCase()
@@ -55,6 +63,10 @@ export function isRigModelPowerCapable(mfgName: string, modelName: string): bool
55
63
  });
56
64
  }
57
65
 
66
+ export function isRigModelPowerCapable(mfgName: string, modelName: string): boolean {
67
+ return findPowerCapableRigEntry(mfgName, modelName) !== undefined;
68
+ }
69
+
58
70
  export type PowerSupportReason =
59
71
  | 'model-unsupported'
60
72
  | 'network-mode-no-wake'
@@ -65,6 +77,8 @@ export interface PowerSupportDecision {
65
77
  canPowerOn: boolean;
66
78
  /** Whether the UI should surface a "power off" control. */
67
79
  canPowerOff: boolean;
80
+ /** Connected-state physical power targets the UI may offer. */
81
+ supportedStates: Array<'operate' | 'standby' | 'off'>;
68
82
  /** Machine-readable reason when `canPowerOn` is false. */
69
83
  reason?: PowerSupportReason;
70
84
  }
@@ -85,23 +99,32 @@ export function decidePowerSupport(
85
99
  ): PowerSupportDecision {
86
100
  switch (config.type) {
87
101
  case 'none':
88
- return { canPowerOn: false, canPowerOff: false, reason: 'none-mode' };
102
+ return { canPowerOn: false, canPowerOff: false, supportedStates: [], reason: 'none-mode' };
89
103
  case 'network':
90
- return { canPowerOn: false, canPowerOff: true, reason: 'network-mode-no-wake' };
104
+ return {
105
+ canPowerOn: false,
106
+ canPowerOff: true,
107
+ supportedStates: ['off'],
108
+ reason: 'network-mode-no-wake',
109
+ };
91
110
  case 'icom-wlan':
92
111
  // ICOM WLAN 的 CI-V-over-UDP 通道在电台关机后无法维持;即便已连接
93
112
  // 发送 powerstat(off) 也不能可靠恢复。整体不暴露电源控制。
94
- return { canPowerOn: false, canPowerOff: false, reason: 'model-unsupported' };
113
+ return { canPowerOn: false, canPowerOff: false, supportedStates: [], reason: 'model-unsupported' };
95
114
  case 'serial': {
96
115
  if (!rigInfo) {
97
- return { canPowerOn: false, canPowerOff: false, reason: 'model-unsupported' };
116
+ return { canPowerOn: false, canPowerOff: false, supportedStates: [], reason: 'model-unsupported' };
98
117
  }
99
- const supported = isRigModelPowerCapable(rigInfo.mfgName, rigInfo.modelName);
100
- return supported
101
- ? { canPowerOn: true, canPowerOff: true }
102
- : { canPowerOn: false, canPowerOff: false, reason: 'model-unsupported' };
118
+ const entry = findPowerCapableRigEntry(rigInfo.mfgName, rigInfo.modelName);
119
+ return entry
120
+ ? {
121
+ canPowerOn: true,
122
+ canPowerOff: true,
123
+ supportedStates: [...(entry.supportedStates ?? ['off'])],
124
+ }
125
+ : { canPowerOn: false, canPowerOff: false, supportedStates: [], reason: 'model-unsupported' };
103
126
  }
104
127
  default:
105
- return { canPowerOn: false, canPowerOff: false, reason: 'model-unsupported' };
128
+ return { canPowerOn: false, canPowerOff: false, supportedStates: [], reason: 'model-unsupported' };
106
129
  }
107
130
  }
@@ -5,10 +5,10 @@ import { HamlibConfigSchema } from './radio.schema.js';
5
5
  * High-level runtime state tracked by the RadioPowerController.
6
6
  *
7
7
  * - `off`: radio is known to be off (or was never connected).
8
- * - `waking`: power-on command sent, waiting for the radio to respond.
9
- * - `awake`: radio is responding; the engine is being started (if autoEngine).
10
- * - `shutting_down`: engine is stopping in preparation for a power-off command.
11
- * - `entering_standby`: powerstat(2) sent, waiting for radio to drop CAT link.
8
+ * - `waking`: physical power-on command sent, waiting for the radio to respond.
9
+ * - `awake`: radio is physically responding; software engine startup is optional.
10
+ * - `shutting_down`: physical power-off command is being applied.
11
+ * - `entering_standby`: physical standby command is being applied.
12
12
  * - `failed`: last transition failed; UI should show an error + retry.
13
13
  */
14
14
  export const RadioPowerStateSchema = z.enum([
@@ -53,14 +53,15 @@ export type RadioPowerStateEvent = z.infer<typeof RadioPowerStateEventSchema>;
53
53
  /**
54
54
  * REST request body: POST /api/radio/power
55
55
  */
56
- /** Allowed target states for a power request. */
56
+ /** Allowed physical radio target states for a power request. */
57
57
  export const RadioPowerTargetSchema = z.enum(['on', 'off', 'standby', 'operate']);
58
58
  export type RadioPowerTarget = z.infer<typeof RadioPowerTargetSchema>;
59
59
 
60
60
  export const RadioPowerRequestSchema = z.object({
61
61
  profileId: z.string().min(1),
62
+ /** Physical radio power target. This is not the TX-5DR software engine state. */
62
63
  state: RadioPowerTargetSchema,
63
- /** Automatically start the engine after successful power-on. Defaults to true. */
64
+ /** Automatically start the TX-5DR engine after successful physical power-on. */
64
65
  autoEngine: z.boolean().optional().default(true),
65
66
  });
66
67
  export type RadioPowerRequest = z.infer<typeof RadioPowerRequestSchema>;
@@ -70,6 +71,7 @@ export type RadioPowerRequest = z.infer<typeof RadioPowerRequestSchema>;
70
71
  */
71
72
  export const RadioPowerResponseSchema = z.object({
72
73
  success: z.boolean(),
74
+ target: RadioPowerTargetSchema,
73
75
  state: RadioPowerStateSchema,
74
76
  });
75
77
  export type RadioPowerResponse = z.infer<typeof RadioPowerResponseSchema>;