@camstack/addon-post-analysis 1.2.662 → 1.2.663

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 (16) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/{clip-model-registry-ELaSS9jq.mjs → clip-model-registry-CwXJyrLg.mjs} +34002 -33152
  3. package/dist/{clip-model-registry-C3N1NpST.js → clip-model-registry-Cz8LWYwU.js} +34002 -33152
  4. package/dist/embedding-encoder/index.js +1 -1
  5. package/dist/embedding-encoder/index.mjs +1 -1
  6. package/dist/pipeline-analytics/_stub.js +2 -2
  7. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BvOlyiL9.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-jPBj20ez.mjs} +2 -2
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Be6bZvOS.mjs +26 -0
  9. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B5Bw-VHt.mjs +26 -0
  10. package/dist/pipeline-analytics/{hostInit-BjZchMMh.mjs → hostInit-DOdK6KJ5.mjs} +2 -2
  11. package/dist/pipeline-analytics/index.js +215 -27
  12. package/dist/pipeline-analytics/index.mjs +215 -27
  13. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  14. package/package.json +2 -2
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D4zkNcgf.mjs +0 -26
  16. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-vaatUWY6.mjs +0 -26
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_clip_model_registry = require("../clip-model-registry-C3N1NpST.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-Cz8LWYwU.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_clip_model_registry.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -70790,6 +70790,7 @@ Object.freeze({
70790
70790
  "pageSize": 50
70791
70791
  } },
70792
70792
  "device-runs": { "listRuns": { "limit": 50 } },
