@camstack/addon-provider-hikvision 1.2.34 → 1.2.35

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 (3) hide show
  1. package/dist/addon.js +184 -34
  2. package/dist/addon.mjs +184 -34
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -5808,6 +5808,13 @@ var BaseAddon = class {
5808
5808
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5809
5809
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5810
5810
  _registeredCapNames = [];
5811
+ /**
5812
+ * True only after `readAddonStore` actually answered. Constructor
5813
+ * defaults look like stored config when the store is down — a forked
5814
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5815
+ * mode, 2026-08-25) is not "the operator chose this".
5816
+ */
5817
+ settingsStoreReady = false;
5811
5818
  /** Default config values. Provided via constructor. */
5812
5819
  defaults;
5813
5820
  constructor(defaults) {
@@ -6208,7 +6215,9 @@ var BaseAddon = class {
6208
6215
  ];
6209
6216
  let lastErr;
6210
6217
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6211
- return await settings.readAddonStore() ?? {};
6218
+ const stored = await settings.readAddonStore() ?? {};
6219
+ this.settingsStoreReady = true;
6220
+ return stored;
6212
6221
  } catch (err) {
6213
6222
  lastErr = err;
6214
6223
  const msg = err instanceof Error ? err.message : String(err);
@@ -6216,6 +6225,7 @@ var BaseAddon = class {
6216
6225
  if (attempt === delaysMs.length) break;
6217
6226
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6218
6227
  }
6228
+ this.settingsStoreReady = false;
6219
6229
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6220
6230
  return {};
6221
6231
  }
@@ -8282,6 +8292,12 @@ var ModelVariantGroupSchema = object({
8282
8292
  */
8283
8293
  resolution: number().int().positive().optional()
8284
8294
  });
8295
+ var ModelProviderIdSchema = _enum([
8296
+ "camstack",
8297
+ "frigate",
8298
+ "scrypted",
8299
+ "custom"
8300
+ ]);
8285
8301
  var ModelCatalogEntrySchema = object({
8286
8302
  id: string(),
8287
8303
  name: string(),
@@ -8379,6 +8395,12 @@ var ModelCatalogEntrySchema = object({
8379
8395
  */
8380
8396
  group: ModelVariantGroupSchema.optional(),
8381
8397
  /**
8398
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8399
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8400
+ * persisted before this field existed (`inferModelProvider` fills those).
8401
+ */
8402
+ provider: ModelProviderIdSchema.optional(),
8403
+ /**
8382
8404
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8383
8405
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8384
8406
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -12182,6 +12204,27 @@ var LinkedDeviceSchema = object({
12182
12204
  features: array(string()),
12183
12205
  producesTrackedEvents: boolean().optional()
12184
12206
  });
12207
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12208
+ * The batch answer needs the tag; the single-device answer already has it
12209
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12210
+ var LinkedDevicesForDeviceSchema = object({
12211
+ deviceId: number(),
12212
+ mode: LinkedDevicesModeSchema,
12213
+ devices: array(LinkedDeviceSchema)
12214
+ });
12215
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12216
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12217
+ * object literal is exactly how the three drift apart. */
12218
+ var DeviceBindingsForDeviceSchema = object({
12219
+ deviceId: number(),
12220
+ entries: array(object({
12221
+ capName: string(),
12222
+ kind: _enum(["native", "wrapped"]),
12223
+ providerAddonId: string(),
12224
+ providerNodeId: string(),
12225
+ nativeAddonId: string()
12226
+ }))
12227
+ });
12185
12228
  var SavedDeviceRowSchema = object({
12186
12229
  /** Numeric id reserved at allocateDeviceId time. */
12187
12230
  id: number(),
@@ -12407,11 +12450,25 @@ method(object({
12407
12450
  projection: _enum(["full", "slim"]).optional(),
12408
12451
  /** Return only camera devices. Filtering server-side instead of
12409
12452
  * shipping 293 rows to find 12. */
12410
- isCamera: boolean().optional()
12453
+ isCamera: boolean().optional(),
12454
+ /**
12455
+ * Return only these device ids. For the caller that already KNOWS the
12456
+ * handful it wants and needs a field the id-bearing answer does not
12457
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12458
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12459
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12460
+ * refetches on the reconcile interval, on a phone.
12461
+ *
12462
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12463
+ * keys rather than rejecting them (verified against the live hub
12464
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12465
+ * it answers today and the caller filters as it already does.
12466
+ */
12467
+ deviceIds: array(number()).optional()
12411
12468
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12412
12469
  mode: LinkedDevicesModeSchema,
12413
12470
  devices: array(LinkedDeviceSchema)
12414
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12471
+ })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12415
12472
  deviceId: number(),
12416
12473
  values: record(string(), unknown())
12417
12474
  }), object({ success: literal(true) }), {
@@ -12438,25 +12495,7 @@ method(object({
12438
12495
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12439
12496
  kind: "mutation",
12440
12497
  auth: "admin"
12441
- }), method(object({ deviceId: number() }), object({
12442
- deviceId: number(),
12443
- entries: array(object({
12444
- capName: string(),
12445
- kind: _enum(["native", "wrapped"]),
12446
- providerAddonId: string(),
12447
- providerNodeId: string(),
12448
- nativeAddonId: string()
12449
- }))
12450
- })), method(object({}), array(object({
12451
- deviceId: number(),
12452
- entries: array(object({
12453
- capName: string(),
12454
- kind: _enum(["native", "wrapped"]),
12455
- providerAddonId: string(),
12456
- providerNodeId: string(),
12457
- nativeAddonId: string()
12458
- }))
12459
- }))), method(object({
12498
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12460
12499
  deviceId: number(),
12461
12500
  capName: string(),
12462
12501
  wrapperAddonId: string(),
@@ -14866,12 +14905,15 @@ var NcOccupancyConditionSchema = object({
14866
14905
  * there is no second switch that can disagree with the first and every rule
14867
14906
  * authored before the decision migrates for free (`audioModeOf`):
14868
14907
  *
14869
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14870
- * classifier labels with one of them. No window, no percentage:
14871
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14872
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14873
- * the analyzer's (`classificationMinScore`, per device) a label only
14874
- * reaches this condition if the classifier was already confident enough.
14908
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14909
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14910
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14911
+ * frames is the wrong question for a classifier that labels 1–3 frames
14912
+ * per episode. The count window is the brake that drops a single-frame
14913
+ * false positive; the rule's own `throttle` cooldown is the other. The
14914
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14915
+ * per device) — a label only reaches this condition if the classifier was
14916
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14875
14917
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14876
14918
  * the condition: at least `hitPercent`% of the samples over
14877
14919
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14898,14 +14940,22 @@ var NcOccupancyConditionSchema = object({
14898
14940
  * an operator who typed `dog` mean the same thing.
14899
14941
  */
14900
14942
  var NcAudioConditionSchema = object({
14901
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14943
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14902
14944
  labels: array(string().min(1)).min(1).optional(),
14903
14945
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14904
14946
  dbThreshold: number().min(-96).max(0).optional(),
14905
14947
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14906
14948
  hitPercent: number().int().min(1).max(100).default(60),
14907
14949
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14908
- samplingSeconds: number().int().min(1).max(300).default(10)
14950
+ samplingSeconds: number().int().min(1).max(300).default(10),
14951
+ /**
14952
+ * LABEL MODE: how many labelled frames must land inside
14953
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14954
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14955
+ */
14956
+ confirmHits: number().int().min(1).max(20).optional(),
14957
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14958
+ confirmWindowSec: number().int().min(1).max(60).optional()
14909
14959
  });
14910
14960
  /**
14911
14961
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17279,6 +17329,46 @@ var RecentTracksPageSchema = object({
17279
17329
  /** Cursor for the next page, or null when this page is the last. */
17280
17330
  nextCursor: string().nullable()
17281
17331
  });
17332
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17333
+ var LIST_GROUPS_MAX_LIMIT = 100;
17334
+ var AnalyticsGroupRecordSchema = object({
17335
+ id: string(),
17336
+ deviceId: number().int(),
17337
+ openedAt: number().int(),
17338
+ closedAt: number().int(),
17339
+ timestamp: number().int(),
17340
+ memberCount: number().int(),
17341
+ memberTrackIds: array(string()).readonly(),
17342
+ className: string(),
17343
+ classes: array(string()).readonly(),
17344
+ /** Relative event-media path, or null when the group has no picture yet. */
17345
+ mediaUrl: string().nullable(),
17346
+ singleton: boolean()
17347
+ });
17348
+ var AnalyticsGroupMemberSchema = object({
17349
+ trackId: string(),
17350
+ deviceId: number().int(),
17351
+ className: string(),
17352
+ firstSeen: number().int(),
17353
+ lastSeen: number().int(),
17354
+ mediaUrl: string().nullable()
17355
+ });
17356
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17357
+ var ListGroupsQueryInput = object({
17358
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17359
+ deviceIds: array(number()),
17360
+ /** Window lower bound on `closedAt` (inclusive). */
17361
+ since: number().optional(),
17362
+ /** Window upper bound on `openedAt` (inclusive). */
17363
+ until: number().optional(),
17364
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17365
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17366
+ cursor: string().optional()
17367
+ });
17368
+ var ListGroupsPageSchema = object({
17369
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17370
+ nextCursor: string().nullable()
17371
+ });
17282
17372
  var KeyEventQueryInput = object({
17283
17373
  deviceId: number(),
17284
17374
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17354,7 +17444,9 @@ var TrackCascadeCountsSchema = object({
17354
17444
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17355
17445
  plates: number().int(),
17356
17446
  /** Per-track CLIP search vectors removed (best-effort). */
17357
- embeddings: number().int()
17447
+ embeddings: number().int(),
17448
+ /** Group membership + group rows removed with their last member (best-effort). */
17449
+ groups: number().int()
17358
17450
  });
17359
17451
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17360
17452
  var DiskReconcileCountsSchema = object({
@@ -17500,7 +17592,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17500
17592
  * stationary registry). Default false: the timeline lists passages,
17501
17593
  * not parking records (operator decision, 2026-08-15). */
17502
17594
  includeStationary: boolean().optional()
17503
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17595
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17596
+ deviceId: number(),
17597
+ groupId: string().min(1)
17598
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17504
17599
  kind: "mutation",
17505
17600
  auth: "admin"
17506
17601
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -17810,7 +17905,8 @@ var PipelineModelOptionSchema = object({
17810
17905
  sizeMB: number()
17811
17906
  })),
17812
17907
  group: ModelVariantGroupSchema.optional(),
17813
- legacy: boolean().optional()
17908
+ legacy: boolean().optional(),
17909
+ provider: ModelProviderIdSchema.optional()
17814
17910
  });
17815
17911
  var ConfigFieldBridge = custom();
17816
17912
  var PipelineAddonSchemaSchema = object({
@@ -26057,7 +26153,12 @@ var PlateInfoSchema = object({
26057
26153
  plateBbox: BoundingBoxSchema.optional(),
26058
26154
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
26059
26155
  keyFrameMediaKey: string().optional(),
26060
- base64: string().optional()
26156
+ base64: string().optional(),
26157
+ /**
26158
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
26159
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
26160
+ */
26161
+ cropUrl: string().optional()
26061
26162
  });
26062
26163
  var MediaFileLiteSchema = object({
26063
26164
  key: string(),
@@ -32099,6 +32200,12 @@ Object.freeze({
32099
32200
  addonId: null,
32100
32201
  access: "view"
32101
32202
  },
32203
+ "deviceManager.getBindingsBatch": {
32204
+ capName: "device-manager",
32205
+ capScope: "system",
32206
+ addonId: null,
32207
+ access: "view"
32208
+ },
32102
32209
  "deviceManager.getChildren": {
32103
32210
  capName: "device-manager",
32104
32211
  capScope: "system",
@@ -32159,6 +32266,12 @@ Object.freeze({
32159
32266
  addonId: null,
32160
32267
  access: "view"
32161
32268
  },
32269
+ "deviceManager.getLinkedDevicesBatch": {
32270
+ capName: "device-manager",
32271
+ capScope: "system",
32272
+ addonId: null,
32273
+ access: "view"
32274
+ },
32162
32275
  "deviceManager.getRoleDisplayDefaults": {
32163
32276
  capName: "device-manager",
32164
32277
  capScope: "system",
@@ -33929,6 +34042,12 @@ Object.freeze({
33929
34042
  addonId: null,
33930
34043
  access: "view"
33931
34044
  },
34045
+ "pipelineAnalytics.getGroup": {
34046
+ capName: "pipeline-analytics",
34047
+ capScope: "device",
34048
+ addonId: null,
34049
+ access: "view"
34050
+ },
33932
34051
  "pipelineAnalytics.getKeyEvents": {
33933
34052
  capName: "pipeline-analytics",
33934
34053
  capScope: "device",
@@ -34013,6 +34132,12 @@ Object.freeze({
34013
34132
  addonId: null,
34014
34133
  access: "view"
34015
34134
  },
34135
+ "pipelineAnalytics.listGroups": {
34136
+ capName: "pipeline-analytics",
34137
+ capScope: "device",
34138
+ addonId: null,
34139
+ access: "view"
34140
+ },
34016
34141
  "pipelineAnalytics.listOpsLog": {
34017
34142
  capName: "pipeline-analytics",
34018
34143
  capScope: "device",
@@ -36794,6 +36919,11 @@ Object.freeze({
36794
36919
  form: "single",
36795
36920
  optional: false
36796
36921
  }],
36922
+ "deviceManager.getBindingsBatch": [{
36923
+ name: "deviceIds",
36924
+ form: "array",
36925
+ optional: false
36926
+ }],
36797
36927
  "deviceManager.getChildren": [{
36798
36928
  name: "parentDeviceId",
36799
36929
  form: "single",
@@ -36839,6 +36969,11 @@ Object.freeze({
36839
36969
  form: "single",
36840
36970
  optional: false
36841
36971
  }],
36972
+ "deviceManager.getLinkedDevicesBatch": [{
36973
+ name: "deviceIds",
36974
+ form: "array",
36975
+ optional: false
36976
+ }],
36842
36977
  "deviceManager.getSettingsSchema": [{
36843
36978
  name: "deviceId",
36844
36979
  form: "single",
@@ -36859,6 +36994,11 @@ Object.freeze({
36859
36994
  form: "single",
36860
36995
  optional: false
36861
36996
  }],
36997
+ "deviceManager.listAll": [{
36998
+ name: "deviceIds",
36999
+ form: "array",
37000
+ optional: true
37001
+ }],
36862
37002
  "deviceManager.loadConfig": [{
36863
37003
  name: "deviceId",
36864
37004
  form: "single",
@@ -37432,6 +37572,11 @@ Object.freeze({
37432
37572
  form: "single",
37433
37573
  optional: false
37434
37574
  }],
37575
+ "pipelineAnalytics.getGroup": [{
37576
+ name: "deviceId",
37577
+ form: "single",
37578
+ optional: false
37579
+ }],
37435
37580
  "pipelineAnalytics.getKeyEvents": [{
37436
37581
  name: "deviceId",
37437
37582
  form: "single",
@@ -37487,6 +37632,11 @@ Object.freeze({
37487
37632
  form: "array",
37488
37633
  optional: false
37489
37634
  }],
37635
+ "pipelineAnalytics.listGroups": [{
37636
+ name: "deviceIds",
37637
+ form: "array",
37638
+ optional: false
37639
+ }],
37490
37640
  "pipelineAnalytics.listOpsLog": [{
37491
37641
  name: "deviceId",
37492
37642
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5809,6 +5809,13 @@ var BaseAddon = class {
5809
5809
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5810
5810
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5811
5811
  _registeredCapNames = [];
5812
+ /**
5813
+ * True only after `readAddonStore` actually answered. Constructor
5814
+ * defaults look like stored config when the store is down — a forked
5815
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5816
+ * mode, 2026-08-25) is not "the operator chose this".
5817
+ */
5818
+ settingsStoreReady = false;
5812
5819
  /** Default config values. Provided via constructor. */
5813
5820
  defaults;
5814
5821
  constructor(defaults) {
@@ -6209,7 +6216,9 @@ var BaseAddon = class {
6209
6216
  ];
6210
6217
  let lastErr;
6211
6218
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6212
- return await settings.readAddonStore() ?? {};
6219
+ const stored = await settings.readAddonStore() ?? {};
6220
+ this.settingsStoreReady = true;
6221
+ return stored;
6213
6222
  } catch (err) {
6214
6223
  lastErr = err;
6215
6224
  const msg = err instanceof Error ? err.message : String(err);
@@ -6217,6 +6226,7 @@ var BaseAddon = class {
6217
6226
  if (attempt === delaysMs.length) break;
6218
6227
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6219
6228
  }
6229
+ this.settingsStoreReady = false;
6220
6230
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6221
6231
  return {};
6222
6232
  }
@@ -8283,6 +8293,12 @@ var ModelVariantGroupSchema = object({
8283
8293
  */
8284
8294
  resolution: number().int().positive().optional()
8285
8295
  });
8296
+ var ModelProviderIdSchema = _enum([
8297
+ "camstack",
8298
+ "frigate",
8299
+ "scrypted",
8300
+ "custom"
8301
+ ]);
8286
8302
  var ModelCatalogEntrySchema = object({
8287
8303
  id: string(),
8288
8304
  name: string(),
@@ -8380,6 +8396,12 @@ var ModelCatalogEntrySchema = object({
8380
8396
  */
8381
8397
  group: ModelVariantGroupSchema.optional(),
8382
8398
  /**
8399
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8400
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8401
+ * persisted before this field existed (`inferModelProvider` fills those).
8402
+ */
8403
+ provider: ModelProviderIdSchema.optional(),
8404
+ /**
8383
8405
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8384
8406
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8385
8407
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -12183,6 +12205,27 @@ var LinkedDeviceSchema = object({
12183
12205
  features: array(string()),
12184
12206
  producesTrackedEvents: boolean().optional()
12185
12207
  });
12208
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12209
+ * The batch answer needs the tag; the single-device answer already has it
12210
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12211
+ var LinkedDevicesForDeviceSchema = object({
12212
+ deviceId: number(),
12213
+ mode: LinkedDevicesModeSchema,
12214
+ devices: array(LinkedDeviceSchema)
12215
+ });
12216
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12217
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12218
+ * object literal is exactly how the three drift apart. */
12219
+ var DeviceBindingsForDeviceSchema = object({
12220
+ deviceId: number(),
12221
+ entries: array(object({
12222
+ capName: string(),
12223
+ kind: _enum(["native", "wrapped"]),
12224
+ providerAddonId: string(),
12225
+ providerNodeId: string(),
12226
+ nativeAddonId: string()
12227
+ }))
12228
+ });
12186
12229
  var SavedDeviceRowSchema = object({
12187
12230
  /** Numeric id reserved at allocateDeviceId time. */
12188
12231
  id: number(),
@@ -12408,11 +12451,25 @@ method(object({
12408
12451
  projection: _enum(["full", "slim"]).optional(),
12409
12452
  /** Return only camera devices. Filtering server-side instead of
12410
12453
  * shipping 293 rows to find 12. */
12411
- isCamera: boolean().optional()
12454
+ isCamera: boolean().optional(),
12455
+ /**
12456
+ * Return only these device ids. For the caller that already KNOWS the
12457
+ * handful it wants and needs a field the id-bearing answer does not
12458
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12459
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12460
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12461
+ * refetches on the reconcile interval, on a phone.
12462
+ *
12463
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12464
+ * keys rather than rejecting them (verified against the live hub
12465
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12466
+ * it answers today and the caller filters as it already does.
12467
+ */
12468
+ deviceIds: array(number()).optional()
12412
12469
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12413
12470
  mode: LinkedDevicesModeSchema,
12414
12471
  devices: array(LinkedDeviceSchema)
12415
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12472
+ })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12416
12473
  deviceId: number(),
12417
12474
  values: record(string(), unknown())
12418
12475
  }), object({ success: literal(true) }), {
@@ -12439,25 +12496,7 @@ method(object({
12439
12496
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12440
12497
  kind: "mutation",
12441
12498
  auth: "admin"
12442
- }), method(object({ deviceId: number() }), object({
12443
- deviceId: number(),
12444
- entries: array(object({
12445
- capName: string(),
12446
- kind: _enum(["native", "wrapped"]),
12447
- providerAddonId: string(),
12448
- providerNodeId: string(),
12449
- nativeAddonId: string()
12450
- }))
12451
- })), method(object({}), array(object({
12452
- deviceId: number(),
12453
- entries: array(object({
12454
- capName: string(),
12455
- kind: _enum(["native", "wrapped"]),
12456
- providerAddonId: string(),
12457
- providerNodeId: string(),
12458
- nativeAddonId: string()
12459
- }))
12460
- }))), method(object({
12499
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12461
12500
  deviceId: number(),
12462
12501
  capName: string(),
12463
12502
  wrapperAddonId: string(),
@@ -14867,12 +14906,15 @@ var NcOccupancyConditionSchema = object({
14867
14906
  * there is no second switch that can disagree with the first and every rule
14868
14907
  * authored before the decision migrates for free (`audioModeOf`):
14869
14908
  *
14870
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14871
- * classifier labels with one of them. No window, no percentage:
14872
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14873
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14874
- * the analyzer's (`classificationMinScore`, per device) a label only
14875
- * reaches this condition if the classifier was already confident enough.
14909
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14910
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14911
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14912
+ * frames is the wrong question for a classifier that labels 1–3 frames
14913
+ * per episode. The count window is the brake that drops a single-frame
14914
+ * false positive; the rule's own `throttle` cooldown is the other. The
14915
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14916
+ * per device) — a label only reaches this condition if the classifier was
14917
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14876
14918
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14877
14919
  * the condition: at least `hitPercent`% of the samples over
14878
14920
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14899,14 +14941,22 @@ var NcOccupancyConditionSchema = object({
14899
14941
  * an operator who typed `dog` mean the same thing.
14900
14942
  */
14901
14943
  var NcAudioConditionSchema = object({
14902
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14944
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14903
14945
  labels: array(string().min(1)).min(1).optional(),
14904
14946
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14905
14947
  dbThreshold: number().min(-96).max(0).optional(),
14906
14948
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14907
14949
  hitPercent: number().int().min(1).max(100).default(60),
14908
14950
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14909
- samplingSeconds: number().int().min(1).max(300).default(10)
14951
+ samplingSeconds: number().int().min(1).max(300).default(10),
14952
+ /**
14953
+ * LABEL MODE: how many labelled frames must land inside
14954
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14955
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14956
+ */
14957
+ confirmHits: number().int().min(1).max(20).optional(),
14958
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14959
+ confirmWindowSec: number().int().min(1).max(60).optional()
14910
14960
  });
14911
14961
  /**
14912
14962
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17280,6 +17330,46 @@ var RecentTracksPageSchema = object({
17280
17330
  /** Cursor for the next page, or null when this page is the last. */
17281
17331
  nextCursor: string().nullable()
17282
17332
  });
17333
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17334
+ var LIST_GROUPS_MAX_LIMIT = 100;
17335
+ var AnalyticsGroupRecordSchema = object({
17336
+ id: string(),
17337
+ deviceId: number().int(),
17338
+ openedAt: number().int(),
17339
+ closedAt: number().int(),
17340
+ timestamp: number().int(),
17341
+ memberCount: number().int(),
17342
+ memberTrackIds: array(string()).readonly(),
17343
+ className: string(),
17344
+ classes: array(string()).readonly(),
17345
+ /** Relative event-media path, or null when the group has no picture yet. */
17346
+ mediaUrl: string().nullable(),
17347
+ singleton: boolean()
17348
+ });
17349
+ var AnalyticsGroupMemberSchema = object({
17350
+ trackId: string(),
17351
+ deviceId: number().int(),
17352
+ className: string(),
17353
+ firstSeen: number().int(),
17354
+ lastSeen: number().int(),
17355
+ mediaUrl: string().nullable()
17356
+ });
17357
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17358
+ var ListGroupsQueryInput = object({
17359
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17360
+ deviceIds: array(number()),
17361
+ /** Window lower bound on `closedAt` (inclusive). */
17362
+ since: number().optional(),
17363
+ /** Window upper bound on `openedAt` (inclusive). */
17364
+ until: number().optional(),
17365
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17366
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17367
+ cursor: string().optional()
17368
+ });
17369
+ var ListGroupsPageSchema = object({
17370
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17371
+ nextCursor: string().nullable()
17372
+ });
17283
17373
  var KeyEventQueryInput = object({
17284
17374
  deviceId: number(),
17285
17375
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17355,7 +17445,9 @@ var TrackCascadeCountsSchema = object({
17355
17445
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17356
17446
  plates: number().int(),
17357
17447
  /** Per-track CLIP search vectors removed (best-effort). */
17358
- embeddings: number().int()
17448
+ embeddings: number().int(),
17449
+ /** Group membership + group rows removed with their last member (best-effort). */
17450
+ groups: number().int()
17359
17451
  });
17360
17452
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17361
17453
  var DiskReconcileCountsSchema = object({
@@ -17501,7 +17593,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17501
17593
  * stationary registry). Default false: the timeline lists passages,
17502
17594
  * not parking records (operator decision, 2026-08-15). */
17503
17595
  includeStationary: boolean().optional()
17504
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17596
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17597
+ deviceId: number(),
17598
+ groupId: string().min(1)
17599
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17505
17600
  kind: "mutation",
17506
17601
  auth: "admin"
17507
17602
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -17811,7 +17906,8 @@ var PipelineModelOptionSchema = object({
17811
17906
  sizeMB: number()
17812
17907
  })),
17813
17908
  group: ModelVariantGroupSchema.optional(),
17814
- legacy: boolean().optional()
17909
+ legacy: boolean().optional(),
17910
+ provider: ModelProviderIdSchema.optional()
17815
17911
  });
17816
17912
  var ConfigFieldBridge = custom();
17817
17913
  var PipelineAddonSchemaSchema = object({
@@ -26058,7 +26154,12 @@ var PlateInfoSchema = object({
26058
26154
  plateBbox: BoundingBoxSchema.optional(),
26059
26155
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
26060
26156
  keyFrameMediaKey: string().optional(),
26061
- base64: string().optional()
26157
+ base64: string().optional(),
26158
+ /**
26159
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
26160
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
26161
+ */
26162
+ cropUrl: string().optional()
26062
26163
  });
26063
26164
  var MediaFileLiteSchema = object({
26064
26165
  key: string(),
@@ -32100,6 +32201,12 @@ Object.freeze({
32100
32201
  addonId: null,
32101
32202
  access: "view"
32102
32203
  },
32204
+ "deviceManager.getBindingsBatch": {
32205
+ capName: "device-manager",
32206
+ capScope: "system",
32207
+ addonId: null,
32208
+ access: "view"
32209
+ },
32103
32210
  "deviceManager.getChildren": {
32104
32211
  capName: "device-manager",
32105
32212
  capScope: "system",
@@ -32160,6 +32267,12 @@ Object.freeze({
32160
32267
  addonId: null,
32161
32268
  access: "view"
32162
32269
  },
32270
+ "deviceManager.getLinkedDevicesBatch": {
32271
+ capName: "device-manager",
32272
+ capScope: "system",
32273
+ addonId: null,
32274
+ access: "view"
32275
+ },
32163
32276
  "deviceManager.getRoleDisplayDefaults": {
32164
32277
  capName: "device-manager",
32165
32278
  capScope: "system",
@@ -33930,6 +34043,12 @@ Object.freeze({
33930
34043
  addonId: null,
33931
34044
  access: "view"
33932
34045
  },
34046
+ "pipelineAnalytics.getGroup": {
34047
+ capName: "pipeline-analytics",
34048
+ capScope: "device",
34049
+ addonId: null,
34050
+ access: "view"
34051
+ },
33933
34052
  "pipelineAnalytics.getKeyEvents": {
33934
34053
  capName: "pipeline-analytics",
33935
34054
  capScope: "device",
@@ -34014,6 +34133,12 @@ Object.freeze({
34014
34133
  addonId: null,
34015
34134
  access: "view"
34016
34135
  },
34136
+ "pipelineAnalytics.listGroups": {
34137
+ capName: "pipeline-analytics",
34138
+ capScope: "device",
34139
+ addonId: null,
34140
+ access: "view"
34141
+ },
34017
34142
  "pipelineAnalytics.listOpsLog": {
34018
34143
  capName: "pipeline-analytics",
34019
34144
  capScope: "device",
@@ -36795,6 +36920,11 @@ Object.freeze({
36795
36920
  form: "single",
36796
36921
  optional: false
36797
36922
  }],
36923
+ "deviceManager.getBindingsBatch": [{
36924
+ name: "deviceIds",
36925
+ form: "array",
36926
+ optional: false
36927
+ }],
36798
36928
  "deviceManager.getChildren": [{
36799
36929
  name: "parentDeviceId",
36800
36930
  form: "single",
@@ -36840,6 +36970,11 @@ Object.freeze({
36840
36970
  form: "single",
36841
36971
  optional: false
36842
36972
  }],
36973
+ "deviceManager.getLinkedDevicesBatch": [{
36974
+ name: "deviceIds",
36975
+ form: "array",
36976
+ optional: false
36977
+ }],
36843
36978
  "deviceManager.getSettingsSchema": [{
36844
36979
  name: "deviceId",
36845
36980
  form: "single",
@@ -36860,6 +36995,11 @@ Object.freeze({
36860
36995
  form: "single",
36861
36996
  optional: false
36862
36997
  }],
36998
+ "deviceManager.listAll": [{
36999
+ name: "deviceIds",
37000
+ form: "array",
37001
+ optional: true
37002
+ }],
36863
37003
  "deviceManager.loadConfig": [{
36864
37004
  name: "deviceId",
36865
37005
  form: "single",
@@ -37433,6 +37573,11 @@ Object.freeze({
37433
37573
  form: "single",
37434
37574
  optional: false
37435
37575
  }],
37576
+ "pipelineAnalytics.getGroup": [{
37577
+ name: "deviceId",
37578
+ form: "single",
37579
+ optional: false
37580
+ }],
37436
37581
  "pipelineAnalytics.getKeyEvents": [{
37437
37582
  name: "deviceId",
37438
37583
  form: "single",
@@ -37488,6 +37633,11 @@ Object.freeze({
37488
37633
  form: "array",
37489
37634
  optional: false
37490
37635
  }],
37636
+ "pipelineAnalytics.listGroups": [{
37637
+ name: "deviceIds",
37638
+ form: "array",
37639
+ optional: false
37640
+ }],
37491
37641
  "pipelineAnalytics.listOpsLog": [{
37492
37642
  name: "deviceId",
37493
37643
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.2.34",
3
+ "version": "1.2.35",
4
4
  "description": "Hikvision camera device provider addon for CamStack — ISAPI over HTTP(S) with digest auth (snapshot, alarm stream, RTSP discovery)",
5
5
  "keywords": [
6
6
  "camstack",