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