@camstack/system 1.2.0 → 1.2.1

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 (48) hide show
  1. package/dist/addon-runner.js +2 -2
  2. package/dist/addon-runner.mjs +2 -2
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/device-manager/device-linked-devices.d.ts +13 -0
  14. package/dist/builtins/device-manager/device-manager.addon.js +86 -26
  15. package/dist/builtins/device-manager/device-manager.addon.mjs +86 -26
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  18. package/dist/builtins/hub-forwarder/index.js +1 -1
  19. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  20. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  22. package/dist/builtins/local-network/local-network.addon.js +1 -1
  23. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  24. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  25. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  26. package/dist/builtins/platform-probe/index.js +1 -1
  27. package/dist/builtins/platform-probe/index.mjs +1 -1
  28. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  29. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  30. package/dist/builtins/snapshot/index.js +1 -1
  31. package/dist/builtins/snapshot/index.mjs +1 -1
  32. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  33. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  34. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  35. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  36. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  37. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  38. package/dist/builtins/system-config/system-config.addon.js +1 -1
  39. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  40. package/dist/builtins/winston-logging/index.js +1 -1
  41. package/dist/builtins/winston-logging/index.mjs +1 -1
  42. package/dist/{dist-D7-BTTgZ.mjs → dist-BLNvh8Qy.mjs} +29 -7
  43. package/dist/{dist-B9OD5Tdn.js → dist-DP2SKMvU.js} +29 -7
  44. package/dist/index.js +2 -2
  45. package/dist/index.mjs +2 -2
  46. package/dist/{manifest-python-deps-D7EIakIT.mjs → manifest-python-deps-Co8MTPdy.mjs} +1 -1
  47. package/dist/{manifest-python-deps-DXqcuOha.js → manifest-python-deps-DE8HxnIx.js} +1 -1
  48. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_dist = require("./dist-B9OD5Tdn.js");
3
- const require_manifest_python_deps = require("./manifest-python-deps-DXqcuOha.js");
2
+ const require_dist = require("./dist-DP2SKMvU.js");
3
+ const require_manifest_python_deps = require("./manifest-python-deps-DE8HxnIx.js");
4
4
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
5
5
  let node_fs = require("node:fs");
6
6
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,5 +1,5 @@
1
- import { M as isCollectionArrayMethod, t as ALL_CAPABILITY_DEFINITIONS } from "./dist-D7-BTTgZ.mjs";
2
- import { I as createUdsLoggerWithControl, L as LocalChildClient, St as resolveAddonClass, at as setWorkerNativeCapsChangeListener, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, yt as installManifestNativeDeps } from "./manifest-python-deps-D7EIakIT.mjs";
1
+ import { M as isCollectionArrayMethod, t as ALL_CAPABILITY_DEFINITIONS } from "./dist-BLNvh8Qy.mjs";
2
+ import { I as createUdsLoggerWithControl, L as LocalChildClient, St as resolveAddonClass, at as setWorkerNativeCapsChangeListener, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, yt as installManifestNativeDeps } from "./manifest-python-deps-Co8MTPdy.mjs";
3
3
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
4
4
  import { register } from "node:module";
5
5
  import * as fs from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, at as BaseAddon, f as addonPagesCapability, it as errMsg } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, at as BaseAddon, f as addonPagesCapability, it as errMsg } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, at as BaseAddon, it as errMsg, m as addonWidgetsCapability } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, at as BaseAddon, it as errMsg, m as addonWidgetsCapability } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  //#region src/builtins/alerts/alerts.addon.ts
8
8
  var CATEGORY_MAPPINGS = {
9
9
  "addon.crashed": {
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, at as BaseAddon, h as alertsCapability, it as errMsg, n as AlertSchema, vt as createEvent } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, at as BaseAddon, h as alertsCapability, it as errMsg, n as AlertSchema, vt as createEvent } from "../../dist-BLNvh8Qy.mjs";
2
2
  //#region src/builtins/alerts/alerts.addon.ts
3
3
  var CATEGORY_MAPPINGS = {
4
4
  "addon.crashed": {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_fs$1 = require_chunk.__toESM(node_fs, 1);
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, at as BaseAddon, v as backupCapability } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, at as BaseAddon, v as backupCapability } from "../../dist-BLNvh8Qy.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import Kt from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { R as logDestinationCapability, at as BaseAddon } from "../../dist-D7-BTTgZ.mjs";
1
+ import { R as logDestinationCapability, at as BaseAddon } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -3,6 +3,9 @@ import { ProviderContext } from './device-provider-context.js';
3
3
  /** Reserved device-config keys (cf. `_profileMap`). */
4
4
  export declare const LINKED_MODE_CONFIG_KEY = "_linkedDevicesMode";
5
5
  export declare const LINKED_IDS_CONFIG_KEY = "_linkedDeviceIds";
