@camstack/addon-osd-manager 0.1.151 → 0.1.152

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. package/dist/{MotionZonesSettings-C5JP-D9r.mjs → MotionZonesSettings-D5c6b4QP.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-BhVDaMHE.mjs → PrivacyMaskSettings-DMve7K-c.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-Bt7UBGiu.mjs → SceneMonitorEditor-e1z_C79o.mjs} +3 -3
  4. package/dist/_stub.js +79 -79
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-BdJcTI3r.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-C8rKpVdZ.mjs} +4 -4
  6. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DQC5oc2I.mjs +26 -0
  7. package/dist/{hostInit-tyNFvZo9.mjs → hostInit-B8_o5hJj.mjs} +3 -3
  8. package/dist/index.js +469 -315
  9. package/dist/index.mjs +469 -315
  10. package/dist/{player-overlays-qwCKS5Oy.mjs → player-overlays-BchlNvHO.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-D-UTdp-i.mjs → responsive-DRZ6VoYV.mjs} +1 -1
  13. package/dist/{scene-monitor-copy-BWmGHVvh.mjs → scene-monitor-copy-CmtMsrJb.mjs} +1 -1
  14. package/dist/{square-DIs3wUiy.mjs → square-BYLYL5VB.mjs} +1 -1
  15. package/dist/{trash-2-DzfMGb1n.mjs → trash-2-BdEqSrHt.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-CHKQMC7P.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-p2S4DkBF.mjs} +1 -1
  17. package/package.json +2 -2
  18. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BC1x5E1q.mjs +0 -26
package/dist/index.js CHANGED
@@ -11901,6 +11901,103 @@ var authProviderCapability = {
11901
11901
  mount: { kind: "skip" }
11902
11902
  };
11903
11903
  /**
11904
+ * How long one snapshot may take end to end: the pinned copy of the whole
11905
+ * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
11906
+ * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
11907
+ * generous because a timeout fails the whole backup run.
11908
+ */
11909
+ var DATABASE_SNAPSHOT_TIMEOUT_MS = 30 * 6e4;
11910
+ /**
11911
+ * The databases an owner snapshots — declared by the OWNER, so a caller never
11912
+ * guesses which file is live from what sits beside it (a cleanly closed
11913
+ * database has no sidecars and is just as owned). Paths are absolute on the
11914
+ * owner's host.
11915
+ */
11916
+ var DatabaseSnapshotTargetsSchema = object({ databases: array(object({ livePath: string() })) });
11917
+ /** One b-tree table of the snapshot and the rows it holds. */
11918
+ var DatabaseSnapshotTableSchema = object({
11919
+ name: string(),
11920
+ rows: number().int().nonnegative()
11921
+ });
11922
+ /**
11923
+ * What the owner wrote. Every path is ABSOLUTE on the owner's host — the
11924
+ * caller pins the call to its own node, so it can read the file; a caller
11925
+ * that finds the path outside its own data root refuses it rather than
11926
+ * archiving something it cannot place.
11927
+ */
11928
+ var DatabaseSnapshotResultSchema = object({
11929
+ /** The live database the snapshot copies. Never to be copied as a file. */
11930
+ livePath: string(),
11931
+ /** The verified copy: `<livePath>` + {@link DATABASE_SNAPSHOT_SUFFIX}. */
11932
+ snapshotPath: string(),
11933
+ sizeBytes: number().int().nonnegative(),
11934
+ pageCount: number().int().nonnegative(),
11935
+ pageSize: number().int().positive(),
11936
+ /**
11937
+ * Every b-tree table with its row count. Read inside the SAME read
11938
+ * transaction the copy was taken from, and found EQUAL on the copy — so
11939
+ * this is the content of the snapshot, not an estimate of the source.
11940
+ */
11941
+ tables: array(DatabaseSnapshotTableSchema),
11942
+ /** Wall time of the pinned copy (row counts included). */
11943
+ copyMs: number().nonnegative(),
11944
+ /** Wall time of the checks on the copy (`integrity_check`, row counts). */
11945
+ verifyMs: number().nonnegative(),
11946
+ /** When the pinned read transaction began (ms epoch) — the snapshot's instant. */
11947
+ takenAt: number()
11948
+ });
11949
+ /**
11950
+ * A snapshot as an ARCHIVE records it (`ArchiveManifest.sqliteSnapshots`):
11951
+ * the same facts, with both paths relative to the archive root (the node's
11952
+ * data dir), because an archive outlives the host that made it.
11953
+ */
11954
+ var ArchiveSqliteSnapshotSchema = DatabaseSnapshotResultSchema.extend({
11955
+ /** Archive-relative path of the live database the archive does NOT carry. */
11956
+ livePath: string(),
11957
+ /** Archive-relative path of the verified copy it carries in its place. */
11958
+ snapshotPath: string()
11959
+ });
11960
+ /**
11961
+ * database-snapshot — the OWNER of a live SQLite database writes a consistent,
11962
+ * verified copy of it (D693).
11963
+ *
11964
+ * A live database cannot be copied as files: three files written by a live
11965
+ * process cannot be read at one instant from outside it, and a tar of the
11966
+ * `.db` / `-wal` / `-shm` trio was measured torn in 6/6 archives (D679). Nor may
11967
+ * another process open it (D679 rule 2). So the process that owns the
11968
+ * connection writes the copy — through its own connection, on a thread,
11969
+ * pinned to one read transaction — and checks it before handing it out.
11970
+ *
11971
+ * Hub-only and internal: `sqlite-settings` is the registrant, and its consumer
11972
+ * is `backup-orchestrator`, which calls it pinned to its own node so the path
11973
+ * it gets back is on its own host. `auth: 'admin'` explicitly — `internal`
11974
+ * does not gate the mount (see `data-store-provider`).
11975
+ */
11976
+ var databaseSnapshotCapability = {
11977
+ name: "database-snapshot",
11978
+ scope: "system",
11979
+ mode: "singleton",
11980
+ internal: true,
11981
+ methods: {
11982
+ /**
11983
+ * Which databases this owner snapshots. Answered from memory — no file is
11984
+ * touched — so a backup can ask it before deciding anything.
11985
+ */
11986
+ listDatabases: method(object({}), DatabaseSnapshotTargetsSchema, { auth: "admin" }),
11987
+ /**
11988
+ * Write `<db>.backup-snapshot` and return what it holds. Throws — and
11989
+ * leaves no file under the final name — when the copy fails
11990
+ * `integrity_check` or its row counts differ from the pinned source's.
11991
+ * A second call while one is running joins it.
11992
+ */
11993
+ writeSnapshot: method(object({}), DatabaseSnapshotResultSchema, {
11994
+ kind: "mutation",
11995
+ auth: "admin",
11996
+ timeoutMs: DATABASE_SNAPSHOT_TIMEOUT_MS
11997
+ })
11998
+ }
11999
+ };
12000
+ /**
11904
12001
  * Orchestrator-side destination metadata. The orchestrator computes
11905
12002
  * `id = <addonId>:<subId>` from its provider lookup so consumers
11906
12003
  * (admin UI, restore flow) see one canonical key.
@@ -12011,7 +12108,12 @@ var ArchiveManifestSchema = object({
12011
12108
  locations: array(string()),
12012
12109
  entries: array(ArchiveEntrySchema),
12013
12110
  totalBytes: number(),
12014
- totalFiles: number()
12111
+ totalFiles: number(),
12112
+ /**
12113
+ * Live SQLite databases this archive carries as their OWNER's verified
12114
+ * snapshot instead of as files (D693). Absent on archives made before.
12115
+ */
12116
+ sqliteSnapshots: array(ArchiveSqliteSnapshotSchema).optional()
12015
12117
  });
