@camstack/addon-static-turn 1.2.22 → 1.2.24

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.
@@ -7550,7 +7550,7 @@ var RecordingBandSchema = object({
7550
7550
  postBufferSec: number().min(0).optional()
7551
7551
  });
7552
7552
  ({
7553
- preBufferSec: 10,
7553
+ preBufferSec: 15,
7554
7554
  postBufferSec: 30
7555
7555
  }).postBufferSec * 1e3;
7556
7556
  /**
@@ -13489,128 +13489,103 @@ var COCO_TO_MACRO = {
13489
13489
  var AUDIO_MACRO_LABELS = [
13490
13490
  {
13491
13491
  id: "speech",
13492
- name: "Speech",
13493
- icon: "🗣️"
13492
+ name: "Speech"
13494
13493
  },
13495
13494
  {
13496
13495
  id: "scream",
13497
- name: "Scream / Shout",
13498
- icon: "😱"
13496
+ name: "Scream / Shout"
13499
13497
  },
13500
13498
  {
13501
13499
  id: "crying",
13502
- name: "Crying / Baby",
13503
- icon: "😢"
13500
+ name: "Crying / Baby"
13504
13501
  },
13505
13502
  {
13506
13503
  id: "laughter",
13507
- name: "Laughter",
13508
- icon: "😂"
13504
+ name: "Laughter"
13509
13505
  },
13510
13506
  {
13511
13507
  id: "music",
13512
- name: "Music",
13513
- icon: "🎵"
13508
+ name: "Music"
13514
13509
  },
13515
13510
  {
13516
13511
  id: "dog",
13517
- name: "Dog",
13518
- icon: "🐕"
13512
+ name: "Dog"
13519
13513
  },
13520
13514
  {
13521
13515
  id: "cat",
13522
- name: "Cat",
13523
- icon: "🐈"
13516
+ name: "Cat"
13524
13517
  },
13525
13518
  {
13526
13519
  id: "bird",
13527
- name: "Bird",
13528
- icon: "🐦"
13520
+ name: "Bird"
13529
13521
  },
13530
13522
  {
13531
13523
  id: "animal",
13532
- name: "Animal (other)",
13533
- icon: "🐾"
13524
+ name: "Animal (other)"
13534
13525
  },
13535
13526
  {
13536
13527
  id: "alarm",
13537
- name: "Alarm / Siren",
13538
- icon: "🚨"
13528
+ name: "Alarm / Siren"
13539
13529
  },
13540
13530
  {
13541
13531
  id: "doorbell",
13542
- name: "Doorbell / Knock",
13543
- icon: "🔔"
13532
+ name: "Doorbell / Knock"
13544
13533
  },
13545
13534
  {
13546
13535
  id: "glass_breaking",
13547
- name: "Glass Breaking",
13548
- icon: "💥"
13536
+ name: "Glass Breaking"
13549
13537
  },
13550
13538
  {
13551
13539
  id: "gunshot",
13552
- name: "Gunshot / Explosion",
13553
- icon: "💣"
13540
+ name: "Gunshot / Explosion"
13554
13541
  },
13555
13542
  {
13556
13543
  id: "vehicle",
13557
- name: "Vehicle",
13558
- icon: "🚗"
13544
+ name: "Vehicle"
13559
13545
  },
13560
13546
  {
13561
13547
  id: "siren",
13562
- name: "Emergency Siren",
13563
- icon: "🚑"
13548
+ name: "Emergency Siren"
13564
13549
  },
13565
13550
  {
13566
13551
  id: "fire",
13567
- name: "Fire / Smoke",
13568
- icon: "🔥"
13552
+ name: "Fire / Smoke"
13569
13553
  },
13570
13554
  {
13571
13555
  id: "water",
13572
- name: "Water",
13573
- icon: "💧"
13556
+ name: "Water"
13574
13557
  },
13575
13558
  {
13576
13559
  id: "wind",
13577
- name: "Wind / Weather",
13578
- icon: "🌬️"
13560
+ name: "Wind / Weather"
13579
13561
  },
13580
13562
  {
13581
13563
  id: "door",
13582
- name: "Door",
13583
- icon: "🚪"
13564
+ name: "Door"
13584
13565
  },
13585
13566
  {
13586
13567
  id: "footsteps",
13587
- name: "Footsteps",
13588
- icon: "👣"
13568
+ name: "Footsteps"
13589
13569
  },
13590
13570
  {
13591
13571
  id: "crowd",
13592
- name: "Crowd / Chatter",
13593
- icon: "👥"
13572
+ name: "Crowd / Chatter"
13594
13573
  },
13595
13574
  {
13596
13575
  id: "telephone",
13597
- name: "Telephone",
13598
- icon: "📞"
13576
+ name: "Telephone"
13599
13577
  },
13600
13578
  {
13601
13579
  id: "engine",
13602
- name: "Engine / Motor",
13603
- icon: "⚙️"
13580
+ name: "Engine / Motor"
13604
13581
  },
13605
13582
  {
13606
13583
  id: "tools",
13607
- name: "Tools / Construction",
13608
- icon: "🔨"
13584
+ name: "Tools / Construction"
13609
13585
  },
13610
13586
  {
13611
13587
  id: "silence",
13612
- name: "Silence",
13613
- icon: "🤫"
13588
+ name: "Silence"
13614
13589
  }
13615
13590
  ];
13616
13591
  var YAMNET_TO_MACRO = {
@@ -16074,7 +16049,7 @@ var TrackEnvelopeSchema = object({
16074
16049
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16075
16050
  * keeps every scalar the list surfaces actually render (ids, class(es),
16076
16051
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16077
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16052
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16078
16053
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16079
16054
  * `getTrack`. Mirrors the event-store `projection` convention
16080
16055
  * (`getObjectEvents` et al.).
@@ -16369,6 +16344,35 @@ var TrackSchema = object({
16369
16344
  */