6
+ /** Reserved key: subset of linked devices that materialize synthetic tracked
7
+ * events on the camera. Absent ⇒ ALL linked devices produce tracked events. */
8
+ export declare const LINKED_TRACKED_IDS_CONFIG_KEY = "_linkedDeviceTrackedIds";
6
9
  export interface LinkedDevicesConfig {
7
10
  readonly mode: DeviceLinkMode;
8
11
  readonly manualIds: readonly number[];
@@ -27,6 +30,16 @@ export declare function resolveLinkedDeviceIds(params: {
27
30
  readonly config: LinkedDevicesConfig;
28
31
  readonly candidates: readonly LinkCandidate[];
29
32
  }): readonly number[];
33
+ /**
34
+ * Which linked devices materialize synthetic tracked events on the camera.
35
+ * Absent / non-array config key ⇒ ALL linked devices (back-compat with the
36
+ * pre-existing "every linked device produces events" behaviour). Otherwise the
37
+ * configured subset intersected with the currently-resolved linked set (a
38
+ * picked id no longer linked is dropped). Ids may arrive as numbers or numeric
39
+ * strings (the multiselect stores string option values); non-numeric entries
40
+ * are ignored.
41
+ */
42
+ export declare function resolveTrackedEventDeviceIds(config: Record<string, unknown>, linkedIds: readonly number[]): readonly number[];
30
43
  /** Cap implementation of `getLinkedDevices`. */
31
44
  export declare function getLinkedDevices(pctx: ProviderContext, input: Parameters<IDeviceManagerProvider['getLinkedDevices']>[0]): ReturnType<IDeviceManagerProvider['getLinkedDevices']>;
32
45
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let _camstack_types_node = require("@camstack/types/node");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1209,6 +1209,9 @@ async function testField(pctx, input) {
1209
1209
  /** Reserved device-config keys (cf. `_profileMap`). */
1210
1210
  var LINKED_MODE_CONFIG_KEY = "_linkedDevicesMode";
1211
1211
  var LINKED_IDS_CONFIG_KEY = "_linkedDeviceIds";
1212
+ /** Reserved key: subset of linked devices that materialize synthetic tracked
1213
+ * events on the camera. Absent ⇒ ALL linked devices produce tracked events. */
1214
+ var LINKED_TRACKED_IDS_CONFIG_KEY = "_linkedDeviceTrackedIds";
1212
1215
  /** Parse the two reserved keys out of a device-config blob. Tolerant:
1213
1216
  * ids may arrive as numbers or numeric strings (the multiselect field
1214
1217
  * stores string option values); anything else is dropped. */
@@ -1244,6 +1247,25 @@ function resolveLinkedDeviceIds(params) {
1244
1247
  ids.delete(selfId);
1245
1248
  return [...ids];
1246
1249
  }
1250
+ /**
1251
+ * Which linked devices materialize synthetic tracked events on the camera.
1252
+ * Absent / non-array config key ⇒ ALL linked devices (back-compat with the
1253
+ * pre-existing "every linked device produces events" behaviour). Otherwise the
1254
+ * configured subset intersected with the currently-resolved linked set (a
1255
+ * picked id no longer linked is dropped). Ids may arrive as numbers or numeric
1256
+ * strings (the multiselect stores string option values); non-numeric entries
1257
+ * are ignored.
1258
+ */
1259
+ function resolveTrackedEventDeviceIds(config, linkedIds) {
1260
+ const raw = config[LINKED_TRACKED_IDS_CONFIG_KEY];
1261
+ if (!Array.isArray(raw)) return linkedIds;
1262
+ const picked = /* @__PURE__ */ new Set();
1263
+ for (const v of raw) {
1264
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
1265
+ if (Number.isInteger(n)) picked.add(n);
1266
+ }
1267
+ return linkedIds.filter((id) => picked.has(id));
1268
+ }
1247
1269
  /** Cap implementation of `getLinkedDevices`. */
1248
1270
  async function getLinkedDevices(pctx, input) {
1249
1271
  const { deviceId } = input;
@@ -1256,6 +1278,7 @@ async function getLinkedDevices(pctx, input) {
1256
1278
  config,
1257
1279
  candidates: all
1258
1280
  });
1281
+ const trackedIds = new Set(resolveTrackedEventDeviceIds(blob, ids));
1259
1282
  const devices = [];
1260
1283
  for (const id of ids) {
1261
1284
  const d = byId.get(id);
@@ -1264,7 +1287,8 @@ async function getLinkedDevices(pctx, input) {
1264
1287
  deviceId: d.id,
1265
1288
  name: d.name,
1266
1289
  location: d.location,
1267
- features: d.features
1290
+ features: d.features,
1291
+ producesTrackedEvents: trackedIds.has(d.id)
1268
1292
  });
1269
1293
  }
1270
1294
  return {
@@ -1287,36 +1311,63 @@ async function buildLinkedDevicesContribution(pctx, deviceId) {
1287
1311
  value: String(d.id),
1288
1312
  label: d.location !== null ? `${d.name} (${d.location})` : d.name
1289
1313
  }));