12016
12118
  var LocationStatSchema = object({
12017
12119
  name: string(),
@@ -13979,6 +14081,65 @@ var CompositionBlockStateSchema = object({
13979
14081
  reason: string().optional(),
13980
14082
  fields: array(CompositionFieldStateSchema)
13981
14083
  });
14084
+ var ComposerBlockSchema = object({
14085
+ id: string().min(1),
14086
+ name: string().min(1).max(120),
14087
+ enabled: boolean(),
14088
+ updatedAt: number(),
14089
+ composition: CompositionSchema
14090
+ });
14091
+ /**
14092
+ * A stored composition row that no longer PARSES — a schema a release
14093
+ * tightened, a row written by a newer build. Sent by id so the composer keeps
14094
+ * its device as a placeholder and reports the block `failed` naming the parse
14095
+ * error. Leaving it out would read, twice, exactly like a delete (D49).
14096
+ */
14097
+ var ComposerUnreadableBlockSchema = object({
14098
+ id: string().min(1),
14099
+ /** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
14100
+ name: string().nullable(),
14101
+ /** The parse error, in the operator's words. */
14102
+ error: string()
14103
+ });
14104
+ var composerCapability = {
14105
+ name: "composer",
14106
+ scope: "system",
14107
+ mode: "singleton",
14108
+ internal: true,
14109
+ methods: {
14110
+ /**
14111
+ * Not `apply`: tRPC reserves `apply`, `call` and `then` as router keys, and
14112
+ * `router({})` throws on one — every cap router is built at boot.
14113
+ */
14114
+ applyBlocks: method(object({
14115
+ /** The shared Blocks integration; null until `core-blocks` has reconciled it. */
14116
+ integrationId: string().nullable(),
14117
+ /** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
14118
+ storeLoaded: boolean(),
14119
+ blocks: array(ComposerBlockSchema),
14120
+ /** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
14121
+ restartBlockIds: array(string()),
14122
+ /** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
14123
+ unreadableBlocks: array(ComposerUnreadableBlockSchema)
14124
+ }), object({
14125
+ blocks: array(object({
14126
+ blockId: string(),
14127
+ status: CompositionRunStatusSchema,
14128
+ reason: string().optional()
14129
+ })),
14130
+ /**
14131
+ * False when the composer declared and withdrew NOTHING (store not loaded,
14132
+ * integration unknown, its own rows unread or read empty once). An empty
14133
+ * apply the caller owes is paid off only by one the composer acted on (D49).
14134
+ */
14135
+ acted: boolean()
14136
+ }), {
14137
+ kind: "mutation",
14138
+ auth: "admin"
14139
+ }),
14140
+ getStates: method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" })
14141
+ }
14142
+ };
13982
14143
  var NUMBER = {
13983
14144
  kind: "fixed",
13984
14145
  result: ["number"]
@@ -14294,65 +14455,6 @@ var coreBlocksCapability = {
14294
14455
  })
14295
14456
  }
14296
14457
  };
