@camstack/types 1.2.427 → 1.2.429

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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-edKiOXLo.js");
2
+ const require_sleep = require("./sleep-hRhTSDlN.js");
3
3
  const require_event_category = require("./event-category-Ave2VSxY.js");
4
4
  const require_composition = require("./composition-CpAc0YfQ.js");
5
5
  const require_node_color = require("./node-color.js");
@@ -289,6 +289,254 @@ function buildCapturingReply(envelope) {
289
289
  return wrapper;
290
290
  }
291
291
  //#endregion
292
+ //#region src/addon/session-health.ts
293
+ var SESSION_LOST_ALERT_CATEGORY = "integration.session-lost";
294
+ var SESSION_EPISODE_KEY = "session-lost";
295
+ /** A transient reconnect is quiet for this long before it is worth an alert. */
296
+ var DEFAULT_SESSION_LOST_DEBOUNCE_MS = 2 * 6e4;
297
+ var REASON_TEXT = {
298
+ kicked: "the account was signed in on another device, which ended this session",
299
+ "auth-rejected": "the cloud rejected the sign-in (check the credentials)",
300
+ "token-expired": "the session token expired and could not be renewed",
301
+ "connection-lost": "the connection to the cloud was lost"
302
+ };
303
+ function sessionLostAlertId(kind, integrationId) {
304
+ return `session:${kind}:${integrationId}`;
305
+ }
306
+ /** Pure builder, exported so an addon with its own flow can still emit the standard shape. */
307
+ function buildSessionLostAlert(input) {
308
+ const now = input.now ?? Date.now();
309
+ return {
310
+ id: sessionLostAlertId(input.kind, input.integrationId),
311
+ category: SESSION_LOST_ALERT_CATEGORY,
312
+ severity: input.severity ?? "error",
313
+ title: `${input.providerName} connection lost`,
314
+ message: `${input.integrationName} lost its ${input.providerName} session: ${REASON_TEXT[input.reason]}.`,
315
+ action: input.action ?? {
316
+ label: "Open integration",
317
+ href: `/integrations/${encodeURIComponent(input.integrationId)}`
318
+ },
319
+ status: "active",
320
+ read: false,
321
+ createdAt: now,
322
+ updatedAt: now,
323
+ source: {
324
+ type: input.kind,
325
+ id: input.integrationId
326
+ },
327
+ metadata: {
328
+ episodeKey: SESSION_EPISODE_KEY,
329
+ reason: input.reason
330
+ }
331
+ };
332
+ }
333
+ var SessionHealthTracker = class {
334
+ #opts;
335
+ #debounceMs;
336
+ #entries = /* @__PURE__ */ new Map();
337
+ constructor(opts) {
338
+ this.#opts = opts;
339
+ this.#debounceMs = opts.debounceMs ?? 12e4;
340
+ }
341
+ /** The session is gone. Idempotent: call it on every failed poll. */
342
+ lost(integrationId, info) {
343
+ const tags = { integrationId };
344
+ const entry = this.#entries.get(integrationId);
345
+ if (entry === void 0 || entry.phase === "ok") {
346
+ const timer = setTimeout(() => void this.#fire(integrationId), this.#debounceMs);
347
+ this.#entries.set(integrationId, {
348
+ phase: "pending",
349
+ info,
350
+ timer
351
+ });
352
+ this.#opts.logger.info(`${this.#opts.providerName} session lost — alert after ${this.#debounceMs}ms unless it recovers`, {
353
+ tags,
354
+ meta: { reason: info.reason }
355
+ });
356
+ return;
357
+ }
358
+ entry.info = info;
359
+ if (entry.phase === "pending") {
360
+ this.#opts.logger.debug(`${this.#opts.providerName} session still lost — alert pending`, {
361
+ tags,
362
+ meta: { reason: info.reason }
363
+ });
364
+ return;
365
+ }
366
+ if (entry.phase === "unsent") {
367
+ this.#emit(integrationId, entry);
368
+ return;
369
+ }
370
+ this.#opts.logger.debug(`${this.#opts.providerName} session still lost — alert already active`, {
371
+ tags,
372
+ meta: { reason: info.reason }
373
+ });
374
+ }
375
+ /** A call went through. Idempotent: call it on every successful poll. */
376
+ async restored(integrationId) {
377
+ const tags = { integrationId };
378
+ const entry = this.#entries.get(integrationId);
379
+ if (entry === void 0) {
380
+ this.#entries.set(integrationId, {
381
+ phase: "ok",
382
+ info: EMPTY_INFO,
383
+ timer: null
384
+ });
385
+ if (!await this.#resolve(integrationId, "startup reconcile")) this.#entries.delete(integrationId);
386
+ return;
387
+ }
388
+ if (entry.phase === "ok") return;
389
+ this.#clearTimer(entry);
390
+ const previous = entry.phase;
391
+ entry.phase = "ok";
392
+ if (previous === "pending" || previous === "unsent") {
393
+ this.#opts.logger.info(`${this.#opts.providerName} session recovered inside the debounce — no alert`, { tags });
394
+ return;
395
+ }
396
+ await this.#resolve(integrationId, "session restored");
397
+ }
398
+ /** The integration is gone (deleted / disabled): drop its state and its alert. */
399
+ async forget(integrationId) {
400
+ const entry = this.#entries.get(integrationId);
401
+ if (entry === void 0) return;
402
+ this.#clearTimer(entry);
403
+ this.#entries.delete(integrationId);
404
+ if (entry.phase === "alerted") await this.#resolve(integrationId, "integration removed");
405
+ }
406
+ /** Stop every pending timer (addon shutdown). */
407
+ dispose() {
408
+ for (const entry of this.#entries.values()) this.#clearTimer(entry);
409
+ this.#entries.clear();
410
+ }
411
+ #clearTimer(entry) {
412
+ if (entry.timer !== null) clearTimeout(entry.timer);
413
+ entry.timer = null;
414
+ }
415
+ async #fire(integrationId) {
416
+ const entry = this.#entries.get(integrationId);
417
+ if (entry === void 0 || entry.phase !== "pending") return;
418
+ entry.timer = null;
419
+ await this.#emit(integrationId, entry);
420
+ }
421
+ async #emit(integrationId, entry) {
422
+ const tags = { integrationId };
423
+ const emit = this.#opts.getAlerts()?.emit;
424
+ if (emit === void 0) {
425
+ entry.phase = "unsent";
426
+ this.#opts.logger.warn(`${this.#opts.providerName} session alert skipped — alerts capability missing`, { tags });
427
+ return;
428
+ }
429
+ try {
430
+ await emit.mutate(buildSessionLostAlert({
431
+ kind: this.#opts.kind,
432
+ providerName: this.#opts.providerName,
433
+ integrationId,
434
+ integrationName: entry.info.integrationName,
435
+ reason: entry.info.reason,
436
+ ...entry.info.action !== void 0 ? { action: entry.info.action } : {}
437
+ }));
438
+ entry.phase = "alerted";
439
+ this.#opts.logger.warn(`${this.#opts.providerName} session lost — alert raised`, {
440
+ tags,
441
+ meta: { reason: entry.info.reason }
442
+ });
443
+ } catch (err) {
444
+ entry.phase = "unsent";
445
+ this.#opts.logger.warn(`${this.#opts.providerName} session alert emit failed`, {
446
+ tags,
447
+ meta: { error: err instanceof Error ? err.message : String(err) }
448
+ });
449
+ }
450
+ }
451
+ async #resolve(integrationId, why) {
452
+ const tags = { integrationId };
453
+ const update = this.#opts.getAlerts()?.update;
454
+ if (update === void 0) {
455
+ this.#opts.logger.debug(`${this.#opts.providerName} session alert not resolved — no alerts cap`, { tags });
456
+ return false;
457
+ }
458
+ try {
459
+ await update.mutate({
460
+ alertId: sessionLostAlertId(this.#opts.kind, integrationId),
461
+ patch: {
462
+ status: "completed",
463
+ read: true
464
+ }
465
+ });
466
+ this.#opts.logger.info(`${this.#opts.providerName} session alert resolved`, {
467
+ tags,
468
+ meta: { why }
469
+ });
470
+ } catch (err) {
471
+ this.#opts.logger.debug(`${this.#opts.providerName} session alert resolve skipped`, {
472
+ tags,
473
+ meta: {
474
+ why,
475
+ error: err instanceof Error ? err.message : String(err)
476
+ }
477
+ });
478
+ }
479
+ return true;
480
+ }
481
+ };
482
+ var EMPTY_INFO = {
483
+ integrationName: "",
484
+ reason: "connection-lost"
485
+ };
486
+ /**
487
+ * The addon-facing face of {@link SessionHealthTracker}: a process-wide object an
488
+ * integration manager AND its device classes can reach (the kernel builds a
489
+ * device from a `DeviceContext` alone, so neither can be handed a tracker).
490
+ * The addon `bind()`s it in `onInitialize`, `unbind()`s it on shutdown and
491
+ * `forget()`s an integration that vanished; everything before `bind` is a no-op.
492
+ */
493
+ var IntegrationSessionReporter = class {
494
+ #opts;
495
+ #tracker = null;
496
+ #names = /* @__PURE__ */ new Map();
497
+ constructor(opts) {
498
+ this.#opts = opts;
499
+ }
500
+ bind(binding) {
501
+ this.#tracker?.dispose();
502
+ this.#tracker = new SessionHealthTracker({
503
+ kind: this.#opts.kind,
504
+ providerName: this.#opts.providerName,
505
+ logger: binding.logger,
506
+ getAlerts: binding.getAlerts,
507
+ ...binding.debounceMs !== void 0 ? { debounceMs: binding.debounceMs } : {}
508
+ });
509
+ }
510
+ unbind() {
511
+ this.#tracker?.dispose();
512
+ this.#tracker = null;
513
+ }
514
+ /** The manager tells us the display name once, so a device can report by id alone. */
515
+ register(integrationId, integrationName) {
516
+ this.#names.set(integrationId, integrationName);
517
+ }
518
+ /** A call failed. Only a session-ending failure counts; anything else is ignored. */
519
+ failure(integrationId, err) {
520
+ const reason = this.#opts.classify(err);
521
+ if (reason === null) return;
522
+ const action = this.#opts.action?.(integrationId);
523
+ this.#tracker?.lost(integrationId, {
524
+ integrationName: this.#names.get(integrationId) ?? integrationId,
525
+ reason,
526
+ ...action !== void 0 ? { action } : {}
527
+ });
528
+ }
529
+ /** A call went through. */
530
+ async ok(integrationId) {
531
+ await this.#tracker?.restored(integrationId);
532
+ }
533
+ /** The integration was removed. */
534
+ async forget(integrationId) {
535
+ this.#names.delete(integrationId);
536
+ await this.#tracker?.forget(integrationId);
537
+ }
538
+ };
539
+ //#endregion
292
540
  //#region src/encode-profile.ts
