camstack 1.2.177 → 1.2.179

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.
@@ -24024,9 +24024,9 @@ var require_zod = __commonJS({
24024
24024
  }
24025
24025
  });
24026
24026
 
24027
- // ../system/dist/dist-DeGPW01G.js
24028
- var require_dist_DeGPW01G = __commonJS({
24029
- "../system/dist/dist-DeGPW01G.js"(exports) {
24027
+ // ../system/dist/dist-DePB7uwU.js
24028
+ var require_dist_DePB7uwU = __commonJS({
24029
+ "../system/dist/dist-DePB7uwU.js"(exports) {
24030
24030
  "use strict";
24031
24031
  var zod = require_zod();
24032
24032
  __require("crypto");
@@ -29287,11 +29287,11 @@ var require_dist_DeGPW01G = __commonJS({
29287
29287
  /** Quantization axis. Omit ⇒ the fp32 base build. */
29288
29288
  precision: zod.z.enum(["fp32", "int8"]).optional(),
29289
29289
  /**
29290
- * Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
29291
- * latency-optimized export (e.g. ReLU-activation variant) — the slot the
29292
- * future performance variants plug into.
29290
+ * Training / latency-variant axis. Omit ⇒ the standard build. `test` marks
29291
+ * an alternate training of the same family (Scrypted `relu_test`); chip
29292
+ * label is "Test".
29293
29293
  */
29294
- optimization: zod.z.enum(["standard", "fast"]).optional(),
29294
+ optimization: zod.z.enum(["standard", "test"]).optional(),
29295
29295
  /**
29296
29296
  * Input-resolution axis (square input side, px). Omit ⇒ the family's native
29297
29297
  * resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
@@ -37878,7 +37878,7 @@ var require_dist_DeGPW01G = __commonJS({
37878
37878
  mode: "singleton",
37879
37879
  internal: true,
37880
37880
  methods: { distributeModel: method(zod.z.object({
37881
- nodeId: zod.z.string(),
37881
+ targetNodeId: zod.z.string(),
37882
37882
  modelId: zod.z.string(),
37883
37883
  format: zod.z.enum(MODEL_FORMATS),
37884
37884
  entry: ModelCatalogEntrySchema
@@ -39852,6 +39852,7 @@ var require_dist_DeGPW01G = __commonJS({
39852
39852
  "detection-blind",
39853
39853
  "nodes",
39854
39854
  "addons",
39855
+ "addon-updates",
39855
39856
  "updates"
39856
39857
  ]);
39857
39858
  var NcStateSummaryPreviewSectionSchema = zod.z.object({
@@ -40238,7 +40239,19 @@ var require_dist_DeGPW01G = __commonJS({
40238
40239
  * When this mode may be armed. Absent / null = always allowed (same as a
40239
40240
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
40240
40241
  */
40241
- schedule: NcScheduleSchema.optional()
40242
+ schedule: NcScheduleSchema.optional(),
40243
+ /**
40244
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
40245
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
40246
+ * only one, and the matches ride the triggered notification's mosaic (D705).
40247
+ */
40248
+ notifyDuringEntryDelay: zod.z.boolean().optional(),
40249
+ /**
40250
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
40251
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
40252
+ * is the one that starts the entry delay — the entry-delay notification already names it.
40253
+ */
40254
+ notifySensorRewatched: zod.z.boolean().optional()
40242
40255
  });
40243
40256
  var NcAlarmModeDelaysListSchema = zod.z.array(NcAlarmModeDelaysSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
40244
40257
  var NcAlarmModeDelaysEditSchema = zod.z.object({
@@ -40248,7 +40261,11 @@ var require_dist_DeGPW01G = __commonJS({
40248
40261
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
40249
40262
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
40250
40263
  /** Set a schedule, or `null` to clear (mode always allowed again). */
40251
- schedule: NcScheduleSchema.nullable().optional()
40264
+ schedule: NcScheduleSchema.nullable().optional(),
40265
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
40266
+ notifyDuringEntryDelay: zod.z.boolean().nullable().optional(),
40267
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
40268
+ notifySensorRewatched: zod.z.boolean().nullable().optional()
40252
40269
  });
40253
40270
  var NcAlarmModeDelaysEditListSchema = zod.z.array(NcAlarmModeDelaysEditSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
40254
40271
  var NcAlarmNonBlockingEditSchema = zod.z.object({
@@ -41088,39 +41105,19 @@ var require_dist_DeGPW01G = __commonJS({
41088
41105
  label: "Face recognition model",
41089
41106
  defaultModelId: "auraface-r100",
41090
41107
  indexName: "the enrolled face gallery",
41091
- options: [
41092
- {
41093
- id: "auraface-r100",
41094
- label: "AuraFace R100"
41095
- },
41096
- {
41097
- id: "arcface-r100",
41098
- label: "ArcFace (ResNet34)"
41099
- },
41100
- {
41101
- id: "inception-resnet-v1",
41102
- label: "Inception ResNet V1"
41103
- }
41104
- ]
41108
+ options: [{
41109
+ id: "auraface-r100",
41110
+ label: "AuraFace R100"
41111
+ }]
41105
41112
  }, {
41106
41113
  stepId: "clip-embedding",
41107
41114
  label: "Semantic search model",
41108
- defaultModelId: "mobileclip-s1",
41115
+ defaultModelId: "siglip2-b16-224",
41109
41116
  indexName: "the object semantic-search index",
41110
- options: [
41111
- {
41112
- id: "mobileclip-s1",
41113
- label: "MobileCLIP S1"
41114
- },
41115
- {
41116
- id: "mobileclip-s2",
41117
- label: "MobileCLIP S2"
41118
- },
41119
- {
41120
- id: "siglip2-b16-224",
41121
- label: "SigLIP2 B/16 (Apache-2.0)"
41122
- }
41123
- ]
41117
+ options: [{
41118
+ id: "siglip2-b16-224",
41119
+ label: "SigLIP2 B/16 (Apache-2.0)"
41120
+ }]
41124
41121
  }].map((s) => [s.stepId, s.defaultModelId])));
41125
41122
  var CLUSTER_STEP_SETTING_FIELDS = [{
41126
41123
  stepId: "face-embedding",
@@ -44238,6 +44235,11 @@ var require_dist_DeGPW01G = __commonJS({
44238
44235
  "analyzer",
44239
44236
  "device-activity"
44240
44237
  ]);
44238
+ var SessionTriggerSchema = zod.z.object({
44239
+ source: MotionSourceEnum,
44240
+ triggeredBy: MotionTriggeredBySchema,
44241
+ sensorName: zod.z.string().optional()
44242
+ });
44241
44243
  var MotionSourcesSchema = zod.z.array(MotionSourceEnum);
44242
44244
  var MOTION_SOURCE_LABELS = {
44243
44245
  analyzer: "Frame-diff Analyzer (motion addon)",
@@ -44385,6 +44387,14 @@ var require_dist_DeGPW01G = __commonJS({
44385
44387
  */
44386
44388
  motionSources: MotionSourcesSchema.default(["analyzer"]),
44387
44389
  /**
44390
+ * What opened this attach, on a SESSION attach only (see
44391
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
44392
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
44393
+ * behind it, and every attach from an orchestrator older than the field — the
44394
+ * runner then names no source on the phase event.
44395
+ */
44396
+ sessionTrigger: SessionTriggerSchema.optional(),
44397
+ /**
44388
44398
  * WHICH root detector runs for this camera. The detection counterpart of
44389
44399
  * {@link motionSources}, same shape and same discipline: a per-camera list
44390
44400
  * of sources the system understands, vendor-agnostic, owned here.
@@ -48125,6 +48135,36 @@ var require_dist_DeGPW01G = __commonJS({
48125
48135
  })
48126
48136
  }
48127
48137
  };
48138
+ var CAMSTACK_APP_LINK_ORIGIN = "https://camstack.zentik.app";
48139
+ function toAppLinkHubOrigin(baseUrl) {
48140
+ let parsed;
48141
+ try {
48142
+ parsed = new URL(baseUrl);
48143
+ } catch {
48144
+ return null;
48145
+ }
48146
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
48147
+ if (parsed.username !== "" || parsed.password !== "") return null;
48148
+ if (parsed.hostname === "") return null;
48149
+ return parsed.origin;
48150
+ }
48151
+ function pickAppLinkHub(endpoints) {
48152
+ const rank2 = (e) => {
48153
+ if (e.kind !== "public" || !e.baseUrl.startsWith("https://")) return null;
48154
+ if (e.interfaceKind === "public") return 0;
48155
+ if (e.interfaceKind === "vpn") return 1;
48156
+ return null;
48157
+ };
48158
+ return endpoints.flatMap((e) => {
48159
+ const r = rank2(e);
48160
+ const origin = toAppLinkHubOrigin(e.baseUrl);
48161
+ return r === null || origin === null ? [] : [{
48162
+ r,
48163
+ p: e.priority,
48164
+ origin
48165
+ }];
48166
+ }).toSorted((a, b) => a.r - b.r || a.p - b.p)[0]?.origin ?? null;
48167
+ }
48128
48168
  var webrtcClientHintsSchema = zod.z.object({
48129
48169
  viewportWidth: zod.z.number().int().positive().optional(),
48130
48170
  viewportHeight: zod.z.number().int().positive().optional(),
@@ -51967,7 +52007,7 @@ var require_dist_DeGPW01G = __commonJS({
51967
52007
  name: zod.z.string(),
51968
52008
  family: zod.z.enum(["IPv4", "IPv6"]),
51969
52009
  address: zod.z.string(),
51970
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
52010
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
51971
52011
  cidr: zod.z.string(),
51972
52012
  /** OS netmask. Stable across calls so the SDK can hash on it. */
51973
52013
  netmask: zod.z.string(),
@@ -52016,7 +52056,8 @@ var require_dist_DeGPW01G = __commonJS({
52016
52056
  "lan-ipv4",
52017
52057
  "lan-ipv6",
52018
52058
  "public",
52019
- "loopback"
52059
+ "loopback",
52060
+ "app-link"
52020
52061
  ]),
52021
52062
  /**
52022
52063
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -52081,8 +52122,20 @@ var require_dist_DeGPW01G = __commonJS({
52081
52122
  var NotificationEndpointSchema = zod.z.object({
52082
52123
  /** The operator's explicit choice, or null for AUTO. */
52083
52124
  baseUrl: zod.z.string().nullable(),
52084
- /** What the ranking currently resolves to (null when nothing is reachable). */
52085
- resolved: zod.z.string().nullable()
52125
+ /**
52126
+ * The effective flagged address: the explicit choice, else (AUTO) the
52127
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
52128
+ * answered the public tunnel / mesh here, or null.
52129
+ */
52130
+ resolved: zod.z.string().nullable(),
52131
+ /**
52132
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
52133
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
52134
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
52135
+ * hub advertises neither; the app link is then minted without `hub=`.
52136
+ * Absent on a pre-D709 hub.
52137
+ */
52138
+ appLinkHub: zod.z.string().nullable()
52086
52139
  });
52087
52140
  var ViewerEndpointsSchema = zod.z.object({
52088
52141
  /** The operator's explicit race set, or empty for AUTO. */
@@ -52146,7 +52199,7 @@ var require_dist_DeGPW01G = __commonJS({
52146
52199
  * every client a second authority on a socket only the hub binds. A viewer
52147
52200
  * configured with a port-less public URL (`https://camstack.example.top`)
52148
52201
  * infers 443 from the scheme, asks with 443, and was handed
52149
- * `https://192.168.x.x:443` for a hub listening on 4443. That candidate
52202
+ * `https://192.168.10.9:443` for a hub listening on 4443. That candidate
52150
52203
  * cannot ever answer, and losing its race reads in the log exactly like
52151
52204
  * "the LAN was tried and it was slower" — so an operator sitting on his own
52152
52205
  * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
@@ -52179,13 +52232,21 @@ var require_dist_DeGPW01G = __commonJS({
52179
52232
  * Pass `'https'` when the caller is itself loaded over HTTPS
52180
52233
  * to avoid mixed-content blocks in the browser. The public
52181
52234
  * tunnel always emits `https://` regardless. */
52182
- scheme: zod.z.enum(["http", "https"]).optional()
52235
+ scheme: zod.z.enum(["http", "https"]).optional(),
52236
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
52237
+ * an address notification links can be flagged with, not a hub
52238
+ * route. Default `false`: racers and rankers never see it. */
52239
+ includeAppLink: zod.z.boolean().optional()
52183
52240
  }), GetConnectionEndpointsResultSchema),
52184
52241
  /**
52185
- * The endpoint OUTBOUND artifacts must be addressed by — notification
52186
- * attachment links and deep links. `baseUrl: null` means AUTO: the first
52187
- * public candidate (a configured origin, then a connected tunnel, then the
52188
- * LAN), never loopback.
52242
+ * The address notification links are flagged with. `baseUrl: null` means
52243
+ * AUTO, which is the CamStack app link (D709): viewer taps go through
52244
+ * `https://camstack.zentik.app/…?hub=<appLinkHub>` and open the app. Any
52245
+ * hub address the palette lists can be flagged instead; then viewer taps,
52246
+ * attachment links and action buttons all use it. Signed media and
52247
+ * buttons never use the app link — with it flagged (or AUTO) they keep
52248
+ * the ranked hub ladder (a configured origin, a connected tunnel, then
52249
+ * the LAN), never loopback.
52189
52250
  *
52190
52251
  * Why an explicit marker: the automatic ranking cannot know which of
52191
52252
  * several reachable addresses the RECIPIENT can actually open. A link is
@@ -52544,7 +52605,7 @@ var require_dist_DeGPW01G = __commonJS({
52544
52605
  tags: zod.z.array(zod.z.string()).readonly(),
52545
52606
  /** True when the peer advertises itself as an exit-node candidate. */
52546
52607
  exitNodeOption: zod.z.boolean(),
52547
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
52608
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
52548
52609
  advertisedRoutes: zod.z.array(zod.z.string()).readonly(),
52549
52610
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
52550
52611
  userLogin: zod.z.string().nullable(),
@@ -53693,7 +53754,13 @@ var require_dist_DeGPW01G = __commonJS({
53693
53754
  */
53694
53755
  availablePositions: zod.z.array(OsdPositionEnum).readonly(),
53695
53756
  /** Firmware does not allow editing the `text` field (e.g. timestamp). */
53696
- readOnly: zod.z.boolean().optional()
53757
+ readOnly: zod.z.boolean().optional(),
53758
+ /**
53759
+ * Firmware text ceiling for this overlay. Absent = no known limit.
53760
+ * The OSD manager truncates here on every write — never stores its own
53761
+ * copy on the binding.
53762
+ */
53763
+ maxCharacters: zod.z.number().int().positive().optional()
53697
53764
  });
53698
53765
  var OsdStatusSchema = zod.z.object({ overlays: zod.z.array(OsdOverlaySchema).readonly() });
53699
53766
  var OsdOverlayPatchSchema = zod.z.object({
@@ -53781,7 +53848,9 @@ var require_dist_DeGPW01G = __commonJS({
53781
53848
  valuePath: zod.z.string().min(1).max(64),
53782
53849
  /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
53783
53850
  displayUnit: zod.z.string().max(16).optional(),
53784
- maxDecimals: zod.z.number().int().min(0).max(4).default(1)
53851
+ maxDecimals: zod.z.number().int().min(0).max(4).default(1),
53852
+ /** Raw value → display text (same shape as slot binding). Empty string hides. */
53853
+ valueMap: zod.z.record(zod.z.string(), zod.z.string()).optional()
53785
53854
  });
53786
53855
  var OsdNamedTemplateSchema = zod.z.object({
53787
53856
  id: zod.z.string().min(1).max(64),
@@ -53826,18 +53895,11 @@ var require_dist_DeGPW01G = __commonJS({
53826
53895
  source: OsdSourceSchema,
53827
53896
  /** `${value}` and `${unit}` are substituted; every occurrence. */
53828
53897
  template: zod.z.string().max(96).default("${value}"),
53829
- /** Truncate with an ellipsis past this length. Absent = no limit. */
53830
- maxCharacters: zod.z.number().int().min(4).max(64).optional(),
53831
53898
  /**
53832
53899
  * Decimal places for a numeric value. `0` yields an integer — the
53833
53900
  * documented workaround for firmwares that reject `.` in overlay text.
53834
53901
  */
53835
53902
  maxDecimals: zod.z.number().int().min(0).max(4).default(1),
53836
- /**
53837
- * Appended via `${unit}` when no unit conversion produces a label.
53838
- * Prefer `source.displayUnit` for converted device-state values.
53839
- */
53840
- unitLabel: zod.z.string().max(8).optional(),
53841
53903
  /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
53842
53904
  valueMap: zod.z.record(zod.z.string(), zod.z.string()).optional(),
53843
53905
  /** Time windows in which the slot is shown. Absent = always. */
@@ -53846,9 +53908,7 @@ var require_dist_DeGPW01G = __commonJS({
53846
53908
  * Display gate, in the notification centre's condition vocabulary.
53847
53909
  * Only the keys reported by `getConditionSupport` are accepted.
53848
53910
  */
53849
- conditions: NcConditionsSchema.optional(),
53850
- /** Rendered when the gate is closed or the value unreadable. Empty = hide. */
53851
- fallbackText: zod.z.string().max(64).default("")
53911
+ conditions: NcConditionsSchema.optional()
53852
53912
  });
53853
53913
  var OsdSlotViewSchema = zod.z.object({
53854
53914
  slotId: zod.z.string(),
@@ -72213,12 +72273,14 @@ var require_dist_DeGPW01G = __commonJS({
72213
72273
  {
72214
72274
  id: "addons",
72215
72275
  family: "system",
72216
- label: "Addons",
72217
- kinds: [
72218
- "addon-crash-loop",
72219
- "addon-update-available",
72220
- "addon-updated"
72221
- ]
72276
+ label: "Addons stopped",
72277
+ kinds: ["addon-crash-loop"]
72278
+ },
72279
+ {
72280
+ id: "addon-updates",
72281
+ family: "system",
72282
+ label: "Addon updates",
72283
+ kinds: ["addon-update-available", "addon-updated"]
72222
72284
  },
72223
72285
  {
72224
72286
  id: "updates",
@@ -72348,13 +72410,15 @@ var require_dist_DeGPW01G = __commonJS({
72348
72410
  },
72349
72411
  {
72350
72412
  name: "summaryAddons",
72351
- label: "Summary: addons",
72352
- example: "@camstack/addon-recorder 1.2.3 \u2192 1.2.4",
72353
- kinds: [
72354
- "addon-crash-loop",
72355
- "addon-update-available",
72356
- "addon-updated"
72357
- ]
72413
+ label: "Summary: addons stopped",
72414
+ example: "hub \u2014 recorder stopped after repeated crashes",
72415
+ kinds: ["addon-crash-loop"]
72416
+ },
72417
+ {
72418
+ name: "summaryAddonUpdates",
72419
+ label: "Summary: addon updates",
72420
+ example: "hub:\n\u2022 @camstack/addon-recorder 1.2.3 \u2192 1.2.4",
72421
+ kinds: ["addon-update-available", "addon-updated"]
72358
72422
  },
72359
72423
  {
72360
72424
  name: "summaryUpdates",
@@ -72438,6 +72502,7 @@ var require_dist_DeGPW01G = __commonJS({
72438
72502
  [...RULE_BODY_ONLY],
72439
72503
  [...RULE_TEXT],
72440
72504
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
72505
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
72441
72506
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
72442
72507
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
72443
72508
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -72478,6 +72543,8 @@ var require_dist_DeGPW01G = __commonJS({
72478
72543
  [...RULE_TEXT],
72479
72544
  [...RULE_TEXT],
72480
72545
  [...RULE_TEXT],
72546
+ [...RULE_TEXT],
72547
+ [...RULE_TEXT],
72481
72548
  ...STATE_SUMMARY_VARS
72482
72549
  ];
72483
72550
  var TimelapseTemplateSchema = zod.z.object({
@@ -72887,6 +72954,12 @@ var require_dist_DeGPW01G = __commonJS({
72887
72954
  return CAMERA_SWITCH_ORDER;
72888
72955
  }
72889
72956
  });
72957
+ Object.defineProperty(exports, "CAMSTACK_APP_LINK_ORIGIN", {
72958
+ enumerable: true,
72959
+ get: function() {
72960
+ return CAMSTACK_APP_LINK_ORIGIN;
72961
+ }
72962
+ });
72890
72963
  Object.defineProperty(exports, "CAP_NAMES_WITH_STATUS", {
72891
72964
  enumerable: true,
72892
72965
  get: function() {
@@ -73721,6 +73794,12 @@ var require_dist_DeGPW01G = __commonJS({
73721
73794
  return parseStreamParamsFormPatch;
73722
73795
  }
73723
73796
  });
73797
+ Object.defineProperty(exports, "pickAppLinkHub", {
73798
+ enumerable: true,
73799
+ get: function() {
73800
+ return pickAppLinkHub;
73801
+ }
73802
+ });
73724
73803
  Object.defineProperty(exports, "planComposition", {
73725
73804
  enumerable: true,
73726
73805
  get: function() {
@@ -73949,7 +74028,7 @@ var require_alerts_addon = __commonJS({
73949
74028
  [Symbol.toStringTag]: { value: "Module" }
73950
74029
  });
73951
74030
  require_chunk_Cek0wNdY();
73952
- var require_dist10 = require_dist_DeGPW01G();
74031
+ var require_dist10 = require_dist_DePB7uwU();
73953
74032
  function deviceLabelFromEventData(data) {
73954
74033
  const name = stringNonEmpty(data["name"]) ?? stringNonEmpty(data["deviceName"]);
73955
74034
  if (name !== void 0) return name;
@@ -74773,7 +74852,7 @@ var require_calendars_addon = __commonJS({
74773
74852
  [Symbol.toStringTag]: { value: "Module" }
74774
74853
  });
74775
74854
  require_chunk_Cek0wNdY();
74776
- var require_dist10 = require_dist_DeGPW01G();
74855
+ var require_dist10 = require_dist_DePB7uwU();
74777
74856
  var require_settle_sources = require_settle_sources_Bhsy57y();
74778
74857
  var zod = require_zod();
74779
74858
  var CALENDAR_COLOR_PALETTE = [
@@ -74987,11 +75066,11 @@ var require_calendars = __commonJS({
74987
75066
  }
74988
75067
  });
74989
75068
 
74990
- // ../system/dist/composition-sources-CUfM-BYp.js
74991
- var require_composition_sources_CUfM_BYp = __commonJS({
74992
- "../system/dist/composition-sources-CUfM-BYp.js"(exports) {
75069
+ // ../system/dist/composition-sources-0nuWOdNd.js
75070
+ var require_composition_sources_0nuWOdNd = __commonJS({
75071
+ "../system/dist/composition-sources-0nuWOdNd.js"(exports) {
74993
75072
  "use strict";
74994
- var require_dist10 = require_dist_DeGPW01G();
75073
+ var require_dist10 = require_dist_DePB7uwU();
74995
75074
  function normalizedNativeOwner(owner) {
74996
75075
  const trimmed = owner?.trim();
74997
75076
  return trimmed ? trimmed : null;
@@ -75106,8 +75185,8 @@ var require_composer_addon = __commonJS({
75106
75185
  [Symbol.toStringTag]: { value: "Module" }
75107
75186
  });
75108
75187
  require_chunk_Cek0wNdY();
75109
- var require_dist10 = require_dist_DeGPW01G();
75110
- var require_composition_sources = require_composition_sources_CUfM_BYp();
75188
+ var require_dist10 = require_dist_DePB7uwU();
75189
+ var require_composition_sources = require_composition_sources_0nuWOdNd();
75111
75190
  var zod = require_zod();
75112
75191
  function isRecord$2(value) {
75113
75192
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -76157,6 +76236,10 @@ var require_composer_addon = __commonJS({
76157
76236
  value: outcome.value
76158
76237
  };
76159
76238
  }
76239
+ function hasObservedRoseLevel(memory) {
76240
+ for (const call of memory.calls.values()) if (call.kind === "rose" && call.priorLevel !== void 0) return true;
76241
+ return false;
76242
+ }
76160
76243
  function strictFiniteAdvance(previous, next) {
76161
76244
  return typeof next === "number" && Number.isFinite(next) && (previous === null || next > previous);
76162
76245
  }
@@ -76196,7 +76279,7 @@ var require_composer_addon = __commonJS({
76196
76279
  const record = next.get(key);
76197
76280
  if (record === void 0) continue;
76198
76281
  const candidateMemory = require_dist10.cloneStatefulExpressionMemory(record.expressionMemory);
76199
- if (record.outcome?.state === "unavailable") require_dist10.disarmRoseMemory(candidateMemory);
76282
+ if (record.outcome?.state === "unavailable" && hasObservedRoseLevel(record.expressionMemory)) require_dist10.disarmRoseMemory(candidateMemory);
76200
76283
  const outcome = require_dist10.evaluateCompositionField({
76201
76284
  source: record.plan.source,
76202
76285
  read,
@@ -78890,7 +78973,7 @@ var require_console_logging = __commonJS({
78890
78973
  [Symbol.toStringTag]: { value: "Module" }
78891
78974
  });
78892
78975
  require_chunk_Cek0wNdY();
78893
- var require_dist10 = require_dist_DeGPW01G();
78976
+ var require_dist10 = require_dist_DePB7uwU();
78894
78977
  var require_formatter = require_formatter_DqAKDlvN();
78895
78978
  var LEVEL_RANK = {
78896
78979
  debug: 0,
@@ -82959,8 +83042,8 @@ var require_core_blocks_addon = __commonJS({
82959
83042
  "use strict";
82960
83043
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
82961
83044
  var require_chunk = require_chunk_Cek0wNdY();
82962
- var require_dist10 = require_dist_DeGPW01G();
82963
- var require_composition_sources = require_composition_sources_CUfM_BYp();
83045
+ var require_dist10 = require_dist_DePB7uwU();
83046
+ var require_composition_sources = require_composition_sources_0nuWOdNd();
82964
83047
  var node_crypto = __require("crypto");
82965
83048
  var _camstack_types_node = require_node();
82966
83049
  var node_fs_promises = __require("fs/promises");
@@ -84832,11 +84915,11 @@ var require_core_blocks = __commonJS({
84832
84915
  }
84833
84916
  });
84834
84917
 
84835
- // ../system/dist/retired-settings-keys-D8G5o4Fc.js
84836
- var require_retired_settings_keys_D8G5o4Fc = __commonJS({
84837
- "../system/dist/retired-settings-keys-D8G5o4Fc.js"(exports) {
84918
+ // ../system/dist/retired-settings-keys-BTKZdPn6.js
84919
+ var require_retired_settings_keys_BTKZdPn6 = __commonJS({
84920
+ "../system/dist/retired-settings-keys-BTKZdPn6.js"(exports) {
84838
84921
  "use strict";
84839
- var require_dist10 = require_dist_DeGPW01G();
84922
+ var require_dist10 = require_dist_DePB7uwU();
84840
84923
  function settingsStoreIsAuthoritativeHere(env) {
84841
84924
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
84842
84925
  return raw === "" || raw === "hub";
@@ -85125,8 +85208,8 @@ var require_device_manager_addon = __commonJS({
85125
85208
  [Symbol.toStringTag]: { value: "Module" }
85126
85209
  });
85127
85210
  require_chunk_Cek0wNdY();
85128
- var require_dist10 = require_dist_DeGPW01G();
85129
- var require_retired_settings_keys = require_retired_settings_keys_D8G5o4Fc();
85211
+ var require_dist10 = require_dist_DePB7uwU();
85212
+ var require_retired_settings_keys = require_retired_settings_keys_BTKZdPn6();
85130
85213
  var zod = require_zod();
85131
85214
  var node_crypto = __require("crypto");
85132
85215
  var _camstack_types_node = require_node();
@@ -93321,7 +93404,7 @@ var require_hub_forwarder = __commonJS({
93321
93404
  [Symbol.toStringTag]: { value: "Module" }
93322
93405
  });
93323
93406
  require_chunk_Cek0wNdY();
93324
- var require_dist10 = require_dist_DeGPW01G();
93407
+ var require_dist10 = require_dist_DePB7uwU();
93325
93408
  var require_formatter = require_formatter_DqAKDlvN();
93326
93409
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
93327
93410
  var HubForwarderDestination = class {
@@ -93458,7 +93541,7 @@ var require_liveness_monitor_addon = __commonJS({
93458
93541
  "use strict";
93459
93542
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
93460
93543
  require_chunk_Cek0wNdY();
93461
- var require_dist10 = require_dist_DeGPW01G();
93544
+ var require_dist10 = require_dist_DePB7uwU();
93462
93545
  var NO_DEVICES = "liveness:no-devices";
93463
93546
  var ALL_OFFLINE = "liveness:all-devices-offline";
93464
93547
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -93674,7 +93757,7 @@ var require_local_auth_addon = __commonJS({
93674
93757
  [Symbol.toStringTag]: { value: "Module" }
93675
93758
  });
93676
93759
  var require_chunk = require_chunk_Cek0wNdY();
93677
- var require_dist10 = require_dist_DeGPW01G();
93760
+ var require_dist10 = require_dist_DePB7uwU();
93678
93761
  var node_crypto = __require("crypto");
93679
93762
  node_crypto = require_chunk.__toESM(node_crypto);
93680
93763
  var crypto$1 = __require("crypto");
@@ -101528,7 +101611,7 @@ var require_loki_logging = __commonJS({
101528
101611
  [Symbol.toStringTag]: { value: "Module" }
101529
101612
  });
101530
101613
  require_chunk_Cek0wNdY();
101531
- var require_dist10 = require_dist_DeGPW01G();
101614
+ var require_dist10 = require_dist_DePB7uwU();
101532
101615
  function sanitizeLabelName(raw) {
101533
101616
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
101534
101617
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -102231,7 +102314,7 @@ var require_native_metrics_addon = __commonJS({
102231
102314
  [Symbol.toStringTag]: { value: "Module" }
102232
102315
  });
102233
102316
  var require_chunk = require_chunk_Cek0wNdY();
102234
- var require_dist10 = require_dist_DeGPW01G();
102317
+ var require_dist10 = require_dist_DePB7uwU();
102235
102318
  var require_proc_stat = require_proc_stat_DL_WUOhl();
102236
102319
  var node_fs_promises = __require("fs/promises");
102237
102320
  var node_os = __require("os");
@@ -105071,7 +105154,7 @@ var require_filesystem_storage_addon = __commonJS({
105071
105154
  [Symbol.toStringTag]: { value: "Module" }
105072
105155
  });
105073
105156
  var require_chunk = require_chunk_Cek0wNdY();
105074
- var require_dist10 = require_dist_DeGPW01G();
105157
+ var require_dist10 = require_dist_DePB7uwU();
105075
105158
  var node_crypto = __require("crypto");
105076
105159
  var node_fs_promises = __require("fs/promises");
105077
105160
  var node_path = __require("path");
@@ -106353,8 +106436,8 @@ var require_sqlite_settings_addon = __commonJS({
106353
106436
  [Symbol.toStringTag]: { value: "Module" }
106354
106437
  });
106355
106438
  var require_chunk = require_chunk_Cek0wNdY();
106356
- var require_dist10 = require_dist_DeGPW01G();
106357
- var require_retired_settings_keys = require_retired_settings_keys_D8G5o4Fc();
106439
+ var require_dist10 = require_dist_DePB7uwU();
106440
+ var require_retired_settings_keys = require_retired_settings_keys_BTKZdPn6();
106358
106441
  var require_sqlite_snapshot_protocol = require_sqlite_snapshot_protocol_B9OndqZv();
106359
106442
  var require_wal_checkpoint_policy = require_wal_checkpoint_policy_d2z2qPqx();
106360
106443
  var node_crypto = __require("crypto");
@@ -109550,7 +109633,7 @@ var require_storage_orchestrator_addon = __commonJS({
109550
109633
  [Symbol.toStringTag]: { value: "Module" }
109551
109634
  });
109552
109635
  var require_chunk = require_chunk_Cek0wNdY();
109553
- var require_dist10 = require_dist_DeGPW01G();
109636
+ var require_dist10 = require_dist_DePB7uwU();
109554
109637
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
109555
109638
  var zod = require_zod();
109556
109639
  var node_crypto = __require("crypto");
@@ -113277,7 +113360,7 @@ var require_system_config_addon = __commonJS({
113277
113360
  [Symbol.toStringTag]: { value: "Module" }
113278
113361
  });
113279
113362
  require_chunk_Cek0wNdY();
113280
- var require_dist10 = require_dist_DeGPW01G();
113363
+ var require_dist10 = require_dist_DePB7uwU();
113281
113364
  var SECTION_TITLES = {
113282
113365
  server: "Server",
113283
113366
  auth: "Authentication"
@@ -131338,7 +131421,7 @@ var require_winston_logging = __commonJS({
131338
131421
  [Symbol.toStringTag]: { value: "Module" }
131339
131422
  });
131340
131423
  var require_chunk = require_chunk_Cek0wNdY();
131341
- var require_dist10 = require_dist_DeGPW01G();
131424
+ var require_dist10 = require_dist_DePB7uwU();
131342
131425
  var require_formatter = require_formatter_DqAKDlvN();
131343
131426
  var node_path = __require("path");
131344
131427
  node_path = require_chunk.__toESM(node_path);
@@ -148227,8 +148310,7 @@ var require_addon = __commonJS({
148227
148310
  "setSlotBinding": { "binding": { "__nested__": {
148228
148311
  "enabled": true,
148229
148312
  "template": "${value}",
148230
- "maxDecimals": 1,
148231
- "fallbackText": ""
148313
+ "maxDecimals": 1
148232
148314
  } } },
148233
148315
  "upsertTemplate": { "template": { "__nested__": {
148234
148316
  "bindings": [],
@@ -155004,12 +155086,12 @@ var require_dist2 = __commonJS({
155004
155086
  }
155005
155087
  });
155006
155088
 
155007
- // ../system/dist/child-cap-dispatch-B_0-zpI6.js
155008
- var require_child_cap_dispatch_B_0_zpI6 = __commonJS({
155009
- "../system/dist/child-cap-dispatch-B_0-zpI6.js"(exports) {
155089
+ // ../system/dist/child-cap-dispatch-CRqYKHAb.js
155090
+ var require_child_cap_dispatch_CRqYKHAb = __commonJS({
155091
+ "../system/dist/child-cap-dispatch-CRqYKHAb.js"(exports) {
155010
155092
  "use strict";
155011
155093
  var require_chunk = require_chunk_Cek0wNdY();
155012
- var require_dist10 = require_dist_DeGPW01G();
155094
+ var require_dist10 = require_dist_DePB7uwU();
155013
155095
  var require_proc_stat = require_proc_stat_DL_WUOhl();
155014
155096
  require_hub_cap_forward_DmHNjbB0();
155015
155097
  var node_crypto = __require("crypto");
@@ -169548,7 +169630,7 @@ var require_dist3 = __commonJS({
169548
169630
  "use strict";
169549
169631
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
169550
169632
  var require_chunk = require_chunk_Cek0wNdY();
169551
- var require_dist10 = require_dist_DeGPW01G();
169633
+ var require_dist10 = require_dist_DePB7uwU();
169552
169634
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
169553
169635
  require_alerts();
169554
169636
  var require_builtins_calendars_calendars_addon = require_calendars_addon();
@@ -169579,7 +169661,7 @@ var require_dist3 = __commonJS({
169579
169661
  var require_builtins_winston_logging_index = require_winston_logging();
169580
169662
  var require_event_loop_stall_monitor = require_event_loop_stall_monitor_ey4mbofz();
169581
169663
  var require_tls$1 = require_tls_ue_Xefmv();
169582
- var require_child_cap_dispatch = require_child_cap_dispatch_B_0_zpI6();
169664
+ var require_child_cap_dispatch = require_child_cap_dispatch_CRqYKHAb();
169583
169665
  var require_resource_monitor = require_resource_monitor_BgWGRxN5();
169584
169666
  var require_custom_action_registry = require_custom_action_registry_acBY3mHA();
169585
169667
  var zod = require_zod();
@@ -169894,6 +169976,29 @@ var require_dist3 = __commonJS({
169894
169976
  function canServeDataPlane(state) {
169895
169977
  return state.hasEntry && state.isForked && state.childKnown;
169896
169978
  }
169979
+ function edgeOwnedResponseHeaders(upRes, clientRes) {
169980
+ const out = {};
169981
+ for (const [name2, value] of Object.entries(upRes.headers)) {
169982
+ if (value === void 0) continue;
169983
+ const lower = name2.toLowerCase();
169984
+ if (lower.startsWith("access-control-")) continue;
169985
+ out[lower] = value;
169986
+ }
169987
+ const edgeVary = clientRes.getHeader("vary");
169988
+ const upVary = out["vary"];
169989
+ if (typeof edgeVary === "string" && edgeVary.length > 0 && upVary !== void 0) {
169990
+ const tokens = /* @__PURE__ */ new Set();
169991
+ const merged = [];
169992
+ for (const part of [edgeVary, String(upVary)].join(",").split(",")) {
169993
+ const token2 = part.trim();
169994
+ if (token2.length === 0 || tokens.has(token2.toLowerCase())) continue;
169995
+ tokens.add(token2.toLowerCase());
169996
+ merged.push(token2);
169997
+ }
169998
+ out["vary"] = merged.join(", ");
169999
+ }
170000
+ return out;
170001
+ }
169897
170002
  function proxyToUpstream(opts) {
169898
170003
  const { baseUrl, secret, upstreamPath, clientReq, clientRes, replayBody } = opts;
169899
170004
  const base = new URL(baseUrl);
@@ -169911,17 +170016,11 @@ var require_dist3 = __commonJS({
169911
170016
  path: upstreamPath,
169912
170017
  headers
169913
170018
  }, (upRes) => {
169914
- clientRes.writeHead(upRes.statusCode ?? 502, {
169915
- "access-control-allow-origin": "*",
169916
- ...upRes.headers
169917
- });
170019
+ clientRes.writeHead(upRes.statusCode ?? 502, edgeOwnedResponseHeaders(upRes, clientRes));
169918
170020
  upRes.pipe(clientRes);
169919
170021
  });
169920
170022
  upstream.on("error", (err) => {
169921
- if (!clientRes.headersSent) clientRes.writeHead(502, {
169922
- "content-type": "application/json",
169923
- "access-control-allow-origin": "*"
169924
- });
170023
+ if (!clientRes.headersSent) clientRes.writeHead(502, { "content-type": "application/json" });
169925
170024
  clientRes.end(JSON.stringify({
169926
170025
  error: "data-plane upstream error",
169927
170026
  detail: String(err)
@@ -176159,10 +176258,19 @@ var require_dist3 = __commonJS({
176159
176258
  };
176160
176259
  }
176161
176260
  var DEFAULT_DATA_PATH = "camstack-data";
176261
+ var originListSchema = zod.z.preprocess((value) => typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) : value, zod.z.array(zod.z.string()));
176162
176262
  var serverSchema = zod.z.object({
176163
176263
  port: zod.z.number().default(4443),
176164
176264
  host: zod.z.string().default("0.0.0.0"),
176165
- dataPath: zod.z.string().default(DEFAULT_DATA_PATH)
176265
+ dataPath: zod.z.string().default(DEFAULT_DATA_PATH),
176266
+ /**
176267
+ * EXTRA browser origins allowed to call this hub cross-origin (D707), on
176268
+ * top of the built-ins: the hub's own origin, the hosted viewer PWA and the
176269
+ * native app's opaque `null` origin. Bootstrap-owned on purpose — widening
176270
+ * who may read hub responses in a browser is a security decision, and the
176271
+ * policy is live from the first request, before the settings store exists.
176272
+ */
176273
+ corsOrigins: originListSchema.default([])
176166
176274
  });
176167
176275
  var authSchema = zod.z.object({
176168
176276
  jwtSecret: zod.z.string().nullable().default(null),
@@ -178632,7 +178740,8 @@ var require_dist3 = __commonJS({
178632
178740
  CAMSTACK_DATA: "server.dataPath",
178633
178741
  CAMSTACK_JWT_SECRET: "auth.jwtSecret",
178634
178742
  CAMSTACK_ADMIN_USER: "auth.adminUsername",
178635
- CAMSTACK_ADMIN_PASS: "auth.adminPassword"
178743
+ CAMSTACK_ADMIN_PASS: "auth.adminPassword",
178744
+ CAMSTACK_CORS_ORIGINS: "server.corsOrigins"
178636
178745
  };
178637
178746
  function loadBootstrapConfig(configPath) {
178638
178747
  const merged = applyEnvOverrides((0, _camstack_types_addon.asJsonObject)(loadYaml(configPath)) ?? {});
@@ -254255,7 +254364,7 @@ var require_dist4 = __commonJS({
254255
254364
  }
254256
254365
  function variantLabel(precision, optimization) {
254257
254366
  const parts = [];
254258
- if (optimization === "fast") parts.push("Fast");
254367
+ if (optimization === "test") parts.push("Test");
254259
254368
  parts.push(precision === "int8" ? "Int8" : "Standard");
254260
254369
  return parts.join(" \xB7 ");
254261
254370
  }
@@ -254305,7 +254414,7 @@ var require_dist4 = __commonJS({
254305
254414
  };
254306
254415
  }).toSorted((a, b) => {
254307
254416
  if (a.resolution !== b.resolution) return (b.resolution ?? Infinity) - (a.resolution ?? Infinity);
254308
- if (a.optimization !== b.optimization) return a.optimization === "fast" ? 1 : -1;
254417
+ if (a.optimization !== b.optimization) return a.optimization === "test" ? 1 : -1;
254309
254418
  return PRECISION_ORDER.indexOf(a.precision) - PRECISION_ORDER.indexOf(b.precision);
254310
254419
  });
254311
254420
  const base = options.find((o) => o.precision === "fp32" && o.optimization === "standard") ?? options[0];
@@ -254441,11 +254550,11 @@ var require_dist4 = __commonJS({
254441
254550
  /** Quantization axis. Omit ⇒ the fp32 base build. */
254442
254551
  precision: zod.z.enum(["fp32", "int8"]).optional(),
254443
254552
  /**
254444
- * Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
254445
- * latency-optimized export (e.g. ReLU-activation variant) — the slot the
254446
- * future performance variants plug into.
254553
+ * Training / latency-variant axis. Omit ⇒ the standard build. `test` marks
254554
+ * an alternate training of the same family (Scrypted `relu_test`); chip
254555
+ * label is "Test".
254447
254556
  */
254448
- optimization: zod.z.enum(["standard", "fast"]).optional(),
254557
+ optimization: zod.z.enum(["standard", "test"]).optional(),
254449
254558
  /**
254450
254559
  * Input-resolution axis (square input side, px). Omit ⇒ the family's native
254451
254560
  * resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
@@ -263788,7 +263897,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263788
263897
  }) }
263789
263898
  };
263790
263899
  var ModelDistributeInputSchema = zod.z.object({
263791
- nodeId: zod.z.string(),
263900
+ targetNodeId: zod.z.string(),
263792
263901
  modelId: zod.z.string(),
263793
263902
  format: zod.z.enum(MODEL_FORMATS),
263794
263903
  entry: ModelCatalogEntrySchema
@@ -266149,6 +266258,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
266149
266258
  "detection-blind",
266150
266259
  "nodes",
266151
266260
  "addons",
266261
+ "addon-updates",
266152
266262
  "updates"
266153
266263
  ]);
266154
266264
  var NcStateSummaryPreviewSectionSchema = zod.z.object({
@@ -267071,7 +267181,19 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267071
267181
  * When this mode may be armed. Absent / null = always allowed (same as a
267072
267182
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
267073
267183
  */
267074
- schedule: NcScheduleSchema.optional()
267184
+ schedule: NcScheduleSchema.optional(),
267185
+ /**
267186
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
267187
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
267188
+ * only one, and the matches ride the triggered notification's mosaic (D705).
267189
+ */
267190
+ notifyDuringEntryDelay: zod.z.boolean().optional(),
267191
+ /**
267192
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
267193
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
267194
+ * is the one that starts the entry delay — the entry-delay notification already names it.
267195
+ */
267196
+ notifySensorRewatched: zod.z.boolean().optional()
267075
267197
  });
267076
267198
  var NcAlarmModeDelaysListSchema = zod.z.array(NcAlarmModeDelaysSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
267077
267199
  var NcAlarmModeDelaysEditSchema = zod.z.object({
@@ -267081,7 +267203,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267081
267203
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
267082
267204
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
267083
267205
  /** Set a schedule, or `null` to clear (mode always allowed again). */
267084
- schedule: NcScheduleSchema.nullable().optional()
267206
+ schedule: NcScheduleSchema.nullable().optional(),
267207
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
267208
+ notifyDuringEntryDelay: zod.z.boolean().nullable().optional(),
267209
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
267210
+ notifySensorRewatched: zod.z.boolean().nullable().optional()
267085
267211
  });
267086
267212
  var NcAlarmModeDelaysEditListSchema = zod.z.array(NcAlarmModeDelaysEditSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
267087
267213
  var NcAlarmNonBlockingEditSchema = zod.z.object({
@@ -267939,39 +268065,19 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267939
268065
  label: "Face recognition model",
267940
268066
  defaultModelId: "auraface-r100",
267941
268067
  indexName: "the enrolled face gallery",
267942
- options: [
267943
- {
267944
- id: "auraface-r100",
267945
- label: "AuraFace R100"
267946
- },
267947
- {
267948
- id: "arcface-r100",
267949
- label: "ArcFace (ResNet34)"
267950
- },
267951
- {
267952
- id: "inception-resnet-v1",
267953
- label: "Inception ResNet V1"
267954
- }
267955
- ]
268068
+ options: [{
268069
+ id: "auraface-r100",
268070
+ label: "AuraFace R100"
268071
+ }]
267956
268072
  }, {
267957
268073
  stepId: "clip-embedding",
267958
268074
  label: "Semantic search model",
267959
- defaultModelId: "mobileclip-s1",
268075
+ defaultModelId: "siglip2-b16-224",
267960
268076
  indexName: "the object semantic-search index",
267961
- options: [
267962
- {
267963
- id: "mobileclip-s1",
267964
- label: "MobileCLIP S1"
267965
- },
267966
- {
267967
- id: "mobileclip-s2",
267968
- label: "MobileCLIP S2"
267969
- },
267970
- {
267971
- id: "siglip2-b16-224",
267972
- label: "SigLIP2 B/16 (Apache-2.0)"
267973
- }
267974
- ]
268077
+ options: [{
268078
+ id: "siglip2-b16-224",
268079
+ label: "SigLIP2 B/16 (Apache-2.0)"
268080
+ }]
267975
268081
  }];
267976
268082
  var CLUSTER_MODEL_SECTION_ID = "cluster-step-models";
267977
268083
  function clusterModelSettingKey(stepId) {
@@ -271188,6 +271294,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
271188
271294
  "analyzer",
271189
271295
  "device-activity"
271190
271296
  ]);
271297
+ var SessionTriggerSchema = zod.z.object({
271298
+ source: MotionSourceEnum,
271299
+ triggeredBy: MotionTriggeredBySchema,
271300
+ sensorName: zod.z.string().optional()
271301
+ });
271191
271302
  var MotionSourcesSchema = zod.z.array(MotionSourceEnum);
271192
271303
  var MOTION_SOURCE_LABELS = {
271193
271304
  analyzer: "Frame-diff Analyzer (motion addon)",
@@ -271339,6 +271450,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
271339
271450
  */
271340
271451
  motionSources: MotionSourcesSchema.default(["analyzer"]),
271341
271452
  /**
271453
+ * What opened this attach, on a SESSION attach only (see
271454
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
271455
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
271456
+ * behind it, and every attach from an orchestrator older than the field — the
271457
+ * runner then names no source on the phase event.
271458
+ */
271459
+ sessionTrigger: SessionTriggerSchema.optional(),
271460
+ /**
271342
271461
  * WHICH root detector runs for this camera. The detection counterpart of
271343
271462
  * {@link motionSources}, same shape and same discipline: a per-camera list
271344
271463
  * of sources the system understands, vendor-agnostic, owned here.
@@ -275351,6 +275470,94 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
275351
275470
  rates: CLIP_BROKER_PACED_RATES
275352
275471
  }
275353
275472
  };
275473
+ var CAMSTACK_APP_LINK_ORIGIN = "https://camstack.zentik.app";
275474
+ var HUB_VIEWER_MOUNT = "/viewer/camstack";
275475
+ function isAppLinkBase(baseUrl) {
275476
+ if (baseUrl === null || baseUrl === void 0 || baseUrl === "") return false;
275477
+ try {
275478
+ return new URL(baseUrl).origin === CAMSTACK_APP_LINK_ORIGIN;
275479
+ } catch {
275480
+ return false;
275481
+ }
275482
+ }
275483
+ function viewerLinkBaseOf(endpoint) {
275484
+ const flagged = endpoint.baseUrl ?? endpoint.resolved;
275485
+ return isAppLinkBase(flagged) ? {
275486
+ form: "app",
275487
+ hubBaseUrl: endpoint.appLinkHub ?? null
275488
+ } : {
275489
+ form: "hub",
275490
+ hubBaseUrl: flagged
275491
+ };
275492
+ }
275493
+ function toAppLinkHubOrigin(baseUrl) {
275494
+ let parsed;
275495
+ try {
275496
+ parsed = new URL(baseUrl);
275497
+ } catch {
275498
+ return null;
275499
+ }
275500
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
275501
+ if (parsed.username !== "" || parsed.password !== "") return null;
275502
+ if (parsed.hostname === "") return null;
275503
+ return parsed.origin;
275504
+ }
275505
+ function pickAppLinkHub(endpoints) {
275506
+ const rank2 = (e) => {
275507
+ if (e.kind !== "public" || !e.baseUrl.startsWith("https://")) return null;
275508
+ if (e.interfaceKind === "public") return 0;
275509
+ if (e.interfaceKind === "vpn") return 1;
275510
+ return null;
275511
+ };
275512
+ return endpoints.flatMap((e) => {
275513
+ const r = rank2(e);
275514
+ const origin = toAppLinkHubOrigin(e.baseUrl);
275515
+ return r === null || origin === null ? [] : [{
275516
+ r,
275517
+ p: e.priority,
275518
+ origin
275519
+ }];
275520
+ }).toSorted((a, b) => a.r - b.r || a.p - b.p)[0]?.origin ?? null;
275521
+ }
275522
+ function viewerRoute(destination) {
275523
+ switch (destination.kind) {
275524
+ case "camera": {
275525
+ if (!Number.isInteger(destination.deviceId) || destination.deviceId <= 0) return null;
275526
+ const at = destination.atMs;
275527
+ return {
275528
+ path: `/camera/${String(destination.deviceId)}`,
275529
+ query: at !== void 0 && Number.isFinite(at) ? [["at", String(Math.trunc(at))]] : []
275530
+ };
275531
+ }
275532
+ case "events":
275533
+ return {
275534
+ path: "/events",
275535
+ query: []
275536
+ };
275537
+ case "notifications":
275538
+ return {
275539
+ path: "/notifications",
275540
+ query: []
275541
+ };
275542
+ }
275543
+ }
275544
+ function encodeQuery(query) {
275545
+ if (query.length === 0) return "";
275546
+ return `?${query.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&")}`;
275547
+ }
275548
+ function buildViewerLink(input) {
275549
+ const route = viewerRoute(input.destination);
275550
+ if (route === null) return null;
275551
+ const raw = input.base.hubBaseUrl;
275552
+ const base = raw === null ? "" : raw.replace(/\/+$/, "");
275553
+ if (input.base.form === "app") {
275554
+ const origin = base === "" ? null : toAppLinkHubOrigin(base);
275555
+ const query = origin === null ? route.query : [...route.query, ["hub", origin]];
275556
+ return `${CAMSTACK_APP_LINK_ORIGIN}${route.path}${encodeQuery(query)}`;
275557
+ }
275558
+ if (base === "" || isAppLinkBase(base)) return null;
275559
+ return `${base}${HUB_VIEWER_MOUNT}${route.path}${encodeQuery(route.query)}`;
275560
+ }
275354
275561
  var webrtcClientHintsSchema = zod.z.object({
275355
275562
  viewportWidth: zod.z.number().int().positive().optional(),
275356
275563
  viewportHeight: zod.z.number().int().positive().optional(),
@@ -279212,7 +279419,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279212
279419
  name: zod.z.string(),
279213
279420
  family: zod.z.enum(["IPv4", "IPv6"]),
279214
279421
  address: zod.z.string(),
279215
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
279422
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
279216
279423
  cidr: zod.z.string(),
279217
279424
  /** OS netmask. Stable across calls so the SDK can hash on it. */
279218
279425
  netmask: zod.z.string(),
@@ -279261,7 +279468,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279261
279468
  "lan-ipv4",
279262
279469
  "lan-ipv6",
279263
279470
  "public",
279264
- "loopback"
279471
+ "loopback",
279472
+ "app-link"
279265
279473
  ]),
279266
279474
  /**
279267
279475
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -279326,8 +279534,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279326
279534
  var NotificationEndpointSchema = zod.z.object({
279327
279535
  /** The operator's explicit choice, or null for AUTO. */
279328
279536
  baseUrl: zod.z.string().nullable(),
279329
- /** What the ranking currently resolves to (null when nothing is reachable). */
279330
- resolved: zod.z.string().nullable()
279537
+ /**
279538
+ * The effective flagged address: the explicit choice, else (AUTO) the
279539
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
279540
+ * answered the public tunnel / mesh here, or null.
279541
+ */
279542
+ resolved: zod.z.string().nullable(),
279543
+ /**
279544
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
279545
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
279546
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
279547
+ * hub advertises neither; the app link is then minted without `hub=`.
279548
+ * Absent on a pre-D709 hub.
279549
+ */
279550
+ appLinkHub: zod.z.string().nullable()
279331
279551
  });
279332
279552
  var ViewerEndpointsSchema = zod.z.object({
279333
279553
  /** The operator's explicit race set, or empty for AUTO. */
@@ -279391,7 +279611,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279391
279611
  * every client a second authority on a socket only the hub binds. A viewer
279392
279612
  * configured with a port-less public URL (`https://camstack.example.top`)
279393
279613
  * infers 443 from the scheme, asks with 443, and was handed
279394
- * `https://192.168.x.x:443` for a hub listening on 4443. That candidate
279614
+ * `https://192.168.10.9:443` for a hub listening on 4443. That candidate
279395
279615
  * cannot ever answer, and losing its race reads in the log exactly like
279396
279616
  * "the LAN was tried and it was slower" — so an operator sitting on his own
279397
279617
  * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
@@ -279424,13 +279644,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279424
279644
  * Pass `'https'` when the caller is itself loaded over HTTPS
279425
279645
  * to avoid mixed-content blocks in the browser. The public
279426
279646
  * tunnel always emits `https://` regardless. */
279427
- scheme: zod.z.enum(["http", "https"]).optional()
279647
+ scheme: zod.z.enum(["http", "https"]).optional(),
279648
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
279649
+ * an address notification links can be flagged with, not a hub
279650
+ * route. Default `false`: racers and rankers never see it. */
279651
+ includeAppLink: zod.z.boolean().optional()
279428
279652
  }), GetConnectionEndpointsResultSchema),
279429
279653
  /**
279430
- * The endpoint OUTBOUND artifacts must be addressed by — notification
279431
- * attachment links and deep links. `baseUrl: null` means AUTO: the first
279432
- * public candidate (a configured origin, then a connected tunnel, then the
279433
- * LAN), never loopback.
279654
+ * The address notification links are flagged with. `baseUrl: null` means
279655
+ * AUTO, which is the CamStack app link (D709): viewer taps go through
279656
+ * `https://camstack.zentik.app/…?hub=<appLinkHub>` and open the app. Any
279657
+ * hub address the palette lists can be flagged instead; then viewer taps,
279658
+ * attachment links and action buttons all use it. Signed media and
279659
+ * buttons never use the app link — with it flagged (or AUTO) they keep
279660
+ * the ranked hub ladder (a configured origin, a connected tunnel, then
279661
+ * the LAN), never loopback.
279434
279662
  *
279435
279663
  * Why an explicit marker: the automatic ranking cannot know which of
279436
279664
  * several reachable addresses the RECIPIENT can actually open. A link is
@@ -279789,7 +280017,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279789
280017
  tags: zod.z.array(zod.z.string()).readonly(),
279790
280018
  /** True when the peer advertises itself as an exit-node candidate. */
279791
280019
  exitNodeOption: zod.z.boolean(),
279792
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
280020
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
279793
280021
  advertisedRoutes: zod.z.array(zod.z.string()).readonly(),
279794
280022
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
279795
280023
  userLogin: zod.z.string().nullable(),
@@ -281077,7 +281305,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
281077
281305
  */
281078
281306
  availablePositions: zod.z.array(OsdPositionEnum).readonly(),
281079
281307
  /** Firmware does not allow editing the `text` field (e.g. timestamp). */
281080
- readOnly: zod.z.boolean().optional()
281308
+ readOnly: zod.z.boolean().optional(),
281309
+ /**
281310
+ * Firmware text ceiling for this overlay. Absent = no known limit.
281311
+ * The OSD manager truncates here on every write — never stores its own
281312
+ * copy on the binding.
281313
+ */
281314
+ maxCharacters: zod.z.number().int().positive().optional()
281081
281315
  });
281082
281316
  var OsdStatusSchema = zod.z.object({ overlays: zod.z.array(OsdOverlaySchema).readonly() });
281083
281317
  var OsdOverlayPatchSchema = zod.z.object({
@@ -281165,7 +281399,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
281165
281399
  valuePath: zod.z.string().min(1).max(64),
281166
281400
  /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
281167
281401
  displayUnit: zod.z.string().max(16).optional(),
281168
- maxDecimals: zod.z.number().int().min(0).max(4).default(1)
281402
+ maxDecimals: zod.z.number().int().min(0).max(4).default(1),
281403
+ /** Raw value → display text (same shape as slot binding). Empty string hides. */
281404
+ valueMap: zod.z.record(zod.z.string(), zod.z.string()).optional()
281169
281405
  });
281170
281406
  var OsdNamedTemplateSchema = zod.z.object({
281171
281407
  id: zod.z.string().min(1).max(64),
@@ -281210,18 +281446,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
281210
281446
  source: OsdSourceSchema,
281211
281447
  /** `${value}` and `${unit}` are substituted; every occurrence. */
281212
281448
  template: zod.z.string().max(96).default("${value}"),
281213
- /** Truncate with an ellipsis past this length. Absent = no limit. */
281214
- maxCharacters: zod.z.number().int().min(4).max(64).optional(),
281215
281449
  /**
281216
281450
  * Decimal places for a numeric value. `0` yields an integer — the
281217
281451
  * documented workaround for firmwares that reject `.` in overlay text.
281218
281452
  */
281219
281453
  maxDecimals: zod.z.number().int().min(0).max(4).default(1),
281220
- /**
281221
- * Appended via `${unit}` when no unit conversion produces a label.
281222
- * Prefer `source.displayUnit` for converted device-state values.
281223
- */
281224
- unitLabel: zod.z.string().max(8).optional(),
281225
281454
  /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
281226
281455
  valueMap: zod.z.record(zod.z.string(), zod.z.string()).optional(),
281227
281456
  /** Time windows in which the slot is shown. Absent = always. */
@@ -281230,9 +281459,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
281230
281459
  * Display gate, in the notification centre's condition vocabulary.
281231
281460
  * Only the keys reported by `getConditionSupport` are accepted.
281232
281461
  */
281233
- conditions: NcConditionsSchema.optional(),
281234
- /** Rendered when the gate is closed or the value unreadable. Empty = hide. */
281235
- fallbackText: zod.z.string().max(64).default("")
281462
+ conditions: NcConditionsSchema.optional()
281236
281463
  });
281237
281464
  var OsdSlotViewSchema = zod.z.object({
281238
281465
  slotId: zod.z.string(),
@@ -305299,12 +305526,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
305299
305526
  {
305300
305527
  id: "addons",
305301
305528
  family: "system",
305302
- label: "Addons",
305303
- kinds: [
305304
- "addon-crash-loop",
305305
- "addon-update-available",
305306
- "addon-updated"
305307
- ]
305529
+ label: "Addons stopped",
305530
+ kinds: ["addon-crash-loop"]
305531
+ },
305532
+ {
305533
+ id: "addon-updates",
305534
+ family: "system",
305535
+ label: "Addon updates",
305536
+ kinds: ["addon-update-available", "addon-updated"]
305308
305537
  },
305309
305538
  {
305310
305539
  id: "updates",
@@ -305914,6 +306143,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
305914
306143
  "entryDelaySec",
305915
306144
  "triggeredDurationSec"
305916
306145
  ];
306146
+ var NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC = 30;
306147
+ var NC_ALARM_NOTIFY_FIELDS = ["notifyDuringEntryDelay", "notifySensorRewatched"];
306148
+ function resolveModeNotifyPolicy(settings, mode) {
306149
+ const entry = (settings.modeDelays ?? []).find((e) => e.mode === mode);
306150
+ return {
306151
+ notifyDuringEntryDelay: entry?.notifyDuringEntryDelay === true,
306152
+ notifySensorRewatched: entry?.notifySensorRewatched === true
306153
+ };
306154
+ }
305917
306155
  function derivePostTriggeredAction(action, triggeredDurationSec) {
305918
306156
  if (action !== void 0) return action;
305919
306157
  return triggeredDurationSec > 0 ? "rearmAfter" : "doNothing";
@@ -305956,6 +306194,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
305956
306194
  if (edit === void 0) return current;
305957
306195
  return edit === null ? void 0 : edit;
305958
306196
  }
306197
+ function editedFlag(current, edit) {
306198
+ if (edit === void 0) return current;
306199
+ return edit === null ? void 0 : edit;
306200
+ }
305959
306201
  function editedSchedule(current, edit) {
305960
306202
  if (edit === void 0) return current;
305961
306203
  return edit === null ? void 0 : edit;
@@ -305986,15 +306228,19 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
305986
306228
  const triggered = edited(base.triggeredDurationSec, edit.triggeredDurationSec);
305987
306229
  const action = editedAction(base.postTriggeredAction, edit.postTriggeredAction);
305988
306230
  const schedule = editedSchedule(base.schedule, edit.schedule);
306231
+ const duringEntry = editedFlag(base.notifyDuringEntryDelay, edit.notifyDuringEntryDelay);
306232
+ const rewatched = editedFlag(base.notifySensorRewatched, edit.notifySensorRewatched);
305989
306233
  if (exit !== void 0) next.exitDelaySec = exit;
305990
306234
  if (entry !== void 0) next.entryDelaySec = entry;
305991
306235
  if (triggered !== void 0) next.triggeredDurationSec = triggered;
305992
306236
  if (action !== void 0) next.postTriggeredAction = action;
305993
306237
  if (schedule !== void 0) next.schedule = schedule;
306238
+ if (duringEntry !== void 0) next.notifyDuringEntryDelay = duringEntry;
306239
+ if (rewatched !== void 0) next.notifySensorRewatched = rewatched;
305994
306240
  if (at >= 0) out[at] = next;
305995
306241
  else out.push(next);
305996
306242
  }
305997
- return out.filter((e) => e.exitDelaySec !== void 0 || e.entryDelaySec !== void 0 || e.triggeredDurationSec !== void 0 || e.postTriggeredAction !== void 0 || e.schedule !== void 0);
306243
+ return out.filter((e) => e.exitDelaySec !== void 0 || e.entryDelaySec !== void 0 || e.triggeredDurationSec !== void 0 || e.postTriggeredAction !== void 0 || e.schedule !== void 0 || e.notifyDuringEntryDelay !== void 0 || e.notifySensorRewatched !== void 0);
305998
306244
  }
305999
306245
  function applyModeDeviceListEdit(lists, edit) {
306000
306246
  const copy2 = lists.map((l) => ({
@@ -306121,6 +306367,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
306121
306367
  const got = entry?.postTriggeredAction;
306122
306368
  if (edit.postTriggeredAction === null ? got !== void 0 : got !== edit.postTriggeredAction) return false;
306123
306369
  }
306370
+ for (const field of NC_ALARM_NOTIFY_FIELDS) {
306371
+ const want = edit[field];
306372
+ if (want === void 0) continue;
306373
+ const got = entry?.[field];
306374
+ if (want === null ? got !== void 0 : got !== want) return false;
306375
+ }
306124
306376
  if (edit.schedule !== void 0) {
306125
306377
  const got = entry?.schedule;
306126
306378
  if (edit.schedule === null ? got !== void 0 : !sameSchedule(got, edit.schedule)) return false;
@@ -306459,13 +306711,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
306459
306711
  },
306460
306712
  {
306461
306713
  name: "summaryAddons",
306462
- label: "Summary: addons",
306463
- example: "@camstack/addon-recorder 1.2.3 \u2192 1.2.4",
306464
- kinds: [
306465
- "addon-crash-loop",
306466
- "addon-update-available",
306467
- "addon-updated"
306468
- ]
306714
+ label: "Summary: addons stopped",
306715
+ example: "hub \u2014 recorder stopped after repeated crashes",
306716
+ kinds: ["addon-crash-loop"]
306717
+ },
306718
+ {
306719
+ name: "summaryAddonUpdates",
306720
+ label: "Summary: addon updates",
306721
+ example: "hub:\n\u2022 @camstack/addon-recorder 1.2.3 \u2192 1.2.4",
306722
+ kinds: ["addon-update-available", "addon-updated"]
306469
306723
  },
306470
306724
  {
306471
306725
  name: "summaryUpdates",
@@ -306806,6 +307060,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
306806
307060
  kinds: ["system"],
306807
307061
  systemEventKinds: [...DEVICE_EVENTS]
306808
307062
  },
307063
+ {
307064
+ name: "locations",
307065
+ label: "Intrusion locations",
307066
+ example: "Ingresso, Corridoio, Salone",
307067
+ group: "system",
307068
+ families: ["rule"],
307069
+ fields: [...RULE_TEXT],
307070
+ kinds: ["system"],
307071
+ systemEventKinds: [...COMBINED_ALARM]
307072
+ },
306809
307073
  {
306810
307074
  name: "deviceId",
306811
307075
  label: "Device id",
@@ -306895,7 +307159,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
306895
307159
  {
306896
307160
  name: "devices",
306897
307161
  label: "Devices",
306898
- description: "node-inference-unavailable: the affected cameras, comma-joined. Alarm arm / refusal: HOW MANY devices are armed (or open).",
307162
+ description: "node-inference-unavailable: the affected cameras, comma-joined. Alarm arm / arming / refusal: HOW MANY devices are armed (or open).",
306899
307163
  example: "Ingresso, Garage",
306900
307164
  group: "system",
306901
307165
  families: ["rule"],
@@ -306904,6 +307168,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
306904
307168
  systemEventKinds: [
306905
307169
  "node-inference-unavailable",
306906
307170
  "alarm-armed",
307171
+ "alarm-arming",
306907
307172
  "alarm-arm-refused"
306908
307173
  ]
306909
307174
  },
@@ -307124,18 +307389,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
307124
307389
  {
307125
307390
  name: "rules",
307126
307391
  label: "Covering rules",
307127
- description: "How many enabled rules the armed mode gates.",
307392
+ description: "How many enabled rules the armed / arming mode gates.",
307128
307393
  example: "2",
307129
307394
  group: "system",
307130
307395
  families: ["rule"],
307131
307396
  fields: [...RULE_TEXT],
307132
307397
  kinds: ["system"],
307133
- systemEventKinds: ["alarm-armed"]
307398
+ systemEventKinds: ["alarm-armed", "alarm-arming"]
307134
307399
  },
307135
307400
  {
307136
307401
  name: "names",
307137
307402
  label: "Device names",
307138
- description: "Armed: the devices armed. Arming: the devices this mode cannot arm. Refusal: the open sensors.",
307403
+ description: "Armed / arming: the Active devices (or every skipped name when none are Active). Refusal: the open sensors.",
307139
307404
  example: "Ingresso, Garage",
307140
307405
  group: "system",
307141
307406
  families: ["rule"],
@@ -307156,7 +307421,29 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
307156
307421
  families: ["rule"],
307157
307422
  fields: [...RULE_TEXT],
307158
307423
  kinds: ["system"],
307159
- systemEventKinds: ["alarm-armed"]
307424
+ systemEventKinds: ["alarm-armed", "alarm-arming"]
307425
+ },
307426
+ {
307427
+ name: "active",
307428
+ label: "Active count",
307429
+ description: "How many named devices the armed / arming mode is watching (title: n attivi / n active).",
307430
+ example: "3",
307431
+ group: "system",
307432
+ families: ["rule"],
307433
+ fields: [...RULE_TEXT],
307434
+ kinds: ["system"],
307435
+ systemEventKinds: ["alarm-armed", "alarm-arming"]
307436
+ },
307437
+ {
307438
+ name: "skipped",
307439
+ label: "Skipped count",
307440
+ description: "How many named devices the mode cannot arm \u2014 muted, detection off, offline (title: m saltati).",
307441
+ example: "1",
307442
+ group: "system",
307443
+ families: ["rule"],
307444
+ fields: [...RULE_TEXT],
307445
+ kinds: ["system"],
307446
+ systemEventKinds: ["alarm-armed", "alarm-arming"]
307160
307447
  },
307161
307448
  {
307162
307449
  name: "bypassedNames",
@@ -307405,7 +307692,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
307405
307692
  approximated: times.length > 0
307406
307693
  };
307407
307694
  }
307408
- var SUMMARY_CONTENT_VAR = /\{\{\s*summary(?:Text|Devices|Streams|Battery|Consumables|Tanks|Detection|Nodes|Addons|Updates)\s*\}\}/;
307695
+ var SUMMARY_CONTENT_VAR = /\{\{\s*summary(?:Text|Devices|Streams|Battery|Consumables|Tanks|Detection|Nodes|Addons|AddonUpdates|Updates)\s*\}\}/;
307409
307696
  function templateCarriesSummary(body) {
307410
307697
  if (body === void 0 || body.trim().length === 0) return true;
307411
307698
  return SUMMARY_CONTENT_VAR.test(body);
@@ -309773,6 +310060,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
309773
310060
  exports.CAMERA_SWITCH_CATALOG = CAMERA_SWITCH_CATALOG;
309774
310061
  exports.CAMERA_SWITCH_ORDER = CAMERA_SWITCH_ORDER;
309775
310062
  exports.CAMERA_TRIGGER = CAMERA_TRIGGER;
310063
+ exports.CAMSTACK_APP_LINK_ORIGIN = CAMSTACK_APP_LINK_ORIGIN;
309776
310064
  exports.CAM_PROFILE_ORDER = require_sleep.CAM_PROFILE_ORDER;
309777
310065
  exports.CAPABILITY_NAMES = CAPABILITY_NAMES;
309778
310066
  exports.CAPABILITY_ROUTER_KEYS = CAPABILITY_ROUTER_KEYS;
@@ -310209,6 +310497,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
310209
310497
  exports.HF_BASE_URL = HF_BASE_URL;
310210
310498
  exports.HF_REPO = HF_REPO;
310211
310499
  exports.HOURS_PER_DAY = HOURS_PER_DAY;
310500
+ exports.HUB_VIEWER_MOUNT = HUB_VIEWER_MOUNT;
310212
310501
  exports.HWACCEL_OPTIONS = HWACCEL_OPTIONS;
310213
310502
  exports.HealthStatusSchema = HealthStatusSchema;
310214
310503
  exports.HfModelResolutionSchema = HfModelResolutionSchema;
@@ -310426,9 +310715,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
310426
310715
  exports.NC_ALARM_CONTROLLABLE_CAPS = NC_ALARM_CONTROLLABLE_CAPS;
310427
310716
  exports.NC_ALARM_DEFAULT_PROFILE_ID = NC_ALARM_DEFAULT_PROFILE_ID;
310428
310717
  exports.NC_ALARM_DEFAULT_PROFILE_NAME = NC_ALARM_DEFAULT_PROFILE_NAME;
310718
+ exports.NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC = NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC;
310429
310719
  exports.NC_ALARM_DELAY_FIELDS = NC_ALARM_DELAY_FIELDS;
310430
310720
  exports.NC_ALARM_DELAY_MAX_SEC = NC_ALARM_DELAY_MAX_SEC;
310431
310721
  exports.NC_ALARM_NONBLOCKING_MAX = NC_ALARM_NONBLOCKING_MAX;
310722
+ exports.NC_ALARM_NOTIFY_FIELDS = NC_ALARM_NOTIFY_FIELDS;
310432
310723
  exports.NC_ALARM_OPENING_EVENT_KINDS = NC_ALARM_OPENING_EVENT_KINDS;
310433
310724
  exports.NC_ALARM_SENSORS_MAX = NC_ALARM_SENSORS_MAX;
310434
310725
  exports.NC_ALARM_SYSTEM_DEFAULT_DELAYS = NC_ALARM_SYSTEM_DEFAULT_DELAYS;
@@ -310904,6 +311195,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
310904
311195
  exports.ServerUpdateCheckResultSchema = ServerUpdateCheckResultSchema;
310905
311196
  exports.ServerUpdateOutcomeSchema = ServerUpdateOutcomeSchema;
310906
311197
  exports.ServerUpdateStateSchema = ServerUpdateStateSchema;
311198
+ exports.SessionTriggerSchema = SessionTriggerSchema;
310907
311199
  exports.SetCameraSensorsInputSchema = SetCameraSensorsInputSchema;
310908
311200
  exports.SetLoggingSettingsInputSchema = SetLoggingSettingsInputSchema;
310909
311201
  exports.SetSiteLocationInputSchema = SetSiteLocationInputSchema;
@@ -311187,6 +311479,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311187
311479
  exports.buildRoleScopes = buildRoleScopes;
311188
311480
  exports.buildStreamParamsConfigSchema = buildStreamParamsConfigSchema;
311189
311481
  exports.buildVideoArgs = require_composition.buildVideoArgs;
311482
+ exports.buildViewerLink = buildViewerLink;
311190
311483
  exports.builtCapabilityBlockers = builtCapabilityBlockers;
311191
311484
  exports.buttonCapability = buttonCapability;
311192
311485
  exports.calendarSourceCapability = calendarSourceCapability;
@@ -311416,6 +311709,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311416
311709
  exports.invocationFromEncodeProfile = require_composition.invocationFromEncodeProfile;
311417
311710
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
311418
311711
  exports.isAlarmControllableCap = isAlarmControllableCap;
311712
+ exports.isAppLinkBase = isAppLinkBase;
311419
311713
  exports.isArrayOutputSchema = isArrayOutputSchema;
311420
311714
  exports.isAudioChunkFormat = require_sleep.isAudioChunkFormat;
311421
311715
  exports.isAudioClassName = isAudioClassName;
@@ -311562,6 +311856,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311562
311856
  exports.patchAudio = patchAudio;
311563
311857
  exports.petFeederCapability = petFeederCapability;
311564
311858
  exports.pickAccessoryControl = pickAccessoryControl;
311859
+ exports.pickAppLinkHub = pickAppLinkHub;
311565
311860
  exports.pickClusterStepModels = pickClusterStepModels;
311566
311861
  exports.pickClusterStepSettings = pickClusterStepSettings;
311567
311862
  exports.pickDetailCropConvention = pickDetailCropConvention;
@@ -311641,6 +311936,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311641
311936
  exports.resolveLocationMode = resolveLocationMode;
311642
311937
  exports.resolveMethodAuth = resolveMethodAuth;
311643
311938
  exports.resolveModeDelays = resolveModeDelays;
311939
+ exports.resolveModeNotifyPolicy = resolveModeNotifyPolicy;
311644
311940
  exports.resolveMutate = resolveMutate;
311645
311941
  exports.resolvePoolMemoryPolicy = resolvePoolMemoryPolicy;
311646
311942
  exports.resolveRecordingProfiles = resolveRecordingProfiles;
@@ -311749,6 +312045,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311749
312045
  exports.terminalSessionCapability = terminalSessionCapability;
311750
312046
  exports.textToHtml = textToHtml;
311751
312047
  exports.timeOfDayToMinutes = timeOfDayToMinutes;
312048
+ exports.toAppLinkHubOrigin = toAppLinkHubOrigin;
311752
312049
  exports.toDeviceSummary = toDeviceSummary;
311753
312050
  exports.toExpressionValue = require_composition.toExpressionValue;
311754
312051
  exports.toNodeId = require_sleep.toNodeId;
@@ -311776,6 +312073,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311776
312073
  exports.vectorStoreCapability = vectorStoreCapability;
311777
312074
  exports.vibrationCapability = vibrationCapability;
311778
312075
  exports.videoclipsCapability = videoclipsCapability;
312076
+ exports.viewerLinkBaseOf = viewerLinkBaseOf;
311779
312077
  exports.viewerUiCapability = require_sleep.viewerUiCapability;
311780
312078
  exports.waterHeaterCapability = waterHeaterCapability;
311781
312079
  exports.weatherCapability = weatherCapability;
@@ -468362,8 +468660,8 @@ var require_model_distributor = __commonJS({
468362
468660
  });
468363
468661
  }
468364
468662
  async function distributeModelWith(seam, input) {
468365
- const { nodeId, modelId, format: format2, entry } = input;
468366
- if (nodeId === "hub") {
468663
+ const { targetNodeId, modelId, format: format2, entry } = input;
468664
+ if (targetNodeId === "hub") {
468367
468665
  return { ok: true, sha256: "", bytes: 0, path: seam.modelsDir };
468368
468666
  }
468369
468667
  if (!seam.isPresent(entry, format2)) {
@@ -468375,7 +468673,7 @@ var require_model_distributor = __commonJS({
468375
468673
  }
468376
468674
  const tgz = await seam.tar(files);
468377
468675
  const source = seam.stage(tgz);
468378
- const res = await seam.callAgent(nodeId, { modelId, format: format2, source });
468676
+ const res = await seam.callAgent(targetNodeId, { modelId, format: format2, source });
468379
468677
  return { ok: true, sha256: source.sha256, bytes: source.bytes, path: res.path ?? "" };
468380
468678
  }
468381
468679
  function buildModelDistributorProvider(moleculer, loggingService) {
@@ -468401,7 +468699,7 @@ var require_model_distributor = __commonJS({
468401
468699
  return {
468402
468700
  distributeModel: async (input) => {
468403
468701
  logger.info("Distributing model to node", {
468404
- meta: { modelId: input.modelId, format: input.format, nodeId: input.nodeId }
468702
+ meta: { modelId: input.modelId, format: input.format, nodeId: input.targetNodeId }
468405
468703
  });
468406
468704
  return distributeModelWith(seam, input);
468407
468705
  }
@@ -469430,6 +469728,224 @@ var require_frame_policy = __commonJS({
469430
469728
  }
469431
469729
  });
469432
469730
 
469731
+ // ../../server/backend/dist/api/cors/cors-policy.js
469732
+ var require_cors_policy = __commonJS({
469733
+ "../../server/backend/dist/api/cors/cors-policy.js"(exports) {
469734
+ "use strict";
469735
+ Object.defineProperty(exports, "__esModule", { value: true });
469736
+ exports.CORS_PREFLIGHT_MAX_AGE_SEC = exports.CORS_EXPOSED_HEADERS = exports.CORS_ALLOWED_HEADERS = exports.CORS_ALLOWED_METHODS = exports.OPAQUE_ORIGIN = exports.VIEWER_PWA_ORIGIN = void 0;
469737
+ exports.normalizeOrigin = normalizeOrigin;
469738
+ exports.parseConfiguredOrigins = parseConfiguredOrigins;
469739
+ exports.builtInOrigins = builtInOrigins;
469740
+ exports.judgeCorsOrigin = judgeCorsOrigin;
469741
+ exports.corsResponseHeaders = corsResponseHeaders;
469742
+ exports.appendVary = appendVary;
469743
+ exports.VIEWER_PWA_ORIGIN = "https://camstack.zentik.app";
469744
+ exports.OPAQUE_ORIGIN = "null";
469745
+ exports.CORS_ALLOWED_METHODS = "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS";
469746
+ exports.CORS_ALLOWED_HEADERS = "authorization, content-type, range, if-range, if-none-match, cache-control, trpc-accept, x-trpc-source, x-trpc-batch-mode";
469747
+ exports.CORS_EXPOSED_HEADERS = "content-range, accept-ranges, content-length, content-disposition, etag, retry-after, x-camstack-reason, x-camstack-reason-code, x-camstack-disposition";
469748
+ exports.CORS_PREFLIGHT_MAX_AGE_SEC = 7200;
469749
+ function normalizeOrigin(raw) {
469750
+ const trimmed = raw.trim();
469751
+ if (trimmed === exports.OPAQUE_ORIGIN)
469752
+ return exports.OPAQUE_ORIGIN;
469753
+ let url;
469754
+ try {
469755
+ url = new URL(trimmed);
469756
+ } catch {
469757
+ return null;
469758
+ }
469759
+ if (url.protocol !== "https:" && url.protocol !== "http:")
469760
+ return null;
469761
+ if (url.username !== "" || url.password !== "")
469762
+ return null;
469763
+ if (url.pathname !== "/" && url.pathname !== "" || url.search !== "" || url.hash !== "") {
469764
+ return null;
469765
+ }
469766
+ return url.origin;
469767
+ }
469768
+ function parseConfiguredOrigins(value) {
469769
+ if (!Array.isArray(value))
469770
+ return { origins: [], rejected: [] };
469771
+ const origins = [];
469772
+ const rejected = [];
469773
+ for (const entry of value) {
469774
+ if (typeof entry !== "string") {
469775
+ rejected.push(String(entry));
469776
+ continue;
469777
+ }
469778
+ const normalized = normalizeOrigin(entry);
469779
+ if (normalized === null)
469780
+ rejected.push(entry);
469781
+ else
469782
+ origins.push(normalized);
469783
+ }
469784
+ return { origins, rejected };
469785
+ }
469786
+ function builtInOrigins(publicOrigin) {
469787
+ const entries = [exports.VIEWER_PWA_ORIGIN, exports.OPAQUE_ORIGIN];
469788
+ const pub = publicOrigin !== void 0 ? normalizeOrigin(publicOrigin) : null;
469789
+ if (pub !== null && pub !== exports.OPAQUE_ORIGIN)
469790
+ entries.push(pub);
469791
+ return entries;
469792
+ }
469793
+ function firstHost(value) {
469794
+ if (value === void 0)
469795
+ return null;
469796
+ const first = value.split(",")[0]?.trim().toLowerCase() ?? "";
469797
+ return first.length > 0 ? first : null;
469798
+ }
469799
+ function hostOfOrigin(origin) {
469800
+ if (origin === exports.OPAQUE_ORIGIN)
469801
+ return null;
469802
+ try {
469803
+ return new URL(origin).host.toLowerCase();
469804
+ } catch {
469805
+ return null;
469806
+ }
469807
+ }
469808
+ function judgeCorsOrigin(facts, listed) {
469809
+ const raw = facts.origin;
469810
+ if (raw === void 0 || raw.length === 0)
469811
+ return { kind: "no-origin" };
469812
+ const normalized = normalizeOrigin(raw);
469813
+ if (normalized === null)
469814
+ return { kind: "denied", origin: raw };
469815
+ if (listed.has(normalized))
469816
+ return { kind: "allowed", origin: raw, reason: "listed" };
469817
+ const originHost = hostOfOrigin(normalized);
469818
+ if (originHost !== null) {
469819
+ const ownHosts = [firstHost(facts.host), firstHost(facts.forwardedHost)];
469820
+ if (ownHosts.includes(originHost))
469821
+ return { kind: "allowed", origin: raw, reason: "own-host" };
469822
+ }
469823
+ return { kind: "denied", origin: raw };
469824
+ }
469825
+ function corsResponseHeaders(origin, preflight) {
469826
+ const base = {
469827
+ "access-control-allow-origin": origin,
469828
+ "access-control-expose-headers": exports.CORS_EXPOSED_HEADERS
469829
+ };
469830
+ if (preflight === null)
469831
+ return base;
469832
+ return {
469833
+ ...base,
469834
+ "access-control-allow-methods": exports.CORS_ALLOWED_METHODS,
469835
+ "access-control-allow-headers": exports.CORS_ALLOWED_HEADERS,
469836
+ "access-control-max-age": String(exports.CORS_PREFLIGHT_MAX_AGE_SEC),
469837
+ // A public origin (the PWA) calling a LAN hub is a "private network"
469838
+ // request to Chromium; its preflight must be answered explicitly.
469839
+ ...preflight.requestPrivateNetwork ? { "access-control-allow-private-network": "true" } : {}
469840
+ };
469841
+ }
469842
+ function appendVary(existing, token2) {
469843
+ if (existing === void 0 || existing.trim().length === 0)
469844
+ return token2;
469845
+ const parts = existing.split(",").map((p) => p.trim().toLowerCase());
469846
+ if (parts.includes("*") || parts.includes(token2.toLowerCase()))
469847
+ return existing;
469848
+ return `${existing}, ${token2}`;
469849
+ }
469850
+ }
469851
+ });
469852
+
469853
+ // ../../server/backend/dist/api/cors/register-cors-policy.js
469854
+ var require_register_cors_policy = __commonJS({
469855
+ "../../server/backend/dist/api/cors/register-cors-policy.js"(exports) {
469856
+ "use strict";
469857
+ Object.defineProperty(exports, "__esModule", { value: true });
469858
+ exports.createCorsPolicy = createCorsPolicy;
469859
+ exports.registerCorsPolicy = registerCorsPolicy;
469860
+ exports.refuseForeignWsUpgrade = refuseForeignWsUpgrade;
469861
+ var cors_policy_js_1 = require_cors_policy();
469862
+ var MAX_LOGGED_DENIALS = 256;
469863
+ function headerValue(value) {
469864
+ if (Array.isArray(value))
469865
+ return value[0];
469866
+ return value;
469867
+ }
469868
+ function varyText(value) {
469869
+ if (typeof value === "string")
469870
+ return value;
469871
+ if (Array.isArray(value))
469872
+ return value.filter((v) => typeof v === "string").join(", ");
469873
+ return void 0;
469874
+ }
469875
+ function createCorsPolicy(deps) {
469876
+ const builtIns = (0, cors_policy_js_1.builtInOrigins)(deps.publicOrigin);
469877
+ let lastConfigured = /* @__PURE__ */ Symbol("unset");
469878
+ let listed = new Set(builtIns);
469879
+ const loggedDenials = /* @__PURE__ */ new Set();
469880
+ const currentList = () => {
469881
+ const configured = deps.configuredOrigins();
469882
+ if (configured === lastConfigured)
469883
+ return listed;
469884
+ lastConfigured = configured;
469885
+ const parsed = (0, cors_policy_js_1.parseConfiguredOrigins)(configured);
469886
+ if (parsed.rejected.length > 0) {
469887
+ deps.logger.warn("cors: ignored configured origins that are not http(s) origins", {
469888
+ meta: { rejected: parsed.rejected, setting: "server.corsOrigins" }
469889
+ });
469890
+ }
469891
+ listed = /* @__PURE__ */ new Set([...builtIns, ...parsed.origins]);
469892
+ return listed;
469893
+ };
469894
+ return {
469895
+ judge(headers) {
469896
+ const verdict = (0, cors_policy_js_1.judgeCorsOrigin)({
469897
+ origin: headerValue(headers.origin),
469898
+ host: headerValue(headers.host),
469899
+ forwardedHost: headerValue(headers["x-forwarded-host"])
469900
+ }, currentList());
469901
+ if (verdict.kind === "denied" && !loggedDenials.has(verdict.origin)) {
469902
+ if (loggedDenials.size < MAX_LOGGED_DENIALS)
469903
+ loggedDenials.add(verdict.origin);
469904
+ deps.logger.warn("cors: refused a cross-origin caller not on the allowlist", {
469905
+ meta: {
469906
+ origin: verdict.origin,
469907
+ host: headerValue(headers.host) ?? null,
469908
+ hint: "add it to server.corsOrigins (config.yaml) or CAMSTACK_CORS_ORIGINS"
469909
+ }
469910
+ });
469911
+ }
469912
+ return verdict;
469913
+ }
469914
+ };
469915
+ }
469916
+ function registerCorsPolicy(fastify, policy) {
469917
+ fastify.addHook("onRequest", async (request, reply) => {
469918
+ const vary = (0, cors_policy_js_1.appendVary)(varyText(reply.getHeader("vary")), "Origin");
469919
+ reply.header("vary", vary);
469920
+ reply.raw.setHeader("vary", vary);
469921
+ const verdict = policy.judge(request.headers);
469922
+ const isPreflight = request.method === "OPTIONS" && request.headers["access-control-request-method"] !== void 0;
469923
+ if (verdict.kind === "allowed") {
469924
+ const headers = (0, cors_policy_js_1.corsResponseHeaders)(verdict.origin, isPreflight ? {
469925
+ requestPrivateNetwork: headerValue(request.headers["access-control-request-private-network"]) === "true"
469926
+ } : null);
469927
+ for (const [name, value] of Object.entries(headers)) {
469928
+ reply.header(name, value);
469929
+ reply.raw.setHeader(name, value);
469930
+ }
469931
+ }
469932
+ if (request.method === "OPTIONS") {
469933
+ return reply.code(204).send();
469934
+ }
469935
+ return void 0;
469936
+ });
469937
+ }
469938
+ function refuseForeignWsUpgrade(policy, headers, socket) {
469939
+ const verdict = policy.judge(headers);
469940
+ if (verdict.kind !== "denied")
469941
+ return false;
469942
+ socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
469943
+ socket.destroy();
469944
+ return true;
469945
+ }
469946
+ }
469947
+ });
469948
+
469433
469949
  // ../../server/backend/dist/boot/integration-id-backfill.js
469434
469950
  var require_integration_id_backfill = __commonJS({
469435
469951
  "../../server/backend/dist/boot/integration-id-backfill.js"(exports) {
@@ -485915,16 +486431,6 @@ var require_manual_boot = __commonJS({
485915
486431
  getHttpAdapter() {
485916
486432
  return { getInstance: () => fastify };
485917
486433
  },
485918
- enableCors() {
485919
- fastify.addHook("onRequest", async (request, reply) => {
485920
- reply.header("access-control-allow-origin", "*");
485921
- reply.header("access-control-allow-methods", "GET,POST,PUT,DELETE,OPTIONS,PATCH");
485922
- reply.header("access-control-allow-headers", "content-type,authorization");
485923
- if (request.method === "OPTIONS") {
485924
- reply.status(204).send();
485925
- }
485926
- });
485927
- },
485928
486434
  enableShutdownHooks() {
485929
486435
  if (shutdownHooksRegistered)
485930
486436
  return;
@@ -486035,6 +486541,7 @@ var require_main4 = __commonJS({
486035
486541
  var spa_static_1 = require_spa_static();
486036
486542
  var spa_mount_1 = require_spa_mount();
486037
486543
  var frame_policy_1 = require_frame_policy();
486544
+ var register_cors_policy_1 = require_register_cors_policy();
486038
486545
  var cap_providers_1 = require_cap_providers();
486039
486546
  var logging_settings_1 = require_logging_settings();
486040
486547
  var request_census_settings_1 = require_request_census_settings();
@@ -486201,8 +486708,14 @@ var require_main4 = __commonJS({
486201
486708
  const fastifyOpts = tlsOptions ? { https: tlsOptions } : {};
486202
486709
  const app = await (0, manual_boot_1.bootManual)({ infra, fastifyOpts });
486203
486710
  app.enableShutdownHooks();
486204
- app.enableCors();
486205
486711
  const fastify = app.getHttpAdapter().getInstance();
486712
+ const corsConfig = app.get(config_service_1.ConfigService);
486713
+ const corsPolicy = (0, register_cors_policy_1.createCorsPolicy)({
486714
+ configuredOrigins: () => corsConfig.get("server.corsOrigins"),
486715
+ publicOrigin: process.env.CAMSTACK_PUBLIC_ORIGIN,
486716
+ logger: app.get(logging_service_1.LoggingService).createLogger("cors")
486717
+ });
486718
+ (0, register_cors_policy_1.registerCorsPolicy)(fastify, corsPolicy);
486206
486719
  await fastify.register(cookie_1.default);
486207
486720
  (0, frame_policy_1.registerFramePolicy)(fastify);
486208
486721
  fastify.addHook("onRequest", async (request) => {
@@ -486785,6 +487298,8 @@ var require_main4 = __commonJS({
486785
487298
  httpServer.on("upgrade", (request, socket, head) => {
486786
487299
  const pathname = (request.url ?? "").split("?")[0];
486787
487300
  if (pathname === "/trpc") {
487301
+ if ((0, register_cors_policy_1.refuseForeignWsUpgrade)(corsPolicy, request.headers, socket))
487302
+ return;
486788
487303
  wss.handleUpgrade(request, socket, head, (ws) => {
486789
487304
  wss.emit("connection", ws, request);
486790
487305
  });