14297
- var ComposerBlockSchema = object({
14298
- id: string().min(1),
14299
- name: string().min(1).max(120),
14300
- enabled: boolean(),
14301
- updatedAt: number(),
14302
- composition: CompositionSchema
14303
- });
14304
- /**
14305
- * A stored composition row that no longer PARSES — a schema a release
14306
- * tightened, a row written by a newer build. Sent by id so the composer keeps
14307
- * its device as a placeholder and reports the block `failed` naming the parse
14308
- * error. Leaving it out would read, twice, exactly like a delete (D49).
14309
- */
14310
- var ComposerUnreadableBlockSchema = object({
14311
- id: string().min(1),
14312
- /** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
14313
- name: string().nullable(),
14314
- /** The parse error, in the operator's words. */
14315
- error: string()
14316
- });
14317
- var composerCapability = {
14318
- name: "composer",
14319
- scope: "system",
14320
- mode: "singleton",
14321
- internal: true,
14322
- methods: {
14323
- /**
14324
- * Not `apply`: tRPC reserves `apply`, `call` and `then` as router keys, and
14325
- * `router({})` throws on one — every cap router is built at boot.
14326
- */
14327
- applyBlocks: method(object({
14328
- /** The shared Blocks integration; null until `core-blocks` has reconciled it. */
14329
- integrationId: string().nullable(),
14330
- /** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
14331
- storeLoaded: boolean(),
14332
- blocks: array(ComposerBlockSchema),
14333
- /** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
14334
- restartBlockIds: array(string()),
14335
- /** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
14336
- unreadableBlocks: array(ComposerUnreadableBlockSchema)
14337
- }), object({
14338
- blocks: array(object({
14339
- blockId: string(),
14340
- status: CompositionRunStatusSchema,
14341
- reason: string().optional()
14342
- })),
14343
- /**
14344
- * False when the composer declared and withdrew NOTHING (store not loaded,
14345
- * integration unknown, its own rows unread or read empty once). An empty
14346
- * apply the caller owes is paid off only by one the composer acted on (D49).
14347
- */
14348
- acted: boolean()
14349
- }), {
14350
- kind: "mutation",
14351
- auth: "admin"
14352
- }),
14353
- getStates: method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" })
14354
- }
14355
- };
14356
14458
  /**
14357
14459
  * `custom-model-registry` — collection cap exposing operator-registered
14358
14460
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -17388,6 +17490,251 @@ var deviceManagerCapability = {
17388
17490
  }
17389
17491
  };
17390
17492
  /**
17493
+ * Doorbell button cap. Two kinds of providers coexist behind this cap
17494
+ * name (same pattern as `snapshot`):
17495
+ *
17496
+ * - **Native** providers: registered per-device by device-driver
17497
+ * addons via `ctx.registerNativeCap` — either on a
17498
+ * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
17499
+ * or directly on the camera (Reolink registers at camera level).
17500
+ * Emits an `onPressed` event every time the firmware pushes a
17501
+ * ring; status tracks the last press and a pressCount since start
17502
+ * (diagnostic).
17503
+ *
17504
+ * - **Wrapper** provider: the `virtual-doorbell` system builtin
17505
+ * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
17506
+ * device (contact / switch / event-emitter …) into a doorbell for
17507
+ * a camera. `defaultActive: false` — the operator explicitly binds
17508
+ * it per camera in the device-bindings UI, then picks the source
17509
+ * device + trigger in the per-device settings.
17510
+ *
17511
+ * The DeviceEventPropagator re-emits `onPressed` on the camera parent
17512
+ * — subscribers listening at the camera level receive ring events
17513
+ * with `via[]` populated. No code on the parent needed.
17514
+ */
17515
+ var DoorbellStatusSchema = object({
17516
+ /** Ms epoch of the last press. null = never observed since this provider started. */
17517
+ lastPressedAt: number().nullable(),
17518
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
17519
+ pressCountSinceStart: number()
17520
+ });
17521
+ var DoorbellPressEventSchema = object({
17522
+ deviceId: number(),
17523
+ timestamp: number()
17524
+ });
17525
+ /**
17526
+ * The `doorbell-source` extension slot's editable value (D648): the camera's
17527
+ * virtual-doorbell trigger devices, exactly what the wrapper's
17528
+ * `doorbellSources` persists (as `{ deviceId }` records).
17529
+ */
17530
+ var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
17531
+ /**
17532
+ * The `doorbell-source` slot — which devices ring this camera's VIRTUAL
17533
+ * doorbell, owned by the `virtual-doorbell` wrapper over its
17534
+ * `doorbellSources` store (D648). A source rings when one of its
17535
+ * `SOURCE_CAPS` rises. Whether the wrapper is bound to the camera at all is a
17536
+ * different authority (`deviceManager.setWrapperActive`, D62) — the slot
17537
+ * reports it, it never writes it.
17538
+ */
17539
+ var DOORBELL_SOURCE_SLOT = {
17540
+ id: "doorbell-source",
17541
+ label: "Doorbell trigger devices",
17542
+ description: "Any of these becoming active rings this camera's virtual doorbell: a binary sensor when it goes active, a switch when it turns on.",
17543
+ shape: "association",
17544
+ hostDeviceTypes: [DeviceType.Camera],
17545
+ sourceCaps: SOURCE_CAPS,
17546
+ cardinality: "many",
17547
+ value: DoorbellSourceSlotValueSchema
17548
+ };
17549
+ var doorbellCapability = {
17550
+ name: "doorbell",
17551
+ scope: "device",
17552
+ deviceNative: true,
17553
+ mode: "singleton",
17554
+ kind: "wrapper",
17555
+ defaultActive: false,
17556
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
17557
+ extensionSlots: [DOORBELL_SOURCE_SLOT],
17558
+ methods: {},
17559
+ events: {
17560
+ /**
17561
+ * Fires once per physical press. Reolink delivers via Baichuan
17562
+ * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
17563
+ * release/duration — it's a pulse.
17564
+ */
17565
+ onPressed: {
17566
+ data: DoorbellPressEventSchema,
17567
+ fromState: {
17568
+ field: "lastPressedAt",
17569
+ on: "advance"
17570
+ }
17571
+ } },
17572
+ status: {
17573
+ schema: DoorbellStatusSchema,
17574
+ kind: "push"
17575
+ },
17576
+ /**
17577
+ * Runtime-state slice — last press timestamp + lifetime press count.
17578
+ * Mirrored by the kernel and readable via
17579
+ * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
17580
+ * without subscribing.
17581
+ */
17582
+ runtimeState: DoorbellStatusSchema,
17583
+ /**
17584
+ * Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
17585
+ *
17586
+ * See `RuntimeStateDurability`. Enforced by
17587
+ * `scripts/check-runtime-state-durability.ts`.
17588
+ */
17589
+ durability: "restored"
17590
+ };
17591
+ var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
17592
+ DeviceType.Camera, DeviceType.Camera;
17593
+ /** A slot's editable value, discriminated on `slot`. */
17594
+ var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
17595
+ /** Every device type at least one slot is offered on — `device-extension`'s `deviceTypes`. */
17596
+ var EXTENSION_HOST_DEVICE_TYPES = [DeviceType.Camera];
17597
+ var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
17598
+ state: literal("unreachable"),
17599
+ reason: string()
17600
+ })]);
17601
+ /** One provider of slots — the D554 source-listing row. */
17602
+ var ExtensionSourceSchema = object({
17603
+ /** The routing prefix of every `slotRef` this provider serves. */
17604
+ source: string(),
17605
+ label: string(),
17606
+ /** Stamped by the dispatcher from the registry (D557). */
17607
+ addonId: string().optional(),
17608
+ availability: ExtensionSourceAvailabilitySchema
17609
+ });
17610
+ /**
17611
+ * How one picked source stands. `missing` — no such device (deleted, or an id
17612
+ * that never existed); `foreign` — a device the slot's owner cannot read from
17613
+ * (a source owned by another addon); `unavailable` — it exists but is not a
17614
+ * valid source now (a container, which is never a pick). A source in any of
17615
+ * these states is SHOWN, never silently dropped or re-pointed.
17616
+ */
17617
+ var ExtensionSourceHealthSchema = _enum([
17618
+ "ok",
17619
+ "missing",
17620
+ "foreign",
17621
+ "unavailable"
17622
+ ]);
17623
+ var ExtensionSlotSourceSchema = object({
17624
+ deviceId: number().int(),
17625
+ /** The device's name, or null when it could not be resolved (`missing`). */
17626
+ name: string().nullable(),
17627
+ health: ExtensionSourceHealthSchema,
17628
+ detail: string().optional()
17629
+ });
17630
+ /**
17631
+ * A DIFFERENT authority that gates whether the slot's sources do anything —
17632
+ * reported read-only, named, never written through this contract (D62). The
17633
+ * virtual doorbell's sources ring only while the wrapper is bound to the
17634
+ * camera, and that binding belongs to `deviceManager.setWrapperActive`.
17635
+ * `unknown` means the gate could not be read; it is not "inactive" (D315).
17636
+ */
17637
+ var ExtensionSlotActivationSchema = object({
17638
+ state: _enum([
17639
+ "active",
17640
+ "inactive",
17641
+ "unknown"
17642
+ ]),
17643
+ authority: string(),
17644
+ detail: string().optional()
17645
+ });
17646
+ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
17647
+ state: literal("ok"),
17648
+ /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
17649
+ slotRef: string(),
17650
+ slot: ExtensionSlotIdSchema,
17651
+ /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
17652
+ authority: string(),
17653
+ activation: ExtensionSlotActivationSchema.nullable(),
17654
+ value: ExtensionSlotValueSchema,
17655
+ sources: array(ExtensionSlotSourceSchema),
17656
+ /**
17657
+ * Devices the owner links to the host ON ITS OWN, by structure, and that
17658
+ * are therefore not in `value` (D655): a camera's device-tree children, and
17659
+ * the child a picked container stands for (D356 — a container is never a
17660
+ * pick, its children are). Read-only; the editor shows them and may offer
17661
+ * per-device options on them (the linked-devices tracked-event opt-in).
17662
+ * Absent when the owner links nothing beyond the picks, and from an owner
17663
+ * older than D655.
17664
+ */
17665
+ impliedSources: array(ExtensionSlotSourceSchema).optional()
17666
+ }), object({
17667
+ state: literal("unreachable"),
17668
+ addonId: string(),
17669
+ reason: string()
17670
+ })]);
17671
+ var ExtensionCandidatesSchema = object({ candidates: array(object({
17672
+ deviceId: number().int(),
17673
+ name: string(),
17674
+ type: string(),
17675
+ location: string().nullable(),
17676
+ /** The container a child belongs to — "Spotlight 2" alone does not say which. */
17677
+ parentName: string().nullable(),
17678
+ /** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
17679
+ suggested: boolean(),
17680
+ /** Why it is suggested ("same location"), null when it is not. */
17681
+ reason: string().nullable()
17682
+ })) });
17683
+ /**
17684
+ * What a write did. `none` — applied in place. `host` / `parent` — the owner
17685
+ * reloaded the host (or, for an accessory child, its parent) because the
17686
+ * write changed what the host REGISTERS. A slot never adds a capability to its
17687
+ * host — that is a composition (D663) — so today's owners answer `none`.
17688
+ */
17689
+ var ExtensionSetResultSchema = object({
17690
+ applied: literal(true),
17691
+ reload: _enum([
17692
+ "none",
17693
+ "host",
17694
+ "parent"
17695
+ ])
17696
+ });
17697
+ var ExtensionSlotRefInputSchema = object({
17698
+ deviceId: number().int(),
17699
+ slotRef: string().min(1)
17700
+ });
17701
+ var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
17702
+ var deviceExtensionCapability = {
17703
+ name: "device-extension",
17704
+ scope: "device",
17705
+ mode: "collection",
17706
+ kind: "wrapper",
17707
+ defaultActive: true,
17708
+ deviceTypes: EXTENSION_HOST_DEVICE_TYPES,
17709
+ deviceConfig: { ui: {
17710
+ kind: "widget",
17711
+ widgetId: "host/extensions-panel",
17712
+ tab: "general",
17713
+ label: "Extensions",
17714
+ order: 72
17715
+ } },
17716
+ methods: {
17717
+ /** One row per slot owner — the D554 source listing `slotRef` routes by. */
17718
+ listSources: method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)),
17719
+ /**
17720
+ * Every slot this device is offered, read by each owner from its own
17721
+ * store. A provider that throws is ONE `unreachable` row (D554.4).
17722
+ */
17723
+ listSlots: method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)),
17724
+ /** The devices the slot's editor may offer, suggestions pre-marked (D356). */
17725
+ listCandidates: method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema),
17726
+ /**
17727
+ * Write one slot through its owner's EXISTING write path. The owner
17728
+ * validates (a container is never a linked device, a camera is never its
17729
+ * own source) and refuses by name before persisting.
17730
+ */
17731
+ setSlot: method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
17732
+ kind: "mutation",
17733
+ auth: "admin"
17734
+ })
17735
+ }
17736
+ };
17737
+ /**
17391
17738
  * Per-field ownership (D663). A composition may CLAIM fields of a native
17392
17739
  * cap's runtime-state slice on an existing device: `replace` overlays the
17393
17740
  * claimed fields on the native slice, `add` builds a slice the device does
@@ -20875,17 +21222,25 @@ var NcScheduleSchema = object({
20875
21222
  */