1314
+ const linkedIds = resolveLinkedDeviceIds({
1315
+ selfId: deviceId,
1316
+ selfLocation: all.find((d) => d.id === deviceId)?.location ?? null,
1317
+ config,
1318
+ candidates: all
1319
+ });
1320
+ const byId = new Map(all.map((d) => [d.id, d]));
1321
+ const trackedOptions = linkedIds.flatMap((id) => {
1322
+ const d = byId.get(id);
1323
+ if (!d) return [];
1324
+ return [{
1325
+ value: String(d.id),
1326
+ label: d.location !== null ? `${d.name} (${d.location})` : d.name
1327
+ }];
1328
+ });
1329
+ const trackedValue = resolveTrackedEventDeviceIds(blob, linkedIds).map(String);
1290
1330
  return { sections: [{
1291
1331
  id: "linked-devices",
1292
1332
  title: "Linked devices",
1293
1333
  description: "Companion devices associated with this camera. Device-tree children are always linked; auto mode also links every device sharing the camera location, manual mode links the picked list instead.",
1294
1334
  order: 70,
1295
- fields: [{
1296
- type: "select",
1297
- key: LINKED_MODE_CONFIG_KEY,
1298
- label: "Link mode",
1299
- description: "Auto: children + same-location devices. Manual: children + the list below.",
1300
- options: [{
1301
- value: "auto",
1302
- label: "Auto (location-based)"
1303
- }, {
1304
- value: "manual",
1305
- label: "Manual"
1306
- }],
1307
- value: config.mode
1308
- }, {
1309
- type: "multiselect",
1310
- key: LINKED_IDS_CONFIG_KEY,
1311
- label: "Manually linked devices",
1312
- description: "Used in manual mode (children stay linked regardless).",
1313
- showWhen: {
1314
- field: LINKED_MODE_CONFIG_KEY,
1315
- equals: "manual"
1335
+ fields: [
1336
+ {
1337
+ type: "select",
1338
+ key: LINKED_MODE_CONFIG_KEY,
1339
+ label: "Link mode",
1340
+ description: "Auto: children + same-location devices. Manual: children + the list below.",
1341
+ options: [{
1342
+ value: "auto",
1343
+ label: "Auto (location-based)"
1344
+ }, {
1345
+ value: "manual",
1346
+ label: "Manual"
1347
+ }],
1348
+ value: config.mode
1316
1349
  },
1317
- options,
1318
- value: config.manualIds.map(String)
1319
- }]
1350
+ {
1351
+ type: "multiselect",
1352
+ key: LINKED_IDS_CONFIG_KEY,
1353
+ label: "Manually linked devices",
1354
+ description: "Used in manual mode (children stay linked regardless).",
1355
+ showWhen: {
1356
+ field: LINKED_MODE_CONFIG_KEY,
1357
+ equals: "manual"
1358
+ },
1359
+ options,
1360
+ value: config.manualIds.map(String)
1361
+ },
1362
+ {
1363
+ type: "multiselect",
1364
+ key: LINKED_TRACKED_IDS_CONFIG_KEY,
1365
+ label: "Devices that produce tracked events",
1366
+ description: "Linked devices whose state changes materialize a synthetic tracked event on this camera. Default: all linked devices.",
1367
+ options: trackedOptions,
1368
+ value: trackedValue
1369
+ }
1370
+ ]
1320
1371
  }] };
1321
1372
  }
1322
1373
  /**
@@ -1329,6 +1380,15 @@ async function applyLinkedDevicesPatch(pctx, deviceId, patch) {
1329
1380
  const next = { ...await pctx.settings.readDeviceStore(deviceId) };
1330
1381
  if ("_linkedDevicesMode" in patch) next[LINKED_MODE_CONFIG_KEY] = patch["_linkedDevicesMode"] === "manual" ? "manual" : "auto";
1331
1382
  if ("_linkedDeviceIds" in patch) next[LINKED_IDS_CONFIG_KEY] = [...parseLinkedDevicesConfig({ [LINKED_IDS_CONFIG_KEY]: patch[LINKED_IDS_CONFIG_KEY] }).manualIds];
1383
+ if ("_linkedDeviceTrackedIds" in patch) {
1384
+ const raw = patch[LINKED_TRACKED_IDS_CONFIG_KEY];
1385
+ const ids = [];
1386
+ if (Array.isArray(raw)) for (const v of raw) {
1387
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
1388
+ if (Number.isInteger(n) && !ids.includes(n)) ids.push(n);
1389
+ }
1390
+ next[LINKED_TRACKED_IDS_CONFIG_KEY] = ids;
1391
+ }
1332
1392
  await pctx.settings.writeDeviceStore(deviceId, next);
1333
1393
  return { success: true };
1334
1394
  }
@@ -1,4 +1,4 @@
1
- import { A as getByPath, At as EventCategory, Ct as isDeviceConfigCap, D as evaluateLinkExpression, E as enumerateSchemaFields, H as normalizeUnit, S as deviceStatusCapability, T as enumerateItemArrayFields, W as parseStreamParamsFormPatch, Y as setByPath, a as DeviceStatusSchema, at as BaseAddon, b as deviceManagerCapability, c as STREAM_PROFILE_META, dt as DeviceType, g as applyTransform, i as CAP_NAMES_WITH_STATUS, it as errMsg, kt as sleep, lt as DeviceFeature, mt as WELL_KNOWN_TAB_MAP, rt as validateExpressionSource, t as ALL_CAPABILITY_DEFINITIONS, tt as toExpressionValue, ut as DeviceRole, x as deviceStateCapability, y as buildStreamParamsConfigSchema } from "../../dist-D7-BTTgZ.mjs";
1
+ import { A as getByPath, At as EventCategory, Ct as isDeviceConfigCap, D as evaluateLinkExpression, E as enumerateSchemaFields, H as normalizeUnit, S as deviceStatusCapability, T as enumerateItemArrayFields, W as parseStreamParamsFormPatch, Y as setByPath, a as DeviceStatusSchema, at as BaseAddon, b as deviceManagerCapability, c as STREAM_PROFILE_META, dt as DeviceType, g as applyTransform, i as CAP_NAMES_WITH_STATUS, it as errMsg, kt as sleep, lt as DeviceFeature, mt as WELL_KNOWN_TAB_MAP, rt as validateExpressionSource, t as ALL_CAPABILITY_DEFINITIONS, tt as toExpressionValue, ut as DeviceRole, x as deviceStateCapability, y as buildStreamParamsConfigSchema } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -1204,6 +1204,9 @@ async function testField(pctx, input) {
1204
1204
  /** Reserved device-config keys (cf. `_profileMap`). */