16370
16345
  hasFace: boolean().optional(),
16371
16346
  /**
16347
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16348
+ * face an operator could ASSIGN to an identity.
16349
+ *
16350
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16351
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16352
+ * branch, before every gate, and means no more than "a face detector produced
16353
+ * a face detail". This one is stamped at the single moment the gallery row
16354
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16355
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16356
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16357
+ * the crop-store drop. Everything between the detector and that insert can
16358
+ * legitimately refuse the face, so a flag written any earlier promises the
16359
+ * operator something to assign and delivers nothing.
16360
+ *
16361
+ * **Independent of recognition.** A face collected but never auto-matched is
16362
+ * still assignable — it is in fact the face an operator most wants to reach —
16363
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16364
+ * `subLabel`; this says only that the raw material exists.
16365
+ *
16366
+ * **Set once, never cleared.** A track that produced a gallery row produced
16367
+ * one; deleting the row later is the gallery's business, not this flag's.
16368
+ *
16369
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16370
+ * before the column omits it, and so does every server that predates the
16371
+ * field. A consumer must test `=== true` and render nothing otherwise —
16372
+ * never infer "no assignable face".
16373
+ */
16374
+ hasEmbeddedFace: boolean().optional(),
16375
+ /**
16372
16376
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16373
16377
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16374
16378
  * so the passage is tracked once and as a VEHICLE.
@@ -17718,6 +17722,32 @@ var DetailResultSchema = object({
17718
17722
  /** FRAME-space bbox (already mapped back from crop space). */
17719
17723
  bbox: NativeCropBboxSchema.optional(),
17720
17724
  embedding: string().optional(),
17725
+ /**
17726
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
17727
+ * so an older runner that does not send it degrades to "unmeasurable", which
17728
+ * the consuming gate treats as accept — never as reject.
17729
+ */
17730
+ embeddingMagnitude: number().optional(),
17731
+ /**
17732
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
17733
+ * `label`, and for the same reason: the producing node is the only side that
17734
+ * knows which model actually ran, and re-deriving it downstream is exactly
17735
+ * the inference this model exists to forbid.
17736
+ *
17737
+ * Additive and optional, so a runner that predates it degrades to
17738
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
17739
+ * its previous assumption instead of refusing.
17740
+ *
17741
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
17742
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
17743
+ * with nothing on the wire it took the id from the GALLERY, so the gate
17744
+ * compared the gallery against itself and could never fire. That turned the
17745
+ * one guard protecting the face feature space into a tautology, and it is
17746
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
17747
+ * gallery as the catalog comment predicts — it would have scored the new
17748
+ * model's vectors against the old model's samples in one cosine space.
17749
+ */
17750
+ embeddingModelId: string().optional(),
17721
17751
  label: string().optional(),