20876
21223
  var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
20877
21224
  /**
21225
+ * How a summary window is SENT (D698): `rule` (absent) = ONE notification with
21226
+ * every section; `section` = one notification per section with something to
21227
+ * say (an unavailable section included), each titled by its section.
21228
+ */
21229
+ var NcStateSummarySplitSchema = _enum(["rule", "section"]);
21230
+ /**
20878
21231
  * `summary`: the rule sends NOTHING per event. When a window of `schedule`
20879
21232
  * CLOSES, it sends one notification with the current state of its kinds
20880
21233
  * (low batteries, consumables to replace, devices offline, updates
20881
- * available, ...), after its own filters. Nothing to report sends nothing.
21234
+ * available, ...), after its own filters — or, with `split: 'section'`
21235
+ * (D698), one notification per section. Nothing to report sends nothing.
20882
21236
  * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
20883
21237
  * midnight crossing); the send time is each window's END. Refused on the
20884
21238
  * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
20885
21239
  */
20886
21240
  var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
20887
21241
  mode: literal("summary"),
20888
- schedule: NcScheduleSchema
21242
+ schedule: NcScheduleSchema,
21243
+ split: NcStateSummarySplitSchema.optional()
20889
21244
  })]);
20890
21245
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
20891
21246
  var NcPlateMatcherSchema = object({
@@ -21536,10 +21891,12 @@ var NcMediaPolicySchema = object({
21536
21891
  /**
21537
21892
  * Cooldown GRANULARITY over the subject's class — how much a fired
21538
21893
  * notification suppresses.
21539
- * - `shared` (default, and the absent value) — one window for the whole
21540
- * rule/scope: a cat silences the next dog for `cooldownSec`.
21541
- * - `per-class` — an independent window per detected class, so cat→dog fires
21542
- * at once and cat→cat still waits.
21894
+ * - `per-class` (default, and the absent value since 2026-10-01 — D36) — an
21895
+ * independent window per detected class: cat→dog fires at once, cat→cat
21896
+ * still waits.
21897
+ * - `shared` — one window for the whole rule/scope: a cat silences the next
21898
+ * dog for `cooldownSec`. Only written when an operator picks it; it is no
21899
+ * longer what an unset rule means.
21543
21900
  *
21544
21901
  * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
21545
21902
  * must not be swallowed by a bark's window (the precedent this generalizes —
@@ -21554,8 +21911,11 @@ var NcThrottleSchema = object({
21554
21911
  /**
21555
21912
  * Class granularity of the cooldown key. Optional rather than defaulted:
21556
21913
  * a Zod default does NOT run on the addon→addon cap path, so a persisted
21557
- * rule authored before this field simply carries none — and the engine
21558
- * reads absent as `shared`, the pre-existing behaviour.
21914
+ * rule authored before this field simply carries none. The engine used to
21915
+ * read absent as `shared` (D36); since 2026-10-01 it reads absent as
21916
+ * `per-class` — the operator's default — and only an explicit `shared`
21917
+ * still means `shared`. See `cooldownKey` / `keysPerClass` in the rule
21918
+ * engine, and D36's dated note.
21559
21919
  */
21560
21920
  granularity: NcThrottleGranularitySchema.optional()
21561
21921
  });
@@ -21879,11 +22239,23 @@ var NcStateSummaryPreviewSectionSchema = object({
21879
22239
  reason: string().optional()
21880
22240
  });
21881
22241
  /**
22242
+ * One notification a previewed summary would send (D698): the whole summary on
22243
+ * a `rule` split (no `section`), one section's on a `section` split.
22244
+ */
22245
+ var NcStateSummaryPreviewRowSchema = object({
22246
+ section: string().optional(),
22247
+ /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
22248
+ title: string(),
22249
+ text: string(),
22250
+ count: number().int().nonnegative()
22251
+ });
22252
+ /**
21882
22253
  * The summary a rule would send now. `empty` = nothing to report and every
21883
22254
  * section read: such a summary is NOT sent. `dropped` counts the open states
21884
22255
  * the rule's own filters (or a camera mute) left out, by reason.
21885
22256
  * `excludedGroups` names the kind groups selected that have no state to report
21886
- * (backups & exports).
22257
+ * (backups & exports). `split` and `rows` (D698) say how it would be sent —
22258
+ * optional, so an older hub's answer still parses.
21887
22259
  */
21888
22260
  var NcStateSummaryPreviewSchema = object({
21889
22261
  empty: boolean(),
@@ -21891,7 +22263,9 @@ var NcStateSummaryPreviewSchema = object({
21891
22263
  text: string(),
21892
22264
  sections: array(NcStateSummaryPreviewSectionSchema),
21893
22265
  dropped: record(string(), number().int().nonnegative()),
21894
- excludedGroups: array(string())
22266
+ excludedGroups: array(string()),
22267
+ split: NcStateSummarySplitSchema.optional(),
22268
+ rows: array(NcStateSummaryPreviewRowSchema).optional()
21895
22269
  });
21896
22270
  var NcTestResultSchema = object({
21897
22271
  recordId: string(),
@@ -32229,251 +32603,6 @@ var videoclipsCapability = {
32229
32603
  }
32230
32604
  };
32231
32605
  /**
32232
- * Doorbell button cap. Two kinds of providers coexist behind this cap
32233
- * name (same pattern as `snapshot`):
32234
- *
32235
- * - **Native** providers: registered per-device by device-driver
32236
- * addons via `ctx.registerNativeCap` — either on a
32237
- * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
32238
- * or directly on the camera (Reolink registers at camera level).
32239
- * Emits an `onPressed` event every time the firmware pushes a
32240
- * ring; status tracks the last press and a pressCount since start
32241
- * (diagnostic).
32242
- *
32243
- * - **Wrapper** provider: the `virtual-doorbell` system builtin
32244
- * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
32245
- * device (contact / switch / event-emitter …) into a doorbell for
32246
- * a camera. `defaultActive: false` — the operator explicitly binds
32247
- * it per camera in the device-bindings UI, then picks the source
32248
- * device + trigger in the per-device settings.
32249
- *
32250
- * The DeviceEventPropagator re-emits `onPressed` on the camera parent
32251
- * — subscribers listening at the camera level receive ring events
32252
- * with `via[]` populated. No code on the parent needed.
32253
- */
32254
- var DoorbellStatusSchema = object({
32255
- /** Ms epoch of the last press. null = never observed since this provider started. */
32256
- lastPressedAt: number().nullable(),
32257
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
32258
- pressCountSinceStart: number()
32259
- });
32260
- var DoorbellPressEventSchema = object({
32261
- deviceId: number(),
32262
- timestamp: number()
32263
- });
32264
- /**
32265
- * The `doorbell-source` extension slot's editable value (D648): the camera's
32266
- * virtual-doorbell trigger devices, exactly what the wrapper's
32267
- * `doorbellSources` persists (as `{ deviceId }` records).
32268
- */
32269
- var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
32270
- /**
32271
- * The `doorbell-source` slot — which devices ring this camera's VIRTUAL
32272
- * doorbell, owned by the `virtual-doorbell` wrapper over its
32273
- * `doorbellSources` store (D648). A source rings when one of its
32274
- * `SOURCE_CAPS` rises. Whether the wrapper is bound to the camera at all is a
32275
- * different authority (`deviceManager.setWrapperActive`, D62) — the slot
32276
- * reports it, it never writes it.
32277
- */
32278
- var DOORBELL_SOURCE_SLOT = {
32279
- id: "doorbell-source",
32280
- label: "Doorbell trigger devices",
32281
- description: "Any of these becoming active rings this camera's virtual doorbell: a binary sensor when it goes active, a switch when it turns on.",
32282
- shape: "association",
32283
- hostDeviceTypes: [DeviceType.Camera],
32284
- sourceCaps: SOURCE_CAPS,
32285
- cardinality: "many",
32286
- value: DoorbellSourceSlotValueSchema
32287
- };
32288
- var doorbellCapability = {
32289
- name: "doorbell",
32290
- scope: "device",
32291
- deviceNative: true,
32292
- mode: "singleton",
32293
- kind: "wrapper",
32294
- defaultActive: false,
32295
- deviceTypes: [DeviceType.Button, DeviceType.Camera],
32296
- extensionSlots: [DOORBELL_SOURCE_SLOT],
32297
- methods: {},
32298
- events: {
32299
- /**
32300
- * Fires once per physical press. Reolink delivers via Baichuan
32301
- * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
32302
- * release/duration — it's a pulse.
32303
- */
32304
- onPressed: {
32305
- data: DoorbellPressEventSchema,
32306
- fromState: {
32307
- field: "lastPressedAt",
32308
- on: "advance"
32309
- }
32310
- } },
32311
- status: {
32312
- schema: DoorbellStatusSchema,
32313
- kind: "push"
32314
- },
32315
- /**
32316
- * Runtime-state slice — last press timestamp + lifetime press count.
32317
- * Mirrored by the kernel and readable via
32318
- * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
32319
- * without subscribing.
32320
- */
32321
- runtimeState: DoorbellStatusSchema,
32322
- /**
32323
- * Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
32324
- *
32325
- * See `RuntimeStateDurability`. Enforced by
32326
- * `scripts/check-runtime-state-durability.ts`.
32327
- */
32328
- durability: "restored"
32329
- };
32330
- var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
32331
- DeviceType.Camera, DeviceType.Camera;
32332
- /** A slot's editable value, discriminated on `slot`. */
32333
- var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
32334
- /** Every device type at least one slot is offered on — `device-extension`'s `deviceTypes`. */
32335
- var EXTENSION_HOST_DEVICE_TYPES = [DeviceType.Camera];
32336
- var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
32337
- state: literal("unreachable"),
32338
- reason: string()
32339
- })]);
32340
- /** One provider of slots — the D554 source-listing row. */
32341
- var ExtensionSourceSchema = object({
32342
- /** The routing prefix of every `slotRef` this provider serves. */
32343
- source: string(),
32344
- label: string(),
32345
- /** Stamped by the dispatcher from the registry (D557). */
32346
- addonId: string().optional(),
32347
- availability: ExtensionSourceAvailabilitySchema
32348
- });
32349
- /**
32350
- * How one picked source stands. `missing` — no such device (deleted, or an id
32351
- * that never existed); `foreign` — a device the slot's owner cannot read from
32352
- * (a source owned by another addon); `unavailable` — it exists but is not a
32353
- * valid source now (a container, which is never a pick). A source in any of
32354
- * these states is SHOWN, never silently dropped or re-pointed.
32355
- */
32356
- var ExtensionSourceHealthSchema = _enum([
32357
- "ok",
32358
- "missing",
32359
- "foreign",
32360
- "unavailable"
32361
- ]);
32362
- var ExtensionSlotSourceSchema = object({
32363
- deviceId: number().int(),
32364
- /** The device's name, or null when it could not be resolved (`missing`). */
32365
- name: string().nullable(),
32366
- health: ExtensionSourceHealthSchema,
32367
- detail: string().optional()
32368
- });
32369
- /**
32370
- * A DIFFERENT authority that gates whether the slot's sources do anything —
32371
- * reported read-only, named, never written through this contract (D62). The
32372
- * virtual doorbell's sources ring only while the wrapper is bound to the
32373
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
32374
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
32375
- */
32376
- var ExtensionSlotActivationSchema = object({
32377
- state: _enum([
32378
- "active",
32379
- "inactive",
32380
- "unknown"
32381
- ]),
32382
- authority: string(),
32383
- detail: string().optional()
32384
- });
32385
- var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
32386
- state: literal("ok"),
32387
- /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
32388
- slotRef: string(),
32389
- slot: ExtensionSlotIdSchema,
32390
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
32391
- authority: string(),
32392
- activation: ExtensionSlotActivationSchema.nullable(),
32393
- value: ExtensionSlotValueSchema,
32394
- sources: array(ExtensionSlotSourceSchema),
32395
- /**
32396
- * Devices the owner links to the host ON ITS OWN, by structure, and that
32397
- * are therefore not in `value` (D655): a camera's device-tree children, and
32398
- * the child a picked container stands for (D356 — a container is never a
32399
- * pick, its children are). Read-only; the editor shows them and may offer
32400
- * per-device options on them (the linked-devices tracked-event opt-in).
32401
- * Absent when the owner links nothing beyond the picks, and from an owner
32402
- * older than D655.
32403
- */
32404
- impliedSources: array(ExtensionSlotSourceSchema).optional()
32405
- }), object({
32406
- state: literal("unreachable"),
32407
- addonId: string(),
32408
- reason: string()
32409
- })]);
32410
- var ExtensionCandidatesSchema = object({ candidates: array(object({
32411
- deviceId: number().int(),
32412
- name: string(),
32413
- type: string(),
32414
- location: string().nullable(),
32415
- /** The container a child belongs to — "Spotlight 2" alone does not say which. */
32416
- parentName: string().nullable(),
32417
- /** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
32418
- suggested: boolean(),
32419
- /** Why it is suggested ("same location"), null when it is not. */
32420
- reason: string().nullable()
32421
- })) });
32422
- /**
32423
- * What a write did. `none` — applied in place. `host` / `parent` — the owner
32424
- * reloaded the host (or, for an accessory child, its parent) because the
32425
- * write changed what the host REGISTERS. A slot never adds a capability to its
32426
- * host — that is a composition (D663) — so today's owners answer `none`.
32427
- */
32428
- var ExtensionSetResultSchema = object({
32429
- applied: literal(true),
32430
- reload: _enum([
32431
- "none",
32432
- "host",
32433
- "parent"
32434
- ])
32435
- });
32436
- var ExtensionSlotRefInputSchema = object({
32437
- deviceId: number().int(),
32438
- slotRef: string().min(1)
32439
- });
32440
- var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
32441
- var deviceExtensionCapability = {
32442
- name: "device-extension",
32443
- scope: "device",
32444
- mode: "collection",
32445
- kind: "wrapper",
32446
- defaultActive: true,
32447
- deviceTypes: EXTENSION_HOST_DEVICE_TYPES,
32448
- deviceConfig: { ui: {
32449
- kind: "widget",
32450
- widgetId: "host/extensions-panel",
32451
- tab: "general",
32452
- label: "Extensions",
32453
- order: 72
32454
- } },
32455
- methods: {
32456
- /** One row per slot owner — the D554 source listing `slotRef` routes by. */
32457
- listSources: method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)),
32458
- /**
32459
- * Every slot this device is offered, read by each owner from its own
32460
- * store. A provider that throws is ONE `unreachable` row (D554.4).
32461
- */
32462
- listSlots: method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)),
32463
- /** The devices the slot's editor may offer, suggestions pre-marked (D356). */
32464
- listCandidates: method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema),
32465
- /**
32466
- * Write one slot through its owner's EXISTING write path. The owner
32467
- * validates (a container is never a linked device, a camera is never its
32468
- * own source) and refuses by name before persisting.
32469
- */
32470
- setSlot: method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
32471
- kind: "mutation",
32472
- auth: "admin"
32473
- })
32474
- }
32475
- };
32476
- /**
32477
32606
  * Optional client-side hints sent at session creation to help the provider
32478
32607
  * pick the best native source. All fields optional — a viewer that knows
32479
32608
  * nothing still gets a sane default. (Relocated from the retired `webrtc`
@@ -46539,6 +46668,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
46539
46668
  coverCapability,
46540
46669
  customModelRegistryCapability,
46541
46670
  dataStoreProviderCapability,
46671
+ databaseSnapshotCapability,
46542
46672
  dayNightCapability,
46543
46673
  decoderCapability,
46544
46674
  detectionPipelineCapability,
@@ -47801,6 +47931,18 @@ Object.freeze({
47801
47931
  addonId: null,
47802
47932
  access: "view"
47803
47933
  },
47934
+ "databaseSnapshot.listDatabases": {
47935
+ capName: "database-snapshot",
47936
+ capScope: "system",
47937
+ addonId: null,
47938
+ access: "view"
47939
+ },
47940
+ "databaseSnapshot.writeSnapshot": {
47941
+ capName: "database-snapshot",
47942
+ capScope: "system",
47943
+ addonId: null,
47944
+ access: "create"
47945
+ },
47804
47946
  "dataStoreProvider.aggregate": {
47805
47947
  capName: "data-store-provider",
47806
47948
  capScope: "system",
@@ -56409,6 +56551,18 @@ var STATE_SUMMARY_VARS = [
56409
56551
  systemEventKinds: [...SUMMARISABLE],
56410
56552
  systemEventDeliveries: ["summary"]
56411
56553
  },
56554
+ {
56555
+ name: "summarySection",
56556
+ label: "Summary: this section",
56557
+ description: "The section a per-section summary row is about (D698); on a summary sent as one message, every section it reports, comma-separated.",
56558
+ example: "Batterie scariche",
56559
+ group: "system",
56560
+ families: ["rule"],
56561
+ fields: [...RULE_TEXT],
56562
+ kinds: ["system"],
56563
+ systemEventKinds: [...SUMMARISABLE],
56564
+ systemEventDeliveries: ["summary"]
56565
+ },
56412
56566
  ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
56413
56567
  name: v.name,
56414
56568
  label: v.label,