@camstack/addon-advanced-notifier 0.1.31 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -4781,6 +4781,16 @@ function record(keyType, valueType, params) {
4781
4781
  ...normalizeParams(params)
4782
4782
  });
4783
4783
  }
4784
+ function partialRecord(keyType, valueType, params) {
4785
+ const k = clone(keyType);
4786
+ k._zod.values = void 0;
4787
+ return new ZodRecord({
4788
+ type: "record",
4789
+ keyType: k,
4790
+ valueType,
4791
+ ...normalizeParams(params)
4792
+ });
4793
+ }
4784
4794
  const ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
4785
4795
  $ZodEnum.init(inst, def);
4786
4796
  ZodType.init(inst, def);
@@ -5057,6 +5067,37 @@ function _instanceof(cls, params = {}) {
5057
5067
  };
5058
5068
  return inst;
5059
5069
  }
5070
+ const wiringProbeKindSchema = _enum(["singleton", "device", "widget"]);
5071
+ const wiringProbeResultSchema = object({
5072
+ capName: string(),
5073
+ kind: wiringProbeKindSchema,
5074
+ deviceId: number().optional(),
5075
+ reachable: boolean(),
5076
+ latencyMs: number(),
5077
+ error: string().optional()
5078
+ });
5079
+ const wiringAddonHealthSchema = object({
5080
+ addonId: string(),
5081
+ caps: array(wiringProbeResultSchema).readonly(),
5082
+ widgets: array(wiringProbeResultSchema).readonly()
5083
+ });
5084
+ const wiringNodeHealthSchema = object({
5085
+ nodeId: string(),
5086
+ addons: array(wiringAddonHealthSchema).readonly()
5087
+ });
5088
+ object({
5089
+ /** True only when every probed target is reachable. */
5090
+ ok: boolean(),
5091
+ /** True when at least one target is unreachable. */
5092
+ degraded: boolean(),
5093
+ checkedAt: string(),
5094
+ nodes: array(wiringNodeHealthSchema).readonly(),
5095
+ summary: object({
5096
+ total: number(),
5097
+ reachable: number(),
5098
+ unreachable: number()
5099
+ })
5100
+ });
5060
5101
  const MODEL_FORMATS = ["onnx", "coreml", "openvino", "tflite", "pt"];
