@camstack/addon-osd-manager 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.
Files changed (18) hide show
  1. package/dist/{MotionZonesSettings-_DSVj2zS.mjs → MotionZonesSettings-Bwxd22Nd.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-CgvKFmKQ.mjs → PrivacyMaskSettings-C00zKECj.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-DJUpb3Ti.mjs → SceneMonitorEditor-D-gSxqns.mjs} +3 -3
  4. package/dist/_stub.js +11 -11
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-B1-6X3ty.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-esbRlHg_.mjs} +4 -4
  6. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DR42bVST.mjs +26 -0
  7. package/dist/{hostInit-BJM9-t1E.mjs → hostInit-DjQdmQv2.mjs} +3 -3
  8. package/dist/index.js +311 -0
  9. package/dist/index.mjs +311 -0
  10. package/dist/{player-overlays-CwE6Ngj5.mjs → player-overlays-hG-OsP21.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-Bu5aY8Ga.mjs → responsive-pP5vcFcr.mjs} +1 -1
  13. package/dist/{square-C3lpKi0o.mjs → square-CZEgeXdp.mjs} +1 -1
  14. package/dist/{trash-2-Be1MSI7M.mjs → trash-2-w-Zu4e4Q.mjs} +1 -1
  15. package/dist/{use-device-snapshot-BOZ1NIb1.mjs → use-device-snapshot-BQ24djI3.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-BcIVFz88.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-CmYtdZEf.mjs} +1 -1
  17. package/package.json +1 -1
  18. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js--IWy5JTL.mjs +0 -26
package/dist/index.js CHANGED
@@ -5919,6 +5919,40 @@ var BaseAddon = class {
5919
5919
  deviceSettingsSchema() {
5920
5920
  return null;
5921
5921
  }
5922
+ /**
5923
+ * INTEGRATION-LEVEL SETTINGS — declare which of this addon's global sections
5924
+ * ARE the configuration of its integration.
5925
+ *
5926
+ * Return the `ConfigSection.id`s, from {@link globalSettingsSchema}, that an
5927
+ * operator should find on the addon's integration page (System →
5928
+ * Integrations → <name>) rather than only in the cluster-wide list of every
5929
+ * addon. Empty (the default) means the addon has no integration-level
5930
+ * settings and no such surface is offered — this is opt-in, because whether
5931
+ * an addon's configuration IS its integration's configuration depends on the
5932
+ * nature of the integration.
5933
+ *
5934
+ * WHAT THIS IS NOT. It is not a scope. The selected sections keep living in
5935
+ * the ONE global schema, in the ONE addon store, written by the ONE
5936
+ * `updateGlobalSettings` path. There is deliberately no
5937
+ * `updateIntegrationSettings`: a second write path is how a surface acquires
5938
+ * a second store key, and this repo has shipped that twice (`btmPath@hub`,
5939
+ * D266). Selecting sections cannot introduce a key that selecting cannot.
5940
+ *
5941
+ * WHY IT IS A LIST OF SECTION IDS AND NOT A MARKER ON THE SECTION.
5942
+ * `ConfigFieldBase` used to carry `scope?: 'device' | 'global'` and it was
5943
+ * removed with the reason recorded at
5944
+ * `packages/types/src/interfaces/config-ui.ts:249` — *"a field's scope is
5945
+ * determined by WHICH schema it lives in, not by a field-level marker."* A
5946
+ * marker sprinkled across sections also has to borrow a field that already
5947
+ * means something else; borrowing `section.tab` put the literal word
5948
+ * "integration" into an operator-facing tab bar, because `tab` means "how to
5949
+ * GROUP this visually" and cannot also mean "where this lives" (D269
5950
+ * supersedes D268). One declaration, in one place, next to the schema whose
5951
+ * ids it names.
5952
+ */
5953
+ integrationSettingSections() {
5954
+ return [];
5955
+ }
5922
5956
  async getGlobalSettings(overlay, cap, nodeId) {
5923
5957
  const schema = this.globalSettingsSchema(cap);
5924
5958
  if (!schema) return { sections: [] };
@@ -5929,6 +5963,55 @@ var BaseAddon = class {
5929
5963
  } : projected);
5930
5964
  }