1205
1205
  var LINKED_MODE_CONFIG_KEY = "_linkedDevicesMode";
1206
1206
  var LINKED_IDS_CONFIG_KEY = "_linkedDeviceIds";
1207
+ /** Reserved key: subset of linked devices that materialize synthetic tracked
1208
+ * events on the camera. Absent ⇒ ALL linked devices produce tracked events. */
1209
+ var LINKED_TRACKED_IDS_CONFIG_KEY = "_linkedDeviceTrackedIds";
1207
1210
  /** Parse the two reserved keys out of a device-config blob. Tolerant:
1208
1211
  * ids may arrive as numbers or numeric strings (the multiselect field
1209
1212
  * stores string option values); anything else is dropped. */
@@ -1239,6 +1242,25 @@ function resolveLinkedDeviceIds(params) {
1239
1242
  ids.delete(selfId);
1240
1243
  return [...ids];
1241
1244
  }
1245
+ /**
1246
+ * Which linked devices materialize synthetic tracked events on the camera.
1247
+ * Absent / non-array config key ⇒ ALL linked devices (back-compat with the
1248
+ * pre-existing "every linked device produces events" behaviour). Otherwise the
1249
+ * configured subset intersected with the currently-resolved linked set (a
1250
+ * picked id no longer linked is dropped). Ids may arrive as numbers or numeric
1251
+ * strings (the multiselect stores string option values); non-numeric entries
1252
+ * are ignored.
1253
+ */
1254
+ function resolveTrackedEventDeviceIds(config, linkedIds) {
1255
+ const raw = config[LINKED_TRACKED_IDS_CONFIG_KEY];
1256
+ if (!Array.isArray(raw)) return linkedIds;
1257
+ const picked = /* @__PURE__ */ new Set();
1258
+ for (const v of raw) {
1259
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
1260
+ if (Number.isInteger(n)) picked.add(n);
1261
+ }
1262
+ return linkedIds.filter((id) => picked.has(id));
1263
+ }
1242
1264
  /** Cap implementation of `getLinkedDevices`. */
1243
1265
  async function getLinkedDevices(pctx, input) {
1244
1266
  const { deviceId } = input;
@@ -1251,6 +1273,7 @@ async function getLinkedDevices(pctx, input) {
1251
1273
  config,
1252
1274
  candidates: all
1253
1275
  });
1276
+ const trackedIds = new Set(resolveTrackedEventDeviceIds(blob, ids));
1254
1277
  const devices = [];
1255
1278
  for (const id of ids) {
1256
1279
  const d = byId.get(id);
@@ -1259,7 +1282,8 @@ async function getLinkedDevices(pctx, input) {
1259
1282
  deviceId: d.id,
1260
1283
  name: d.name,
1261
1284
  location: d.location,
1262
- features: d.features
1285
+ features: d.features,
1286
+ producesTrackedEvents: trackedIds.has(d.id)
1263
1287
  });
1264
1288
  }
1265
1289
  return {
@@ -1282,36 +1306,63 @@ async function buildLinkedDevicesContribution(pctx, deviceId) {
1282
1306
  value: String(d.id),
1283
1307
  label: d.location !== null ? `${d.name} (${d.location})` : d.name
1284
1308
  }));