17722
17752
  /**
17723
17753
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -18593,6 +18623,12 @@ var CameraStatusSchema = object({
18593
18623
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
18594
18624
  fetchedAt: number()
18595
18625
  });
18626
+ var InferenceDeviceExclusionReasonSchema = _enum([
18627
+ "disabled",
18628
+ "unavailable",
18629
+ "cannot-host-camera-root",
18630
+ "accelerator-preferred"
18631
+ ]);
18596
18632
  var NodeInferenceDeviceSchema = object({
18597
18633
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
18598
18634
  key: string(),
@@ -18623,7 +18659,17 @@ var NodeInferenceDeviceSchema = object({
18623
18659
  * available per format; this is the stored selection that becomes the
18624
18660
  * default for EVERY camera landing on this accelerator.
18625
18661
  */
18626
- steps: record(string(), DeviceStepConfigSchema).optional()
18662
+ steps: record(string(), DeviceStepConfigSchema).optional(),
18663
+ /**
18664
+ * `null` when the device IS a camera-root candidate on this node; otherwise
18665
+ * the reason the dispatcher drops it. Computed by the SAME
18666
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
18667
+ * never disagree with the election — deriving it in the UI from
18668
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
18669
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
18670
+ * "an accelerator is serving" predicate).
18671
+ */
18672
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
18627
18673
  });