5931
5965
  /**
5966
+ * The integration-level view of this addon's settings: exactly the sections
5967
+ * named by {@link integrationSettingSections}, hydrated from the SAME store
5968
+ * `getGlobalSettings` reads, and narrowed to cluster-scoped fields.
5969
+ *
5970
+ * Returns `null` when the addon declared nothing — an addon that opts out has
5971
+ * no integration settings surface at all, rather than an empty one that reads
5972
+ * as a failed load.
5973
+ *
5974
+ * Three properties hold BY CONSTRUCTION, which is why they are here in core
5975
+ * and not in whichever UI happens to render this:
5976
+ *
5977
+ * 1. **One key.** The payload is a SUBSET of the global schema, so a field
5978
+ * shown here is the same field, with the same bare key, that the addon's
5979
+ * own page shows. There is no integration-specific writer — callers save
5980
+ * through `updateGlobalSettings` — so a second store key is unreachable,
5981
+ * not merely discouraged.
5982
+ * 2. **No node scope.** `perNode: true` fields are DROPPED. Their store key
5983
+ * is `<key>@<nodeId>` and an integration is not a node; whichever node
5984
+ * such a field silently picked would be a wrong answer for the operator
5985
+ * who opened the page (D266).
5986
+ * 3. **No silent typo.** A declared id that names no section throws. The
5987
+ * alternative — skip it — turns a rename into a surface that quietly
5988
+ * empties, which looks exactly like an addon with nothing to configure.
5989
+ */
5990
+ async getIntegrationSettings(nodeId) {
5991
+ const declared = this.integrationSettingSections();
5992
+ if (declared.length === 0) return null;
5993
+ const schema = this.globalSettingsSchema();
5994
+ if (!schema) throw new Error(`${this.constructor.name}: integrationSettingSections() names [${declared.join(", ")}] but globalSettingsSchema() returns null.`);
5995
+ const byId = new Map(schema.sections.map((section) => [section.id, section]));
5996
+ const sections = [];
5997
+ for (const id of declared) {
5998
+ const section = byId.get(id);
5999
+ if (!section) throw new Error(`${this.constructor.name}: integrationSettingSections() names unknown section "${id}". Known sections: [${[...byId.keys()].join(", ")}].`);
6000
+ const fields = dropPerNodeFields(section.fields);
6001
+ if (fields.length === 0) continue;
6002
+ sections.push({
6003
+ ...section,
6004
+ fields
6005
+ });
6006
+ }
6007
+ if (sections.length === 0) return null;
6008
+ const projected = await this.resolveGlobalStore(nodeId);
6009
+ return hydrateSchema({
6010
+ ...schema,
6011
+ sections
6012
+ }, projected);
6013
+ }
6014
+ /**
5932
6015
  * The raw addon store PROJECTED onto the target node's bare per-node keys:
5933
6016
  * every `perNode: true` field carries THAT node's scoped value on its bare
5934
6017
  * key (absent scoped key ⇒ key absent, so the schema `default` wins — no
@@ -6232,6 +6315,41 @@ var BaseAddon = class {
6232
6315
  * `hydrateSchema` does. Valueless structural fields (separator/info/…)
6233
6316
  * don't declare `perNode` and are excluded by the `in` narrowing.
6234
6317
  */