1309
+ const linkedIds = resolveLinkedDeviceIds({
1310
+ selfId: deviceId,
1311
+ selfLocation: all.find((d) => d.id === deviceId)?.location ?? null,
1312
+ config,
1313
+ candidates: all
1314
+ });
1315
+ const byId = new Map(all.map((d) => [d.id, d]));
1316
+ const trackedOptions = linkedIds.flatMap((id) => {
1317
+ const d = byId.get(id);
1318
+ if (!d) return [];
1319
+ return [{
1320
+ value: String(d.id),
1321
+ label: d.location !== null ? `${d.name} (${d.location})` : d.name
1322
+ }];
1323
+ });
1324
+ const trackedValue = resolveTrackedEventDeviceIds(blob, linkedIds).map(String);
1285
1325
  return { sections: [{
1286
1326
  id: "linked-devices",
1287
1327
  title: "Linked devices",
1288
1328
  description: "Companion devices associated with this camera. Device-tree children are always linked; auto mode also links every device sharing the camera location, manual mode links the picked list instead.",
1289
1329
  order: 70,
1290
- fields: [{
1291
- type: "select",
1292
- key: LINKED_MODE_CONFIG_KEY,
1293
- label: "Link mode",
1294
- description: "Auto: children + same-location devices. Manual: children + the list below.",
1295
- options: [{
1296
- value: "auto",
1297
- label: "Auto (location-based)"
1298
- }, {
1299
- value: "manual",
1300
- label: "Manual"
1301
- }],
1302
- value: config.mode
1303
- }, {
1304
- type: "multiselect",
1305
- key: LINKED_IDS_CONFIG_KEY,
1306
- label: "Manually linked devices",
1307
- description: "Used in manual mode (children stay linked regardless).",
1308
- showWhen: {
1309
- field: LINKED_MODE_CONFIG_KEY,
1310
- equals: "manual"
1330
+ fields: [
1331
+ {
1332
+ type: "select",
1333
+ key: LINKED_MODE_CONFIG_KEY,
1334
+ label: "Link mode",
1335
+ description: "Auto: children + same-location devices. Manual: children + the list below.",
1336
+ options: [{
1337
+ value: "auto",
1338
+ label: "Auto (location-based)"
1339
+ }, {
1340
+ value: "manual",
1341
+ label: "Manual"
1342
+ }],
1343
+ value: config.mode
1311
1344
  },
1312
- options,
1313
- value: config.manualIds.map(String)
1314
- }]
1345
+ {
1346
+ type: "multiselect",
1347
+ key: LINKED_IDS_CONFIG_KEY,
1348
+ label: "Manually linked devices",
1349
+ description: "Used in manual mode (children stay linked regardless).",
1350
+ showWhen: {
1351
+ field: LINKED_MODE_CONFIG_KEY,
1352
+ equals: "manual"
1353
+ },
1354
+ options,
1355
+ value: config.manualIds.map(String)
1356
+ },
1357
+ {
1358
+ type: "multiselect",
1359
+ key: LINKED_TRACKED_IDS_CONFIG_KEY,
1360
+ label: "Devices that produce tracked events",
1361
+ description: "Linked devices whose state changes materialize a synthetic tracked event on this camera. Default: all linked devices.",
1362
+ options: trackedOptions,
1363
+ value: trackedValue
1364
+ }
1365
+ ]
1315
1366
  }] };
1316
1367
  }