5061
5102
  const WELL_KNOWN_TABS = [
5062
5103
  { id: "overview", label: "Overview", icon: "layout-dashboard", order: -10 },
@@ -6576,7 +6617,7 @@ const SpatialDetectionSchema = object({
6576
6617
  bbox: BoundingBoxSchema
6577
6618
  });
6578
6619
  const AudioChunkInputSchema = object({
6579
- data: _instanceof(Float32Array),
6620
+ data: _instanceof(Uint8Array),
6580
6621
  sampleRate: number(),
6581
6622
  channels: number(),
6582
6623
  timestamp: number(),
@@ -7247,7 +7288,23 @@ const RunnerCameraConfigSchema = object({
7247
7288
  * whenever its `zones` device-state slice changes, so the runner's
7248
7289
  * copy stays in sync. Empty array → no zone filtering.
7249
7290
  */
7250
- zones: array(ZoneSchema).readonly().default([])
7291
+ zones: array(ZoneSchema).readonly().default([]),
7292
+ /**
7293
+ * When true (default) and the camera's `motionSources` contains only
7294
+ * `'onboard'`, the runner dynamically opens the same WASM frame-diff
7295
+ * motion-frames subscription on each `MotionOnMotionChanged
7296
+ * source:'onboard'` event and tears it down after `motionCooldownMs`.
7297
+ * This causes `runMotionAnalysis` to emit `MotionZonesRaw` /
7298
+ * `MotionAnalysis` during active-motion windows without the
7299
+ * substream being held open continuously.
7300
+ *
7301
+ * Set to `false` to disable the dynamic analyzer for this camera
7302
+ * (e.g. very low-bandwidth links where the extra substream is
7303
+ * undesirable). Has no effect when `motionSources` already includes
7304
+ * `'analyzer'` — the analyzer runs continuously in that case and
7305
+ * this gate is bypassed.
7306
+ */
7307
+ onboardMotionDrivesAnalyzer: boolean().default(true)
7251
7308
  });
7252
7309
  const RunnerLocalLoadSchema = object({
7253
7310
  /** Moleculer node id of this runner instance. */
@@ -7384,22 +7441,68 @@ MotionTriggerStatusSchema.extend({
7384
7441
  }) }
7385
7442
  }
7386
7443
  });
7444
+ const MaskPointSchema = object({
7445
+ x: number(),
7446
+ y: number()
7447
+ });
7448
+ const MaskRectShapeSchema = object({
7449
+ kind: literal("rect"),
7450
+ x: number(),
7451
+ y: number(),
7452
+ width: number(),
7453
+ height: number()
7454
+ });
7455
+ const MaskPolygonShapeSchema = object({
7456
+ kind: literal("polygon"),
7457
+ points: array(MaskPointSchema)
7458
+ });
7459
+ const MaskGridShapeSchema = object({
7460
+ kind: literal("grid"),
7461
+ gridWidth: number(),
7462
+ gridHeight: number(),
7463
+ cells: array(boolean())
7464
+ });
7465
+ const MaskLineShapeSchema = object({
7466
+ kind: literal("line"),
7467
+ points: array(MaskPointSchema)
7468
+ });
7469
+ discriminatedUnion("kind", [
7470
+ MaskRectShapeSchema,
7471
+ MaskPolygonShapeSchema,
7472
+ MaskGridShapeSchema,
7473
+ MaskLineShapeSchema
7474
+ ]);
7475
+ const MaskShapeKindSchema = _enum(["rect", "polygon", "grid", "line"]);
7476
+ const MaskPolygonVerticesSchema = object({
7477
+ min: number(),
7478
+ max: number()
7479
+ });
7480
+ const MaskGridDimsSchema = object({
7481
+ width: number(),
7482
+ height: number()
7483
+ });
7484
+ const MotionZoneRegionSchema = object({
7485
+ id: number(),
7486
+ enabled: boolean(),
7487
+ shape: MaskGridShapeSchema
7488
+ });
7387
7489
  object({
7388
7490
  enabled: boolean(),
7389
7491
  sensitivity: number(),
7390
- /** Row-major active-cell grid. Length = gridWidth*gridHeight (see getOptions). */
7391
- cells: array(boolean()),
7492
+ /** Grid region(s). Today exactly one `grid` shape. */
7493
+ regions: array(MotionZoneRegionSchema),
7392
7494
  lastFetchedAt: number()
7393
7495
  });
7394
7496
  const MotionZoneOptionsSchema = object({
7395
- gridWidth: number(),
7396
- gridHeight: number(),
7497
+ maxRegions: number(),
7498
+ supportedShapes: array(MaskShapeKindSchema),
7499
+ grid: MaskGridDimsSchema,
7397
7500
  sensitivity: object({ min: number(), max: number(), step: number() })
7398
7501
  });
7399
7502
  const MotionZonePatchSchema = object({
7400
7503
  enabled: boolean().optional(),
7401
7504
  sensitivity: number().optional(),
7402
- cells: array(boolean()).optional()
7505
+ regions: array(MotionZoneRegionSchema).optional()
7403
7506
  });
7404
7507
  ({
7405
7508
  deviceTypes: [DeviceType.Camera],
@@ -7412,6 +7515,102 @@ const MotionZonePatchSchema = object({
7412
7515
  )
7413
7516
  }
7414
7517
  });
7518
+ const NativeObjectClassEnum = _enum([
7519
+ "person",
7520
+ "vehicle",
7521
+ "animal",
7522
+ "face",
7523
+ "package",
7524
+ "other"
7525
+ ]);
7526
+ const NativeDetectionSchema = object({
7527
+ class: NativeObjectClassEnum,
7528
+ timestamp: number(),
7529
+ /** Firmware-provided confidence [0..1]. Reolink pushes don't carry it → undefined. */
7530
+ confidence: number().min(0).max(1).optional()
7531
+ });
7532
+ const NativeObjectDetectionStatusSchema = object({
7533
+ /**
7534
+ * Last observed instance per class. Missing entries mean the class
7535
+ * is supported but nothing has been seen since the provider started.
7536
+ *
7537
+ * MUST be a partial record: providers seed an empty `{}` on cold-start
7538
+ * and write one class at a time as detections arrive. In Zod 4
7539
+ * `z.record(enum, …)` is EXHAUSTIVE (requires every enum key), so a
7540
+ * partial write throws "expected object, received undefined" for every
7541
+ * unseen class. `z.partialRecord` keeps the enum-key narrowing while
7542
+ * allowing the sparse shape the providers actually write.
7543
+ */
7544
+ lastByClass: partialRecord(NativeObjectClassEnum, NativeDetectionSchema.nullable()),
7545
+ /** Classes the firmware is capable of detecting — enumerated at device register. */
7546
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
7547
+ /**
7548
+ * Whether forwarding of onboard AI detections is enabled for this device.
7549
+ * Default true (on cold-start) — detections flow unconditionally before
7550
+ * the toggle is saved, so defaulting true preserves existing behaviour.
7551
+ */
7552
+ enabled: boolean()
7553
+ });
7554
+ NativeObjectDetectionStatusSchema.extend({
7555
+ /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
7556
+ lastFetchedAt: number()
7557
+ });
7558
+ ({
7559
+ deviceTypes: [DeviceType.Camera],
7560
+ methods: {
7561
+ setEnabled: method(
7562
+ object({ deviceId: number(), enabled: boolean() }),
7563
+ _void(),
7564
+ { kind: "mutation", auth: "admin" }
7565
+ )
7566
+ },
7567
+ events: {
7568
+ onDetected: { data: object({
7569
+ deviceId: number(),
7570
+ detection: NativeDetectionSchema
7571
+ }) }
7572
+ }
7573
+ });
7574
+ const PrivacyMaskShapeSchema = discriminatedUnion("kind", [
7575
+ MaskRectShapeSchema,
7576
+ MaskPolygonShapeSchema
7577
+ ]);
7578
+ const PrivacyMaskRegionSchema = object({
7579
+ /** Slot id, 0-based. Stable across read/write. */
7580
+ id: number(),
7581
+ /** Whether this zone is active (blanked out by the camera). */
7582
+ enabled: boolean(),
7583
+ shape: PrivacyMaskShapeSchema
7584
+ });
7585
+ object({
7586
+ enabled: boolean(),
7587
+ /** Active zones (normalized 0..1). Length ≤ maxRegions. */
7588
+ regions: array(PrivacyMaskRegionSchema),
7589
+ lastFetchedAt: number()
7590
+ });
7591
+ const PrivacyMaskOptionsSchema = object({
7592
+ /** Maximum number of supported zones. */
7593
+ maxRegions: number(),
7594
+ /** Shape kinds this camera accepts — Reolink: ['rect']; Hikvision: ['rect','polygon']. */
7595
+ supportedShapes: array(MaskShapeKindSchema),
7596
+ /** Polygon vertex bounds when 'polygon' is supported (Hikvision: {min:4,max:4}). */
7597
+ polygonVertices: MaskPolygonVerticesSchema.optional()
7598
+ });
7599
+ const PrivacyMaskPatchSchema = object({
7600
+ enabled: boolean().optional(),
7601
+ regions: array(PrivacyMaskRegionSchema).optional()
7602
+ });
7603
+ ({
7604
+ deviceTypes: [DeviceType.Camera],
7605
+ methods: {
7606
+ getOptions: method(object({ deviceId: number() }), PrivacyMaskOptionsSchema),
7607
+ setMask: method(
7608
+ object({ deviceId: number(), patch: PrivacyMaskPatchSchema }),
7609
+ _void(),
7610
+ { kind: "mutation", auth: "admin" }
7611
+ )
7612
+ }
7613
+ });
7415
7614
  const AutotrackTargetTypeSchema = string().describe("Vendor target string (people/vehicle/pet); empty = camera default");
7416
7615
  const PtzAutotrackSettingsSchema = object({
7417
7616
  targetType: AutotrackTargetTypeSchema,
@@ -7930,7 +8129,8 @@ const SettingsUpdateResultSchema = object({
7930
8129
  object({
7931
8130
  addonId: string(),
7932
8131
  nodeId: string().optional(),
7933
- overlay: record(string(), unknown()).optional()
8132
+ overlay: record(string(), unknown()).optional(),
8133
+ cap: string().optional()
7934
8134
  }),
7935
8135
  SettingsSchemaWithValuesSchema.nullable()
7936
8136
  ),
@@ -8661,7 +8861,14 @@ const OauthIntegrationDescriptorSchema = object({
8661
8861
  /** Allowed redirect_uri prefixes. /api/oauth2/authorize rejects any
8662
8862
  * redirect_uri that does not start with one of these. Required —
8663
8863
  * an empty list means the integration can never complete linking. */
8664
- allowedRedirectPrefixes: array(string()).min(1)
8864
+ allowedRedirectPrefixes: array(string()).min(1),
8865
+ /** Optional public origin (no trailing slash) that this integration's
8866
+ * issued codes/tokens should carry as the `hubUrl` claim — typically the
8867
+ * operator-selected external-access endpoint resolved by the addon. When
8868
+ * present, /api/oauth2/authorize bakes THIS into the code instead of the
8869
+ * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
8870
+ * the hub's env) drives the claim that its cloud Lambda routes back on. */
8871
+ hubUrl: string().optional()
8665
8872
  });
8666
8873
  ({
8667
8874
  methods: {
@@ -9275,7 +9482,20 @@ const WebrtcStreamChoiceSchema = object({
9275
9482
  object({
9276
9483
  deviceId: number().int().nonnegative(),
9277
9484
  target: WebrtcStreamTargetSchema,
9278
- hints: webrtcClientHintsSchema.optional()
9485
+ hints: webrtcClientHintsSchema.optional(),
9486
+ /**
9487
+ * SERVER-INJECTED — NOT a client hint. The hub layer that holds
9488
+ * the tRPC request context (and therefore the client IP) sets
9489
+ * this to `true` when the viewer's source IP is non-LAN
9490
+ * (4G/CGNAT/internet). The broker then forces TURN-relay-only
9491
+ * ICE for the session so a CGNAT client (which can only offer a
9492
+ * relay candidate) gets a clean relay↔relay media path instead
9493
+ * of werift nominating a dead host/hairpin-srflx pair. LAN
9494
+ * clients leave this absent/false and keep the low-latency
9495
+ * direct (host/srflx) path. Clients MUST NOT send this — the
9496
+ * server overwrites it from the request context.
9497
+ */
9498
+ relayOnly: boolean().optional()
9279
9499
  }),
9280
9500
  object({ sessionId: string(), sdpOffer: string() }),
9281
9501
  { kind: "mutation" }
@@ -9298,7 +9518,22 @@ const WebrtcStreamChoiceSchema = object({
9298
9518
  deviceId: number().int().nonnegative(),
9299
9519
  target: WebrtcStreamTargetSchema.optional(),
9300
9520
  sdpOffer: string(),
9301
- sessionId: string().optional()
9521
+ sessionId: string().optional(),
9522
+ /**
9523
+ * Force TURN-relay-only ICE for this session. Two kinds of caller
9524
+ * set it:
9525
+ * - A cloud peer like Alexa's RTCSessionController (reachable
9526
+ * only via TURN, never our host/srflx behind NAT) passes
9527
+ * `true` from its own trusted addon context.
9528
+ * - The hub injects it for browser client-offer viewers from the
9529
+ * request's source IP (non-LAN ⇒ true), exactly as it does for
9530
+ * `createSession`.
9531
+ * A LAN/Tailscale browser doing client-offer passthrough leaves it
9532
+ * absent/false so a direct host pair carries full native quality.
9533
+ * Untrusted browser clients MUST NOT send it — the hub overwrites
9534
+ * it from the request context.
9535
+ */
9536
+ relayOnly: boolean().optional()
9302
9537
  }),
9303
9538
  object({ sessionId: string(), sdpAnswer: string() }),
9304
9539
  { kind: "mutation" }
@@ -9312,6 +9547,46 @@ const WebrtcStreamChoiceSchema = object({
9312
9547
  _void(),
9313
9548
  { kind: "mutation" }
9314
9549
  ),
9550
+ /**
9551
+ * Trickle ICE — add a remote (client) ICE candidate to a live session.
9552
+ * Lets the client send its SDP offer/answer IMMEDIATELY (before ICE
9553
+ * gathering finishes) and deliver candidates as they arrive, so the
9554
+ * connection establishes in ~0s instead of waiting for full gathering.
9555
+ * The dual of `getIceCandidates`. Mirrors Scrypted's signaling.
9556
+ */
9557
+ addIceCandidate: method(
9558
+ object({
9559
+ deviceId: number().int().nonnegative(),
9560
+ sessionId: string(),
9561
+ candidate: string(),
9562
+ sdpMid: string().nullable().optional(),
9563
+ sdpMLineIndex: number().int().nullable().optional()
9564
+ }),
9565
+ _void(),
9566
+ { kind: "mutation" }
9567
+ ),
9568
+ /**
9569
+ * Trickle ICE — poll the server's gathered ICE candidates for a session.
9570
+ * The server answers immediately (no gathering wait) and the client polls
9571
+ * this to receive host/srflx/relay candidates as werift gathers them,
9572
+ * adding each to its PeerConnection. Returns all candidates gathered so
9573
+ * far; the client dedupes. `done` flips true once gathering completes.
9574
+ */
9575
+ getIceCandidates: method(
9576
+ object({
9577
+ deviceId: number().int().nonnegative(),
9578
+ sessionId: string()
9579
+ }),
9580
+ object({
9581
+ candidates: array(object({
9582
+ candidate: string(),
9583
+ sdpMid: string().nullable(),
9584
+ sdpMLineIndex: number().int().nullable()
9585
+ })),
9586
+ done: boolean()
9587
+ }),
9588
+ { kind: "query" }
9589
+ ),
9315
9590
  closeSession: method(
9316
9591
  object({
9317
9592
  deviceId: number().int().nonnegative(),
@@ -10990,6 +11265,9 @@ const UpdateConfigInput = object({
10990
11265
  ({
10991
11266
  deviceTypes: [DeviceType.Camera]
10992
11267
  });
11268
+ ({
11269
+ deviceTypes: [DeviceType.Camera]
11270
+ });
10993
11271
  const TrackStateSchema = _enum(["new", "entered", "left", "moving", "idle"]);
10994
11272
  const EventKindSchema = _enum(["motion", "object", "audio"]);
10995
11273
  const TrackPositionSchema = object({
@@ -11817,36 +12095,28 @@ const IntercomStatusSchema = object({
11817
12095
  }) }
11818
12096
  }
11819
12097
  });
11820
- const NativeObjectClassEnum = _enum([
11821
- "person",
11822
- "vehicle",
11823
- "animal",
11824
- "face",
11825
- "package",
11826
- "other"
11827
- ]);
11828
- const NativeDetectionSchema = object({
11829
- class: NativeObjectClassEnum,
11830
- timestamp: number(),
11831
- /** Firmware-provided confidence [0..1]. Reolink pushes don't carry it → undefined. */
11832
- confidence: number().min(0).max(1).optional()
11833
- });
11834
- object({
11835
- /**
11836
- * Last observed instance per class. Undefined entries mean the class
11837
- * is supported but nothing has been seen since the provider started.
11838
- */
11839
- lastByClass: record(NativeObjectClassEnum, NativeDetectionSchema.nullable()),
11840
- /** Classes the firmware is capable of detecting — enumerated at device register. */
11841
- supportedClasses: array(NativeObjectClassEnum).readonly()
12098
+ const CamStreamDescriptorSchema = object({
12099
+ camStreamId: string().min(1),
12100
+ kind: CamStreamKindSchema,
12101
+ url: string().optional(),
12102
+ codec: string().optional(),
12103
+ resolution: CamStreamResolutionSchema.optional(),
12104
+ fps: number().positive().optional(),
12105
+ label: string().optional(),
12106
+ /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
12107
+ deviceFeatures: array(string()).optional(),
12108
+ /** Eligible for automatic profile assignment. Absent = `true`. */
12109
+ autoEligible: boolean().optional(),
12110
+ /** Transport-specific opaque metadata (e.g. rfc4571 SDP). */
12111
+ metadata: record(string(), unknown()).optional()
11842
12112
  });
11843
12113
  ({
11844
12114
  deviceTypes: [DeviceType.Camera],
11845
- events: {
11846
- onDetected: { data: object({
11847
- deviceId: number(),
11848
- detection: NativeDetectionSchema
11849
- }) }
12115
+ methods: {
12116
+ getCatalog: method(
12117
+ object({ deviceId: number().int().nonnegative() }),
12118
+ array(CamStreamDescriptorSchema).readonly()
12119
+ )
11850
12120
  }
11851
12121
  });
11852
12122
  const ModelFormatSchema = _enum(MODEL_FORMATS);
@@ -13054,7 +13324,8 @@ const PackageUpdateSchema = object({
13054
13324
  currentVersion: string(),
13055
13325
  latestVersion: string(),
13056
13326
  category: _enum(["addon", "core"]),
13057
- requiresRestart: boolean()
13327
+ requiresRestart: boolean(),
13328
+ isSystem: boolean()
13058
13329
  });
13059
13330
  const PackageVersionInfoSchema = object({
13060
13331
  version: string(),
@@ -13087,6 +13358,42 @@ const UpdateFrameworkPackageResultSchema = object({
13087
13358
  /** Ms-epoch the server scheduled its self-restart. */
13088
13359
  restartingAt: number()
13089
13360
  });
13361
+ const BulkUpdateItemStatusSchema = _enum([
13362
+ "queued",
13363
+ "updating",
13364
+ "done",
13365
+ "done-pending-restart",
13366
+ "failed"
13367
+ ]);
13368
+ const BulkUpdateItemSchema = object({
13369
+ name: string(),
13370
+ isSystem: boolean(),
13371
+ fromVersion: string(),
13372
+ toVersion: string(),
13373
+ status: BulkUpdateItemStatusSchema,
13374
+ error: string().optional(),
13375
+ startedAtMs: number().optional(),
13376
+ completedAtMs: number().optional()
13377
+ });
13378
+ const BulkUpdatePhaseSchema = _enum([
13379
+ "regular",
13380
+ "system",
13381
+ "restarting",
13382
+ "finalizing"
13383
+ ]);
13384
+ const BulkUpdateStateSchema = object({
13385
+ id: string(),
13386
+ nodeId: string(),
13387
+ startedAtMs: number(),
13388
+ completedAtMs: number().optional(),
13389
+ total: number(),
13390
+ completed: number(),
13391
+ failed: number(),
13392
+ current: string().nullable(),
13393
+ phase: BulkUpdatePhaseSchema,
13394
+ cancelled: boolean(),
13395
+ items: array(BulkUpdateItemSchema).readonly()
13396
+ });
13090
13397
  const FrameworkPackageStatusSchema = object({
13091
13398
  packageName: string(),
13092
13399
  currentVersion: string(),
@@ -13224,7 +13531,7 @@ const CustomActionInputSchema = object({
13224
13531
  getLastRestart: method(
13225
13532
  _void(),
13226
13533
  object({
13227
- kind: _enum(["framework-update", "manual", "system"]),
13534
+ kind: _enum(["framework-update", "manual", "system", "framework-bulk-update"]),
13228
13535
  packageName: string().optional(),
13229
13536
  fromVersion: string().optional(),
13230
13537
  toVersion: string().optional(),
@@ -13314,11 +13621,70 @@ const CustomActionInputSchema = object({
13314
13621
  updateFrameworkPackage: method(
13315
13622
  object({
13316
13623
  packageName: string().min(1),
13317
- version: string().optional()
13624
+ version: string().optional(),
13625
+ deferRestart: boolean().optional()
13318
13626
  }),
13319
13627
  UpdateFrameworkPackageResultSchema,
13320
13628
  { kind: "mutation", auth: "admin" }
13321
13629
  ),
13630
+ /**
13631
+ * Kicks off a server-side bulk update operation and returns the bulk
13632
+ * id immediately. The operation runs asynchronously; observe progress
13633
+ * via the `AddonsBulkUpdateProgress` event or `getBulkUpdateState`.
13634
+ * Items with `isSystem: true` use `deferRestart` — the hub restarts
13635
+ * ONCE at the end of the system phase, after all system packages are
13636
+ * installed.
13637
+ *
13638
+ * `items[].version` is REQUIRED — callers must pass the resolved
13639
+ * version from `listUpdates`. There is no `'latest'` default here
13640
+ * (unlike `updatePackage`) to guarantee deterministic bulk rolls.
13641
+ */
13642
+ startBulkUpdate: method(
13643
+ object({
13644
+ nodeId: string(),
13645
+ items: array(object({
13646
+ name: string(),
13647
+ version: string(),
13648
+ isSystem: boolean()
13649
+ })).readonly()
13650
+ }),
13651
+ object({ id: string() }),
13652
+ { kind: "mutation", auth: "admin" }
13653
+ ),
13654
+ /**
13655
+ * Returns the current state of a bulk update by id.
13656
+ * Returns `null` if the id is unknown or has been auto-cleaned
13657
+ * (5 minutes after `completedAt` the record is evicted from memory).
13658
+ */
13659
+ getBulkUpdateState: method(
13660
+ object({ id: string() }),
13661
+ BulkUpdateStateSchema.nullable(),
13662
+ { auth: "admin" }
13663
+ ),
13664
+ /**
13665
+ * Cancels an in-flight bulk update. The update loop exits after the
13666
+ * currently-processing item completes — cancellation is not
13667
+ * instantaneous. Has no effect once the `restarting` phase has been
13668
+ * entered (the hub is already shutting down at that point).
13669
+ * Returns `{ cancelled: false }` if the id is unknown, the operation
13670
+ * has already completed, or the `restarting` phase is active.
13671
+ */
13672
+ cancelBulkUpdate: method(
13673
+ object({ id: string() }),
13674
+ object({ cancelled: boolean() }),
13675
+ { kind: "mutation", auth: "admin" }
13676
+ ),
13677
+ /**
13678
+ * Lists all currently active (non-completed) bulk updates.
13679
+ * If `nodeId` is provided, filters to only bulk updates targeting
13680
+ * that node. Useful for restoring an in-progress banner on a fresh
13681
+ * page load when the UI reconnects mid-operation.
13682
+ */
13683
+ listActiveBulkUpdates: method(
13684
+ object({ nodeId: string().optional() }),
13685
+ array(BulkUpdateStateSchema).readonly(),
13686
+ { auth: "admin" }
13687
+ ),
13322
13688
  getVersions: method(
13323
13689
  object({ name: string() }),
13324
13690
  array(PackageVersionInfoSchema).readonly()
@@ -13446,6 +13812,7 @@ var EventCategory = /* @__PURE__ */ ((EventCategory2) => {
13446
13812
  EventCategory2["StreamBrokerOnCamStreamDemand"] = "stream-broker.onCamStreamDemand";
13447
13813
  EventCategory2["StreamBrokerOnCamStreamIdle"] = "stream-broker.onCamStreamIdle";
13448
13814
  EventCategory2["StreamBrokerOnRequestStreamSourceRefresh"] = "stream-broker.onRequestStreamSourceRefresh";
13815
+ EventCategory2["StreamParamsChanged"] = "stream-params.changed";
13449
13816
  EventCategory2["DeviceStateChanged"] = "device.state-changed";
13450
13817
  EventCategory2["BatteryOnStatusChanged"] = "battery.onStatusChanged";
13451
13818
  EventCategory2["DoorbellOnPressed"] = "doorbell.onPressed";
@@ -13493,6 +13860,7 @@ var EventCategory = /* @__PURE__ */ ((EventCategory2) => {
13493
13860
  EventCategory2["NetworkTunnelStarted"] = "network.tunnel.started";
13494
13861
  EventCategory2["NetworkTunnelStopped"] = "network.tunnel.stopped";
13495
13862
  EventCategory2["LocalNetworkChanged"] = "network.local.changed";
13863
+ EventCategory2["MeshNetworkChanged"] = "network.mesh.changed";
13496
13864
  EventCategory2["BackupCompleted"] = "backup.completed";
13497
13865
  EventCategory2["BackupRestored"] = "backup.restored";
13498
13866
  EventCategory2["NotificationDispatched"] = "notification.dispatched";
@@ -13503,6 +13871,7 @@ var EventCategory = /* @__PURE__ */ ((EventCategory2) => {
13503
13871
  EventCategory2["DeviceAwake"] = "device.awake";
13504
13872
  EventCategory2["DeviceSleeping"] = "device.sleeping";
13505
13873
  EventCategory2["RetentionCleanup"] = "retention.cleanup";
13874
+ EventCategory2["AddonsBulkUpdateProgress"] = "addons.bulk-update-progress";
13506
13875
  return EventCategory2;
13507
13876
  })(EventCategory || {});
13508
13877
  function createEvent(category, source, data) {
@@ -13652,7 +14021,7 @@ class BaseAddon {
13652
14021
  }
13653
14022
  // ── Settings schemas (override to provide UI) ─────────────────────────
13654
14023
  /** Override to provide global-level settings UI schema. */
13655
- globalSettingsSchema() {
14024
+ globalSettingsSchema(_cap) {
13656
14025
  return null;
13657
14026
  }
13658
14027
  /** Override to provide device-level settings UI schema. */
@@ -13666,8 +14035,8 @@ class BaseAddon {
13666
14035
  // blob and every addon used exactly one of them; the distinction was
13667
14036
  // never semantically load-bearing. `global` won because it was the
13668
14037
  // widely-used one and the name reads naturally (per-node addon config).
13669
- async getGlobalSettings(overlay) {
13670
- const schema = this.globalSettingsSchema();
14038
+ async getGlobalSettings(overlay, cap) {
14039
+ const schema = this.globalSettingsSchema(cap);
13671
14040
  if (!schema) return { sections: [] };
13672
14041
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
13673
14042
  return hydrateSchema(schema, overlay ? { ...raw, ...overlay } : raw);