70793
+ "fan-presets": { "capturePreset": { "steps": [] } },
70793
70794
  "llm": { "upsertProfile": { "profile": { "__nested__": {
70794
70795
  "timeoutMs": 6e4,
70795
70796
  "connectTimeoutMs": 1e4,
@@ -71030,6 +71031,10 @@ Object.freeze({
71030
71031
  "speed": 2
71031
71032
  }
71032
71033
  },
71034
+ "system": { "debugWatch": {
71035
+ "channels": [],
71036
+ "deviceIds": []
71037
+ } },
71033
71038
  "terminal-session": { "pullOutput": { "waitMs": 0 } },
71034
71039
  "training": {
71035
71040
  "planJob": { "selection": { "__nested__": { "workState": "complete" } } },
@@ -71076,6 +71081,7 @@ Object.freeze({
71076
71081
  ],
71077
71082
  "device-removal-hook": ["listDeviceData"],
71078
71083
  "failure-contribution": ["list"],
71084
+ "integration-instances": ["listInstances"],
71079
71085
  "llm": [
71080
71086
  "getDefaults",
71081
71087
  "getUsage",
@@ -71117,6 +71123,7 @@ Object.freeze({
71117
71123
  "listSupportedEntityKinds",
71118
71124
  "unexposeEntity"
71119
71125
  ],
71126
+ "integration-instances": ["testInstance"],
71120
71127
  "log-destination": [
71121
71128
  "cancelRead",
71122
71129
  "describeRead",
@@ -85215,6 +85222,9 @@ async function renderMosaic(request, logger) {
85215
85222
  * one composition are decisions about the set, and none of them belongs in a
85216
85223
  * shelf that photographs one camera for an absence.
85217
85224
  *
85225
+ * A SLEEPING battery camera is woken by one forced capture when the rule leaves
85226
+ * it allowed (D998) — bounded, once per gap per camera, never by a poll.
85227
+ *
85218
85228
  * Two reads can only ever ADD a camera back, never take one away on their own
85219
85229
  * failure (D49): a wrapper-state read (run in parallel with the capture) that
85220
85230
  * misses or throws keeps the tile, and a freshness read that misses or throws
@@ -85228,6 +85238,15 @@ var NC_LINKED_CAMERA_PREFIX = "nc-linked:";
85228
85238
  function isLinkedStillId(id) {
85229
85239
  return id.startsWith(NC_LINKED_MOSAIC_PREFIX) || id.startsWith(NC_LINKED_CAMERA_PREFIX);
85230
85240
  }
85241
+ /**
85242
+ * D998 — the one forced capture that wakes a sleeping battery camera for a
85243
+ * sensor notification: the bound it may take, the pause between re-polls of a
85244
+ * camera another wake already started, and the gap per camera before it is
85245
+ * woken again (a flapping contact must not drain a battery).
85246
+ */
85247
+ var NC_LINKED_WAKE_CAPTURE_TIMEOUT_MS = 15e3;
85248
+ var NC_LINKED_WAKE_POLL_MS = 1e3;
85249
+ var NC_LINKED_WAKE_MIN_GAP_MS = 6e4;
85231
85250
  /** Per camera — the marker's own bound (`SENSOR_MARKER_SNAPSHOT_TIMEOUT_MS`). */
85232
85251
  var SENSOR_LINKED_CAPTURE_TIMEOUT_MS = 2500;
85233
85252
  /** The capture-free wrapper-state read, per camera. It runs IN PARALLEL with
@@ -85260,11 +85279,14 @@ var NcLinkedStillShelf = class {
85260
85279
  deps;
85261
85280
  sets = /* @__PURE__ */ new Map();
85262
85281
  owners = /* @__PURE__ */ new Map();
85282
+ /** setId → when its in-flight wake gives up (the dispatcher waits that long). */
85283
+ pendingWakes = /* @__PURE__ */ new Map();
85284
+ lastWakeAt = /* @__PURE__ */ new Map();
85263
85285
  constructor(deps) {
85264
85286
  this.deps = deps;
85265
85287
  }
85266
85288
  /** SYNCHRONOUS: the evaluation that follows names these owners. */
85267
- captureSet(sensorId, cameraIds, atMs) {
85289
+ captureSet(sensorId, cameraIds, atMs, options = { wakeSleeping: false }) {
85268
85290
  const now = this.deps.now();
85269
85291
  this.prune(now);
85270
85292
  const setId = (0, node_crypto.randomUUID)();
@@ -85292,7 +85314,7 @@ var NcLinkedStillShelf = class {
85292
85314
  camera: cameraId
85293
85315
  });
85294
85316
  });
85295
- this.run(setId, stills, atMs, now).catch((err) => {
85317
+ this.run(setId, stills, atMs, now, options).catch((err) => {
85296
85318
  this.deps.logger.warn("sensor notification pictures failed — it ships text-only", {
85297
85319
  tags: { deviceId: sensorId },
85298
85320
  meta: { error: String(err) }
@@ -85300,6 +85322,15 @@ var NcLinkedStillShelf = class {
85300
85322
  });
85301
85323
  return stills;
85302
85324
  }
85325
+ /**
85326
+ * While a wake of one of this owner's cameras is in flight: the instant it
85327
+ * gives up (its bound plus one poll). `undefined` otherwise — the dispatcher
85328
+ * then keeps its ordinary still-wait.
85329
+ */
85330
+ pendingUntil(ownerId) {
85331
+ const ref = this.owners.get(ownerId);
85332
+ return ref === void 0 ? void 0 : this.pendingWakes.get(ref.setId);
85333
+ }
85303
85334
  /** `undefined` = not an owner this shelf minted (or pruned); `[]` = nothing (yet). */
85304
85335
  get(ownerId) {
85305
85336
  const ref = this.owners.get(ownerId);
@@ -85321,20 +85352,37 @@ var NcLinkedStillShelf = class {
85321
85352
  clear() {
85322
85353
  this.sets.clear();
85323
85354
  this.owners.clear();
85355
+ this.pendingWakes.clear();
85324
85356
  }
85325
- async run(setId, stills, atMs, startedAt) {
85326
- const results = await Promise.all(stills.cameraIds.map((id) => this.captureOne(id, startedAt)));
85357
+ async run(setId, stills, atMs, startedAt, options) {
85358
+ const ctx = {
85359
+ setId,
85360
+ sensorId: stills.sensorId,
85361
+ startedAt,
85362
+ options
85363
+ };
85364
+ let results;
85365
+ try {
85366
+ results = await Promise.all(stills.cameraIds.map((id) => this.captureOne(id, ctx)));
85367
+ } finally {
85368
+ this.pendingWakes.delete(setId);
85369
+ }
85327
85370
  const judged = await this.judgeFreshness(stills.sensorId, results, atMs);
85328
85371
  for (const r of judged) this.logTile(stills.sensorId, r);
85329
85372
  const landed = judged.filter(isLandedTile);
85330
85373
  const composed = await this.compose(stills.sensorId, landed, atMs);
85331
85374
  const undrawable = landed.length - composed.drawn;
85375
+ const woken = judged.filter((r) => r.woken === true).map((r) => r.cameraId);
85332
85376
  this.deps.logger.info(composed.mosaic.length === 0 ? "sensor notification has no linked picture — it ships text-only" : "sensor notification pictures ready", {
85333
85377
  tags: { deviceId: stills.sensorId },
85334
85378
  meta: {
85335
85379
  cameras: stills.cameraIds.length,
85336
85380
  landed: composed.drawn,
85337
85381
  ...undrawable > 0 ? { undrawable } : {},
85382
+ ...woken.length > 0 ? {
85383
+ woken,
85384
+ waitedMs: this.deps.now() - startedAt
85385
+ } : {},
85338
85386
  dropped: Object.fromEntries(judged.filter((r) => r.outcome !== "landed").map((r) => [String(r.cameraId), r.outcome])),
85339
85387
  tookMs: this.deps.now() - startedAt
85340
85388
  }
@@ -85347,21 +85395,25 @@ var NcLinkedStillShelf = class {
85347
85395
  mosaic: composed.mosaic
85348
85396
  });
85349
85397
  }
85350
- async captureOne(cameraId, startedAt) {
85351
- const took = () => this.deps.now() - startedAt;
85398
+ async captureOne(cameraId, ctx) {
85399
+ const took = () => this.deps.now() - ctx.startedAt;
85352
85400
  if (this.deps.isOnline(cameraId) === false) return {
85353
85401
  cameraId,
85354
85402
  outcome: "excluded",
85355
85403
  reason: "offline",
85356
85404
  tookMs: took()
85357
85405
  };
85358
- const [verdict, capture] = await Promise.all([this.wrapperVerdict(cameraId), this.captureStill(cameraId)]);
85359
- if (verdict.reason !== null) return {
85406
+ const [verdict, capture] = await Promise.all([this.wrapperVerdict(cameraId), this.captureStill(cameraId, false, SENSOR_LINKED_CAPTURE_TIMEOUT_MS)]);
85407
+ const excluded = (reason) => ({
85360
85408
  cameraId,
85361
85409
  outcome: "excluded",
85362
- reason: verdict.reason,
85410
+ reason,
85363
85411
  tookMs: took()
85364
- };
85412
+ });
85413
+ if (verdict.reason === "sleeping") return this.wakeOne(cameraId, ctx);
85414
+ if (verdict.reason === "waking" && ctx.options.wakeSleeping) return this.awaitWaking(cameraId, ctx);
85415
+ if (verdict.reason === "unreachable" && ctx.options.wakeSleeping) return excluded("wake-skipped:unreachable");
85416
+ if (verdict.reason !== null) return excluded(verdict.reason);
85365
85417
  return {
85366
85418
  cameraId,
85367
85419
  ...capture,
@@ -85369,9 +85421,84 @@ var NcLinkedStillShelf = class {
85369
85421
  tookMs: took()
85370
85422
  };
85371
85423
  }
85372
- async captureStill(cameraId) {
85424
+ /** D998: one forced capture, by the rule's leave, at most once per gap per camera. */
85425
+ async wakeOne(cameraId, ctx) {
85426
+ const took = () => this.deps.now() - ctx.startedAt;
85427
+ const skipped = (reason) => ({
85428
+ cameraId,
85429
+ outcome: "excluded",
85430
+ reason,
85431
+ tookMs: took()
85432
+ });
85433
+ if (!ctx.options.wakeSleeping) return skipped("wake-skipped:policy");
85434
+ const last = this.lastWakeAt.get(cameraId);
85435
+ if (last !== void 0 && ctx.startedAt - last < NC_LINKED_WAKE_MIN_GAP_MS) return skipped("wake-skipped:recent-wake");
85436
+ this.lastWakeAt.set(cameraId, ctx.startedAt);
85437
+ this.markPending(ctx);
85438
+ this.deps.logger.info("linked battery camera woken for the sensor notification", {
85439
+ tags: { deviceId: cameraId },
85440
+ meta: {
85441
+ sensorId: ctx.sensorId,
85442
+ ruleIds: ctx.options.ruleIds ?? [],
85443
+ boundMs: NC_LINKED_WAKE_CAPTURE_TIMEOUT_MS
85444
+ }
85445
+ });
85446
+ const capture = await this.captureStill(cameraId, true, NC_LINKED_WAKE_CAPTURE_TIMEOUT_MS);
85447
+ return {
85448
+ cameraId,
85449
+ ...this.asWakeResult(capture),
85450
+ woken: true,
85451
+ tookMs: took()
85452
+ };
85453
+ }
85454
+ /** A camera another wake already started: never forced, re-polled within the bound. */
85455
+ async awaitWaking(cameraId, ctx) {
85456
+ const took = () => this.deps.now() - ctx.startedAt;
85457
+ this.markPending(ctx);
85458
+ const polls = Math.floor(NC_LINKED_WAKE_CAPTURE_TIMEOUT_MS / NC_LINKED_WAKE_POLL_MS);
85459
+ for (let i = 0; i < polls; i++) {
85460
+ await new Promise((resolve) => setTimeout(resolve, NC_LINKED_WAKE_POLL_MS));
85461
+ const verdict = await this.wrapperVerdict(cameraId);
85462
+ if (verdict.reason === "waking") continue;
85463
+ if (verdict.reason !== null) return {
85464
+ cameraId,
85465
+ outcome: "excluded",
85466
+ reason: verdict.reason,
85467
+ tookMs: took()
85468
+ };
85469
+ const capture = await this.captureStill(cameraId, false, SENSOR_LINKED_CAPTURE_TIMEOUT_MS);
85470
+ return {
85471
+ cameraId,
85472
+ ...this.asWakeResult(capture),
85473
+ woken: true,
85474
+ tookMs: took()
85475
+ };
85476
+ }
85477
+ return {
85478
+ cameraId,
85479
+ outcome: "wake-timeout",
85480
+ woken: true,
85481
+ tookMs: took()
85482
+ };
85483
+ }
85484
+ markPending(ctx) {
85485
+ const until = ctx.startedAt + NC_LINKED_WAKE_CAPTURE_TIMEOUT_MS + NC_LINKED_WAKE_POLL_MS;
85486
+ this.pendingWakes.set(ctx.setId, Math.max(this.pendingWakes.get(ctx.setId) ?? 0, until));
85487
+ }
85488
+ /** A wake that brought nothing back names itself, not the generic `empty`. */
85489
+ asWakeResult(capture) {
85490
+ if (capture.outcome === "timeout") return {
85491
+ ...capture,
85492
+ outcome: "wake-timeout"
85493
+ };
85494
+ return capture.outcome === "empty" || capture.outcome === "error" && capture.reason !== "undecodable" ? {
85495
+ ...capture,
85496
+ outcome: "wake-refused"
85497
+ } : capture;
85498
+ }
85499
+ async captureStill(cameraId, force, timeoutMs) {
85373
85500
  try {
85374
- const shot = await withTimeout$2(this.deps.getSnapshot(cameraId), SENSOR_LINKED_CAPTURE_TIMEOUT_MS, "snapshot cap");
85501
+ const shot = await withTimeout$2(force ? this.deps.getSnapshot(cameraId, { force: true }) : this.deps.getSnapshot(cameraId), timeoutMs, "snapshot cap");
85375
85502
  if (shot === null || shot.base64.length === 0) return { outcome: "empty" };
85376
85503
  const jpeg = await this.decodableStill(Buffer.from(shot.base64, "base64"));
85377
85504
  if (jpeg === null) return {
@@ -86930,6 +87057,13 @@ var NC_RECORDED_FOOTAGE_WAIT_MS = 3e4;
86930
87057
  var NC_STILL_WAIT_MS = 3e3;
86931
87058
  var NC_STILL_WAIT_INTERVAL_MS = 500;
86932
87059
  /**
87060
+ * D998 — the longest a sensor notification holds for a linked battery camera
87061
+ * that is being woken (`stillDeadline`). A `sent` row is final, so the picture
87062
+ * cannot follow the push: it is waited for here, bounded, or never attached.
87063
+ * Owners with no wake in flight keep {@link NC_STILL_WAIT_MS}.
87064
+ */
87065
+ var NC_LINKED_WAKE_WAIT_MS = 15e3;
87066
+ /**
86933
87067
  * How far a still's OWN instant may sit from the trigger and still count as
86934
87068
  * being ABOUT the trigger, for a `delivery: 'immediate'` rule.
86935
87069
  *
@@ -88146,7 +88280,15 @@ var NcDispatcher = class {
88146
88280
  const firstLook = groupTiles !== null ? null : await this.resolveAttachment(entry, canStillGainMedia(entry) ? "trigger-only" : "any");
88147
88281
  const footagePending = this.renderFootage(entry);
88148
88282
  const mosaic = groupView !== void 0 && groupTiles !== null ? await this.groupedMosaic(entry, groupView, groupTiles) : null;
88149
- const still = mosaic !== null ? mosaic.attachment : firstLook !== null || entry.payload.media === "none" ? firstLook : await this.waitForStill(entry);
88283
+ let waitedMs = 0;
88284
+ let still;
88285
+ if (mosaic !== null) still = mosaic.attachment;
88286
+ else if (firstLook !== null || entry.payload.media === "none") still = firstLook;
88287
+ else {
88288
+ const waited = await this.waitForStill(entry);
88289
+ still = waited.still;
88290
+ waitedMs = waited.waitedMs;
88291
+ }
88150
88292
  const grouped = groupView !== void 0 && groupMembers !== null ? {
88151
88293
  windowKey: groupView.key,
88152
88294
  members: groupView.members.length,
@@ -88171,6 +88313,7 @@ var NcDispatcher = class {
88171
88313
  recordKind: entry.recordKind,
88172
88314
  recordId: entry.recordId,
88173
88315
  policy: entry.payload.media,
88316
+ waitedMs,
88174
88317
  ...entry.payload.mediaImage !== void 0 ? { image: entry.payload.mediaImage } : {},
88175
88318
  ...memberTrackIds.length > 1 ? { memberTrackIds } : {},
88176
88319
  owners: [...entry.payload.subject.eventId !== void 0 ? [`object:${entry.payload.subject.eventId}`] : [], ...entry.payload.subject.trackId !== void 0 ? [`track:${entry.payload.subject.trackId}`] : []]
@@ -88197,7 +88340,13 @@ var NcDispatcher = class {
88197
88340
  const subject = entry.payload.subject;
88198
88341
  const out = [];
88199
88342
  for (const { ownerId, cameraId } of subject.extraStills ?? []) {
88200
- const file = await this.linkedStillFile(entry, ownerId, cameraId);
88343
+ let file = await this.linkedStillFile(entry, ownerId, cameraId);
88344
+ for (let polls = 0; file === void 0 && polls < NC_LINKED_WAKE_WAIT_MS / NC_STILL_WAIT_INTERVAL_MS; polls += 1) {
88345
+ const deadline = this.deps.stillDeadline?.(ownerId);
88346
+ if (deadline === void 0 || this.now() >= deadline) break;
88347
+ await this.sleep(NC_STILL_WAIT_INTERVAL_MS);
88348
+ file = await this.linkedStillFile(entry, ownerId, cameraId);
88349
+ }
88201
88350
  const bytes = file === void 0 ? null : decodeStillBytes(file.base64);
88202
88351
  if (file === void 0 || bytes === null) {
88203
88352
  this.deps.logger.info("linked camera picture missing at send — skipped", {
@@ -88684,11 +88833,19 @@ var NcDispatcher = class {
88684
88833
  */
88685
88834
  async waitForStill(entry) {
88686
88835
  const subject = entry.payload.subject;
88687
- if (this.now() - subject.timestamp > NC_STILL_WAIT_MAX_AGE_MS) return this.resolveAttachment(entry, "any");
88688
- if (subject.eventId === void 0 && subject.trackId === void 0) return null;
88689
- const attempts = Math.floor(NC_STILL_WAIT_MS / NC_STILL_WAIT_INTERVAL_MS);
88690
- for (let i = 0; i < attempts; i += 1) {
88836
+ if (this.now() - subject.timestamp > NC_STILL_WAIT_MAX_AGE_MS) return {
88837
+ still: await this.resolveAttachment(entry, "any"),
88838
+ waitedMs: 0
88839
+ };
88840
+ if (subject.eventId === void 0 && subject.trackId === void 0) return {
88841
+ still: null,
88842
+ waitedMs: 0
88843
+ };
88844
+ const startedAt = this.now();
88845
+ let waitedMs = 0;
88846
+ for (let i = 0; waitedMs < this.stillWaitBoundMs(entry, startedAt); i += 1) {
88691
88847
  await this.sleep(NC_STILL_WAIT_INTERVAL_MS);
88848
+ waitedMs += NC_STILL_WAIT_INTERVAL_MS;
88692
88849
  const still = await this.resolveAttachment(entry, "trigger-only");
88693
88850
  if (still !== null) {
88694
88851
  this.deps.logger.info("the still landed during the bounded wait", {
@@ -88700,10 +88857,32 @@ var NcDispatcher = class {
88700
88857
  kind: still.name
88701
88858
  }
88702
88859
  });
88703
- return still;
88860
+ return {
88861
+ still,
88862
+ waitedMs
88863
+ };
88704
88864
  }
88705
88865
  }
88706
- return this.resolveAttachment(entry, "any");
88866
+ return {
88867
+ still: await this.resolveAttachment(entry, "any"),
88868
+ waitedMs
88869
+ };
88870
+ }
88871
+ /**
88872
+ * How long this row's still poll may run: {@link NC_STILL_WAIT_MS}, or — while
88873
+ * the shelf says a linked camera of this row is being woken — until that
88874
+ * wake gives up, at most {@link NC_LINKED_WAKE_WAIT_MS} (D998).
88875
+ */
88876
+ stillWaitBoundMs(entry, startedAt) {
88877
+ const subject = entry.payload.subject;
88878
+ const owners = [...subject.trackId !== void 0 ? [subject.trackId] : [], ...(subject.extraStills ?? []).map((extra) => extra.ownerId)];
88879
+ let deadline;
88880
+ for (const ownerId of owners) {
88881
+ const until = this.deps.stillDeadline?.(ownerId);
88882
+ if (until !== void 0 && (deadline === void 0 || until > deadline)) deadline = until;
88883
+ }
88884
+ if (deadline === void 0) return NC_STILL_WAIT_MS;
88885
+ return Math.max(NC_STILL_WAIT_MS, Math.min(NC_LINKED_WAKE_WAIT_MS, deadline - startedAt));
88707
88886
  }
88708
88887
  /**
88709
88888
  * The grouped MOSAIC (D692 §6.2): one tile per member, at most
@@ -88851,8 +89030,8 @@ var NcDispatcher = class {
88851
89030
  return ts !== void 0 && this.now() - ts <= NC_STILL_WAIT_MAX_AGE_MS;
88852
89031
  });
88853
89032
  await look("trigger-only", pollable());
88854
- const attempts = Math.floor(NC_STILL_WAIT_MS / NC_STILL_WAIT_INTERVAL_MS);
88855
- for (let i = 0; i < attempts && pollable().length > 0; i += 1) {
89033
+ const startedAt = this.now();
89034
+ for (let waited = 0; waited < this.stillWaitBoundMs(entry, startedAt) && pollable().length > 0; waited += NC_STILL_WAIT_INTERVAL_MS) {
88856
89035
  await this.sleep(NC_STILL_WAIT_INTERVAL_MS);
88857
89036
  await look("trigger-only", pollable());
88858
89037
  }
@@ -104372,7 +104551,7 @@ function beginLinkedStills(host, request) {
104372
104551
  if (rowCameraIds.length > 0 && rowShown.length === 0) return void 0;
104373
104552
  const shown = host.unmutedCameras(cameraIds);
104374
104553
  const involved = [sensorId, ...rowShown];
104375
- if (!host.rules.listEnabled("device-event").some((rule) => {
104554
+ const wanting = host.rules.listEnabled("device-event").filter((rule) => {
104376
104555
  if (ruleShipsNoStill(rule)) return false;
104377
104556
  if (!deviceEventRuleMayFireOnKind(rule, sensorKind, sensorRaised)) return false;
104378
104557
  const excluded = rule.conditions.devicesExclude;
@@ -104383,9 +104562,14 @@ function beginLinkedStills(host, request) {
104383
104562
  })) return false;
104384
104563
  const devices = rule.conditions.devices;
104385
104564
  return devices === void 0 || devices.length === 0 || devices.some((id) => involved.includes(id));
104386
- })) return void 0;
104565
+ });
104566
+ if (wanting.length === 0) return void 0;
104567
+ const wakeSleeping = wanting.some((rule) => rule.media?.wakeSleepingCamera !== false);
104387
104568
  for (const cameraId of cameraIds) if (!shown.includes(cameraId)) host.reportLinkedCameraMuted(cameraId, sensorId);
104388
- return shown.length > 0 ? shelf.captureSet(sensorId, shown, atMs) : void 0;
104569
+ return shown.length > 0 ? shelf.captureSet(sensorId, shown, atMs, {
104570
+ wakeSleeping,
104571
+ ruleIds: wanting.map((rule) => rule.id)
104572
+ }) : void 0;
104389
104573
  }
104390
104574
  /**
104391
104575
  * A muted camera left out of a linked set — one INFO line per camera per
@@ -107108,6 +107292,7 @@ var NotificationCenter = class {
107108
107292
  ...deps.dispatcher,
107109
107293
  logger: this.logger.child("dispatch"),
107110
107294
  texts: this.texts,
107295
+ stillDeadline: (ownerId) => isLinkedStillId(ownerId) ? this.linkedStills?.pendingUntil(ownerId) : void 0,
107111
107296
  getMediaForOwner: async (ownerKind, ownerId, eventScope) => {
107112
107297
  const held = isSyntheticId(ownerId) ? this.syntheticMedia.get(ownerId) : void 0;
107113
107298
  if (held !== void 0) return held.files;
@@ -109691,7 +109876,10 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
109691
109876
  },
109692
109877
  readAlerts: () => api.alerts.list.query({ limit: 5e3 }),
109693
109878
  emitBusEvent: (event) => this.ctx.eventBus.emit(event),
109694
- getSnapshot: (deviceId) => api.snapshot.getSnapshot.query({ deviceId }),
109879
+ getSnapshot: (deviceId, opts) => api.snapshot.getSnapshot.query(opts?.force === true ? {
109880
+ deviceId,
109881
+ force: true
109882
+ } : { deviceId }),
109695
109883
  recognitionOutlook: ({ deviceId, trackId, className, labelKind, now }) => recognitionOutlook({
109696
109884
  className,
109697
109885
  ...labelKind !== void 0 ? { labelKind } : {},