1317
1368
  /**
@@ -1324,6 +1375,15 @@ async function applyLinkedDevicesPatch(pctx, deviceId, patch) {
1324
1375
  const next = { ...await pctx.settings.readDeviceStore(deviceId) };
1325
1376
  if ("_linkedDevicesMode" in patch) next[LINKED_MODE_CONFIG_KEY] = patch["_linkedDevicesMode"] === "manual" ? "manual" : "auto";
1326
1377
  if ("_linkedDeviceIds" in patch) next[LINKED_IDS_CONFIG_KEY] = [...parseLinkedDevicesConfig({ [LINKED_IDS_CONFIG_KEY]: patch[LINKED_IDS_CONFIG_KEY] }).manualIds];
1378
+ if ("_linkedDeviceTrackedIds" in patch) {
1379
+ const raw = patch[LINKED_TRACKED_IDS_CONFIG_KEY];
1380
+ const ids = [];
1381
+ if (Array.isArray(raw)) for (const v of raw) {
1382
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
1383
+ if (Number.isInteger(n) && !ids.includes(n)) ids.push(n);
1384
+ }
1385
+ next[LINKED_TRACKED_IDS_CONFIG_KEY] = ids;
1386
+ }
1327
1387
  await pctx.settings.writeDeviceStore(deviceId, next);
1328
1388
  return { success: true };
1329
1389
  }
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  //#region src/builtins/doorbell/trigger-engine.ts
8
8
  /**
9
9
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, C as doorbellCapability, at as BaseAddon, dt as DeviceType, it as errMsg, vt as createEvent } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, C as doorbellCapability, at as BaseAddon, dt as DeviceType, it as errMsg, vt as createEvent } from "../../dist-BLNvh8Qy.mjs";
2
2
  //#region src/builtins/doorbell/trigger-engine.ts
3
3
  /**
4
4
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { R as logDestinationCapability, at as BaseAddon } from "../../dist-D7-BTTgZ.mjs";
1
+ import { R as logDestinationCapability, at as BaseAddon } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,4 +1,4 @@
1
- import { _ as authProviderCapability, at as BaseAddon, d as UserRecordSchema, l as ScopedTokenSchema, nt as userManagementCapability, r as ApiKeyRecordSchema } from "../../dist-D7-BTTgZ.mjs";
1
+ import { _ as authProviderCapability, at as BaseAddon, d as UserRecordSchema, l as ScopedTokenSchema, nt as userManagementCapability, r as ApiKeyRecordSchema } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as crypto$1 from "node:crypto";
4
4
  import { randomUUID } from "node:crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-B9OD5Tdn.js");
3
+ const require_dist = require("../../dist-DP2SKMvU.js");
4
4
  let node_os = require("node:os");
5
5
  node_os = require_chunk.__toESM(node_os);
6
6
  //#region src/builtins/local-network/local-network.addon.ts
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, L as localNetworkCapability, at as BaseAddon } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, L as localNetworkCapability, at as BaseAddon } from "../../dist-BLNvh8Qy.mjs";
2
2
  import * as os from "node:os";
3
3
  //#region src/builtins/local-network/local-network.addon.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, B as metricsProviderCapability, at as BaseAddon, vt as createEvent } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, B as metricsProviderCapability, at as BaseAddon, vt as createEvent } from "../../dist-BLNvh8Qy.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile, execFileSync } from "node:child_process";
4
4
  import { promisify } from "node:util";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, G as platformProbeCapability, J as scoreRuntimes, at as BaseAddon, bt as emitReadiness, it as errMsg, w as enumerateInferenceDevices } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, G as platformProbeCapability, J as scoreRuntimes, at as BaseAddon, bt as emitReadiness, it as errMsg, w as enumerateInferenceDevices } from "../../dist-BLNvh8Qy.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { At as EventCategory, at as BaseAddon } from "../../dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory, at as BaseAddon } from "../../dist-BLNvh8Qy.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  //#region src/builtins/snapshot/snapshot-coalescing.ts
9
9
  /**
@@ -1,4 +1,4 @@
1
- import { V as nodePin, Z as snapshotCapability, at as BaseAddon, dt as DeviceType, et as streamQualityLabel, it as errMsg, lt as DeviceFeature } from "../../dist-D7-BTTgZ.mjs";
1
+ import { V as nodePin, Z as snapshotCapability, at as BaseAddon, dt as DeviceType, et as streamQualityLabel, it as errMsg, lt as DeviceFeature } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { execFile } from "node:child_process";
3
3
  //#region src/builtins/snapshot/snapshot-coalescing.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { $ as storageProviderCapability, at as BaseAddon, k as filesystemBrowseCapability } from "../../dist-D7-BTTgZ.mjs";
1
+ import { $ as storageProviderCapability, at as BaseAddon, k as filesystemBrowseCapability } from "../../dist-BLNvh8Qy.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import * as path$1 from "node:path";
4
4
  import { basename, dirname, join, resolve, sep } from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let better_sqlite3 = require("better-sqlite3");
9
9
  better_sqlite3 = require_chunk.__toESM(better_sqlite3);
@@ -1,4 +1,4 @@
1
- import { Tt as parseJsonUnknown, X as settingsStoreCapability, at as BaseAddon, ht as asJsonObject, it as errMsg, s as RUNTIME_DEFAULTS } from "../../dist-D7-BTTgZ.mjs";
1
+ import { Tt as parseJsonUnknown, X as settingsStoreCapability, at as BaseAddon, ht as asJsonObject, it as errMsg, s as RUNTIME_DEFAULTS } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import Database from "better-sqlite3";
4
4
  //#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_chunk.__toESM(node_path);
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { Q as storageCapability, at as BaseAddon, u as StorageLocationTypeSchema, wt as parseJsonObject } from "../../dist-D7-BTTgZ.mjs";
1
+ import { Q as storageCapability, at as BaseAddon, u as StorageLocationTypeSchema, wt as parseJsonObject } from "../../dist-BLNvh8Qy.mjs";
2
2
  import * as path$1 from "node:path";
3
3
  import * as fs from "node:fs/promises";
4
4
  import { buildStorageLocationRegistry } from "@camstack/system";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { St as hydrateSchema, at as BaseAddon, it as errMsg } from "../../dist-D7-BTTgZ.mjs";
1
+ import { St as hydrateSchema, at as BaseAddon, it as errMsg } from "../../dist-BLNvh8Qy.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-B9OD5Tdn.js");
6
+ const require_dist = require("../../dist-DP2SKMvU.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { R as logDestinationCapability, at as BaseAddon } from "../../dist-D7-BTTgZ.mjs";
1
+ import { R as logDestinationCapability, at as BaseAddon } from "../../dist-BLNvh8Qy.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -8989,7 +8989,21 @@ var NativeCropResultSchema = z.object({
8989
8989
  /** Packed rgb (24-bit) pixels of the crop. */
8990
8990
  bytes: z.instanceof(Uint8Array),
8991
8991
  width: z.number().int().positive(),
8992
- height: z.number().int().positive()
8992
+ height: z.number().int().positive(),
8993
+ /**
8994
+ * Which source served this crop, so a quality-sensitive consumer (the native
8995
+ * `keyFrame`) can reject a degraded fallback:
8996
+ * - `native` — cut from the decode worker's retained NATIVE surface (the
8997
+ * quality path).
8998
+ * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
8999
+ * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
9000
+ * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
9001
+ *
9002
+ * OPTIONAL: a pre-tier runner (version skew) omits it — an ABSENT `tier` MUST be
9003
+ * treated as `native` (accepted) by every consumer so mixed-version clusters
9004
+ * keep the pre-tier behaviour. An ROI miss returns `null` (no tier at all).
9005
+ */
9006
+ tier: z.enum(["native", "ram-fullframe"]).optional()
8993
9007
  });