293
541
  /**
294
542
  * Shared video / audio encode profile schemas used by every consumer
@@ -13871,7 +14119,7 @@ async function runFanOut(calls, invoke) {
13871
14119
  //#region src/generated/history-catalog.ts
13872
14120
  /**
13873
14121
  * Cap history catalog, projected from every `*.cap.ts` `history` block.
13874
- * 42 caps with series (76 series), 33 caps answered `'none'`.
14122
+ * 42 caps with series (77 series), 33 caps answered `'none'`.
13875
14123
  * Field keys are `runtimeState` paths (`a.b` into fixed objects only).
13876
14124
  */
13877
14125
  var HISTORY_CATALOG = {
@@ -14098,6 +14346,7 @@ var HISTORY_CATALOG = {
14098
14346
  } },
14099
14347
  "pet-feeder": {
14100
14348
  "foodLevel": { kind: "gauge" },
14349
+ "bowlFood": { kind: "gauge" },
14101
14350
  "food1": { kind: "gauge" },
14102
14351
  "food2": { kind: "gauge" },
14103
14352
  "batteryPower": {
@@ -43856,7 +44105,7 @@ var NetworkAccessStatusSchema = zod.z.object({
43856
44105
  });
43857
44106
  /**
43858
44107
  * Optional, richer endpoint shape returned by providers that expose
43859
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
44108
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
43860
44109
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
43861
44110
  * the originating provider config (mode + sourcePort) so the
43862
44111
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -70222,10 +70471,20 @@ var PetFeederDeviceStatusSchema = zod.z.enum([
70222
70471
  ]);
70223
70472
  var gramsPortion = zod.z.number().int().min(4).max(200);
70224
70473
  var PetFeederStatusSchema = zod.z.object({
70225
- /** Food currently in the bowl (grams). Null when the device has not
70226
- * reported a reading yet. On dual-hopper models this is the combined
70227
- * bowl reading; per-hopper levels live in `food1`/`food2`. */
70474
+ /** Food in the TANK (hopper), as the feeder's own sensor reports it
70475
+ * (`state.food`). A coarse level code, not grams — `0` is empty. The
70476
+ * amount in the bowl is `bowlFood`. On dual-hopper models this is the
70477
+ * combined reading; per-hopper levels live in `food1`/`food2`. Null when
70478
+ * the device has not reported yet. */
70228
70479
  foodLevel: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
70480
+ /** Food in the BOWL as the feeder reports it (`state.bowl`), in the
70481
+ * feeder's own units. Null when the model reports none. */
70482
+ bowlFood: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
70483
+ /** True while the pet is eating (`state.eating`). */
70484
+ eating: zod.z.boolean(),
70485
+ /** Whether `markFoodReplenished` does anything on this model. When false
70486
+ * the client must not offer it. */
70487
+ supportsMarkRefilled: zod.z.boolean(),
70229
70488
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
70230
70489
  * single-hopper models. */
70231
70490
  food1: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
@@ -70233,8 +70492,8 @@ var PetFeederStatusSchema = zod.z.object({
70233
70492
  * single-hopper models. */
70234
70493
  food2: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
70235
70494
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
70236
- * (`device_class: problem`, on = low). True when the bowl is empty /
70237
- * below the feeder's low threshold. */
70495
+ * (`device_class: problem`, on = low). True when the tank reports empty,
70496
+ * or, on a dual-hopper model, when EITHER hopper is empty. */
70238
70497
  lowFood: zod.z.boolean(),
70239
70498
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
70240
70499
  * device has no battery reading. */
@@ -70302,7 +70561,9 @@ var petFeederCapability = {
70302
70561
  kind: "mutation",
70303
70562
  auth: "protected"
70304
70563
  }),
70305
- /** Mark a hopper as refilled (D4H/D4S/D4SH). */
70564
+ /** Tell the PetKit cloud a hopper was refilled so it stops its low-food
70565
+ * reminders (D4H/D4S/D4SH only — see `supportsMarkRefilled`). It does not
70566
+ * change any sensor reading. */
70306
70567
  markFoodReplenished: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.void(), {
70307
70568
  kind: "mutation",
70308
70569
  auth: "protected"
@@ -70375,6 +70636,7 @@ var petFeederCapability = {
70375
70636
  */
70376
70637
  history: { fields: {
70377
70638
  foodLevel: { kind: "gauge" },
70639
+ bowlFood: { kind: "gauge" },
70378
70640
  food1: { kind: "gauge" },
70379
70641
  food2: { kind: "gauge" },
70380
70642
  batteryPower: {
@@ -117307,6 +117569,7 @@ exports.DEFAULT_POOL_MEMORY_POLICY = DEFAULT_POOL_MEMORY_POLICY;
117307
117569
  exports.DEFAULT_RECORDING_PROFILES = DEFAULT_RECORDING_PROFILES;
117308
117570
  exports.DEFAULT_RETENTION = DEFAULT_RETENTION;
117309
117571
  exports.DEFAULT_RUNTIME_STATE_DURABILITY = require_sleep.DEFAULT_RUNTIME_STATE_DURABILITY;
117572
+ exports.DEFAULT_SESSION_LOST_DEBOUNCE_MS = DEFAULT_SESSION_LOST_DEBOUNCE_MS;
117310
117573
  exports.DEFAULT_SESSION_PLACEMENT_POLICY = DEFAULT_SESSION_PLACEMENT_POLICY;
117311
117574
  exports.DEFAULT_SHARE_SOURCE = DEFAULT_SHARE_SOURCE;
117312
117575
  exports.DEFAULT_TIMELAPSE_PREVIEW_TEXT = DEFAULT_TIMELAPSE_PREVIEW_TEXT;
@@ -117652,6 +117915,7 @@ exports.IngestOwnerSchema = IngestOwnerSchema;
117652
117915
  exports.InstalledPackageSchema = InstalledPackageSchema;
117653
117916
  exports.IntegralHelperSpecSchema = require_composition.IntegralHelperSpecSchema;
117654
117917
  exports.IntegrationLiteSchema = IntegrationLiteSchema;
117918
+ exports.IntegrationSessionReporter = IntegrationSessionReporter;
117655
117919
  exports.IntegrationWithStateSchema = IntegrationWithStateSchema;
117656
117920
  exports.IntercomAbilitySchema = IntercomAbilitySchema;
117657
117921
  exports.IntercomStatusSchema = IntercomStatusSchema;
@@ -118556,6 +118820,7 @@ exports.SERIAL_CAPACITY_CAMERA_FPS = SERIAL_CAPACITY_CAMERA_FPS;
118556
118820
  exports.SERIES_KINDS = require_composition.SERIES_KINDS;
118557
118821
  exports.SERVER_NAME_MAX_LENGTH = SERVER_NAME_MAX_LENGTH;
118558
118822
  exports.SERVER_NAME_SETTING_KEY = SERVER_NAME_SETTING_KEY;
118823
+ exports.SESSION_LOST_ALERT_CATEGORY = SESSION_LOST_ALERT_CATEGORY;
118559
118824
  exports.SESSION_PLACEMENT_SETTING_KEY = SESSION_PLACEMENT_SETTING_KEY;
118560
118825
  exports.SHARE_AFFINITY_TOLERANCE_SESSIONS = SHARE_AFFINITY_TOLERANCE_SESSIONS;
118561
118826
  exports.SHARE_SOURCE_SETTING_KEY = SHARE_SOURCE_SETTING_KEY;
@@ -118612,6 +118877,7 @@ exports.ServerUpdateActionResultSchema = ServerUpdateActionResultSchema;
118612
118877
  exports.ServerUpdateCheckResultSchema = ServerUpdateCheckResultSchema;
118613
118878
  exports.ServerUpdateOutcomeSchema = ServerUpdateOutcomeSchema;
118614
118879
  exports.ServerUpdateStateSchema = ServerUpdateStateSchema;
118880
+ exports.SessionHealthTracker = SessionHealthTracker;
118615
118881
  exports.SessionPlacementPolicySchema = SessionPlacementPolicySchema;
118616
118882
  exports.SessionTriggerSchema = SessionTriggerSchema;
118617
118883
  exports.SetCameraSensorsInputSchema = SetCameraSensorsInputSchema;
@@ -118995,6 +119261,7 @@ exports.buildNcTaxonomy = buildNcTaxonomy;
118995
119261
  exports.buildPlusCatalogClassMap = buildPlusCatalogClassMap;
118996
119262
  exports.buildRoleScopes = buildRoleScopes;
118997
119263
  exports.buildSecretRef = require_sleep.buildSecretRef;
119264
+ exports.buildSessionLostAlert = buildSessionLostAlert;
118998
119265
  exports.buildStreamParamsConfigSchema = buildStreamParamsConfigSchema;
118999
119266
  exports.buildSubjectPath = buildSubjectPath;
119000
119267
  exports.buildVideoArgs = require_composition.buildVideoArgs;
@@ -119795,6 +120062,7 @@ exports.sensorTransitionNeverFires = sensorTransitionNeverFires;
119795
120062
  exports.sensorTransitionOffered = sensorTransitionOffered;
119796
120063
  exports.servedTemplateVars = servedTemplateVars;
119797
120064
  exports.serverManagementCapability = serverManagementCapability;
120065
+ exports.sessionLostAlertId = sessionLostAlertId;
119798
120066
  exports.sessionsOfDeviceCell = sessionsOfDeviceCell;
119799
120067
  exports.setByPath = setByPath;
119800
120068
  exports.setFormulaExpr = setFormulaExpr;