@camstack/addon-import-alexa 0.2.195 → 0.2.197
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/CHANGELOG.md +12 -0
- package/dist/addon.js +1721 -1377
- package/dist/addon.mjs +1721 -1377
- package/package.json +2 -2
- package/python/alexa_gateway.py +6 -1
package/dist/addon.mjs
CHANGED
|
@@ -46,1194 +46,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
46
46
|
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
47
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
48
|
//#endregion
|
|
49
|
-
//#region ../types/dist/event-category-78tcBemo.mjs
|
|
50
|
-
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
51
|
-
EventCategory["SystemBoot"] = "system.boot";
|
|
52
|
-
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
53
|
-
EventCategory["SystemRestarting"] = "system.restarting";
|
|
54
|
-
/**
|
|
55
|
-
* Fired exactly once after the hub finishes booting following a
|
|
56
|
-
* restart that was triggered by `RestartCoordinator` (today: framework
|
|
57
|
-
* live-update). Payload is the marker that was on disk at boot
|
|
58
|
-
* (`PendingRestartMarkerPayload`); admin UI listens for it to display a
|
|
59
|
-
* success toast describing what changed.
|
|
60
|
-
*
|
|
61
|
-
* Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
|
|
62
|
-
*/
|
|
63
|
-
EventCategory["SystemRestartCompleted"] = "system.restart-completed";
|
|
64
|
-
/**
|
|
65
|
-
* The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
|
|
66
|
-
* Emitted only when the material on disk actually changed, so an
|
|
67
|
-
* operator who trusted the old certificate by hand is told rather than
|
|
68
|
-
* discovering it as a browser error. Payload `TlsCertChangedPayload`.
|
|
69
|
-
*
|
|
70
|
-
* Rule: docs/decisions/adr-0227-*.md
|
|
71
|
-
*/
|
|
72
|
-
EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
|
|
73
|
-
/**
|
|
74
|
-
* A newer addon or server-root package version was found by the
|
|
75
|
-
* authoritative registry check. Emitted once when any observed
|
|
76
|
-
* `latestVersion` changes (or a package/node first appears behind);
|
|
77
|
-
* the payload carries the full currently-available list. Repeated
|
|
78
|
-
* polling of the same latests is silent.
|
|
79
|
-
*/
|
|
80
|
-
EventCategory["UpdateAvailable"] = "update.available";
|
|
81
|
-
/**
|
|
82
|
-
* Readiness transition for a capability provider. Every producer emits
|
|
83
|
-
* this event on `onInitialize` completion, `onDestroy`, and
|
|
84
|
-
* `$node.reconnect`; every consumer that needs to gate on a cross-process
|
|
85
|
-
* cap subscribes via the kernel's readiness module (`awaitReady` /
|
|
86
|
-
* `onReadyState`) instead of polling. Payload is
|
|
87
|
-
* `SystemReadyStatePayload` — see event-bus.ts.
|
|
88
|
-
*/
|
|
89
|
-
EventCategory["SystemReadyState"] = "system.ready-state";
|
|
90
|
-
EventCategory["AddonStarted"] = "addon.started";
|
|
91
|
-
EventCategory["AddonStopped"] = "addon.stopped";
|
|
92
|
-
EventCategory["AddonRestarted"] = "addon.restarted";
|
|
93
|
-
EventCategory["AddonUpdated"] = "addon.updated";
|
|
94
|
-
EventCategory["AddonInstalled"] = "addon.installed";
|
|
95
|
-
EventCategory["AddonUninstalled"] = "addon.uninstalled";
|
|
96
|
-
EventCategory["AddonCrashed"] = "addon.crashed";
|
|
97
|
-
/**
|
|
98
|
-
* A RUNNER the D6 crash circuit-breaker gave up on — terminal.
|
|
99
|
-
*
|
|
100
|
-
* `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
|
|
101
|
-
* respawned"); this is the one that never resolves itself. Emitted exactly
|
|
102
|
-
* once per trip, by `process-service.ts`, carrying the node, the runner, the
|
|
103
|
-
* addons it hosted and the crash count that tripped the breaker.
|
|
104
|
-
*
|
|
105
|
-
* It exists because a runner marked terminally `failed` used to be SILENT:
|
|
106
|
-
* on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
|
|
107
|
-
* errors, the breaker stopped respawning it (correctly), `/health` kept
|
|
108
|
-
* returning 200, and fleet-wide recording was gone for 3h15 before the
|
|
109
|
-
* operator's phone told him. The Notification Center's system-event intake
|
|
110
|
-
* consumes this category and maps it to the `addon-crash-loop` kind.
|
|
111
|
-
*/
|
|
112
|
-
EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
|
|
113
|
-
/**
|
|
114
|
-
* A runner whose addon refused to start with a typed reason (D877) - parked
|
|
115
|
-
* in `refused`, never counted by the crash breaker, retried on operator
|
|
116
|
-
* action. One event per refusal.
|
|
117
|
-
*/
|
|
118
|
-
EventCategory["AddonRunnerRefused"] = "addon.runner-refused";
|
|
119
|
-
/**
|
|
120
|
-
* A runner settled on a state (D877): `running` (READY), `crashed`, `failed` or
|
|
121
|
-
* `refused`. In-process telemetry for the boot check (re-evaluate while any
|
|
122
|
-
* runner is still pending) and the alert centre (close a recovered runner).
|
|
123
|
-
*/
|
|
124
|
-
EventCategory["ProcessStateChanged"] = "process.state-changed";
|
|
125
|
-
/**
|
|
126
|
-
* The node's one-shot runner boot check (D877): planned runners against the
|
|
127
|
-
* process service, `degraded` when any is failed, refused or missing. The
|
|
128
|
-
* alert centre turns it into one critical alert.
|
|
129
|
-
*/
|
|
130
|
-
EventCategory["SystemRunnerCheck"] = "system.runner-check";
|
|
131
|
-
/**
|
|
132
|
-
* An alert EPISODE opened or closed, emitted by the alert centre for alerts
|
|
133
|
-
* of severity `critical` or `error` (`AlertEpisodePayload`). The alert
|
|
134
|
-
* centre sends no notification of its own: Notification Center turns this
|
|
135
|
-
* into the `alert-critical` / `alert-error` / `alert-critical-resolved`
|
|
136
|
-
* system-event kinds, and reconciles from the alerts store because a bus
|
|
137
|
-
* event may be dropped (D8, D11).
|
|
138
|
-
*/
|
|
139
|
-
EventCategory["AlertEpisode"] = "alert.episode";
|
|
140
|
-
EventCategory["AddonError"] = "addon.error";
|
|
141
|
-
EventCategory["AddonPageReady"] = "addon.page-ready";
|
|
142
|
-
EventCategory["AddonWidgetReady"] = "addon.widget-ready";
|
|
143
|
-
/**
|
|
144
|
-
* Addon failed to load (import or initialize). Emitted by the kernel's
|
|
145
|
-
* AddonHealthMonitor only AFTER the boot grace period ends — failures
|
|
146
|
-
* during the first 5 minutes are silently retried without alerting
|
|
147
|
-
* (slow-starting addons must have time to come up). Post-grace, this
|
|
148
|
-
* event is emitted exactly once per failure-streak; AlertCenter
|
|
149
|
-
* consumes it to create a persistent operator-visible alert.
|
|
150
|
-
*
|
|
151
|
-
* Payload: `{ packageName, addonId?, error: { message, stack }, retryCount, nextRetryAt }`.
|
|
152
|
-
*/
|
|
153
|
-
EventCategory["AddonLoadFailed"] = "addon.load-failed";
|
|
154
|
-
/**
|
|
155
|
-
* A keychain value was stored or removed (D787). Carries the ref, its
|
|
156
|
-
* owner and the new version — NEVER the value. A process holding the ref
|
|
157
|
-
* drops its cached copy and the owning `BaseAddon` re-resolves its config.
|
|
158
|
-
* Telemetry (D8): a process that misses it still revalidates by version.
|
|
159
|
-
*/
|
|
160
|
-
EventCategory["KeychainChanged"] = "keychain.changed";
|
|
161
|
-
/**
|
|
162
|
-
* Addon recovered from a previous failure. Emitted when an addon
|
|
163
|
-
* transitions from `failed` back to `healthy` (typically via the
|
|
164
|
-
* monitor's auto-retry loop, or after manual `addons.retryLoad`).
|
|
165
|
-
* AlertCenter dismisses the corresponding `AddonLoadFailed` alert
|
|
166
|
-
* on this event.
|
|
167
|
-
*/
|
|
168
|
-
EventCategory["AddonLoadRecovered"] = "addon.load-recovered";
|
|
169
|
-
/**
|
|
170
|
-
* An addon is RUNNING although its installed package is below what the
|
|
171
|
-
* running server requires (`camstack.requires.addons` of the server, or the
|
|
172
|
-
* addon's own `requires.server`). Raised once per streak by the unsupported-
|
|
173
|
-
* addon tracker, on the hub and on agents; AlertCenter turns it into one
|
|
174
|
-
* operator alert per node and package. The addon is never held back for it.
|
|
175
|
-
*
|
|
176
|
-
* Payload: `{ packageName, addonId, nodeId, installedVersion, requiredMinimum, serverVersion, unmet }`.
|
|
177
|
-
*/
|
|
178
|
-
EventCategory["AddonUnsupportedVersion"] = "addon.unsupported-version";
|
|
179
|
-
/**
|
|
180
|
-
* The package of an {@link AddonUnsupportedVersion} alert reached a supported
|
|
181
|
-
* version, or is no longer installed on the node. AlertCenter dismisses the matching alert.
|
|
182
|
-
*
|
|
183
|
-
* Payload: `{ packageName, addonId, nodeId }`.
|
|
184
|
-
*/
|
|
185
|
-
EventCategory["AddonUnsupportedResolved"] = "addon.unsupported-resolved";
|
|
186
|
-
/**
|
|
187
|
-
* Monitor scheduled the next retry for a failed addon. Transient —
|
|
188
|
-
* surfaced to the UI for live-updating the "next retry in Ns"
|
|
189
|
-
* countdown on the Addons page row, NOT persisted as an alert.
|
|
190
|
-
*/
|
|
191
|
-
EventCategory["AddonRetryScheduled"] = "addon.retry-scheduled";
|
|
192
|
-
/**
|
|
193
|
-
* A liveness invariant of this node is false — it has no devices, every
|
|
194
|
-
* camera is offline, or a camera that was recording has stopped producing
|
|
195
|
-
* segments. Emitted by the `liveness-monitor` builtin, once per fault (the
|
|
196
|
-
* `findingId` is stable across ticks), only after its boot grace period.
|
|
197
|
-
* AlertCenter raises a persistent operator-visible alert.
|
|
198
|
-
*
|
|
199
|
-
* It exists because on 2026-08-03 the hub ran four hours with zero devices
|
|
200
|
-
* and zero recordings while every surface stayed quiet.
|
|
201
|
-
*
|
|
202
|
-
* Payload: `{ findingId, severity, title, message, deviceId? }`.
|
|
203
|
-
*/
|
|
204
|
-
EventCategory["SystemLivenessFailed"] = "system.liveness-failed";
|
|
205
|
-
/**
|
|
206
|
-
* A previously reported liveness fault is true again. AlertCenter dismisses
|
|
207
|
-
* the matching `SystemLivenessFailed` alert.
|
|
208
|
-
*
|
|
209
|
-
* Payload: `{ findingId }`.
|
|
210
|
-
*/
|
|
211
|
-
EventCategory["SystemLivenessRecovered"] = "system.liveness-recovered";
|
|
212
|
-
/**
|
|
213
|
-
* Monitor is attempting to reload a failed addon NOW. UI uses this
|
|
214
|
-
* to show a spinner during the retry attempt. Same transient nature
|
|
215
|
-
* as AddonRetryScheduled.
|
|
216
|
-
*/
|
|
217
|
-
EventCategory["AddonRetryAttempting"] = "addon.retry-attempting";
|
|
218
|
-
EventCategory["DeviceRegistered"] = "device.registered";
|
|
219
|
-
EventCategory["DeviceUnregistered"] = "device.unregistered";
|
|
220
|
-
EventCategory["DeviceEnabled"] = "device.enabled";
|
|
221
|
-
EventCategory["DeviceDisabled"] = "device.disabled";
|
|
222
|
-
EventCategory["DeviceSettingsUpdated"] = "device.settings-updated";
|
|
223
|
-
/**
|
|
224
|
-
* Emitted when the set of native capability providers bound to a device
|
|
225
|
-
* changes — e.g. an addon registers a new native cap via
|
|
226
|
-
* `DeviceContext.registerNativeCap`, or all native bindings for a device
|
|
227
|
-
* are cleared on removal. Hub consumers re-resolve device-proxy routes
|
|
228
|
-
* when this fires.
|
|
229
|
-
*/
|
|
230
|
-
EventCategory["DeviceBindingsChanged"] = "device.bindings-changed";
|
|
231
|
-
/**
|
|
232
|
-
* Emitted when the operator-organisational meta surface changes
|
|
233
|
-
* (`name` / `location` / `disabled`). Payload: `{deviceId, field,
|
|
234
|
-
* value}`. Live consumers (UI device list, alert center) react
|
|
235
|
-
* without polling. Distinct from `DeviceSettingsUpdated` which
|
|
236
|
-
* fires on hardware-config changes (host/port/credentials/etc).
|
|
237
|
-
*/
|
|
238
|
-
EventCategory["DeviceMetaChanged"] = "device.meta-changed";
|
|
239
|
-
/**
|
|
240
|
-
* Emitted by `BaseDevice.updateSourceInfo()` after a successful patch
|
|
241
|
-
* to the device's upstream-system identity / rendering envelope. The
|
|
242
|
-
* full new SourceInfo travels in the payload so cross-process listeners
|
|
243
|
-
* (UI, export adapters) don't need to re-read the meta blob.
|
|
244
|
-
*
|
|
245
|
-
* Payload: `{deviceId, sourceInfo}`.
|
|
246
|
-
*
|
|
247
|
-
* Distinct from `DeviceMetaChanged` which covers the operator-edited
|
|
248
|
-
* surface (`name` / `location` / `disabled`). The two share the
|
|
249
|
-
* persistence layer (both ride on `device-manager.setMetadata` for
|
|
250
|
-
* SourceInfo, or `setName`/`setLocation`/`setDisabled` for the meta
|
|
251
|
-
* fields) but consumers care about different subsets.
|
|
252
|
-
*/
|
|
253
|
-
EventCategory["DeviceSourceInfoChanged"] = "device.source-info-changed";
|
|
254
|
-
/**
|
|
255
|
-
* Emitted by DeviceStreamWiringService after a successful
|
|
256
|
-
* `stream-broker.registerDeviceStreams` call. Payload includes
|
|
257
|
-
* deviceId — consumers look up the full registered device info via
|
|
258
|
-
* `brokerManager.getRegisteredDevice(deviceId)`.
|
|
259
|
-
*
|
|
260
|
-
* Replaces the legacy `StreamRouterService.onDeviceRegistered` callback.
|
|
261
|
-
*/
|
|
262
|
-
EventCategory["DeviceStreamsRegistered"] = "device.streams-registered";
|
|
263
|
-
/**
|
|
264
|
-
* Device-level "fully provisioned" signal — the EXPORT trigger. Emitted once
|
|
265
|
-
* a device's persisted export-relevant shape (its `DeviceFeature` set) is
|
|
266
|
-
* established or changes, carrying `{deviceId, fingerprint, generation}`.
|
|
267
|
-
* Export adapters (Alexa / HAP) react to THIS instead of the chatty per-cap
|
|
268
|
-
* `DeviceBindingsChanged`, turning a boot's incomplete→complete trickle into
|
|
269
|
-
* one clean delta. `fingerprint` is `canonicalDeviceFingerprint(shape)`.
|
|
270
|
-
*
|
|
271
|
-
* Spec: docs/superpowers/specs/2026-06-01-alexa-hap-export-reconciler-design.md
|
|
272
|
-
*/
|
|
273
|
-
EventCategory["DeviceProvisioned"] = "device.provisioned";
|
|
274
|
-
/**
|
|
275
|
-
* Fires once when a device's initial feature-probe completes successfully
|
|
276
|
-
* (lastProbedAt 0→>0); exported shape is now stable. Telemetry (may be
|
|
277
|
-
* dropped) — consumers MUST also gate on the device record's `probed` flag.
|
|
278
|
-
*/
|
|
279
|
-
EventCategory["DeviceReady"] = "device.ready";
|
|
280
|
-
EventCategory["IntegrationEnabled"] = "integration.enabled";
|
|
281
|
-
EventCategory["IntegrationDisabled"] = "integration.disabled";
|
|
282
|
-
EventCategory["IntegrationDeleted"] = "integration.deleted";
|
|
283
|
-
/** Emitted when a broker connection's status flips (connected /
|
|
284
|
-
* disconnected / error). Carries `{ brokerId, status, error? }`. */
|
|
285
|
-
EventCategory["BrokerStatusChanged"] = "broker.status-changed";
|
|
286
|
-
/** Emitted for every subscription-matched message routed by a
|
|
287
|
-
* broker provider. Carries `{ brokerId, subscriptionId, key, payload }`.
|
|
288
|
-
* Consumers filter by `brokerId` + `subscriptionId` in the handler. */
|
|
289
|
-
EventCategory["BrokerMessage"] = "broker.message";
|
|
290
|
-
EventCategory["ProviderStarted"] = "provider.started";
|
|
291
|
-
EventCategory["ProviderStopped"] = "provider.stopped";
|
|
292
|
-
EventCategory["ProcessCrashed"] = "process.crashed";
|
|
293
|
-
EventCategory["ProcessRestartScheduled"] = "process.restart_scheduled";
|
|
294
|
-
EventCategory["ProcessRestarted"] = "process.restarted";
|
|
295
|
-
/**
|
|
296
|
-
* The SET of storage locations changed — one was created, edited, enabled,
|
|
297
|
-
* disabled or deleted through the location instances or the forced-delete action.
|
|
298
|
-
*
|
|
299
|
-
* Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
|
|
300
|
-
* it must also converge on its own periodic path, because a dropped event
|
|
301
|
-
* must not leave a node writing to yesterday's disk set forever. It exists
|
|
302
|
-
* because there was NO signal at all — an operator who added a second
|
|
303
|
-
* recordings disk in the admin UI got nothing, and the recorder kept its
|
|
304
|
-
* resolved locations until something else happened to re-resolve them
|
|
305
|
-
* (D387). Payload `StorageLocationsChangedPayload`.
|
|
306
|
-
*/
|
|
307
|
-
EventCategory["StorageLocationsChanged"] = "storage.locations-changed";
|
|
308
|
-
EventCategory["RecordingStarted"] = "recording.started";
|
|
309
|
-
EventCategory["RecordingStopped"] = "recording.stopped";
|
|
310
|
-
EventCategory["RecordingError"] = "recording.error";
|
|
311
|
-
EventCategory["RecordingHealthDegraded"] = "recording.health.degraded";
|
|
312
|
-
/** Recording enabled but no segment finalized for the NC stall window. */
|
|
313
|
-
EventCategory["RecordingHealthStalled"] = "recording.health.stalled";
|
|
314
|
-
/** Recovery from {@link RecordingHealthStalled}. */
|
|
315
|
-
EventCategory["RecordingHealthOk"] = "recording.health.ok";
|
|
316
|
-
EventCategory["RecordingStorageCritical"] = "recording.storage.critical";
|
|
317
|
-
/** Free space on a storage location crossed the warn bar. */
|
|
318
|
-
EventCategory["StorageLocationLow"] = "storage.location.low";
|
|
319
|
-
/** Free space at/under the critical bar, or capacity probe failed (`null`). */
|
|
320
|
-
EventCategory["StorageLocationCritical"] = "storage.location.critical";
|
|
321
|
-
/** Recovery from low/critical on a storage location. */
|
|
322
|
-
EventCategory["StorageLocationOk"] = "storage.location.ok";
|
|
323
|
-
EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
|
|
324
|
-
EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
|
|
325
|
-
EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
|
|
326
|
-
/** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
|
|
327
|
-
* progress bar the client reconciles via `recordingExport.getExport`. */
|
|
328
|
-
EventCategory["RecordingExportProgress"] = "recording.export.progress";
|
|
329
|
-
EventCategory["RecordingExportCompleted"] = "recording.export.completed";
|
|
330
|
-
EventCategory["RecordingExportFailed"] = "recording.export.failed";
|
|
331
|
-
EventCategory["DetectionEvent"] = "detection.event";
|
|
332
|
-
EventCategory["SessionTrackNew"] = "session.track.new";
|
|
333
|
-
EventCategory["SessionTrackExpired"] = "session.track.expired";
|
|
334
|
-
EventCategory["BenchmarkProgress"] = "benchmark.progress";
|
|
335
|
-
EventCategory["PlatformProbePhase"] = "platform-probe.phase";
|
|
336
|
-
EventCategory["PipelineProgress"] = "pipeline.progress";
|
|
337
|
-
/** Per-frame execution trace emitted by the pipeline executor for live observability. */
|
|
338
|
-
EventCategory["PipelineTrace"] = "pipeline.trace";
|
|
339
|
-
/**
|
|
340
|
-
* Raw inference output emitted by `addon-pipeline-runner` after running the
|
|
341
|
-
* detection pipeline on a frame. Carries the `FrameResult` (with
|
|
342
|
-
* `detections[]`, `width`/`height`, timing debug) — never the frame
|
|
343
|
-
* buffer itself. Hub-side consumers (analysis pipeline, class filters,
|
|
344
|
-
* notifications) subscribe to this and re-emit `detection.result` after
|
|
345
|
-
* post-processing.
|
|
346
|
-
*/
|
|
347
|
-
EventCategory["PipelineInferenceResult"] = "pipeline.inference-result";
|
|
348
|
-
/**
|
|
349
|
-
* Camera lifecycle event emitted by `addon-pipeline-orchestrator` when it
|
|
350
|
-
* assigns or unassigns a camera to/from an agent. Carries no frame data;
|
|
351
|
-
* pure observability for UI dashboards and metrics consumers.
|
|
352
|
-
*/
|
|
353
|
-
EventCategory["PipelineCameraAssigned"] = "pipeline.camera-assigned";
|
|
354
|
-
EventCategory["PipelineCameraUnassigned"] = "pipeline.camera-unassigned";
|
|
355
|
-
/**
|
|
356
|
-
* A node the orchestrator would otherwise place cameras on has NO usable
|
|
357
|
-
* inference device: the operator enabled one or more accelerators there and
|
|
358
|
-
* the live probe reports every one of them unavailable. Emitted once per
|
|
359
|
-
* TRANSITION into that state (never per dispatch), and the node is dropped
|
|
360
|
-
* from the placement candidate set for as long as it holds.
|
|
361
|
-
*
|
|
362
|
-
* This exists because the state was previously invisible: agent-a
|
|
363
|
-
* absorbed 283k inference errors in a day while still being handed cameras,
|
|
364
|
-
* and nothing in the system said so.
|
|
365
|
-
*
|
|
366
|
-
* A node with no accelerators configured at all is NOT this — its devices
|
|
367
|
-
* are `disabled`, not `unavailable`, and the runner's default CPU pool
|
|
368
|
-
* serves it exactly as before.
|
|
369
|
-
*/
|
|
370
|
-
EventCategory["PipelineNodeInferenceUnavailable"] = "pipeline.node-inference-unavailable";
|
|
371
|
-
/**
|
|
372
|
-
* A camera has an OPEN detection session and has produced no detection at
|
|
373
|
-
* all for longer than the blind threshold — the camera is being decoded and
|
|
374
|
-
* inferred and is returning nothing. Emitted once per transition into blind,
|
|
375
|
-
* per camera.
|
|
376
|
-
*
|
|
377
|
-
* The failure it reports: a 1h43 detection blackout on the entrance camera
|
|
378
|
-
* that nobody noticed, because "a camera that detects nothing" and "a quiet
|
|
379
|
-
* camera" produce byte-identical silence.
|
|
380
|
-
*/
|
|
381
|
-
EventCategory["PipelineDetectionBlind"] = "pipeline.detection-blind";
|
|
382
|
-
/**
|
|
383
|
-
* A camera's motion source has fired constantly with no subject for an hour
|
|
384
|
-
* (motion duty or session count over the bar, zero detections) — it is not
|
|
385
|
-
* blind, its motion zones or sensitivity want tuning (D907). Paired with
|
|
386
|
-
* {@link PipelineMotionCalm}.
|
|
387
|
-
*/
|
|
388
|
-
EventCategory["PipelineMotionNoisy"] = "pipeline.motion-noisy";
|
|
389
|
-
/** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
|
|
390
|
-
EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
|
|
391
|
-
/**
|
|
392
|
-
* A camera's package LLM confirm has failed closed three times in a row
|
|
393
|
-
* (D931, 2026-10-26). One edge per camera until
|
|
394
|
-
* {@link PipelinePackageLlmAnswered}.
|
|
395
|
-
*/
|
|
396
|
-
EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
|
|
397
|
-
/** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
|
|
398
|
-
EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
|
|
399
|
-
/**
|
|
400
|
-
* Per-camera pipeline config was mutated by the orchestrator
|
|
401
|
-
* (a settings change via `setAgentInferenceDevices` /
|
|
402
|
-
* `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
|
|
403
|
-
* subscribes to its own emission to hot-reload the assigned runner
|
|
404
|
-
* via `attachCamera` so the next frame executes against the new
|
|
405
|
-
* engine/steps without waiting for a rebalance or the next
|
|
406
|
-
* `DeviceStreamsRegistered` cycle.
|
|
407
|
-
*/
|
|
408
|
-
EventCategory["PipelineCameraUpdated"] = "pipeline.camera-updated";
|
|
409
|
-
/**
|
|
410
|
-
* The cluster camera-source OWNER changed (`clusterRoles.ingestNode`).
|
|
411
|
-
* Emitted by addon-pipeline-orchestrator whenever it (re)derives node
|
|
412
|
-
* capabilities — at boot, on agent online/offline, and on an ingest-node
|
|
413
|
-
* flip. Carries the resolved `ownerNodeId`. The stream-broker consumes it to
|
|
414
|
-
* keep its ingest-owner-gate decision current WITHOUT a per-`ensureBroker`
|
|
415
|
-
* cross-process `getIngestOwner` query (push the authority's decision instead
|
|
416
|
-
* of polling it on the hot path). Idempotent state — re-emitted on every
|
|
417
|
-
* topology change, so a dropped event self-heals on the next one (plus the
|
|
418
|
-
* broker's long backstop reconcile query).
|
|
419
|
-
*/
|
|
420
|
-
EventCategory["PipelineIngestOwnerChanged"] = "pipeline.ingest-owner-changed";
|
|
421
|
-
/**
|
|
422
|
-
* Periodic snapshot of per-node pipeline-runner load
|
|
423
|
-
* (`RunnerLocalLoad`). Emitted ~1Hz by every runner so UI dashboards
|
|
424
|
-
* subscribe instead of polling `pipelineRunner.getLocalLoad`.
|
|
425
|
-
* `nodeId` carried in the payload + on `event.source.nodeId`.
|
|
426
|
-
*/
|
|
427
|
-
EventCategory["PipelineRunnerLoadSnapshot"] = "pipeline.runner-load-snapshot";
|
|
428
|
-
/**
|
|
429
|
-
* Periodic snapshot of per-camera pipeline metrics (`CameraMetrics`
|
|
430
|
-
* + `deviceId` + `nodeId`). Emitted ~1Hz by every runner for each
|
|
431
|
-
* attached camera. UI subscribes to drive overlay phase / fps /
|
|
432
|
-
* inference time without polling `getCameraMetrics`.
|
|
433
|
-
*/
|
|
434
|
-
EventCategory["PipelineCameraMetricsSnapshot"] = "pipeline.camera-metrics-snapshot";
|
|
435
|
-
/**
|
|
436
|
-
* Periodic snapshot of stream-broker per-broker statistics (input
|
|
437
|
-
* fps, decoded fps, bitrate, codec). Emitted ~1Hz by every
|
|
438
|
-
* stream-broker process for each active broker so the UI can drive
|
|
439
|
-
* the Stream / Cluster dashboards without polling
|
|
440
|
-
* `streamBroker.listAllProfileSlots` and friends.
|
|
441
|
-
*/
|
|
442
|
-
EventCategory["StreamBrokerMetricsSnapshot"] = "stream-broker.metrics-snapshot";
|
|
443
|
-
/**
|
|
444
|
-
* Cap event fired by `stream-broker` when a profile slot enters
|
|
445
|
-
* "demanded" state — a cam stream has been assigned and at least one
|
|
446
|
-
* consumer (RTSP restream, decoded subscriber, WebRTC session, …) is
|
|
447
|
-
* present. Camera-provider addons (Reolink Baichuan push, …)
|
|
448
|
-
* subscribe to this category to start their underlying transport
|
|
449
|
-
* lazily. Payload: `{ deviceId, camStreamId, profile }`.
|
|
450
|
-
*/
|
|
451
|
-
EventCategory["StreamBrokerOnCamStreamDemand"] = "stream-broker.onCamStreamDemand";
|
|
452
|
-
/**
|
|
453
|
-
* Cap event fired by `stream-broker` when the last consumer leaves a
|
|
454
|
-
* previously-demanded cam stream. Providers tear down their
|
|
455
|
-
* underlying transport on receipt. Payload: `{ deviceId, camStreamId }`.
|
|
456
|
-
*/
|
|
457
|
-
EventCategory["StreamBrokerOnCamStreamIdle"] = "stream-broker.onCamStreamIdle";
|
|
458
|
-
/**
|
|
459
|
-
* Cap event fired by `stream-broker` when a broker fails to dial a
|
|
460
|
-
* managed-loopback source (today: `pull-rfc4571`) and the publisher
|
|
461
|
-
* needs to refresh the cached URL. The lib's TCP server idle-tears-down
|
|
462
|
-
* on its own schedule, so a re-publish with a fresh `host:port` is the
|
|
463
|
-
* only way to keep the broker dialable. Camera providers (Reolink
|
|
464
|
-
* Baichuan native, …) subscribe and respond by re-running their publish
|
|
465
|
-
* pipeline.
|
|
466
|
-
* Payload: `{ deviceId, camStreamId, brokerId }`.
|
|
467
|
-
*/
|
|
468
|
-
EventCategory["StreamBrokerOnRequestStreamSourceRefresh"] = "stream-broker.onRequestStreamSourceRefresh";
|
|
469
|
-
/**
|
|
470
|
-
* A camera provider changed a device's stream parameters (codec /
|
|
471
|
-
* resolution / bitrate / a stream added or removed). A LOW-LATENCY NUDGE
|
|
472
|
-
* for the stream-broker's catalog reconcile: it carries NO authoritative
|
|
473
|
-
* data — the broker re-PULLS that device's `stream-catalog` cap on receipt
|
|
474
|
-
* (the 30s reconcile poll is the backstop if this nudge is dropped). Emitted
|
|
475
|
-
* by providers from `stream-params.setProfile`. Payload: `{ deviceId }`.
|
|
476
|
-
*/
|
|
477
|
-
EventCategory["StreamParamsChanged"] = "stream-params.changed";
|
|
478
|
-
/**
|
|
479
|
-
* Generic per-device runtime-state change. Fired by `device-manager`
|
|
480
|
-
* whenever a persisted slice in any cap's `runtimeState` shape
|
|
481
|
-
* mutates. Payload: `{deviceId, capName, slice}`. Subscribers are
|
|
482
|
-
* the `deviceState` cap router (cross-process listeners) and the
|
|
483
|
-
* deviceProxy reactive bindings (`device.state.<capName>.value`).
|
|
484
|
-
* Cap-specific events (`battery.onStatusChanged`, …) still fire
|
|
485
|
-
* — they're authoritative for callers that want a typed payload
|
|
486
|
-
* without filtering on `capName`.
|
|
487
|
-
*/
|
|
488
|
-
EventCategory["DeviceStateChanged"] = "device.state-changed";
|
|
489
|
-
/**
|
|
490
|
-
* The NATIVE shadow of a cap a composition has claimed fields of moved
|
|
491
|
-
* (D663). Payload: `{ deviceId, capName, native }` — what the provider
|
|
492
|
-
* wrote, BEFORE the owned fields are overlaid. Owner-only (D224): it is for
|
|
493
|
-
* the composer's native self-reads, and the public event routers refuse it
|
|
494
|
-
* (`isOwnerOnlyEventCategory`) so no consumer surface ever shows a second
|
|
495
|
-
* truth beside the merged `DeviceStateChanged.slice`. Emitted only under a
|
|
496
|
-
* `replace` claim, one per native write of that cap.
|
|
497
|
-
*/
|
|
498
|
-
EventCategory["DeviceNativeShadowChanged"] = "device.native-shadow-changed";
|
|
499
|
-
/**
|
|
500
|
-
* Frame occupancy for a camera CHANGED — a tracked object was gained or
|
|
501
|
-
* lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
|
|
502
|
-
*
|
|
503
|
-
* Emitted only on a change, so a steady scene is silent. It exists so a
|
|
504
|
-
* client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
|
|
505
|
-
* one live badge with no push signal at all, and it cost a request every
|
|
506
|
-
* four seconds per visible camera.
|
|
507
|
-
*
|
|
508
|
-
* Like every event it is telemetry and may be dropped ([D8]) — a consumer
|
|
509
|
-
* keeps a slow reconcile rather than trusting it alone.
|
|
510
|
-
*/
|
|
511
|
-
EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
|
|
512
|
-
/**
|
|
513
|
-
* Cap event fired by every device that registers the `battery`
|
|
514
|
-
* capability. Mirrors the cap definition's `onStatusChanged`. Carries
|
|
515
|
-
* `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
|
|
516
|
-
* snapshot wrapper, UI) react to charge/sleep transitions without
|
|
517
|
-
* polling `batteryCapability.getStatus`.
|
|
518
|
-
*/
|
|
519
|
-
EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
|
|
520
|
-
/**
|
|
521
|
-
* Cap event fired by every device that registers the `network-link`
|
|
522
|
-
* capability. Mirrors the cap definition's `onStatusChanged`. Carries
|
|
523
|
-
* `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
|
|
524
|
-
* reading that moved.
|
|
525
|
-
*/
|
|
526
|
-
EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
|
|
527
|
-
/**
|
|
528
|
-
* Emitted by the battery cap provider WHEN `wakeForStream` enters the
|
|
529
|
-
* "wake in progress" window — between the Baichuan wake-up issue and
|
|
530
|
-
* the camera's first dialed-back RTP packet. The stream-broker
|
|
531
|
-
* manager subscribes to flip the per-broker placeholder reason to
|
|
532
|
-
* `'waking'` so viewers see a labelled "WAKING UP" tile instead of
|
|
533
|
-
* the generic `'reconnecting'` frame. Carries `{ deviceId }`. The
|
|
534
|
-
* complementary "wake complete" signal is the existing
|
|
535
|
-
* `BatteryOnStatusChanged { sleeping: false }` event.
|
|
536
|
-
*/
|
|
537
|
-
EventCategory["BatteryOnWakeStarted"] = "battery.onWakeStarted";
|
|
538
|
-
/**
|
|
539
|
-
* Cap event fired by every device that registers the `doorbell`
|
|
540
|
-
* capability. Mirrors `doorbellCapability.events.onPressed`. Carries
|
|
541
|
-
* `{ deviceId, timestamp }`. Operators consuming the UI subscribe
|
|
542
|
-
* here to render transient ring toasts and a "Recent presses" row
|
|
543
|
-
* on the device detail page.
|
|
544
|
-
*/
|
|
545
|
-
EventCategory["DoorbellOnPressed"] = "doorbell.onPressed";
|
|
546
|
-
/** Mirrors `runningStateCapability.events.onStarted`. */
|
|
547
|
-
EventCategory["RunningStarted"] = "running.started";
|
|
548
|
-
/** Mirrors `runningStateCapability.events.onFinished`. */
|
|
549
|
-
EventCategory["RunningFinished"] = "running.finished";
|
|
550
|
-
/** Mirrors `deviceRunsCapability.events.onRunOpened` - a durable run record opened. */
|
|
551
|
-
EventCategory["DeviceRunOpened"] = "device-run.opened";
|
|
552
|
-
/** Mirrors `deviceRunsCapability.events.onRunClosed` - a durable run record closed. */
|
|
553
|
-
EventCategory["DeviceRunClosed"] = "device-run.closed";
|
|
554
|
-
/**
|
|
555
|
-
* Cap event fired by every device that registers the `event-emitter`
|
|
556
|
-
* capability. Mirrors `eventEmitterCapability.events.onEvent`. Carries
|
|
557
|
-
* `{ deviceId, eventType, data, timestamp, seq }` — the device's EXACT
|
|
558
|
-
* declared event verbatim (NO normalization). Subscribers (UI event
|
|
559
|
-
* stream, advanced-notifier rules) react to fired events without
|
|
560
|
-
* holding a cap reference.
|
|
561
|
-
*/
|
|
562
|
-
EventCategory["EventEmitted"] = "event-emitter.event";
|
|
563
|
-
/**
|
|
564
|
-
* Periodic snapshot of the per-node detection-pipeline engine
|
|
565
|
-
* registry (loaded engines, models resident, in-use cameras, idle
|
|
566
|
-
* TTL). Emitted ~0.2Hz (every 5 s) by every detection-pipeline
|
|
567
|
-
* process. The Engines tab subscribes to drive its inventory view
|
|
568
|
-
* without polling `pipelineExecutor.listLoadedEngines`.
|
|
569
|
-
*/
|
|
570
|
-
EventCategory["PipelineEngineMetricsSnapshot"] = "pipeline.engine-metrics-snapshot";
|
|
571
|
-
/**
|
|
572
|
-
* Per-node detection-engine runtime-provisioning transition. Emitted by
|
|
573
|
-
* the detection-pipeline provider on every state change of its lazy
|
|
574
|
-
* engine-provisioning machine (idle → installing → verifying → ready,
|
|
575
|
-
* or → failed with a `nextRetryAt`). Payload is the
|
|
576
|
-
* `EngineProvisioningState` snapshot; `event.source.nodeId` carries the
|
|
577
|
-
* node. The Pipeline page subscribes to drive a live "installing
|
|
578
|
-
* OpenVINO… / ready" indicator per node without polling
|
|
579
|
-
* `pipelineExecutor.getEngineProvisioning`. Telemetry-grade (D8): the UI
|
|
580
|
-
* also reads the cap snapshot on mount / reconnect. Phase 2.
|
|
581
|
-
*/
|
|
582
|
-
EventCategory["PipelineEngineProvisioning"] = "pipeline.engine-provisioning";
|
|
583
|
-
/**
|
|
584
|
-
* Cluster topology snapshot. Carries the same payload returned by
|
|
585
|
-
* `nodes.topology` (every reachable node + addons + processes).
|
|
586
|
-
* Emitted by the hub on any agent / addon lifecycle change
|
|
587
|
-
* (debounced) plus a periodic safety net. Replaces UI polling on
|
|
588
|
-
* `nodes.topology` — admin-ui dashboards subscribe to drive the
|
|
589
|
-
* cluster view directly from the event payload.
|
|
590
|
-
*/
|
|
591
|
-
EventCategory["ClusterTopologySnapshot"] = "cluster.topology-snapshot";
|
|
592
|
-
/**
|
|
593
|
-
* Periodic per-node system metrics snapshot (CPU / memory / GPU /
|
|
594
|
-
* disk / network). Emitted ~0.2 Hz by the metrics-provider addon
|
|
595
|
-
* for each node. Drives the dashboard SystemStatus / ProcessResources
|
|
596
|
-
* widgets without polling `metricsProvider.getCurrent`.
|
|
597
|
-
*/
|
|
598
|
-
EventCategory["MetricsNodeResourcesSnapshot"] = "metrics.node-resources-snapshot";
|
|
599
|
-
/**
|
|
600
|
-
* Periodic per-node process-tree snapshot (camstack-related pids
|
|
601
|
-
* with root / managed / system classification). Emitted ~0.2 Hz by
|
|
602
|
-
* the metrics-provider addon. Drives the Cluster → Processes tab
|
|
603
|
-
* without polling `metricsProvider.listNodeProcesses`.
|
|
604
|
-
*/
|
|
605
|
-
EventCategory["MetricsNodeProcessesSnapshot"] = "metrics.node-processes-snapshot";
|
|
606
|
-
/**
|
|
607
|
-
* Capability binding change event emitted by `addon-pipeline-orchestrator`
|
|
608
|
-
* when a user changes which addon implements a cap on a node. Subscribed
|
|
609
|
-
* by every kernel process to update its local `preferredProviderRegistry`
|
|
610
|
-
* so future capability lookups respect the new binding.
|
|
611
|
-
*/
|
|
612
|
-
/**
|
|
613
|
-
* A capability binding was changed for a node — addon X now provides
|
|
614
|
-
* capability `cap` on node `nodeId`. Lives under the generic
|
|
615
|
-
* `capability.*` namespace because capability bindings are a kernel-
|
|
616
|
-
* level concept used by many addons, not strictly pipeline-scoped.
|
|
617
|
-
*/
|
|
618
|
-
EventCategory["CapabilityBindingChanged"] = "capability.binding-changed";
|
|
619
|
-
EventCategory["ModelDownloadProgress"] = "model.download.progress";
|
|
620
|
-
EventCategory["AgentRegistered"] = "agent.registered";
|
|
621
|
-
EventCategory["AgentUnregistered"] = "agent.unregistered";
|
|
622
|
-
EventCategory["AgentOnline"] = "agent.online";
|
|
623
|
-
EventCategory["AgentOffline"] = "agent.offline";
|
|
624
|
-
/** Forked worker process (e.g. hub/pipeline) connected to the broker. */
|
|
625
|
-
EventCategory["WorkerOnline"] = "worker.online";
|
|
626
|
-
/** Forked worker process disconnected from the broker. */
|
|
627
|
-
EventCategory["WorkerOffline"] = "worker.offline";
|
|
628
|
-
EventCategory["AgentTaskDispatched"] = "agent.task.dispatched";
|
|
629
|
-
EventCategory["AgentTaskAssigned"] = "agent.task.assigned";
|
|
630
|
-
EventCategory["AgentTrpcConnected"] = "agent.trpc.connected";
|
|
631
|
-
EventCategory["AgentWsConnected"] = "agent.ws.connected";
|
|
632
|
-
EventCategory["AgentWsDisconnected"] = "agent.ws.disconnected";
|
|
633
|
-
EventCategory["AgentBackupActivated"] = "agent.backup.activated";
|
|
634
|
-
EventCategory["OrchestrationSettingsUpdated"] = "orchestration.settings-updated";
|
|
635
|
-
EventCategory["MotionAnalysis"] = "detection.motion-analysis";
|
|
636
|
-
/** All raw motion zones from CCL before minArea filter — for UI debug overlay. */
|
|
637
|
-
EventCategory["MotionZonesRaw"] = "detection.motion-zones-raw";
|
|
638
|
-
/**
|
|
639
|
-
* Per-camera motion phase transition (`watching ↔ active`) emitted
|
|
640
|
-
* by the runner. Mirrors the `motion.onMotionChanged` cap event
|
|
641
|
-
* surface — payload `MotionOnMotionChangedPayload` carries
|
|
642
|
-
* `{deviceId, detected, timestamp, source, regions?}`. Subscribers
|
|
643
|
-
* include addons that need to react to motion state without
|
|
644
|
-
* polling the runtime-state mirror.
|
|
645
|
-
*/
|
|
646
|
-
EventCategory["MotionOnMotionChanged"] = "motion.on-motion-changed";
|
|
647
|
-
EventCategory["DetectionResult"] = "detection.result";
|
|
648
|
-
EventCategory["DetectionRaw"] = "detection.raw";
|
|
649
|
-
EventCategory["DetectionCameraNative"] = "detection.camera-native";
|
|
650
|
-
/**
|
|
651
|
-
* Canonical per-chunk live audio pipeline output. Payload is
|
|
652
|
-
* `PipelineAudioInferenceResultPayload` carrying a full `AudioResult`
|
|
653
|
-
* (level + detections + debug). Lives on the pipeline.* namespace
|
|
654
|
-
* alongside `pipeline.inference-result` (video) for symmetry.
|
|
655
|
-
*/
|
|
656
|
-
EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
|
|
657
|
-
EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
|
|
658
|
-
/**
|
|
659
|
-
* A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
|
|
660
|
-
* data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
|
|
661
|
-
* consumer is the orchestrator's camera-sensors bridge, which re-emits the
|
|
662
|
-
* camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
|
|
663
|
-
* for a camera with no attached sensor). Providers must not emit
|
|
664
|
-
* `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
|
|
665
|
-
* keeps it so (camera-grid is a named, deferred exception).
|
|
666
|
-
*/
|
|
667
|
-
EventCategory["ProviderMotion"] = "provider.motion";
|
|
668
|
-
EventCategory["ProviderDetection"] = "provider.detection";
|
|
669
|
-
EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
|
|
670
|
-
EventCategory["EnrichmentSceneStateChanged"] = "enrichment.scene.state-changed";
|
|
671
|
-
EventCategory["EnrichmentActivitySummary"] = "enrichment.activity.summary";
|
|
672
|
-
/**
|
|
673
|
-
* Unified track-lifecycle event: ONE category carrying `phase:
|
|
674
|
-
* 'start' | 'update' | 'end'` with a rich, typed
|
|
675
|
-
* `PipelineAnalyticsTrackLifecyclePayload`. Supersedes the thin
|
|
676
|
-
* `PipelineAnalyticsTrackStarted` / `PipelineAnalyticsTrackEnded`
|
|
677
|
-
* pair — a consumer subscribes once and branches on `phase`.
|
|
678
|
-
*/
|
|
679
|
-
EventCategory["PipelineAnalyticsTrackLifecycle"] = "pipeline-analytics.track-lifecycle";
|
|
680
|
-
/**
|
|
681
|
-
* @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
|
|
682
|
-
* (`phase:'start'`). Kept as a soft alias — no known subscribers.
|
|
683
|
-
*/
|
|
684
|
-
EventCategory["PipelineAnalyticsTrackStarted"] = "pipeline-analytics.track-started";
|
|
685
|
-
/**
|
|
686
|
-
* @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
|
|
687
|
-
* (`phase:'end'`). Kept as a soft alias — no known subscribers.
|
|
688
|
-
*/
|
|
689
|
-
EventCategory["PipelineAnalyticsTrackEnded"] = "pipeline-analytics.track-ended";
|
|
690
|
-
EventCategory["PipelineAnalyticsDetectionEvent"] = "pipeline-analytics.detection-event";
|
|
691
|
-
EventCategory["PipelineAnalyticsFrameTracked"] = "pipeline-analytics.frame-tracked";
|
|
692
|
-
/**
|
|
693
|
-
* Fired by `addon-post-analysis` when a parked (stationary) object appears
|
|
694
|
-
* (a track was promoted to a stationary-registry entry) or departs (the
|
|
695
|
-
* object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
|
|
696
|
-
* the dedicated "Stationary" section — never the live event feed. Payload:
|
|
697
|
-
* `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
|
|
698
|
-
*/
|
|
699
|
-
EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
|
|
700
|
-
/**
|
|
701
|
-
* Fired by `addon-post-analysis` when a package-drop is confirmed inside
|
|
702
|
-
* a package zone — a newly-appeared stationary object of a package class
|
|
703
|
-
* that cleared the class / zone / dwell / size gates. Payload:
|
|
704
|
-
* `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
|
|
705
|
-
* entryId, className, zoneIds, keyFrameMediaId?, bbox, timestamp }`.
|
|
706
|
-
* Telemetry (D8): the durable record is the `package-events` store row;
|
|
707
|
-
* this bus topic drives notifier rules + live UI. See
|
|
708
|
-
* docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
|
|
709
|
-
*/
|
|
710
|
-
EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
|
|
711
|
-
/**
|
|
712
|
-
* Fired by `addon-post-analysis` when a previously-delivered package
|
|
713
|
-
* leaves its zone (the stationary entry departed — moved or swept).
|
|
714
|
-
* Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
|
|
715
|
-
* `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
|
|
716
|
-
* Telemetry (D8). See package-zones-design §5.2.
|
|
717
|
-
*/
|
|
718
|
-
EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
|
|
719
|
-
/**
|
|
720
|
-
* Fired by `addon-post-analysis` whenever a gallery face row changes:
|
|
721
|
-
* `kind:'buffered'` a new detected face was persisted, `'assigned'` /
|
|
722
|
-
* `'unassigned'` its identity link changed, `'deleted'` the row was
|
|
723
|
-
* removed. Telemetry semantics (D8): a lost event only delays a refresh,
|
|
724
|
-
* never a correctness issue. Payload `PipelineAnalyticsFaceGalleryChangedPayload`.
|
|
725
|
-
*/
|
|
726
|
-
EventCategory["PipelineAnalyticsFaceGalleryChanged"] = "pipeline-analytics.face-gallery-changed";
|
|
727
|
-
/**
|
|
728
|
-
* Fired by `addon-post-analysis` whenever a gallery plate row changes:
|
|
729
|
-
* `kind:'buffered'` a new read was persisted (`PlateRecognizer.onTrackEnd`),
|
|
730
|
-
* `'assigned'` / `'unassigned'` its vehicle link changed
|
|
731
|
-
* (`PlateGalleryProvider`), `'deleted'` the row was removed. Telemetry
|
|
732
|
-
* semantics (D8): a lost event only delays a refresh, never a correctness
|
|
733
|
-
* issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
|
|
734
|
-
*/
|
|
735
|
-
EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
|
|
736
|
-
EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
|
|
737
|
-
/**
|
|
738
|
-
* Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
|
|
739
|
-
* `stream.offline` fires after STREAM_STALE_TIMEOUT_MS without an encoded
|
|
740
|
-
* packet on an active broker. `stream.online` fires on first packet
|
|
741
|
-
* after a stale gap (or on initial first packet). Payload includes
|
|
742
|
-
* the assigned camStreamId as `profileKey`.
|
|
743
|
-
*/
|
|
744
|
-
EventCategory["StreamOnline"] = "stream.online";
|
|
745
|
-
EventCategory["StreamOffline"] = "stream.offline";
|
|
746
|
-
EventCategory["NetworkTunnelStarted"] = "network.tunnel.started";
|
|
747
|
-
EventCategory["NetworkTunnelStopped"] = "network.tunnel.stopped";
|
|
748
|
-
/** Fired by the `local-network` cap when the host's interface set
|
|
749
|
-
* changes (new IP from DHCP, VPN connect, docker bridge added). */
|
|
750
|
-
EventCategory["LocalNetworkChanged"] = "network.local.changed";
|
|
751
|
-
/**
|
|
752
|
-
* Fired by a `mesh-network` provider (Tailscale, …) when its
|
|
753
|
-
* mesh-reachable host changes (join / leave / MagicDNS or 100.x IP
|
|
754
|
-
* change). Lets `local-network` fold the mesh endpoint into its
|
|
755
|
-
* connection-endpoint list without a cross-package import. Payload
|
|
756
|
-
* carries the preferred host (`host: ''` = no longer reachable),
|
|
757
|
-
* the hub port, and the scheme. Telemetry-grade (D8): consumers
|
|
758
|
-
* pull-reconcile from the provider's `getStatus` on reconnect. */
|
|
759
|
-
EventCategory["MeshNetworkChanged"] = "network.mesh.changed";
|
|
760
|
-
EventCategory["BackupCompleted"] = "backup.completed";
|
|
761
|
-
/**
|
|
762
|
-
* A whole backup RUN finished — every destination attempted, win or lose.
|
|
763
|
-
*
|
|
764
|
-
* `backup.completed` fires once per DESTINATION, which is the right grain for
|
|
765
|
-
* a progress UI and the wrong one for a notification: an operator with three
|
|
766
|
-
* destinations would be told three times. And a run where two of three
|
|
767
|
-
* destinations succeeded is not a clean success — a single "backup
|
|
768
|
-
* completed" that hid the failed one would be a lie, so the count of each is
|
|
769
|
-
* carried here and the message says both.
|
|
770
|
-
*
|
|
771
|
-
* Emitted by the backup orchestrator only after the destination loop, so a
|
|
772
|
-
* run that dies during the BUILD phase produces no completion at all.
|
|
773
|
-
*/
|
|
774
|
-
EventCategory["BackupRunCompleted"] = "backup.run-completed";
|
|
775
|
-
EventCategory["BackupRestored"] = "backup.restored";
|
|
776
|
-
EventCategory["NotificationDispatched"] = "notification.dispatched";
|
|
777
|
-
EventCategory["NotificationFailed"] = "notification.failed";
|
|
778
|
-
/**
|
|
779
|
-
* A notification rule that cannot work (D905): its stored row does not load,
|
|
780
|
-
* or it still names a device / delivery target the prune CONFIRMED deleted.
|
|
781
|
-
* One edge per rule per distinct set of reasons; never on an unanswered read.
|
|
782
|
-
*/
|
|
783
|
-
EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
|
|
784
|
-
/** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
|
|
785
|
-
EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
|
|
786
|
-
/** A migration failed or was blocked; the Alert Center re-reads the ledger. */
|
|
787
|
-
EventCategory["MigrationFailed"] = "migration.failed";
|
|
788
|
-
/** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
|
|
789
|
-
EventCategory["MigrationApplied"] = "migration.applied";
|
|
790
|
-
EventCategory["DeviceUpdated"] = "device.updated";
|
|
791
|
-
/**
|
|
792
|
-
* Transport-level connectivity. Emitted by the device driver when
|
|
793
|
-
* the underlying control socket actually connects / disconnects
|
|
794
|
-
* (Baichuan TCP, ONVIF probe response, RTSP DESCRIBE, …) — NOT for
|
|
795
|
-
* power-state transitions on a battery camera. For battery wake /
|
|
796
|
-
* doze cycles see `DeviceAwake` / `DeviceSleeping`.
|
|
797
|
-
*/
|
|
798
|
-
EventCategory["DeviceOnline"] = "device.online";
|
|
799
|
-
/**
|
|
800
|
-
* Stream-broker watchdog — emitted when no encoded packet has been
|
|
801
|
-
* received for STREAM_STALE_TIMEOUT_MS on an active broker (rtsp or
|
|
802
|
-
* push). Paired with DeviceOnline which fires on first packet after
|
|
803
|
-
* a stale gap. Payload: DeviceStreamHealthPayload.
|
|
804
|
-
*/
|
|
805
|
-
EventCategory["DeviceOffline"] = "device.offline";
|
|
806
|
-
/**
|
|
807
|
-
* Battery cam woke up — physical power-state transition reported
|
|
808
|
-
* by the device firmware. Distinct from `DeviceOnline` so a UI panel
|
|
809
|
-
* watching power state doesn't flap on every UDP socket reconnect.
|
|
810
|
-
*/
|
|
811
|
-
EventCategory["DeviceAwake"] = "device.awake";
|
|
812
|
-
/**
|
|
813
|
-
* Battery cam went to sleep. See `DeviceAwake`.
|
|
814
|
-
*/
|
|
815
|
-
EventCategory["DeviceSleeping"] = "device.sleeping";
|
|
816
|
-
EventCategory["RetentionCleanup"] = "retention.cleanup";
|
|
817
|
-
/**
|
|
818
|
-
* Legacy bulk-update progress snapshot (payload `BulkUpdateState`). No longer
|
|
819
|
-
* emitted — F3 removed the coordinator that produced it; "Update all" now runs
|
|
820
|
-
* as one lifecycle engine job (`AddonsJobProgress`/`AddonsJobLog`). Retained
|
|
821
|
-
* (with `BulkUpdateState`) only to avoid regenerating the event maps; removed
|
|
822
|
-
* in F4 once live bulk progress is re-implemented over the engine events.
|
|
823
|
-
*/
|
|
824
|
-
EventCategory["AddonsBulkUpdateProgress"] = "addons.bulk-update-progress";
|
|
825
|
-
EventCategory["AddonsJobProgress"] = "addons.job-progress";
|
|
826
|
-
EventCategory["AddonsJobLog"] = "addons.job-log";
|
|
827
|
-
/**
|
|
828
|
-
* A container's child visibility toggled (hidden/shown). Emitted by the
|
|
829
|
-
* `accessories` cap when a child device is hidden or revealed.
|
|
830
|
-
* Payload: `{ deviceId, childDeviceId, hidden }`.
|
|
831
|
-
*/
|
|
832
|
-
EventCategory["AccessoriesChildVisibilityChanged"] = "accessories.onChildVisibilityChanged";
|
|
833
|
-
/**
|
|
834
|
-
* A container's child set changed (children added/removed/reordered).
|
|
835
|
-
* Payload: `{ deviceId, childDeviceIds, hiddenChildIds }`.
|
|
836
|
-
*/
|
|
837
|
-
EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
|
|
838
|
-
/**
|
|
839
|
-
* An action run started or ended. Emitted by the `actions` surface.
|
|
840
|
-
* Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
|
|
841
|
-
*/
|
|
842
|
-
EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
|
|
843
|
-
/**
|
|
844
|
-
* Progress update from a running model conversion job.
|
|
845
|
-
* Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
|
|
846
|
-
* Emitted by `addon-model-studio` on the converting node.
|
|
847
|
-
*/
|
|
848
|
-
EventCategory["ModelConvertProgress"] = "model-convert.progress";
|
|
849
|
-
return EventCategory;
|
|
850
|
-
}({});
|
|
851
|
-
//#endregion
|
|
852
|
-
//#region ../types/dist/device-type-D1fH5kaC.mjs
|
|
853
|
-
/**
|
|
854
|
-
import { errMsg } from '@camstack/types'
|
|
855
|
-
* Extract a human-readable message from an unknown error value.
|
|
856
|
-
* Replaces the ubiquitous `errMsg(err)` pattern.
|
|
857
|
-
*/
|
|
858
|
-
function errMsg(err) {
|
|
859
|
-
if (err instanceof Error) return err.message;
|
|
860
|
-
if (typeof err === "string") return err;
|
|
861
|
-
return String(err);
|
|
862
|
-
}
|
|
863
|
-
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
864
|
-
DeviceType["Camera"] = "camera";
|
|
865
|
-
DeviceType["Hub"] = "hub";
|
|
866
|
-
DeviceType["Light"] = "light";
|
|
867
|
-
DeviceType["Siren"] = "siren";
|
|
868
|
-
DeviceType["Switch"] = "switch";
|
|
869
|
-
DeviceType["Sensor"] = "sensor";
|
|
870
|
-
DeviceType["Thermostat"] = "thermostat";
|
|
871
|
-
/** Air-conditioner / heat-pump climate device (HVAC) — shares the
|
|
872
|
-
* `climate-control` cap surface with `Thermostat` but renders a
|
|
873
|
-
* dedicated AC-appropriate control UI (mode chips, fan speed,
|
|
874
|
-
* independent vertical/horizontal swing). Sources: native Gree, and
|
|
875
|
-
* reusable by other AC integrations. */
|
|
876
|
-
DeviceType["Climate"] = "climate";
|
|
877
|
-
DeviceType["Button"] = "button";
|
|
878
|
-
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
879
|
-
* event vocabulary verbatim (no normalization). Installed with the
|
|
880
|
-
* `event-emitter` cap. Sources: HA `event.*` entities (structured) and
|
|
881
|
-
* HA bus events (e.g. `zha_event`, generic). */
|
|
882
|
-
DeviceType["EventEmitter"] = "event-emitter";
|
|
883
|
-
/** Firmware/software update entity — current vs available version,
|
|
884
|
-
* updatable flag, update state, and an install action. Installed with
|
|
885
|
-
* the `update` cap. Sources: Homematic firmware-update channels (and
|
|
886
|
-
* reusable by other providers, e.g. HA `update.*` entities). */
|
|
887
|
-
DeviceType["Update"] = "update";
|
|
888
|
-
DeviceType["Generic"] = "generic";
|
|
889
|
-
/** Generic notification delivery target (HA `notify.<service>`, future
|
|
890
|
-
* Telegram / Discord / ntfy / SMTP, …). One device per delivery
|
|
891
|
-
* endpoint; the `notifier` cap defines the send surface. */
|
|
892
|
-
DeviceType["Notifier"] = "notifier";
|
|
893
|
-
/** Pre-recorded action sequence with optional parameters
|
|
894
|
-
* (HA `script.*`). Runnable via `script-runner` cap. */
|
|
895
|
-
DeviceType["Script"] = "script";
|
|
896
|
-
/** Automation rule (HA `automation.*`) — enable/disable + manual
|
|
897
|
-
* trigger surface exposed via `automation-control` cap. */
|
|
898
|
-
DeviceType["Automation"] = "automation";
|
|
899
|
-
/** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
|
|
900
|
-
DeviceType["Lock"] = "lock";
|
|
901
|
-
/** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
|
|
902
|
-
* `valve.*`). `cover` cap with sub-roles for variant. */
|
|
903
|
-
DeviceType["Cover"] = "cover";
|
|
904
|
-
/** Pipe / water / gas valve with open/close/stop and optional
|
|
905
|
-
* position (HA `valve.*`). `valve` cap — a cover-sibling actuator
|
|
906
|
-
* modelled on the same open/closed lifecycle. */
|
|
907
|
-
DeviceType["Valve"] = "valve";
|
|
908
|
-
/** Humidifier / dehumidifier with on/off + target humidity + mode
|
|
909
|
-
* (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
|
|
910
|
-
* modelled on the same target / mode lifecycle. */
|
|
911
|
-
DeviceType["Humidifier"] = "humidifier";
|
|
912
|
-
/** Water heater / boiler with target temperature + operation mode +
|
|
913
|
-
* away mode (HA `water_heater.*`). `water-heater` cap — a
|
|
914
|
-
* climate-family actuator. */
|
|
915
|
-
DeviceType["WaterHeater"] = "water-heater";
|
|
916
|
-
/** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
|
|
917
|
-
DeviceType["Fan"] = "fan";
|
|
918
|
-
/** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
|
|
919
|
-
* the camera surface — those use `Camera`. `media-player` cap. */
|
|
920
|
-
DeviceType["MediaPlayer"] = "media-player";
|
|
921
|
-
/** Security panel / alarm system (HA `alarm_control_panel.*`).
|
|
922
|
-
* `alarm-panel` cap. */
|
|
923
|
-
DeviceType["AlarmPanel"] = "alarm-panel";
|
|
924
|
-
/** Generic user-settable input (HA `number` / `input_number` / `select`
|
|
925
|
-
* / `input_select` / `text` / `input_text` / `input_datetime`).
|
|
926
|
-
* Sub-type via `DeviceRole`: NumericControl / SelectControl /
|
|
927
|
-
* TextControl / DateTimeControl. */
|
|
928
|
-
DeviceType["Control"] = "control";
|
|
929
|
-
/** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
|
|
930
|
-
* `presence` cap. */
|
|
931
|
-
DeviceType["Presence"] = "presence";
|
|
932
|
-
/** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
|
|
933
|
-
* `weather` cap. */
|
|
934
|
-
DeviceType["Weather"] = "weather";
|
|
935
|
-
/** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
|
|
936
|
-
DeviceType["Vacuum"] = "vacuum";
|
|
937
|
-
/** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
|
|
938
|
-
* `lawn-mower-control` cap. */
|
|
939
|
-
DeviceType["LawnMower"] = "lawn-mower";
|
|
940
|
-
/** Physical HA device group — parent container for entity-children
|
|
941
|
-
* adopted from a single HA device entry. Not renderable as a
|
|
942
|
-
* standalone device; exists only to anchor child entities. */
|
|
943
|
-
DeviceType["Container"] = "container";
|
|
944
|
-
/** Single still-image entity (HA `image.*`). Read-only display of an
|
|
945
|
-
* `entity_picture` signed URL the browser loads directly. `image` cap. */
|
|
946
|
-
DeviceType["Image"] = "image";
|
|
947
|
-
/** Smart pet feeder — cloud-connected food dispenser with a bowl food
|
|
948
|
-
* level, battery, desiccant life, feeding state and manual-feed /
|
|
949
|
-
* call-pet / maintenance actions. Installed with the `pet-feeder` cap;
|
|
950
|
-
* dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
|
|
951
|
-
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
952
|
-
* integrations sharing the same food/desiccant/hopper surface. */
|
|
953
|
-
DeviceType["PetFeeder"] = "pet-feeder";
|
|
954
|
-
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
955
|
-
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
956
|
-
* of a container and is BUILT as a composed device (D967). */
|
|
957
|
-
DeviceType["PetFountain"] = "pet-fountain";
|
|
958
|
-
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
959
|
-
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
960
|
-
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
961
|
-
* A plug-built dishwasher carries running-state and power instead. */
|
|
962
|
-
DeviceType["Dishwasher"] = "dishwasher";
|
|
963
|
-
/** Washing machine. A plug-built one is running while its power stays
|
|
964
|
-
* above a threshold. No program cap — that surface is the dishwasher's. */
|
|
965
|
-
DeviceType["Washer"] = "washer";
|
|
966
|
-
/** A dumb plug-in stove or heater: a switch (and, with a meter, a power
|
|
967
|
-
* reading) that only runs while the plug is on. Built from a smart plug;
|
|
968
|
-
* the `timed-run` cap switches it off again after a chosen time. */
|
|
969
|
-
DeviceType["Heater"] = "heater";
|
|
970
|
-
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
971
|
-
DeviceType["Dryer"] = "dryer";
|
|
972
|
-
/** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
|
|
973
|
-
* other radio-to-IP bridges). It carries the box's own facts (firmware,
|
|
974
|
-
* address, connection state) and the actions the box really supports
|
|
975
|
-
* (`reboot`, `device-admin-link`). Products are NOT its children — they are
|
|
976
|
-
* adopted beside it — and its connection settings stay on the integration. */
|
|
977
|
-
DeviceType["Gateway"] = "gateway";
|
|
978
|
-
return DeviceType;
|
|
979
|
-
}({});
|
|
980
|
-
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
981
|
-
DeviceFeature["BatteryOperated"] = "battery-operated";
|
|
982
|
-
DeviceFeature["Rebootable"] = "rebootable";
|
|
983
|
-
/**
|
|
984
|
-
* Device supports an on-demand re-sync of its derived spec with its
|
|
985
|
-
* upstream source — drives the generic Re-sync button. The owning
|
|
986
|
-
* provider implements the action via the `device-adoption.resync` cap.
|
|
987
|
-
*/
|
|
988
|
-
DeviceFeature["Resyncable"] = "resyncable";
|
|
989
|
-
DeviceFeature["NativeSnapshot"] = "native-snapshot";
|
|
990
|
-
DeviceFeature["DoorbellButton"] = "doorbell-button";
|
|
991
|
-
DeviceFeature["TwoWayAudio"] = "two-way-audio";
|
|
992
|
-
DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
|
|
993
|
-
/**
|
|
994
|
-
* Camera supports the on-firmware autotrack subsystem (subject-
|
|
995
|
-
* following). Distinct from `PanTiltZoom` because not every PTZ
|
|
996
|
-
* camera ships autotrack — the admin UI uses this flag to gate
|
|
997
|
-
* the autotrack toggle / settings card without re-deriving from
|
|
998
|
-
* the cap registry. Mirrors `ptz-autotrack` cap registration:
|
|
999
|
-
* driver sets this feature when probe confirms the firmware
|
|
1000
|
-
* surface, and registers the cap in the same code path.
|
|
1001
|
-
*/
|
|
1002
|
-
DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
|
|
1003
|
-
/**
|
|
1004
|
-
* Accessory exposes a "trigger on motion" toggle — the parent camera's
|
|
1005
|
-
* motion detection automatically activates this device. Mirrors
|
|
1006
|
-
* `motion-trigger` cap registration: drivers set this feature in the
|
|
1007
|
-
* same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
|
|
1008
|
-
*
|
|
1009
|
-
* Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
|
|
1010
|
-
* fast scalar without binding fetch), notifier rules, and `listAll`
|
|
1011
|
-
* filters that want "all devices with on-motion behaviour".
|
|
1012
|
-
*/
|
|
1013
|
-
DeviceFeature["MotionTrigger"] = "motion-trigger";
|
|
1014
|
-
/** Light supports rgb-triplet color via `color` cap. */
|
|
1015
|
-
DeviceFeature["LightColorRgb"] = "light-color-rgb";
|
|
1016
|
-
/** Light supports HSV color via `color` cap. */
|
|
1017
|
-
DeviceFeature["LightColorHsv"] = "light-color-hsv";
|
|
1018
|
-
/** Light supports color-temperature (mired) via `color` cap. */
|
|
1019
|
-
DeviceFeature["LightColorMired"] = "light-color-mired";
|
|
1020
|
-
/** Thermostat supports a `heat_cool` dual setpoint (targetLow +
|
|
1021
|
-
* targetHigh). Gates the range slider UI. */
|
|
1022
|
-
DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
|
|
1023
|
-
/** Thermostat exposes target humidity and/or current humidity
|
|
1024
|
-
* readings. Gates the humidity controls. */
|
|
1025
|
-
DeviceFeature["ClimateHumidity"] = "climate-humidity";
|
|
1026
|
-
/** Thermostat exposes a fan-mode selector. */
|
|
1027
|
-
DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
|
|
1028
|
-
/** Thermostat exposes preset modes (eco / away / sleep / vendor). */
|
|
1029
|
-
DeviceFeature["ClimatePreset"] = "climate-preset";
|
|
1030
|
-
/** Thermostat exposes a vertical louver swing toggle. Gates the
|
|
1031
|
-
* vertical-swing switch in the climate UI. Independent of horizontal. */
|
|
1032
|
-
DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
|
|
1033
|
-
/** Thermostat exposes a horizontal louver swing toggle. Gates the
|
|
1034
|
-
* horizontal-swing switch in the climate UI. Independent of vertical. */
|
|
1035
|
-
DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
|
|
1036
|
-
/** Cover exposes intermediate position control (0..100). Gates the
|
|
1037
|
-
* position slider UI. */
|
|
1038
|
-
DeviceFeature["CoverPositionable"] = "cover-positionable";
|
|
1039
|
-
/** Cover exposes slat-tilt control. Gates the tilt slider UI. */
|
|
1040
|
-
DeviceFeature["CoverTilt"] = "cover-tilt";
|
|
1041
|
-
/** Cover has a real stop: `stop` halts the travel in place. Gates the stop
|
|
1042
|
-
* control in exporters; a provider whose `stop` is a no-op never declares it. */
|
|
1043
|
-
DeviceFeature["CoverStoppable"] = "cover-stoppable";
|
|
1044
|
-
/** Valve exposes intermediate position control (0..100). Gates the
|
|
1045
|
-
* position slider / drag surface UI. */
|
|
1046
|
-
DeviceFeature["ValvePositionable"] = "valve-positionable";
|
|
1047
|
-
/** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
|
|
1048
|
-
DeviceFeature["FanSpeed"] = "fan-speed";
|
|
1049
|
-
/** Fan exposes a preset mode selector. */
|
|
1050
|
-
DeviceFeature["FanPreset"] = "fan-preset";
|
|
1051
|
-
/** Fan exposes blade direction (forward/reverse) — typical of
|
|
1052
|
-
* ceiling fans. */
|
|
1053
|
-
DeviceFeature["FanDirection"] = "fan-direction";
|
|
1054
|
-
/** Fan exposes an oscillation toggle. */
|
|
1055
|
-
DeviceFeature["FanOscillating"] = "fan-oscillating";
|
|
1056
|
-
/** Lock requires a PIN code on lock/unlock. Gates the code-entry
|
|
1057
|
-
* field on the UI lock-controls panel. */
|
|
1058
|
-
DeviceFeature["LockPinRequired"] = "lock-pin-required";
|
|
1059
|
-
/** Lock supports a latch-release ("open door") action distinct from
|
|
1060
|
-
* unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
|
|
1061
|
-
* `supported_features`. Gates the Open Door button in the UI. */
|
|
1062
|
-
DeviceFeature["LockOpen"] = "lock-open";
|
|
1063
|
-
/** Media player exposes a seek-to-position surface. */
|
|
1064
|
-
DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
|
|
1065
|
-
/** Media player exposes a volume-level setter. */
|
|
1066
|
-
DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
|
|
1067
|
-
/** Media player exposes a mute toggle distinct from volume=0. */
|
|
1068
|
-
DeviceFeature["MediaPlayerMute"] = "media-player-mute";
|
|
1069
|
-
/** Media player exposes a shuffle toggle. */
|
|
1070
|
-
DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
|
|
1071
|
-
/** Media player exposes a repeat mode (off / all / one). */
|
|
1072
|
-
DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
|
|
1073
|
-
/** Media player exposes a source / input selector. */
|
|
1074
|
-
DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
|
|
1075
|
-
/** Media player exposes a play-arbitrary-media surface (URL / id). */
|
|
1076
|
-
DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
|
|
1077
|
-
/** Media player exposes next-track. */
|
|
1078
|
-
DeviceFeature["MediaPlayerNext"] = "media-player-next";
|
|
1079
|
-
/** Media player exposes previous-track. */
|
|
1080
|
-
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
1081
|
-
/** Media player exposes stop distinct from pause. */
|
|
1082
|
-
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
1083
|
-
/** Media player exposes volume up / down in steps (an infrared TV has no level). */
|
|
1084
|
-
DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
|
|
1085
|
-
/** Media player exposes channel up / down in steps. */
|
|
1086
|
-
DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
|
|
1087
|
-
/** Media player exposes power on / off. */
|
|
1088
|
-
DeviceFeature["MediaPlayerPower"] = "media-player-power";
|
|
1089
|
-
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
1090
|
-
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
1091
|
-
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
1092
|
-
* addition to a textual location. */
|
|
1093
|
-
DeviceFeature["PresenceGps"] = "presence-gps";
|
|
1094
|
-
/** Notifier accepts an inline / URL image attachment. */
|
|
1095
|
-
DeviceFeature["NotifierImage"] = "notifier-image";
|
|
1096
|
-
/** Notifier accepts a priority hint (high/normal/low). */
|
|
1097
|
-
DeviceFeature["NotifierPriority"] = "notifier-priority";
|
|
1098
|
-
/** Notifier accepts a free-form `data` payload for platform-specific
|
|
1099
|
-
* fields. */
|
|
1100
|
-
DeviceFeature["NotifierData"] = "notifier-data";
|
|
1101
|
-
/** Notifier supports interactive action buttons / callbacks. */
|
|
1102
|
-
DeviceFeature["NotifierActions"] = "notifier-actions";
|
|
1103
|
-
/** Notifier supports per-call recipient targeting (multi-user). */
|
|
1104
|
-
DeviceFeature["NotifierRecipients"] = "notifier-recipients";
|
|
1105
|
-
/** Script runner accepts a variables map on each run invocation. */
|
|
1106
|
-
DeviceFeature["ScriptVariables"] = "script-variables";
|
|
1107
|
-
/** Automation `trigger` accepts a skipCondition flag — fires the
|
|
1108
|
-
* automation's actions while bypassing its condition block. */
|
|
1109
|
-
DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
|
|
1110
|
-
/** Robot vacuum exposes a live cleaning map (image child). Gates the
|
|
1111
|
-
* map tile in the vacuum UI. */
|
|
1112
|
-
DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
|
|
1113
|
-
/** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
|
|
1114
|
-
* switches group. */
|
|
1115
|
-
DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
|
|
1116
|
-
/** Robot mower exposes a live mowing map (SVG image child). Gates the
|
|
1117
|
-
* mower map tile. */
|
|
1118
|
-
DeviceFeature["MowerHasMap"] = "mower-has-map";
|
|
1119
|
-
/** Robot mower exposes targeted mowing (all-area / zones / edges /
|
|
1120
|
-
* spots) — gates the Mow action + map selectors. */
|
|
1121
|
-
DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
|
|
1122
|
-
return DeviceFeature;
|
|
1123
|
-
}({});
|
|
1124
|
-
/**
|
|
1125
|
-
* Semantic role a device plays within its parent. Populated by driver
|
|
1126
|
-
* addons when creating accessory devices (Reolink siren/floodlight/
|
|
1127
|
-
* PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
|
|
1128
|
-
* admin UI to pick icons, labels, and widgets — a `Switch` with
|
|
1129
|
-
* `role: Floodlight` renders as a bulb with a brightness slider,
|
|
1130
|
-
* whereas a `Switch` with `role: Siren` renders as a klaxon.
|
|
1131
|
-
*
|
|
1132
|
-
* Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
|
|
1133
|
-
* sqlite as a nullable TEXT column — old rows keep working unchanged.
|
|
1134
|
-
*/
|
|
1135
|
-
var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
1136
|
-
DeviceRole["Siren"] = "siren";
|
|
1137
|
-
DeviceRole["Floodlight"] = "floodlight";
|
|
1138
|
-
DeviceRole["Spotlight"] = "spotlight";
|
|
1139
|
-
DeviceRole["PirSensor"] = "pir-sensor";
|
|
1140
|
-
DeviceRole["Chime"] = "chime";
|
|
1141
|
-
DeviceRole["Autotrack"] = "autotrack";
|
|
1142
|
-
DeviceRole["Nightvision"] = "nightvision";
|
|
1143
|
-
DeviceRole["PrivacyMask"] = "privacy-mask";
|
|
1144
|
-
DeviceRole["Doorbell"] = "doorbell";
|
|
1145
|
-
/** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
|
|
1146
|
-
DeviceRole["CoverBlind"] = "cover-blind";
|
|
1147
|
-
/** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
|
|
1148
|
-
DeviceRole["CoverShutter"] = "cover-shutter";
|
|
1149
|
-
/** Exterior awning. HA `device_class: awning`. */
|
|
1150
|
-
DeviceRole["CoverAwning"] = "cover-awning";
|
|
1151
|
-
/** Curtain / drape. HA `device_class: curtain`. */
|
|
1152
|
-
DeviceRole["CoverCurtain"] = "cover-curtain";
|
|
1153
|
-
/** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
|
|
1154
|
-
DeviceRole["CoverGarage"] = "cover-garage";
|
|
1155
|
-
/** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
|
|
1156
|
-
DeviceRole["CoverGate"] = "cover-gate";
|
|
1157
|
-
/** Door. HA `device_class: door`. A security actuator: exports gate it. */
|
|
1158
|
-
DeviceRole["CoverDoor"] = "cover-door";
|
|
1159
|
-
/** Window. HA `device_class: window`. */
|
|
1160
|
-
DeviceRole["CoverWindow"] = "cover-window";
|
|
1161
|
-
/** Virtual HA toggle (input_boolean.*) — distinguishable from a
|
|
1162
|
-
* real Switch device for UI rendering / export adapters. */
|
|
1163
|
-
DeviceRole["BinaryHelper"] = "binary-helper";
|
|
1164
|
-
/** A smart plug: a `switch` whose source says it is an outlet (HA
|
|
1165
|
-
* `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
|
|
1166
|
-
DeviceRole["Outlet"] = "outlet";
|
|
1167
|
-
/** Generic motion / occupancy / moving event source. Distinct from
|
|
1168
|
-
* the camera accessory PirSensor role: that one is a camera child;
|
|
1169
|
-
* this is a standalone HA / 3rd-party motion sensor. */
|
|
1170
|
-
DeviceRole["MotionSensor"] = "motion-sensor";
|
|
1171
|
-
DeviceRole["ContactSensor"] = "contact-sensor";
|
|
1172
|
-
DeviceRole["LeakSensor"] = "leak-sensor";
|
|
1173
|
-
DeviceRole["SmokeSensor"] = "smoke-sensor";
|
|
1174
|
-
DeviceRole["COSensor"] = "co-sensor";
|
|
1175
|
-
DeviceRole["GasSensor"] = "gas-sensor";
|
|
1176
|
-
DeviceRole["TamperSensor"] = "tamper-sensor";
|
|
1177
|
-
DeviceRole["VibrationSensor"] = "vibration-sensor";
|
|
1178
|
-
DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
|
|
1179
|
-
DeviceRole["SoundSensor"] = "sound-sensor";
|
|
1180
|
-
/** Fallback for `binary_sensor` without a known `device_class`. */
|
|
1181
|
-
DeviceRole["BinarySensor"] = "binary-sensor";
|
|
1182
|
-
DeviceRole["TemperatureSensor"] = "temperature-sensor";
|
|
1183
|
-
DeviceRole["HumiditySensor"] = "humidity-sensor";
|
|
1184
|
-
DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
|
|
1185
|
-
DeviceRole["PressureSensor"] = "pressure-sensor";
|
|
1186
|
-
/** Wind speed or direction (weather-station `wind-sensor` cap). */
|
|
1187
|
-
DeviceRole["WindSensor"] = "wind-sensor";
|
|
1188
|
-
/** Rain accumulation or rate (weather-station `rain-sensor` cap). */
|
|
1189
|
-
DeviceRole["RainSensor"] = "rain-sensor";
|
|
1190
|
-
/** UV index (weather-station `uv-sensor` cap). */
|
|
1191
|
-
DeviceRole["UvSensor"] = "uv-sensor";
|
|
1192
|
-
/** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
|
|
1193
|
-
* Distinct from AmbientLightSensor (lux). */
|
|
1194
|
-
DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
|
|
1195
|
-
/** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
|
|
1196
|
-
DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
|
|
1197
|
-
DeviceRole["PowerSensor"] = "power-sensor";
|
|
1198
|
-
DeviceRole["EnergySensor"] = "energy-sensor";
|
|
1199
|
-
DeviceRole["VoltageSensor"] = "voltage-sensor";
|
|
1200
|
-
DeviceRole["CurrentSensor"] = "current-sensor";
|
|
1201
|
-
DeviceRole["AirQualitySensor"] = "air-quality-sensor";
|
|
1202
|
-
/** Battery level (numeric % via `sensor` OR low-bool via
|
|
1203
|
-
* `binary_sensor` — the cap distinguishes via the value type). */
|
|
1204
|
-
DeviceRole["BatterySensor"] = "battery-sensor";
|
|
1205
|
-
/** Fallback for `sensor` numeric without a known `device_class`. */
|
|
1206
|
-
DeviceRole["NumericSensor"] = "numeric-sensor";
|
|
1207
|
-
/** String / enum state (HA `sensor` with `state_class: enum` or
|
|
1208
|
-
* `attributes.options`). */
|
|
1209
|
-
DeviceRole["EnumSensor"] = "enum-sensor";
|
|
1210
|
-
/** Date / timestamp state (HA `sensor` with `device_class: timestamp`
|
|
1211
|
-
* or `date`). The slice carries the raw ISO string verbatim (hosted on
|
|
1212
|
-
* the `enum-sensor` cap); the UI renders it locale-formatted. */
|
|
1213
|
-
DeviceRole["DateTimeSensor"] = "datetime-sensor";
|
|
1214
|
-
/** Last-resort fallback when nothing else matches. */
|
|
1215
|
-
DeviceRole["GenericSensor"] = "generic-sensor";
|
|
1216
|
-
DeviceRole["NumericControl"] = "numeric-control";
|
|
1217
|
-
DeviceRole["SelectControl"] = "select-control";
|
|
1218
|
-
DeviceRole["TextControl"] = "text-control";
|
|
1219
|
-
DeviceRole["DateTimeControl"] = "datetime-control";
|
|
1220
|
-
/** Mobile push notifier (HA `notify.mobile_app_*`) — supports
|
|
1221
|
-
* rich features (image, priority, channel routing). */
|
|
1222
|
-
DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
|
|
1223
|
-
/** Chat / messaging service (HA `notify.telegram_*`,
|
|
1224
|
-
* `notify.discord_*`, etc.). */
|
|
1225
|
-
DeviceRole["MessagingNotifier"] = "messaging-notifier";
|
|
1226
|
-
/** Email-based delivery (HA `notify.smtp`, etc.). */
|
|
1227
|
-
DeviceRole["EmailNotifier"] = "email-notifier";
|
|
1228
|
-
/** Fallback when the notifier service name doesn't match a known
|
|
1229
|
-
* pattern. */
|
|
1230
|
-
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
1231
|
-
/** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
|
|
1232
|
-
* cap. A `DeviceType.Generic` device. */
|
|
1233
|
-
DeviceRole["IrTransmitter"] = "ir-transmitter";
|
|
1234
|
-
return DeviceRole;
|
|
1235
|
-
}({});
|
|
1236
|
-
//#endregion
|
|
1237
49
|
//#region ../../node_modules/zod/v4/core/core.js
|
|
1238
50
|
var _a$1;
|
|
1239
51
|
function $constructor(name, initializer, params) {
|
|
@@ -6054,12 +4866,1200 @@ var ZodIssueCode = {
|
|
|
6054
4866
|
var ZodFirstPartyTypeKind;
|
|
6055
4867
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
6056
4868
|
//#endregion
|
|
6057
|
-
//#region ../../node_modules/zod/v4/classic/coerce.js
|
|
6058
|
-
function number(params) {
|
|
6059
|
-
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
4869
|
+
//#region ../../node_modules/zod/v4/classic/coerce.js
|
|
4870
|
+
function number(params) {
|
|
4871
|
+
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
4872
|
+
}
|
|
4873
|
+
//#endregion
|
|
4874
|
+
//#region ../types/dist/event-category-78tcBemo.mjs
|
|
4875
|
+
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4876
|
+
EventCategory["SystemBoot"] = "system.boot";
|
|
4877
|
+
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
4878
|
+
EventCategory["SystemRestarting"] = "system.restarting";
|
|
4879
|
+
/**
|
|
4880
|
+
* Fired exactly once after the hub finishes booting following a
|
|
4881
|
+
* restart that was triggered by `RestartCoordinator` (today: framework
|
|
4882
|
+
* live-update). Payload is the marker that was on disk at boot
|
|
4883
|
+
* (`PendingRestartMarkerPayload`); admin UI listens for it to display a
|
|
4884
|
+
* success toast describing what changed.
|
|
4885
|
+
*
|
|
4886
|
+
* Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
|
|
4887
|
+
*/
|
|
4888
|
+
EventCategory["SystemRestartCompleted"] = "system.restart-completed";
|
|
4889
|
+
/**
|
|
4890
|
+
* The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
|
|
4891
|
+
* Emitted only when the material on disk actually changed, so an
|
|
4892
|
+
* operator who trusted the old certificate by hand is told rather than
|
|
4893
|
+
* discovering it as a browser error. Payload `TlsCertChangedPayload`.
|
|
4894
|
+
*
|
|
4895
|
+
* Rule: docs/decisions/adr-0227-*.md
|
|
4896
|
+
*/
|
|
4897
|
+
EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
|
|
4898
|
+
/**
|
|
4899
|
+
* A newer addon or server-root package version was found by the
|
|
4900
|
+
* authoritative registry check. Emitted once when any observed
|
|
4901
|
+
* `latestVersion` changes (or a package/node first appears behind);
|
|
4902
|
+
* the payload carries the full currently-available list. Repeated
|
|
4903
|
+
* polling of the same latests is silent.
|
|
4904
|
+
*/
|
|
4905
|
+
EventCategory["UpdateAvailable"] = "update.available";
|
|
4906
|
+
/**
|
|
4907
|
+
* Readiness transition for a capability provider. Every producer emits
|
|
4908
|
+
* this event on `onInitialize` completion, `onDestroy`, and
|
|
4909
|
+
* `$node.reconnect`; every consumer that needs to gate on a cross-process
|
|
4910
|
+
* cap subscribes via the kernel's readiness module (`awaitReady` /
|
|
4911
|
+
* `onReadyState`) instead of polling. Payload is
|
|
4912
|
+
* `SystemReadyStatePayload` — see event-bus.ts.
|
|
4913
|
+
*/
|
|
4914
|
+
EventCategory["SystemReadyState"] = "system.ready-state";
|
|
4915
|
+
EventCategory["AddonStarted"] = "addon.started";
|
|
4916
|
+
EventCategory["AddonStopped"] = "addon.stopped";
|
|
4917
|
+
EventCategory["AddonRestarted"] = "addon.restarted";
|
|
4918
|
+
EventCategory["AddonUpdated"] = "addon.updated";
|
|
4919
|
+
EventCategory["AddonInstalled"] = "addon.installed";
|
|
4920
|
+
EventCategory["AddonUninstalled"] = "addon.uninstalled";
|
|
4921
|
+
EventCategory["AddonCrashed"] = "addon.crashed";
|
|
4922
|
+
/**
|
|
4923
|
+
* A RUNNER the D6 crash circuit-breaker gave up on — terminal.
|
|
4924
|
+
*
|
|
4925
|
+
* `AddonCrashed` is the routine, self-healing fact ("it crashed; it is being
|
|
4926
|
+
* respawned"); this is the one that never resolves itself. Emitted exactly
|
|
4927
|
+
* once per trip, by `process-service.ts`, carrying the node, the runner, the
|
|
4928
|
+
* addons it hosted and the crash count that tripped the breaker.
|
|
4929
|
+
*
|
|
4930
|
+
* It exists because a runner marked terminally `failed` used to be SILENT:
|
|
4931
|
+
* on 2026-08-18 the `recorder` runner died of three unhandled ffmpeg spawn
|
|
4932
|
+
* errors, the breaker stopped respawning it (correctly), `/health` kept
|
|
4933
|
+
* returning 200, and fleet-wide recording was gone for 3h15 before the
|
|
4934
|
+
* operator's phone told him. The Notification Center's system-event intake
|
|
4935
|
+
* consumes this category and maps it to the `addon-crash-loop` kind.
|
|
4936
|
+
*/
|
|
4937
|
+
EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
|
|
4938
|
+
/**
|
|
4939
|
+
* A runner whose addon refused to start with a typed reason (D877) - parked
|
|
4940
|
+
* in `refused`, never counted by the crash breaker, retried on operator
|
|
4941
|
+
* action. One event per refusal.
|
|
4942
|
+
*/
|
|
4943
|
+
EventCategory["AddonRunnerRefused"] = "addon.runner-refused";
|
|
4944
|
+
/**
|
|
4945
|
+
* A runner settled on a state (D877): `running` (READY), `crashed`, `failed` or
|
|
4946
|
+
* `refused`. In-process telemetry for the boot check (re-evaluate while any
|
|
4947
|
+
* runner is still pending) and the alert centre (close a recovered runner).
|
|
4948
|
+
*/
|
|
4949
|
+
EventCategory["ProcessStateChanged"] = "process.state-changed";
|
|
4950
|
+
/**
|
|
4951
|
+
* The node's one-shot runner boot check (D877): planned runners against the
|
|
4952
|
+
* process service, `degraded` when any is failed, refused or missing. The
|
|
4953
|
+
* alert centre turns it into one critical alert.
|
|
4954
|
+
*/
|
|
4955
|
+
EventCategory["SystemRunnerCheck"] = "system.runner-check";
|
|
4956
|
+
/**
|
|
4957
|
+
* An alert EPISODE opened or closed, emitted by the alert centre for alerts
|
|
4958
|
+
* of severity `critical` or `error` (`AlertEpisodePayload`). The alert
|
|
4959
|
+
* centre sends no notification of its own: Notification Center turns this
|
|
4960
|
+
* into the `alert-critical` / `alert-error` / `alert-critical-resolved`
|
|
4961
|
+
* system-event kinds, and reconciles from the alerts store because a bus
|
|
4962
|
+
* event may be dropped (D8, D11).
|
|
4963
|
+
*/
|
|
4964
|
+
EventCategory["AlertEpisode"] = "alert.episode";
|
|
4965
|
+
EventCategory["AddonError"] = "addon.error";
|
|
4966
|
+
EventCategory["AddonPageReady"] = "addon.page-ready";
|
|
4967
|
+
EventCategory["AddonWidgetReady"] = "addon.widget-ready";
|
|
4968
|
+
/**
|
|
4969
|
+
* Addon failed to load (import or initialize). Emitted by the kernel's
|
|
4970
|
+
* AddonHealthMonitor only AFTER the boot grace period ends — failures
|
|
4971
|
+
* during the first 5 minutes are silently retried without alerting
|
|
4972
|
+
* (slow-starting addons must have time to come up). Post-grace, this
|
|
4973
|
+
* event is emitted exactly once per failure-streak; AlertCenter
|
|
4974
|
+
* consumes it to create a persistent operator-visible alert.
|
|
4975
|
+
*
|
|
4976
|
+
* Payload: `{ packageName, addonId?, error: { message, stack }, retryCount, nextRetryAt }`.
|
|
4977
|
+
*/
|
|
4978
|
+
EventCategory["AddonLoadFailed"] = "addon.load-failed";
|
|
4979
|
+
/**
|
|
4980
|
+
* A keychain value was stored or removed (D787). Carries the ref, its
|
|
4981
|
+
* owner and the new version — NEVER the value. A process holding the ref
|
|
4982
|
+
* drops its cached copy and the owning `BaseAddon` re-resolves its config.
|
|
4983
|
+
* Telemetry (D8): a process that misses it still revalidates by version.
|
|
4984
|
+
*/
|
|
4985
|
+
EventCategory["KeychainChanged"] = "keychain.changed";
|
|
4986
|
+
/**
|
|
4987
|
+
* Addon recovered from a previous failure. Emitted when an addon
|
|
4988
|
+
* transitions from `failed` back to `healthy` (typically via the
|
|
4989
|
+
* monitor's auto-retry loop, or after manual `addons.retryLoad`).
|
|
4990
|
+
* AlertCenter dismisses the corresponding `AddonLoadFailed` alert
|
|
4991
|
+
* on this event.
|
|
4992
|
+
*/
|
|
4993
|
+
EventCategory["AddonLoadRecovered"] = "addon.load-recovered";
|
|
4994
|
+
/**
|
|
4995
|
+
* An addon is RUNNING although its installed package is below what the
|
|
4996
|
+
* running server requires (`camstack.requires.addons` of the server, or the
|
|
4997
|
+
* addon's own `requires.server`). Raised once per streak by the unsupported-
|
|
4998
|
+
* addon tracker, on the hub and on agents; AlertCenter turns it into one
|
|
4999
|
+
* operator alert per node and package. The addon is never held back for it.
|
|
5000
|
+
*
|
|
5001
|
+
* Payload: `{ packageName, addonId, nodeId, installedVersion, requiredMinimum, serverVersion, unmet }`.
|
|
5002
|
+
*/
|
|
5003
|
+
EventCategory["AddonUnsupportedVersion"] = "addon.unsupported-version";
|
|
5004
|
+
/**
|
|
5005
|
+
* The package of an {@link AddonUnsupportedVersion} alert reached a supported
|
|
5006
|
+
* version, or is no longer installed on the node. AlertCenter dismisses the matching alert.
|
|
5007
|
+
*
|
|
5008
|
+
* Payload: `{ packageName, addonId, nodeId }`.
|
|
5009
|
+
*/
|
|
5010
|
+
EventCategory["AddonUnsupportedResolved"] = "addon.unsupported-resolved";
|
|
5011
|
+
/**
|
|
5012
|
+
* Monitor scheduled the next retry for a failed addon. Transient —
|
|
5013
|
+
* surfaced to the UI for live-updating the "next retry in Ns"
|
|
5014
|
+
* countdown on the Addons page row, NOT persisted as an alert.
|
|
5015
|
+
*/
|
|
5016
|
+
EventCategory["AddonRetryScheduled"] = "addon.retry-scheduled";
|
|
5017
|
+
/**
|
|
5018
|
+
* A liveness invariant of this node is false — it has no devices, every
|
|
5019
|
+
* camera is offline, or a camera that was recording has stopped producing
|
|
5020
|
+
* segments. Emitted by the `liveness-monitor` builtin, once per fault (the
|
|
5021
|
+
* `findingId` is stable across ticks), only after its boot grace period.
|
|
5022
|
+
* AlertCenter raises a persistent operator-visible alert.
|
|
5023
|
+
*
|
|
5024
|
+
* It exists because on 2026-08-03 the hub ran four hours with zero devices
|
|
5025
|
+
* and zero recordings while every surface stayed quiet.
|
|
5026
|
+
*
|
|
5027
|
+
* Payload: `{ findingId, severity, title, message, deviceId? }`.
|
|
5028
|
+
*/
|
|
5029
|
+
EventCategory["SystemLivenessFailed"] = "system.liveness-failed";
|
|
5030
|
+
/**
|
|
5031
|
+
* A previously reported liveness fault is true again. AlertCenter dismisses
|
|
5032
|
+
* the matching `SystemLivenessFailed` alert.
|
|
5033
|
+
*
|
|
5034
|
+
* Payload: `{ findingId }`.
|
|
5035
|
+
*/
|
|
5036
|
+
EventCategory["SystemLivenessRecovered"] = "system.liveness-recovered";
|
|
5037
|
+
/**
|
|
5038
|
+
* Monitor is attempting to reload a failed addon NOW. UI uses this
|
|
5039
|
+
* to show a spinner during the retry attempt. Same transient nature
|
|
5040
|
+
* as AddonRetryScheduled.
|
|
5041
|
+
*/
|
|
5042
|
+
EventCategory["AddonRetryAttempting"] = "addon.retry-attempting";
|
|
5043
|
+
EventCategory["DeviceRegistered"] = "device.registered";
|
|
5044
|
+
EventCategory["DeviceUnregistered"] = "device.unregistered";
|
|
5045
|
+
EventCategory["DeviceEnabled"] = "device.enabled";
|
|
5046
|
+
EventCategory["DeviceDisabled"] = "device.disabled";
|
|
5047
|
+
EventCategory["DeviceSettingsUpdated"] = "device.settings-updated";
|
|
5048
|
+
/**
|
|
5049
|
+
* Emitted when the set of native capability providers bound to a device
|
|
5050
|
+
* changes — e.g. an addon registers a new native cap via
|
|
5051
|
+
* `DeviceContext.registerNativeCap`, or all native bindings for a device
|
|
5052
|
+
* are cleared on removal. Hub consumers re-resolve device-proxy routes
|
|
5053
|
+
* when this fires.
|
|
5054
|
+
*/
|
|
5055
|
+
EventCategory["DeviceBindingsChanged"] = "device.bindings-changed";
|
|
5056
|
+
/**
|
|
5057
|
+
* Emitted when the operator-organisational meta surface changes
|
|
5058
|
+
* (`name` / `location` / `disabled`). Payload: `{deviceId, field,
|
|
5059
|
+
* value}`. Live consumers (UI device list, alert center) react
|
|
5060
|
+
* without polling. Distinct from `DeviceSettingsUpdated` which
|
|
5061
|
+
* fires on hardware-config changes (host/port/credentials/etc).
|
|
5062
|
+
*/
|
|
5063
|
+
EventCategory["DeviceMetaChanged"] = "device.meta-changed";
|
|
5064
|
+
/**
|
|
5065
|
+
* Emitted by `BaseDevice.updateSourceInfo()` after a successful patch
|
|
5066
|
+
* to the device's upstream-system identity / rendering envelope. The
|
|
5067
|
+
* full new SourceInfo travels in the payload so cross-process listeners
|
|
5068
|
+
* (UI, export adapters) don't need to re-read the meta blob.
|
|
5069
|
+
*
|
|
5070
|
+
* Payload: `{deviceId, sourceInfo}`.
|
|
5071
|
+
*
|
|
5072
|
+
* Distinct from `DeviceMetaChanged` which covers the operator-edited
|
|
5073
|
+
* surface (`name` / `location` / `disabled`). The two share the
|
|
5074
|
+
* persistence layer (both ride on `device-manager.setMetadata` for
|
|
5075
|
+
* SourceInfo, or `setName`/`setLocation`/`setDisabled` for the meta
|
|
5076
|
+
* fields) but consumers care about different subsets.
|
|
5077
|
+
*/
|
|
5078
|
+
EventCategory["DeviceSourceInfoChanged"] = "device.source-info-changed";
|
|
5079
|
+
/**
|
|
5080
|
+
* Emitted by DeviceStreamWiringService after a successful
|
|
5081
|
+
* `stream-broker.registerDeviceStreams` call. Payload includes
|
|
5082
|
+
* deviceId — consumers look up the full registered device info via
|
|
5083
|
+
* `brokerManager.getRegisteredDevice(deviceId)`.
|
|
5084
|
+
*
|
|
5085
|
+
* Replaces the legacy `StreamRouterService.onDeviceRegistered` callback.
|
|
5086
|
+
*/
|
|
5087
|
+
EventCategory["DeviceStreamsRegistered"] = "device.streams-registered";
|
|
5088
|
+
/**
|
|
5089
|
+
* Device-level "fully provisioned" signal — the EXPORT trigger. Emitted once
|
|
5090
|
+
* a device's persisted export-relevant shape (its `DeviceFeature` set) is
|
|
5091
|
+
* established or changes, carrying `{deviceId, fingerprint, generation}`.
|
|
5092
|
+
* Export adapters (Alexa / HAP) react to THIS instead of the chatty per-cap
|
|
5093
|
+
* `DeviceBindingsChanged`, turning a boot's incomplete→complete trickle into
|
|
5094
|
+
* one clean delta. `fingerprint` is `canonicalDeviceFingerprint(shape)`.
|
|
5095
|
+
*
|
|
5096
|
+
* Spec: docs/superpowers/specs/2026-06-01-alexa-hap-export-reconciler-design.md
|
|
5097
|
+
*/
|
|
5098
|
+
EventCategory["DeviceProvisioned"] = "device.provisioned";
|
|
5099
|
+
/**
|
|
5100
|
+
* Fires once when a device's initial feature-probe completes successfully
|
|
5101
|
+
* (lastProbedAt 0→>0); exported shape is now stable. Telemetry (may be
|
|
5102
|
+
* dropped) — consumers MUST also gate on the device record's `probed` flag.
|
|
5103
|
+
*/
|
|
5104
|
+
EventCategory["DeviceReady"] = "device.ready";
|
|
5105
|
+
EventCategory["IntegrationEnabled"] = "integration.enabled";
|
|
5106
|
+
EventCategory["IntegrationDisabled"] = "integration.disabled";
|
|
5107
|
+
EventCategory["IntegrationDeleted"] = "integration.deleted";
|
|
5108
|
+
/** Emitted when a broker connection's status flips (connected /
|
|
5109
|
+
* disconnected / error). Carries `{ brokerId, status, error? }`. */
|
|
5110
|
+
EventCategory["BrokerStatusChanged"] = "broker.status-changed";
|
|
5111
|
+
/** Emitted for every subscription-matched message routed by a
|
|
5112
|
+
* broker provider. Carries `{ brokerId, subscriptionId, key, payload }`.
|
|
5113
|
+
* Consumers filter by `brokerId` + `subscriptionId` in the handler. */
|
|
5114
|
+
EventCategory["BrokerMessage"] = "broker.message";
|
|
5115
|
+
EventCategory["ProviderStarted"] = "provider.started";
|
|
5116
|
+
EventCategory["ProviderStopped"] = "provider.stopped";
|
|
5117
|
+
EventCategory["ProcessCrashed"] = "process.crashed";
|
|
5118
|
+
EventCategory["ProcessRestartScheduled"] = "process.restart_scheduled";
|
|
5119
|
+
EventCategory["ProcessRestarted"] = "process.restarted";
|
|
5120
|
+
/**
|
|
5121
|
+
* The SET of storage locations changed — one was created, edited, enabled,
|
|
5122
|
+
* disabled or deleted through the location instances or the forced-delete action.
|
|
5123
|
+
*
|
|
5124
|
+
* Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
|
|
5125
|
+
* it must also converge on its own periodic path, because a dropped event
|
|
5126
|
+
* must not leave a node writing to yesterday's disk set forever. It exists
|
|
5127
|
+
* because there was NO signal at all — an operator who added a second
|
|
5128
|
+
* recordings disk in the admin UI got nothing, and the recorder kept its
|
|
5129
|
+
* resolved locations until something else happened to re-resolve them
|
|
5130
|
+
* (D387). Payload `StorageLocationsChangedPayload`.
|
|
5131
|
+
*/
|
|
5132
|
+
EventCategory["StorageLocationsChanged"] = "storage.locations-changed";
|
|
5133
|
+
EventCategory["RecordingStarted"] = "recording.started";
|
|
5134
|
+
EventCategory["RecordingStopped"] = "recording.stopped";
|
|
5135
|
+
EventCategory["RecordingError"] = "recording.error";
|
|
5136
|
+
EventCategory["RecordingHealthDegraded"] = "recording.health.degraded";
|
|
5137
|
+
/** Recording enabled but no segment finalized for the NC stall window. */
|
|
5138
|
+
EventCategory["RecordingHealthStalled"] = "recording.health.stalled";
|
|
5139
|
+
/** Recovery from {@link RecordingHealthStalled}. */
|
|
5140
|
+
EventCategory["RecordingHealthOk"] = "recording.health.ok";
|
|
5141
|
+
EventCategory["RecordingStorageCritical"] = "recording.storage.critical";
|
|
5142
|
+
/** Free space on a storage location crossed the warn bar. */
|
|
5143
|
+
EventCategory["StorageLocationLow"] = "storage.location.low";
|
|
5144
|
+
/** Free space at/under the critical bar, or capacity probe failed (`null`). */
|
|
5145
|
+
EventCategory["StorageLocationCritical"] = "storage.location.critical";
|
|
5146
|
+
/** Recovery from low/critical on a storage location. */
|
|
5147
|
+
EventCategory["StorageLocationOk"] = "storage.location.ok";
|
|
5148
|
+
EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
|
|
5149
|
+
EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
|
|
5150
|
+
EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
|
|
5151
|
+
/** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
|
|
5152
|
+
* progress bar the client reconciles via `recordingExport.getExport`. */
|
|
5153
|
+
EventCategory["RecordingExportProgress"] = "recording.export.progress";
|
|
5154
|
+
EventCategory["RecordingExportCompleted"] = "recording.export.completed";
|
|
5155
|
+
EventCategory["RecordingExportFailed"] = "recording.export.failed";
|
|
5156
|
+
EventCategory["DetectionEvent"] = "detection.event";
|
|
5157
|
+
EventCategory["SessionTrackNew"] = "session.track.new";
|
|
5158
|
+
EventCategory["SessionTrackExpired"] = "session.track.expired";
|
|
5159
|
+
EventCategory["BenchmarkProgress"] = "benchmark.progress";
|
|
5160
|
+
EventCategory["PlatformProbePhase"] = "platform-probe.phase";
|
|
5161
|
+
EventCategory["PipelineProgress"] = "pipeline.progress";
|
|
5162
|
+
/** Per-frame execution trace emitted by the pipeline executor for live observability. */
|
|
5163
|
+
EventCategory["PipelineTrace"] = "pipeline.trace";
|
|
5164
|
+
/**
|
|
5165
|
+
* Raw inference output emitted by `addon-pipeline-runner` after running the
|
|
5166
|
+
* detection pipeline on a frame. Carries the `FrameResult` (with
|
|
5167
|
+
* `detections[]`, `width`/`height`, timing debug) — never the frame
|
|
5168
|
+
* buffer itself. Hub-side consumers (analysis pipeline, class filters,
|
|
5169
|
+
* notifications) subscribe to this and re-emit `detection.result` after
|
|
5170
|
+
* post-processing.
|
|
5171
|
+
*/
|
|
5172
|
+
EventCategory["PipelineInferenceResult"] = "pipeline.inference-result";
|
|
5173
|
+
/**
|
|
5174
|
+
* Camera lifecycle event emitted by `addon-pipeline-orchestrator` when it
|
|
5175
|
+
* assigns or unassigns a camera to/from an agent. Carries no frame data;
|
|
5176
|
+
* pure observability for UI dashboards and metrics consumers.
|
|
5177
|
+
*/
|
|
5178
|
+
EventCategory["PipelineCameraAssigned"] = "pipeline.camera-assigned";
|
|
5179
|
+
EventCategory["PipelineCameraUnassigned"] = "pipeline.camera-unassigned";
|
|
5180
|
+
/**
|
|
5181
|
+
* A node the orchestrator would otherwise place cameras on has NO usable
|
|
5182
|
+
* inference device: the operator enabled one or more accelerators there and
|
|
5183
|
+
* the live probe reports every one of them unavailable. Emitted once per
|
|
5184
|
+
* TRANSITION into that state (never per dispatch), and the node is dropped
|
|
5185
|
+
* from the placement candidate set for as long as it holds.
|
|
5186
|
+
*
|
|
5187
|
+
* This exists because the state was previously invisible: agent-a
|
|
5188
|
+
* absorbed 283k inference errors in a day while still being handed cameras,
|
|
5189
|
+
* and nothing in the system said so.
|
|
5190
|
+
*
|
|
5191
|
+
* A node with no accelerators configured at all is NOT this — its devices
|
|
5192
|
+
* are `disabled`, not `unavailable`, and the runner's default CPU pool
|
|
5193
|
+
* serves it exactly as before.
|
|
5194
|
+
*/
|
|
5195
|
+
EventCategory["PipelineNodeInferenceUnavailable"] = "pipeline.node-inference-unavailable";
|
|
5196
|
+
/**
|
|
5197
|
+
* A camera has an OPEN detection session and has produced no detection at
|
|
5198
|
+
* all for longer than the blind threshold — the camera is being decoded and
|
|
5199
|
+
* inferred and is returning nothing. Emitted once per transition into blind,
|
|
5200
|
+
* per camera.
|
|
5201
|
+
*
|
|
5202
|
+
* The failure it reports: a 1h43 detection blackout on the entrance camera
|
|
5203
|
+
* that nobody noticed, because "a camera that detects nothing" and "a quiet
|
|
5204
|
+
* camera" produce byte-identical silence.
|
|
5205
|
+
*/
|
|
5206
|
+
EventCategory["PipelineDetectionBlind"] = "pipeline.detection-blind";
|
|
5207
|
+
/**
|
|
5208
|
+
* A camera's motion source has fired constantly with no subject for an hour
|
|
5209
|
+
* (motion duty or session count over the bar, zero detections) — it is not
|
|
5210
|
+
* blind, its motion zones or sensitivity want tuning (D907). Paired with
|
|
5211
|
+
* {@link PipelineMotionCalm}.
|
|
5212
|
+
*/
|
|
5213
|
+
EventCategory["PipelineMotionNoisy"] = "pipeline.motion-noisy";
|
|
5214
|
+
/** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
|
|
5215
|
+
EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
|
|
5216
|
+
/**
|
|
5217
|
+
* A camera's package LLM confirm has failed closed three times in a row
|
|
5218
|
+
* (D931, 2026-10-26). One edge per camera until
|
|
5219
|
+
* {@link PipelinePackageLlmAnswered}.
|
|
5220
|
+
*/
|
|
5221
|
+
EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
|
|
5222
|
+
/** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
|
|
5223
|
+
EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
|
|
5224
|
+
/**
|
|
5225
|
+
* Per-camera pipeline config was mutated by the orchestrator
|
|
5226
|
+
* (a settings change via `setAgentInferenceDevices` /
|
|
5227
|
+
* `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
|
|
5228
|
+
* subscribes to its own emission to hot-reload the assigned runner
|
|
5229
|
+
* via `attachCamera` so the next frame executes against the new
|
|
5230
|
+
* engine/steps without waiting for a rebalance or the next
|
|
5231
|
+
* `DeviceStreamsRegistered` cycle.
|
|
5232
|
+
*/
|
|
5233
|
+
EventCategory["PipelineCameraUpdated"] = "pipeline.camera-updated";
|
|
5234
|
+
/**
|
|
5235
|
+
* The cluster camera-source OWNER changed (`clusterRoles.ingestNode`).
|
|
5236
|
+
* Emitted by addon-pipeline-orchestrator whenever it (re)derives node
|
|
5237
|
+
* capabilities — at boot, on agent online/offline, and on an ingest-node
|
|
5238
|
+
* flip. Carries the resolved `ownerNodeId`. The stream-broker consumes it to
|
|
5239
|
+
* keep its ingest-owner-gate decision current WITHOUT a per-`ensureBroker`
|
|
5240
|
+
* cross-process `getIngestOwner` query (push the authority's decision instead
|
|
5241
|
+
* of polling it on the hot path). Idempotent state — re-emitted on every
|
|
5242
|
+
* topology change, so a dropped event self-heals on the next one (plus the
|
|
5243
|
+
* broker's long backstop reconcile query).
|
|
5244
|
+
*/
|
|
5245
|
+
EventCategory["PipelineIngestOwnerChanged"] = "pipeline.ingest-owner-changed";
|
|
5246
|
+
/**
|
|
5247
|
+
* Periodic snapshot of per-node pipeline-runner load
|
|
5248
|
+
* (`RunnerLocalLoad`). Emitted ~1Hz by every runner so UI dashboards
|
|
5249
|
+
* subscribe instead of polling `pipelineRunner.getLocalLoad`.
|
|
5250
|
+
* `nodeId` carried in the payload + on `event.source.nodeId`.
|
|
5251
|
+
*/
|
|
5252
|
+
EventCategory["PipelineRunnerLoadSnapshot"] = "pipeline.runner-load-snapshot";
|
|
5253
|
+
/**
|
|
5254
|
+
* Periodic snapshot of per-camera pipeline metrics (`CameraMetrics`
|
|
5255
|
+
* + `deviceId` + `nodeId`). Emitted ~1Hz by every runner for each
|
|
5256
|
+
* attached camera. UI subscribes to drive overlay phase / fps /
|
|
5257
|
+
* inference time without polling `getCameraMetrics`.
|
|
5258
|
+
*/
|
|
5259
|
+
EventCategory["PipelineCameraMetricsSnapshot"] = "pipeline.camera-metrics-snapshot";
|
|
5260
|
+
/**
|
|
5261
|
+
* Periodic snapshot of stream-broker per-broker statistics (input
|
|
5262
|
+
* fps, decoded fps, bitrate, codec). Emitted ~1Hz by every
|
|
5263
|
+
* stream-broker process for each active broker so the UI can drive
|
|
5264
|
+
* the Stream / Cluster dashboards without polling
|
|
5265
|
+
* `streamBroker.listAllProfileSlots` and friends.
|
|
5266
|
+
*/
|
|
5267
|
+
EventCategory["StreamBrokerMetricsSnapshot"] = "stream-broker.metrics-snapshot";
|
|
5268
|
+
/**
|
|
5269
|
+
* Cap event fired by `stream-broker` when a profile slot enters
|
|
5270
|
+
* "demanded" state — a cam stream has been assigned and at least one
|
|
5271
|
+
* consumer (RTSP restream, decoded subscriber, WebRTC session, …) is
|
|
5272
|
+
* present. Camera-provider addons (Reolink Baichuan push, …)
|
|
5273
|
+
* subscribe to this category to start their underlying transport
|
|
5274
|
+
* lazily. Payload: `{ deviceId, camStreamId, profile }`.
|
|
5275
|
+
*/
|
|
5276
|
+
EventCategory["StreamBrokerOnCamStreamDemand"] = "stream-broker.onCamStreamDemand";
|
|
5277
|
+
/**
|
|
5278
|
+
* Cap event fired by `stream-broker` when the last consumer leaves a
|
|
5279
|
+
* previously-demanded cam stream. Providers tear down their
|
|
5280
|
+
* underlying transport on receipt. Payload: `{ deviceId, camStreamId }`.
|
|
5281
|
+
*/
|
|
5282
|
+
EventCategory["StreamBrokerOnCamStreamIdle"] = "stream-broker.onCamStreamIdle";
|
|
5283
|
+
/**
|
|
5284
|
+
* Cap event fired by `stream-broker` when a broker fails to dial a
|
|
5285
|
+
* managed-loopback source (today: `pull-rfc4571`) and the publisher
|
|
5286
|
+
* needs to refresh the cached URL. The lib's TCP server idle-tears-down
|
|
5287
|
+
* on its own schedule, so a re-publish with a fresh `host:port` is the
|
|
5288
|
+
* only way to keep the broker dialable. Camera providers (Reolink
|
|
5289
|
+
* Baichuan native, …) subscribe and respond by re-running their publish
|
|
5290
|
+
* pipeline.
|
|
5291
|
+
* Payload: `{ deviceId, camStreamId, brokerId }`.
|
|
5292
|
+
*/
|
|
5293
|
+
EventCategory["StreamBrokerOnRequestStreamSourceRefresh"] = "stream-broker.onRequestStreamSourceRefresh";
|
|
5294
|
+
/**
|
|
5295
|
+
* A camera provider changed a device's stream parameters (codec /
|
|
5296
|
+
* resolution / bitrate / a stream added or removed). A LOW-LATENCY NUDGE
|
|
5297
|
+
* for the stream-broker's catalog reconcile: it carries NO authoritative
|
|
5298
|
+
* data — the broker re-PULLS that device's `stream-catalog` cap on receipt
|
|
5299
|
+
* (the 30s reconcile poll is the backstop if this nudge is dropped). Emitted
|
|
5300
|
+
* by providers from `stream-params.setProfile`. Payload: `{ deviceId }`.
|
|
5301
|
+
*/
|
|
5302
|
+
EventCategory["StreamParamsChanged"] = "stream-params.changed";
|
|
5303
|
+
/**
|
|
5304
|
+
* Generic per-device runtime-state change. Fired by `device-manager`
|
|
5305
|
+
* whenever a persisted slice in any cap's `runtimeState` shape
|
|
5306
|
+
* mutates. Payload: `{deviceId, capName, slice}`. Subscribers are
|
|
5307
|
+
* the `deviceState` cap router (cross-process listeners) and the
|
|
5308
|
+
* deviceProxy reactive bindings (`device.state.<capName>.value`).
|
|
5309
|
+
* Cap-specific events (`battery.onStatusChanged`, …) still fire
|
|
5310
|
+
* — they're authoritative for callers that want a typed payload
|
|
5311
|
+
* without filtering on `capName`.
|
|
5312
|
+
*/
|
|
5313
|
+
EventCategory["DeviceStateChanged"] = "device.state-changed";
|
|
5314
|
+
/**
|
|
5315
|
+
* The NATIVE shadow of a cap a composition has claimed fields of moved
|
|
5316
|
+
* (D663). Payload: `{ deviceId, capName, native }` — what the provider
|
|
5317
|
+
* wrote, BEFORE the owned fields are overlaid. Owner-only (D224): it is for
|
|
5318
|
+
* the composer's native self-reads, and the public event routers refuse it
|
|
5319
|
+
* (`isOwnerOnlyEventCategory`) so no consumer surface ever shows a second
|
|
5320
|
+
* truth beside the merged `DeviceStateChanged.slice`. Emitted only under a
|
|
5321
|
+
* `replace` claim, one per native write of that cap.
|
|
5322
|
+
*/
|
|
5323
|
+
EventCategory["DeviceNativeShadowChanged"] = "device.native-shadow-changed";
|
|
5324
|
+
/**
|
|
5325
|
+
* Frame occupancy for a camera CHANGED — a tracked object was gained or
|
|
5326
|
+
* lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
|
|
5327
|
+
*
|
|
5328
|
+
* Emitted only on a change, so a steady scene is silent. It exists so a
|
|
5329
|
+
* client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
|
|
5330
|
+
* one live badge with no push signal at all, and it cost a request every
|
|
5331
|
+
* four seconds per visible camera.
|
|
5332
|
+
*
|
|
5333
|
+
* Like every event it is telemetry and may be dropped ([D8]) — a consumer
|
|
5334
|
+
* keeps a slow reconcile rather than trusting it alone.
|
|
5335
|
+
*/
|
|
5336
|
+
EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
|
|
5337
|
+
/**
|
|
5338
|
+
* Cap event fired by every device that registers the `battery`
|
|
5339
|
+
* capability. Mirrors the cap definition's `onStatusChanged`. Carries
|
|
5340
|
+
* `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
|
|
5341
|
+
* snapshot wrapper, UI) react to charge/sleep transitions without
|
|
5342
|
+
* polling `batteryCapability.getStatus`.
|
|
5343
|
+
*/
|
|
5344
|
+
EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
|
|
5345
|
+
/**
|
|
5346
|
+
* Cap event fired by every device that registers the `network-link`
|
|
5347
|
+
* capability. Mirrors the cap definition's `onStatusChanged`. Carries
|
|
5348
|
+
* `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
|
|
5349
|
+
* reading that moved.
|
|
5350
|
+
*/
|
|
5351
|
+
EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
|
|
5352
|
+
/**
|
|
5353
|
+
* Emitted by the battery cap provider WHEN `wakeForStream` enters the
|
|
5354
|
+
* "wake in progress" window — between the Baichuan wake-up issue and
|
|
5355
|
+
* the camera's first dialed-back RTP packet. The stream-broker
|
|
5356
|
+
* manager subscribes to flip the per-broker placeholder reason to
|
|
5357
|
+
* `'waking'` so viewers see a labelled "WAKING UP" tile instead of
|
|
5358
|
+
* the generic `'reconnecting'` frame. Carries `{ deviceId }`. The
|
|
5359
|
+
* complementary "wake complete" signal is the existing
|
|
5360
|
+
* `BatteryOnStatusChanged { sleeping: false }` event.
|
|
5361
|
+
*/
|
|
5362
|
+
EventCategory["BatteryOnWakeStarted"] = "battery.onWakeStarted";
|
|
5363
|
+
/**
|
|
5364
|
+
* Cap event fired by every device that registers the `doorbell`
|
|
5365
|
+
* capability. Mirrors `doorbellCapability.events.onPressed`. Carries
|
|
5366
|
+
* `{ deviceId, timestamp }`. Operators consuming the UI subscribe
|
|
5367
|
+
* here to render transient ring toasts and a "Recent presses" row
|
|
5368
|
+
* on the device detail page.
|
|
5369
|
+
*/
|
|
5370
|
+
EventCategory["DoorbellOnPressed"] = "doorbell.onPressed";
|
|
5371
|
+
/** Mirrors `runningStateCapability.events.onStarted`. */
|
|
5372
|
+
EventCategory["RunningStarted"] = "running.started";
|
|
5373
|
+
/** Mirrors `runningStateCapability.events.onFinished`. */
|
|
5374
|
+
EventCategory["RunningFinished"] = "running.finished";
|
|
5375
|
+
/** Mirrors `deviceRunsCapability.events.onRunOpened` - a durable run record opened. */
|
|
5376
|
+
EventCategory["DeviceRunOpened"] = "device-run.opened";
|
|
5377
|
+
/** Mirrors `deviceRunsCapability.events.onRunClosed` - a durable run record closed. */
|
|
5378
|
+
EventCategory["DeviceRunClosed"] = "device-run.closed";
|
|
5379
|
+
/**
|
|
5380
|
+
* Cap event fired by every device that registers the `event-emitter`
|
|
5381
|
+
* capability. Mirrors `eventEmitterCapability.events.onEvent`. Carries
|
|
5382
|
+
* `{ deviceId, eventType, data, timestamp, seq }` — the device's EXACT
|
|
5383
|
+
* declared event verbatim (NO normalization). Subscribers (UI event
|
|
5384
|
+
* stream, advanced-notifier rules) react to fired events without
|
|
5385
|
+
* holding a cap reference.
|
|
5386
|
+
*/
|
|
5387
|
+
EventCategory["EventEmitted"] = "event-emitter.event";
|
|
5388
|
+
/**
|
|
5389
|
+
* Periodic snapshot of the per-node detection-pipeline engine
|
|
5390
|
+
* registry (loaded engines, models resident, in-use cameras, idle
|
|
5391
|
+
* TTL). Emitted ~0.2Hz (every 5 s) by every detection-pipeline
|
|
5392
|
+
* process. The Engines tab subscribes to drive its inventory view
|
|
5393
|
+
* without polling `pipelineExecutor.listLoadedEngines`.
|
|
5394
|
+
*/
|
|
5395
|
+
EventCategory["PipelineEngineMetricsSnapshot"] = "pipeline.engine-metrics-snapshot";
|
|
5396
|
+
/**
|
|
5397
|
+
* Per-node detection-engine runtime-provisioning transition. Emitted by
|
|
5398
|
+
* the detection-pipeline provider on every state change of its lazy
|
|
5399
|
+
* engine-provisioning machine (idle → installing → verifying → ready,
|
|
5400
|
+
* or → failed with a `nextRetryAt`). Payload is the
|
|
5401
|
+
* `EngineProvisioningState` snapshot; `event.source.nodeId` carries the
|
|
5402
|
+
* node. The Pipeline page subscribes to drive a live "installing
|
|
5403
|
+
* OpenVINO… / ready" indicator per node without polling
|
|
5404
|
+
* `pipelineExecutor.getEngineProvisioning`. Telemetry-grade (D8): the UI
|
|
5405
|
+
* also reads the cap snapshot on mount / reconnect. Phase 2.
|
|
5406
|
+
*/
|
|
5407
|
+
EventCategory["PipelineEngineProvisioning"] = "pipeline.engine-provisioning";
|
|
5408
|
+
/**
|
|
5409
|
+
* Cluster topology snapshot. Carries the same payload returned by
|
|
5410
|
+
* `nodes.topology` (every reachable node + addons + processes).
|
|
5411
|
+
* Emitted by the hub on any agent / addon lifecycle change
|
|
5412
|
+
* (debounced) plus a periodic safety net. Replaces UI polling on
|
|
5413
|
+
* `nodes.topology` — admin-ui dashboards subscribe to drive the
|
|
5414
|
+
* cluster view directly from the event payload.
|
|
5415
|
+
*/
|
|
5416
|
+
EventCategory["ClusterTopologySnapshot"] = "cluster.topology-snapshot";
|
|
5417
|
+
/**
|
|
5418
|
+
* Periodic per-node system metrics snapshot (CPU / memory / GPU /
|
|
5419
|
+
* disk / network). Emitted ~0.2 Hz by the metrics-provider addon
|
|
5420
|
+
* for each node. Drives the dashboard SystemStatus / ProcessResources
|
|
5421
|
+
* widgets without polling `metricsProvider.getCurrent`.
|
|
5422
|
+
*/
|
|
5423
|
+
EventCategory["MetricsNodeResourcesSnapshot"] = "metrics.node-resources-snapshot";
|
|
5424
|
+
/**
|
|
5425
|
+
* Periodic per-node process-tree snapshot (camstack-related pids
|
|
5426
|
+
* with root / managed / system classification). Emitted ~0.2 Hz by
|
|
5427
|
+
* the metrics-provider addon. Drives the Cluster → Processes tab
|
|
5428
|
+
* without polling `metricsProvider.listNodeProcesses`.
|
|
5429
|
+
*/
|
|
5430
|
+
EventCategory["MetricsNodeProcessesSnapshot"] = "metrics.node-processes-snapshot";
|
|
5431
|
+
/**
|
|
5432
|
+
* Capability binding change event emitted by `addon-pipeline-orchestrator`
|
|
5433
|
+
* when a user changes which addon implements a cap on a node. Subscribed
|
|
5434
|
+
* by every kernel process to update its local `preferredProviderRegistry`
|
|
5435
|
+
* so future capability lookups respect the new binding.
|
|
5436
|
+
*/
|
|
5437
|
+
/**
|
|
5438
|
+
* A capability binding was changed for a node — addon X now provides
|
|
5439
|
+
* capability `cap` on node `nodeId`. Lives under the generic
|
|
5440
|
+
* `capability.*` namespace because capability bindings are a kernel-
|
|
5441
|
+
* level concept used by many addons, not strictly pipeline-scoped.
|
|
5442
|
+
*/
|
|
5443
|
+
EventCategory["CapabilityBindingChanged"] = "capability.binding-changed";
|
|
5444
|
+
EventCategory["ModelDownloadProgress"] = "model.download.progress";
|
|
5445
|
+
EventCategory["AgentRegistered"] = "agent.registered";
|
|
5446
|
+
EventCategory["AgentUnregistered"] = "agent.unregistered";
|
|
5447
|
+
EventCategory["AgentOnline"] = "agent.online";
|
|
5448
|
+
EventCategory["AgentOffline"] = "agent.offline";
|
|
5449
|
+
/** Forked worker process (e.g. hub/pipeline) connected to the broker. */
|
|
5450
|
+
EventCategory["WorkerOnline"] = "worker.online";
|
|
5451
|
+
/** Forked worker process disconnected from the broker. */
|
|
5452
|
+
EventCategory["WorkerOffline"] = "worker.offline";
|
|
5453
|
+
EventCategory["AgentTaskDispatched"] = "agent.task.dispatched";
|
|
5454
|
+
EventCategory["AgentTaskAssigned"] = "agent.task.assigned";
|
|
5455
|
+
EventCategory["AgentTrpcConnected"] = "agent.trpc.connected";
|
|
5456
|
+
EventCategory["AgentWsConnected"] = "agent.ws.connected";
|
|
5457
|
+
EventCategory["AgentWsDisconnected"] = "agent.ws.disconnected";
|
|
5458
|
+
EventCategory["AgentBackupActivated"] = "agent.backup.activated";
|
|
5459
|
+
EventCategory["OrchestrationSettingsUpdated"] = "orchestration.settings-updated";
|
|
5460
|
+
EventCategory["MotionAnalysis"] = "detection.motion-analysis";
|
|
5461
|
+
/** All raw motion zones from CCL before minArea filter — for UI debug overlay. */
|
|
5462
|
+
EventCategory["MotionZonesRaw"] = "detection.motion-zones-raw";
|
|
5463
|
+
/**
|
|
5464
|
+
* Per-camera motion phase transition (`watching ↔ active`) emitted
|
|
5465
|
+
* by the runner. Mirrors the `motion.onMotionChanged` cap event
|
|
5466
|
+
* surface — payload `MotionOnMotionChangedPayload` carries
|
|
5467
|
+
* `{deviceId, detected, timestamp, source, regions?}`. Subscribers
|
|
5468
|
+
* include addons that need to react to motion state without
|
|
5469
|
+
* polling the runtime-state mirror.
|
|
5470
|
+
*/
|
|
5471
|
+
EventCategory["MotionOnMotionChanged"] = "motion.on-motion-changed";
|
|
5472
|
+
EventCategory["DetectionResult"] = "detection.result";
|
|
5473
|
+
EventCategory["DetectionRaw"] = "detection.raw";
|
|
5474
|
+
EventCategory["DetectionCameraNative"] = "detection.camera-native";
|
|
5475
|
+
/**
|
|
5476
|
+
* Canonical per-chunk live audio pipeline output. Payload is
|
|
5477
|
+
* `PipelineAudioInferenceResultPayload` carrying a full `AudioResult`
|
|
5478
|
+
* (level + detections + debug). Lives on the pipeline.* namespace
|
|
5479
|
+
* alongside `pipeline.inference-result` (video) for symmetry.
|
|
5480
|
+
*/
|
|
5481
|
+
EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
|
|
5482
|
+
EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
|
|
5483
|
+
/**
|
|
5484
|
+
* A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
|
|
5485
|
+
* data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
|
|
5486
|
+
* consumer is the orchestrator's camera-sensors bridge, which re-emits the
|
|
5487
|
+
* camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
|
|
5488
|
+
* for a camera with no attached sensor). Providers must not emit
|
|
5489
|
+
* `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
|
|
5490
|
+
* keeps it so (camera-grid is a named, deferred exception).
|
|
5491
|
+
*/
|
|
5492
|
+
EventCategory["ProviderMotion"] = "provider.motion";
|
|
5493
|
+
EventCategory["ProviderDetection"] = "provider.detection";
|
|
5494
|
+
EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
|
|
5495
|
+
EventCategory["EnrichmentSceneStateChanged"] = "enrichment.scene.state-changed";
|
|
5496
|
+
EventCategory["EnrichmentActivitySummary"] = "enrichment.activity.summary";
|
|
5497
|
+
/**
|
|
5498
|
+
* Unified track-lifecycle event: ONE category carrying `phase:
|
|
5499
|
+
* 'start' | 'update' | 'end'` with a rich, typed
|
|
5500
|
+
* `PipelineAnalyticsTrackLifecyclePayload`. Supersedes the thin
|
|
5501
|
+
* `PipelineAnalyticsTrackStarted` / `PipelineAnalyticsTrackEnded`
|
|
5502
|
+
* pair — a consumer subscribes once and branches on `phase`.
|
|
5503
|
+
*/
|
|
5504
|
+
EventCategory["PipelineAnalyticsTrackLifecycle"] = "pipeline-analytics.track-lifecycle";
|
|
5505
|
+
/**
|
|
5506
|
+
* @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
|
|
5507
|
+
* (`phase:'start'`). Kept as a soft alias — no known subscribers.
|
|
5508
|
+
*/
|
|
5509
|
+
EventCategory["PipelineAnalyticsTrackStarted"] = "pipeline-analytics.track-started";
|
|
5510
|
+
/**
|
|
5511
|
+
* @deprecated Superseded by {@link PipelineAnalyticsTrackLifecycle}
|
|
5512
|
+
* (`phase:'end'`). Kept as a soft alias — no known subscribers.
|
|
5513
|
+
*/
|
|
5514
|
+
EventCategory["PipelineAnalyticsTrackEnded"] = "pipeline-analytics.track-ended";
|
|
5515
|
+
EventCategory["PipelineAnalyticsDetectionEvent"] = "pipeline-analytics.detection-event";
|
|
5516
|
+
EventCategory["PipelineAnalyticsFrameTracked"] = "pipeline-analytics.frame-tracked";
|
|
5517
|
+
/**
|
|
5518
|
+
* Fired by `addon-post-analysis` when a parked (stationary) object appears
|
|
5519
|
+
* (a track was promoted to a stationary-registry entry) or departs (the
|
|
5520
|
+
* object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
|
|
5521
|
+
* the dedicated "Stationary" section — never the live event feed. Payload:
|
|
5522
|
+
* `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
|
|
5523
|
+
*/
|
|
5524
|
+
EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
|
|
5525
|
+
/**
|
|
5526
|
+
* Fired by `addon-post-analysis` when a package-drop is confirmed inside
|
|
5527
|
+
* a package zone — a newly-appeared stationary object of a package class
|
|
5528
|
+
* that cleared the class / zone / dwell / size gates. Payload:
|
|
5529
|
+
* `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
|
|
5530
|
+
* entryId, className, zoneIds, keyFrameMediaId?, bbox, timestamp }`.
|
|
5531
|
+
* Telemetry (D8): the durable record is the `package-events` store row;
|
|
5532
|
+
* this bus topic drives notifier rules + live UI. See
|
|
5533
|
+
* docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
|
|
5534
|
+
*/
|
|
5535
|
+
EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
|
|
5536
|
+
/**
|
|
5537
|
+
* Fired by `addon-post-analysis` when a previously-delivered package
|
|
5538
|
+
* leaves its zone (the stationary entry departed — moved or swept).
|
|
5539
|
+
* Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
|
|
5540
|
+
* `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
|
|
5541
|
+
* Telemetry (D8). See package-zones-design §5.2.
|
|
5542
|
+
*/
|
|
5543
|
+
EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
|
|
5544
|
+
/**
|
|
5545
|
+
* Fired by `addon-post-analysis` whenever a gallery face row changes:
|
|
5546
|
+
* `kind:'buffered'` a new detected face was persisted, `'assigned'` /
|
|
5547
|
+
* `'unassigned'` its identity link changed, `'deleted'` the row was
|
|
5548
|
+
* removed. Telemetry semantics (D8): a lost event only delays a refresh,
|
|
5549
|
+
* never a correctness issue. Payload `PipelineAnalyticsFaceGalleryChangedPayload`.
|
|
5550
|
+
*/
|
|
5551
|
+
EventCategory["PipelineAnalyticsFaceGalleryChanged"] = "pipeline-analytics.face-gallery-changed";
|
|
5552
|
+
/**
|
|
5553
|
+
* Fired by `addon-post-analysis` whenever a gallery plate row changes:
|
|
5554
|
+
* `kind:'buffered'` a new read was persisted (`PlateRecognizer.onTrackEnd`),
|
|
5555
|
+
* `'assigned'` / `'unassigned'` its vehicle link changed
|
|
5556
|
+
* (`PlateGalleryProvider`), `'deleted'` the row was removed. Telemetry
|
|
5557
|
+
* semantics (D8): a lost event only delays a refresh, never a correctness
|
|
5558
|
+
* issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
|
|
5559
|
+
*/
|
|
5560
|
+
EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
|
|
5561
|
+
EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
|
|
5562
|
+
/**
|
|
5563
|
+
* Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
|
|
5564
|
+
* `stream.offline` fires after STREAM_STALE_TIMEOUT_MS without an encoded
|
|
5565
|
+
* packet on an active broker. `stream.online` fires on first packet
|
|
5566
|
+
* after a stale gap (or on initial first packet). Payload includes
|
|
5567
|
+
* the assigned camStreamId as `profileKey`.
|
|
5568
|
+
*/
|
|
5569
|
+
EventCategory["StreamOnline"] = "stream.online";
|
|
5570
|
+
EventCategory["StreamOffline"] = "stream.offline";
|
|
5571
|
+
EventCategory["NetworkTunnelStarted"] = "network.tunnel.started";
|
|
5572
|
+
EventCategory["NetworkTunnelStopped"] = "network.tunnel.stopped";
|
|
5573
|
+
/** Fired by the `local-network` cap when the host's interface set
|
|
5574
|
+
* changes (new IP from DHCP, VPN connect, docker bridge added). */
|
|
5575
|
+
EventCategory["LocalNetworkChanged"] = "network.local.changed";
|
|
5576
|
+
/**
|
|
5577
|
+
* Fired by a `mesh-network` provider (Tailscale, …) when its
|
|
5578
|
+
* mesh-reachable host changes (join / leave / MagicDNS or 100.x IP
|
|
5579
|
+
* change). Lets `local-network` fold the mesh endpoint into its
|
|
5580
|
+
* connection-endpoint list without a cross-package import. Payload
|
|
5581
|
+
* carries the preferred host (`host: ''` = no longer reachable),
|
|
5582
|
+
* the hub port, and the scheme. Telemetry-grade (D8): consumers
|
|
5583
|
+
* pull-reconcile from the provider's `getStatus` on reconnect. */
|
|
5584
|
+
EventCategory["MeshNetworkChanged"] = "network.mesh.changed";
|
|
5585
|
+
EventCategory["BackupCompleted"] = "backup.completed";
|
|
5586
|
+
/**
|
|
5587
|
+
* A whole backup RUN finished — every destination attempted, win or lose.
|
|
5588
|
+
*
|
|
5589
|
+
* `backup.completed` fires once per DESTINATION, which is the right grain for
|
|
5590
|
+
* a progress UI and the wrong one for a notification: an operator with three
|
|
5591
|
+
* destinations would be told three times. And a run where two of three
|
|
5592
|
+
* destinations succeeded is not a clean success — a single "backup
|
|
5593
|
+
* completed" that hid the failed one would be a lie, so the count of each is
|
|
5594
|
+
* carried here and the message says both.
|
|
5595
|
+
*
|
|
5596
|
+
* Emitted by the backup orchestrator only after the destination loop, so a
|
|
5597
|
+
* run that dies during the BUILD phase produces no completion at all.
|
|
5598
|
+
*/
|
|
5599
|
+
EventCategory["BackupRunCompleted"] = "backup.run-completed";
|
|
5600
|
+
EventCategory["BackupRestored"] = "backup.restored";
|
|
5601
|
+
EventCategory["NotificationDispatched"] = "notification.dispatched";
|
|
5602
|
+
EventCategory["NotificationFailed"] = "notification.failed";
|
|
5603
|
+
/**
|
|
5604
|
+
* A notification rule that cannot work (D905): its stored row does not load,
|
|
5605
|
+
* or it still names a device / delivery target the prune CONFIRMED deleted.
|
|
5606
|
+
* One edge per rule per distinct set of reasons; never on an unanswered read.
|
|
5607
|
+
*/
|
|
5608
|
+
EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
|
|
5609
|
+
/** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
|
|
5610
|
+
EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
|
|
5611
|
+
/** A migration failed or was blocked; the Alert Center re-reads the ledger. */
|
|
5612
|
+
EventCategory["MigrationFailed"] = "migration.failed";
|
|
5613
|
+
/** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
|
|
5614
|
+
EventCategory["MigrationApplied"] = "migration.applied";
|
|
5615
|
+
EventCategory["DeviceUpdated"] = "device.updated";
|
|
5616
|
+
/**
|
|
5617
|
+
* Transport-level connectivity. Emitted by the device driver when
|
|
5618
|
+
* the underlying control socket actually connects / disconnects
|
|
5619
|
+
* (Baichuan TCP, ONVIF probe response, RTSP DESCRIBE, …) — NOT for
|
|
5620
|
+
* power-state transitions on a battery camera. For battery wake /
|
|
5621
|
+
* doze cycles see `DeviceAwake` / `DeviceSleeping`.
|
|
5622
|
+
*/
|
|
5623
|
+
EventCategory["DeviceOnline"] = "device.online";
|
|
5624
|
+
/**
|
|
5625
|
+
* Stream-broker watchdog — emitted when no encoded packet has been
|
|
5626
|
+
* received for STREAM_STALE_TIMEOUT_MS on an active broker (rtsp or
|
|
5627
|
+
* push). Paired with DeviceOnline which fires on first packet after
|
|
5628
|
+
* a stale gap. Payload: DeviceStreamHealthPayload.
|
|
5629
|
+
*/
|
|
5630
|
+
EventCategory["DeviceOffline"] = "device.offline";
|
|
5631
|
+
/**
|
|
5632
|
+
* Battery cam woke up — physical power-state transition reported
|
|
5633
|
+
* by the device firmware. Distinct from `DeviceOnline` so a UI panel
|
|
5634
|
+
* watching power state doesn't flap on every UDP socket reconnect.
|
|
5635
|
+
*/
|
|
5636
|
+
EventCategory["DeviceAwake"] = "device.awake";
|
|
5637
|
+
/**
|
|
5638
|
+
* Battery cam went to sleep. See `DeviceAwake`.
|
|
5639
|
+
*/
|
|
5640
|
+
EventCategory["DeviceSleeping"] = "device.sleeping";
|
|
5641
|
+
EventCategory["RetentionCleanup"] = "retention.cleanup";
|
|
5642
|
+
/**
|
|
5643
|
+
* Legacy bulk-update progress snapshot (payload `BulkUpdateState`). No longer
|
|
5644
|
+
* emitted — F3 removed the coordinator that produced it; "Update all" now runs
|
|
5645
|
+
* as one lifecycle engine job (`AddonsJobProgress`/`AddonsJobLog`). Retained
|
|
5646
|
+
* (with `BulkUpdateState`) only to avoid regenerating the event maps; removed
|
|
5647
|
+
* in F4 once live bulk progress is re-implemented over the engine events.
|
|
5648
|
+
*/
|
|
5649
|
+
EventCategory["AddonsBulkUpdateProgress"] = "addons.bulk-update-progress";
|
|
5650
|
+
EventCategory["AddonsJobProgress"] = "addons.job-progress";
|
|
5651
|
+
EventCategory["AddonsJobLog"] = "addons.job-log";
|
|
5652
|
+
/**
|
|
5653
|
+
* A container's child visibility toggled (hidden/shown). Emitted by the
|
|
5654
|
+
* `accessories` cap when a child device is hidden or revealed.
|
|
5655
|
+
* Payload: `{ deviceId, childDeviceId, hidden }`.
|
|
5656
|
+
*/
|
|
5657
|
+
EventCategory["AccessoriesChildVisibilityChanged"] = "accessories.onChildVisibilityChanged";
|
|
5658
|
+
/**
|
|
5659
|
+
* A container's child set changed (children added/removed/reordered).
|
|
5660
|
+
* Payload: `{ deviceId, childDeviceIds, hiddenChildIds }`.
|
|
5661
|
+
*/
|
|
5662
|
+
EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
|
|
5663
|
+
/**
|
|
5664
|
+
* An action run started or ended. Emitted by the `actions` surface.
|
|
5665
|
+
* Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
|
|
5666
|
+
*/
|
|
5667
|
+
EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
|
|
5668
|
+
/**
|
|
5669
|
+
* Progress update from a running model conversion job.
|
|
5670
|
+
* Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
|
|
5671
|
+
* Emitted by `addon-model-studio` on the converting node.
|
|
5672
|
+
*/
|
|
5673
|
+
EventCategory["ModelConvertProgress"] = "model-convert.progress";
|
|
5674
|
+
return EventCategory;
|
|
5675
|
+
}({});
|
|
5676
|
+
//#endregion
|
|
5677
|
+
//#region ../types/dist/device-type-D1fH5kaC.mjs
|
|
5678
|
+
/**
|
|
5679
|
+
import { errMsg } from '@camstack/types'
|
|
5680
|
+
* Extract a human-readable message from an unknown error value.
|
|
5681
|
+
* Replaces the ubiquitous `errMsg(err)` pattern.
|
|
5682
|
+
*/
|
|
5683
|
+
function errMsg(err) {
|
|
5684
|
+
if (err instanceof Error) return err.message;
|
|
5685
|
+
if (typeof err === "string") return err;
|
|
5686
|
+
return String(err);
|
|
6060
5687
|
}
|
|
5688
|
+
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
5689
|
+
DeviceType["Camera"] = "camera";
|
|
5690
|
+
DeviceType["Hub"] = "hub";
|
|
5691
|
+
DeviceType["Light"] = "light";
|
|
5692
|
+
DeviceType["Siren"] = "siren";
|
|
5693
|
+
DeviceType["Switch"] = "switch";
|
|
5694
|
+
DeviceType["Sensor"] = "sensor";
|
|
5695
|
+
DeviceType["Thermostat"] = "thermostat";
|
|
5696
|
+
/** Air-conditioner / heat-pump climate device (HVAC) — shares the
|
|
5697
|
+
* `climate-control` cap surface with `Thermostat` but renders a
|
|
5698
|
+
* dedicated AC-appropriate control UI (mode chips, fan speed,
|
|
5699
|
+
* independent vertical/horizontal swing). Sources: native Gree, and
|
|
5700
|
+
* reusable by other AC integrations. */
|
|
5701
|
+
DeviceType["Climate"] = "climate";
|
|
5702
|
+
DeviceType["Button"] = "button";
|
|
5703
|
+
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
5704
|
+
* event vocabulary verbatim (no normalization). Installed with the
|
|
5705
|
+
* `event-emitter` cap. Sources: HA `event.*` entities (structured) and
|
|
5706
|
+
* HA bus events (e.g. `zha_event`, generic). */
|
|
5707
|
+
DeviceType["EventEmitter"] = "event-emitter";
|
|
5708
|
+
/** Firmware/software update entity — current vs available version,
|
|
5709
|
+
* updatable flag, update state, and an install action. Installed with
|
|
5710
|
+
* the `update` cap. Sources: Homematic firmware-update channels (and
|
|
5711
|
+
* reusable by other providers, e.g. HA `update.*` entities). */
|
|
5712
|
+
DeviceType["Update"] = "update";
|
|
5713
|
+
DeviceType["Generic"] = "generic";
|
|
5714
|
+
/** Generic notification delivery target (HA `notify.<service>`, future
|
|
5715
|
+
* Telegram / Discord / ntfy / SMTP, …). One device per delivery
|
|
5716
|
+
* endpoint; the `notifier` cap defines the send surface. */
|
|
5717
|
+
DeviceType["Notifier"] = "notifier";
|
|
5718
|
+
/** Pre-recorded action sequence with optional parameters
|
|
5719
|
+
* (HA `script.*`). Runnable via `script-runner` cap. */
|
|
5720
|
+
DeviceType["Script"] = "script";
|
|
5721
|
+
/** Automation rule (HA `automation.*`) — enable/disable + manual
|
|
5722
|
+
* trigger surface exposed via `automation-control` cap. */
|
|
5723
|
+
DeviceType["Automation"] = "automation";
|
|
5724
|
+
/** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
|
|
5725
|
+
DeviceType["Lock"] = "lock";
|
|
5726
|
+
/** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
|
|
5727
|
+
* `valve.*`). `cover` cap with sub-roles for variant. */
|
|
5728
|
+
DeviceType["Cover"] = "cover";
|
|
5729
|
+
/** Pipe / water / gas valve with open/close/stop and optional
|
|
5730
|
+
* position (HA `valve.*`). `valve` cap — a cover-sibling actuator
|
|
5731
|
+
* modelled on the same open/closed lifecycle. */
|
|
5732
|
+
DeviceType["Valve"] = "valve";
|
|
5733
|
+
/** Humidifier / dehumidifier with on/off + target humidity + mode
|
|
5734
|
+
* (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
|
|
5735
|
+
* modelled on the same target / mode lifecycle. */
|
|
5736
|
+
DeviceType["Humidifier"] = "humidifier";
|
|
5737
|
+
/** Water heater / boiler with target temperature + operation mode +
|
|
5738
|
+
* away mode (HA `water_heater.*`). `water-heater` cap — a
|
|
5739
|
+
* climate-family actuator. */
|
|
5740
|
+
DeviceType["WaterHeater"] = "water-heater";
|
|
5741
|
+
/** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
|
|
5742
|
+
DeviceType["Fan"] = "fan";
|
|
5743
|
+
/** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
|
|
5744
|
+
* the camera surface — those use `Camera`. `media-player` cap. */
|
|
5745
|
+
DeviceType["MediaPlayer"] = "media-player";
|
|
5746
|
+
/** Security panel / alarm system (HA `alarm_control_panel.*`).
|
|
5747
|
+
* `alarm-panel` cap. */
|
|
5748
|
+
DeviceType["AlarmPanel"] = "alarm-panel";
|
|
5749
|
+
/** Generic user-settable input (HA `number` / `input_number` / `select`
|
|
5750
|
+
* / `input_select` / `text` / `input_text` / `input_datetime`).
|
|
5751
|
+
* Sub-type via `DeviceRole`: NumericControl / SelectControl /
|
|
5752
|
+
* TextControl / DateTimeControl. */
|
|
5753
|
+
DeviceType["Control"] = "control";
|
|
5754
|
+
/** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
|
|
5755
|
+
* `presence` cap. */
|
|
5756
|
+
DeviceType["Presence"] = "presence";
|
|
5757
|
+
/** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
|
|
5758
|
+
* `weather` cap. */
|
|
5759
|
+
DeviceType["Weather"] = "weather";
|
|
5760
|
+
/** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
|
|
5761
|
+
DeviceType["Vacuum"] = "vacuum";
|
|
5762
|
+
/** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
|
|
5763
|
+
* `lawn-mower-control` cap. */
|
|
5764
|
+
DeviceType["LawnMower"] = "lawn-mower";
|
|
5765
|
+
/** Physical HA device group — parent container for entity-children
|
|
5766
|
+
* adopted from a single HA device entry. Not renderable as a
|
|
5767
|
+
* standalone device; exists only to anchor child entities. */
|
|
5768
|
+
DeviceType["Container"] = "container";
|
|
5769
|
+
/** Single still-image entity (HA `image.*`). Read-only display of an
|
|
5770
|
+
* `entity_picture` signed URL the browser loads directly. `image` cap. */
|
|
5771
|
+
DeviceType["Image"] = "image";
|
|
5772
|
+
/** Smart pet feeder — cloud-connected food dispenser with a bowl food
|
|
5773
|
+
* level, battery, desiccant life, feeding state and manual-feed /
|
|
5774
|
+
* call-pet / maintenance actions. Installed with the `pet-feeder` cap;
|
|
5775
|
+
* dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
|
|
5776
|
+
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
5777
|
+
* integrations sharing the same food/desiccant/hopper surface. */
|
|
5778
|
+
DeviceType["PetFeeder"] = "pet-feeder";
|
|
5779
|
+
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
5780
|
+
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
5781
|
+
* of a container and is BUILT as a composed device (D967). */
|
|
5782
|
+
DeviceType["PetFountain"] = "pet-fountain";
|
|
5783
|
+
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
5784
|
+
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
5785
|
+
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
5786
|
+
* A plug-built dishwasher carries running-state and power instead. */
|
|
5787
|
+
DeviceType["Dishwasher"] = "dishwasher";
|
|
5788
|
+
/** Washing machine. A plug-built one is running while its power stays
|
|
5789
|
+
* above a threshold. No program cap — that surface is the dishwasher's. */
|
|
5790
|
+
DeviceType["Washer"] = "washer";
|
|
5791
|
+
/** A dumb plug-in stove or heater: a switch (and, with a meter, a power
|
|
5792
|
+
* reading) that only runs while the plug is on. Built from a smart plug;
|
|
5793
|
+
* the `timed-run` cap switches it off again after a chosen time. */
|
|
5794
|
+
DeviceType["Heater"] = "heater";
|
|
5795
|
+
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
5796
|
+
DeviceType["Dryer"] = "dryer";
|
|
5797
|
+
/** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
|
|
5798
|
+
* other radio-to-IP bridges). It carries the box's own facts (firmware,
|
|
5799
|
+
* address, connection state) and the actions the box really supports
|
|
5800
|
+
* (`reboot`, `device-admin-link`). Products are NOT its children — they are
|
|
5801
|
+
* adopted beside it — and its connection settings stay on the integration. */
|
|
5802
|
+
DeviceType["Gateway"] = "gateway";
|
|
5803
|
+
return DeviceType;
|
|
5804
|
+
}({});
|
|
5805
|
+
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
5806
|
+
DeviceFeature["BatteryOperated"] = "battery-operated";
|
|
5807
|
+
DeviceFeature["Rebootable"] = "rebootable";
|
|
5808
|
+
/**
|
|
5809
|
+
* Device supports an on-demand re-sync of its derived spec with its
|
|
5810
|
+
* upstream source — drives the generic Re-sync button. The owning
|
|
5811
|
+
* provider implements the action via the `device-adoption.resync` cap.
|
|
5812
|
+
*/
|
|
5813
|
+
DeviceFeature["Resyncable"] = "resyncable";
|
|
5814
|
+
DeviceFeature["NativeSnapshot"] = "native-snapshot";
|
|
5815
|
+
DeviceFeature["DoorbellButton"] = "doorbell-button";
|
|
5816
|
+
DeviceFeature["TwoWayAudio"] = "two-way-audio";
|
|
5817
|
+
DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
|
|
5818
|
+
/**
|
|
5819
|
+
* Camera supports the on-firmware autotrack subsystem (subject-
|
|
5820
|
+
* following). Distinct from `PanTiltZoom` because not every PTZ
|
|
5821
|
+
* camera ships autotrack — the admin UI uses this flag to gate
|
|
5822
|
+
* the autotrack toggle / settings card without re-deriving from
|
|
5823
|
+
* the cap registry. Mirrors `ptz-autotrack` cap registration:
|
|
5824
|
+
* driver sets this feature when probe confirms the firmware
|
|
5825
|
+
* surface, and registers the cap in the same code path.
|
|
5826
|
+
*/
|
|
5827
|
+
DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
|
|
5828
|
+
/**
|
|
5829
|
+
* Accessory exposes a "trigger on motion" toggle — the parent camera's
|
|
5830
|
+
* motion detection automatically activates this device. Mirrors
|
|
5831
|
+
* `motion-trigger` cap registration: drivers set this feature in the
|
|
5832
|
+
* same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
|
|
5833
|
+
*
|
|
5834
|
+
* Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
|
|
5835
|
+
* fast scalar without binding fetch), notifier rules, and `listAll`
|
|
5836
|
+
* filters that want "all devices with on-motion behaviour".
|
|
5837
|
+
*/
|
|
5838
|
+
DeviceFeature["MotionTrigger"] = "motion-trigger";
|
|
5839
|
+
/** Light supports rgb-triplet color via `color` cap. */
|
|
5840
|
+
DeviceFeature["LightColorRgb"] = "light-color-rgb";
|
|
5841
|
+
/** Light supports HSV color via `color` cap. */
|
|
5842
|
+
DeviceFeature["LightColorHsv"] = "light-color-hsv";
|
|
5843
|
+
/** Light supports color-temperature (mired) via `color` cap. */
|
|
5844
|
+
DeviceFeature["LightColorMired"] = "light-color-mired";
|
|
5845
|
+
/** Thermostat supports a `heat_cool` dual setpoint (targetLow +
|
|
5846
|
+
* targetHigh). Gates the range slider UI. */
|
|
5847
|
+
DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
|
|
5848
|
+
/** Thermostat exposes target humidity and/or current humidity
|
|
5849
|
+
* readings. Gates the humidity controls. */
|
|
5850
|
+
DeviceFeature["ClimateHumidity"] = "climate-humidity";
|
|
5851
|
+
/** Thermostat exposes a fan-mode selector. */
|
|
5852
|
+
DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
|
|
5853
|
+
/** Thermostat exposes preset modes (eco / away / sleep / vendor). */
|
|
5854
|
+
DeviceFeature["ClimatePreset"] = "climate-preset";
|
|
5855
|
+
/** Thermostat exposes a vertical louver swing toggle. Gates the
|
|
5856
|
+
* vertical-swing switch in the climate UI. Independent of horizontal. */
|
|
5857
|
+
DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
|
|
5858
|
+
/** Thermostat exposes a horizontal louver swing toggle. Gates the
|
|
5859
|
+
* horizontal-swing switch in the climate UI. Independent of vertical. */
|
|
5860
|
+
DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
|
|
5861
|
+
/** Cover exposes intermediate position control (0..100). Gates the
|
|
5862
|
+
* position slider UI. */
|
|
5863
|
+
DeviceFeature["CoverPositionable"] = "cover-positionable";
|
|
5864
|
+
/** Cover exposes slat-tilt control. Gates the tilt slider UI. */
|
|
5865
|
+
DeviceFeature["CoverTilt"] = "cover-tilt";
|
|
5866
|
+
/** Cover has a real stop: `stop` halts the travel in place. Gates the stop
|
|
5867
|
+
* control in exporters; a provider whose `stop` is a no-op never declares it. */
|
|
5868
|
+
DeviceFeature["CoverStoppable"] = "cover-stoppable";
|
|
5869
|
+
/** Valve exposes intermediate position control (0..100). Gates the
|
|
5870
|
+
* position slider / drag surface UI. */
|
|
5871
|
+
DeviceFeature["ValvePositionable"] = "valve-positionable";
|
|
5872
|
+
/** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
|
|
5873
|
+
DeviceFeature["FanSpeed"] = "fan-speed";
|
|
5874
|
+
/** Fan exposes a preset mode selector. */
|
|
5875
|
+
DeviceFeature["FanPreset"] = "fan-preset";
|
|
5876
|
+
/** Fan exposes blade direction (forward/reverse) — typical of
|
|
5877
|
+
* ceiling fans. */
|
|
5878
|
+
DeviceFeature["FanDirection"] = "fan-direction";
|
|
5879
|
+
/** Fan exposes an oscillation toggle. */
|
|
5880
|
+
DeviceFeature["FanOscillating"] = "fan-oscillating";
|
|
5881
|
+
/** Lock requires a PIN code on lock/unlock. Gates the code-entry
|
|
5882
|
+
* field on the UI lock-controls panel. */
|
|
5883
|
+
DeviceFeature["LockPinRequired"] = "lock-pin-required";
|
|
5884
|
+
/** Lock supports a latch-release ("open door") action distinct from
|
|
5885
|
+
* unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
|
|
5886
|
+
* `supported_features`. Gates the Open Door button in the UI. */
|
|
5887
|
+
DeviceFeature["LockOpen"] = "lock-open";
|
|
5888
|
+
/** Media player exposes a seek-to-position surface. */
|
|
5889
|
+
DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
|
|
5890
|
+
/** Media player exposes a volume-level setter. */
|
|
5891
|
+
DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
|
|
5892
|
+
/** Media player exposes a mute toggle distinct from volume=0. */
|
|
5893
|
+
DeviceFeature["MediaPlayerMute"] = "media-player-mute";
|
|
5894
|
+
/** Media player exposes a shuffle toggle. */
|
|
5895
|
+
DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
|
|
5896
|
+
/** Media player exposes a repeat mode (off / all / one). */
|
|
5897
|
+
DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
|
|
5898
|
+
/** Media player exposes a source / input selector. */
|
|
5899
|
+
DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
|
|
5900
|
+
/** Media player exposes a play-arbitrary-media surface (URL / id). */
|
|
5901
|
+
DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
|
|
5902
|
+
/** Media player exposes next-track. */
|
|
5903
|
+
DeviceFeature["MediaPlayerNext"] = "media-player-next";
|
|
5904
|
+
/** Media player exposes previous-track. */
|
|
5905
|
+
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
5906
|
+
/** Media player exposes stop distinct from pause. */
|
|
5907
|
+
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
5908
|
+
/** Media player exposes volume up / down in steps (an infrared TV has no level). */
|
|
5909
|
+
DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
|
|
5910
|
+
/** Media player exposes channel up / down in steps. */
|
|
5911
|
+
DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
|
|
5912
|
+
/** Media player exposes power on / off. */
|
|
5913
|
+
DeviceFeature["MediaPlayerPower"] = "media-player-power";
|
|
5914
|
+
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
5915
|
+
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
5916
|
+
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
5917
|
+
* addition to a textual location. */
|
|
5918
|
+
DeviceFeature["PresenceGps"] = "presence-gps";
|
|
5919
|
+
/** Notifier accepts an inline / URL image attachment. */
|
|
5920
|
+
DeviceFeature["NotifierImage"] = "notifier-image";
|
|
5921
|
+
/** Notifier accepts a priority hint (high/normal/low). */
|
|
5922
|
+
DeviceFeature["NotifierPriority"] = "notifier-priority";
|
|
5923
|
+
/** Notifier accepts a free-form `data` payload for platform-specific
|
|
5924
|
+
* fields. */
|
|
5925
|
+
DeviceFeature["NotifierData"] = "notifier-data";
|
|
5926
|
+
/** Notifier supports interactive action buttons / callbacks. */
|
|
5927
|
+
DeviceFeature["NotifierActions"] = "notifier-actions";
|
|
5928
|
+
/** Notifier supports per-call recipient targeting (multi-user). */
|
|
5929
|
+
DeviceFeature["NotifierRecipients"] = "notifier-recipients";
|
|
5930
|
+
/** Script runner accepts a variables map on each run invocation. */
|
|
5931
|
+
DeviceFeature["ScriptVariables"] = "script-variables";
|
|
5932
|
+
/** Automation `trigger` accepts a skipCondition flag — fires the
|
|
5933
|
+
* automation's actions while bypassing its condition block. */
|
|
5934
|
+
DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
|
|
5935
|
+
/** Robot vacuum exposes a live cleaning map (image child). Gates the
|
|
5936
|
+
* map tile in the vacuum UI. */
|
|
5937
|
+
DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
|
|
5938
|
+
/** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
|
|
5939
|
+
* switches group. */
|
|
5940
|
+
DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
|
|
5941
|
+
/** Robot mower exposes a live mowing map (SVG image child). Gates the
|
|
5942
|
+
* mower map tile. */
|
|
5943
|
+
DeviceFeature["MowerHasMap"] = "mower-has-map";
|
|
5944
|
+
/** Robot mower exposes targeted mowing (all-area / zones / edges /
|
|
5945
|
+
* spots) — gates the Mow action + map selectors. */
|
|
5946
|
+
DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
|
|
5947
|
+
return DeviceFeature;
|
|
5948
|
+
}({});
|
|
5949
|
+
/**
|
|
5950
|
+
* Semantic role a device plays within its parent. Populated by driver
|
|
5951
|
+
* addons when creating accessory devices (Reolink siren/floodlight/
|
|
5952
|
+
* PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
|
|
5953
|
+
* admin UI to pick icons, labels, and widgets — a `Switch` with
|
|
5954
|
+
* `role: Floodlight` renders as a bulb with a brightness slider,
|
|
5955
|
+
* whereas a `Switch` with `role: Siren` renders as a klaxon.
|
|
5956
|
+
*
|
|
5957
|
+
* Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
|
|
5958
|
+
* sqlite as a nullable TEXT column — old rows keep working unchanged.
|
|
5959
|
+
*/
|
|
5960
|
+
var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
5961
|
+
DeviceRole["Siren"] = "siren";
|
|
5962
|
+
DeviceRole["Floodlight"] = "floodlight";
|
|
5963
|
+
DeviceRole["Spotlight"] = "spotlight";
|
|
5964
|
+
DeviceRole["PirSensor"] = "pir-sensor";
|
|
5965
|
+
DeviceRole["Chime"] = "chime";
|
|
5966
|
+
DeviceRole["Autotrack"] = "autotrack";
|
|
5967
|
+
DeviceRole["Nightvision"] = "nightvision";
|
|
5968
|
+
DeviceRole["PrivacyMask"] = "privacy-mask";
|
|
5969
|
+
DeviceRole["Doorbell"] = "doorbell";
|
|
5970
|
+
/** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
|
|
5971
|
+
DeviceRole["CoverBlind"] = "cover-blind";
|
|
5972
|
+
/** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
|
|
5973
|
+
DeviceRole["CoverShutter"] = "cover-shutter";
|
|
5974
|
+
/** Exterior awning. HA `device_class: awning`. */
|
|
5975
|
+
DeviceRole["CoverAwning"] = "cover-awning";
|
|
5976
|
+
/** Curtain / drape. HA `device_class: curtain`. */
|
|
5977
|
+
DeviceRole["CoverCurtain"] = "cover-curtain";
|
|
5978
|
+
/** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
|
|
5979
|
+
DeviceRole["CoverGarage"] = "cover-garage";
|
|
5980
|
+
/** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
|
|
5981
|
+
DeviceRole["CoverGate"] = "cover-gate";
|
|
5982
|
+
/** Door. HA `device_class: door`. A security actuator: exports gate it. */
|
|
5983
|
+
DeviceRole["CoverDoor"] = "cover-door";
|
|
5984
|
+
/** Window. HA `device_class: window`. */
|
|
5985
|
+
DeviceRole["CoverWindow"] = "cover-window";
|
|
5986
|
+
/** Virtual HA toggle (input_boolean.*) — distinguishable from a
|
|
5987
|
+
* real Switch device for UI rendering / export adapters. */
|
|
5988
|
+
DeviceRole["BinaryHelper"] = "binary-helper";
|
|
5989
|
+
/** A smart plug: a `switch` whose source says it is an outlet (HA
|
|
5990
|
+
* `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
|
|
5991
|
+
DeviceRole["Outlet"] = "outlet";
|
|
5992
|
+
/** Generic motion / occupancy / moving event source. Distinct from
|
|
5993
|
+
* the camera accessory PirSensor role: that one is a camera child;
|
|
5994
|
+
* this is a standalone HA / 3rd-party motion sensor. */
|
|
5995
|
+
DeviceRole["MotionSensor"] = "motion-sensor";
|
|
5996
|
+
DeviceRole["ContactSensor"] = "contact-sensor";
|
|
5997
|
+
DeviceRole["LeakSensor"] = "leak-sensor";
|
|
5998
|
+
DeviceRole["SmokeSensor"] = "smoke-sensor";
|
|
5999
|
+
DeviceRole["COSensor"] = "co-sensor";
|
|
6000
|
+
DeviceRole["GasSensor"] = "gas-sensor";
|
|
6001
|
+
DeviceRole["TamperSensor"] = "tamper-sensor";
|
|
6002
|
+
DeviceRole["VibrationSensor"] = "vibration-sensor";
|
|
6003
|
+
DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
|
|
6004
|
+
DeviceRole["SoundSensor"] = "sound-sensor";
|
|
6005
|
+
/** Fallback for `binary_sensor` without a known `device_class`. */
|
|
6006
|
+
DeviceRole["BinarySensor"] = "binary-sensor";
|
|
6007
|
+
DeviceRole["TemperatureSensor"] = "temperature-sensor";
|
|
6008
|
+
DeviceRole["HumiditySensor"] = "humidity-sensor";
|
|
6009
|
+
DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
|
|
6010
|
+
DeviceRole["PressureSensor"] = "pressure-sensor";
|
|
6011
|
+
/** Wind speed or direction (weather-station `wind-sensor` cap). */
|
|
6012
|
+
DeviceRole["WindSensor"] = "wind-sensor";
|
|
6013
|
+
/** Rain accumulation or rate (weather-station `rain-sensor` cap). */
|
|
6014
|
+
DeviceRole["RainSensor"] = "rain-sensor";
|
|
6015
|
+
/** UV index (weather-station `uv-sensor` cap). */
|
|
6016
|
+
DeviceRole["UvSensor"] = "uv-sensor";
|
|
6017
|
+
/** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
|
|
6018
|
+
* Distinct from AmbientLightSensor (lux). */
|
|
6019
|
+
DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
|
|
6020
|
+
/** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
|
|
6021
|
+
DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
|
|
6022
|
+
DeviceRole["PowerSensor"] = "power-sensor";
|
|
6023
|
+
DeviceRole["EnergySensor"] = "energy-sensor";
|
|
6024
|
+
DeviceRole["VoltageSensor"] = "voltage-sensor";
|
|
6025
|
+
DeviceRole["CurrentSensor"] = "current-sensor";
|
|
6026
|
+
DeviceRole["AirQualitySensor"] = "air-quality-sensor";
|
|
6027
|
+
/** Battery level (numeric % via `sensor` OR low-bool via
|
|
6028
|
+
* `binary_sensor` — the cap distinguishes via the value type). */
|
|
6029
|
+
DeviceRole["BatterySensor"] = "battery-sensor";
|
|
6030
|
+
/** Fallback for `sensor` numeric without a known `device_class`. */
|
|
6031
|
+
DeviceRole["NumericSensor"] = "numeric-sensor";
|
|
6032
|
+
/** String / enum state (HA `sensor` with `state_class: enum` or
|
|
6033
|
+
* `attributes.options`). */
|
|
6034
|
+
DeviceRole["EnumSensor"] = "enum-sensor";
|
|
6035
|
+
/** Date / timestamp state (HA `sensor` with `device_class: timestamp`
|
|
6036
|
+
* or `date`). The slice carries the raw ISO string verbatim (hosted on
|
|
6037
|
+
* the `enum-sensor` cap); the UI renders it locale-formatted. */
|
|
6038
|
+
DeviceRole["DateTimeSensor"] = "datetime-sensor";
|
|
6039
|
+
/** Last-resort fallback when nothing else matches. */
|
|
6040
|
+
DeviceRole["GenericSensor"] = "generic-sensor";
|
|
6041
|
+
DeviceRole["NumericControl"] = "numeric-control";
|
|
6042
|
+
DeviceRole["SelectControl"] = "select-control";
|
|
6043
|
+
DeviceRole["TextControl"] = "text-control";
|
|
6044
|
+
DeviceRole["DateTimeControl"] = "datetime-control";
|
|
6045
|
+
/** Mobile push notifier (HA `notify.mobile_app_*`) — supports
|
|
6046
|
+
* rich features (image, priority, channel routing). */
|
|
6047
|
+
DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
|
|
6048
|
+
/** Chat / messaging service (HA `notify.telegram_*`,
|
|
6049
|
+
* `notify.discord_*`, etc.). */
|
|
6050
|
+
DeviceRole["MessagingNotifier"] = "messaging-notifier";
|
|
6051
|
+
/** Email-based delivery (HA `notify.smtp`, etc.). */
|
|
6052
|
+
DeviceRole["EmailNotifier"] = "email-notifier";
|
|
6053
|
+
/** Fallback when the notifier service name doesn't match a known
|
|
6054
|
+
* pattern. */
|
|
6055
|
+
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
6056
|
+
/** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
|
|
6057
|
+
* cap. A `DeviceType.Generic` device. */
|
|
6058
|
+
DeviceRole["IrTransmitter"] = "ir-transmitter";
|
|
6059
|
+
return DeviceRole;
|
|
6060
|
+
}({});
|
|
6061
6061
|
//#endregion
|
|
6062
|
-
//#region ../types/dist/sleep-
|
|
6062
|
+
//#region ../types/dist/sleep-CQP-REJp.mjs
|
|
6063
6063
|
/**
|
|
6064
6064
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6065
6065
|
* window to float samples (D455).
|
|
@@ -6423,6 +6423,59 @@ Object.fromEntries([
|
|
|
6423
6423
|
function isValuelessField(field) {
|
|
6424
6424
|
return field.type === "separator" || field.type === "info" || field.type === "qr-code" || field.type === "button" || field.type === "object-array" || field.type === "widget" || field.type === "addon-action-button" || field.type === "device-action-button" || field.type === "account-sign-in" || field.type === "secret-presence";
|
|
6425
6425
|
}
|
|
6426
|
+
/**
|
|
6427
|
+
* Base collector: flatten a hydrated `ConfigUISchemaWithValues` into
|
|
6428
|
+
* `key → { value, default }`, recursing through `group` and `sub-tabs`
|
|
6429
|
+
* layout fields. Keeping `value` and `default` SEPARATE (rather than
|
|
6430
|
+
* pre-resolving) lets callers both read the effective value AND detect
|
|
6431
|
+
* overrides (`value` differs from `default`). Structural over `unknown`;
|
|
6432
|
+
* malformed input yields an empty map, never throws.
|
|
6433
|
+
*/
|
|
6434
|
+
function collectHydratedFieldEntries(schema) {
|
|
6435
|
+
const out = /* @__PURE__ */ new Map();
|
|
6436
|
+
if (typeof schema !== "object" || schema === null) return out;
|
|
6437
|
+
const sections = schema.sections;
|
|
6438
|
+
if (!Array.isArray(sections)) return out;
|
|
6439
|
+
for (const section of sections) if (typeof section === "object" && section !== null) collectFieldEntriesInto(section.fields, out);
|
|
6440
|
+
return out;
|
|
6441
|
+
}
|
|
6442
|
+
/** Recursive field walker for {@link collectHydratedFieldEntries}. */
|
|
6443
|
+
function collectFieldEntriesInto(fields, into) {
|
|
6444
|
+
if (!Array.isArray(fields)) return;
|
|
6445
|
+
for (const field of fields) {
|
|
6446
|
+
if (typeof field !== "object" || field === null) continue;
|
|
6447
|
+
const type = field.type;
|
|
6448
|
+
if (type === "group") {
|
|
6449
|
+
collectFieldEntriesInto(field.fields, into);
|
|
6450
|
+
continue;
|
|
6451
|
+
}
|
|
6452
|
+
if (type === "sub-tabs") {
|
|
6453
|
+
const tabs = field.tabs;
|
|
6454
|
+
if (Array.isArray(tabs)) {
|
|
6455
|
+
for (const tab of tabs) if (typeof tab === "object" && tab !== null) collectFieldEntriesInto(tab.fields, into);
|
|
6456
|
+
}
|
|
6457
|
+
continue;
|
|
6458
|
+
}
|
|
6459
|
+
const key = field.key;
|
|
6460
|
+
if (typeof key !== "string" || key.length === 0) continue;
|
|
6461
|
+
if (!("value" in field)) continue;
|
|
6462
|
+
into.set(key, {
|
|
6463
|
+
value: field.value,
|
|
6464
|
+
default: field.default
|
|
6465
|
+
});
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
/**
|
|
6469
|
+
* Flatten a hydrated settings payload into `key → EFFECTIVE value`
|
|
6470
|
+
* (`value ?? default`). The read-side inverse of `hydrateSchema` for the
|
|
6471
|
+
* common "just give me the resolved values" case. Built on
|
|
6472
|
+
* {@link collectHydratedFieldEntries}.
|
|
6473
|
+
*/
|
|
6474
|
+
function collectHydratedFieldValues(schema) {
|
|
6475
|
+
const out = /* @__PURE__ */ new Map();
|
|
6476
|
+
for (const [key, entry] of collectHydratedFieldEntries(schema)) out.set(key, entry.value ?? entry.default);
|
|
6477
|
+
return out;
|
|
6478
|
+
}
|
|
6426
6479
|
function hydrateSchema(schema, values, options = {}) {
|
|
6427
6480
|
return {
|
|
6428
6481
|
...schema.tabs ? { tabs: [...schema.tabs] } : {},
|
|
@@ -6760,7 +6813,7 @@ function switchRecordsOverMigrationApi(api) {
|
|
|
6760
6813
|
};
|
|
6761
6814
|
} };
|
|
6762
6815
|
}
|
|
6763
|
-
function integrationRowsApiOver(api) {
|
|
6816
|
+
function integrationRowsApiOver$1(api) {
|
|
6764
6817
|
return {
|
|
6765
6818
|
list: async () => (await api.integrations.list({})).integrations,
|
|
6766
6819
|
readLegacyIntegrationRows: (input) => api.legacyRows.read(input),
|
|
@@ -7662,6 +7715,21 @@ var SCOPED_SETTINGS_SCOPES = [
|
|
|
7662
7715
|
"device-defaults",
|
|
7663
7716
|
"node"
|
|
7664
7717
|
];
|
|
7718
|
+
/** The one refusal of a key a scope did not declare. */
|
|
7719
|
+
function assertDeclaredKeys(keys, declared) {
|
|
7720
|
+
const undeclared = keys.filter((key) => !declared.has(key));
|
|
7721
|
+
if (undeclared.length > 0) throw new Error(`scoped settings: [${undeclared.join(", ")}] not declared for this scope (declared: [${[...declared].join(", ")}])`);
|
|
7722
|
+
}
|
|
7723
|
+
/** Build the handle over an addon's scoped read, write and unset. */
|
|
7724
|
+
function createScopedSettings(deps) {
|
|
7725
|
+
return {
|
|
7726
|
+
async get() {
|
|
7727
|
+
return deps.schema.parse(await deps.read());
|
|
7728
|
+
},
|
|
7729
|
+
patch: (patch) => deps.write(Object.fromEntries(Object.entries(patch))),
|
|
7730
|
+
unset: (keys) => deps.unset(keys)
|
|
7731
|
+
};
|
|
7732
|
+
}
|
|
7665
7733
|
/** A write to a secret path could not be stored in the keychain, so it was refused. */
|
|
7666
7734
|
var KeychainWriteRefusedError = class extends Error {
|
|
7667
7735
|
fieldPath;
|
|
@@ -8237,7 +8305,16 @@ function createHandle(raw, opts) {
|
|
|
8237
8305
|
return blob;
|
|
8238
8306
|
}
|
|
8239
8307
|
},
|
|
8240
|
-
writeAddonStore: writeAddon
|
|
8308
|
+
writeAddonStore: writeAddon,
|
|
8309
|
+
removeClearedKeys: (keys) => serialize("addon", async () => {
|
|
8310
|
+
const stored = await readStoredAddonBlob(raw);
|
|
8311
|
+
index.seen("addon", stored);
|
|
8312
|
+
const removable = keys.filter((key) => collectSecretMarkerRefs(stored[key]).length === 0);
|
|
8313
|
+
if (removable.length === 0) return;
|
|
8314
|
+
const patch = Object.fromEntries(removable.map((key) => [key, void 0]));
|
|
8315
|
+
await raw.writeAddonStore(patch);
|
|
8316
|
+
index.wrote("addon", patch);
|
|
8317
|
+
})
|
|
8241
8318
|
};
|
|
8242
8319
|
}
|
|
8243
8320
|
/** The context the addon sees, plus the private raw view and write outcome. */
|
|
@@ -8568,7 +8645,7 @@ var BaseAddon = class {
|
|
|
8568
8645
|
seq: 1e3,
|
|
8569
8646
|
initialBody: spec.initialBody
|
|
8570
8647
|
})], (a) => registrySwitchPortsOver({
|
|
8571
|
-
api: integrationRowsApiOver(a),
|
|
8648
|
+
api: integrationRowsApiOver$1(a),
|
|
8572
8649
|
records: switchRecordsOverMigrationApi(a),
|
|
8573
8650
|
kind: spec.kind
|
|
8574
8651
|
})).bind(api);
|
|
@@ -8782,6 +8859,151 @@ var BaseAddon = class {
|
|
|
8782
8859
|
}
|
|
8783
8860
|
}
|
|
8784
8861
|
/**
|
|
8862
|
+
* Hook: values layered over the stored ones when the FORM of one scope is served
|
|
8863
|
+
* ({@link getScopedSettings}) — a preview that derives dependent options without writing.
|
|
8864
|
+
* Never applied to {@link ScopedSettings.get} or to a write. `null` ⇒ no overlay.
|
|
8865
|
+
*/
|
|
8866
|
+
async scopedSettingsOverlay(_scope, _cap, _nodeId) {
|
|
8867
|
+
return null;
|
|
8868
|
+
}
|
|
8869
|
+
/**
|
|
8870
|
+
* Hook: a write of one scope changed stored values (not called when the store reports no change,
|
|
8871
|
+
* e.g. the write-back of a stored secret). `keys` are the bare field keys of the write, node keys included.
|
|
8872
|
+
*/
|
|
8873
|
+
async onScopedSettingsChanged(_scope, _cap, _keys) {}
|
|
8874
|
+
/** Typed handle over the CLUSTER settings `cap` declares (`clusterSettingsSchema`). */
|
|
8875
|
+
clusterSettings(cap, schema) {
|
|
8876
|
+
return this.scopedHandle("cluster", cap, schema);
|
|
8877
|
+
}
|
|
8878
|
+
/** Typed handle over the fleet defaults of the per-device settings `cap` declares. */
|
|
8879
|
+
deviceDefaults(cap, schema) {
|
|
8880
|
+
return this.scopedHandle("device-defaults", cap, schema);
|
|
8881
|
+
}
|
|
8882
|
+
/**
|
|
8883
|
+
* Typed handle over the settings `cap` owns PER NODE. `nodeId` omitted ⇒ the local node. A node
|
|
8884
|
+
* with no row yields the schema defaults — never a bare-key fallback.
|
|
8885
|
+
*/
|
|
8886
|
+
nodeSettings(cap, schema, nodeId) {
|
|
8887
|
+
return this.scopedHandle("node", cap, schema, nodeId);
|
|
8888
|
+
}
|
|
8889
|
+
/**
|
|
8890
|
+
* {@link state} for a value that is kept per node: stored under `<key>@<nodeId>`, and the
|
|
8891
|
+
* settings store carries `nodeId` in the row data (D986 §1), so a forgotten node's rows go in one
|
|
8892
|
+
* central delete. `nodeId` omitted ⇒ the local node.
|
|
8893
|
+
*/
|
|
8894
|
+
nodeState(key, schema, fallback, nodeId) {
|
|
8895
|
+
this._declaredStateKeys.add(key);
|
|
8896
|
+
return createDurableState({
|
|
8897
|
+
key: nodeScopedKey(key, nodeId ?? this._ctx?.kernel?.localNodeId ?? ""),
|
|
8898
|
+
schema,
|
|
8899
|
+
fallback,
|
|
8900
|
+
read: async () => this.resolveStoredSecrets(await this.readAddonStoreWithRetry()),
|
|
8901
|
+
write: async (patch) => {
|
|
8902
|
+
await this._ctx?.settings?.writeAddonStore(patch);
|
|
8903
|
+
},
|
|
8904
|
+
onParseError: (k, e) => this._ctx?.logger?.warn?.(`durable-state: stored "${k}" failed validation — using fallback`, { meta: {
|
|
8905
|
+
addonId: this._ctx?.id,
|
|
8906
|
+
key: k,
|
|
8907
|
+
error: String(e)
|
|
8908
|
+
} })
|
|
8909
|
+
});
|
|
8910
|
+
}
|
|
8911
|
+
/** The loaded schema `cap` declares for `scope`, or `null`. */
|
|
8912
|
+
async declaredScopedSchema(scope, cap) {
|
|
8913
|
+
await this.loadScopedSchemas(cap);
|
|
8914
|
+
return this.loadedScopedSchemas(cap).find((entry) => entry.scope === scope)?.schema ?? null;
|
|
8915
|
+
}
|
|
8916
|
+
async requireScopedSchema(scope, cap) {
|
|
8917
|
+
const schema = await this.declaredScopedSchema(scope, cap);
|
|
8918
|
+
if (schema === null) throw new Error(`${this.constructor.name}: cap "${cap}" declares no ${scope} settings`);
|
|
8919
|
+
return schema;
|
|
8920
|
+
}
|
|
8921
|
+
scopedKeys(schema) {
|
|
8922
|
+
return new Set(schema.sections.flatMap((section) => collectValueFieldKeys(section.fields)));
|
|
8923
|
+
}
|
|
8924
|
+
/** `store` narrowed to the keys `schema` declares. */
|
|
8925
|
+
narrowToScope(store, schema) {
|
|
8926
|
+
const keys = this.scopedKeys(schema);
|
|
8927
|
+
return Object.fromEntries(Object.entries(store).filter(([key]) => keys.has(key)));
|
|
8928
|
+
}
|
|
8929
|
+
scopedHandle(scope, cap, schema, nodeId) {
|
|
8930
|
+
return createScopedSettings({
|
|
8931
|
+
schema,
|
|
8932
|
+
read: async () => {
|
|
8933
|
+
const declared = await this.requireScopedSchema(scope, cap);
|
|
8934
|
+
const stored = this.narrowToScope(await this.resolveGlobalStore(nodeId, cap), declared);
|
|
8935
|
+
const effective = collectHydratedFieldValues(hydrateSchema(declared, stored));
|
|
8936
|
+
return {
|
|
8937
|
+
...Object.fromEntries([...effective].filter(([, value]) => value !== void 0)),
|
|
8938
|
+
...stored
|
|
8939
|
+
};
|
|
8940
|
+
},
|
|
8941
|
+
write: (patch) => this.writeScoped(scope, cap, patch, nodeId),
|
|
8942
|
+
unset: (keys) => this.unsetScoped(scope, cap, keys, nodeId)
|
|
8943
|
+
});
|
|
8944
|
+
}
|
|
8945
|
+
/** The one write path of the scoped API: declared keys only, then the global patch. */
|
|
8946
|
+
async writeScoped(scope, cap, patch, nodeId) {
|
|
8947
|
+
assertDeclaredKeys(Object.keys(patch), this.scopedKeys(await this.requireScopedSchema(scope, cap)));
|
|
8948
|
+
if (await this.applyGlobalPatch(patch, scope === "node" ? nodeId : void 0, cap)) await this.onScopedSettingsChanged(scope, cap, Object.keys(patch));
|
|
8949
|
+
}
|
|
8950
|
+
/**
|
|
8951
|
+
* Remove declared keys of one scope. A secret goes through {@link clearSecrets} — its keychain
|
|
8952
|
+
* value is released exactly as there — and the emptied row is then removed; every other key is
|
|
8953
|
+
* removed by an `undefined` patch (which a secret transform would read as "keep").
|
|
8954
|
+
*/
|
|
8955
|
+
async unsetScoped(scope, cap, keys, nodeId) {
|
|
8956
|
+
assertDeclaredKeys(keys, this.scopedKeys(await this.requireScopedSchema(scope, cap)));
|
|
8957
|
+
const target = scope === "node" ? nodeId : void 0;
|
|
8958
|
+
const tree = this.addonSecretTree();
|
|
8959
|
+
const secretKeys = keys.filter((key) => tree.has(key));
|
|
8960
|
+
const plainKeys = keys.filter((key) => !tree.has(key));
|
|
8961
|
+
let changed = false;
|
|
8962
|
+
if (plainKeys.length > 0) {
|
|
8963
|
+
const patch = Object.fromEntries(plainKeys.map((key) => [key, void 0]));
|
|
8964
|
+
changed = await this.applyGlobalPatch(patch, target, cap) || changed;
|
|
8965
|
+
}
|
|
8966
|
+
if (secretKeys.length > 0) {
|
|
8967
|
+
await this.clearSecretKeys(secretKeys, target, cap);
|
|
8968
|
+
const node = normalizeNodeId(target ?? this._ctx?.kernel?.localNodeId);
|
|
8969
|
+
const rows = secretKeys.map((key) => scope === "node" ? nodeScopedKey(key, node) : key);
|
|
8970
|
+
if (this._secretSettings !== null) await this._secretSettings.removeClearedKeys(rows);
|
|
8971
|
+
else await this._ctx?.settings?.writeAddonStore(Object.fromEntries(rows.map((row) => [row, void 0])));
|
|
8972
|
+
changed = true;
|
|
8973
|
+
}
|
|
8974
|
+
if (changed) await this.onScopedSettingsChanged(scope, cap, [...keys]);
|
|
8975
|
+
}
|
|
8976
|
+
/**
|
|
8977
|
+
* The hydrated form of one scope of one capability's settings (any
|
|
8978
|
+
* {@link scopedSettingsOverlay} applied), or `null` when the capability declares nothing there.
|
|
8979
|
+
*/
|
|
8980
|
+
async getScopedSettings(scope, cap, nodeId) {
|
|
8981
|
+
const schema = await this.declaredScopedSchema(scope, cap);
|
|
8982
|
+
if (schema === null) return null;
|
|
8983
|
+
const stored = this.narrowToScope(await this.readRawProjectedStore(nodeId, cap), schema);
|
|
8984
|
+
const overlay = await this.scopedSettingsOverlay(scope, cap, nodeId);
|
|
8985
|
+
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(stored, overlay) : stored, { inlineSecretsAsViews: true });
|
|
8986
|
+
}
|
|
8987
|
+
/** The OPERATOR's write of declared keys of one scope (D787 secret semantics). */
|
|
8988
|
+
async updateScopedSettings(scope, cap, patch, nodeId) {
|
|
8989
|
+
this._operatorWriteDepth++;
|
|
8990
|
+
try {
|
|
8991
|
+
await this.writeScoped(scope, cap, patch, nodeId);
|
|
8992
|
+
} finally {
|
|
8993
|
+
this._operatorWriteDepth--;
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
/**
|
|
8997
|
+
* The effective values (`stored ?? default`) of one scope, a secret as its `set` view — never
|
|
8998
|
+
* its value. `null` when the capability declares nothing for the scope.
|
|
8999
|
+
*/
|
|
9000
|
+
async readScopedValues(scope, cap, nodeId) {
|
|
9001
|
+
const schema = await this.declaredScopedSchema(scope, cap);
|
|
9002
|
+
if (schema === null) return null;
|
|
9003
|
+
const form = hydrateSchema(schema, this.narrowToScope(await this.readRawProjectedStore(nodeId, cap), schema), { inlineSecretsAsViews: true });
|
|
9004
|
+
return Object.fromEntries([...collectHydratedFieldValues(form)].filter(([, value]) => value !== void 0));
|
|
9005
|
+
}
|
|
9006
|
+
/**
|
|
8785
9007
|
* What the create wizard and the integration edit page render for one instance: the addon's
|
|
8786
9008
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
8787
9009
|
*/
|
|
@@ -8933,6 +9155,9 @@ var BaseAddon = class {
|
|
|
8933
9155
|
* a keychain (an older kernel) it writes `''`, as such code always did.
|
|
8934
9156
|
*/
|
|
8935
9157
|
async clearSecrets(keys, nodeId) {
|
|
9158
|
+
await this.clearSecretKeys(keys, nodeId);
|
|
9159
|
+
}
|
|
9160
|
+
async clearSecretKeys(keys, nodeId, cap) {
|
|
8936
9161
|
const tree = this.addonSecretTree();
|
|
8937
9162
|
const keychain = this._ctx?.secrets !== void 0;
|
|
8938
9163
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
@@ -8945,25 +9170,27 @@ var BaseAddon = class {
|
|
|
8945
9170
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8946
9171
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8947
9172
|
}
|
|
8948
|
-
await this.applyGlobalPatch(patch, nodeId);
|
|
9173
|
+
await this.applyGlobalPatch(patch, nodeId, cap);
|
|
8949
9174
|
}
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
9175
|
+
/** Resolves `true` when the write changed stored values. */
|
|
9176
|
+
async applyGlobalPatch(barePatch, nodeId, cap) {
|
|
9177
|
+
await this.loadScopedSchemas(cap);
|
|
9178
|
+
const keys = this.perNodeKeys(cap);
|
|
8953
9179
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8954
9180
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
8955
9181
|
const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
|
|
8956
9182
|
const handle = this._secretSettings;
|
|
8957
9183
|
if (handle !== null) {
|
|
8958
9184
|
const { changed } = await handle.writeAddonStore(scoped);
|
|
8959
|
-
if (!changed) return;
|
|
9185
|
+
if (!changed) return false;
|
|
8960
9186
|
} else await this._ctx?.settings?.writeAddonStore(scoped);
|
|
8961
|
-
if (target !== localNode) return;
|
|
9187
|
+
if (target !== localNode) return true;
|
|
8962
9188
|
await this.resolveConfig();
|
|
8963
9189
|
await this.onConfigChanged();
|
|
8964
9190
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8965
9191
|
const legacy = this.globalSettingsSchema();
|
|
8966
9192
|
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
9193
|
+
return true;
|
|
8967
9194
|
}
|
|
8968
9195
|
/**
|
|
8969
9196
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -16015,20 +16242,6 @@ async function flattenNestedRecords(ports, spec) {
|
|
|
16015
16242
|
summary: { converted: rows.size }
|
|
16016
16243
|
};
|
|
16017
16244
|
}
|
|
16018
|
-
/** The ports over one kind's records handle. */
|
|
16019
|
-
function flattenPortsOver(handle) {
|
|
16020
|
-
return {
|
|
16021
|
-
listNested: async (legacyKey) => (await handle.query({ limit: 1e3 })).records.filter((r) => legacyKey in r.data).map((r) => r.id),
|
|
16022
|
-
readResolved: async (id) => {
|
|
16023
|
-
const row = await handle.getResolved(id);
|
|
16024
|
-
if (row === null) throw new Error("the record is gone");
|
|
16025
|
-
return row.data;
|
|
16026
|
-
},
|
|
16027
|
-
patch: async (id, patch) => {
|
|
16028
|
-
await handle.patch(id, patch);
|
|
16029
|
-
}
|
|
16030
|
-
};
|
|
16031
|
-
}
|
|
16032
16245
|
/**
|
|
16033
16246
|
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
|
|
16034
16247
|
* from its own process, so the leftover rows are reached through the migration-only door
|
|
@@ -16076,25 +16289,6 @@ function integrationRowsPortsOverApi(deps) {
|
|
|
16076
16289
|
}
|
|
16077
16290
|
};
|
|
16078
16291
|
}
|
|
16079
|
-
function defaultIntegrationApiOver(api) {
|
|
16080
|
-
return {
|
|
16081
|
-
list: () => api.integrations.list.query(),
|
|
16082
|
-
readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
|
|
16083
|
-
clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
|
|
16084
|
-
listDevices: (addonId) => api.deviceManager.listAll.query({
|
|
16085
|
-
addonId,
|
|
16086
|
-
projection: "slim"
|
|
16087
|
-
}),
|
|
16088
|
-
setDeviceIntegrationId: async (deviceId, integrationId) => {
|
|
16089
|
-
await api.deviceManager.setIntegrationId.mutate({
|
|
16090
|
-
deviceId,
|
|
16091
|
-
integrationId
|
|
16092
|
-
});
|
|
16093
|
-
},
|
|
16094
|
-
readDeviceHasConfig: async (deviceId) => Object.keys((await api.deviceManager.getDevice.query({ deviceId }))?.config ?? {}).length > 0,
|
|
16095
|
-
readDeviceIntegrationId: async (deviceId) => (await api.deviceManager.getDevice.query({ deviceId }))?.integrationId ?? null
|
|
16096
|
-
};
|
|
16097
|
-
}
|
|
16098
16292
|
/**
|
|
16099
16293
|
* The trainer contract — what the hub hands the cloud trainer and what it
|
|
16100
16294
|
* gets back (cloud training, D805+).
|
|
@@ -45492,6 +45686,8 @@ var SettingsSchemaWithValuesSchema = object({
|
|
|
45492
45686
|
});
|
|
45493
45687
|
/** Patch object — keys are field names, values are the new field values. */
|
|
45494
45688
|
var SettingsPatchSchema = record(string(), unknown());
|
|
45689
|
+
/** The scope of a scoped-settings call (D986): where the capability declared the values. */
|
|
45690
|
+
var ScopedSettingsScopeSchema = _enum(SCOPED_SETTINGS_SCOPES);
|
|
45495
45691
|
/** Standard success response for update operations. */
|
|
45496
45692
|
var SettingsUpdateResultSchema = object({ success: literal(true) });
|
|
45497
45693
|
method(object({
|
|
@@ -45507,6 +45703,25 @@ method(object({
|
|
|
45507
45703
|
kind: "mutation",
|
|
45508
45704
|
auth: "admin"
|
|
45509
45705
|
}), method(object({
|
|
45706
|
+
addonId: string(),
|
|
45707
|
+
cap: string(),
|
|
45708
|
+
scope: ScopedSettingsScopeSchema,
|
|
45709
|
+
nodeId: string().optional()
|
|
45710
|
+
}), SettingsSchemaWithValuesSchema.nullable()), method(object({
|
|
45711
|
+
addonId: string(),
|
|
45712
|
+
cap: string(),
|
|
45713
|
+
scope: ScopedSettingsScopeSchema,
|
|
45714
|
+
nodeId: string().optional(),
|
|
45715
|
+
patch: SettingsPatchSchema
|
|
45716
|
+
}), SettingsUpdateResultSchema, {
|
|
45717
|
+
kind: "mutation",
|
|
45718
|
+
auth: "admin"
|
|
45719
|
+
}), method(object({
|
|
45720
|
+
addonId: string(),
|
|
45721
|
+
cap: string(),
|
|
45722
|
+
scope: ScopedSettingsScopeSchema,
|
|
45723
|
+
nodeId: string().optional()
|
|
45724
|
+
}), record(string(), unknown()).nullable()), method(object({
|
|
45510
45725
|
addonId: string(),
|
|
45511
45726
|
nodeId: string().optional()
|
|
45512
45727
|
}), SettingsSchemaWithValuesSchema.nullable()), method(object({
|
|
@@ -78498,7 +78713,7 @@ var TerminalInstanceInfoSchema = object({
|
|
|
78498
78713
|
/**
|
|
78499
78714
|
* False while the node the instance is bound to is not in the cluster
|
|
78500
78715
|
* topology (forgotten, renamed, re-paired). The instance is kept; the
|
|
78501
|
-
* operator re-points it
|
|
78716
|
+
* operator re-points it on the Terminal page.
|
|
78502
78717
|
*/
|
|
78503
78718
|
nodeInCluster: boolean(),
|
|
78504
78719
|
profileId: string(),
|
|
@@ -78536,41 +78751,7 @@ var TerminalOutputBatchSchema = object({
|
|
|
78536
78751
|
snapshot: string().optional(),
|
|
78537
78752
|
events: array(TerminalOutputEventSchema).readonly()
|
|
78538
78753
|
});
|
|
78539
|
-
method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(
|
|
78540
|
-
targetNodeId: string().min(1),
|
|
78541
|
-
profileId: string().min(1),
|
|
78542
|
-
name: string().trim().min(1).max(160).optional(),
|
|
78543
|
-
executable: string().max(1024).optional(),
|
|
78544
|
-
args: array(string().max(2048)).max(64).optional(),
|
|
78545
|
-
cwd: string().max(1024).optional(),
|
|
78546
|
-
environment: array(string().max(4096)).max(64).optional(),
|
|
78547
|
-
profileSettings: ProfileSettingsBagSchema.optional()
|
|
78548
|
-
}), TerminalInstanceInfoSchema, {
|
|
78549
|
-
kind: "mutation",
|
|
78550
|
-
auth: "admin"
|
|
78551
|
-
}), method(object({
|
|
78552
|
-
instanceId: string().min(1),
|
|
78553
|
-
/** Re-point the instance to another node; the profile must exist there. */
|
|
78554
|
-
targetNodeId: string().min(1).max(256).optional(),
|
|
78555
|
-
name: string().trim().min(1).max(160).optional(),
|
|
78556
|
-
executable: string().max(1024).optional(),
|
|
78557
|
-
args: array(string().max(2048)).max(64).optional(),
|
|
78558
|
-
cwd: string().max(1024).optional(),
|
|
78559
|
-
environment: array(string().max(4096)).max(64).optional(),
|
|
78560
|
-
profileSettings: ProfileSettingsBagSchema.optional()
|
|
78561
|
-
}), TerminalInstanceInfoSchema, {
|
|
78562
|
-
kind: "mutation",
|
|
78563
|
-
auth: "admin"
|
|
78564
|
-
}), method(object({ instanceId: string().min(1) }), _void(), {
|
|
78565
|
-
kind: "mutation",
|
|
78566
|
-
auth: "admin"
|
|
78567
|
-
}), method(object({
|
|
78568
|
-
instanceId: string().min(1),
|
|
78569
|
-
enabled: boolean()
|
|
78570
|
-
}), TerminalInstanceInfoSchema, {
|
|
78571
|
-
kind: "mutation",
|
|
78572
|
-
auth: "admin"
|
|
78573
|
-
}), method(_void(), array(TerminalLegacyCameraSchema).readonly(), { auth: "admin" }), method(object({
|
|
78754
|
+
method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalLegacyCameraSchema).readonly(), { auth: "admin" }), method(object({
|
|
78574
78755
|
stableId: string().min(1),
|
|
78575
78756
|
name: string().trim().min(1).max(160).optional()
|
|
78576
78757
|
}), TerminalInstanceInfoSchema, {
|
|
@@ -83390,6 +83571,20 @@ Object.freeze({
|
|
|
83390
83571
|
access: "view",
|
|
83391
83572
|
role: "admin"
|
|
83392
83573
|
},
|
|
83574
|
+
"addonSettings.getScopedSettings": {
|
|
83575
|
+
capName: "addon-settings",
|
|
83576
|
+
capScope: "system",
|
|
83577
|
+
addonId: null,
|
|
83578
|
+
access: "view",
|
|
83579
|
+
role: "admin"
|
|
83580
|
+
},
|
|
83581
|
+
"addonSettings.readScopedValues": {
|
|
83582
|
+
capName: "addon-settings",
|
|
83583
|
+
capScope: "system",
|
|
83584
|
+
addonId: null,
|
|
83585
|
+
access: "view",
|
|
83586
|
+
role: "admin"
|
|
83587
|
+
},
|
|
83393
83588
|
"addonSettings.unsetStoredSettingsKeys": {
|
|
83394
83589
|
capName: "addon-settings",
|
|
83395
83590
|
capScope: "system",
|
|
@@ -83411,6 +83606,13 @@ Object.freeze({
|
|
|
83411
83606
|
access: "create",
|
|
83412
83607
|
role: "admin"
|
|
83413
83608
|
},
|
|
83609
|
+
"addonSettings.updateScopedSettings": {
|
|
83610
|
+
capName: "addon-settings",
|
|
83611
|
+
capScope: "system",
|
|
83612
|
+
addonId: null,
|
|
83613
|
+
access: "create",
|
|
83614
|
+
role: "admin"
|
|
83615
|
+
},
|
|
83414
83616
|
"addonWidgets.listWidgets": {
|
|
83415
83617
|
capName: "addon-widgets",
|
|
83416
83618
|
capScope: "system",
|
|
@@ -92301,27 +92503,6 @@ Object.freeze({
|
|
|
92301
92503
|
access: "create",
|
|
92302
92504
|
role: "admin"
|
|
92303
92505
|
},
|
|
92304
|
-
"terminalSession.createInstance": {
|
|
92305
|
-
capName: "terminal-session",
|
|
92306
|
-
capScope: "system",
|
|
92307
|
-
addonId: null,
|
|
92308
|
-
access: "create",
|
|
92309
|
-
role: "admin"
|
|
92310
|
-
},
|
|
92311
|
-
"terminalSession.deleteInstance": {
|
|
92312
|
-
capName: "terminal-session",
|
|
92313
|
-
capScope: "system",
|
|
92314
|
-
addonId: null,
|
|
92315
|
-
access: "delete",
|
|
92316
|
-
role: "admin"
|
|
92317
|
-
},
|
|
92318
|
-
"terminalSession.listInstances": {
|
|
92319
|
-
capName: "terminal-session",
|
|
92320
|
-
capScope: "system",
|
|
92321
|
-
addonId: null,
|
|
92322
|
-
access: "view",
|
|
92323
|
-
role: "admin"
|
|
92324
|
-
},
|
|
92325
92506
|
"terminalSession.listLegacyCameras": {
|
|
92326
92507
|
capName: "terminal-session",
|
|
92327
92508
|
capScope: "system",
|
|
@@ -92364,20 +92545,6 @@ Object.freeze({
|
|
|
92364
92545
|
access: "create",
|
|
92365
92546
|
role: "admin"
|
|
92366
92547
|
},
|
|
92367
|
-
"terminalSession.setInstanceEnabled": {
|
|
92368
|
-
capName: "terminal-session",
|
|
92369
|
-
capScope: "system",
|
|
92370
|
-
addonId: null,
|
|
92371
|
-
access: "create",
|
|
92372
|
-
role: "admin"
|
|
92373
|
-
},
|
|
92374
|
-
"terminalSession.updateInstance": {
|
|
92375
|
-
capName: "terminal-session",
|
|
92376
|
-
capScope: "system",
|
|
92377
|
-
addonId: null,
|
|
92378
|
-
access: "create",
|
|
92379
|
-
role: "admin"
|
|
92380
|
-
},
|
|
92381
92548
|
"terminalSession.writeInput": {
|
|
92382
92549
|
capName: "terminal-session",
|
|
92383
92550
|
capScope: "system",
|
|
@@ -98744,7 +98911,6 @@ function buildLinkedInstances(records, spec) {
|
|
|
98744
98911
|
});
|
|
98745
98912
|
return instances;
|
|
98746
98913
|
}
|
|
98747
|
-
var BROKER_ROWS_MAX = 1e3;
|
|
98748
98914
|
var STRAY_REGISTRY_ID = /^int_\d+$/;
|
|
98749
98915
|
async function moveBrokerLinksToConnections(ports) {
|
|
98750
98916
|
const brokers = await ports.listBrokers();
|
|
@@ -98776,36 +98942,6 @@ async function moveBrokerLinksToConnections(ports) {
|
|
|
98776
98942
|
}
|
|
98777
98943
|
};
|
|
98778
98944
|
}
|
|
98779
|
-
/** The ports over the two record kinds of the addon. */
|
|
98780
|
-
function brokerLinkPortsOver(deps) {
|
|
98781
|
-
return {
|
|
98782
|
-
warn: deps.warn,
|
|
98783
|
-
listBrokers: async () => {
|
|
98784
|
-
return (await deps.brokers.query({ limit: BROKER_ROWS_MAX })).records.map((row) => {
|
|
98785
|
-
const name = row.data["name"];
|
|
98786
|
-
const link = row.data["integrationId"];
|
|
98787
|
-
return {
|
|
98788
|
-
id: row.id,
|
|
98789
|
-
name: typeof name === "string" && name.length > 0 ? name : row.id,
|
|
98790
|
-
integrationId: typeof link === "string" ? link : void 0
|
|
98791
|
-
};
|
|
98792
|
-
});
|
|
98793
|
-
},
|
|
98794
|
-
readConnection: async (id) => {
|
|
98795
|
-
const row = await deps.connections.get(id);
|
|
98796
|
-
return row === null ? null : { brokerId: row.data.brokerId };
|
|
98797
|
-
},
|
|
98798
|
-
putConnection: async (id, body) => {
|
|
98799
|
-
await deps.connections.put({
|
|
98800
|
-
id,
|
|
98801
|
-
data: body
|
|
98802
|
-
}, { ifRev: 0 });
|
|
98803
|
-
},
|
|
98804
|
-
clearBrokerLink: async (brokerId) => {
|
|
98805
|
-
await deps.brokers.patch(brokerId, { integrationId: null });
|
|
98806
|
-
}
|
|
98807
|
-
};
|
|
98808
|
-
}
|
|
98809
98945
|
//#endregion
|
|
98810
98946
|
//#region src/speak/constants.ts
|
|
98811
98947
|
/** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
|
|
@@ -144130,6 +144266,7 @@ var EchoSessionRegistry = class {
|
|
|
144130
144266
|
#resolve = null;
|
|
144131
144267
|
#dnd = /* @__PURE__ */ new Map();
|
|
144132
144268
|
#volumes = /* @__PURE__ */ new Map();
|
|
144269
|
+
#groupPlayers = /* @__PURE__ */ new Map();
|
|
144133
144270
|
setResolver(fn) {
|
|
144134
144271
|
this.#resolve = fn;
|
|
144135
144272
|
}
|
|
@@ -144168,10 +144305,35 @@ var EchoSessionRegistry = class {
|
|
|
144168
144305
|
});
|
|
144169
144306
|
return pending;
|
|
144170
144307
|
}
|
|
144308
|
+
/**
|
|
144309
|
+
* The player state of a speaker group, shared by its members within one poll cycle.
|
|
144310
|
+
* A failed read is not kept for the next member.
|
|
144311
|
+
*/
|
|
144312
|
+
groupPlayer(brokerId, group, now, deviceId) {
|
|
144313
|
+
const key = `${brokerId}\u0000${group.id}`;
|
|
144314
|
+
const shared = this.#groupPlayers.get(key);
|
|
144315
|
+
if (shared !== void 0 && now - shared.at < 25e3) return shared.pending;
|
|
144316
|
+
const session = this.get(brokerId);
|
|
144317
|
+
if (session === null) return Promise.reject(/* @__PURE__ */ new Error("no Echo session for this account"));
|
|
144318
|
+
const pending = session.playerState(group.id, group.deviceType, deviceId);
|
|
144319
|
+
this.#groupPlayers.set(key, {
|
|
144320
|
+
at: now,
|
|
144321
|
+
pending
|
|
144322
|
+
});
|
|
144323
|
+
pending.catch(() => {
|
|
144324
|
+
if (this.#groupPlayers.get(key)?.pending === pending) this.#groupPlayers.delete(key);
|
|
144325
|
+
});
|
|
144326
|
+
return pending;
|
|
144327
|
+
}
|
|
144171
144328
|
/** Forget the shared volume list: a list fetched before a set must not answer a poll after it. */
|
|
144172
144329
|
invalidateVolumes(brokerId) {
|
|
144173
144330
|
this.#volumes.delete(brokerId);
|
|
144174
144331
|
}
|
|
144332
|
+
/** Forget the shared group players: a transport command or a push makes a cached group answer stale. */
|
|
144333
|
+
invalidateGroupPlayers(brokerId) {
|
|
144334
|
+
const prefix = `${brokerId}\u0000`;
|
|
144335
|
+
for (const key of [...this.#groupPlayers.keys()]) if (key.startsWith(prefix)) this.#groupPlayers.delete(key);
|
|
144336
|
+
}
|
|
144175
144337
|
/** Forget the shared Do Not Disturb list, for the same reason. */
|
|
144176
144338
|
invalidateDnd(brokerId) {
|
|
144177
144339
|
this.#dnd.delete(brokerId);
|
|
@@ -144187,6 +144349,7 @@ var EchoSessionRegistry = class {
|
|
|
144187
144349
|
}
|
|
144188
144350
|
event(brokerId, serial) {
|
|
144189
144351
|
this.invalidateVolumes(brokerId);
|
|
144352
|
+
this.invalidateGroupPlayers(brokerId);
|
|
144190
144353
|
this.invalidateDnd(brokerId);
|
|
144191
144354
|
for (const listener of [...this.#pushListeners.get(`${brokerId}\u0000${serial}`) ?? []]) listener();
|
|
144192
144355
|
}
|
|
@@ -144205,6 +144368,7 @@ var EchoSessionRegistry = class {
|
|
|
144205
144368
|
this.#resolve = null;
|
|
144206
144369
|
this.#dnd.clear();
|
|
144207
144370
|
this.#volumes.clear();
|
|
144371
|
+
this.#groupPlayers.clear();
|
|
144208
144372
|
}
|
|
144209
144373
|
};
|
|
144210
144374
|
function subscribe(map, key, listener) {
|
|
@@ -144247,6 +144411,11 @@ function mapEchoPlaybackState(value) {
|
|
|
144247
144411
|
default: return "idle";
|
|
144248
144412
|
}
|
|
144249
144413
|
}
|
|
144414
|
+
/** Whether a player answer carries any playback: an active state, a text or artwork. */
|
|
144415
|
+
function hasPlayback(info) {
|
|
144416
|
+
if (mapEchoPlaybackState(info.state) !== "idle") return true;
|
|
144417
|
+
return nonEmpty(info.infoText?.title) !== null || nonEmpty(info.mainArt?.url) !== null;
|
|
144418
|
+
}
|
|
144250
144419
|
function percent(value) {
|
|
144251
144420
|
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
144252
144421
|
return Math.round(Math.max(0, Math.min(100, value)));
|
|
@@ -144336,7 +144505,9 @@ var EchoDeviceSchema = object({
|
|
|
144336
144505
|
dnd: boolean(),
|
|
144337
144506
|
deviceType: string(),
|
|
144338
144507
|
/** The room the account files the device under, when Amazon names one. */
|
|
144339
|
-
room: string().optional()
|
|
144508
|
+
room: string().optional(),
|
|
144509
|
+
/** Serials of the speaker groups this device belongs to (Amazon's `parentClusters`); absent when none. */
|
|
144510
|
+
parentClusters: array(string()).optional()
|
|
144340
144511
|
});
|
|
144341
144512
|
var LoginStatusSchema = _enum(["ok", "failed"]);
|
|
144342
144513
|
var SessionStatusSchema = object({
|
|
@@ -144565,6 +144736,8 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
144565
144736
|
unsubscribers = [];
|
|
144566
144737
|
polling = false;
|
|
144567
144738
|
failureLogged = false;
|
|
144739
|
+
/** The name of the speaker group whose playback this Echo shows; null when it plays on its own. */
|
|
144740
|
+
viaGroup = null;
|
|
144568
144741
|
volumeFailureLogged = false;
|
|
144569
144742
|
health = "ok";
|
|
144570
144743
|
/** When this device last wrote its volume / Do Not Disturb optimistically; a read that began earlier must not overwrite it. */
|
|
@@ -144692,9 +144865,11 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
144692
144865
|
}
|
|
144693
144866
|
const deviceType = this.amazonDeviceType !== "" ? this.amazonDeviceType : listed?.deviceType ?? "";
|
|
144694
144867
|
try {
|
|
144695
|
-
const
|
|
144868
|
+
const own = await session.playerState(this.serial, deviceType, this.id);
|
|
144869
|
+
const now = Date.now();
|
|
144870
|
+
const fromPlayer = mapPlayerInfoToStatus(await this.withGroupPlayback(own, listed, session), {
|
|
144696
144871
|
online: true,
|
|
144697
|
-
now
|
|
144872
|
+
now
|
|
144698
144873
|
});
|
|
144699
144874
|
const read = await this.completeVolume(fromPlayer);
|
|
144700
144875
|
const kept = this.keepNewerVolume(read, readStartedAt);
|
|
@@ -144709,6 +144884,49 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
144709
144884
|
await this.readDoNotDisturb(readStartedAt);
|
|
144710
144885
|
}
|
|
144711
144886
|
/**
|
|
144887
|
+
* A member of a playing speaker group is answered empty by its own player read while
|
|
144888
|
+
* the group plays. When the member's answer carries no playback, serve the first
|
|
144889
|
+
* parent group that is playing or paused (its track and artwork; the volume stays the
|
|
144890
|
+
* member's own). A failed group read leaves the member's own answer.
|
|
144891
|
+
*/
|
|
144892
|
+
async withGroupPlayback(own, listed, session) {
|
|
144893
|
+
const parents = listed?.parentClusters ?? [];
|
|
144894
|
+
if (hasPlayback(own) || parents.length === 0) {
|
|
144895
|
+
this.noteViaGroup(null);
|
|
144896
|
+
return own;
|
|
144897
|
+
}
|
|
144898
|
+
for (const parentId of parents) {
|
|
144899
|
+
const group = session.devices.find((device) => device.id === parentId);
|
|
144900
|
+
if (group === void 0 || !group.online) continue;
|
|
144901
|
+
try {
|
|
144902
|
+
const groupInfo = await alexaEchoSessions.groupPlayer(this.brokerId, group, Date.now(), this.id);
|
|
144903
|
+
if (groupInfo.state === "PLAYING" || groupInfo.state === "PAUSED" || groupInfo.state === "BUFFERING") {
|
|
144904
|
+
this.noteViaGroup(group.name);
|
|
144905
|
+
return {
|
|
144906
|
+
...groupInfo,
|
|
144907
|
+
volume: own.volume
|
|
144908
|
+
};
|
|
144909
|
+
}
|
|
144910
|
+
} catch (err) {
|
|
144911
|
+
this.ctx.logger.debug("alexa echo: the group player could not be read", {
|
|
144912
|
+
tags: { deviceId: this.id },
|
|
144913
|
+
meta: {
|
|
144914
|
+
group: group.name,
|
|
144915
|
+
error: err instanceof Error ? err.message : String(err)
|
|
144916
|
+
}
|
|
144917
|
+
});
|
|
144918
|
+
}
|
|
144919
|
+
}
|
|
144920
|
+
this.noteViaGroup(null);
|
|
144921
|
+
return own;
|
|
144922
|
+
}
|
|
144923
|
+
/** Log each transition into and out of "playing via a group" once. */
|
|
144924
|
+
noteViaGroup(name) {
|
|
144925
|
+
if (name === this.viaGroup) return;
|
|
144926
|
+
this.viaGroup = name;
|
|
144927
|
+
this.ctx.logger.info(name === null ? "alexa echo: no longer playing via a group" : `alexa echo: playing via group ${name}`, { tags: { deviceId: this.id } });
|
|
144928
|
+
}
|
|
144929
|
+
/**
|
|
144712
144930
|
* An idle Echo's player read carries no volume. Ask the account's volume list
|
|
144713
144931
|
* (shared by every Echo of the account in this cycle); a failed read leaves null.
|
|
144714
144932
|
*/
|
|
@@ -144780,6 +144998,7 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
144780
144998
|
}
|
|
144781
144999
|
async command(command) {
|
|
144782
145000
|
await this.session().npCommand(this.serial, command, this.id);
|
|
145001
|
+
alexaEchoSessions.invalidateGroupPlayers(this.brokerId);
|
|
144783
145002
|
this.rereadSoon();
|
|
144784
145003
|
}
|
|
144785
145004
|
/**
|
|
@@ -145679,7 +145898,7 @@ function echoTargetStoreOver(records, options = {}) {
|
|
|
145679
145898
|
/** The `echoTargets` setting as the legacy source: a row the migration left there is still served. */
|
|
145680
145899
|
function legacyEchoTargetsOver(setting) {
|
|
145681
145900
|
return {
|
|
145682
|
-
list: async () => readEchoTargets({ echoTargets: setting.readRows() }).map((row) => ({
|
|
145901
|
+
list: async () => readEchoTargets({ echoTargets: await setting.readRows() }).map((row) => ({
|
|
145683
145902
|
id: row.id,
|
|
145684
145903
|
name: row.name,
|
|
145685
145904
|
kind: alexaTargetKind().kind,
|
|
@@ -145691,11 +145910,12 @@ function legacyEchoTargetsOver(setting) {
|
|
|
145691
145910
|
}
|
|
145692
145911
|
})),
|
|
145693
145912
|
delete: async (id) => {
|
|
145694
|
-
const
|
|
145913
|
+
const rows = await setting.readRows();
|
|
145914
|
+
const rest = rows.filter((row) => {
|
|
145695
145915
|
const parsed = alexaEchoTargetSchema.safeParse(row);
|
|
145696
145916
|
return !(parsed.success && parsed.data.id === id);
|
|
145697
145917
|
});
|
|
145698
|
-
if (rest.length !==
|
|
145918
|
+
if (rest.length !== rows.length) await setting.writeRows(rest);
|
|
145699
145919
|
}
|
|
145700
145920
|
};
|
|
145701
145921
|
}
|
|
@@ -145712,8 +145932,9 @@ function echoRowOf(target) {
|
|
|
145712
145932
|
}
|
|
145713
145933
|
/** The migration's ports over the `notify-alexa-echo` record kind. */
|
|
145714
145934
|
function echoRecordPortsOver(records, logger) {
|
|
145715
|
-
const
|
|
145716
|
-
if (
|
|
145935
|
+
const [kind] = Object.values(alexaNotifyRecordKinds);
|
|
145936
|
+
if (kind === void 0) throw new Error("import-alexa: the Echo target record kind is not declared");
|
|
145937
|
+
const handle = records.kind(kind);
|
|
145717
145938
|
return {
|
|
145718
145939
|
readEchoRecord: async (id) => (await handle.getResolved(id))?.data ?? null,
|
|
145719
145940
|
writeEchoRecord: async (id, body) => {
|
|
@@ -146798,17 +147019,42 @@ async function flattenBrokerRecords(ports) {
|
|
|
146798
147019
|
//#endregion
|
|
146799
147020
|
//#region src/migrations/broker-links-to-connections.ts
|
|
146800
147021
|
var BROKER_LINKS_TO_CONNECTIONS_ID = "import-alexa/2026-11-05-broker-links-to-connections";
|
|
147022
|
+
var BROKER_ROWS_MAX = 1e3;
|
|
146801
147023
|
/** The broker records and the integration (`connections`) records of this addon. */
|
|
146802
147024
|
function alexaBrokerLinkPorts(records, warn) {
|
|
146803
|
-
|
|
146804
|
-
|
|
146805
|
-
|
|
146806
|
-
|
|
146807
|
-
schema: record(string(), unknown())
|
|
146808
|
-
}),
|
|
146809
|
-
connections: records.kind(alexaConnectionsEntryKind),
|
|
146810
|
-
warn
|
|
147025
|
+
const brokers = records.kind({
|
|
147026
|
+
name: alexaBrokersEntryKind.name,
|
|
147027
|
+
declaration: alexaBrokersEntryKind.declaration,
|
|
147028
|
+
schema: record(string(), unknown())
|
|
146811
147029
|
});
|
|
147030
|
+
const connections = records.kind(alexaConnectionsEntryKind);
|
|
147031
|
+
return {
|
|
147032
|
+
warn,
|
|
147033
|
+
listBrokers: async () => {
|
|
147034
|
+
return (await brokers.query({ limit: BROKER_ROWS_MAX })).records.map((row) => {
|
|
147035
|
+
const name = row.data["name"];
|
|
147036
|
+
const link = row.data["integrationId"];
|
|
147037
|
+
return {
|
|
147038
|
+
id: row.id,
|
|
147039
|
+
name: typeof name === "string" && name.length > 0 ? name : row.id,
|
|
147040
|
+
integrationId: typeof link === "string" ? link : void 0
|
|
147041
|
+
};
|
|
147042
|
+
});
|
|
147043
|
+
},
|
|
147044
|
+
readConnection: async (id) => {
|
|
147045
|
+
const row = await connections.get(id);
|
|
147046
|
+
return row === null ? null : { brokerId: row.data.brokerId };
|
|
147047
|
+
},
|
|
147048
|
+
putConnection: async (id, body) => {
|
|
147049
|
+
await connections.put({
|
|
147050
|
+
id,
|
|
147051
|
+
data: body
|
|
147052
|
+
}, { ifRev: 0 });
|
|
147053
|
+
},
|
|
147054
|
+
clearBrokerLink: async (brokerId) => {
|
|
147055
|
+
await brokers.patch(brokerId, { integrationId: null });
|
|
147056
|
+
}
|
|
147057
|
+
};
|
|
146812
147058
|
}
|
|
146813
147059
|
//#endregion
|
|
146814
147060
|
//#region src/migrations/strip-echo-target-volume-and-mode.ts
|
|
@@ -146824,7 +147070,7 @@ function strippedRow(row) {
|
|
|
146824
147070
|
return Object.fromEntries(Object.entries(row).filter(([key]) => !RETIRED_KEYS.includes(key)));
|
|
146825
147071
|
}
|
|
146826
147072
|
async function stripEchoTargetVolumeAndMode(ports) {
|
|
146827
|
-
const rows = ports.readRows();
|
|
147073
|
+
const rows = await ports.readRows();
|
|
146828
147074
|
const stripped = rows.filter(carriesRetiredKey).length;
|
|
146829
147075
|
if (stripped === 0) return {
|
|
146830
147076
|
outcome: "already-done",
|
|
@@ -146857,7 +147103,7 @@ async function stripEchoTargetVolumeAndMode(ports) {
|
|
|
146857
147103
|
*/
|
|
146858
147104
|
var ECHO_TARGETS_TO_RECORDS_ID = "import-alexa/2026-11-06-echo-targets-to-records";
|
|
146859
147105
|
async function moveEchoTargetsToRecords(ports) {
|
|
146860
|
-
const rows = ports.readRows();
|
|
147106
|
+
const rows = await ports.readRows();
|
|
146861
147107
|
if (rows.length === 0) return {
|
|
146862
147108
|
outcome: "already-done",
|
|
146863
147109
|
summary: { converted: 0 }
|
|
@@ -147008,6 +147254,118 @@ var BROKER_RECORD_MIGRATION_IDS = [
|
|
|
147008
147254
|
BROKER_LINKS_TO_CONNECTIONS_ID
|
|
147009
147255
|
];
|
|
147010
147256
|
//#endregion
|
|
147257
|
+
//#region src/migrations/records-door.ts
|
|
147258
|
+
function positionalHandleOver(handle) {
|
|
147259
|
+
return {
|
|
147260
|
+
get: (id) => handle.get({ id }),
|
|
147261
|
+
getResolved: (id) => handle.getResolved({ id }),
|
|
147262
|
+
put: (input, opts) => handle.put({
|
|
147263
|
+
...input,
|
|
147264
|
+
...opts?.ifRev === void 0 ? {} : { ifRev: opts.ifRev }
|
|
147265
|
+
}),
|
|
147266
|
+
patch: (id, partial) => handle.patch({
|
|
147267
|
+
id,
|
|
147268
|
+
partial
|
|
147269
|
+
}),
|
|
147270
|
+
delete: (id) => handle.delete({ id }),
|
|
147271
|
+
query: ({ limit }) => handle.query({ limit })
|
|
147272
|
+
};
|
|
147273
|
+
}
|
|
147274
|
+
/** The records door of the migration API, in the shape the migrations call. */
|
|
147275
|
+
function positionalRecordsOver(api) {
|
|
147276
|
+
return { kind: (definition) => positionalHandleOver(api.records.kind(definition)) };
|
|
147277
|
+
}
|
|
147278
|
+
//#endregion
|
|
147279
|
+
//#region src/migrations/ports.ts
|
|
147280
|
+
var NESTED_MAX = 1e3;
|
|
147281
|
+
/** The registry list and the migration-only rows door, over the migration API. */
|
|
147282
|
+
function integrationRowsApiOver(api) {
|
|
147283
|
+
return {
|
|
147284
|
+
list: async () => (await api.integrations.list({})).integrations,
|
|
147285
|
+
readLegacyIntegrationRows: (input) => api.legacyRows.read(input),
|
|
147286
|
+
clearLegacyIntegrationRows: (input) => api.legacyRows.clear(input)
|
|
147287
|
+
};
|
|
147288
|
+
}
|
|
147289
|
+
/** The keychain handle of the migration API: resolve and delete, owner-scoped by the transport. */
|
|
147290
|
+
function secretsOver(api) {
|
|
147291
|
+
return {
|
|
147292
|
+
resolve: async (refs) => {
|
|
147293
|
+
const { values } = await api.secrets.resolve({ refs: [...refs] });
|
|
147294
|
+
const out = {};
|
|
147295
|
+
for (const ref of refs) {
|
|
147296
|
+
const value = values[ref];
|
|
147297
|
+
out[ref] = value === null || value === void 0 ? { kind: "missing" } : {
|
|
147298
|
+
kind: "value",
|
|
147299
|
+
value,
|
|
147300
|
+
version: 0
|
|
147301
|
+
};
|
|
147302
|
+
}
|
|
147303
|
+
return out;
|
|
147304
|
+
},
|
|
147305
|
+
delete: async (refs) => (await api.secrets.delete({ refs: [...refs] })).deleted
|
|
147306
|
+
};
|
|
147307
|
+
}
|
|
147308
|
+
/** The Echo targets kept in the addon's `echoTargets` setting, read and written as stored. */
|
|
147309
|
+
function echoSettingPortsOver(api) {
|
|
147310
|
+
return {
|
|
147311
|
+
readRows: async () => {
|
|
147312
|
+
const { store } = await api.settings.read({});
|
|
147313
|
+
const rows = store["echoTargets"];
|
|
147314
|
+
return Array.isArray(rows) ? rows : [];
|
|
147315
|
+
},
|
|
147316
|
+
writeRows: async (rows) => {
|
|
147317
|
+
await api.settings.write({ patch: { echoTargets: [...rows] } });
|
|
147318
|
+
}
|
|
147319
|
+
};
|
|
147320
|
+
}
|
|
147321
|
+
/** The broker rows' flatten ports over the records door. */
|
|
147322
|
+
function flattenPortsOverDoor(records) {
|
|
147323
|
+
const handle = records.kind({
|
|
147324
|
+
name: alexaBrokersEntryKind.name,
|
|
147325
|
+
declaration: alexaBrokersEntryKind.declaration,
|
|
147326
|
+
schema: record(string(), unknown())
|
|
147327
|
+
});
|
|
147328
|
+
return {
|
|
147329
|
+
listNested: async (legacyKey) => (await handle.query({ limit: NESTED_MAX })).records.filter((row) => legacyKey in row.data).map((row) => row.id),
|
|
147330
|
+
readResolved: async (id) => {
|
|
147331
|
+
const row = await handle.getResolved(id);
|
|
147332
|
+
if (row === null) throw new Error("the record is gone");
|
|
147333
|
+
return row.data;
|
|
147334
|
+
},
|
|
147335
|
+
patch: async (id, patch) => {
|
|
147336
|
+
await handle.patch(id, patch);
|
|
147337
|
+
}
|
|
147338
|
+
};
|
|
147339
|
+
}
|
|
147340
|
+
/**
|
|
147341
|
+
* The catalogue's ports over {@link MigrationApiV1}, and nothing else (planning#447): the addon's
|
|
147342
|
+
* own settings store (the Echo rows), its keychain handle, the legacy integration rows door and its
|
|
147343
|
+
* broker, connection and Echo target records.
|
|
147344
|
+
*/
|
|
147345
|
+
function alexaPortsOver(api) {
|
|
147346
|
+
const records = positionalRecordsOver(api);
|
|
147347
|
+
const warn = (message) => {
|
|
147348
|
+
api.log.warn({ message });
|
|
147349
|
+
};
|
|
147350
|
+
return {
|
|
147351
|
+
...echoSettingPortsOver(api),
|
|
147352
|
+
...flattenPortsOverDoor(records),
|
|
147353
|
+
...alexaBrokerLinkPorts(records, warn),
|
|
147354
|
+
...echoRecordPortsOver(records, { warn: (message, extras) => {
|
|
147355
|
+
api.log.warn({
|
|
147356
|
+
message,
|
|
147357
|
+
...extras?.meta === void 0 ? {} : { meta: extras.meta }
|
|
147358
|
+
});
|
|
147359
|
+
} }),
|
|
147360
|
+
...alexaIntegrationRowsPorts({
|
|
147361
|
+
api: integrationRowsApiOver(api),
|
|
147362
|
+
records,
|
|
147363
|
+
secrets: secretsOver(api),
|
|
147364
|
+
warn
|
|
147365
|
+
})
|
|
147366
|
+
};
|
|
147367
|
+
}
|
|
147368
|
+
//#endregion
|
|
147011
147369
|
//#region src/speak/icon-routes.ts
|
|
147012
147370
|
var ALEXA_ICON_SVG = "<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#00CAFF\"><title>Amazon Alexa</title><path d=\"M12 0C5.37 0 0 5.37 0 12C0 18.09 4.53 23.11 10.4 23.9V21.5A1.59 1.59 0 0 0 9.32 19.97A8.41 8.41 0 0 1 3.6 11.8A8.37 8.37 0 0 1 12.09 3.6A8.4 8.4 0 0 1 20.4 12.31L20.39 12.38A8.68 8.68 0 0 1 20.36 12.76C20.36 12.83 20.35 12.9 20.34 12.96C20.34 13.04 20.33 13.12 20.32 13.19L20.3 13.29C19.27 20.07 10.45 23.87 10.4 23.9C10.92 23.97 11.46 24 12 24C18.63 24 24 18.63 24 12S18.63 0 12 0Z\"/></svg>";
|
|
147013
147371
|
/** One day; the mark is immutable per addon version. */
|
|
@@ -147127,7 +147485,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
147127
147485
|
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
147128
147486
|
onChanged: (id) => this.syncBrokerRuntime(id)
|
|
147129
147487
|
});
|
|
147130
|
-
await this.runMigrations(
|
|
147488
|
+
await this.runMigrations(ADDON_MIGRATIONS, (api) => alexaPortsOver(api), { only: BROKER_RECORD_MIGRATION_IDS });
|
|
147131
147489
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
147132
147490
|
await this.adoptSessionFiles();
|
|
147133
147491
|
this.registry = new AlexaBrokerRegistry(this.ctx.logger, {
|
|
@@ -147168,7 +147526,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
147168
147526
|
deviceCode: this.deviceCodeLogin,
|
|
147169
147527
|
commitCookie: (input) => this.commitCapturedCookie(input)
|
|
147170
147528
|
}), ...speakIconRoutes()];
|
|
147171
|
-
await this.runMigrations(
|
|
147529
|
+
await this.runMigrations(ADDON_MIGRATIONS, (api) => alexaPortsOver(api), { only: ADDON_MIGRATIONS.map((m) => m.meta.id).filter((id) => !BROKER_RECORD_MIGRATION_IDS.includes(id)) });
|
|
147172
147530
|
const echoStore = echoTargetStoreOver(records, {
|
|
147173
147531
|
logger: this.ctx.logger,
|
|
147174
147532
|
legacy: legacyEchoTargetsOver(this.echoSettingPorts())
|
|
@@ -147219,29 +147577,15 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
147219
147577
|
}
|
|
147220
147578
|
] };
|
|
147221
147579
|
}
|
|
147222
|
-
/**
|
|
147580
|
+
/**
|
|
147581
|
+
* The `echoTargets` setting the legacy Echo store still serves: the addon's own durable state
|
|
147582
|
+
* under the key it was always stored at, so a row an older release left there is still read.
|
|
147583
|
+
*/
|
|
147223
147584
|
echoSettingPorts() {
|
|
147585
|
+
const rows = this.state("echoTargets", array(unknown()), []);
|
|
147224
147586
|
return {
|
|
147225
|
-
readRows: () =>
|
|
147226
|
-
writeRows:
|
|
147227
|
-
await this.updateGlobalSettings({ echoTargets: [...readEchoTargets({ echoTargets: rows })] });
|
|
147228
|
-
}
|
|
147229
|
-
};
|
|
147230
|
-
}
|
|
147231
|
-
migrationPorts(records) {
|
|
147232
|
-
const secrets = this.ctx.secrets;
|
|
147233
|
-
if (secrets === void 0) throw new Error("import-alexa: ctx.secrets is required");
|
|
147234
|
-
return {
|
|
147235
|
-
...this.echoSettingPorts(),
|
|
147236
|
-
...flattenPortsOver(records.kind(alexaBrokersEntryKind)),
|
|
147237
|
-
...alexaBrokerLinkPorts(records, (message) => this.ctx.logger.warn(message)),
|
|
147238
|
-
...echoRecordPortsOver(records, this.ctx.logger),
|
|
147239
|
-
...alexaIntegrationRowsPorts({
|
|
147240
|
-
api: defaultIntegrationApiOver(this.ctx.api),
|
|
147241
|
-
records,
|
|
147242
|
-
secrets,
|
|
147243
|
-
warn: (message) => this.ctx.logger.warn(message)
|
|
147244
|
-
})
|
|
147587
|
+
readRows: () => rows.get(),
|
|
147588
|
+
writeRows: (next) => rows.set([...readEchoTargets({ echoTargets: next })])
|
|
147245
147589
|
};
|
|
147246
147590
|
}
|
|
147247
147591
|
/** One provider over the two record kinds: the account (broker) and the integrations using it. */
|