6318
+ /**
6319
+ * The same fields with every `perNode: true` one removed, recursing into layout
6320
+ * containers exactly as {@link collectPerNodeFieldKeys} does. A container left
6321
+ * with no child is dropped rather than rendered empty.
6322
+ *
6323
+ * Used by `getIntegrationSettings`: an integration is not a node, so a field
6324
+ * whose store key is `<key>@<nodeId>` has no node to belong to there.
6325
+ */
6326
+ function dropPerNodeFields(fields) {
6327
+ const kept = [];
6328
+ for (const field of fields) {
6329
+ if (field.type === "group") {
6330
+ const inner = dropPerNodeFields(field.fields);
6331
+ if (inner.length > 0) kept.push({
6332
+ ...field,
6333
+ fields: inner
6334
+ });
6335
+ continue;
6336
+ }
6337
+ if (field.type === "sub-tabs") {
6338
+ const tabs = field.tabs.map((tab) => ({
6339
+ ...tab,
6340
+ fields: dropPerNodeFields(tab.fields)
6341
+ })).filter((tab) => tab.fields.length > 0);
6342
+ if (tabs.length > 0) kept.push({
6343
+ ...field,
6344
+ tabs
6345
+ });
6346
+ continue;
6347
+ }
6348
+ if ("perNode" in field && field.perNode === true) continue;
6349
+ kept.push(field);
6350
+ }
6351
+ return kept;
6352
+ }
6235
6353
  function collectPerNodeFieldKeys(fields) {
6236
6354
  const collected = [];
6237
6355
  for (const field of fields) {
@@ -9571,6 +9689,10 @@ var addonSettingsCapability = {
9571
9689
  kind: "mutation",
9572
9690
  auth: "admin"
9573
9691
  }),
9692
+ getIntegrationSettings: method(object({
9693
+ addonId: string(),
9694
+ nodeId: string().optional()
9695
+ }), SettingsSchemaWithValuesSchema.nullable()),
9574
9696
  getDeviceSettings: method(object({
9575
9697
  addonId: string(),
9576
9698
  deviceId: number(),
@@ -15001,6 +15123,133 @@ var logDestinationCapability = {
15001
15123
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
15002
15124
  mount: { kind: "skip" }
15003
15125
  };
15126
+ /**
15127
+ * `failure-contribution` — the capability an addon reports its OWN losses
15128
+ * through, per camera, with the denominator attached. It stores nothing.
15129
+ *
15130
+ * ## The twin of `load-contribution`, and why it is a twin and not a field
15131
+ *
15132
+ * `load-contribution` answers *what did this camera COST*. This answers *what
15133
+ * did this camera LOSE*. The reporting discipline is identical and deliberately
15134
+ * copied: the contributor reports what it already knows, hub-main adds only
15135
+ * `addonId`, nothing needs global knowledge, and there is no central list for
15136
+ * somebody to forget to edit.
15137
+ *
15138
+ * They are not merged, because their invariants are opposites:
15139
+ *
15140
+ * - a `load-contribution` measurement is **absent, never zero** — a zero would
15141
+ * claim a camera cost nothing, which is a measurement nobody made;
15142
+ * - a `failure-contribution` zero is the **most valuable value on the
15143
+ * surface** — `attempts: 400, succeeded: 400` is the proof a fix landed,
15144
+ * and it is exactly what an absent entry cannot say.
15145
+ *
15146
+ * Putting a loss counter on a cost entry would also break the reconciliation
15147
+ * that gives `load-contribution` its point: contributions are subtracted from
15148
+ * `metrics.node-processes-snapshot` to find processes nobody claims. A failure
15149
+ * has no process.
15150
+ *
15151
+ * ## Why not a log line, since the counters already exist
15152
+ *
15153
+ * Several of these paths already counted themselves — `CaptureScheduler`'s
15154
+ * per-device window, `KeyFrameCaptureLog`, `bumpCropMetric`. Every one of them
15155
+ * ends in a log line, and a log line is the thing the operator asked to stop
15156
+ * needing: *"possiamo armare questi errori intanto? Così al prossimo giro
15157
+ * ricontrolliamo tutti questi punti"*. Reading them meant grepping Loki and
15158
+ * hand-correlating timestamps, which is how a 22% thumbnail gap and a 3-hour
15159
+ * media blackout were both diagnosed. The counters stay; this is where they can
15160
+ * be READ.
15161
+ *
15162
+ * ## The rate is served with its denominator or not at all
15163
+ *
15164
+ * Every entry carries `attempts` and `succeeded`. A miss count alone is
15165
+ * unreadable: on 2026-08-28 the enrichment-crop miss count read as "35x worse
15166
+ * than yesterday" and was **flat across twelve hours** once divided by the
15167
+ * successes on the same path. A surface that publishes only the numerator
15168
+ * reproduces that mistake on every read.
15169
+ *
15170
+ * ## Shape
15171
+ *
15172
+ * Copied from `load-contribution.cap.ts` (`mode: 'collection'`,
15173
+ * `internal: true`, `mount: { kind: 'skip' }`): no tRPC route of its own and no
15174
+ * generated hooks, while `addons.listCapabilityProviders` still enumerates it
15175
+ * and the hub's `CapabilityRegistry` still holds an RPC proxy per provider — so
15176
+ * a forked runner's entries reach hub-main over transport that already exists.
15177
+ * No new UDS message, no second registry (D3). The operator reads the assembled
15178
+ * result through `system.getFailureContributions`.
15179
+ */
15180
+ var FailureReasonCountSchema = object({
15181
+ /**
15182
+ * Why the attempt did not land, in the contributor's own vocabulary —
15183
+ * `worker-lease-gone`, `queue-overflow`, `timeout`, `empty-read`. The same
15184
+ * strings that already appear in this repo's logs and, where one exists, the
15185
+ * same string the per-track `previewMissReason` records (D276): a second
15186
+ * vocabulary for the same loss would make the row and the counter
15187
+ * un-joinable.
15188
+ */
15189
+ reason: string(),
15190
+ count: number().int().nonnegative()
15191
+ });
15192
+ var FailureContributionSchema = object({
15193
+ /**
15194
+ * The failing path — `enrichment-crop`, `inference`, `plate-ocr`,
15195
+ * `person-over-vehicle`. Free text, for the reason `load-contribution` keeps
15196
+ * `unit` free: the families are owned by different addons and a shared enum
15197
+ * is a central list that rots invisibly.
15198
+ */
15199
+ family: string(),
15200
+ /**
15201
+ * The NUMERIC device id — the same value every log line carries as
15202
+ * `tags.deviceId`. Never nullable and never absent: a contributor that
15203
+ * cannot name the camera must not emit the entry, because a fleet total
15204
+ * cannot answer the only question anybody asks of this surface.
15205
+ */
15206
+ deviceId: number().int().positive(),
15207
+ /**
15208
+ * A second dimension inside the family: the model / step id for an inference
15209
+ * timeout, so "which camera AND which model" is one read. Absent when the
15210
+ * family has a single variant.
15211
+ */
15212
+ variant: string().optional(),
15213
+ /**
15214
+ * Epoch ms this counter started — the INCARNATION MARKER. A consumer
15215
+ * differencing two reads must drop the interval when it changes, because the
15216
+ * counter restarted from zero in a respawned runner. Same discipline as
15217
+ * `LoadContribution.startedAtMs`.
15218
+ */
15219
+ sinceMs: number(),
15220
+ /** Epoch ms it was read. `atMs - sinceMs` is the interval this covers. */
15221
+ atMs: number(),
15222
+ /**
15223
+ * THE DENOMINATOR — every attempt on this path for this camera in the
15224
+ * window. A failure count published without it is the mistake this schema
15225
+ * exists to make impossible.
15226
+ */
15227
+ attempts: number().int().nonnegative(),
15228
+ /** Attempts that landed. `attempts - succeeded` is the loss. */
15229
+ succeeded: number().int().nonnegative(),
15230
+ /** The loss, partitioned. Sums to `attempts - succeeded`. */
15231
+ reasons: array(FailureReasonCountSchema).readonly()
15232
+ });
15233
+ var failureContributionCapability = {
15234
+ name: "failure-contribution",
15235
+ scope: "system",
15236
+ mode: "collection",
15237
+ internal: true,
15238
+ methods: {
15239
+ /**
15240
+ * This addon's per-camera failure counters, read live from bounded in-RAM
15241
+ * state it already keeps. Inert: no persistence, no sampling, no timer.
15242
+ *
15243
+ * READING NEVER RESETS. The counters are CUMULATIVE since `sinceMs`, and a
15244
+ * consumer that wants a rate differences two reads. A draining read would
15245
+ * make two operators with the page open each destroy half of the other's
15246
+ * numbers, and `load-contribution` already settled the same question the
15247
+ * same way for `cpuSeconds`.
15248
+ */
15249
+ list: method(_void(), array(FailureContributionSchema).readonly()) },
15250
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
15251
+ mount: { kind: "skip" }
15252
+ };
15004
15253
  var LoadContributionSchema = object({
15005
15254
  role: _enum([
15006
15255
  "decode",
@@ -20527,6 +20776,20 @@ var TrackSchema = object({
20527
20776
  * `=== true` and render nothing otherwise — never infer "no rider".
20528
20777
  */
20529
20778
  hasRider: boolean().optional(),
20779
+ /**
20780
+ * WHY this track ended without a NATIVE best-shot tile
20781
+ * ([D276](../decisions/adr-0276-a-stand-in-tile-is-provisional-and-a-close-says-why.md)) —
20782
+ * a composed token line (`no-key-frame capture=keyframe:native-missx4`,
20783
+ * `derive-returned-null tile=standin`, …) written at close and CLEARED by
20784
+ * the late-keyFrame upgrade when a native tile lands after all. The
20785
+ * operator-facing answer to "perché manca l'immagine?" on a track whose
20786
+ * tile is a face/plate stand-in, a raster crop, or an icon.
20787
+ *
20788
+ * **Absent ≠ "missed silently"**: a row written before the column, a hub
20789
+ * that predates the field, and every track whose tile landed native all
20790
+ * omit it. Render nothing when absent.
20791
+ */
20792
+ previewMissReason: string().optional(),
20530
20793
  ...TrackFlagFields,
20531
20794
  ...TrackRetrainFields
20532
20795
  });
@@ -34042,6 +34305,13 @@ var LoggingSettingsPatchSchema = object({
34042
34305
  * anyone but its owner.
34043
34306
  */
34044
34307
  var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: string() });
34308
+ /**
34309
+ * One per-camera failure counter, plus WHO reported it.
34310
+ *
34311
+ * Same rule as {@link ReportedLoadContributionSchema}: `addonId` is stamped by
34312
+ * the hub as it enumerates providers, never by the contributor.
34313
+ */
34314
+ var ReportedFailureContributionSchema = FailureContributionSchema.extend({ addonId: string() });
34045
34315
  var GetLoggingSettingsInputSchema = object({
34046
34316
  scopeNodeId: string().optional(),
34047
34317
  /**
@@ -34160,6 +34430,28 @@ var systemCapability = {
34160
34430
  */
34161
34431
  getLoadContributions: method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }),
34162
34432
  /**
34433
+ * Every `failure-contribution` an addon on this cluster reports — per
34434
+ * camera, per reason, **with the denominator attached**.
34435
+ *
34436
+ * This is the surface the operator asked for on 2026-08-28 (*"possiamo
34437
+ * armare questi errori intanto? Così al prossimo giro ricontrolliamo tutti
34438
+ * questi punti"*). Before it, four live failure modes could only be counted
34439
+ * by grepping Loki and hand-correlating timestamps, which is exactly how a
34440
+ * 22% thumbnail gap and a 3-hour media blackout were diagnosed — twice.
34441
+ *
34442
+ * Read it as a RATIO, never as a count. `attempts` is on every entry
34443
+ * because the count on its own lies: `enrichment crop native miss` read as
34444
+ * "35x worse than yesterday" and was flat across twelve hours once divided
34445
+ * by the successes on the same path.
34446
+ *
34447
+ * The counters are CUMULATIVE since each entry's `sinceMs`. Reading does
34448
+ * not reset them, and `sinceMs` changing means the reporting runner
34449
+ * respawned — a consumer differencing two reads drops that interval.
34450
+ *
34451
+ * Admin-only: the rows name cameras and the paths that fail on them.
34452
+ */
34453
+ getFailureContributions: method(_void(), array(ReportedFailureContributionSchema).readonly(), { auth: "admin" }),
34454
+ /**
34163
34455
  * The logging settings document — levels and armed diagnostics — resolved
34164
34456
  * for `nodeId`, or for the cluster when `nodeId` is absent.
34165
34457
  *
@@ -35394,6 +35686,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
35394
35686
  eventEmitterCapability,
35395
35687
  eventsCapability,
35396
35688
  faceGalleryCapability,
35689
+ failureContributionCapability,
35397
35690
  fanControlCapability,
35398
35691
  featureProbeCapability,
35399
35692
  filesystemBrowseCapability,
@@ -35730,6 +36023,12 @@ Object.freeze({
35730
36023
  addonId: null,
35731
36024
  access: "view"
35732
36025
  },
36026
+ "addonSettings.getIntegrationSettings": {
36027
+ capName: "addon-settings",
36028
+ capScope: "system",
36029
+ addonId: null,
36030
+ access: "view"
36031
+ },
35733
36032
  "addonSettings.updateDeviceSettings": {
35734
36033
  capName: "addon-settings",
35735
36034
  capScope: "system",
@@ -37392,6 +37691,12 @@ Object.freeze({
37392
37691
  addonId: null,
37393
37692
  access: "create"
37394
37693
  },
37694
+ "failureContribution.list": {
37695
+ capName: "failure-contribution",
37696
+ capScope: "system",
37697
+ addonId: null,
37698
+ access: "view"
37699
+ },
37395
37700
  "fanControl.setDirection": {
37396
37701
  capName: "fan-control",
37397
37702
  capScope: "device",
@@ -40698,6 +41003,12 @@ Object.freeze({
40698
41003
  addonId: null,
40699
41004
  access: "create"
40700
41005
  },
41006
+ "system.getFailureContributions": {
41007
+ capName: "system",
41008
+ capScope: "system",
41009
+ addonId: null,
41010
+ access: "view"
41011
+ },
40701
41012
  "system.getLoadContributions": {
40702
41013
  capName: "system",
40703
41014
  capScope: "system",