18628
18674
  var NodeInferenceDevicesSchema = object({
18629
18675
  nodeId: string(),
@@ -19128,6 +19174,7 @@ DeviceType.Camera, method(object({
19128
19174
  isBattery: boolean(),
19129
19175
  reason: _enum([
19130
19176
  "disabled",
19177
+ "offline",
19131
19178
  "sleeping",
19132
19179
  "unreachable",
19133
19180
  "waking"
@@ -19173,9 +19220,17 @@ targets: array(object({
19173
19220
  sleeping: boolean(),
19174
19221
  /** Current device state rendered over the cached frame. State images
19175
19222
  * remain authoritative even when their photographic background is
19176
- * old; null means the link must carry a current camera frame. */
19223
+ * old; null means the link must carry a current camera frame.
19224
+ *
19225
+ * **This is the ONLY source a surface may derive an inactive tile
19226
+ * from.** A client that reads a device's `online`/`disabled` flags and
19227
+ * draws its own verdict will disagree with the pixels it is painting
19228
+ * — the frame comes from here, and so must the sentence over it
19229
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19230
+ * state. */
19177
19231
  stateReason: _enum([
19178
19232
  "disabled",
19233
+ "offline",
19179
19234
  "sleeping",
19180
19235
  "unreachable",
19181
19236
  "waking"
@@ -22395,7 +22450,12 @@ var ListResultSchema = object({
22395
22450
  probedAt: number()
22396
22451
  });
22397
22452
  var PreferredSchema = LocalInterfaceSchema.nullable();
22398
- var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
22453
+ /**
22454
+ * Candidate base URL for the SDK to race on connect. Order matters —
22455
+ * the SDK should attempt these top-to-bottom with a short per-candidate
22456
+ * timeout (e.g. 1500ms) and cache the winner for the session.
22457
+ */
22458
+ var ConnectionEndpointSchema = object({
22399
22459
  /** Operator-facing label (e.g. "LAN — en0", "Public tunnel"). */
22400
22460
  label: string(),
22401
22461
  /** Fully-formed base URL with scheme + host + port. */
@@ -22438,7 +22498,42 @@ var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
22438
22498
  * ordering between polls.
22439
22499
  */
22440
22500
  priority: number()
22441
- })).readonly() });
22501
+ });
22502
+ /**
22503
+ * Where the advertised local port came from. Ordered most → least
22504
+ * authoritative, and the whole point of returning it: a client must be able to
22505
+ * tell a FACT about the hub's socket from an echo of its own guess.
22506
+ */
22507
+ var LocalPortSourceEnum = _enum([
22508
+ "server-config",
22509
+ "server-env",
22510
+ "caller-hint",
22511
+ "default"
22512
+ ]);
22513
+ /** The port every LAN/loopback `baseUrl` in the same result was built with. */
22514
+ var AdvertisedLocalPortSchema = object({
22515
+ port: number().int().min(1).max(65535),
22516
+ source: LocalPortSourceEnum
22517
+ });
22518
+ var GetConnectionEndpointsResultSchema = object({
22519
+ endpoints: array(ConnectionEndpointSchema).readonly(),
22520
+ /**
22521
+ * The port the hub built the LAN/loopback URLs with, and where that number
22522
+ * came from.
22523
+ *
22524
+ * Returned rather than merely applied, because "the URL is right" and "the
22525
+ * client can KNOW the URL is right" are different properties. A client that
22526
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
22527
+ * a hub that echoed the port the client sent, so it cannot decide whether to
22528
+ * race the candidate or discard it. With `source` it can: anything but
22529
+ * `caller-hint` is the hub's own socket.
22530
+ *
22531
+ * Absent on hubs predating this field — a client that finds it missing is
22532
+ * talking to an echoing hub and must degrade exactly as it does for
22533
+ * `caller-hint`.
22534
+ */
22535
+ localPort: AdvertisedLocalPortSchema
22536
+ });
22442
22537
  /**
22443
22538
  * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
22444
22539
  * AUTO (resolved from the candidate ranking at send time); `resolved` reports
@@ -22459,8 +22554,13 @@ var AllowedAddressesSchema = object({
22459
22554
  */
22460
22555
  addresses: array(string()).readonly() });
22461
22556
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22462
- /** Local hub HTTP port to use in base URLs. */
22463
- port: number().int().min(1).max(65535),
22557
+ /**
22558
+ * LEGACY HINT — do not send from new code. Kept optional so clients
22559
+ * written against the echoing contract keep working; the hub uses it
22560
+ * only when it cannot read its own port, and says so via
22561
+ * `localPort.source === 'caller-hint'`.
22562
+ */
22563
+ port: number().int().min(1).max(65535).optional(),
22464
22564
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22465
22565
  * candidate. Default `true`. */
22466
22566
  includeLoopback: boolean().optional(),
@@ -33384,7 +33484,6 @@ var NC_AUDIO_DEFAULTS = {
33384
33484
  hitPercent: 60,
33385
33485
  samplingSeconds: 10
33386
33486
  };
33387
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
33388
33487
  new Set(["devices", "classes"]);
33389
33488
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
33390
33489
  /**
@@ -33618,6 +33717,39 @@ TimelapseRuleInputSchema.extend({
33618
33717
  createdAt: number(),
33619
33718
  updatedAt: number()
33620
33719
  });
33720
+ Object.freeze(Object.fromEntries([{
33721
+ stepId: "face-embedding",
33722
+ label: "Face recognition model",
33723
+ defaultModelId: "arcface-r100",
33724
+ indexName: "the enrolled face gallery",
33725
+ options: [
33726
+ {
33727
+ id: "arcface-r100",
33728
+ label: "ArcFace (ResNet34)"
33729
+ },
33730
+ {
33731
+ id: "auraface-r100",
33732
+ label: "AuraFace R100"
33733
+ },
33734
+ {
33735
+ id: "inception-resnet-v1",
33736
+ label: "Inception ResNet V1"
33737
+ }
33738
+ ]
33739
+ }, {
33740
+ stepId: "clip-embedding",
33741
+ label: "Semantic search model",
33742
+ defaultModelId: "mobileclip-s1",
33743
+ indexName: "the object semantic-search index",
33744
+ options: [{
33745
+ id: "mobileclip-s1",
33746
+ label: "MobileCLIP S1"
33747
+ }, {
33748
+ id: "mobileclip-s2",
33749
+ label: "MobileCLIP S2"
33750
+ }]
33751
+ }].map((s) => [s.stepId, s.defaultModelId])));
33752
+ string().min(1);
33621
33753
  object({
33622
33754
  /**
33623
33755
  * Fraction of the box's own size added on EACH side before cutting.
@@ -33638,10 +33770,21 @@ object({
33638
33770
  */
33639
33771
  square: boolean()
33640
33772
  });
33641
- ({
33773
+ /**
33774
+ * The convention in force when nobody has configured one — byte-for-byte the
33775
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
33776
+ */
33777
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
33642
33778
  paddingRatio: .15,
33643
33779
  square: false
33644
- }).paddingRatio;
33780
+ };
33781
+ Object.freeze({
33782
+ x: 0,
33783
+ y: 0,
33784
+ w: 1,
33785
+ h: 1
33786
+ });
33787
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
33645
33788
  /**
33646
33789
  * WHICH delivered frames the decode worker retains a native copy of.
33647
33790
  *
@@ -7549,7 +7549,7 @@ var RecordingBandSchema = object({
7549
7549
  postBufferSec: number().min(0).optional()
7550
7550
  });
7551
7551
  ({
7552
- preBufferSec: 10,
7552
+ preBufferSec: 15,
7553
7553
  postBufferSec: 30
7554
7554
  }).postBufferSec * 1e3;
7555
7555
  /**
@@ -13488,128 +13488,103 @@ var COCO_TO_MACRO = {
13488
13488
  var AUDIO_MACRO_LABELS = [
13489
13489
  {
13490
13490
  id: "speech",
13491
- name: "Speech",
13492
- icon: "🗣️"
13491
+ name: "Speech"
13493
13492
  },
13494
13493
  {
13495
13494
  id: "scream",
13496
- name: "Scream / Shout",
13497
- icon: "😱"
13495
+ name: "Scream / Shout"
13498
13496
  },
13499
13497
  {
13500
13498
  id: "crying",
13501
- name: "Crying / Baby",
13502
- icon: "😢"
13499
+ name: "Crying / Baby"
13503
13500
  },
13504
13501
  {
13505
13502
  id: "laughter",
13506
- name: "Laughter",
13507
- icon: "😂"
13503
+ name: "Laughter"
13508
13504
  },
13509
13505
  {
13510
13506
  id: "music",
13511
- name: "Music",
13512
- icon: "🎵"
13507
+ name: "Music"
13513
13508
  },
13514
13509
  {
13515
13510
  id: "dog",
13516
- name: "Dog",
13517
- icon: "🐕"
13511
+ name: "Dog"
13518
13512
  },
13519
13513
  {
13520
13514
  id: "cat",
13521
- name: "Cat",
13522
- icon: "🐈"
13515
+ name: "Cat"
13523
13516
  },
13524
13517
  {
13525
13518
  id: "bird",
13526
- name: "Bird",
13527
- icon: "🐦"
13519
+ name: "Bird"
13528
13520
  },
13529
13521
  {
13530
13522
  id: "animal",
13531
- name: "Animal (other)",
13532
- icon: "🐾"
13523
+ name: "Animal (other)"
13533
13524
  },
13534
13525
  {
13535
13526
  id: "alarm",
13536
- name: "Alarm / Siren",
13537
- icon: "🚨"
13527
+ name: "Alarm / Siren"
13538
13528
  },
13539
13529
  {
13540
13530
  id: "doorbell",
13541
- name: "Doorbell / Knock",
13542
- icon: "🔔"
13531
+ name: "Doorbell / Knock"
13543
13532
  },
13544
13533
  {
13545
13534
  id: "glass_breaking",
13546
- name: "Glass Breaking",
13547
- icon: "💥"
13535
+ name: "Glass Breaking"
13548
13536
  },
13549
13537
  {
13550
13538
  id: "gunshot",
13551
- name: "Gunshot / Explosion",
13552
- icon: "💣"
13539
+ name: "Gunshot / Explosion"
13553
13540
  },
13554
13541
  {
13555
13542
  id: "vehicle",
13556
- name: "Vehicle",
13557
- icon: "🚗"
13543
+ name: "Vehicle"
13558
13544
  },
13559
13545
  {
13560
13546
  id: "siren",
13561
- name: "Emergency Siren",
13562
- icon: "🚑"
13547
+ name: "Emergency Siren"
13563
13548
  },
13564
13549
  {
13565
13550
  id: "fire",
13566
- name: "Fire / Smoke",
13567
- icon: "🔥"
13551
+ name: "Fire / Smoke"
13568
13552
  },
13569
13553
  {
13570
13554
  id: "water",
13571
- name: "Water",
13572
- icon: "💧"
13555
+ name: "Water"
13573
13556
  },
13574
13557
  {
13575
13558
  id: "wind",
13576
- name: "Wind / Weather",
13577
- icon: "🌬️"
13559
+ name: "Wind / Weather"
13578
13560
  },
13579
13561
  {
13580
13562
  id: "door",
13581
- name: "Door",
13582
- icon: "🚪"
13563
+ name: "Door"
13583
13564
  },
13584
13565
  {
13585
13566
  id: "footsteps",
13586
- name: "Footsteps",
13587
- icon: "👣"
13567
+ name: "Footsteps"
13588
13568
  },
13589
13569
  {
13590
13570
  id: "crowd",
13591
- name: "Crowd / Chatter",
13592
- icon: "👥"
13571
+ name: "Crowd / Chatter"
13593
13572
  },
13594
13573
  {
13595
13574
  id: "telephone",
13596
- name: "Telephone",
13597
- icon: "📞"
13575
+ name: "Telephone"
13598
13576
  },
13599
13577
  {
13600
13578
  id: "engine",
13601
- name: "Engine / Motor",
13602
- icon: "⚙️"
13579
+ name: "Engine / Motor"
13603
13580
  },
13604
13581
  {
13605
13582
  id: "tools",
13606
- name: "Tools / Construction",
13607
- icon: "🔨"
13583
+ name: "Tools / Construction"
13608
13584
  },
13609
13585
  {
13610
13586
  id: "silence",
13611
- name: "Silence",
13612
- icon: "🤫"
13587
+ name: "Silence"
13613
13588
  }
13614
13589
  ];
13615
13590
  var YAMNET_TO_MACRO = {
@@ -16073,7 +16048,7 @@ var TrackEnvelopeSchema = object({
16073
16048
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16074
16049
  * keeps every scalar the list surfaces actually render (ids, class(es),
16075
16050
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16076
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16051
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16077
16052
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16078
16053
  * `getTrack`. Mirrors the event-store `projection` convention
16079
16054
  * (`getObjectEvents` et al.).
@@ -16368,6 +16343,35 @@ var TrackSchema = object({
16368
16343
  */
16369
16344
  hasFace: boolean().optional(),
16370
16345
  /**
16346
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16347
+ * face an operator could ASSIGN to an identity.
16348
+ *
16349
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16350
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16351
+ * branch, before every gate, and means no more than "a face detector produced
16352
+ * a face detail". This one is stamped at the single moment the gallery row
16353
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16354
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16355
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16356
+ * the crop-store drop. Everything between the detector and that insert can
16357
+ * legitimately refuse the face, so a flag written any earlier promises the
16358
+ * operator something to assign and delivers nothing.
16359
+ *
16360
+ * **Independent of recognition.** A face collected but never auto-matched is
16361
+ * still assignable — it is in fact the face an operator most wants to reach —
16362
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16363
+ * `subLabel`; this says only that the raw material exists.
16364
+ *
16365
+ * **Set once, never cleared.** A track that produced a gallery row produced
16366
+ * one; deleting the row later is the gallery's business, not this flag's.
16367
+ *
16368
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16369
+ * before the column omits it, and so does every server that predates the
16370
+ * field. A consumer must test `=== true` and render nothing otherwise —
16371
+ * never infer "no assignable face".
16372
+ */
16373
+ hasEmbeddedFace: boolean().optional(),
16374
+ /**
16371
16375
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16372
16376
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16373
16377
  * so the passage is tracked once and as a VEHICLE.
@@ -17717,6 +17721,32 @@ var DetailResultSchema = object({
17717
17721
  /** FRAME-space bbox (already mapped back from crop space). */
17718
17722
  bbox: NativeCropBboxSchema.optional(),
17719
17723
  embedding: string().optional(),
17724
+ /**
17725
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
17726
+ * so an older runner that does not send it degrades to "unmeasurable", which
17727
+ * the consuming gate treats as accept — never as reject.
17728
+ */
17729
+ embeddingMagnitude: number().optional(),
17730
+ /**
17731
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
17732
+ * `label`, and for the same reason: the producing node is the only side that
17733
+ * knows which model actually ran, and re-deriving it downstream is exactly
17734
+ * the inference this model exists to forbid.
17735
+ *
17736
+ * Additive and optional, so a runner that predates it degrades to
17737
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
17738
+ * its previous assumption instead of refusing.
17739
+ *
17740
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
17741
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
17742
+ * with nothing on the wire it took the id from the GALLERY, so the gate
17743
+ * compared the gallery against itself and could never fire. That turned the
17744
+ * one guard protecting the face feature space into a tautology, and it is
17745
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
17746
+ * gallery as the catalog comment predicts — it would have scored the new
17747
+ * model's vectors against the old model's samples in one cosine space.
17748
+ */
17749
+ embeddingModelId: string().optional(),
17720
17750
  label: string().optional(),
17721
17751
  /**
17722
17752
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -18592,6 +18622,12 @@ var CameraStatusSchema = object({
18592
18622
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
18593
18623
  fetchedAt: number()
18594
18624
  });
18625
+ var InferenceDeviceExclusionReasonSchema = _enum([
18626
+ "disabled",
18627
+ "unavailable",
18628
+ "cannot-host-camera-root",
18629
+ "accelerator-preferred"
18630
+ ]);
18595
18631
  var NodeInferenceDeviceSchema = object({
18596
18632
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
18597
18633
  key: string(),
@@ -18622,7 +18658,17 @@ var NodeInferenceDeviceSchema = object({
18622
18658
  * available per format; this is the stored selection that becomes the
18623
18659
  * default for EVERY camera landing on this accelerator.
18624
18660
  */
18625
- steps: record(string(), DeviceStepConfigSchema).optional()
18661
+ steps: record(string(), DeviceStepConfigSchema).optional(),
18662
+ /**
18663
+ * `null` when the device IS a camera-root candidate on this node; otherwise
18664
+ * the reason the dispatcher drops it. Computed by the SAME
18665
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
18666
+ * never disagree with the election — deriving it in the UI from
18667
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
18668
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
18669
+ * "an accelerator is serving" predicate).
18670
+ */
18671
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
18626
18672
  });