8994
9008
  /** Parent detection context passed to `runDetailSubtree` — the crop's
8995
9009
  * originating detection, in FRAME-space coordinates. Reuses
@@ -9343,10 +9357,15 @@ var pipelineRunnerCapability = {
9343
9357
  * `FrameHandle` that rode an inference-result event and a normalized `bbox`,
9344
9358
  * the runner asks the decode worker still holding that frame's NATIVE
9345
9359
  * surface to GPU/CPU-crop ONLY the ROI at native res and download just the
9346
- * crop. Returns `null` on a miss (handle not registered, or the worker's
9347
- * tiny native-retention ring already evicted the frame) — the caller falls
9348
- * back to a detection-frame crop. Routed to the frame's owning node by
9349
- * `handle.nodeId`; NEVER ships a full native frame.
9360
+ * crop. Routed to the frame's owning node by `handle.nodeId`; NEVER ships a
9361
+ * full native frame.
9362
+ *
9363
+ * Miss behaviour differs by request shape (see `NativeCropResultSchema.tier`):
9364
+ * - an ROI (subject-crop) miss returns `null` — the caller reports the miss
9365
+ * and retries; it is NEVER handed a ≤640 crop masquerading as native.
9366
+ * - a FULL-FRAME miss falls back to the ≤640 RAM `RetainedFrameStore`, tagged
9367
+ * `tier: 'ram-fullframe'` (honest lower-res for the blank-frame guard /
9368
+ * 640-snapshot resolve). A native hit is tagged `tier: 'native'`.
9350
9369
  */