18627
18673
  var NodeInferenceDevicesSchema = object({
18628
18674
  nodeId: string(),
@@ -19127,6 +19173,7 @@ DeviceType.Camera, method(object({
19127
19173
  isBattery: boolean(),
19128
19174
  reason: _enum([
19129
19175
  "disabled",
19176
+ "offline",
19130
19177
  "sleeping",
19131
19178
  "unreachable",
19132
19179
  "waking"
@@ -19172,9 +19219,17 @@ targets: array(object({
19172
19219
  sleeping: boolean(),
19173
19220
  /** Current device state rendered over the cached frame. State images
19174
19221
  * remain authoritative even when their photographic background is
19175
- * old; null means the link must carry a current camera frame. */
19222
+ * old; null means the link must carry a current camera frame.
19223
+ *
19224
+ * **This is the ONLY source a surface may derive an inactive tile
19225
+ * from.** A client that reads a device's `online`/`disabled` flags and
19226
+ * draws its own verdict will disagree with the pixels it is painting
19227
+ * — the frame comes from here, and so must the sentence over it
19228
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19229
+ * state. */
19176
19230
  stateReason: _enum([
19177
19231
  "disabled",
19232
+ "offline",
19178
19233
  "sleeping",
19179
19234
  "unreachable",
19180
19235
  "waking"
@@ -22394,7 +22449,12 @@ var ListResultSchema = object({
22394
22449
  probedAt: number()
22395
22450
  });
22396
22451
  var PreferredSchema = LocalInterfaceSchema.nullable();
22397
- var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
22452
+ /**
22453
+ * Candidate base URL for the SDK to race on connect. Order matters —
22454
+ * the SDK should attempt these top-to-bottom with a short per-candidate
22455
+ * timeout (e.g. 1500ms) and cache the winner for the session.
22456
+ */
22457
+ var ConnectionEndpointSchema = object({
22398
22458
  /** Operator-facing label (e.g. "LAN — en0", "Public tunnel"). */
22399
22459
  label: string(),
22400
22460
  /** Fully-formed base URL with scheme + host + port. */
@@ -22437,7 +22497,42 @@ var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
22437
22497
  * ordering between polls.
22438
22498
  */
22439
22499
  priority: number()
22440
- })).readonly() });
22500
+ });
22501
+ /**
22502
+ * Where the advertised local port came from. Ordered most → least
22503
+ * authoritative, and the whole point of returning it: a client must be able to
22504
+ * tell a FACT about the hub's socket from an echo of its own guess.
22505
+ */
22506
+ var LocalPortSourceEnum = _enum([
22507
+ "server-config",
22508
+ "server-env",
22509
+ "caller-hint",
22510
+ "default"
22511
+ ]);
22512
+ /** The port every LAN/loopback `baseUrl` in the same result was built with. */
22513
+ var AdvertisedLocalPortSchema = object({
22514
+ port: number().int().min(1).max(65535),
22515
+ source: LocalPortSourceEnum
22516
+ });
22517
+ var GetConnectionEndpointsResultSchema = object({
22518
+ endpoints: array(ConnectionEndpointSchema).readonly(),
22519
+ /**
22520
+ * The port the hub built the LAN/loopback URLs with, and where that number
22521
+ * came from.
22522
+ *
22523
+ * Returned rather than merely applied, because "the URL is right" and "the
22524
+ * client can KNOW the URL is right" are different properties. A client that
22525
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
22526
+ * a hub that echoed the port the client sent, so it cannot decide whether to
22527
+ * race the candidate or discard it. With `source` it can: anything but
22528
+ * `caller-hint` is the hub's own socket.
22529
+ *
22530
+ * Absent on hubs predating this field — a client that finds it missing is
22531
+ * talking to an echoing hub and must degrade exactly as it does for
22532
+ * `caller-hint`.
22533
+ */
22534
+ localPort: AdvertisedLocalPortSchema
22535
+ });
22441
22536
  /**
22442
22537
  * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
22443
22538
  * AUTO (resolved from the candidate ranking at send time); `resolved` reports
@@ -22458,8 +22553,13 @@ var AllowedAddressesSchema = object({
22458
22553
  */
22459
22554
  addresses: array(string()).readonly() });
22460
22555
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22461
- /** Local hub HTTP port to use in base URLs. */
22462
- port: number().int().min(1).max(65535),
22556
+ /**
22557
+ * LEGACY HINT — do not send from new code. Kept optional so clients
22558
+ * written against the echoing contract keep working; the hub uses it
22559
+ * only when it cannot read its own port, and says so via
22560
+ * `localPort.source === 'caller-hint'`.
22561
+ */
22562
+ port: number().int().min(1).max(65535).optional(),
22463
22563
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22464
22564
  * candidate. Default `true`. */
22465
22565
  includeLoopback: boolean().optional(),
@@ -33383,7 +33483,6 @@ var NC_AUDIO_DEFAULTS = {
33383
33483
  hitPercent: 60,
33384
33484
  samplingSeconds: 10
33385
33485
  };
33386
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
33387
33486
  new Set(["devices", "classes"]);
33388
33487
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
33389
33488
  /**
@@ -33617,6 +33716,39 @@ TimelapseRuleInputSchema.extend({
33617
33716
  createdAt: number(),
33618
33717
  updatedAt: number()
33619
33718
  });
33719
+ Object.freeze(Object.fromEntries([{
33720
+ stepId: "face-embedding",
33721
+ label: "Face recognition model",
33722
+ defaultModelId: "arcface-r100",
33723
+ indexName: "the enrolled face gallery",
33724
+ options: [
33725
+ {
33726
+ id: "arcface-r100",
33727
+ label: "ArcFace (ResNet34)"
33728
+ },
33729
+ {
33730
+ id: "auraface-r100",
33731
+ label: "AuraFace R100"
33732
+ },
33733
+ {
33734
+ id: "inception-resnet-v1",
33735
+ label: "Inception ResNet V1"
33736
+ }
33737
+ ]
33738
+ }, {
33739
+ stepId: "clip-embedding",
33740
+ label: "Semantic search model",
33741
+ defaultModelId: "mobileclip-s1",
33742
+ indexName: "the object semantic-search index",
33743
+ options: [{
33744
+ id: "mobileclip-s1",
33745
+ label: "MobileCLIP S1"
33746
+ }, {
33747
+ id: "mobileclip-s2",
33748
+ label: "MobileCLIP S2"
33749
+ }]
33750
+ }].map((s) => [s.stepId, s.defaultModelId])));
33751
+ string().min(1);
33620
33752
  object({
33621
33753
  /**
33622
33754
  * Fraction of the box's own size added on EACH side before cutting.
@@ -33637,10 +33769,21 @@ object({
33637
33769
  */
33638
33770
  square: boolean()
33639
33771
  });
33640
- ({
33772
+ /**
33773
+ * The convention in force when nobody has configured one — byte-for-byte the
33774
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
33775
+ */
33776
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
33641
33777
  paddingRatio: .15,
33642
33778
  square: false
33643
- }).paddingRatio;
33779
+ };
33780
+ Object.freeze({
33781
+ x: 0,
33782
+ y: 0,
33783
+ w: 1,
33784
+ h: 1
33785
+ });
33786
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
33644
33787
  /**
33645
33788
  * WHICH delivered frames the decode worker retains a native copy of.
33646
33789
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-static-turn",
3
- "version": "1.2.22",
3
+ "version": "1.2.24",
4
4
  "description": "Static / self-hosted (coturn) TURN provider for CamStack",
5
5
  "keywords": [
6
6
  "camstack",