9351
9370
  getNativeCrop: method(z.object({
9352
9371
  handle: FrameHandleSchema,
@@ -14081,7 +14100,8 @@ var LinkedDeviceSchema = z.object({
14081
14100
  deviceId: z.number(),
14082
14101
  name: z.string(),
14083
14102
  location: z.string().nullable(),
14084
- features: z.array(z.string())
14103
+ features: z.array(z.string()),
14104
+ producesTrackedEvents: z.boolean().optional()
14085
14105
  });
14086
14106
  var SavedDeviceRowSchema = z.object({
14087
14107
  /** Numeric id reserved at allocateDeviceId time. */
@@ -16307,6 +16327,7 @@ var TrackSchema = z.object({
16307
16327
  deviceId: z.number(),
16308
16328
  className: z.string(),
16309
16329
  label: z.string().optional(),
16330
+ producingDeviceName: z.string().optional(),
16310
16331
  /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16311
16332
  source: TrackSourceSchema.optional(),
16312
16333
  firstSeen: z.number(),
@@ -16444,7 +16465,8 @@ var MediaFileKindEnum = z.enum([
16444
16465
  "fullFrameBoxed",
16445
16466
  "faceCrop",
16446
16467
  "plateCrop",
16447
- "keyFrame"
16468
+ "keyFrame",
16469
+ "keyFrameSmall"
16448
16470
  ]);
16449
16471
  var MediaFileSchema = z.object({
16450
16472
  key: z.string(),
@@ -8989,7 +8989,21 @@ var NativeCropResultSchema = zod.z.object({
8989
8989
  /** Packed rgb (24-bit) pixels of the crop. */
8990
8990
  bytes: zod.z.instanceof(Uint8Array),
8991
8991
  width: zod.z.number().int().positive(),
8992
- height: zod.z.number().int().positive()
8992
+ height: zod.z.number().int().positive(),
8993
+ /**
8994
+ * Which source served this crop, so a quality-sensitive consumer (the native
8995
+ * `keyFrame`) can reject a degraded fallback:
8996
+ * - `native` — cut from the decode worker's retained NATIVE surface (the
8997
+ * quality path).
8998
+ * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
8999
+ * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
9000
+ * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
9001
+ *
9002
+ * OPTIONAL: a pre-tier runner (version skew) omits it — an ABSENT `tier` MUST be
9003
+ * treated as `native` (accepted) by every consumer so mixed-version clusters
9004
+ * keep the pre-tier behaviour. An ROI miss returns `null` (no tier at all).
9005
+ */
9006
+ tier: zod.z.enum(["native", "ram-fullframe"]).optional()
8993
9007
  });
8994
9008
  /** Parent detection context passed to `runDetailSubtree` — the crop's
8995
9009
  * originating detection, in FRAME-space coordinates. Reuses
@@ -9343,10 +9357,15 @@ var pipelineRunnerCapability = {
9343
9357
  * `FrameHandle` that rode an inference-result event and a normalized `bbox`,
9344
9358
  * the runner asks the decode worker still holding that frame's NATIVE
9345
9359
  * surface to GPU/CPU-crop ONLY the ROI at native res and download just the
9346
- * crop. Returns `null` on a miss (handle not registered, or the worker's
9347
- * tiny native-retention ring already evicted the frame) — the caller falls
9348
- * back to a detection-frame crop. Routed to the frame's owning node by
9349
- * `handle.nodeId`; NEVER ships a full native frame.
9360
+ * crop. Routed to the frame's owning node by `handle.nodeId`; NEVER ships a
9361
+ * full native frame.
9362
+ *
9363
+ * Miss behaviour differs by request shape (see `NativeCropResultSchema.tier`):
9364
+ * - an ROI (subject-crop) miss returns `null` — the caller reports the miss
9365
+ * and retries; it is NEVER handed a ≤640 crop masquerading as native.
9366
+ * - a FULL-FRAME miss falls back to the ≤640 RAM `RetainedFrameStore`, tagged
9367
+ * `tier: 'ram-fullframe'` (honest lower-res for the blank-frame guard /
9368
+ * 640-snapshot resolve). A native hit is tagged `tier: 'native'`.
9350
9369
  */
9351
9370
  getNativeCrop: method(zod.z.object({
9352
9371
  handle: FrameHandleSchema,
@@ -14081,7 +14100,8 @@ var LinkedDeviceSchema = zod.z.object({
14081
14100
  deviceId: zod.z.number(),
14082
14101
  name: zod.z.string(),
14083
14102
  location: zod.z.string().nullable(),
14084
- features: zod.z.array(zod.z.string())
14103
+ features: zod.z.array(zod.z.string()),
14104
+ producesTrackedEvents: zod.z.boolean().optional()
14085
14105
  });
14086
14106
  var SavedDeviceRowSchema = zod.z.object({
14087
14107
  /** Numeric id reserved at allocateDeviceId time. */
@@ -16307,6 +16327,7 @@ var TrackSchema = zod.z.object({
16307
16327
  deviceId: zod.z.number(),
16308
16328
  className: zod.z.string(),
16309
16329
  label: zod.z.string().optional(),
16330
+ producingDeviceName: zod.z.string().optional(),
16310
16331
  /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16311
16332
  source: TrackSourceSchema.optional(),
16312
16333
  firstSeen: zod.z.number(),
@@ -16444,7 +16465,8 @@ var MediaFileKindEnum = zod.z.enum([
16444
16465
  "fullFrameBoxed",
16445
16466
  "faceCrop",
16446
16467
  "plateCrop",
16447
- "keyFrame"
16468
+ "keyFrame",
16469
+ "keyFrameSmall"
16448
16470
  ]);
16449
16471
  var MediaFileSchema = zod.z.object({
16450
16472
  key: zod.z.string(),
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-B9OD5Tdn.js");
3
+ const require_dist = require("./dist-DP2SKMvU.js");
4
4
  const require_model_download_service = require("./model-download-service-hf0ookyy.js");
5
- const require_manifest_python_deps = require("./manifest-python-deps-DXqcuOha.js");
5
+ const require_manifest_python_deps = require("./manifest-python-deps-DE8HxnIx.js");
6
6
  const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
7
7
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
8
8
  const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { At as EventCategory$1, Dt as resolveCapMount, Et as readinessKey, F as kebabToCamel, I as lifecycleJobSchema, K as procedureAuthKey, M as isCollectionArrayMethod, N as isObjectInput, O as extractNestedAddonId, Ot as scopeKey, P as isVoidInput, Tt as parseJsonUnknown$1, U as objectInputDeclaresAddonId, _t as asString$1, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, j as isArrayOutputSchema, o as METHOD_ACCESS_MAP, ot as DATAPLANE_SECRET_HEADER$1, p as addonSettingsCapability, pt as ReadinessTimeoutError, s as RUNTIME_DEFAULTS, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, wt as parseJsonObject, xt as expandCapMethods, yt as emitDownForOwnedCaps, z as looseSchema } from "./dist-D7-BTTgZ.mjs";
1
+ import { At as EventCategory$1, Dt as resolveCapMount, Et as readinessKey, F as kebabToCamel, I as lifecycleJobSchema, K as procedureAuthKey, M as isCollectionArrayMethod, N as isObjectInput, O as extractNestedAddonId, Ot as scopeKey, P as isVoidInput, Tt as parseJsonUnknown$1, U as objectInputDeclaresAddonId, _t as asString$1, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, j as isArrayOutputSchema, o as METHOD_ACCESS_MAP, ot as DATAPLANE_SECRET_HEADER$1, p as addonSettingsCapability, pt as ReadinessTimeoutError, s as RUNTIME_DEFAULTS, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, wt as parseJsonObject, xt as expandCapMethods, yt as emitDownForOwnedCaps, z as looseSchema } from "./dist-BLNvh8Qy.mjs";
2
2
  import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-Cp9f4dk6.mjs";
3
- import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as createAddonDataPlaneFacility, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveAddonClass, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as resolveNpmInvocation, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, x as getCapUsageRegistry, xt as runNpm, y as CapUsageRegistry, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-D7EIakIT.mjs";
3
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as createAddonDataPlaneFacility, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveAddonClass, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as resolveNpmInvocation, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, x as getCapUsageRegistry, xt as runNpm, y as CapUsageRegistry, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-Co8MTPdy.mjs";
4
4
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Le3l0qS2.mjs";
5
5
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
6
6
  import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
@@ -1,4 +1,4 @@
1
- import { q as readNodePin } from "./dist-D7-BTTgZ.mjs";
1
+ import { q as readNodePin } from "./dist-BLNvh8Qy.mjs";
2
2
  import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
3
3
  import { createServer } from "node:http";
4
4
  import * as fs from "node:fs";
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_dist = require("./dist-B9OD5Tdn.js");
2
+ const require_dist = require("./dist-DP2SKMvU.js");
3
3
  let _camstack_types_node = require("@camstack/types/node");
4
4
  let node_http = require("node:http");
5
5
  let node_fs = require("node:fs");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",