camstack 1.2.112 → 1.2.114
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.
|
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
|
|
|
23633
23633
|
}
|
|
23634
23634
|
});
|
|
23635
23635
|
|
|
23636
|
-
// ../system/dist/dist-
|
|
23637
|
-
var
|
|
23638
|
-
"../system/dist/dist-
|
|
23636
|
+
// ../system/dist/dist-BFvWv_bz.js
|
|
23637
|
+
var require_dist_BFvWv_bz = __commonJS({
|
|
23638
|
+
"../system/dist/dist-BFvWv_bz.js"(exports) {
|
|
23639
23639
|
"use strict";
|
|
23640
23640
|
var zod = require_zod();
|
|
23641
23641
|
var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
@@ -23684,6 +23684,7 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
23684
23684
|
EventCategory2["ProcessCrashed"] = "process.crashed";
|
|
23685
23685
|
EventCategory2["ProcessRestartScheduled"] = "process.restart_scheduled";
|
|
23686
23686
|
EventCategory2["ProcessRestarted"] = "process.restarted";
|
|
23687
|
+
EventCategory2["StorageLocationsChanged"] = "storage.locations-changed";
|
|
23687
23688
|
EventCategory2["RecordingStarted"] = "recording.started";
|
|
23688
23689
|
EventCategory2["RecordingStopped"] = "recording.stopped";
|
|
23689
23690
|
EventCategory2["RecordingError"] = "recording.error";
|
|
@@ -26656,6 +26657,71 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
26656
26657
|
error: zod.z.string().nullable()
|
|
26657
26658
|
});
|
|
26658
26659
|
var StorageCleanupStatusInputSchema = zod.z.object({ jobId: zod.z.string().optional() });
|
|
26660
|
+
var STORAGE_LOCATION_MODES = [
|
|
26661
|
+
"active",
|
|
26662
|
+
"readonly",
|
|
26663
|
+
"drain",
|
|
26664
|
+
"disabled"
|
|
26665
|
+
];
|
|
26666
|
+
var StorageLocationModeSchema = zod.z.enum(STORAGE_LOCATION_MODES);
|
|
26667
|
+
zod.z.enum([
|
|
26668
|
+
"normal",
|
|
26669
|
+
"never",
|
|
26670
|
+
"drain"
|
|
26671
|
+
]);
|
|
26672
|
+
var LEGACY_READ_ONLY_CONFIG_KEY = "readOnly";
|
|
26673
|
+
function modeMayWrite(mode) {
|
|
26674
|
+
return mode === "active";
|
|
26675
|
+
}
|
|
26676
|
+
function modeMayRead(mode) {
|
|
26677
|
+
return mode !== "disabled";
|
|
26678
|
+
}
|
|
26679
|
+
function evictionPolicyForMode(mode) {
|
|
26680
|
+
switch (mode) {
|
|
26681
|
+
case "active":
|
|
26682
|
+
return "normal";
|
|
26683
|
+
case "drain":
|
|
26684
|
+
return "drain";
|
|
26685
|
+
case "readonly":
|
|
26686
|
+
case "disabled":
|
|
26687
|
+
return "never";
|
|
26688
|
+
}
|
|
26689
|
+
}
|
|
26690
|
+
function legacyModeOf(location) {
|
|
26691
|
+
if (location.mode !== void 0) return null;
|
|
26692
|
+
if (location.config["readOnly"] === true) return "readonly";
|
|
26693
|
+
if (location.enabled === false) return "readonly";
|
|
26694
|
+
return null;
|
|
26695
|
+
}
|
|
26696
|
+
function resolveLocationMode(location) {
|
|
26697
|
+
return (isStorageLocationMode(location.mode) ? location.mode : void 0) ?? legacyModeOf(location) ?? "active";
|
|
26698
|
+
}
|
|
26699
|
+
function isStorageLocationMode(value) {
|
|
26700
|
+
return STORAGE_LOCATION_MODES.some((mode) => mode === value);
|
|
26701
|
+
}
|
|
26702
|
+
function mayWriteToLocation(location) {
|
|
26703
|
+
return modeMayWrite(resolveLocationMode(location));
|
|
26704
|
+
}
|
|
26705
|
+
function mayReadLocation(location) {
|
|
26706
|
+
return modeMayRead(resolveLocationMode(location));
|
|
26707
|
+
}
|
|
26708
|
+
function evictionPolicyOfLocation(location) {
|
|
26709
|
+
return evictionPolicyForMode(resolveLocationMode(location));
|
|
26710
|
+
}
|
|
26711
|
+
function strippedOfLegacyReadOnly(config) {
|
|
26712
|
+
if (!("readOnly" in config)) return config;
|
|
26713
|
+
const next = { ...config };
|
|
26714
|
+
delete next[LEGACY_READ_ONLY_CONFIG_KEY];
|
|
26715
|
+
return next;
|
|
26716
|
+
}
|
|
26717
|
+
function withLocationMode(location, mode) {
|
|
26718
|
+
return {
|
|
26719
|
+
...location,
|
|
26720
|
+
mode,
|
|
26721
|
+
enabled: modeMayWrite(mode),
|
|
26722
|
+
config: strippedOfLegacyReadOnly(location.config)
|
|
26723
|
+
};
|
|
26724
|
+
}
|
|
26659
26725
|
var StorageLocationTypeSchema = zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*$/);
|
|
26660
26726
|
var StorageLocationSchema = zod.z.object({
|
|
26661
26727
|
id: zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
|
|
@@ -26687,6 +26753,21 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
26687
26753
|
* stops existing rather than being re-derived on every read.
|
|
26688
26754
|
*/
|
|
26689
26755
|
enabled: zod.z.boolean().optional(),
|
|
26756
|
+
/**
|
|
26757
|
+
* THE state of this location (D385), and the only authority on what may be
|
|
26758
|
+
* written, read or evicted here. Interpreted in exactly one place —
|
|
26759
|
+
* `storage-location-mode.ts` — which also folds the legacy
|
|
26760
|
+
* `enabled` / `config.readOnly` pair into a mode so an old row is never
|
|
26761
|
+
* ambiguous.
|
|
26762
|
+
*
|
|
26763
|
+
* OPTIONAL only for the wire and for rows written before D385: absence is
|
|
26764
|
+
* resolved by `resolveLocationMode`, and the orchestrator stamps every
|
|
26765
|
+
* unstamped row ONCE at hydrate so absence stops existing rather than being
|
|
26766
|
+
* re-derived on every read. `enabled` survives one release as a DERIVED
|
|
26767
|
+
* mirror (`mode === 'active'`); `withLocationMode` is the only writer of
|
|
26768
|
+
* either, so the two cannot disagree.
|
|
26769
|
+
*/
|
|
26770
|
+
mode: StorageLocationModeSchema.optional(),
|
|
26690
26771
|
/** COMPUTED at read time by the orchestrator (statfs of the backing volume
|
|
26691
26772
|
* for node-local locations it can reach) — never persisted, absent when the
|
|
26692
26773
|
* volume is remote/unreachable. The single capacity truth every UI reads. */
|
|
@@ -26694,13 +26775,47 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
26694
26775
|
totalBytes: zod.z.number(),
|
|
26695
26776
|
availableBytes: zod.z.number()
|
|
26696
26777
|
}).nullable().optional(),
|
|
26778
|
+
/**
|
|
26779
|
+
* How much of that volume CamStack ITSELF holds on this location (D388) —
|
|
26780
|
+
* COMPUTED at read time from the `storage-occupancy` providers' own figures,
|
|
26781
|
+
* never persisted, never a filesystem walk.
|
|
26782
|
+
*
|
|
26783
|
+
* **ABSENT MEANS UNKNOWN, never zero.** No provider has reported for this
|
|
26784
|
+
* location yet — nobody stores here, the owning addon is down, or the first
|
|
26785
|
+
* refresh has not completed. A UI must omit the segment rather than draw it
|
|
26786
|
+
* at zero, which would claim we occupy nothing (D315). It is an OBJECT and
|
|
26787
|
+
* not a bare number precisely so that a `?? 0` on the consuming side has to
|
|
26788
|
+
* be spelled out loud instead of appearing by accident.
|
|
26789
|
+
*
|
|
26790
|
+
* `measuredAtMs` is the OLDEST contributing measurement, so it is honest
|
|
26791
|
+
* about the whole figure rather than about its freshest part.
|
|
26792
|
+
*/
|
|
26793
|
+
owned: zod.z.object({
|
|
26794
|
+
bytes: zod.z.number().int().nonnegative(),
|
|
26795
|
+
measuredAtMs: zod.z.number().int().nonnegative()
|
|
26796
|
+
}).optional(),
|
|
26697
26797
|
createdAt: zod.z.number(),
|
|
26698
26798
|
updatedAt: zod.z.number()
|
|
26699
26799
|
});
|
|
26700
26800
|
var LegacyStorageLocationDefaultSchema = zod.z.object({ isDefault: zod.z.boolean().optional() });
|
|
26701
26801
|
function isLocationEnabled(location) {
|
|
26702
|
-
return
|
|
26802
|
+
return mayWriteToLocation({
|
|
26803
|
+
...location,
|
|
26804
|
+
config: location.config ?? {}
|
|
26805
|
+
});
|
|
26806
|
+
}
|
|
26807
|
+
var STORAGE_BYTES_PER_GB = 1024 ** 3;
|
|
26808
|
+
function gbToBytes(gb) {
|
|
26809
|
+
return Math.floor(gb * STORAGE_BYTES_PER_GB);
|
|
26703
26810
|
}
|
|
26811
|
+
var StorageDrainProgressSchema = zod.z.object({
|
|
26812
|
+
locationId: zod.z.string(),
|
|
26813
|
+
startedAtMs: zod.z.number(),
|
|
26814
|
+
startBytes: zod.z.number(),
|
|
26815
|
+
bytesRemaining: zod.z.number(),
|
|
26816
|
+
drained: zod.z.boolean(),
|
|
26817
|
+
estimatedEmptyAtMs: zod.z.number().nullable()
|
|
26818
|
+
});
|
|
26704
26819
|
var StorageLocationRefSchema = zod.z.union([StorageLocationTypeSchema, zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/)]);
|
|
26705
26820
|
var StorageAccessSchema = zod.z.enum(["local-path", "cap-mediated"]);
|
|
26706
26821
|
var STORAGE_ACCESS_FALLBACK = "local-path";
|
|
@@ -40702,6 +40817,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
40702
40817
|
kind: "mutation",
|
|
40703
40818
|
auth: "admin"
|
|
40704
40819
|
}),
|
|
40820
|
+
/**
|
|
40821
|
+
* How far each draining location has got (D386). A pure READ of what the
|
|
40822
|
+
* last pressure sweep computed — it starts no work, and a location that is
|
|
40823
|
+
* not draining simply does not appear.
|
|
40824
|
+
*/
|
|
40825
|
+
listDrainProgress: method(zod.z.void(), zod.z.array(StorageDrainProgressSchema).readonly()),
|
|
40705
40826
|
testLocation: method(zod.z.object({ id: zod.z.string() }), zod.z.object({
|
|
40706
40827
|
ok: zod.z.boolean(),
|
|
40707
40828
|
error: zod.z.string().optional()
|
|
@@ -40831,6 +40952,37 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
40831
40952
|
history: method(zod.z.object({}), zod.z.array(StorageMigrationJobSchema).readonly(), { auth: "admin" })
|
|
40832
40953
|
}
|
|
40833
40954
|
};
|
|
40955
|
+
var StorageOccupancyReportSchema = zod.z.object({
|
|
40956
|
+
locationId: zod.z.string(),
|
|
40957
|
+
/** Bytes this provider holds on THAT location — not its eviction domain, and
|
|
40958
|
+
* not net of what it is willing to delete. */
|
|
40959
|
+
ownedBytes: zod.z.number().int().nonnegative(),
|
|
40960
|
+
/** When the provider last actually measured this. The orchestrator carries it
|
|
40961
|
+
* through so a UI can say how old the figure is instead of implying "now". */
|
|
40962
|
+
measuredAtMs: zod.z.number().int().nonnegative()
|
|
40963
|
+
});
|
|
40964
|
+
var storageOccupancyCapability = {
|
|
40965
|
+
name: "storage-occupancy",
|
|
40966
|
+
scope: "system",
|
|
40967
|
+
mode: "collection",
|
|
40968
|
+
internal: true,
|
|
40969
|
+
methods: {
|
|
40970
|
+
/**
|
|
40971
|
+
* Occupancy for the given locations, in ONE round trip.
|
|
40972
|
+
*
|
|
40973
|
+
* A provider answers only for the locations it actually holds bytes on and
|
|
40974
|
+
* OMITS the rest — an omitted location is "I hold nothing measurable here",
|
|
40975
|
+
* which the orchestrator merges as a contribution of nothing rather than as
|
|
40976
|
+
* a claim that the location is empty. Only a location no provider reports
|
|
40977
|
+
* at all stays unknown.
|
|
40978
|
+
*
|
|
40979
|
+
* This must be CHEAP and must never walk a filesystem: it is on the admin
|
|
40980
|
+
* UI's `listLocations` path. The owner keeps its own figure fresh (D224) and
|
|
40981
|
+
* answers from what it already has.
|
|
40982
|
+
*/
|
|
40983
|
+
getOccupancy: method(zod.z.object({ locationIds: zod.z.array(zod.z.string()).readonly() }), zod.z.array(StorageOccupancyReportSchema).readonly(), { auth: "admin" })
|
|
40984
|
+
}
|
|
40985
|
+
};
|
|
40834
40986
|
var ProviderInfoSchema = zod.z.discriminatedUnion("shouldSaveDiskSpace", [zod.z.object({
|
|
40835
40987
|
providerId: zod.z.string().min(1),
|
|
40836
40988
|
displayName: zod.z.string().min(1),
|
|
@@ -42788,83 +42940,6 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
42788
42940
|
* whether persisting is worth a SQLite commit. */
|
|
42789
42941
|
volatileStateFields: ["lastUpdated"]
|
|
42790
42942
|
};
|
|
42791
|
-
var NetworkLinkStatusSchema = zod.z.object({
|
|
42792
|
-
/** The link the device is on. `'unknown'` = not read yet, not "no link". */
|
|
42793
|
-
type: zod.z.enum([
|
|
42794
|
-
"wifi",
|
|
42795
|
-
"ethernet",
|
|
42796
|
-
"cellular",
|
|
42797
|
-
"unknown"
|
|
42798
|
-
]),
|
|
42799
|
-
/**
|
|
42800
|
-
* Link quality, 0..100 inclusive, normalised by the provider from whatever
|
|
42801
|
-
* the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
|
|
42802
|
-
* KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
|
|
42803
|
-
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
42804
|
-
* SKIP a null rather than coerce it.
|
|
42805
|
-
*/
|
|
42806
|
-
signalPercent: zod.z.number().min(0).max(100).nullable(),
|
|
42807
|
-
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
42808
|
-
rssiDbm: zod.z.number().optional(),
|
|
42809
|
-
/** Network name of a wireless link, when the firmware reports it. */
|
|
42810
|
-
ssid: zod.z.string().optional(),
|
|
42811
|
-
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
42812
|
-
lastUpdated: zod.z.number()
|
|
42813
|
-
});
|
|
42814
|
-
var networkLinkCapability = {
|
|
42815
|
-
name: "network-link",
|
|
42816
|
-
scope: "device",
|
|
42817
|
-
deviceNative: true,
|
|
42818
|
-
mode: "singleton",
|
|
42819
|
-
deviceTypes: [
|
|
42820
|
-
DeviceType.Camera,
|
|
42821
|
-
DeviceType.Sensor,
|
|
42822
|
-
DeviceType.Button,
|
|
42823
|
-
DeviceType.Switch,
|
|
42824
|
-
DeviceType.Light,
|
|
42825
|
-
DeviceType.Lock,
|
|
42826
|
-
DeviceType.Siren
|
|
42827
|
-
],
|
|
42828
|
-
methods: {},
|
|
42829
|
-
events: {
|
|
42830
|
-
/**
|
|
42831
|
-
* Emitted whenever the cached status changes (a link switch, a signal
|
|
42832
|
-
* reading that moved). Mirrored on the parent chain by the
|
|
42833
|
-
* DeviceEventPropagator like `battery.onStatusChanged`.
|
|
42834
|
-
*/
|
|
42835
|
-
onStatusChanged: { data: zod.z.object({
|
|
42836
|
-
deviceId: zod.z.number(),
|
|
42837
|
-
status: NetworkLinkStatusSchema
|
|
42838
|
-
}) }
|
|
42839
|
-
},
|
|
42840
|
-
status: {
|
|
42841
|
-
schema: NetworkLinkStatusSchema,
|
|
42842
|
-
kind: "push",
|
|
42843
|
-
empty: {
|
|
42844
|
-
type: "unknown",
|
|
42845
|
-
signalPercent: null,
|
|
42846
|
-
lastUpdated: 0
|
|
42847
|
-
}
|
|
42848
|
-
},
|
|
42849
|
-
/**
|
|
42850
|
-
* Runtime-state slice — every provider stores the same shape under
|
|
42851
|
-
* `device.runtimeState['network-link']`, read once by the badge and the
|
|
42852
|
-
* Home Assistant projector regardless of the driver.
|
|
42853
|
-
*/
|
|
42854
|
-
runtimeState: NetworkLinkStatusSchema,
|
|
42855
|
-
/**
|
|
42856
|
-
* Runtime-state durability: **restored** — a link reading is slow to
|
|
42857
|
-
* change and a sleeping battery camera may not report for hours; the
|
|
42858
|
-
* restored slice is what the badge shows until the next read.
|
|
42859
|
-
*
|
|
42860
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
42861
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
42862
|
-
*/
|
|
42863
|
-
durability: "restored",
|
|
42864
|
-
/** Clock fields: written, but excluded from the compare that decides
|
|
42865
|
-
* whether persisting is worth a SQLite commit. */
|
|
42866
|
-
volatileStateFields: ["lastUpdated"]
|
|
42867
|
-
};
|
|
42868
42943
|
var BinaryStatusSchema = zod.z.object({
|
|
42869
42944
|
on: zod.z.boolean(),
|
|
42870
42945
|
/** Ms epoch of the last transition. 0 if never observed. */
|
|
@@ -46005,6 +46080,246 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
46005
46080
|
* whether persisting is worth a SQLite commit. */
|
|
46006
46081
|
volatileStateFields: ["lastFetchedAt"]
|
|
46007
46082
|
};
|
|
46083
|
+
var NavigationMoveCommandSchema = zod.z.object({
|
|
46084
|
+
pan: zod.z.number().min(-1).max(1).optional(),
|
|
46085
|
+
tilt: zod.z.number().min(-1).max(1).optional(),
|
|
46086
|
+
speed: zod.z.number().min(0).max(1).optional()
|
|
46087
|
+
});
|
|
46088
|
+
var NavigationActionIdSchema = zod.z.enum([
|
|
46089
|
+
"goHome",
|
|
46090
|
+
"locate",
|
|
46091
|
+
"spotClean",
|
|
46092
|
+
"findPet",
|
|
46093
|
+
"personFollow",
|
|
46094
|
+
"stop",
|
|
46095
|
+
"startClean",
|
|
46096
|
+
"pauseClean",
|
|
46097
|
+
"dockWash",
|
|
46098
|
+
"autoEmpty",
|
|
46099
|
+
"flashOn",
|
|
46100
|
+
"flashOff"
|
|
46101
|
+
]);
|
|
46102
|
+
var NavigationEntryKindSchema = zod.z.enum(["action", "sound"]);
|
|
46103
|
+
var NavigationActionEntrySchema = zod.z.object({
|
|
46104
|
+
id: zod.z.string(),
|
|
46105
|
+
kind: NavigationEntryKindSchema,
|
|
46106
|
+
label: zod.z.string(),
|
|
46107
|
+
icon: zod.z.string(),
|
|
46108
|
+
/** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
|
|
46109
|
+
soundId: zod.z.number().int().optional(),
|
|
46110
|
+
/** Per-device feature flag — render this entry only when true. */
|
|
46111
|
+
enabled: zod.z.boolean()
|
|
46112
|
+
});
|
|
46113
|
+
var NavigationPointSchema = zod.z.object({
|
|
46114
|
+
x: zod.z.number(),
|
|
46115
|
+
y: zod.z.number()
|
|
46116
|
+
});
|
|
46117
|
+
var NavigationFeaturesSchema = zod.z.object({
|
|
46118
|
+
move: zod.z.boolean(),
|
|
46119
|
+
stop: zod.z.boolean(),
|
|
46120
|
+
goToPoint: zod.z.boolean(),
|
|
46121
|
+
runAction: zod.z.boolean(),
|
|
46122
|
+
playSound: zod.z.boolean(),
|
|
46123
|
+
light: zod.z.boolean(),
|
|
46124
|
+
lightMode: zod.z.boolean()
|
|
46125
|
+
});
|
|
46126
|
+
var NavigationLightModeSchema = zod.z.enum(["auto", "manual"]);
|
|
46127
|
+
var NavigationModeSchema = zod.z.enum([
|
|
46128
|
+
"idle",
|
|
46129
|
+
"cleaning",
|
|
46130
|
+
"spot",
|
|
46131
|
+
"following",
|
|
46132
|
+
"goto",
|
|
46133
|
+
"returning",
|
|
46134
|
+
"paused",
|
|
46135
|
+
"unknown"
|
|
46136
|
+
]);
|
|
46137
|
+
var NavigationStatusSchema = zod.z.object({
|
|
46138
|
+
mode: NavigationModeSchema,
|
|
46139
|
+
following: zod.z.boolean(),
|
|
46140
|
+
flash: zod.z.boolean(),
|
|
46141
|
+
lightMode: NavigationLightModeSchema,
|
|
46142
|
+
lightLevel: zod.z.number().min(40).max(100),
|
|
46143
|
+
/** Ms epoch when the slice was last updated. */
|
|
46144
|
+
lastChangedAt: zod.z.number()
|
|
46145
|
+
});
|
|
46146
|
+
var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: zod.z.number() });
|
|
46147
|
+
var navigationCapability = {
|
|
46148
|
+
name: "navigation",
|
|
46149
|
+
scope: "device",
|
|
46150
|
+
deviceNative: true,
|
|
46151
|
+
mode: "singleton",
|
|
46152
|
+
deviceTypes: [DeviceType.Camera],
|
|
46153
|
+
deviceConfig: { ui: {
|
|
46154
|
+
kind: "widget",
|
|
46155
|
+
widgetId: "host/navigation-panel",
|
|
46156
|
+
tab: "navigation",
|
|
46157
|
+
topTab: true,
|
|
46158
|
+
label: "Navigation",
|
|
46159
|
+
order: 0
|
|
46160
|
+
} },
|
|
46161
|
+
methods: {
|
|
46162
|
+
/**
|
|
46163
|
+
* Momentary drive nudge (the robot moves). `protected` — mirrors
|
|
46164
|
+
* `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
|
|
46165
|
+
* path) works for any authenticated user, not admin-only. The UI sends
|
|
46166
|
+
* these at ~1 Hz while a control is held; the provider forwards each one to
|
|
46167
|
+
* a single drive write WITHOUT debouncing.
|
|
46168
|
+
*/
|
|
46169
|
+
move: method(NavigationMoveCommandSchema.extend({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
46170
|
+
/** Halt all motion immediately (zero drive vector). */
|
|
46171
|
+
stop: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
46172
|
+
/** Send the robot to a point on its live map. */
|
|
46173
|
+
goToPoint: method(NavigationPointSchema.extend({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
46174
|
+
/**
|
|
46175
|
+
* Enumerate the discrete controls THIS device supports (data-driven UI +
|
|
46176
|
+
* PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
|
|
46177
|
+
*/
|
|
46178
|
+
listActions: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(NavigationActionEntrySchema)),
|
|
46179
|
+
/**
|
|
46180
|
+
* Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
|
|
46181
|
+
* unsupported action ids are rejected by the provider.
|
|
46182
|
+
*/
|
|
46183
|
+
runAction: method(zod.z.object({
|
|
46184
|
+
deviceId: zod.z.number(),
|
|
46185
|
+
actionId: NavigationActionIdSchema
|
|
46186
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
46187
|
+
/** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
|
|
46188
|
+
playSound: method(zod.z.object({
|
|
46189
|
+
deviceId: zod.z.number(),
|
|
46190
|
+
soundId: zod.z.number().int()
|
|
46191
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
46192
|
+
/**
|
|
46193
|
+
* Turn the on-camera fill light on / off (the `OpenFullLight` control —
|
|
46194
|
+
* works anytime, no active stream required).
|
|
46195
|
+
*/
|
|
46196
|
+
setLightOn: method(zod.z.object({
|
|
46197
|
+
deviceId: zod.z.number(),
|
|
46198
|
+
on: zod.z.boolean()
|
|
46199
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
46200
|
+
/**
|
|
46201
|
+
* Set the fill-light mode (auto vs manual). `manual` optionally carries the
|
|
46202
|
+
* initial `level`. The auto/manual + level control is a CAMERA-service
|
|
46203
|
+
* action that generally needs an active camera stream/monitor session — the
|
|
46204
|
+
* UI shows the manual level slider ONLY when `mode === 'manual'`.
|
|
46205
|
+
*/
|
|
46206
|
+
setLightMode: method(zod.z.object({
|
|
46207
|
+
deviceId: zod.z.number(),
|
|
46208
|
+
mode: NavigationLightModeSchema,
|
|
46209
|
+
level: zod.z.number().min(40).max(100).optional()
|
|
46210
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
46211
|
+
/** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
|
|
46212
|
+
setLightLevel: method(zod.z.object({
|
|
46213
|
+
deviceId: zod.z.number(),
|
|
46214
|
+
level: zod.z.number().min(40).max(100)
|
|
46215
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
46216
|
+
/**
|
|
46217
|
+
* Per-device FEATURE-FLAG report for the general primitives — drives which
|
|
46218
|
+
* controls the UI shows (the per-entry flags for the dictionary come back on
|
|
46219
|
+
* `listActions`).
|
|
46220
|
+
*/
|
|
46221
|
+
getFeatures: method(zod.z.object({ deviceId: zod.z.number() }), NavigationFeaturesSchema)
|
|
46222
|
+
},
|
|
46223
|
+
events: { onStatusChanged: { data: zod.z.object({
|
|
46224
|
+
deviceId: zod.z.number(),
|
|
46225
|
+
status: NavigationStatusSchema
|
|
46226
|
+
}) } },
|
|
46227
|
+
status: {
|
|
46228
|
+
schema: NavigationStatusSchema,
|
|
46229
|
+
kind: "push"
|
|
46230
|
+
},
|
|
46231
|
+
/**
|
|
46232
|
+
* Runtime-state slice mirrored by the kernel. The navigation panel watches it
|
|
46233
|
+
* for live mode / follow / flash changes.
|
|
46234
|
+
*/
|
|
46235
|
+
runtimeState: NavigationRuntimeStateSchema,
|
|
46236
|
+
/**
|
|
46237
|
+
* Runtime-state durability: **session** — like `vacuum-control`, a restored
|
|
46238
|
+
* `mode: cleaning` / `following: true` is a robot that is not actually doing
|
|
46239
|
+
* that. The live handle re-publishes on connect.
|
|
46240
|
+
*
|
|
46241
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
46242
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
46243
|
+
*/
|
|
46244
|
+
durability: "session"
|
|
46245
|
+
};
|
|
46246
|
+
var NetworkLinkStatusSchema = zod.z.object({
|
|
46247
|
+
/** The link the device is on. `'unknown'` = not read yet, not "no link". */
|
|
46248
|
+
type: zod.z.enum([
|
|
46249
|
+
"wifi",
|
|
46250
|
+
"ethernet",
|
|
46251
|
+
"cellular",
|
|
46252
|
+
"unknown"
|
|
46253
|
+
]),
|
|
46254
|
+
/**
|
|
46255
|
+
* Link quality, 0..100 inclusive, normalised by the provider from whatever
|
|
46256
|
+
* the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
|
|
46257
|
+
* KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
|
|
46258
|
+
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
46259
|
+
* SKIP a null rather than coerce it.
|
|
46260
|
+
*/
|
|
46261
|
+
signalPercent: zod.z.number().min(0).max(100).nullable(),
|
|
46262
|
+
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
46263
|
+
rssiDbm: zod.z.number().optional(),
|
|
46264
|
+
/** Network name of a wireless link, when the firmware reports it. */
|
|
46265
|
+
ssid: zod.z.string().optional(),
|
|
46266
|
+
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
46267
|
+
lastUpdated: zod.z.number()
|
|
46268
|
+
});
|
|
46269
|
+
var networkLinkCapability = {
|
|
46270
|
+
name: "network-link",
|
|
46271
|
+
scope: "device",
|
|
46272
|
+
deviceNative: true,
|
|
46273
|
+
mode: "singleton",
|
|
46274
|
+
deviceTypes: [
|
|
46275
|
+
DeviceType.Camera,
|
|
46276
|
+
DeviceType.Sensor,
|
|
46277
|
+
DeviceType.Button,
|
|
46278
|
+
DeviceType.Switch,
|
|
46279
|
+
DeviceType.Light,
|
|
46280
|
+
DeviceType.Lock,
|
|
46281
|
+
DeviceType.Siren
|
|
46282
|
+
],
|
|
46283
|
+
methods: {},
|
|
46284
|
+
events: {
|
|
46285
|
+
/**
|
|
46286
|
+
* Emitted whenever the cached status changes (a link switch, a signal
|
|
46287
|
+
* reading that moved). Mirrored on the parent chain by the
|
|
46288
|
+
* DeviceEventPropagator like `battery.onStatusChanged`.
|
|
46289
|
+
*/
|
|
46290
|
+
onStatusChanged: { data: zod.z.object({
|
|
46291
|
+
deviceId: zod.z.number(),
|
|
46292
|
+
status: NetworkLinkStatusSchema
|
|
46293
|
+
}) }
|
|
46294
|
+
},
|
|
46295
|
+
status: {
|
|
46296
|
+
schema: NetworkLinkStatusSchema,
|
|
46297
|
+
kind: "push",
|
|
46298
|
+
empty: {
|
|
46299
|
+
type: "unknown",
|
|
46300
|
+
signalPercent: null,
|
|
46301
|
+
lastUpdated: 0
|
|
46302
|
+
}
|
|
46303
|
+
},
|
|
46304
|
+
/**
|
|
46305
|
+
* Runtime-state slice — every provider stores the same shape under
|
|
46306
|
+
* `device.runtimeState['network-link']`, read once by the badge and the
|
|
46307
|
+
* Home Assistant projector regardless of the driver.
|
|
46308
|
+
*/
|
|
46309
|
+
runtimeState: NetworkLinkStatusSchema,
|
|
46310
|
+
/**
|
|
46311
|
+
* Runtime-state durability: **restored** — a link reading is slow to
|
|
46312
|
+
* change and a sleeping battery camera may not report for hours; the
|
|
46313
|
+
* restored slice is what the badge shows until the next read.
|
|
46314
|
+
*
|
|
46315
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
46316
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
46317
|
+
*/
|
|
46318
|
+
durability: "restored",
|
|
46319
|
+
/** Clock fields: written, but excluded from the compare that decides
|
|
46320
|
+
* whether persisting is worth a SQLite commit. */
|
|
46321
|
+
volatileStateFields: ["lastUpdated"]
|
|
46322
|
+
};
|
|
46008
46323
|
var StreamNetworkStatsSchema = zod.z.object({
|
|
46009
46324
|
nominalBitrateKbps: zod.z.number(),
|
|
46010
46325
|
observedBitrateKbps: zod.z.number(),
|
|
@@ -47570,169 +47885,6 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
47570
47885
|
*/
|
|
47571
47886
|
durability: "session"
|
|
47572
47887
|
};
|
|
47573
|
-
var NavigationMoveCommandSchema = zod.z.object({
|
|
47574
|
-
pan: zod.z.number().min(-1).max(1).optional(),
|
|
47575
|
-
tilt: zod.z.number().min(-1).max(1).optional(),
|
|
47576
|
-
speed: zod.z.number().min(0).max(1).optional()
|
|
47577
|
-
});
|
|
47578
|
-
var NavigationActionIdSchema = zod.z.enum([
|
|
47579
|
-
"goHome",
|
|
47580
|
-
"locate",
|
|
47581
|
-
"spotClean",
|
|
47582
|
-
"findPet",
|
|
47583
|
-
"personFollow",
|
|
47584
|
-
"stop",
|
|
47585
|
-
"startClean",
|
|
47586
|
-
"pauseClean",
|
|
47587
|
-
"dockWash",
|
|
47588
|
-
"autoEmpty",
|
|
47589
|
-
"flashOn",
|
|
47590
|
-
"flashOff"
|
|
47591
|
-
]);
|
|
47592
|
-
var NavigationEntryKindSchema = zod.z.enum(["action", "sound"]);
|
|
47593
|
-
var NavigationActionEntrySchema = zod.z.object({
|
|
47594
|
-
id: zod.z.string(),
|
|
47595
|
-
kind: NavigationEntryKindSchema,
|
|
47596
|
-
label: zod.z.string(),
|
|
47597
|
-
icon: zod.z.string(),
|
|
47598
|
-
/** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
|
|
47599
|
-
soundId: zod.z.number().int().optional(),
|
|
47600
|
-
/** Per-device feature flag — render this entry only when true. */
|
|
47601
|
-
enabled: zod.z.boolean()
|
|
47602
|
-
});
|
|
47603
|
-
var NavigationPointSchema = zod.z.object({
|
|
47604
|
-
x: zod.z.number(),
|
|
47605
|
-
y: zod.z.number()
|
|
47606
|
-
});
|
|
47607
|
-
var NavigationFeaturesSchema = zod.z.object({
|
|
47608
|
-
move: zod.z.boolean(),
|
|
47609
|
-
stop: zod.z.boolean(),
|
|
47610
|
-
goToPoint: zod.z.boolean(),
|
|
47611
|
-
runAction: zod.z.boolean(),
|
|
47612
|
-
playSound: zod.z.boolean(),
|
|
47613
|
-
light: zod.z.boolean(),
|
|
47614
|
-
lightMode: zod.z.boolean()
|
|
47615
|
-
});
|
|
47616
|
-
var NavigationLightModeSchema = zod.z.enum(["auto", "manual"]);
|
|
47617
|
-
var NavigationModeSchema = zod.z.enum([
|
|
47618
|
-
"idle",
|
|
47619
|
-
"cleaning",
|
|
47620
|
-
"spot",
|
|
47621
|
-
"following",
|
|
47622
|
-
"goto",
|
|
47623
|
-
"returning",
|
|
47624
|
-
"paused",
|
|
47625
|
-
"unknown"
|
|
47626
|
-
]);
|
|
47627
|
-
var NavigationStatusSchema = zod.z.object({
|
|
47628
|
-
mode: NavigationModeSchema,
|
|
47629
|
-
following: zod.z.boolean(),
|
|
47630
|
-
flash: zod.z.boolean(),
|
|
47631
|
-
lightMode: NavigationLightModeSchema,
|
|
47632
|
-
lightLevel: zod.z.number().min(40).max(100),
|
|
47633
|
-
/** Ms epoch when the slice was last updated. */
|
|
47634
|
-
lastChangedAt: zod.z.number()
|
|
47635
|
-
});
|
|
47636
|
-
var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: zod.z.number() });
|
|
47637
|
-
var navigationCapability = {
|
|
47638
|
-
name: "navigation",
|
|
47639
|
-
scope: "device",
|
|
47640
|
-
deviceNative: true,
|
|
47641
|
-
mode: "singleton",
|
|
47642
|
-
deviceTypes: [DeviceType.Camera],
|
|
47643
|
-
deviceConfig: { ui: {
|
|
47644
|
-
kind: "widget",
|
|
47645
|
-
widgetId: "host/navigation-panel",
|
|
47646
|
-
tab: "navigation",
|
|
47647
|
-
topTab: true,
|
|
47648
|
-
label: "Navigation",
|
|
47649
|
-
order: 0
|
|
47650
|
-
} },
|
|
47651
|
-
methods: {
|
|
47652
|
-
/**
|
|
47653
|
-
* Momentary drive nudge (the robot moves). `protected` — mirrors
|
|
47654
|
-
* `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
|
|
47655
|
-
* path) works for any authenticated user, not admin-only. The UI sends
|
|
47656
|
-
* these at ~1 Hz while a control is held; the provider forwards each one to
|
|
47657
|
-
* a single drive write WITHOUT debouncing.
|
|
47658
|
-
*/
|
|
47659
|
-
move: method(NavigationMoveCommandSchema.extend({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
47660
|
-
/** Halt all motion immediately (zero drive vector). */
|
|
47661
|
-
stop: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
47662
|
-
/** Send the robot to a point on its live map. */
|
|
47663
|
-
goToPoint: method(NavigationPointSchema.extend({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
47664
|
-
/**
|
|
47665
|
-
* Enumerate the discrete controls THIS device supports (data-driven UI +
|
|
47666
|
-
* PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
|
|
47667
|
-
*/
|
|
47668
|
-
listActions: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(NavigationActionEntrySchema)),
|
|
47669
|
-
/**
|
|
47670
|
-
* Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
|
|
47671
|
-
* unsupported action ids are rejected by the provider.
|
|
47672
|
-
*/
|
|
47673
|
-
runAction: method(zod.z.object({
|
|
47674
|
-
deviceId: zod.z.number(),
|
|
47675
|
-
actionId: NavigationActionIdSchema
|
|
47676
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
47677
|
-
/** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
|
|
47678
|
-
playSound: method(zod.z.object({
|
|
47679
|
-
deviceId: zod.z.number(),
|
|
47680
|
-
soundId: zod.z.number().int()
|
|
47681
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
47682
|
-
/**
|
|
47683
|
-
* Turn the on-camera fill light on / off (the `OpenFullLight` control —
|
|
47684
|
-
* works anytime, no active stream required).
|
|
47685
|
-
*/
|
|
47686
|
-
setLightOn: method(zod.z.object({
|
|
47687
|
-
deviceId: zod.z.number(),
|
|
47688
|
-
on: zod.z.boolean()
|
|
47689
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
47690
|
-
/**
|
|
47691
|
-
* Set the fill-light mode (auto vs manual). `manual` optionally carries the
|
|
47692
|
-
* initial `level`. The auto/manual + level control is a CAMERA-service
|
|
47693
|
-
* action that generally needs an active camera stream/monitor session — the
|
|
47694
|
-
* UI shows the manual level slider ONLY when `mode === 'manual'`.
|
|
47695
|
-
*/
|
|
47696
|
-
setLightMode: method(zod.z.object({
|
|
47697
|
-
deviceId: zod.z.number(),
|
|
47698
|
-
mode: NavigationLightModeSchema,
|
|
47699
|
-
level: zod.z.number().min(40).max(100).optional()
|
|
47700
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
47701
|
-
/** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
|
|
47702
|
-
setLightLevel: method(zod.z.object({
|
|
47703
|
-
deviceId: zod.z.number(),
|
|
47704
|
-
level: zod.z.number().min(40).max(100)
|
|
47705
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
47706
|
-
/**
|
|
47707
|
-
* Per-device FEATURE-FLAG report for the general primitives — drives which
|
|
47708
|
-
* controls the UI shows (the per-entry flags for the dictionary come back on
|
|
47709
|
-
* `listActions`).
|
|
47710
|
-
*/
|
|
47711
|
-
getFeatures: method(zod.z.object({ deviceId: zod.z.number() }), NavigationFeaturesSchema)
|
|
47712
|
-
},
|
|
47713
|
-
events: { onStatusChanged: { data: zod.z.object({
|
|
47714
|
-
deviceId: zod.z.number(),
|
|
47715
|
-
status: NavigationStatusSchema
|
|
47716
|
-
}) } },
|
|
47717
|
-
status: {
|
|
47718
|
-
schema: NavigationStatusSchema,
|
|
47719
|
-
kind: "push"
|
|
47720
|
-
},
|
|
47721
|
-
/**
|
|
47722
|
-
* Runtime-state slice mirrored by the kernel. The navigation panel watches it
|
|
47723
|
-
* for live mode / follow / flash changes.
|
|
47724
|
-
*/
|
|
47725
|
-
runtimeState: NavigationRuntimeStateSchema,
|
|
47726
|
-
/**
|
|
47727
|
-
* Runtime-state durability: **session** — like `vacuum-control`, a restored
|
|
47728
|
-
* `mode: cleaning` / `following: true` is a robot that is not actually doing
|
|
47729
|
-
* that. The live handle re-publishes on connect.
|
|
47730
|
-
*
|
|
47731
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
47732
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
47733
|
-
*/
|
|
47734
|
-
durability: "session"
|
|
47735
|
-
};
|
|
47736
47888
|
var rebootCapability = {
|
|
47737
47889
|
name: "reboot",
|
|
47738
47890
|
scope: "device",
|
|
@@ -51257,6 +51409,7 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
51257
51409
|
storageCapability,
|
|
51258
51410
|
storageEvictableCapability,
|
|
51259
51411
|
storageMigrationCapability,
|
|
51412
|
+
storageOccupancyCapability,
|
|
51260
51413
|
storageProviderCapability,
|
|
51261
51414
|
streamBrokerCapability,
|
|
51262
51415
|
streamCatalogCapability,
|
|
@@ -56229,6 +56382,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
56229
56382
|
addonId: null,
|
|
56230
56383
|
access: "view"
|
|
56231
56384
|
},
|
|
56385
|
+
"storage.listDrainProgress": {
|
|
56386
|
+
capName: "storage",
|
|
56387
|
+
capScope: "system",
|
|
56388
|
+
addonId: null,
|
|
56389
|
+
access: "view"
|
|
56390
|
+
},
|
|
56232
56391
|
"storage.listLocationDeclarations": {
|
|
56233
56392
|
capName: "storage",
|
|
56234
56393
|
capScope: "system",
|
|
@@ -56373,6 +56532,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
56373
56532
|
addonId: null,
|
|
56374
56533
|
access: "view"
|
|
56375
56534
|
},
|
|
56535
|
+
"storageOccupancy.getOccupancy": {
|
|
56536
|
+
capName: "storage-occupancy",
|
|
56537
|
+
capScope: "system",
|
|
56538
|
+
addonId: null,
|
|
56539
|
+
access: "view"
|
|
56540
|
+
},
|
|
56376
56541
|
"storageProvider.abortUpload": {
|
|
56377
56542
|
capName: "storage-provider",
|
|
56378
56543
|
capScope: "system",
|
|
@@ -60186,6 +60351,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
60186
60351
|
return StorageCleanupJobSchema;
|
|
60187
60352
|
}
|
|
60188
60353
|
});
|
|
60354
|
+
Object.defineProperty(exports, "StorageLocationModeSchema", {
|
|
60355
|
+
enumerable: true,
|
|
60356
|
+
get: function() {
|
|
60357
|
+
return StorageLocationModeSchema;
|
|
60358
|
+
}
|
|
60359
|
+
});
|
|
60189
60360
|
Object.defineProperty(exports, "StorageLocationTypeSchema", {
|
|
60190
60361
|
enumerable: true,
|
|
60191
60362
|
get: function() {
|
|
@@ -60396,6 +60567,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
60396
60567
|
return evaluateSensorEdge;
|
|
60397
60568
|
}
|
|
60398
60569
|
});
|
|
60570
|
+
Object.defineProperty(exports, "evictionPolicyOfLocation", {
|
|
60571
|
+
enumerable: true,
|
|
60572
|
+
get: function() {
|
|
60573
|
+
return evictionPolicyOfLocation;
|
|
60574
|
+
}
|
|
60575
|
+
});
|
|
60399
60576
|
Object.defineProperty(exports, "expandCapMethods", {
|
|
60400
60577
|
enumerable: true,
|
|
60401
60578
|
get: function() {
|
|
@@ -60420,6 +60597,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
60420
60597
|
return foldSnapshotByFunction;
|
|
60421
60598
|
}
|
|
60422
60599
|
});
|
|
60600
|
+
Object.defineProperty(exports, "gbToBytes", {
|
|
60601
|
+
enumerable: true,
|
|
60602
|
+
get: function() {
|
|
60603
|
+
return gbToBytes;
|
|
60604
|
+
}
|
|
60605
|
+
});
|
|
60423
60606
|
Object.defineProperty(exports, "hydrateSchema", {
|
|
60424
60607
|
enumerable: true,
|
|
60425
60608
|
get: function() {
|
|
@@ -60480,6 +60663,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
60480
60663
|
return kebabToCamel;
|
|
60481
60664
|
}
|
|
60482
60665
|
});
|
|
60666
|
+
Object.defineProperty(exports, "legacyModeOf", {
|
|
60667
|
+
enumerable: true,
|
|
60668
|
+
get: function() {
|
|
60669
|
+
return legacyModeOf;
|
|
60670
|
+
}
|
|
60671
|
+
});
|
|
60483
60672
|
Object.defineProperty(exports, "lifecycleJobSchema", {
|
|
60484
60673
|
enumerable: true,
|
|
60485
60674
|
get: function() {
|
|
@@ -60510,12 +60699,30 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
60510
60699
|
return looseSchema;
|
|
60511
60700
|
}
|
|
60512
60701
|
});
|
|
60702
|
+
Object.defineProperty(exports, "mayReadLocation", {
|
|
60703
|
+
enumerable: true,
|
|
60704
|
+
get: function() {
|
|
60705
|
+
return mayReadLocation;
|
|
60706
|
+
}
|
|
60707
|
+
});
|
|
60708
|
+
Object.defineProperty(exports, "mayWriteToLocation", {
|
|
60709
|
+
enumerable: true,
|
|
60710
|
+
get: function() {
|
|
60711
|
+
return mayWriteToLocation;
|
|
60712
|
+
}
|
|
60713
|
+
});
|
|
60513
60714
|
Object.defineProperty(exports, "metricsProviderCapability", {
|
|
60514
60715
|
enumerable: true,
|
|
60515
60716
|
get: function() {
|
|
60516
60717
|
return metricsProviderCapability;
|
|
60517
60718
|
}
|
|
60518
60719
|
});
|
|
60720
|
+
Object.defineProperty(exports, "modeMayWrite", {
|
|
60721
|
+
enumerable: true,
|
|
60722
|
+
get: function() {
|
|
60723
|
+
return modeMayWrite;
|
|
60724
|
+
}
|
|
60725
|
+
});
|
|
60519
60726
|
Object.defineProperty(exports, "nodePin", {
|
|
60520
60727
|
enumerable: true,
|
|
60521
60728
|
get: function() {
|
|
@@ -60594,6 +60801,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
60594
60801
|
return resolveContainerPrimaryChild;
|
|
60595
60802
|
}
|
|
60596
60803
|
});
|
|
60804
|
+
Object.defineProperty(exports, "resolveLocationMode", {
|
|
60805
|
+
enumerable: true,
|
|
60806
|
+
get: function() {
|
|
60807
|
+
return resolveLocationMode;
|
|
60808
|
+
}
|
|
60809
|
+
});
|
|
60597
60810
|
Object.defineProperty(exports, "resolveMethodAuth", {
|
|
60598
60811
|
enumerable: true,
|
|
60599
60812
|
get: function() {
|
|
@@ -60660,6 +60873,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
60660
60873
|
return storageMigrationCapability;
|
|
60661
60874
|
}
|
|
60662
60875
|
});
|
|
60876
|
+
Object.defineProperty(exports, "storageOccupancyCapability", {
|
|
60877
|
+
enumerable: true,
|
|
60878
|
+
get: function() {
|
|
60879
|
+
return storageOccupancyCapability;
|
|
60880
|
+
}
|
|
60881
|
+
});
|
|
60663
60882
|
Object.defineProperty(exports, "storageProviderCapability", {
|
|
60664
60883
|
enumerable: true,
|
|
60665
60884
|
get: function() {
|
|
@@ -60690,6 +60909,12 @@ var require_dist_jZb_HKI5 = __commonJS({
|
|
|
60690
60909
|
return vectorStoreCapability;
|
|
60691
60910
|
}
|
|
60692
60911
|
});
|
|
60912
|
+
Object.defineProperty(exports, "withLocationMode", {
|
|
60913
|
+
enumerable: true,
|
|
60914
|
+
get: function() {
|
|
60915
|
+
return withLocationMode;
|
|
60916
|
+
}
|
|
60917
|
+
});
|
|
60693
60918
|
}
|
|
60694
60919
|
});
|
|
60695
60920
|
|
|
@@ -60702,7 +60927,7 @@ var require_alerts_addon = __commonJS({
|
|
|
60702
60927
|
[Symbol.toStringTag]: { value: "Module" }
|
|
60703
60928
|
});
|
|
60704
60929
|
require_chunk_Cek0wNdY();
|
|
60705
|
-
var require_dist10 =
|
|
60930
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
60706
60931
|
function selectExpired(alerts, cutoffMs) {
|
|
60707
60932
|
return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
|
|
60708
60933
|
}
|
|
@@ -61521,7 +61746,7 @@ var require_console_logging = __commonJS({
|
|
|
61521
61746
|
[Symbol.toStringTag]: { value: "Module" }
|
|
61522
61747
|
});
|
|
61523
61748
|
require_chunk_Cek0wNdY();
|
|
61524
|
-
var require_dist10 =
|
|
61749
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
61525
61750
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
61526
61751
|
var LEVEL_RANK = {
|
|
61527
61752
|
debug: 0,
|
|
@@ -61615,7 +61840,7 @@ var require_core_blocks_addon = __commonJS({
|
|
|
61615
61840
|
"use strict";
|
|
61616
61841
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
61617
61842
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
61618
|
-
var require_dist10 =
|
|
61843
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
61619
61844
|
var node_crypto = __require("crypto");
|
|
61620
61845
|
var node_fs_promises = __require("fs/promises");
|
|
61621
61846
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -62512,11 +62737,11 @@ var require_core_blocks = __commonJS({
|
|
|
62512
62737
|
}
|
|
62513
62738
|
});
|
|
62514
62739
|
|
|
62515
|
-
// ../system/dist/retired-settings-keys-
|
|
62516
|
-
var
|
|
62517
|
-
"../system/dist/retired-settings-keys-
|
|
62740
|
+
// ../system/dist/retired-settings-keys-Dd7K3K3r.js
|
|
62741
|
+
var require_retired_settings_keys_Dd7K3K3r = __commonJS({
|
|
62742
|
+
"../system/dist/retired-settings-keys-Dd7K3K3r.js"(exports) {
|
|
62518
62743
|
"use strict";
|
|
62519
|
-
var require_dist10 =
|
|
62744
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
62520
62745
|
function settingsStoreIsAuthoritativeHere(env) {
|
|
62521
62746
|
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
62522
62747
|
return raw === "" || raw === "hub";
|
|
@@ -63769,281 +63994,420 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
63769
63994
|
hash
|
|
63770
63995
|
} });
|
|
63771
63996
|
}
|
|
63772
|
-
var
|
|
63773
|
-
var
|
|
63774
|
-
var
|
|
63775
|
-
|
|
63776
|
-
|
|
63777
|
-
|
|
63778
|
-
|
|
63779
|
-
|
|
63780
|
-
|
|
63781
|
-
|
|
63782
|
-
|
|
63783
|
-
|
|
63784
|
-
|
|
63785
|
-
if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;
|
|
63786
|
-
return (utime + stime) / CLOCK_TICKS_PER_SEC;
|
|
63787
|
-
}
|
|
63788
|
-
function parseProcRssBytes(line) {
|
|
63789
|
-
const fields = line.trim().split(/\s+/);
|
|
63790
|
-
const residentPages = Number(fields[1]);
|
|
63791
|
-
if (!Number.isFinite(residentPages)) return null;
|
|
63792
|
-
return residentPages * PAGE_BYTES;
|
|
63793
|
-
}
|
|
63794
|
-
async function readProcessCost(pid, reader = nodeProcStatReader) {
|
|
63795
|
-
let cpuSeconds = null;
|
|
63796
|
-
let rssBytes = null;
|
|
63797
|
-
try {
|
|
63798
|
-
cpuSeconds = parseProcCpuSeconds(await reader.readStat(pid));
|
|
63799
|
-
} catch {
|
|
63800
|
-
cpuSeconds = null;
|
|
63997
|
+
var DEFAULT_FIRST_UNIT_TIMEOUT_MS = 12e3;
|
|
63998
|
+
var FRAGMENT_LOG_EVERY = 30;
|
|
63999
|
+
var Fmp4FragmentChild = class {
|
|
64000
|
+
deps;
|
|
64001
|
+
args;
|
|
64002
|
+
child = null;
|
|
64003
|
+
splitter = new require_canonical_hash.Fmp4BoxSplitter();
|
|
64004
|
+
stopped = false;
|
|
64005
|
+
unitsOut = 0;
|
|
64006
|
+
activeHwAccel = null;
|
|
64007
|
+
constructor(deps, args) {
|
|
64008
|
+
this.deps = deps;
|
|
64009
|
+
this.args = args;
|
|
63801
64010
|
}
|
|
63802
|
-
|
|
63803
|
-
|
|
63804
|
-
|
|
63805
|
-
|
|
64011
|
+
/** Spawn, and resolve once the INIT segment has been cut out of stdout. */
|
|
64012
|
+
async start() {
|
|
64013
|
+
const requested = this.args.invocation.decodeHwAccel;
|
|
64014
|
+
this.activeHwAccel = requested;
|
|
64015
|
+
try {
|
|
64016
|
+
await this.spawnAttempt(requested);
|
|
64017
|
+
return;
|
|
64018
|
+
} catch (err) {
|
|
64019
|
+
if (this.stopped) throw err;
|
|
64020
|
+
if (requested === null || require_canonical_hash.isSoftwareDecode(requested)) throw err;
|
|
64021
|
+
this.deps.logger.warn("fmp4 fragment child: hardware decode produced NO fragment \u2014 retrying in SOFTWARE", {
|
|
64022
|
+
tags: { deviceId: this.args.deviceId },
|
|
64023
|
+
meta: {
|
|
64024
|
+
sourceId: this.args.sourceId,
|
|
64025
|
+
decodeHwAccel: requested,
|
|
64026
|
+
error: require_err_msg.errMsg(err)
|
|
64027
|
+
}
|
|
64028
|
+
});
|
|
64029
|
+
this.killChild();
|
|
64030
|
+
this.splitter = new require_canonical_hash.Fmp4BoxSplitter();
|
|
64031
|
+
this.activeHwAccel = null;
|
|
64032
|
+
await this.spawnAttempt(null);
|
|
64033
|
+
}
|
|
63806
64034
|
}
|
|
63807
|
-
|
|
63808
|
-
|
|
63809
|
-
|
|
63810
|
-
|
|
63811
|
-
}
|
|
63812
|
-
var ChildCostRegistry = class {
|
|
63813
|
-
reader;
|
|
63814
|
-
now;
|
|
63815
|
-
claims = /* @__PURE__ */ new Map();
|
|
63816
|
-
constructor(reader = nodeProcStatReader, now = Date.now) {
|
|
63817
|
-
this.reader = reader;
|
|
63818
|
-
this.now = now;
|
|
64035
|
+
/** The backend the child ACTUALLY ran with — `null` for software. */
|
|
64036
|
+
activeDecodeHwAccel() {
|
|
64037
|
+
const value = this.activeHwAccel;
|
|
64038
|
+
return value === null || value === "none" || value === "copy" ? null : value;
|
|
63819
64039
|
}
|
|
63820
|
-
/**
|
|
63821
|
-
|
|
63822
|
-
|
|
63823
|
-
|
|
63824
|
-
|
|
63825
|
-
|
|
63826
|
-
*/
|
|
63827
|
-
claim(input) {
|
|
63828
|
-
const pid = input.pid;
|
|
63829
|
-
if (pid === void 0 || !Number.isInteger(pid) || pid <= 0) return NO_COST_CLAIM;
|
|
63830
|
-
const key = /* @__PURE__ */ Symbol("child-cost-claim");
|
|
63831
|
-
this.claims.set(key, {
|
|
63832
|
-
role: input.role,
|
|
63833
|
-
deviceId: input.deviceId,
|
|
63834
|
-
unit: input.unit,
|
|
63835
|
-
attribution: input.attribution ?? "measured",
|
|
63836
|
-
pid,
|
|
63837
|
-
startedAtMs: this.now()
|
|
63838
|
-
});
|
|
63839
|
-
return { release: () => {
|
|
63840
|
-
this.claims.delete(key);
|
|
63841
|
-
} };
|
|
64040
|
+
/** Kill ffmpeg and end the plane. Idempotent. */
|
|
64041
|
+
async stop() {
|
|
64042
|
+
if (this.stopped) return;
|
|
64043
|
+
this.stopped = true;
|
|
64044
|
+
this.killChild();
|
|
64045
|
+
this.args.plane.end("the fragment child stopped");
|
|
63842
64046
|
}
|
|
63843
|
-
|
|
63844
|
-
|
|
63845
|
-
|
|
63846
|
-
|
|
63847
|
-
|
|
63848
|
-
|
|
63849
|
-
|
|
63850
|
-
|
|
63851
|
-
|
|
64047
|
+
spawnAttempt(decodeHwAccel) {
|
|
64048
|
+
const args = require_canonical_hash.buildFfmpegArgs({
|
|
64049
|
+
...this.args.invocation,
|
|
64050
|
+
decodeHwAccel,
|
|
64051
|
+
sink: {
|
|
64052
|
+
kind: "stdout",
|
|
64053
|
+
container: "mp4",
|
|
64054
|
+
fragmentMs: this.args.fragmentMs
|
|
64055
|
+
}
|
|
64056
|
+
});
|
|
64057
|
+
this.deps.logger.info("fmp4 fragment child: spawning ffmpeg", {
|
|
64058
|
+
tags: { deviceId: this.args.deviceId },
|
|
64059
|
+
meta: {
|
|
64060
|
+
sourceId: this.args.sourceId,
|
|
64061
|
+
fragmentMs: this.args.fragmentMs,
|
|
64062
|
+
decodeHwAccel: decodeHwAccel ?? "software",
|
|
64063
|
+
argv: args.join(" ")
|
|
64064
|
+
}
|
|
64065
|
+
});
|
|
64066
|
+
return new Promise((resolve, reject) => {
|
|
64067
|
+
const child = this.deps.spawnFn(this.deps.ffmpegBinaryPath, args, { stdio: [
|
|
64068
|
+
"ignore",
|
|
64069
|
+
"pipe",
|
|
64070
|
+
"pipe"
|
|
64071
|
+
] });
|
|
64072
|
+
this.child = child;
|
|
64073
|
+
let settled = false;
|
|
64074
|
+
let failed = false;
|
|
64075
|
+
const isCurrent = () => !this.stopped && !failed;
|
|
64076
|
+
const timeoutMs = this.deps.firstUnitTimeoutMs ?? DEFAULT_FIRST_UNIT_TIMEOUT_MS;
|
|
64077
|
+
const settle = (fail) => {
|
|
64078
|
+
if (settled) return;
|
|
64079
|
+
settled = true;
|
|
64080
|
+
clearTimeout(timer);
|
|
64081
|
+
if (fail) {
|
|
64082
|
+
failed = true;
|
|
64083
|
+
reject(fail);
|
|
64084
|
+
} else resolve();
|
|
64085
|
+
};
|
|
64086
|
+
const timer = setTimeout(() => {
|
|
64087
|
+
settle(/* @__PURE__ */ new Error(`fmp4 fragment child: no fragment within ${timeoutMs}ms`));
|
|
64088
|
+
this.killChild();
|
|
64089
|
+
}, timeoutMs);
|
|
64090
|
+
timer.unref?.();
|
|
64091
|
+
child.stdout?.on("data", (chunk) => {
|
|
64092
|
+
for (const unit of this.splitter.push(chunk)) {
|
|
64093
|
+
this.unitsOut += 1;
|
|
64094
|
+
this.args.plane.publish(unit);
|
|
64095
|
+
if (unit.kind === "init") {
|
|
64096
|
+
this.deps.logger.info("fmp4 fragment child: INIT segment cut", {
|
|
64097
|
+
tags: { deviceId: this.args.deviceId },
|
|
64098
|
+
meta: {
|
|
64099
|
+
sourceId: this.args.sourceId,
|
|
64100
|
+
bytes: unit.data.length
|
|
64101
|
+
}
|
|
64102
|
+
});
|
|
64103
|
+
settle();
|
|
64104
|
+
} else if (this.unitsOut % FRAGMENT_LOG_EVERY === 0) this.deps.logger.info("fmp4 fragment child: fragments still flowing", {
|
|
64105
|
+
tags: { deviceId: this.args.deviceId },
|
|
64106
|
+
meta: {
|
|
64107
|
+
sourceId: this.args.sourceId,
|
|
64108
|
+
unitsOut: this.unitsOut,
|
|
64109
|
+
bytes: unit.data.length,
|
|
64110
|
+
subscribers: this.args.plane.subscriberCount
|
|
64111
|
+
}
|
|
64112
|
+
});
|
|
64113
|
+
}
|
|
64114
|
+
const fault = this.splitter.fault;
|
|
64115
|
+
if (fault !== null) this.onFault(fault, settled, isCurrent(), settle);
|
|
64116
|
+
});
|
|
64117
|
+
child.stderr?.setEncoding("utf8");
|
|
64118
|
+
child.stderr?.on("data", (line) => {
|
|
64119
|
+
this.deps.logger.debug("fmp4 fragment child ffmpeg", {
|
|
64120
|
+
tags: { deviceId: this.args.deviceId },
|
|
64121
|
+
meta: {
|
|
64122
|
+
sourceId: this.args.sourceId,
|
|
64123
|
+
line: line.trim()
|
|
64124
|
+
}
|
|
64125
|
+
});
|
|
64126
|
+
});
|
|
64127
|
+
child.once("error", (err) => {
|
|
64128
|
+
if (!settled) {
|
|
64129
|
+
settle(err);
|
|
64130
|
+
return;
|
|
64131
|
+
}
|
|
64132
|
+
if (!isCurrent()) return;
|
|
64133
|
+
this.args.plane.end("the fragment child errored");
|
|
64134
|
+
this.deps.onChildExit?.(err);
|
|
64135
|
+
});
|
|
64136
|
+
child.once("exit", (code, signal) => {
|
|
64137
|
+
if (!settled) {
|
|
64138
|
+
settle(/* @__PURE__ */ new Error(`fmp4 fragment child: ffmpeg exited before any fragment (code=${code} signal=${signal})`));
|
|
64139
|
+
return;
|
|
64140
|
+
}
|
|
64141
|
+
if (!isCurrent()) return;
|
|
64142
|
+
const error = /* @__PURE__ */ new Error(`fmp4 fragment child: ffmpeg exited while live (code=${code} signal=${signal})`);
|
|
64143
|
+
this.deps.logger.warn("fmp4 fragment child: ffmpeg exited while live", {
|
|
64144
|
+
tags: { deviceId: this.args.deviceId },
|
|
64145
|
+
meta: {
|
|
64146
|
+
sourceId: this.args.sourceId,
|
|
64147
|
+
code,
|
|
64148
|
+
signal,
|
|
64149
|
+
unitsOut: this.unitsOut
|
|
64150
|
+
}
|
|
64151
|
+
});
|
|
64152
|
+
this.args.plane.end("the fragment child exited");
|
|
64153
|
+
this.deps.onChildExit?.(error);
|
|
64154
|
+
});
|
|
64155
|
+
});
|
|
63852
64156
|
}
|
|
63853
64157
|
/**
|
|
63854
|
-
*
|
|
63855
|
-
*
|
|
63856
|
-
*
|
|
63857
|
-
* A child that has exited between the claim and this read contributes an
|
|
63858
|
-
* entry with no numbers rather than no entry — the unit exists, the addon
|
|
63859
|
-
* believes it is running, and hiding it would make a dying writer look like
|
|
63860
|
-
* a writer that was never started.
|
|
64158
|
+
* The byte stream stopped being splittable. Not recoverable — the splitter
|
|
64159
|
+
* cannot resynchronise mid-box — so the child is a corpse and every consumer
|
|
64160
|
+
* has to be told, loudly, with the reason.
|
|
63861
64161
|
*/
|
|
63862
|
-
|
|
63863
|
-
const
|
|
63864
|
-
|
|
63865
|
-
|
|
63866
|
-
|
|
63867
|
-
|
|
63868
|
-
|
|
63869
|
-
|
|
63870
|
-
|
|
63871
|
-
|
|
64162
|
+
onFault(reason, wasLive, current, settle) {
|
|
64163
|
+
const error = /* @__PURE__ */ new Error(`fmp4 fragment child: ${reason}`);
|
|
64164
|
+
this.deps.logger.error("fmp4 fragment child: the ffmpeg output stopped parsing as fMP4", {
|
|
64165
|
+
tags: { deviceId: this.args.deviceId },
|
|
64166
|
+
meta: {
|
|
64167
|
+
sourceId: this.args.sourceId,
|
|
64168
|
+
unitsOut: this.unitsOut,
|
|
64169
|
+
interstitial: this.splitter.discardedInterstitialTypes,
|
|
64170
|
+
reason
|
|
64171
|
+
}
|
|
63872
64172
|
});
|
|
63873
|
-
|
|
64173
|
+
this.killChild();
|
|
64174
|
+
settle(error);
|
|
64175
|
+
if (wasLive && current) {
|
|
64176
|
+
this.args.plane.end("the fragment child produced unsplittable output");
|
|
64177
|
+
this.deps.onChildExit?.(error);
|
|
64178
|
+
}
|
|
63874
64179
|
}
|
|
63875
|
-
|
|
63876
|
-
|
|
63877
|
-
|
|
63878
|
-
|
|
63879
|
-
|
|
63880
|
-
|
|
63881
|
-
|
|
63882
|
-
|
|
63883
|
-
|
|
63884
|
-
|
|
63885
|
-
|
|
63886
|
-
|
|
63887
|
-
|
|
63888
|
-
"backups"
|
|
63889
|
-
];
|
|
63890
|
-
var DEFAULT_LOCATION_SUBDIRS = {
|
|
63891
|
-
data: "db",
|
|
63892
|
-
media: "media",
|
|
63893
|
-
recordings: "recordings",
|
|
63894
|
-
"recordings-high": "recordings-high",
|
|
63895
|
-
"recordings-low": "recordings-low",
|
|
63896
|
-
"recordings-clips": "recordings-clips",
|
|
63897
|
-
"event-images": "event-images",
|
|
63898
|
-
models: "models",
|
|
63899
|
-
"addons-data": "addons-data",
|
|
63900
|
-
cache: "/tmp/camstack-cache",
|
|
63901
|
-
logs: "logs",
|
|
63902
|
-
backups: "backups"
|
|
63903
|
-
};
|
|
63904
|
-
var FilesystemStorageProvider = class {
|
|
63905
|
-
id = "local";
|
|
63906
|
-
name = "Local Filesystem";
|
|
63907
|
-
supportedLocations = [...STORAGE_LOCATION_TYPES];
|
|
63908
|
-
rootPath;
|
|
63909
|
-
locationPaths;
|
|
63910
|
-
constructor(rootPath, overrides) {
|
|
63911
|
-
this.rootPath = node_path.resolve(rootPath);
|
|
63912
|
-
this.locationPaths = /* @__PURE__ */ new Map();
|
|
63913
|
-
for (const loc of STORAGE_LOCATION_TYPES) {
|
|
63914
|
-
const override = overrides?.[loc];
|
|
63915
|
-
if (override) this.locationPaths.set(loc, node_path.resolve(override));
|
|
63916
|
-
else {
|
|
63917
|
-
const subdir = DEFAULT_LOCATION_SUBDIRS[loc] ?? loc;
|
|
63918
|
-
this.locationPaths.set(loc, node_path.isAbsolute(subdir) ? subdir : node_path.join(this.rootPath, subdir));
|
|
63919
|
-
}
|
|
64180
|
+
killChild() {
|
|
64181
|
+
const child = this.child;
|
|
64182
|
+
this.child = null;
|
|
64183
|
+
if (child && !child.killed) try {
|
|
64184
|
+
child.kill("SIGTERM");
|
|
64185
|
+
} catch (err) {
|
|
64186
|
+
this.deps.logger.warn("fmp4 fragment child: kill error", {
|
|
64187
|
+
tags: { deviceId: this.args.deviceId },
|
|
64188
|
+
meta: {
|
|
64189
|
+
sourceId: this.args.sourceId,
|
|
64190
|
+
error: require_err_msg.errMsg(err)
|
|
64191
|
+
}
|
|
64192
|
+
});
|
|
63920
64193
|
}
|
|
63921
|
-
for (const [loc, override] of Object.entries(overrides ?? {})) if (typeof override === "string" && !this.locationPaths.has(loc)) this.locationPaths.set(loc, node_path.resolve(override));
|
|
63922
64194
|
}
|
|
63923
|
-
|
|
63924
|
-
|
|
63925
|
-
|
|
64195
|
+
};
|
|
64196
|
+
var DEFAULT_QUEUE_CAPACITY = 4;
|
|
64197
|
+
var Fmp4FragmentPlane = class {
|
|
64198
|
+
logger;
|
|
64199
|
+
prebuffer;
|
|
64200
|
+
now;
|
|
64201
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
64202
|
+
/** The last init unit seen, handed to every later subscriber. */
|
|
64203
|
+
retainedInit = null;
|
|
64204
|
+
ended = false;
|
|
64205
|
+
/** Oldest first. Empty unless {@link Fmp4PrebufferOptions} was supplied. */
|
|
64206
|
+
ring = [];
|
|
64207
|
+
ringBytes = 0;
|
|
64208
|
+
constructor(logger, prebuffer, now = Date.now) {
|
|
64209
|
+
this.logger = logger;
|
|
64210
|
+
this.prebuffer = prebuffer;
|
|
64211
|
+
this.now = now;
|
|
63926
64212
|
}
|
|
63927
|
-
|
|
63928
|
-
|
|
63929
|
-
|
|
63930
|
-
|
|
63931
|
-
|
|
63932
|
-
|
|
63933
|
-
|
|
63934
|
-
|
|
63935
|
-
|
|
63936
|
-
|
|
63937
|
-
|
|
63938
|
-
|
|
63939
|
-
|
|
63940
|
-
|
|
64213
|
+
get subscriberCount() {
|
|
64214
|
+
return this.subscriptions.size;
|
|
64215
|
+
}
|
|
64216
|
+
/** True once {@link end} has been called — no further units are accepted. */
|
|
64217
|
+
get isEnded() {
|
|
64218
|
+
return this.ended;
|
|
64219
|
+
}
|
|
64220
|
+
/** What the prebuffer ring holds right now. All zeroes when disabled. */
|
|
64221
|
+
prebufferStats() {
|
|
64222
|
+
const oldest = this.ring[0];
|
|
64223
|
+
return {
|
|
64224
|
+
fragments: this.ring.length,
|
|
64225
|
+
bytes: this.ringBytes,
|
|
64226
|
+
spanMs: oldest === void 0 ? 0 : this.now() - oldest.arrivedAt
|
|
64227
|
+
};
|
|
64228
|
+
}
|
|
64229
|
+
subscribe(input) {
|
|
64230
|
+
const replay = input.withPrebuffer === true ? this.trimmedRing() : [];
|
|
64231
|
+
const requested = Math.max(1, input.queueCapacity ?? DEFAULT_QUEUE_CAPACITY);
|
|
64232
|
+
const sub = {
|
|
64233
|
+
id: `fmp4-${(0, node_crypto.randomUUID)()}`,
|
|
64234
|
+
tag: input.tag,
|
|
64235
|
+
subscribedAt: this.now(),
|
|
64236
|
+
capacity: requested + replay.length,
|
|
64237
|
+
queue: [],
|
|
64238
|
+
delivered: 0,
|
|
64239
|
+
closedReason: null,
|
|
64240
|
+
wake: null,
|
|
64241
|
+
iterating: false
|
|
64242
|
+
};
|
|
64243
|
+
this.subscriptions.set(sub.id, sub);
|
|
64244
|
+
if (this.retainedInit !== null) this.enqueue(sub, this.retainedInit);
|
|
64245
|
+
for (const retained of replay) this.enqueue(sub, retained.unit);
|
|
64246
|
+
if (this.ended) this.closeSubscription(sub, "ended");
|
|
64247
|
+
this.logger?.info("fmp4 plane: subscribed", { meta: {
|
|
64248
|
+
subscriptionId: sub.id,
|
|
64249
|
+
tag: sub.tag,
|
|
64250
|
+
hasRetainedInit: this.retainedInit !== null,
|
|
64251
|
+
prebufferFragments: replay.length,
|
|
64252
|
+
prebufferBytes: replay.reduce((n, r) => n + r.unit.data.length, 0)
|
|
64253
|
+
} });
|
|
64254
|
+
return this.facade(sub);
|
|
64255
|
+
}
|
|
64256
|
+
/**
|
|
64257
|
+
* Fan one splitter unit out. An `init` REPLACES the retained one — ffmpeg
|
|
64258
|
+
* emits exactly one per child, and a second means the child was respawned, in
|
|
64259
|
+
* which case the old one describes a stream that no longer exists.
|
|
64260
|
+
*/
|
|
64261
|
+
publish(unit) {
|
|
64262
|
+
if (this.ended) return;
|
|
64263
|
+
if (unit.kind === "init") {
|
|
64264
|
+
this.retainedInit = unit;
|
|
64265
|
+
this.ring.length = 0;
|
|
64266
|
+
this.ringBytes = 0;
|
|
64267
|
+
} else this.retain(unit);
|
|
64268
|
+
for (const sub of this.subscriptions.values()) {
|
|
64269
|
+
if (sub.closedReason !== null) continue;
|
|
64270
|
+
this.enqueue(sub, unit);
|
|
63941
64271
|
}
|
|
63942
64272
|
}
|
|
63943
|
-
|
|
63944
|
-
|
|
63945
|
-
|
|
63946
|
-
|
|
64273
|
+
/**
|
|
64274
|
+
* The producer stopped. Every subscriber drains what it holds; its final
|
|
64275
|
+
* packet carries `isLast`, and its generator then completes.
|
|
64276
|
+
*/
|
|
64277
|
+
end(reason = "producer ended") {
|
|
64278
|
+
if (this.ended) return;
|
|
64279
|
+
this.ended = true;
|
|
64280
|
+
this.logger?.info("fmp4 plane: ended", { meta: {
|
|
64281
|
+
reason,
|
|
64282
|
+
subscribers: this.subscriptions.size
|
|
64283
|
+
} });
|
|
64284
|
+
for (const sub of this.subscriptions.values()) if (sub.closedReason === null) this.closeSubscription(sub, "ended");
|
|
64285
|
+
}
|
|
64286
|
+
listSubscribers() {
|
|
64287
|
+
return [...this.subscriptions.values()].map((s) => ({
|
|
64288
|
+
tag: s.tag,
|
|
64289
|
+
subscribedAt: s.subscribedAt,
|
|
64290
|
+
delivered: s.delivered,
|
|
64291
|
+
closedReason: s.closedReason
|
|
63947
64292
|
}));
|
|
63948
64293
|
}
|
|
63949
|
-
|
|
63950
|
-
|
|
63951
|
-
|
|
63952
|
-
|
|
63953
|
-
|
|
63954
|
-
|
|
63955
|
-
|
|
63956
|
-
} catch {
|
|
63957
|
-
return false;
|
|
63958
|
-
}
|
|
64294
|
+
/** End and forget everything. Idempotent. */
|
|
64295
|
+
dispose() {
|
|
64296
|
+
this.end("disposed");
|
|
64297
|
+
this.subscriptions.clear();
|
|
64298
|
+
this.retainedInit = null;
|
|
64299
|
+
this.ring.length = 0;
|
|
64300
|
+
this.ringBytes = 0;
|
|
63959
64301
|
}
|
|
63960
|
-
|
|
63961
|
-
|
|
63962
|
-
|
|
63963
|
-
|
|
63964
|
-
|
|
63965
|
-
|
|
63966
|
-
|
|
63967
|
-
|
|
64302
|
+
/**
|
|
64303
|
+
* Add one fragment to the ring and evict from the front until BOTH bounds
|
|
64304
|
+
* hold. Eviction is oldest-first, which is the one place in this file where
|
|
64305
|
+
* dropping is correct: the ring is context, not stream — nobody is mid-decode
|
|
64306
|
+
* on it, and a subscriber only ever receives a contiguous tail of it.
|
|
64307
|
+
*/
|
|
64308
|
+
retain(unit) {
|
|
64309
|
+
const prebuffer = this.prebuffer;
|
|
64310
|
+
if (prebuffer === void 0) return;
|
|
64311
|
+
const arrivedAt = this.now();
|
|
64312
|
+
this.ring.push({
|
|
64313
|
+
unit,
|
|
64314
|
+
arrivedAt
|
|
64315
|
+
});
|
|
64316
|
+
this.ringBytes += unit.data.length;
|
|
64317
|
+
const cutoff = arrivedAt - prebuffer.windowMs;
|
|
64318
|
+
while (this.ring.length > 0) {
|
|
64319
|
+
const oldest = this.ring[0];
|
|
64320
|
+
if (oldest === void 0) break;
|
|
64321
|
+
const tooOld = oldest.arrivedAt < cutoff;
|
|
64322
|
+
const tooBig = this.ringBytes > prebuffer.maxBytes;
|
|
64323
|
+
if (!tooOld && !tooBig || this.ring.length === 1) break;
|
|
64324
|
+
this.ring.shift();
|
|
64325
|
+
this.ringBytes -= oldest.unit.data.length;
|
|
63968
64326
|
}
|
|
63969
64327
|
}
|
|
63970
|
-
|
|
63971
|
-
|
|
63972
|
-
|
|
63973
|
-
|
|
63974
|
-
|
|
63975
|
-
|
|
64328
|
+
/**
|
|
64329
|
+
* The ring as a subscriber should receive it — window applied AT SUBSCRIBE
|
|
64330
|
+
* time, not only at publish time. A camera that went quiet keeps its last
|
|
64331
|
+
* fragment in the ring indefinitely (see the never-evict-the-newest rule),
|
|
64332
|
+
* and replaying a 40-second-old fragment as "prebuffer" would put stale video
|
|
64333
|
+
* at the head of a clip iOS presents as the moment of the event.
|
|
64334
|
+
*/
|
|
64335
|
+
trimmedRing() {
|
|
64336
|
+
const prebuffer = this.prebuffer;
|
|
64337
|
+
if (prebuffer === void 0) return [];
|
|
64338
|
+
const cutoff = this.now() - prebuffer.windowMs;
|
|
64339
|
+
return this.ring.filter((r) => r.arrivedAt >= cutoff);
|
|
63976
64340
|
}
|
|
63977
|
-
|
|
63978
|
-
|
|
63979
|
-
|
|
63980
|
-
|
|
63981
|
-
|
|
63982
|
-
|
|
63983
|
-
|
|
63984
|
-
|
|
63985
|
-
|
|
63986
|
-
|
|
63987
|
-
const stats = await node_fs.promises.statfs(target);
|
|
63988
|
-
return stats.bavail * stats.bsize;
|
|
63989
|
-
} catch {
|
|
63990
|
-
return null;
|
|
64341
|
+
enqueue(sub, unit) {
|
|
64342
|
+
if (sub.queue.length >= sub.capacity) {
|
|
64343
|
+
this.logger?.warn("fmp4 plane: subscriber fell behind \u2014 CLOSING it rather than gapping it", { meta: {
|
|
64344
|
+
subscriptionId: sub.id,
|
|
64345
|
+
tag: sub.tag,
|
|
64346
|
+
capacity: sub.capacity,
|
|
64347
|
+
delivered: sub.delivered
|
|
64348
|
+
} });
|
|
64349
|
+
this.closeSubscription(sub, "slow-consumer");
|
|
64350
|
+
return;
|
|
63991
64351
|
}
|
|
64352
|
+
sub.queue.push({
|
|
64353
|
+
kind: unit.kind,
|
|
64354
|
+
data: unit.data,
|
|
64355
|
+
sequence: unit.sequence,
|
|
64356
|
+
isLast: false
|
|
64357
|
+
});
|
|
64358
|
+
this.wake(sub);
|
|
63992
64359
|
}
|
|
63993
|
-
|
|
63994
|
-
|
|
63995
|
-
|
|
63996
|
-
|
|
64360
|
+
closeSubscription(sub, reason) {
|
|
64361
|
+
if (sub.closedReason !== null) return;
|
|
64362
|
+
sub.closedReason = reason;
|
|
64363
|
+
if (reason === "slow-consumer") sub.queue.length = 0;
|
|
64364
|
+
this.wake(sub);
|
|
63997
64365
|
}
|
|
63998
|
-
|
|
63999
|
-
|
|
64000
|
-
|
|
64366
|
+
wake(sub) {
|
|
64367
|
+
const resume = sub.wake;
|
|
64368
|
+
sub.wake = null;
|
|
64369
|
+
resume?.();
|
|
64001
64370
|
}
|
|
64002
|
-
|
|
64003
|
-
|
|
64004
|
-
|
|
64005
|
-
|
|
64006
|
-
|
|
64007
|
-
|
|
64008
|
-
|
|
64009
|
-
|
|
64010
|
-
|
|
64011
|
-
|
|
64012
|
-
|
|
64013
|
-
|
|
64014
|
-
|
|
64015
|
-
|
|
64016
|
-
|
|
64017
|
-
|
|
64018
|
-
|
|
64019
|
-
const features = [...new Set(shape.features)].toSorted();
|
|
64020
|
-
return require_canonical_hash.canonicalHash({
|
|
64021
|
-
deviceType: shape.deviceType,
|
|
64022
|
-
features
|
|
64023
|
-
});
|
|
64024
|
-
}
|
|
64025
|
-
function diffExportTargets(desired, lastAdvertised) {
|
|
64026
|
-
const toAdd = [];
|
|
64027
|
-
const toUpdate = [];
|
|
64028
|
-
const desiredIds = /* @__PURE__ */ new Set();
|
|
64029
|
-
for (const entry of desired) {
|
|
64030
|
-
desiredIds.add(entry.deviceId);
|
|
64031
|
-
const previous = lastAdvertised.get(entry.deviceId);
|
|
64032
|
-
if (previous === void 0) toAdd.push(entry);
|
|
64033
|
-
else if (previous.fingerprint !== entry.fingerprint) toUpdate.push(entry);
|
|
64371
|
+
facade(sub) {
|
|
64372
|
+
const plane = this;
|
|
64373
|
+
return {
|
|
64374
|
+
id: sub.id,
|
|
64375
|
+
tag: sub.tag,
|
|
64376
|
+
get delivered() {
|
|
64377
|
+
return sub.delivered;
|
|
64378
|
+
},
|
|
64379
|
+
get closedReason() {
|
|
64380
|
+
return sub.closedReason;
|
|
64381
|
+
},
|
|
64382
|
+
packets: () => plane.iterate(sub),
|
|
64383
|
+
release: () => {
|
|
64384
|
+
plane.closeSubscription(sub, "released");
|
|
64385
|
+
plane.subscriptions.delete(sub.id);
|
|
64386
|
+
}
|
|
64387
|
+
};
|
|
64034
64388
|
}
|
|
64035
|
-
|
|
64036
|
-
|
|
64037
|
-
|
|
64038
|
-
|
|
64039
|
-
|
|
64040
|
-
|
|
64041
|
-
|
|
64042
|
-
|
|
64043
|
-
|
|
64044
|
-
|
|
64045
|
-
|
|
64046
|
-
|
|
64389
|
+
async *iterate(sub) {
|
|
64390
|
+
if (sub.iterating) throw new Error(`fmp4 plane: subscription ${sub.tag} is already being consumed \u2014 take a second subscription`);
|
|
64391
|
+
sub.iterating = true;
|
|
64392
|
+
for (; ; ) {
|
|
64393
|
+
const next = sub.queue.shift();
|
|
64394
|
+
if (next === void 0) {
|
|
64395
|
+
if (sub.closedReason !== null) return;
|
|
64396
|
+
await new Promise((resolve) => {
|
|
64397
|
+
sub.wake = resolve;
|
|
64398
|
+
});
|
|
64399
|
+
continue;
|
|
64400
|
+
}
|
|
64401
|
+
const isLast = sub.closedReason === "ended" && sub.queue.length === 0;
|
|
64402
|
+
sub.delivered += 1;
|
|
64403
|
+
yield {
|
|
64404
|
+
...next,
|
|
64405
|
+
isLast
|
|
64406
|
+
};
|
|
64407
|
+
if (isLast) return;
|
|
64408
|
+
}
|
|
64409
|
+
}
|
|
64410
|
+
};
|
|
64047
64411
|
var DEFAULT_FIRST_DATA_TIMEOUT_MS = 8e3;
|
|
64048
64412
|
var DEFAULT_RESTART_DELAY_MS = 1e3;
|
|
64049
64413
|
var DEFAULT_STABLE_RUN_MS = 3e4;
|
|
@@ -64276,420 +64640,300 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
64276
64640
|
if (typeof grace === "object" && grace !== null && "unref" in grace) grace.unref();
|
|
64277
64641
|
}
|
|
64278
64642
|
};
|
|
64279
|
-
var
|
|
64280
|
-
var
|
|
64281
|
-
|
|
64282
|
-
|
|
64283
|
-
|
|
64284
|
-
|
|
64285
|
-
|
|
64286
|
-
|
|
64287
|
-
|
|
64288
|
-
|
|
64289
|
-
|
|
64290
|
-
|
|
64291
|
-
|
|
64292
|
-
|
|
64293
|
-
|
|
64294
|
-
|
|
64295
|
-
|
|
64296
|
-
|
|
64297
|
-
|
|
64298
|
-
|
|
64299
|
-
|
|
64300
|
-
|
|
64301
|
-
|
|
64302
|
-
|
|
64303
|
-
|
|
64304
|
-
|
|
64305
|
-
|
|
64306
|
-
|
|
64307
|
-
|
|
64308
|
-
bytes: this.ringBytes,
|
|
64309
|
-
spanMs: oldest === void 0 ? 0 : this.now() - oldest.arrivedAt
|
|
64310
|
-
};
|
|
64643
|
+
var CLOCK_TICKS_PER_SEC = 100;
|
|
64644
|
+
var PAGE_BYTES = 4096;
|
|
64645
|
+
var NO_COST_CLAIM = { release: () => void 0 };
|
|
64646
|
+
var nodeProcStatReader = {
|
|
64647
|
+
readStat: (pid) => (0, node_fs_promises.readFile)(`/proc/${pid}/stat`, "utf8"),
|
|
64648
|
+
readStatm: (pid) => (0, node_fs_promises.readFile)(`/proc/${pid}/statm`, "utf8")
|
|
64649
|
+
};
|
|
64650
|
+
function parseProcCpuSeconds(line) {
|
|
64651
|
+
const close = line.lastIndexOf(")");
|
|
64652
|
+
if (close < 0) return null;
|
|
64653
|
+
const rest = line.slice(close + 1).trim().split(/\s+/);
|
|
64654
|
+
const utime = Number(rest[11]);
|
|
64655
|
+
const stime = Number(rest[12]);
|
|
64656
|
+
if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;
|
|
64657
|
+
return (utime + stime) / CLOCK_TICKS_PER_SEC;
|
|
64658
|
+
}
|
|
64659
|
+
function parseProcRssBytes(line) {
|
|
64660
|
+
const fields = line.trim().split(/\s+/);
|
|
64661
|
+
const residentPages = Number(fields[1]);
|
|
64662
|
+
if (!Number.isFinite(residentPages)) return null;
|
|
64663
|
+
return residentPages * PAGE_BYTES;
|
|
64664
|
+
}
|
|
64665
|
+
async function readProcessCost(pid, reader = nodeProcStatReader) {
|
|
64666
|
+
let cpuSeconds = null;
|
|
64667
|
+
let rssBytes = null;
|
|
64668
|
+
try {
|
|
64669
|
+
cpuSeconds = parseProcCpuSeconds(await reader.readStat(pid));
|
|
64670
|
+
} catch {
|
|
64671
|
+
cpuSeconds = null;
|
|
64311
64672
|
}
|
|
64312
|
-
|
|
64313
|
-
|
|
64314
|
-
|
|
64315
|
-
|
|
64316
|
-
id: `fmp4-${(0, node_crypto.randomUUID)()}`,
|
|
64317
|
-
tag: input.tag,
|
|
64318
|
-
subscribedAt: this.now(),
|
|
64319
|
-
capacity: requested + replay.length,
|
|
64320
|
-
queue: [],
|
|
64321
|
-
delivered: 0,
|
|
64322
|
-
closedReason: null,
|
|
64323
|
-
wake: null,
|
|
64324
|
-
iterating: false
|
|
64325
|
-
};
|
|
64326
|
-
this.subscriptions.set(sub.id, sub);
|
|
64327
|
-
if (this.retainedInit !== null) this.enqueue(sub, this.retainedInit);
|
|
64328
|
-
for (const retained of replay) this.enqueue(sub, retained.unit);
|
|
64329
|
-
if (this.ended) this.closeSubscription(sub, "ended");
|
|
64330
|
-
this.logger?.info("fmp4 plane: subscribed", { meta: {
|
|
64331
|
-
subscriptionId: sub.id,
|
|
64332
|
-
tag: sub.tag,
|
|
64333
|
-
hasRetainedInit: this.retainedInit !== null,
|
|
64334
|
-
prebufferFragments: replay.length,
|
|
64335
|
-
prebufferBytes: replay.reduce((n, r) => n + r.unit.data.length, 0)
|
|
64336
|
-
} });
|
|
64337
|
-
return this.facade(sub);
|
|
64673
|
+
try {
|
|
64674
|
+
rssBytes = parseProcRssBytes(await reader.readStatm(pid));
|
|
64675
|
+
} catch {
|
|
64676
|
+
rssBytes = null;
|
|
64338
64677
|
}
|
|
64339
|
-
|
|
64340
|
-
|
|
64341
|
-
|
|
64342
|
-
|
|
64343
|
-
|
|
64344
|
-
|
|
64345
|
-
|
|
64346
|
-
|
|
64347
|
-
|
|
64348
|
-
|
|
64349
|
-
|
|
64350
|
-
|
|
64351
|
-
for (const sub of this.subscriptions.values()) {
|
|
64352
|
-
if (sub.closedReason !== null) continue;
|
|
64353
|
-
this.enqueue(sub, unit);
|
|
64354
|
-
}
|
|
64678
|
+
return {
|
|
64679
|
+
...cpuSeconds === null ? {} : { cpuSeconds },
|
|
64680
|
+
...rssBytes === null ? {} : { rssBytes }
|
|
64681
|
+
};
|
|
64682
|
+
}
|
|
64683
|
+
var ChildCostRegistry = class {
|
|
64684
|
+
reader;
|
|
64685
|
+
now;
|
|
64686
|
+
claims = /* @__PURE__ */ new Map();
|
|
64687
|
+
constructor(reader = nodeProcStatReader, now = Date.now) {
|
|
64688
|
+
this.reader = reader;
|
|
64689
|
+
this.now = now;
|
|
64355
64690
|
}
|
|
64356
64691
|
/**
|
|
64357
|
-
*
|
|
64358
|
-
*
|
|
64692
|
+
* Record one child. The returned handle is the only way to remove it.
|
|
64693
|
+
*
|
|
64694
|
+
* A claim with no pid is dropped rather than stored: it could carry no
|
|
64695
|
+
* measurement, and an entry with a camera and no numbers reads on a chart as
|
|
64696
|
+
* a camera that cost nothing.
|
|
64359
64697
|
*/
|
|
64360
|
-
|
|
64361
|
-
|
|
64362
|
-
|
|
64363
|
-
|
|
64364
|
-
|
|
64365
|
-
|
|
64366
|
-
|
|
64367
|
-
|
|
64698
|
+
claim(input) {
|
|
64699
|
+
const pid = input.pid;
|
|
64700
|
+
if (pid === void 0 || !Number.isInteger(pid) || pid <= 0) return NO_COST_CLAIM;
|
|
64701
|
+
const key = /* @__PURE__ */ Symbol("child-cost-claim");
|
|
64702
|
+
this.claims.set(key, {
|
|
64703
|
+
role: input.role,
|
|
64704
|
+
deviceId: input.deviceId,
|
|
64705
|
+
unit: input.unit,
|
|
64706
|
+
attribution: input.attribution ?? "measured",
|
|
64707
|
+
pid,
|
|
64708
|
+
startedAtMs: this.now()
|
|
64709
|
+
});
|
|
64710
|
+
return { release: () => {
|
|
64711
|
+
this.claims.delete(key);
|
|
64712
|
+
} };
|
|
64368
64713
|
}
|
|
64369
|
-
|
|
64370
|
-
|
|
64371
|
-
|
|
64372
|
-
|
|
64373
|
-
|
|
64374
|
-
|
|
64714
|
+
/** Live claims, in claim order. Diagnostics and tests; not a contribution. */
|
|
64715
|
+
list() {
|
|
64716
|
+
return [...this.claims.values()].map((c) => ({
|
|
64717
|
+
role: c.role,
|
|
64718
|
+
deviceId: c.deviceId,
|
|
64719
|
+
unit: c.unit,
|
|
64720
|
+
attribution: c.attribution,
|
|
64721
|
+
pid: c.pid
|
|
64375
64722
|
}));
|
|
64376
64723
|
}
|
|
64377
|
-
/** End and forget everything. Idempotent. */
|
|
64378
|
-
dispose() {
|
|
64379
|
-
this.end("disposed");
|
|
64380
|
-
this.subscriptions.clear();
|
|
64381
|
-
this.retainedInit = null;
|
|
64382
|
-
this.ring.length = 0;
|
|
64383
|
-
this.ringBytes = 0;
|
|
64384
|
-
}
|
|
64385
64724
|
/**
|
|
64386
|
-
*
|
|
64387
|
-
*
|
|
64388
|
-
*
|
|
64389
|
-
*
|
|
64725
|
+
* This addon's contribution: one entry per live claim, with whatever the OS
|
|
64726
|
+
* will tell us about that child right now.
|
|
64727
|
+
*
|
|
64728
|
+
* A child that has exited between the claim and this read contributes an
|
|
64729
|
+
* entry with no numbers rather than no entry — the unit exists, the addon
|
|
64730
|
+
* believes it is running, and hiding it would make a dying writer look like
|
|
64731
|
+
* a writer that was never started.
|
|
64390
64732
|
*/
|
|
64391
|
-
|
|
64392
|
-
const
|
|
64393
|
-
|
|
64394
|
-
|
|
64395
|
-
|
|
64396
|
-
|
|
64397
|
-
|
|
64733
|
+
async contributions() {
|
|
64734
|
+
const out = [];
|
|
64735
|
+
for (const claim of this.claims.values()) out.push({
|
|
64736
|
+
role: claim.role,
|
|
64737
|
+
deviceId: claim.deviceId,
|
|
64738
|
+
attribution: claim.attribution,
|
|
64739
|
+
unit: claim.unit,
|
|
64740
|
+
pid: claim.pid,
|
|
64741
|
+
startedAtMs: claim.startedAtMs,
|
|
64742
|
+
...await readProcessCost(claim.pid, this.reader)
|
|
64398
64743
|
});
|
|
64399
|
-
|
|
64400
|
-
const cutoff = arrivedAt - prebuffer.windowMs;
|
|
64401
|
-
while (this.ring.length > 0) {
|
|
64402
|
-
const oldest = this.ring[0];
|
|
64403
|
-
if (oldest === void 0) break;
|
|
64404
|
-
const tooOld = oldest.arrivedAt < cutoff;
|
|
64405
|
-
const tooBig = this.ringBytes > prebuffer.maxBytes;
|
|
64406
|
-
if (!tooOld && !tooBig || this.ring.length === 1) break;
|
|
64407
|
-
this.ring.shift();
|
|
64408
|
-
this.ringBytes -= oldest.unit.data.length;
|
|
64409
|
-
}
|
|
64410
|
-
}
|
|
64411
|
-
/**
|
|
64412
|
-
* The ring as a subscriber should receive it — window applied AT SUBSCRIBE
|
|
64413
|
-
* time, not only at publish time. A camera that went quiet keeps its last
|
|
64414
|
-
* fragment in the ring indefinitely (see the never-evict-the-newest rule),
|
|
64415
|
-
* and replaying a 40-second-old fragment as "prebuffer" would put stale video
|
|
64416
|
-
* at the head of a clip iOS presents as the moment of the event.
|
|
64417
|
-
*/
|
|
64418
|
-
trimmedRing() {
|
|
64419
|
-
const prebuffer = this.prebuffer;
|
|
64420
|
-
if (prebuffer === void 0) return [];
|
|
64421
|
-
const cutoff = this.now() - prebuffer.windowMs;
|
|
64422
|
-
return this.ring.filter((r) => r.arrivedAt >= cutoff);
|
|
64744
|
+
return out;
|
|
64423
64745
|
}
|
|
64424
|
-
|
|
64425
|
-
|
|
64426
|
-
|
|
64427
|
-
|
|
64428
|
-
|
|
64429
|
-
|
|
64430
|
-
|
|
64431
|
-
|
|
64432
|
-
|
|
64433
|
-
|
|
64746
|
+
};
|
|
64747
|
+
var STORAGE_LOCATION_TYPES = [
|
|
64748
|
+
"data",
|
|
64749
|
+
"media",
|
|
64750
|
+
"recordings",
|
|
64751
|
+
"recordings-high",
|
|
64752
|
+
"recordings-low",
|
|
64753
|
+
"recordings-clips",
|
|
64754
|
+
"event-images",
|
|
64755
|
+
"models",
|
|
64756
|
+
"addons-data",
|
|
64757
|
+
"cache",
|
|
64758
|
+
"logs",
|
|
64759
|
+
"backups"
|
|
64760
|
+
];
|
|
64761
|
+
var DEFAULT_LOCATION_SUBDIRS = {
|
|
64762
|
+
data: "db",
|
|
64763
|
+
media: "media",
|
|
64764
|
+
recordings: "recordings",
|
|
64765
|
+
"recordings-high": "recordings-high",
|
|
64766
|
+
"recordings-low": "recordings-low",
|
|
64767
|
+
"recordings-clips": "recordings-clips",
|
|
64768
|
+
"event-images": "event-images",
|
|
64769
|
+
models: "models",
|
|
64770
|
+
"addons-data": "addons-data",
|
|
64771
|
+
cache: "/tmp/camstack-cache",
|
|
64772
|
+
logs: "logs",
|
|
64773
|
+
backups: "backups"
|
|
64774
|
+
};
|
|
64775
|
+
var FilesystemStorageProvider = class {
|
|
64776
|
+
id = "local";
|
|
64777
|
+
name = "Local Filesystem";
|
|
64778
|
+
supportedLocations = [...STORAGE_LOCATION_TYPES];
|
|
64779
|
+
rootPath;
|
|
64780
|
+
locationPaths;
|
|
64781
|
+
constructor(rootPath, overrides) {
|
|
64782
|
+
this.rootPath = node_path.resolve(rootPath);
|
|
64783
|
+
this.locationPaths = /* @__PURE__ */ new Map();
|
|
64784
|
+
for (const loc of STORAGE_LOCATION_TYPES) {
|
|
64785
|
+
const override = overrides?.[loc];
|
|
64786
|
+
if (override) this.locationPaths.set(loc, node_path.resolve(override));
|
|
64787
|
+
else {
|
|
64788
|
+
const subdir = DEFAULT_LOCATION_SUBDIRS[loc] ?? loc;
|
|
64789
|
+
this.locationPaths.set(loc, node_path.isAbsolute(subdir) ? subdir : node_path.join(this.rootPath, subdir));
|
|
64790
|
+
}
|
|
64434
64791
|
}
|
|
64435
|
-
|
|
64436
|
-
kind: unit.kind,
|
|
64437
|
-
data: unit.data,
|
|
64438
|
-
sequence: unit.sequence,
|
|
64439
|
-
isLast: false
|
|
64440
|
-
});
|
|
64441
|
-
this.wake(sub);
|
|
64442
|
-
}
|
|
64443
|
-
closeSubscription(sub, reason) {
|
|
64444
|
-
if (sub.closedReason !== null) return;
|
|
64445
|
-
sub.closedReason = reason;
|
|
64446
|
-
if (reason === "slow-consumer") sub.queue.length = 0;
|
|
64447
|
-
this.wake(sub);
|
|
64448
|
-
}
|
|
64449
|
-
wake(sub) {
|
|
64450
|
-
const resume = sub.wake;
|
|
64451
|
-
sub.wake = null;
|
|
64452
|
-
resume?.();
|
|
64792
|
+
for (const [loc, override] of Object.entries(overrides ?? {})) if (typeof override === "string" && !this.locationPaths.has(loc)) this.locationPaths.set(loc, node_path.resolve(override));
|
|
64453
64793
|
}
|
|
64454
|
-
|
|
64455
|
-
const
|
|
64456
|
-
return
|
|
64457
|
-
id: sub.id,
|
|
64458
|
-
tag: sub.tag,
|
|
64459
|
-
get delivered() {
|
|
64460
|
-
return sub.delivered;
|
|
64461
|
-
},
|
|
64462
|
-
get closedReason() {
|
|
64463
|
-
return sub.closedReason;
|
|
64464
|
-
},
|
|
64465
|
-
packets: () => plane.iterate(sub),
|
|
64466
|
-
release: () => {
|
|
64467
|
-
plane.closeSubscription(sub, "released");
|
|
64468
|
-
plane.subscriptions.delete(sub.id);
|
|
64469
|
-
}
|
|
64470
|
-
};
|
|
64794
|
+
async resolve({ location, relativePath }) {
|
|
64795
|
+
const base = this.locationPaths.get(location) ?? node_path.join(this.rootPath, location);
|
|
64796
|
+
return node_path.join(base, relativePath);
|
|
64471
64797
|
}
|
|
64472
|
-
async
|
|
64473
|
-
|
|
64474
|
-
|
|
64475
|
-
|
|
64476
|
-
|
|
64477
|
-
|
|
64478
|
-
|
|
64479
|
-
|
|
64480
|
-
|
|
64481
|
-
|
|
64482
|
-
|
|
64483
|
-
|
|
64484
|
-
|
|
64485
|
-
|
|
64486
|
-
yield {
|
|
64487
|
-
...next,
|
|
64488
|
-
isLast
|
|
64489
|
-
};
|
|
64490
|
-
if (isLast) return;
|
|
64798
|
+
async write({ location, relativePath, data }) {
|
|
64799
|
+
const filePath = await this.resolve({
|
|
64800
|
+
location,
|
|
64801
|
+
relativePath
|
|
64802
|
+
});
|
|
64803
|
+
await node_fs.promises.mkdir(node_path.dirname(filePath), { recursive: true });
|
|
64804
|
+
if (Buffer.isBuffer(data)) await node_fs.promises.writeFile(filePath, data);
|
|
64805
|
+
else {
|
|
64806
|
+
const writeStream = node_fs.createWriteStream(filePath);
|
|
64807
|
+
await new Promise((resolve, reject) => {
|
|
64808
|
+
data.pipe(writeStream);
|
|
64809
|
+
writeStream.on("finish", resolve);
|
|
64810
|
+
writeStream.on("error", reject);
|
|
64811
|
+
});
|
|
64491
64812
|
}
|
|
64492
64813
|
}
|
|
64493
|
-
|
|
64494
|
-
|
|
64495
|
-
|
|
64496
|
-
|
|
64497
|
-
|
|
64498
|
-
args;
|
|
64499
|
-
child = null;
|
|
64500
|
-
splitter = new require_canonical_hash.Fmp4BoxSplitter();
|
|
64501
|
-
stopped = false;
|
|
64502
|
-
unitsOut = 0;
|
|
64503
|
-
activeHwAccel = null;
|
|
64504
|
-
constructor(deps, args) {
|
|
64505
|
-
this.deps = deps;
|
|
64506
|
-
this.args = args;
|
|
64814
|
+
async read({ location, relativePath }) {
|
|
64815
|
+
return node_fs.promises.readFile(await this.resolve({
|
|
64816
|
+
location,
|
|
64817
|
+
relativePath
|
|
64818
|
+
}));
|
|
64507
64819
|
}
|
|
64508
|
-
|
|
64509
|
-
async start() {
|
|
64510
|
-
const requested = this.args.invocation.decodeHwAccel;
|
|
64511
|
-
this.activeHwAccel = requested;
|
|
64820
|
+
async exists({ location, relativePath }) {
|
|
64512
64821
|
try {
|
|
64513
|
-
await this.
|
|
64514
|
-
|
|
64515
|
-
|
|
64516
|
-
|
|
64517
|
-
|
|
64518
|
-
|
|
64519
|
-
|
|
64520
|
-
meta: {
|
|
64521
|
-
sourceId: this.args.sourceId,
|
|
64522
|
-
decodeHwAccel: requested,
|
|
64523
|
-
error: require_err_msg.errMsg(err)
|
|
64524
|
-
}
|
|
64525
|
-
});
|
|
64526
|
-
this.killChild();
|
|
64527
|
-
this.splitter = new require_canonical_hash.Fmp4BoxSplitter();
|
|
64528
|
-
this.activeHwAccel = null;
|
|
64529
|
-
await this.spawnAttempt(null);
|
|
64822
|
+
await node_fs.promises.access(await this.resolve({
|
|
64823
|
+
location,
|
|
64824
|
+
relativePath
|
|
64825
|
+
}));
|
|
64826
|
+
return true;
|
|
64827
|
+
} catch {
|
|
64828
|
+
return false;
|
|
64530
64829
|
}
|
|
64531
64830
|
}
|
|
64532
|
-
|
|
64533
|
-
|
|
64534
|
-
|
|
64535
|
-
|
|
64536
|
-
|
|
64537
|
-
|
|
64538
|
-
|
|
64539
|
-
|
|
64540
|
-
|
|
64541
|
-
this.killChild();
|
|
64542
|
-
this.args.plane.end("the fragment child stopped");
|
|
64831
|
+
async list({ location, prefix }) {
|
|
64832
|
+
const base = this.locationPaths.get(location);
|
|
64833
|
+
if (!base) return [];
|
|
64834
|
+
const dir = prefix ? node_path.join(base, prefix) : base;
|
|
64835
|
+
try {
|
|
64836
|
+
return (await node_fs.promises.readdir(dir, { withFileTypes: true })).map((e) => prefix ? `${prefix}/${e.name}` : e.name);
|
|
64837
|
+
} catch {
|
|
64838
|
+
return [];
|
|
64839
|
+
}
|
|
64543
64840
|
}
|
|
64544
|
-
|
|
64545
|
-
const
|
|
64546
|
-
|
|
64547
|
-
|
|
64548
|
-
sink: {
|
|
64549
|
-
kind: "stdout",
|
|
64550
|
-
container: "mp4",
|
|
64551
|
-
fragmentMs: this.args.fragmentMs
|
|
64552
|
-
}
|
|
64553
|
-
});
|
|
64554
|
-
this.deps.logger.info("fmp4 fragment child: spawning ffmpeg", {
|
|
64555
|
-
tags: { deviceId: this.args.deviceId },
|
|
64556
|
-
meta: {
|
|
64557
|
-
sourceId: this.args.sourceId,
|
|
64558
|
-
fragmentMs: this.args.fragmentMs,
|
|
64559
|
-
decodeHwAccel: decodeHwAccel ?? "software",
|
|
64560
|
-
argv: args.join(" ")
|
|
64561
|
-
}
|
|
64562
|
-
});
|
|
64563
|
-
return new Promise((resolve, reject) => {
|
|
64564
|
-
const child = this.deps.spawnFn(this.deps.ffmpegBinaryPath, args, { stdio: [
|
|
64565
|
-
"ignore",
|
|
64566
|
-
"pipe",
|
|
64567
|
-
"pipe"
|
|
64568
|
-
] });
|
|
64569
|
-
this.child = child;
|
|
64570
|
-
let settled = false;
|
|
64571
|
-
let failed = false;
|
|
64572
|
-
const isCurrent = () => !this.stopped && !failed;
|
|
64573
|
-
const timeoutMs = this.deps.firstUnitTimeoutMs ?? DEFAULT_FIRST_UNIT_TIMEOUT_MS;
|
|
64574
|
-
const settle = (fail) => {
|
|
64575
|
-
if (settled) return;
|
|
64576
|
-
settled = true;
|
|
64577
|
-
clearTimeout(timer);
|
|
64578
|
-
if (fail) {
|
|
64579
|
-
failed = true;
|
|
64580
|
-
reject(fail);
|
|
64581
|
-
} else resolve();
|
|
64582
|
-
};
|
|
64583
|
-
const timer = setTimeout(() => {
|
|
64584
|
-
settle(/* @__PURE__ */ new Error(`fmp4 fragment child: no fragment within ${timeoutMs}ms`));
|
|
64585
|
-
this.killChild();
|
|
64586
|
-
}, timeoutMs);
|
|
64587
|
-
timer.unref?.();
|
|
64588
|
-
child.stdout?.on("data", (chunk) => {
|
|
64589
|
-
for (const unit of this.splitter.push(chunk)) {
|
|
64590
|
-
this.unitsOut += 1;
|
|
64591
|
-
this.args.plane.publish(unit);
|
|
64592
|
-
if (unit.kind === "init") {
|
|
64593
|
-
this.deps.logger.info("fmp4 fragment child: INIT segment cut", {
|
|
64594
|
-
tags: { deviceId: this.args.deviceId },
|
|
64595
|
-
meta: {
|
|
64596
|
-
sourceId: this.args.sourceId,
|
|
64597
|
-
bytes: unit.data.length
|
|
64598
|
-
}
|
|
64599
|
-
});
|
|
64600
|
-
settle();
|
|
64601
|
-
} else if (this.unitsOut % FRAGMENT_LOG_EVERY === 0) this.deps.logger.info("fmp4 fragment child: fragments still flowing", {
|
|
64602
|
-
tags: { deviceId: this.args.deviceId },
|
|
64603
|
-
meta: {
|
|
64604
|
-
sourceId: this.args.sourceId,
|
|
64605
|
-
unitsOut: this.unitsOut,
|
|
64606
|
-
bytes: unit.data.length,
|
|
64607
|
-
subscribers: this.args.plane.subscriberCount
|
|
64608
|
-
}
|
|
64609
|
-
});
|
|
64610
|
-
}
|
|
64611
|
-
const fault = this.splitter.fault;
|
|
64612
|
-
if (fault !== null) this.onFault(fault, settled, isCurrent(), settle);
|
|
64613
|
-
});
|
|
64614
|
-
child.stderr?.setEncoding("utf8");
|
|
64615
|
-
child.stderr?.on("data", (line) => {
|
|
64616
|
-
this.deps.logger.debug("fmp4 fragment child ffmpeg", {
|
|
64617
|
-
tags: { deviceId: this.args.deviceId },
|
|
64618
|
-
meta: {
|
|
64619
|
-
sourceId: this.args.sourceId,
|
|
64620
|
-
line: line.trim()
|
|
64621
|
-
}
|
|
64622
|
-
});
|
|
64623
|
-
});
|
|
64624
|
-
child.once("error", (err) => {
|
|
64625
|
-
if (!settled) {
|
|
64626
|
-
settle(err);
|
|
64627
|
-
return;
|
|
64628
|
-
}
|
|
64629
|
-
if (!isCurrent()) return;
|
|
64630
|
-
this.args.plane.end("the fragment child errored");
|
|
64631
|
-
this.deps.onChildExit?.(err);
|
|
64632
|
-
});
|
|
64633
|
-
child.once("exit", (code, signal) => {
|
|
64634
|
-
if (!settled) {
|
|
64635
|
-
settle(/* @__PURE__ */ new Error(`fmp4 fragment child: ffmpeg exited before any fragment (code=${code} signal=${signal})`));
|
|
64636
|
-
return;
|
|
64637
|
-
}
|
|
64638
|
-
if (!isCurrent()) return;
|
|
64639
|
-
const error = /* @__PURE__ */ new Error(`fmp4 fragment child: ffmpeg exited while live (code=${code} signal=${signal})`);
|
|
64640
|
-
this.deps.logger.warn("fmp4 fragment child: ffmpeg exited while live", {
|
|
64641
|
-
tags: { deviceId: this.args.deviceId },
|
|
64642
|
-
meta: {
|
|
64643
|
-
sourceId: this.args.sourceId,
|
|
64644
|
-
code,
|
|
64645
|
-
signal,
|
|
64646
|
-
unitsOut: this.unitsOut
|
|
64647
|
-
}
|
|
64648
|
-
});
|
|
64649
|
-
this.args.plane.end("the fragment child exited");
|
|
64650
|
-
this.deps.onChildExit?.(error);
|
|
64651
|
-
});
|
|
64841
|
+
async delete({ location, relativePath }) {
|
|
64842
|
+
const filePath = await this.resolve({
|
|
64843
|
+
location,
|
|
64844
|
+
relativePath
|
|
64652
64845
|
});
|
|
64846
|
+
await node_fs.promises.rm(filePath, { force: true });
|
|
64653
64847
|
}
|
|
64654
|
-
|
|
64655
|
-
|
|
64656
|
-
|
|
64657
|
-
|
|
64658
|
-
|
|
64659
|
-
|
|
64660
|
-
|
|
64661
|
-
|
|
64662
|
-
|
|
64663
|
-
meta: {
|
|
64664
|
-
sourceId: this.args.sourceId,
|
|
64665
|
-
unitsOut: this.unitsOut,
|
|
64666
|
-
interstitial: this.splitter.discardedInterstitialTypes,
|
|
64667
|
-
reason
|
|
64848
|
+
async getAvailableSpace({ location }) {
|
|
64849
|
+
const base = this.locationPaths.get(location);
|
|
64850
|
+
if (!base) return null;
|
|
64851
|
+
try {
|
|
64852
|
+
let target = base;
|
|
64853
|
+
while (!node_fs.existsSync(target)) {
|
|
64854
|
+
const parent = node_path.dirname(target);
|
|
64855
|
+
if (!parent || parent === target) return null;
|
|
64856
|
+
target = parent;
|
|
64668
64857
|
}
|
|
64669
|
-
|
|
64670
|
-
|
|
64671
|
-
|
|
64672
|
-
|
|
64673
|
-
this.args.plane.end("the fragment child produced unsplittable output");
|
|
64674
|
-
this.deps.onChildExit?.(error);
|
|
64858
|
+
const stats = await node_fs.promises.statfs(target);
|
|
64859
|
+
return stats.bavail * stats.bsize;
|
|
64860
|
+
} catch {
|
|
64861
|
+
return null;
|
|
64675
64862
|
}
|
|
64676
64863
|
}
|
|
64677
|
-
|
|
64678
|
-
|
|
64679
|
-
|
|
64680
|
-
|
|
64681
|
-
|
|
64682
|
-
|
|
64683
|
-
|
|
64684
|
-
|
|
64685
|
-
meta: {
|
|
64686
|
-
sourceId: this.args.sourceId,
|
|
64687
|
-
error: require_err_msg.errMsg(err)
|
|
64688
|
-
}
|
|
64689
|
-
});
|
|
64690
|
-
}
|
|
64864
|
+
/** Get the resolved path for a location type (addon-declared ids fall back
|
|
64865
|
+
* to `<rootPath>/<id>`). */
|
|
64866
|
+
getLocationPath(location) {
|
|
64867
|
+
return this.locationPaths.get(location) ?? node_path.join(this.rootPath, location);
|
|
64868
|
+
}
|
|
64869
|
+
/** Get the root path */
|
|
64870
|
+
getRootPath() {
|
|
64871
|
+
return this.rootPath;
|
|
64691
64872
|
}
|
|
64692
64873
|
};
|
|
64874
|
+
function physicalRootOf(root, realpath = node_fs.realpathSync) {
|
|
64875
|
+
const resolved = node_path.default.resolve(root);
|
|
64876
|
+
try {
|
|
64877
|
+
return {
|
|
64878
|
+
key: realpath(resolved),
|
|
64879
|
+
resolved: true
|
|
64880
|
+
};
|
|
64881
|
+
} catch {
|
|
64882
|
+
return {
|
|
64883
|
+
key: resolved,
|
|
64884
|
+
resolved: false
|
|
64885
|
+
};
|
|
64886
|
+
}
|
|
64887
|
+
}
|
|
64888
|
+
function containsOrEquals(outer, inner) {
|
|
64889
|
+
if (outer === inner) return true;
|
|
64890
|
+
const relative = node_path.default.relative(outer, inner);
|
|
64891
|
+
return relative !== "" && !relative.startsWith("..") && !node_path.default.isAbsolute(relative);
|
|
64892
|
+
}
|
|
64893
|
+
function signExpiringUrl(secret, id, expMs) {
|
|
64894
|
+
return (0, node_crypto.createHmac)("sha256", secret).update(`${id}:${String(expMs)}`).digest("hex");
|
|
64895
|
+
}
|
|
64896
|
+
function verifyExpiringUrl(input) {
|
|
64897
|
+
const { secret, id, exp, sig, nowMs } = input;
|
|
64898
|
+
if (exp === void 0 || sig === void 0) return false;
|
|
64899
|
+
const expMs = typeof exp === "number" ? exp : Number(exp);
|
|
64900
|
+
if (!Number.isFinite(expMs)) return false;
|
|
64901
|
+
if (expMs <= nowMs) return false;
|
|
64902
|
+
const expected = signExpiringUrl(secret, id, expMs);
|
|
64903
|
+
const a = Buffer.from(expected, "utf8");
|
|
64904
|
+
const b = Buffer.from(sig, "utf8");
|
|
64905
|
+
if (a.length !== b.length) return false;
|
|
64906
|
+
return (0, node_crypto.timingSafeEqual)(a, b);
|
|
64907
|
+
}
|
|
64908
|
+
function canonicalDeviceFingerprint(shape) {
|
|
64909
|
+
const features = [...new Set(shape.features)].toSorted();
|
|
64910
|
+
return require_canonical_hash.canonicalHash({
|
|
64911
|
+
deviceType: shape.deviceType,
|
|
64912
|
+
features
|
|
64913
|
+
});
|
|
64914
|
+
}
|
|
64915
|
+
function diffExportTargets(desired, lastAdvertised) {
|
|
64916
|
+
const toAdd = [];
|
|
64917
|
+
const toUpdate = [];
|
|
64918
|
+
const desiredIds = /* @__PURE__ */ new Set();
|
|
64919
|
+
for (const entry of desired) {
|
|
64920
|
+
desiredIds.add(entry.deviceId);
|
|
64921
|
+
const previous = lastAdvertised.get(entry.deviceId);
|
|
64922
|
+
if (previous === void 0) toAdd.push(entry);
|
|
64923
|
+
else if (previous.fingerprint !== entry.fingerprint) toUpdate.push(entry);
|
|
64924
|
+
}
|
|
64925
|
+
const toRemove = [];
|
|
64926
|
+
for (const deviceId of lastAdvertised.keys()) if (!desiredIds.has(deviceId)) toRemove.push(deviceId);
|
|
64927
|
+
return {
|
|
64928
|
+
toAdd,
|
|
64929
|
+
toUpdate,
|
|
64930
|
+
toRemove
|
|
64931
|
+
};
|
|
64932
|
+
}
|
|
64933
|
+
function resolveExportFingerprint(input) {
|
|
64934
|
+
if (input.ready) return input.fresh;
|
|
64935
|
+
return input.persisted ?? input.fresh;
|
|
64936
|
+
}
|
|
64693
64937
|
exports.ChildCostRegistry = ChildCostRegistry;
|
|
64694
64938
|
exports.FfmpegProcess = FfmpegProcess;
|
|
64695
64939
|
exports.FilesystemStorageProvider = FilesystemStorageProvider;
|
|
@@ -64700,6 +64944,7 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
64700
64944
|
exports.buildBinaryPath = buildBinaryPath;
|
|
64701
64945
|
exports.canonicalDeviceFingerprint = canonicalDeviceFingerprint;
|
|
64702
64946
|
exports.canonicalHash = require_canonical_hash.canonicalHash;
|
|
64947
|
+
exports.containsOrEquals = containsOrEquals;
|
|
64703
64948
|
exports.diffExportTargets = diffExportTargets;
|
|
64704
64949
|
exports.downloadBinary = downloadBinary;
|
|
64705
64950
|
exports.ensureBinary = ensureBinary;
|
|
@@ -64714,6 +64959,7 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
64714
64959
|
exports.nodeProcStatReader = nodeProcStatReader;
|
|
64715
64960
|
exports.parseProcCpuSeconds = parseProcCpuSeconds;
|
|
64716
64961
|
exports.parseProcRssBytes = parseProcRssBytes;
|
|
64962
|
+
exports.physicalRootOf = physicalRootOf;
|
|
64717
64963
|
exports.readProcessCost = readProcessCost;
|
|
64718
64964
|
exports.resolveExportFingerprint = resolveExportFingerprint;
|
|
64719
64965
|
exports.signExpiringUrl = signExpiringUrl;
|
|
@@ -64730,8 +64976,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
64730
64976
|
[Symbol.toStringTag]: { value: "Module" }
|
|
64731
64977
|
});
|
|
64732
64978
|
require_chunk_Cek0wNdY();
|
|
64733
|
-
var require_dist10 =
|
|
64734
|
-
var require_retired_settings_keys =
|
|
64979
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
64980
|
+
var require_retired_settings_keys = require_retired_settings_keys_Dd7K3K3r();
|
|
64735
64981
|
var node_crypto = __require("crypto");
|
|
64736
64982
|
var _camstack_types_node = require_node();
|
|
64737
64983
|
var JOB_HISTORY = 20;
|
|
@@ -70356,7 +70602,7 @@ var require_hub_forwarder = __commonJS({
|
|
|
70356
70602
|
[Symbol.toStringTag]: { value: "Module" }
|
|
70357
70603
|
});
|
|
70358
70604
|
require_chunk_Cek0wNdY();
|
|
70359
|
-
var require_dist10 =
|
|
70605
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
70360
70606
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
70361
70607
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
70362
70608
|
var HubForwarderDestination = class {
|
|
@@ -70493,7 +70739,7 @@ var require_liveness_monitor_addon = __commonJS({
|
|
|
70493
70739
|
"use strict";
|
|
70494
70740
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
70495
70741
|
require_chunk_Cek0wNdY();
|
|
70496
|
-
var require_dist10 =
|
|
70742
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
70497
70743
|
var NO_DEVICES = "liveness:no-devices";
|
|
70498
70744
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
70499
70745
|
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
@@ -70689,7 +70935,7 @@ var require_local_auth_addon = __commonJS({
|
|
|
70689
70935
|
[Symbol.toStringTag]: { value: "Module" }
|
|
70690
70936
|
});
|
|
70691
70937
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
70692
|
-
var require_dist10 =
|
|
70938
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
70693
70939
|
var node_crypto = __require("crypto");
|
|
70694
70940
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
70695
70941
|
var crypto$1 = __require("crypto");
|
|
@@ -78502,7 +78748,7 @@ var require_loki_logging = __commonJS({
|
|
|
78502
78748
|
[Symbol.toStringTag]: { value: "Module" }
|
|
78503
78749
|
});
|
|
78504
78750
|
require_chunk_Cek0wNdY();
|
|
78505
|
-
var require_dist10 =
|
|
78751
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
78506
78752
|
function sanitizeLabelName(raw) {
|
|
78507
78753
|
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
78508
78754
|
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
@@ -79067,7 +79313,7 @@ var require_native_metrics_addon = __commonJS({
|
|
|
79067
79313
|
[Symbol.toStringTag]: { value: "Module" }
|
|
79068
79314
|
});
|
|
79069
79315
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
79070
|
-
var require_dist10 =
|
|
79316
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
79071
79317
|
var node_fs_promises = __require("fs/promises");
|
|
79072
79318
|
var node_child_process = __require("child_process");
|
|
79073
79319
|
var node_util = __require("util");
|
|
@@ -81651,7 +81897,7 @@ var require_filesystem_storage_addon = __commonJS({
|
|
|
81651
81897
|
[Symbol.toStringTag]: { value: "Module" }
|
|
81652
81898
|
});
|
|
81653
81899
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
81654
|
-
var require_dist10 =
|
|
81900
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
81655
81901
|
var node_crypto = __require("crypto");
|
|
81656
81902
|
var node_fs_promises = __require("fs/promises");
|
|
81657
81903
|
var node_path = __require("path");
|
|
@@ -82767,8 +83013,8 @@ var require_sqlite_settings_addon = __commonJS({
|
|
|
82767
83013
|
[Symbol.toStringTag]: { value: "Module" }
|
|
82768
83014
|
});
|
|
82769
83015
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
82770
|
-
var require_dist10 =
|
|
82771
|
-
var require_retired_settings_keys =
|
|
83016
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
83017
|
+
var require_retired_settings_keys = require_retired_settings_keys_Dd7K3K3r();
|
|
82772
83018
|
var node_crypto = __require("crypto");
|
|
82773
83019
|
var node_fs = __require("fs");
|
|
82774
83020
|
var node_module = __require("module");
|
|
@@ -85258,10 +85504,11 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
85258
85504
|
[Symbol.toStringTag]: { value: "Module" }
|
|
85259
85505
|
});
|
|
85260
85506
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
85261
|
-
var require_dist10 =
|
|
85507
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
85262
85508
|
var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
|
|
85263
85509
|
var zod = require_zod();
|
|
85264
85510
|
var node_crypto = __require("crypto");
|
|
85511
|
+
var _camstack_types_node = require_node();
|
|
85265
85512
|
var node_fs_promises = __require("fs/promises");
|
|
85266
85513
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
85267
85514
|
var node_path = __require("path");
|
|
@@ -85269,6 +85516,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
85269
85516
|
var STORAGE_LOCATIONS = "storage_locations";
|
|
85270
85517
|
var NODE_ID_CONFIG_KEY = "__nodeId";
|
|
85271
85518
|
var ENABLED_CONFIG_KEY = "__enabled";
|
|
85519
|
+
var MODE_CONFIG_KEY = "__mode";
|
|
85272
85520
|
var STORAGE_LOCATIONS_COLUMNS = [
|
|
85273
85521
|
{
|
|
85274
85522
|
name: "id",
|
|
@@ -85399,6 +85647,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
85399
85647
|
const storedConfig = { ...loc.config };
|
|
85400
85648
|
if (loc.nodeId !== void 0) storedConfig[NODE_ID_CONFIG_KEY] = loc.nodeId;
|
|
85401
85649
|
if (loc.enabled !== void 0) storedConfig[ENABLED_CONFIG_KEY] = loc.enabled;
|
|
85650
|
+
if (loc.mode !== void 0) storedConfig[MODE_CONFIG_KEY] = loc.mode;
|
|
85402
85651
|
return {
|
|
85403
85652
|
id: loc.id,
|
|
85404
85653
|
type: loc.type,
|
|
@@ -85423,9 +85672,12 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
85423
85672
|
const nodeId = typeof nodeIdRaw === "string" && nodeIdRaw.length > 0 ? nodeIdRaw : void 0;
|
|
85424
85673
|
const enabledRaw = storedConfig[ENABLED_CONFIG_KEY];
|
|
85425
85674
|
const enabled = typeof enabledRaw === "boolean" ? enabledRaw : void 0;
|
|
85675
|
+
const parsedMode = require_dist10.StorageLocationModeSchema.safeParse(storedConfig[MODE_CONFIG_KEY]);
|
|
85676
|
+
const mode = parsedMode.success ? parsedMode.data : void 0;
|
|
85426
85677
|
const config = { ...storedConfig };
|
|
85427
85678
|
delete config[NODE_ID_CONFIG_KEY];
|
|
85428
85679
|
delete config[ENABLED_CONFIG_KEY];
|
|
85680
|
+
delete config[MODE_CONFIG_KEY];
|
|
85429
85681
|
return {
|
|
85430
85682
|
id,
|
|
85431
85683
|
type,
|
|
@@ -85434,6 +85686,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
85434
85686
|
config,
|
|
85435
85687
|
...nodeId !== void 0 ? { nodeId } : {},
|
|
85436
85688
|
...enabled !== void 0 ? { enabled } : {},
|
|
85689
|
+
...mode !== void 0 ? { mode } : {},
|
|
85437
85690
|
...require_dist10.LegacyStorageLocationDefaultSchema.parse({ isDefault: row["is_default"] === 1 || row["is_default"] === true }),
|
|
85438
85691
|
isSystem: row["is_system"] === 1 || row["is_system"] === true,
|
|
85439
85692
|
createdAt: Number(row["created_at"] ?? 0),
|
|
@@ -85764,7 +86017,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
85764
86017
|
const remaining = residue.filter((entry) => entry.storageClass === storageClass);
|
|
85765
86018
|
if (remaining.length === 0) throw new Error(`"${storageClass}" has nothing left outside its write set \u2014 there is nothing to drain. To CHANGE where it writes, use storageMigration.start with a new destination.`);
|
|
85766
86019
|
for (const entry of remaining) {
|
|
85767
|
-
|
|
86020
|
+
const target = this.deps.locations.getLocationById(entry.toLocationId);
|
|
86021
|
+
if (target !== void 0 && !require_dist10.mayWriteToLocation(target)) throw new Error(`The "${storageClass}" drain destination "${entry.toLocationId}" is not a write target (mode: ${require_dist10.resolveLocationMode(target)}) \u2014 it takes no writes, and a drain is the largest write there is. Set it active on the Storage screen first.`);
|
|
85768
86022
|
moves.push({
|
|
85769
86023
|
storageClass,
|
|
85770
86024
|
fromLocationId: entry.fromLocationId,
|
|
@@ -86097,12 +86351,9 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86097
86351
|
for (const move of job.moves) {
|
|
86098
86352
|
if (move.freezeSource !== true) continue;
|
|
86099
86353
|
const loc = this.deps.locations.getLocationById(move.fromLocationId);
|
|
86100
|
-
if (!loc || loc
|
|
86354
|
+
if (!loc || !require_dist10.mayWriteToLocation(loc)) continue;
|
|
86101
86355
|
const { createdAt: _c, updatedAt: _u, capacity: _cap, ...rest } = loc;
|
|
86102
|
-
this.deps.locations.upsertLocation(
|
|
86103
|
-
...rest,
|
|
86104
|
-
enabled: false
|
|
86105
|
-
});
|
|
86356
|
+
this.deps.locations.upsertLocation(require_dist10.withLocationMode(rest, "readonly"));
|
|
86106
86357
|
}
|
|
86107
86358
|
}
|
|
86108
86359
|
/**
|
|
@@ -86267,6 +86518,126 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86267
86518
|
declareCollection: async (input) => (await engine()).declareCollection(input)
|
|
86268
86519
|
};
|
|
86269
86520
|
}
|
|
86521
|
+
var STORAGE_DRAIN_STATE = "storage_drain_state";
|
|
86522
|
+
var STORAGE_DRAIN_STATE_COLUMNS = [
|
|
86523
|
+
{
|
|
86524
|
+
name: "location_id",
|
|
86525
|
+
type: "TEXT",
|
|
86526
|
+
primaryKey: true
|
|
86527
|
+
},
|
|
86528
|
+
{
|
|
86529
|
+
name: "started_at_ms",
|
|
86530
|
+
type: "INTEGER",
|
|
86531
|
+
notNull: true
|
|
86532
|
+
},
|
|
86533
|
+
{
|
|
86534
|
+
name: "start_bytes",
|
|
86535
|
+
type: "INTEGER",
|
|
86536
|
+
notNull: true
|
|
86537
|
+
},
|
|
86538
|
+
{
|
|
86539
|
+
name: "cap_bytes",
|
|
86540
|
+
type: "INTEGER",
|
|
86541
|
+
notNull: true
|
|
86542
|
+
},
|
|
86543
|
+
{
|
|
86544
|
+
name: "last_peer_bytes",
|
|
86545
|
+
type: "INTEGER"
|
|
86546
|
+
},
|
|
86547
|
+
{
|
|
86548
|
+
name: "last_sweep_ms",
|
|
86549
|
+
type: "INTEGER",
|
|
86550
|
+
notNull: true
|
|
86551
|
+
},
|
|
86552
|
+
{
|
|
86553
|
+
name: "observed_growth_bytes",
|
|
86554
|
+
type: "INTEGER",
|
|
86555
|
+
notNull: true,
|
|
86556
|
+
defaultValue: 0
|
|
86557
|
+
},
|
|
86558
|
+
{
|
|
86559
|
+
name: "observed_elapsed_ms",
|
|
86560
|
+
type: "INTEGER",
|
|
86561
|
+
notNull: true,
|
|
86562
|
+
defaultValue: 0
|
|
86563
|
+
}
|
|
86564
|
+
];
|
|
86565
|
+
function num(raw, fallback) {
|
|
86566
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : fallback;
|
|
86567
|
+
}
|
|
86568
|
+
function mapRow(row) {
|
|
86569
|
+
const locationId = String(row["location_id"] ?? "");
|
|
86570
|
+
if (!locationId) return null;
|
|
86571
|
+
const lastPeerRaw = row["last_peer_bytes"];
|
|
86572
|
+
return {
|
|
86573
|
+
locationId,
|
|
86574
|
+
startedAtMs: num(row["started_at_ms"], 0),
|
|
86575
|
+
startBytes: num(row["start_bytes"], 0),
|
|
86576
|
+
capBytes: num(row["cap_bytes"], 0),
|
|
86577
|
+
lastPeerBytes: typeof lastPeerRaw === "number" && Number.isFinite(lastPeerRaw) ? lastPeerRaw : null,
|
|
86578
|
+
lastSweepMs: num(row["last_sweep_ms"], 0),
|
|
86579
|
+
observedGrowthBytes: num(row["observed_growth_bytes"], 0),
|
|
86580
|
+
observedElapsedMs: num(row["observed_elapsed_ms"], 0)
|
|
86581
|
+
};
|
|
86582
|
+
}
|
|
86583
|
+
function mapState(state) {
|
|
86584
|
+
return {
|
|
86585
|
+
location_id: state.locationId,
|
|
86586
|
+
started_at_ms: state.startedAtMs,
|
|
86587
|
+
start_bytes: state.startBytes,
|
|
86588
|
+
cap_bytes: state.capBytes,
|
|
86589
|
+
last_peer_bytes: state.lastPeerBytes,
|
|
86590
|
+
last_sweep_ms: state.lastSweepMs,
|
|
86591
|
+
observed_growth_bytes: state.observedGrowthBytes,
|
|
86592
|
+
observed_elapsed_ms: state.observedElapsedMs
|
|
86593
|
+
};
|
|
86594
|
+
}
|
|
86595
|
+
var SqliteDrainStateStore = class {
|
|
86596
|
+
store;
|
|
86597
|
+
declared = false;
|
|
86598
|
+
constructor(store) {
|
|
86599
|
+
this.store = store;
|
|
86600
|
+
}
|
|
86601
|
+
async ensureDeclared() {
|
|
86602
|
+
if (this.declared) return;
|
|
86603
|
+
await this.store.declareCollection({
|
|
86604
|
+
collection: STORAGE_DRAIN_STATE,
|
|
86605
|
+
columns: STORAGE_DRAIN_STATE_COLUMNS
|
|
86606
|
+
});
|
|
86607
|
+
this.declared = true;
|
|
86608
|
+
}
|
|
86609
|
+
async load() {
|
|
86610
|
+
await this.ensureDeclared();
|
|
86611
|
+
const rows = await this.store.query({ collection: STORAGE_DRAIN_STATE });
|
|
86612
|
+
const out = [];
|
|
86613
|
+
for (const row of rows) {
|
|
86614
|
+
const mapped = mapRow(row.data);
|
|
86615
|
+
if (mapped) out.push(mapped);
|
|
86616
|
+
}
|
|
86617
|
+
return out;
|
|
86618
|
+
}
|
|
86619
|
+
async save(state) {
|
|
86620
|
+
await this.ensureDeclared();
|
|
86621
|
+
await this.store.deleteWhere({
|
|
86622
|
+
collection: STORAGE_DRAIN_STATE,
|
|
86623
|
+
filter: { where: { location_id: state.locationId } }
|
|
86624
|
+
});
|
|
86625
|
+
await this.store.insert({
|
|
86626
|
+
collection: STORAGE_DRAIN_STATE,
|
|
86627
|
+
record: {
|
|
86628
|
+
id: state.locationId,
|
|
86629
|
+
data: mapState(state)
|
|
86630
|
+
}
|
|
86631
|
+
});
|
|
86632
|
+
}
|
|
86633
|
+
async clear(locationId) {
|
|
86634
|
+
await this.ensureDeclared();
|
|
86635
|
+
await this.store.deleteWhere({
|
|
86636
|
+
collection: STORAGE_DRAIN_STATE,
|
|
86637
|
+
filter: { where: { location_id: locationId } }
|
|
86638
|
+
});
|
|
86639
|
+
}
|
|
86640
|
+
};
|
|
86270
86641
|
function nodeOwnsItsDataStore(env) {
|
|
86271
86642
|
const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
|
|
86272
86643
|
return raw === "" || raw === "hub";
|
|
@@ -86346,6 +86717,101 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86346
86717
|
}
|
|
86347
86718
|
return "empty";
|
|
86348
86719
|
}
|
|
86720
|
+
var DEFAULT_STALE_AFTER_MS = 6e4;
|
|
86721
|
+
var LocationOccupancySlice = class {
|
|
86722
|
+
deps;
|
|
86723
|
+
slice = /* @__PURE__ */ new Map();
|
|
86724
|
+
refreshing = null;
|
|
86725
|
+
lastRefreshMs = 0;
|
|
86726
|
+
constructor(deps) {
|
|
86727
|
+
this.deps = deps;
|
|
86728
|
+
}
|
|
86729
|
+
/**
|
|
86730
|
+
* What the last completed refresh saw for this location, or `undefined` when
|
|
86731
|
+
* nothing has reported for it. There is deliberately no fallback: unknown is
|
|
86732
|
+
* the truth about a location nobody owns bytes on.
|
|
86733
|
+
*/
|
|
86734
|
+
ownedFor(locationId) {
|
|
86735
|
+
return this.slice.get(locationId);
|
|
86736
|
+
}
|
|
86737
|
+
/**
|
|
86738
|
+
* Serve-and-revalidate. Called by every read: it returns immediately, and
|
|
86739
|
+
* kicks a background refresh when the slice is older than the window. A read
|
|
86740
|
+
* never awaits, and never joins a refresh already in flight (D224).
|
|
86741
|
+
*/
|
|
86742
|
+
serveAndRevalidate() {
|
|
86743
|
+
if (this.refreshing !== null) return;
|
|
86744
|
+
const staleAfter = this.deps.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
86745
|
+
if (this.lastRefreshMs !== 0 && this.deps.now() - this.lastRefreshMs < staleAfter) return;
|
|
86746
|
+
this.refresh();
|
|
86747
|
+
}
|
|
86748
|
+
/**
|
|
86749
|
+
* Ask every provider once and replace the slice.
|
|
86750
|
+
*
|
|
86751
|
+
* A provider that throws contributes NOTHING and is logged — it does not
|
|
86752
|
+
* contribute a zero, and it does not take the other providers' answers down
|
|
86753
|
+
* with it. A location that ends up with no contribution at all simply stays
|
|
86754
|
+
* out of the slice, which is what makes it read back as unknown.
|
|
86755
|
+
*/
|
|
86756
|
+
async refresh() {
|
|
86757
|
+
if (this.refreshing !== null) return this.refreshing;
|
|
86758
|
+
this.refreshing = this.runRefresh().finally(() => {
|
|
86759
|
+
this.refreshing = null;
|
|
86760
|
+
});
|
|
86761
|
+
return this.refreshing;
|
|
86762
|
+
}
|
|
86763
|
+
async runRefresh() {
|
|
86764
|
+
const locationIds = [...this.deps.locationIds()];
|
|
86765
|
+
if (locationIds.length === 0) {
|
|
86766
|
+
this.slice = /* @__PURE__ */ new Map();
|
|
86767
|
+
this.lastRefreshMs = this.deps.now();
|
|
86768
|
+
return;
|
|
86769
|
+
}
|
|
86770
|
+
const merged = /* @__PURE__ */ new Map();
|
|
86771
|
+
for (const provider of this.deps.getProviders()) {
|
|
86772
|
+
let reports;
|
|
86773
|
+
try {
|
|
86774
|
+
reports = await provider.getOccupancy({ locationIds });
|
|
86775
|
+
} catch (err) {
|
|
86776
|
+
this.deps.logger.warn("storage-orchestrator: a storage-occupancy provider failed \u2014 its bytes are MISSING from this refresh, not counted as zero", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
86777
|
+
continue;
|
|
86778
|
+
}
|
|
86779
|
+
for (const report of reports) {
|
|
86780
|
+
const previous = merged.get(report.locationId);
|
|
86781
|
+
merged.set(report.locationId, {
|
|
86782
|
+
bytes: (previous?.bytes ?? 0) + report.ownedBytes,
|
|
86783
|
+
measuredAtMs: previous === void 0 ? report.measuredAtMs : Math.min(previous.measuredAtMs, report.measuredAtMs)
|
|
86784
|
+
});
|
|
86785
|
+
}
|
|
86786
|
+
}
|
|
86787
|
+
this.slice = merged;
|
|
86788
|
+
this.lastRefreshMs = this.deps.now();
|
|
86789
|
+
}
|
|
86790
|
+
};
|
|
86791
|
+
function positiveNumber(raw) {
|
|
86792
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) return null;
|
|
86793
|
+
return raw;
|
|
86794
|
+
}
|
|
86795
|
+
function resolveMinFreePercent(config, providerDefaultPercent, capacity, notice) {
|
|
86796
|
+
const minFreeGb = positiveNumber(config["minFreeGb"]);
|
|
86797
|
+
const minFreePercent = positiveNumber(config["minFreePercent"]);
|
|
86798
|
+
const fallback = minFreePercent ?? providerDefaultPercent;
|
|
86799
|
+
if (minFreeGb === null) return fallback;
|
|
86800
|
+
if (minFreePercent !== null) notice("storage-orchestrator: location declares BOTH minFreeGb and minFreePercent \u2014 minFreeGb wins and minFreePercent is ignored. Remove one.", {
|
|
86801
|
+
minFreeGb,
|
|
86802
|
+
minFreePercent
|
|
86803
|
+
});
|
|
86804
|
+
const totalBytes = capacity?.totalBytes ?? 0;
|
|
86805
|
+
if (totalBytes <= 0) {
|
|
86806
|
+
notice("storage-orchestrator: minFreeGb cannot be converted \u2014 the volume total is unknown (remote node, or an unstattable path). Falling back to the percentage guard.", {
|
|
86807
|
+
minFreeGb,
|
|
86808
|
+
fallbackPercent: fallback
|
|
86809
|
+
});
|
|
86810
|
+
return fallback;
|
|
86811
|
+
}
|
|
86812
|
+
const percent = require_dist10.gbToBytes(minFreeGb) / totalBytes * 100;
|
|
86813
|
+
return Math.min(100, percent);
|
|
86814
|
+
}
|
|
86349
86815
|
var FRAMEWORK_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
86350
86816
|
"basePath",
|
|
86351
86817
|
"readOnly",
|
|
@@ -86413,7 +86879,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86413
86879
|
return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
|
|
86414
86880
|
}
|
|
86415
86881
|
function footageFrozen(location) {
|
|
86416
|
-
return location
|
|
86882
|
+
return require_dist10.evictionPolicyOfLocation(location) === "never";
|
|
86417
86883
|
}
|
|
86418
86884
|
var StorageCleanupCoordinator = class {
|
|
86419
86885
|
deps;
|
|
@@ -86566,6 +87032,66 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86566
87032
|
await this.deps.state.set(job);
|
|
86567
87033
|
}
|
|
86568
87034
|
};
|
|
87035
|
+
function basePathOf(row) {
|
|
87036
|
+
const raw = row.config["basePath"];
|
|
87037
|
+
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
87038
|
+
}
|
|
87039
|
+
function sameNode(a, b) {
|
|
87040
|
+
return (a.nodeId ?? "hub") === (b.nodeId ?? "hub");
|
|
87041
|
+
}
|
|
87042
|
+
function findRootCollision(next, existing, resolveRoot, storedSelf) {
|
|
87043
|
+
if (storedSelf !== void 0 && basePathOf(storedSelf) === basePathOf(next)) return { outcome: "clear" };
|
|
87044
|
+
const basePath = basePathOf(next);
|
|
87045
|
+
if (basePath === null) return { outcome: "clear" };
|
|
87046
|
+
const root = resolveRoot(basePath);
|
|
87047
|
+
if (!root.resolved) return {
|
|
87048
|
+
outcome: "unknown",
|
|
87049
|
+
unknown: {
|
|
87050
|
+
reason: "unresolvable",
|
|
87051
|
+
detail: `could not resolve "${basePath}" on this node`
|
|
87052
|
+
}
|
|
87053
|
+
};
|
|
87054
|
+
for (const other of existing) {
|
|
87055
|
+
if (other.id === next.id) continue;
|
|
87056
|
+
if (other.type !== next.type) continue;
|
|
87057
|
+
if (!sameNode(other, next)) continue;
|
|
87058
|
+
const otherPath = basePathOf(other);
|
|
87059
|
+
if (otherPath === null) continue;
|
|
87060
|
+
const otherRoot = resolveRoot(otherPath);
|
|
87061
|
+
if (!otherRoot.resolved) continue;
|
|
87062
|
+
if (root.key === otherRoot.key) return {
|
|
87063
|
+
outcome: "collides",
|
|
87064
|
+
collision: {
|
|
87065
|
+
otherId: other.id,
|
|
87066
|
+
type: next.type,
|
|
87067
|
+
sharedRoot: root.key,
|
|
87068
|
+
kind: "equal"
|
|
87069
|
+
}
|
|
87070
|
+
};
|
|
87071
|
+
if ((0, _camstack_types_node.containsOrEquals)(otherRoot.key, root.key)) return {
|
|
87072
|
+
outcome: "collides",
|
|
87073
|
+
collision: {
|
|
87074
|
+
otherId: other.id,
|
|
87075
|
+
type: next.type,
|
|
87076
|
+
sharedRoot: otherRoot.key,
|
|
87077
|
+
kind: "nested"
|
|
87078
|
+
}
|
|
87079
|
+
};
|
|
87080
|
+
if ((0, _camstack_types_node.containsOrEquals)(root.key, otherRoot.key)) return {
|
|
87081
|
+
outcome: "collides",
|
|
87082
|
+
collision: {
|
|
87083
|
+
otherId: other.id,
|
|
87084
|
+
type: next.type,
|
|
87085
|
+
sharedRoot: root.key,
|
|
87086
|
+
kind: "nested"
|
|
87087
|
+
}
|
|
87088
|
+
};
|
|
87089
|
+
}
|
|
87090
|
+
return { outcome: "clear" };
|
|
87091
|
+
}
|
|
87092
|
+
function rootCollisionMessage(id, collision) {
|
|
87093
|
+
return `Cannot save "${id}": it is ${collision.kind === "equal" ? `the same directory as "${collision.otherId}"` : `nested with "${collision.otherId}" (one is inside the other's tree)`}, and both are type "${collision.type}". Two locations of one type on the same disk share a class subtree, so every per-location figure double-counts and a deletion under one is a deletion under the other. Shared root: ${collision.sharedRoot}. Two locations of DIFFERENT types may share a root \u2014 that is how recordings and recordingsLow are configured by default.`;
|
|
87094
|
+
}
|
|
86569
87095
|
var SESSION_VOCABULARY = {
|
|
86570
87096
|
upload: {
|
|
86571
87097
|
idLabel: "uploadId",
|
|
@@ -86691,38 +87217,41 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86691
87217
|
this.migrateOntoTheWriteSet(rows);
|
|
86692
87218
|
}
|
|
86693
87219
|
/**
|
|
86694
|
-
* ONE explicit migration onto the
|
|
86695
|
-
*
|
|
87220
|
+
* ONE explicit migration onto the four-state model (D385), run once per boot
|
|
87221
|
+
* against the rows that came off disk. Idempotent: the second boot finds
|
|
86696
87222
|
* nothing to do.
|
|
86697
87223
|
*
|
|
86698
87224
|
* Two steps, in order, and each one is a WRITE — the point is that "absent"
|
|
86699
87225
|
* stops existing rather than being re-interpreted on every read forever:
|
|
86700
87226
|
*
|
|
86701
|
-
* 1. A
|
|
86702
|
-
* `
|
|
86703
|
-
*
|
|
86704
|
-
*
|
|
86705
|
-
*
|
|
86706
|
-
*
|
|
87227
|
+
* 1. A row with no `mode` is stamped with the mode its legacy flags imply
|
|
87228
|
+
* (`legacyModeOf`): `config.readOnly === true` → `readonly` and the
|
|
87229
|
+
* config key is DELETED; `enabled === false` → `readonly` (the
|
|
87230
|
+
* CONSERVATIVE direction — a state change must never start deleting
|
|
87231
|
+
* footage on its own); everything else → `active`. `withLocationMode`
|
|
87232
|
+
* derives `enabled` from the mode, so the two can never disagree, and
|
|
87233
|
+
* this subsumes the D383 pass that stamped flagless rows `enabled: true`.
|
|
87234
|
+
* 2. A type left with NO write target — only reachable from a hand-edited
|
|
87235
|
+
* store or a row an older build wrote — gets one made `active`, chosen as
|
|
86707
87236
|
* the row the RETIRED default flag named, else the `:default`-slugged
|
|
86708
87237
|
* row, else the lexicographically first. Loudly: a class with no write
|
|
86709
87238
|
* target records nowhere, and refusing to repair it here would leave the
|
|
86710
87239
|
* hub silently unable to write with no operator action pending.
|
|
86711
87240
|
*
|
|
86712
|
-
* An operator's explicit `
|
|
86713
|
-
* step 2 touches it, and only when the alternative is a class
|
|
86714
|
-
* write at all.
|
|
87241
|
+
* An operator's explicit `readonly`/`drain`/`disabled` is never reversed by
|
|
87242
|
+
* step 1 — only step 2 touches it, and only when the alternative is a class
|
|
87243
|
+
* that cannot write at all.
|
|
87244
|
+
*
|
|
87245
|
+
* On the live hub every location is enabled with no `readOnly`, so step 1 is
|
|
87246
|
+
* a no-op that stamps `active` and nothing else changes.
|
|
86715
87247
|
*/
|
|
86716
87248
|
migrateOntoTheWriteSet(rows) {
|
|
86717
87249
|
const legacyDefaultOfType = /* @__PURE__ */ new Map();
|
|
86718
87250
|
for (const row of rows) if (row.isDefault === true) legacyDefaultOfType.set(row.type, row.id);
|
|
86719
87251
|
const stamped = [];
|
|
86720
87252
|
for (const loc of this.locations.values()) {
|
|
86721
|
-
if (loc.
|
|
86722
|
-
this.persistMigrated(
|
|
86723
|
-
...loc,
|
|
86724
|
-
enabled: true
|
|
86725
|
-
});
|
|
87253
|
+
if (loc.mode !== void 0) continue;
|
|
87254
|
+
this.persistMigrated(require_dist10.withLocationMode(loc, require_dist10.legacyModeOf(loc) ?? "active"));
|
|
86726
87255
|
stamped.push(loc.id);
|
|
86727
87256
|
}
|
|
86728
87257
|
const repaired = [];
|
|
@@ -86732,13 +87261,10 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86732
87261
|
const legacyDefault = legacyDefaultOfType.get(type);
|
|
86733
87262
|
const pick = ofType.find((l) => l.id === legacyDefault) ?? ofType.find((l) => l.id === `${type}:default`) ?? ofType.toSorted((a, b) => a.id.localeCompare(b.id))[0];
|
|
86734
87263
|
if (!pick) continue;
|
|
86735
|
-
this.persistMigrated(
|
|
86736
|
-
...pick,
|
|
86737
|
-
enabled: true
|
|
86738
|
-
});
|
|
87264
|
+
this.persistMigrated(require_dist10.withLocationMode(pick, "active"));
|
|
86739
87265
|
repaired.push(pick.id);
|
|
86740
87266
|
}
|
|
86741
|
-
if (stamped.length > 0 || repaired.length > 0) this.logger.info("storage-orchestrator: migrated locations onto the
|
|
87267
|
+
if (stamped.length > 0 || repaired.length > 0) this.logger.info("storage-orchestrator: migrated locations onto the state model", { meta: {
|
|
86742
87268
|
stamped,
|
|
86743
87269
|
repaired
|
|
86744
87270
|
} });
|
|
@@ -86754,7 +87280,7 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86754
87280
|
const store = this.locationStore;
|
|
86755
87281
|
if (!store) return;
|
|
86756
87282
|
store.upsert(withStamp).catch((err) => {
|
|
86757
|
-
this.logger.error("storage-orchestrator:
|
|
87283
|
+
this.logger.error("storage-orchestrator: state-model migration persist failed", { meta: {
|
|
86758
87284
|
id: withStamp.id,
|
|
86759
87285
|
error: err instanceof Error ? err.message : String(err)
|
|
86760
87286
|
} });
|
|
@@ -86896,12 +87422,11 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
86896
87422
|
for (const [id, enabled] of enabledByLocationId) {
|
|
86897
87423
|
const location = nextById.get(id);
|
|
86898
87424
|
if (!location) throw new Error(`Storage location "${id}" not found`);
|
|
86899
|
-
if (location
|
|
86900
|
-
const updated = {
|
|
87425
|
+
if (require_dist10.isLocationEnabled(location) === enabled) continue;
|
|
87426
|
+
const updated = require_dist10.withLocationMode({
|
|
86901
87427
|
...location,
|
|
86902
|
-
enabled,
|
|
86903
87428
|
updatedAt: now
|
|
86904
|
-
};
|
|
87429
|
+
}, enabled ? "active" : "readonly");
|
|
86905
87430
|
nextById.set(id, updated);
|
|
86906
87431
|
touched.set(id, updated);
|
|
86907
87432
|
}
|
|
@@ -87006,22 +87531,19 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87006
87531
|
...input,
|
|
87007
87532
|
nodeId: "hub"
|
|
87008
87533
|
};
|
|
87009
|
-
input
|
|
87010
|
-
|
|
87011
|
-
...resolveEnabled(input, existing, this.hasAnyLocationOfType(input.type))
|
|
87012
|
-
};
|
|
87013
|
-
const next = {
|
|
87534
|
+
this.refuseSameTypeRootCollision(input, existing);
|
|
87535
|
+
const next = require_dist10.withLocationMode({
|
|
87014
87536
|
...input,
|
|
87015
87537
|
createdAt: existing?.createdAt ?? now,
|
|
87016
87538
|
updatedAt: now
|
|
87017
|
-
};
|
|
87539
|
+
}, resolveUpsertMode(input, existing, this.hasAnyLocationOfType(input.type)));
|
|
87018
87540
|
refuseLastWriteTargetDisabled(existing, next, this.locations.values());
|
|
87019
87541
|
this.locations.set(next.id, next);
|
|
87020
87542
|
this.logger.debug("storage-orchestrator: upsertLocation", { meta: {
|
|
87021
87543
|
id: next.id,
|
|
87022
87544
|
type: next.type,
|
|
87023
87545
|
providerId: next.providerId,
|
|
87024
|
-
|
|
87546
|
+
mode: next.mode
|
|
87025
87547
|
} });
|
|
87026
87548
|
if (this.locationStore) {
|
|
87027
87549
|
const store = this.locationStore;
|
|
@@ -87050,8 +87572,8 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87050
87572
|
if (!loc) throw new Error(`Storage location "${id}" not found`);
|
|
87051
87573
|
const ofType = [...this.locations.values()].filter((l) => l.type === loc.type);
|
|
87052
87574
|
if (ofType.length <= 1) throw new Error(`Cannot delete "${id}" \u2014 it is the only location for type "${loc.type}"`);
|
|
87053
|
-
const enabledOfType = ofType.filter((l) => l
|
|
87054
|
-
if (loc
|
|
87575
|
+
const enabledOfType = ofType.filter((l) => require_dist10.mayWriteToLocation(l));
|
|
87576
|
+
if (require_dist10.mayWriteToLocation(loc) && enabledOfType.length <= 1) throw new Error(`Cannot delete "${id}" \u2014 it is the only enabled location for type "${loc.type}"`);
|
|
87055
87577
|
const occupancy = await this.probeOccupancy(loc);
|
|
87056
87578
|
if (occupancy !== "empty") {
|
|
87057
87579
|
if (options?.force !== true) throw new Error(occupancy === "occupied" ? `Storage location "${id}" still holds data \u2014 drain it first (storage migration / relocate), or pass force to delete the record anyway and strand what is on it` : `Storage location "${id}" could not be checked for remaining data (unreachable provider, another node, or an unmounted root) \u2014 pass force to delete the record anyway`);
|
|
@@ -87292,29 +87814,57 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87292
87814
|
basePath: input.basePath
|
|
87293
87815
|
} });
|
|
87294
87816
|
}
|
|
87817
|
+
/**
|
|
87818
|
+
* The same-type physical-root refusal (D389).
|
|
87819
|
+
*
|
|
87820
|
+
* An unresolvable root — another node's disk, a remote provider, a path that
|
|
87821
|
+
* does not exist yet — WARNS and lets the write through. Refusing on a read
|
|
87822
|
+
* that could not be taken blocks the operator on a doubt, and the recorder's
|
|
87823
|
+
* demotion still catches what slips past (D49's spirit).
|
|
87824
|
+
*/
|
|
87825
|
+
refuseSameTypeRootCollision(input, existing) {
|
|
87826
|
+
const verdict = findRootCollision(input, this.locations.values(), this.resolveRoot, existing);
|
|
87827
|
+
if (verdict.outcome === "clear") return;
|
|
87828
|
+
if (verdict.outcome === "unknown") {
|
|
87829
|
+
this.logger.warn("storage-orchestrator: could not check this location for a same-type root collision \u2014 saving it anyway. A read that fails is not a refusal; the recorder demotes a same-root duplicate it later finds.", { meta: {
|
|
87830
|
+
id: input.id,
|
|
87831
|
+
type: input.type,
|
|
87832
|
+
...verdict.unknown
|
|
87833
|
+
} });
|
|
87834
|
+
return;
|
|
87835
|
+
}
|
|
87836
|
+
throw new Error(rootCollisionMessage(input.id, verdict.collision));
|
|
87837
|
+
}
|
|
87838
|
+
/** Injectable so a test can drive symlinks and unresolvable paths without
|
|
87839
|
+
* touching a real filesystem. Production takes the shared derivation. */
|
|
87840
|
+
resolveRoot = (basePath) => (0, _camstack_types_node.physicalRootOf)(basePath);
|
|
87841
|
+
/** Test seam for {@link refuseSameTypeRootCollision}. */
|
|
87842
|
+
setRootResolver(resolveRoot) {
|
|
87843
|
+
this.resolveRoot = resolveRoot;
|
|
87844
|
+
}
|
|
87295
87845
|
/** Internal: check whether any location of the given type exists. */
|
|
87296
87846
|
hasAnyLocationOfType(type) {
|
|
87297
87847
|
for (const loc of this.locations.values()) if (loc.type === type) return true;
|
|
87298
87848
|
return false;
|
|
87299
87849
|
}
|
|
87300
87850
|
};
|
|
87301
|
-
function
|
|
87302
|
-
|
|
87303
|
-
|
|
87304
|
-
if (existing) return {};
|
|
87305
|
-
return { enabled: !typeAlreadyHasLocation };
|
|
87851
|
+
function resolveUpsertMode(input, existing, typeAlreadyHasLocation) {
|
|
87852
|
+
if (input.mode !== void 0) {
|
|
87853
|
+
if (input.enabled === void 0 || input.enabled === require_dist10.modeMayWrite(input.mode)) return input.mode;
|
|
87306
87854
|
}
|
|
87307
|
-
return
|
|
87855
|
+
if (input.enabled !== void 0) return input.enabled ? "active" : "readonly";
|
|
87856
|
+
if (existing) return require_dist10.resolveLocationMode(existing);
|
|
87857
|
+
return typeAlreadyHasLocation ? "readonly" : "active";
|
|
87308
87858
|
}
|
|
87309
87859
|
var OPTIONAL_WRITE_TYPES = /* @__PURE__ */ new Set(["backups"]);
|
|
87310
87860
|
function refuseLastWriteTargetDisabled(existing, next, locations) {
|
|
87311
87861
|
if (OPTIONAL_WRITE_TYPES.has(next.type)) return;
|
|
87312
|
-
if (next
|
|
87862
|
+
if (require_dist10.mayWriteToLocation(next)) return;
|
|
87313
87863
|
if (existing === void 0) return;
|
|
87314
|
-
if (existing
|
|
87864
|
+
if (!require_dist10.mayWriteToLocation(existing)) return;
|
|
87315
87865
|
for (const loc of locations) {
|
|
87316
87866
|
if (loc.id === next.id || loc.type !== next.type) continue;
|
|
87317
|
-
if (loc
|
|
87867
|
+
if (require_dist10.mayWriteToLocation(loc)) return;
|
|
87318
87868
|
}
|
|
87319
87869
|
throw new Error(`Cannot disable "${next.id}": it is the last enabled location of type "${next.type}". Enable another location of this type first.`);
|
|
87320
87870
|
}
|
|
@@ -87328,10 +87878,70 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87328
87878
|
throw new Error(`Refusing the write-set change: it would leave type "${type}" with no enabled location. Enable one of ${ofType.map((loc) => loc.id).toSorted().join(", ")} first.`);
|
|
87329
87879
|
}
|
|
87330
87880
|
}
|
|
87881
|
+
function beginDrain(observation) {
|
|
87882
|
+
return {
|
|
87883
|
+
locationId: observation.locationId,
|
|
87884
|
+
startedAtMs: observation.atMs,
|
|
87885
|
+
startBytes: observation.usedBytes,
|
|
87886
|
+
capBytes: observation.usedBytes,
|
|
87887
|
+
lastPeerBytes: observation.peerUsedBytes,
|
|
87888
|
+
lastSweepMs: observation.atMs,
|
|
87889
|
+
observedGrowthBytes: 0,
|
|
87890
|
+
observedElapsedMs: 0
|
|
87891
|
+
};
|
|
87892
|
+
}
|
|
87893
|
+
function stepDrain(previous, observation) {
|
|
87894
|
+
if (previous === null) return {
|
|
87895
|
+
state: beginDrain(observation),
|
|
87896
|
+
capBytes: null,
|
|
87897
|
+
drained: observation.usedBytes <= 0,
|
|
87898
|
+
growthBytes: null
|
|
87899
|
+
};
|
|
87900
|
+
const growthBytes = previous.lastPeerBytes !== null && observation.peerUsedBytes !== null ? observation.peerUsedBytes - previous.lastPeerBytes : null;
|
|
87901
|
+
const ceded = growthBytes !== null && growthBytes > 0 ? growthBytes : 0;
|
|
87902
|
+
const nextCap = Math.max(0, Math.min(previous.capBytes, observation.usedBytes) - ceded);
|
|
87903
|
+
const state = {
|
|
87904
|
+
...previous,
|
|
87905
|
+
capBytes: nextCap,
|
|
87906
|
+
lastPeerBytes: observation.peerUsedBytes ?? previous.lastPeerBytes,
|
|
87907
|
+
lastSweepMs: observation.atMs,
|
|
87908
|
+
observedGrowthBytes: previous.observedGrowthBytes + ceded,
|
|
87909
|
+
observedElapsedMs: growthBytes === null ? previous.observedElapsedMs : previous.observedElapsedMs + Math.max(0, observation.atMs - previous.lastSweepMs)
|
|
87910
|
+
};
|
|
87911
|
+
if (observation.usedBytes <= 0) return {
|
|
87912
|
+
state: {
|
|
87913
|
+
...state,
|
|
87914
|
+
capBytes: 0
|
|
87915
|
+
},
|
|
87916
|
+
capBytes: null,
|
|
87917
|
+
drained: true,
|
|
87918
|
+
growthBytes
|
|
87919
|
+
};
|
|
87920
|
+
return {
|
|
87921
|
+
state,
|
|
87922
|
+
capBytes: nextCap,
|
|
87923
|
+
drained: false,
|
|
87924
|
+
growthBytes
|
|
87925
|
+
};
|
|
87926
|
+
}
|
|
87927
|
+
function drainProgressOf(state, usedBytes, nowMs) {
|
|
87928
|
+
const bytesRemaining = Math.max(0, usedBytes);
|
|
87929
|
+
const rateBytesPerMs = state.observedGrowthBytes > 0 && state.observedElapsedMs > 0 ? state.observedGrowthBytes / state.observedElapsedMs : null;
|
|
87930
|
+
return {
|
|
87931
|
+
locationId: state.locationId,
|
|
87932
|
+
startedAtMs: state.startedAtMs,
|
|
87933
|
+
startBytes: state.startBytes,
|
|
87934
|
+
bytesRemaining,
|
|
87935
|
+
drained: bytesRemaining <= 0,
|
|
87936
|
+
estimatedEmptyAtMs: rateBytesPerMs === null || bytesRemaining <= 0 ? null : nowMs + Math.ceil(bytesRemaining / rateBytesPerMs)
|
|
87937
|
+
};
|
|
87938
|
+
}
|
|
87331
87939
|
var DEFAULT_MAX_ROUNDS = 8;
|
|
87332
87940
|
var DEFAULT_HYSTERESIS_PERCENT = 1;
|
|
87333
87941
|
var StoragePressureManager = class {
|
|
87334
87942
|
deps;
|
|
87943
|
+
/** Last sweep's drain progress, by location. Read-only surface for the UI. */
|
|
87944
|
+
drainProgress = /* @__PURE__ */ new Map();
|
|
87335
87945
|
constructor(deps) {
|
|
87336
87946
|
this.deps = deps;
|
|
87337
87947
|
}
|
|
@@ -87373,6 +87983,73 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87373
87983
|
} });
|
|
87374
87984
|
}
|
|
87375
87985
|
}
|
|
87986
|
+
await this.sweepDrains();
|
|
87987
|
+
}
|
|
87988
|
+
/**
|
|
87989
|
+
* The drain pass (D386). A `drain` location is excluded from nothing — it is
|
|
87990
|
+
* given a monotonically descending effective usage cap and handed to the SAME
|
|
87991
|
+
* `relieveToUsageCap` machinery every other cap uses. There is no second
|
|
87992
|
+
* eviction path, and no operator-facing rate.
|
|
87993
|
+
*/
|
|
87994
|
+
async sweepDrains() {
|
|
87995
|
+
const plan = await this.deps.getDrainPlan?.() ?? [];
|
|
87996
|
+
const store = this.deps.drainState;
|
|
87997
|
+
if (store === void 0) {
|
|
87998
|
+
if (plan.length > 0) this.deps.logger.warn("storage drain: locations are draining but no durable state store is wired \u2014 the ratchet stays inert rather than restarting from scratch every boot", { meta: { locationIds: plan.map((e) => e.locationId) } });
|
|
87999
|
+
this.drainProgress = /* @__PURE__ */ new Map();
|
|
88000
|
+
return;
|
|
88001
|
+
}
|
|
88002
|
+
const persisted = new Map((await store.load()).map((state) => [state.locationId, state]));
|
|
88003
|
+
const draining = new Set(plan.map((entry) => entry.locationId));
|
|
88004
|
+
for (const locationId of persisted.keys()) {
|
|
88005
|
+
if (draining.has(locationId)) continue;
|
|
88006
|
+
await store.clear(locationId);
|
|
88007
|
+
this.drainProgress.delete(locationId);
|
|
88008
|
+
this.deps.logger.info("storage drain: stopped \u2014 location is no longer draining", { meta: { locationId } });
|
|
88009
|
+
}
|
|
88010
|
+
const nowMs = Date.now();
|
|
88011
|
+
const progress = /* @__PURE__ */ new Map();
|
|
88012
|
+
for (const entry of plan) try {
|
|
88013
|
+
const usedBytes = await this.evictableUsageOf(entry.locationId);
|
|
88014
|
+
const peerUsedBytes = entry.activePeerLocationIds.length === 0 ? null : await this.evictableUsageOfAll(entry.activePeerLocationIds);
|
|
88015
|
+
const step = stepDrain(persisted.get(entry.locationId) ?? null, {
|
|
88016
|
+
locationId: entry.locationId,
|
|
88017
|
+
usedBytes,
|
|
88018
|
+
peerUsedBytes,
|
|
88019
|
+
atMs: nowMs
|
|
88020
|
+
});
|
|
88021
|
+
await store.save(step.state);
|
|
88022
|
+
this.deps.logger.info("storage drain: sweep", { meta: {
|
|
88023
|
+
locationId: entry.locationId,
|
|
88024
|
+
usedBytes,
|
|
88025
|
+
peerUsedBytes,
|
|
88026
|
+
growthBytes: step.growthBytes,
|
|
88027
|
+
capBytes: step.capBytes,
|
|
88028
|
+
drained: step.drained
|
|
88029
|
+
} });
|
|
88030
|
+
if (step.capBytes !== null) await this.relieveToUsageCap(entry.locationId, step.capBytes);
|
|
88031
|
+
progress.set(entry.locationId, drainProgressOf(step.state, await this.evictableUsageOf(entry.locationId), nowMs));
|
|
88032
|
+
} catch (err) {
|
|
88033
|
+
this.deps.logger.warn("storage drain: sweep failed for location", { meta: {
|
|
88034
|
+
locationId: entry.locationId,
|
|
88035
|
+
error: err instanceof Error ? err.message : String(err)
|
|
88036
|
+
} });
|
|
88037
|
+
}
|
|
88038
|
+
this.drainProgress = progress;
|
|
88039
|
+
}
|
|
88040
|
+
/** Progress for every draining location, as of the last sweep. The read a UI
|
|
88041
|
+
* asks for — it never triggers work of its own. */
|
|
88042
|
+
drainProgressSnapshot() {
|
|
88043
|
+
return [...this.drainProgress.values()];
|
|
88044
|
+
}
|
|
88045
|
+
/** Evictable bytes on one location, summed across every provider. */
|
|
88046
|
+
async evictableUsageOf(locationId) {
|
|
88047
|
+
const providers = this.deps.getEvictableProviders();
|
|
88048
|
+
return (await Promise.all(providers.map(async (p) => (await p.getEvictableUsage({ locationId })).bytes))).reduce((sum, bytes) => sum + bytes, 0);
|
|
88049
|
+
}
|
|
88050
|
+
/** Evictable bytes across a set of locations. */
|
|
88051
|
+
async evictableUsageOfAll(locationIds) {
|
|
88052
|
+
return (await Promise.all(locationIds.map((id) => this.evictableUsageOf(id)))).reduce((sum, bytes) => sum + bytes, 0);
|
|
87376
88053
|
}
|
|
87377
88054
|
/** Evict oldest-first until the location's evictable usage fits `maxBytes`. */
|
|
87378
88055
|
async relieveToUsageCap(locationId, maxBytes) {
|
|
@@ -87466,12 +88143,31 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87466
88143
|
var STORAGE_PROVIDER_CAP_NAME = "storage-provider";
|
|
87467
88144
|
var DATA_STORE_PROVIDER_CAP_NAME = "data-store-provider";
|
|
87468
88145
|
var STORAGE_EVICTABLE_CAP_NAME = "storage-evictable";
|
|
88146
|
+
var STORAGE_OCCUPANCY_CAP_NAME = "storage-occupancy";
|
|
87469
88147
|
var HUB_NODE_ID = "hub";
|
|
87470
88148
|
var PRESSURE_SWEEP_INTERVAL_MS = 6e4;
|
|
87471
88149
|
var CAP_PROVIDER_REGISTERED_CATEGORY = "capability:provider-registered";
|
|
87472
88150
|
var StorageOrchestratorAddon = class extends require_dist10.BaseAddon {
|
|
87473
88151
|
service = null;
|
|
87474
88152
|
pressureManager = null;
|
|
88153
|
+
/**
|
|
88154
|
+
* How much of each location CamStack itself holds (D388).
|
|
88155
|
+
*
|
|
88156
|
+
* Built on first use rather than in a field initializer: `this.ctx` does not
|
|
88157
|
+
* exist until `initialize()` runs, and a field initializer touching it throws
|
|
88158
|
+
* in the constructor.
|
|
88159
|
+
*/
|
|
88160
|
+
occupancySlice = null;
|
|
88161
|
+
get occupancy() {
|
|
88162
|
+
if (this.occupancySlice) return this.occupancySlice;
|
|
88163
|
+
this.occupancySlice = new LocationOccupancySlice({
|
|
88164
|
+
logger: this.ctx.logger,
|
|
88165
|
+
getProviders: () => this.ctx.kernel.capabilityRegistry?.getCollection(STORAGE_OCCUPANCY_CAP_NAME) ?? [],
|
|
88166
|
+
locationIds: () => this.service?.listLocations().map((loc) => loc.id) ?? [],
|
|
88167
|
+
now: () => Date.now()
|
|
88168
|
+
});
|
|
88169
|
+
return this.occupancySlice;
|
|
88170
|
+
}
|
|
87475
88171
|
pressureTimer = null;
|
|
87476
88172
|
pressureSweepInFlight = false;
|
|
87477
88173
|
migration = null;
|
|
@@ -87530,12 +88226,15 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87530
88226
|
const provider = {
|
|
87531
88227
|
listLocations: async ({ type }) => {
|
|
87532
88228
|
const rows = type !== void 0 ? service.listLocations({ type }) : service.listLocations();
|
|
88229
|
+
this.occupancy.serveAndRevalidate();
|
|
87533
88230
|
return Promise.all(rows.map(async (loc) => ({
|
|
87534
88231
|
...this.redacted(loc),
|
|
88232
|
+
...this.occupancy.ownedFor(loc.id) !== void 0 ? { owned: this.occupancy.ownedFor(loc.id) } : {},
|
|
87535
88233
|
capacity: require_dist10.isLocationEnabled(loc) ? await this.localCapacityOf(loc) : null
|
|
87536
88234
|
})));
|
|
87537
88235
|
},
|
|
87538
88236
|
listLocationDeclarations: async () => service.listDeclarations(),
|
|
88237
|
+
listDrainProgress: async () => this.drainProgress(),
|
|
87539
88238
|
upsertLocation: async (input) => {
|
|
87540
88239
|
const stored = service.getLocationById(input.id);
|
|
87541
88240
|
const config = restoreRedactedSecrets(input.config, stored?.config, this.secretKeysFor(input.providerId, stored?.config ?? {}));
|
|
@@ -87543,10 +88242,13 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87543
88242
|
...input,
|
|
87544
88243
|
config
|
|
87545
88244
|
});
|
|
88245
|
+
this.announceLocationsChanged(saved.id, saved.type, "upsert");
|
|
87546
88246
|
return this.redacted(saved);
|
|
87547
88247
|
},
|
|
87548
88248
|
deleteLocation: async ({ id, force }) => {
|
|
88249
|
+
const doomed = service.getLocationById(id);
|
|
87549
88250
|
await service.deleteLocation(id, { force: force === true });
|
|
88251
|
+
this.announceLocationsChanged(id, doomed?.type ?? "", "delete");
|
|
87550
88252
|
},
|
|
87551
88253
|
testLocation: async ({ id }) => {
|
|
87552
88254
|
const loc = service.getLocationById(id);
|
|
@@ -87807,7 +88509,17 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87807
88509
|
getEvictableProviders: () => this.ctx.kernel.capabilityRegistry?.getCollection(STORAGE_EVICTABLE_CAP_NAME) ?? [],
|
|
87808
88510
|
getLocationThresholds: () => this.resolveLocationThresholds(),
|
|
87809
88511
|
getLocationMaxUsedBytes: () => this.resolveLocationMaxUsedBytes(),
|
|
87810
|
-
getLocationDeviceKey: (id) => this.locationDeviceKey(id)
|
|
88512
|
+
getLocationDeviceKey: (id) => this.locationDeviceKey(id),
|
|
88513
|
+
getDrainPlan: () => Promise.resolve(this.resolveDrainPlan()),
|
|
88514
|
+
drainState: {
|
|
88515
|
+
load: async () => await this.resolveDrainStore()?.load() ?? [],
|
|
88516
|
+
save: async (state) => {
|
|
88517
|
+
await this.resolveDrainStore()?.save(state);
|
|
88518
|
+
},
|
|
88519
|
+
clear: async (locationId) => {
|
|
88520
|
+
await this.resolveDrainStore()?.clear(locationId);
|
|
88521
|
+
}
|
|
88522
|
+
}
|
|
87811
88523
|
});
|
|
87812
88524
|
this.pressureTimer = setInterval(() => {
|
|
87813
88525
|
this.runPressureSweep();
|
|
@@ -87957,14 +88669,64 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87957
88669
|
});
|
|
87958
88670
|
const byProvider = new Map(infos.map((i) => [i.providerId, i]));
|
|
87959
88671
|
for (const loc of svc.listLocations()) {
|
|
88672
|
+
if (!require_dist10.mayReadLocation(loc)) continue;
|
|
87960
88673
|
const info = byProvider.get(loc.providerId);
|
|
87961
88674
|
if (!info || !info.shouldSaveDiskSpace) continue;
|
|
87962
|
-
const
|
|
87963
|
-
const threshold = typeof override === "number" && Number.isFinite(override) && override >= 0 ? override : info.minFreePercent ?? 0;
|
|
88675
|
+
const threshold = resolveMinFreePercent(loc.config, info.minFreePercent ?? 0, await this.localCapacityOf(loc), (message, meta) => this.logMinFreeRedundancyOnce(loc.id, message, meta));
|
|
87964
88676
|
if (threshold > 0) out.set(loc.id, threshold);
|
|
87965
88677
|
}
|
|
87966
88678
|
return out;
|
|
87967
88679
|
}
|
|
88680
|
+
/** Location ids that have already logged the `minFreeGb` / `minFreePercent`
|
|
88681
|
+
* redundancy. The pressure sweep runs on a timer; the notice is worth
|
|
88682
|
+
* exactly one line per location per process, not one per minute. */
|
|
88683
|
+
minFreeRedundancyLogged = /* @__PURE__ */ new Set();
|
|
88684
|
+
logMinFreeRedundancyOnce(locationId, message, meta) {
|
|
88685
|
+
if (this.minFreeRedundancyLogged.has(locationId)) return;
|
|
88686
|
+
this.minFreeRedundancyLogged.add(locationId);
|
|
88687
|
+
this.ctx.logger.warn(message, { meta: {
|
|
88688
|
+
locationId,
|
|
88689
|
+
...meta
|
|
88690
|
+
} });
|
|
88691
|
+
}
|
|
88692
|
+
/**
|
|
88693
|
+
* The draining locations and the ACTIVE peers of their class whose growth
|
|
88694
|
+
* paces them (D386). Rebuilt every sweep from the live mode of every
|
|
88695
|
+
* location, so this is also what STOPS a drain: a location the operator
|
|
88696
|
+
* switched back simply stops appearing.
|
|
88697
|
+
*/
|
|
88698
|
+
drainStore = null;
|
|
88699
|
+
/** The drain ratchet's durable home, once a data-store engine exists. Same
|
|
88700
|
+
* late-wiring window as the location table — on an agent it is never. */
|
|
88701
|
+
resolveDrainStore() {
|
|
88702
|
+
if (this.drainStore) return this.drainStore;
|
|
88703
|
+
const engine = this.ctx.kernel.capabilityRegistry?.getCollection(DATA_STORE_PROVIDER_CAP_NAME)[0];
|
|
88704
|
+
if (!engine) return null;
|
|
88705
|
+
this.drainStore = new SqliteDrainStateStore(engine);
|
|
88706
|
+
return this.drainStore;
|
|
88707
|
+
}
|
|
88708
|
+
drainPlanRefusalLogged = false;
|
|
88709
|
+
resolveDrainPlan() {
|
|
88710
|
+
const svc = this.service;
|
|
88711
|
+
if (!svc) return [];
|
|
88712
|
+
if (this.resolveDrainStore() === null) {
|
|
88713
|
+
const draining = svc.listLocations().filter((l) => require_dist10.evictionPolicyOfLocation(l) === "drain");
|
|
88714
|
+
if (draining.length > 0 && !this.drainPlanRefusalLogged) {
|
|
88715
|
+
this.drainPlanRefusalLogged = true;
|
|
88716
|
+
this.ctx.logger.warn("storage-orchestrator: REFUSING to drain \u2014 this node has no data-store engine to persist the drain cap, and a cap recomputed at every boot would delete on a pace nobody chose", { meta: { locationIds: draining.map((l) => l.id) } });
|
|
88717
|
+
}
|
|
88718
|
+
return [];
|
|
88719
|
+
}
|
|
88720
|
+
const locations = svc.listLocations();
|
|
88721
|
+
return locations.filter((loc) => require_dist10.evictionPolicyOfLocation(loc) === "drain").map((loc) => ({
|
|
88722
|
+
locationId: loc.id,
|
|
88723
|
+
activePeerLocationIds: locations.filter((peer) => peer.type === loc.type && peer.id !== loc.id && require_dist10.mayWriteToLocation(peer)).map((peer) => peer.id)
|
|
88724
|
+
}));
|
|
88725
|
+
}
|
|
88726
|
+
/** Drain progress for every draining location, as of the last sweep. */
|
|
88727
|
+
drainProgress() {
|
|
88728
|
+
return this.pressureManager?.drainProgressSnapshot() ?? [];
|
|
88729
|
+
}
|
|
87968
88730
|
/**
|
|
87969
88731
|
* What the provider's own schema declared secret, or `undefined` when this
|
|
87970
88732
|
* node has never heard from that provider. The distinction is the whole
|
|
@@ -87977,6 +88739,48 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
87977
88739
|
secretKeysFor(providerId, config) {
|
|
87978
88740
|
return secretKeysForConfig(config, this.declaredSecretKeys(providerId));
|
|
87979
88741
|
}
|
|
88742
|
+
/**
|
|
88743
|
+
* Ring the doorbell: the SET of storage locations changed (D387).
|
|
88744
|
+
*
|
|
88745
|
+
* A consumer that resolves locations at boot had no way to learn that the
|
|
88746
|
+
* operator added, edited or removed one — the recorder kept writing to the
|
|
88747
|
+
* disk set it resolved hours ago, and an operator who added a second
|
|
88748
|
+
* recordings disk in the admin UI got nothing until something unrelated
|
|
88749
|
+
* happened to re-resolve.
|
|
88750
|
+
*
|
|
88751
|
+
* Telemetry, and deliberately so (D8/D11): it carries no row, it is allowed
|
|
88752
|
+
* to be dropped, and every consumer re-reads `listLocations` for itself and
|
|
88753
|
+
* converges on its own periodic path anyway. A bulk edit rings it once per
|
|
88754
|
+
* row — coalescing is the consumer's job, not this one's.
|
|
88755
|
+
*
|
|
88756
|
+
* Never throws into the mutation that produced it: the location IS saved,
|
|
88757
|
+
* and a bus that will not take a telemetry event must not turn that into a
|
|
88758
|
+
* failed upsert.
|
|
88759
|
+
*/
|
|
88760
|
+
announceLocationsChanged(locationId, locationType, reason) {
|
|
88761
|
+
try {
|
|
88762
|
+
this.ctx.eventBus.emit({
|
|
88763
|
+
id: (0, node_crypto.randomUUID)(),
|
|
88764
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
88765
|
+
source: {
|
|
88766
|
+
type: "addon",
|
|
88767
|
+
id: this.ctx.id
|
|
88768
|
+
},
|
|
88769
|
+
category: require_dist10.EventCategory.StorageLocationsChanged,
|
|
88770
|
+
data: {
|
|
88771
|
+
locationId,
|
|
88772
|
+
locationType,
|
|
88773
|
+
reason
|
|
88774
|
+
}
|
|
88775
|
+
});
|
|
88776
|
+
} catch (err) {
|
|
88777
|
+
this.ctx.logger.warn("storage-orchestrator: could not announce the location change \u2014 consumers will converge on their own periodic path", { meta: {
|
|
88778
|
+
locationId,
|
|
88779
|
+
reason,
|
|
88780
|
+
error: err instanceof Error ? err.message : String(err)
|
|
88781
|
+
} });
|
|
88782
|
+
}
|
|
88783
|
+
}
|
|
87980
88784
|
/** A location with its provider's declared secrets replaced by the sentinel. */
|
|
87981
88785
|
redacted(location) {
|
|
87982
88786
|
return redactLocation(location, this.secretKeysFor(location.providerId, location.config));
|
|
@@ -88035,8 +88839,9 @@ var require_storage_orchestrator_addon = __commonJS({
|
|
|
88035
88839
|
const svc = this.service;
|
|
88036
88840
|
if (!svc) return out;
|
|
88037
88841
|
for (const loc of svc.listLocations()) {
|
|
88842
|
+
if (!require_dist10.mayReadLocation(loc)) continue;
|
|
88038
88843
|
const raw = loc.config["maxUsedGb"];
|
|
88039
|
-
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) out.set(loc.id,
|
|
88844
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) out.set(loc.id, require_dist10.gbToBytes(raw));
|
|
88040
88845
|
}
|
|
88041
88846
|
return out;
|
|
88042
88847
|
}
|
|
@@ -88255,7 +89060,7 @@ var require_system_config_addon = __commonJS({
|
|
|
88255
89060
|
[Symbol.toStringTag]: { value: "Module" }
|
|
88256
89061
|
});
|
|
88257
89062
|
require_chunk_Cek0wNdY();
|
|
88258
|
-
var require_dist10 =
|
|
89063
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
88259
89064
|
var SECTION_TITLES = {
|
|
88260
89065
|
server: "Server",
|
|
88261
89066
|
auth: "Authentication"
|
|
@@ -106316,7 +107121,7 @@ var require_winston_logging = __commonJS({
|
|
|
106316
107121
|
[Symbol.toStringTag]: { value: "Module" }
|
|
106317
107122
|
});
|
|
106318
107123
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
106319
|
-
var require_dist10 =
|
|
107124
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
106320
107125
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
106321
107126
|
var node_path = __require("path");
|
|
106322
107127
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -108092,9 +108897,9 @@ ${(0, node_fs.readFileSync)(ca.caCertPath, "utf-8").trimEnd()}
|
|
|
108092
108897
|
}
|
|
108093
108898
|
});
|
|
108094
108899
|
|
|
108095
|
-
// ../types/dist/event-category-
|
|
108096
|
-
var
|
|
108097
|
-
"../types/dist/event-category-
|
|
108900
|
+
// ../types/dist/event-category-BVo6ta6_.js
|
|
108901
|
+
var require_event_category_BVo6ta6 = __commonJS({
|
|
108902
|
+
"../types/dist/event-category-BVo6ta6_.js"(exports) {
|
|
108098
108903
|
"use strict";
|
|
108099
108904
|
var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
108100
108905
|
EventCategory2["SystemBoot"] = "system.boot";
|
|
@@ -108142,6 +108947,7 @@ var require_event_category_b8kBSOTT = __commonJS({
|
|
|
108142
108947
|
EventCategory2["ProcessCrashed"] = "process.crashed";
|
|
108143
108948
|
EventCategory2["ProcessRestartScheduled"] = "process.restart_scheduled";
|
|
108144
108949
|
EventCategory2["ProcessRestarted"] = "process.restarted";
|
|
108950
|
+
EventCategory2["StorageLocationsChanged"] = "storage.locations-changed";
|
|
108145
108951
|
EventCategory2["RecordingStarted"] = "recording.started";
|
|
108146
108952
|
EventCategory2["RecordingStopped"] = "recording.stopped";
|
|
108147
108953
|
EventCategory2["RecordingError"] = "recording.error";
|
|
@@ -108260,11 +109066,11 @@ var require_event_category_b8kBSOTT = __commonJS({
|
|
|
108260
109066
|
}
|
|
108261
109067
|
});
|
|
108262
109068
|
|
|
108263
|
-
// ../types/dist/sleep-
|
|
108264
|
-
var
|
|
108265
|
-
"../types/dist/sleep-
|
|
109069
|
+
// ../types/dist/sleep-BZtO-eFY.js
|
|
109070
|
+
var require_sleep_BZtO_eFY = __commonJS({
|
|
109071
|
+
"../types/dist/sleep-BZtO-eFY.js"(exports) {
|
|
108266
109072
|
"use strict";
|
|
108267
|
-
var require_event_category =
|
|
109073
|
+
var require_event_category = require_event_category_BVo6ta6();
|
|
108268
109074
|
var zod = require_zod();
|
|
108269
109075
|
var WELL_KNOWN_TABS = [
|
|
108270
109076
|
{
|
|
@@ -111993,8 +112799,8 @@ var require_addon = __commonJS({
|
|
|
111993
112799
|
"../types/dist/addon.js"(exports) {
|
|
111994
112800
|
"use strict";
|
|
111995
112801
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
111996
|
-
var require_event_category =
|
|
111997
|
-
var require_sleep =
|
|
112802
|
+
var require_event_category = require_event_category_BVo6ta6();
|
|
112803
|
+
var require_sleep = require_sleep_BZtO_eFY();
|
|
111998
112804
|
var require_err_msg = require_err_msg_COpsHMw2();
|
|
111999
112805
|
var CAP_INPUT_DEFAULTS = Object.freeze({
|
|
112000
112806
|
"addons": { "getLogs": { "limit": 100 } },
|
|
@@ -112270,6 +113076,7 @@ var require_addon = __commonJS({
|
|
|
112270
113076
|
"listTargetKinds",
|
|
112271
113077
|
"listTargets"
|
|
112272
113078
|
],
|
|
113079
|
+
"storage-occupancy": ["getOccupancy"],
|
|
112273
113080
|
"storage-provider": ["list"],
|
|
112274
113081
|
"turn-provider": ["getTurnServers"],
|
|
112275
113082
|
"user-passkeys": ["listPasskeys"]
|
|
@@ -130924,7 +131731,7 @@ var require_dist3 = __commonJS({
|
|
|
130924
131731
|
"use strict";
|
|
130925
131732
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
130926
131733
|
var require_chunk = require_chunk_Cek0wNdY();
|
|
130927
|
-
var require_dist10 =
|
|
131734
|
+
var require_dist10 = require_dist_BFvWv_bz();
|
|
130928
131735
|
var require_builtins_alerts_alerts_addon = require_alerts_addon();
|
|
130929
131736
|
require_alerts();
|
|
130930
131737
|
var require_formatter = require_formatter_DqAKDlvN();
|
|
@@ -212020,7 +212827,7 @@ var require_enums = __commonJS({
|
|
|
212020
212827
|
"../types/dist/enums.js"(exports) {
|
|
212021
212828
|
"use strict";
|
|
212022
212829
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
212023
|
-
var require_event_category =
|
|
212830
|
+
var require_event_category = require_event_category_BVo6ta6();
|
|
212024
212831
|
var EventSourceType = /* @__PURE__ */ (function(EventSourceType2) {
|
|
212025
212832
|
EventSourceType2["Addon"] = "addon";
|
|
212026
212833
|
EventSourceType2["Core"] = "core";
|
|
@@ -212040,8 +212847,8 @@ var require_dist4 = __commonJS({
|
|
|
212040
212847
|
"../types/dist/index.js"(exports) {
|
|
212041
212848
|
"use strict";
|
|
212042
212849
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
212043
|
-
var require_event_category =
|
|
212044
|
-
var require_sleep =
|
|
212850
|
+
var require_event_category = require_event_category_BVo6ta6();
|
|
212851
|
+
var require_sleep = require_sleep_BZtO_eFY();
|
|
212045
212852
|
var require_canonical_hash = require_canonical_hash_DNV8S5ET();
|
|
212046
212853
|
var require_enums2 = require_enums();
|
|
212047
212854
|
var require_err_msg = require_err_msg_COpsHMw2();
|
|
@@ -213621,6 +214428,71 @@ var require_dist4 = __commonJS({
|
|
|
213621
214428
|
"clip",
|
|
213622
214429
|
"custom"
|
|
213623
214430
|
];
|
|
214431
|
+
var STORAGE_LOCATION_MODES = [
|
|
214432
|
+
"active",
|
|
214433
|
+
"readonly",
|
|
214434
|
+
"drain",
|
|
214435
|
+
"disabled"
|
|
214436
|
+
];
|
|
214437
|
+
var StorageLocationModeSchema = zod.z.enum(STORAGE_LOCATION_MODES);
|
|
214438
|
+
var StorageEvictionPolicySchema = zod.z.enum([
|
|
214439
|
+
"normal",
|
|
214440
|
+
"never",
|
|
214441
|
+
"drain"
|
|
214442
|
+
]);
|
|
214443
|
+
var LEGACY_READ_ONLY_CONFIG_KEY = "readOnly";
|
|
214444
|
+
function modeMayWrite(mode) {
|
|
214445
|
+
return mode === "active";
|
|
214446
|
+
}
|
|
214447
|
+
function modeMayRead(mode) {
|
|
214448
|
+
return mode !== "disabled";
|
|
214449
|
+
}
|
|
214450
|
+
function evictionPolicyForMode(mode) {
|
|
214451
|
+
switch (mode) {
|
|
214452
|
+
case "active":
|
|
214453
|
+
return "normal";
|
|
214454
|
+
case "drain":
|
|
214455
|
+
return "drain";
|
|
214456
|
+
case "readonly":
|
|
214457
|
+
case "disabled":
|
|
214458
|
+
return "never";
|
|
214459
|
+
}
|
|
214460
|
+
}
|
|
214461
|
+
function legacyModeOf(location) {
|
|
214462
|
+
if (location.mode !== void 0) return null;
|
|
214463
|
+
if (location.config["readOnly"] === true) return "readonly";
|
|
214464
|
+
if (location.enabled === false) return "readonly";
|
|
214465
|
+
return null;
|
|
214466
|
+
}
|
|
214467
|
+
function resolveLocationMode(location) {
|
|
214468
|
+
return (isStorageLocationMode(location.mode) ? location.mode : void 0) ?? legacyModeOf(location) ?? "active";
|
|
214469
|
+
}
|
|
214470
|
+
function isStorageLocationMode(value) {
|
|
214471
|
+
return STORAGE_LOCATION_MODES.some((mode) => mode === value);
|
|
214472
|
+
}
|
|
214473
|
+
function mayWriteToLocation(location) {
|
|
214474
|
+
return modeMayWrite(resolveLocationMode(location));
|
|
214475
|
+
}
|
|
214476
|
+
function mayReadLocation(location) {
|
|
214477
|
+
return modeMayRead(resolveLocationMode(location));
|
|
214478
|
+
}
|
|
214479
|
+
function evictionPolicyOfLocation(location) {
|
|
214480
|
+
return evictionPolicyForMode(resolveLocationMode(location));
|
|
214481
|
+
}
|
|
214482
|
+
function strippedOfLegacyReadOnly(config) {
|
|
214483
|
+
if (!("readOnly" in config)) return config;
|
|
214484
|
+
const next = { ...config };
|
|
214485
|
+
delete next[LEGACY_READ_ONLY_CONFIG_KEY];
|
|
214486
|
+
return next;
|
|
214487
|
+
}
|
|
214488
|
+
function withLocationMode(location, mode) {
|
|
214489
|
+
return {
|
|
214490
|
+
...location,
|
|
214491
|
+
mode,
|
|
214492
|
+
enabled: modeMayWrite(mode),
|
|
214493
|
+
config: strippedOfLegacyReadOnly(location.config)
|
|
214494
|
+
};
|
|
214495
|
+
}
|
|
213624
214496
|
var StorageLocationTypeSchema = zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*$/);
|
|
213625
214497
|
var StorageLocationSchema = zod.z.object({
|
|
213626
214498
|
id: zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
|
|
@@ -213652,6 +214524,21 @@ var require_dist4 = __commonJS({
|
|
|
213652
214524
|
* stops existing rather than being re-derived on every read.
|
|
213653
214525
|
*/
|
|
213654
214526
|
enabled: zod.z.boolean().optional(),
|
|
214527
|
+
/**
|
|
214528
|
+
* THE state of this location (D385), and the only authority on what may be
|
|
214529
|
+
* written, read or evicted here. Interpreted in exactly one place —
|
|
214530
|
+
* `storage-location-mode.ts` — which also folds the legacy
|
|
214531
|
+
* `enabled` / `config.readOnly` pair into a mode so an old row is never
|
|
214532
|
+
* ambiguous.
|
|
214533
|
+
*
|
|
214534
|
+
* OPTIONAL only for the wire and for rows written before D385: absence is
|
|
214535
|
+
* resolved by `resolveLocationMode`, and the orchestrator stamps every
|
|
214536
|
+
* unstamped row ONCE at hydrate so absence stops existing rather than being
|
|
214537
|
+
* re-derived on every read. `enabled` survives one release as a DERIVED
|
|
214538
|
+
* mirror (`mode === 'active'`); `withLocationMode` is the only writer of
|
|
214539
|
+
* either, so the two cannot disagree.
|
|
214540
|
+
*/
|
|
214541
|
+
mode: StorageLocationModeSchema.optional(),
|
|
213655
214542
|
/** COMPUTED at read time by the orchestrator (statfs of the backing volume
|
|
213656
214543
|
* for node-local locations it can reach) — never persisted, absent when the
|
|
213657
214544
|
* volume is remote/unreachable. The single capacity truth every UI reads. */
|
|
@@ -213659,13 +214546,47 @@ var require_dist4 = __commonJS({
|
|
|
213659
214546
|
totalBytes: zod.z.number(),
|
|
213660
214547
|
availableBytes: zod.z.number()
|
|
213661
214548
|
}).nullable().optional(),
|
|
214549
|
+
/**
|
|
214550
|
+
* How much of that volume CamStack ITSELF holds on this location (D388) —
|
|
214551
|
+
* COMPUTED at read time from the `storage-occupancy` providers' own figures,
|
|
214552
|
+
* never persisted, never a filesystem walk.
|
|
214553
|
+
*
|
|
214554
|
+
* **ABSENT MEANS UNKNOWN, never zero.** No provider has reported for this
|
|
214555
|
+
* location yet — nobody stores here, the owning addon is down, or the first
|
|
214556
|
+
* refresh has not completed. A UI must omit the segment rather than draw it
|
|
214557
|
+
* at zero, which would claim we occupy nothing (D315). It is an OBJECT and
|
|
214558
|
+
* not a bare number precisely so that a `?? 0` on the consuming side has to
|
|
214559
|
+
* be spelled out loud instead of appearing by accident.
|
|
214560
|
+
*
|
|
214561
|
+
* `measuredAtMs` is the OLDEST contributing measurement, so it is honest
|
|
214562
|
+
* about the whole figure rather than about its freshest part.
|
|
214563
|
+
*/
|
|
214564
|
+
owned: zod.z.object({
|
|
214565
|
+
bytes: zod.z.number().int().nonnegative(),
|
|
214566
|
+
measuredAtMs: zod.z.number().int().nonnegative()
|
|
214567
|
+
}).optional(),
|
|
213662
214568
|
createdAt: zod.z.number(),
|
|
213663
214569
|
updatedAt: zod.z.number()
|
|
213664
214570
|
});
|
|
213665
214571
|
var LegacyStorageLocationDefaultSchema = zod.z.object({ isDefault: zod.z.boolean().optional() });
|
|
213666
214572
|
function isLocationEnabled(location) {
|
|
213667
|
-
return
|
|
214573
|
+
return mayWriteToLocation({
|
|
214574
|
+
...location,
|
|
214575
|
+
config: location.config ?? {}
|
|
214576
|
+
});
|
|
213668
214577
|
}
|
|
214578
|
+
var STORAGE_BYTES_PER_GB = 1024 ** 3;
|
|
214579
|
+
function gbToBytes(gb) {
|
|
214580
|
+
return Math.floor(gb * STORAGE_BYTES_PER_GB);
|
|
214581
|
+
}
|
|
214582
|
+
var StorageDrainProgressSchema = zod.z.object({
|
|
214583
|
+
locationId: zod.z.string(),
|
|
214584
|
+
startedAtMs: zod.z.number(),
|
|
214585
|
+
startBytes: zod.z.number(),
|
|
214586
|
+
bytesRemaining: zod.z.number(),
|
|
214587
|
+
drained: zod.z.boolean(),
|
|
214588
|
+
estimatedEmptyAtMs: zod.z.number().nullable()
|
|
214589
|
+
});
|
|
213669
214590
|
var StorageLocationRefSchema = zod.z.union([StorageLocationTypeSchema, zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/)]);
|
|
213670
214591
|
var StorageAccessSchema = zod.z.enum(["local-path", "cap-mediated"]);
|
|
213671
214592
|
var STORAGE_ACCESS_FALLBACK = "local-path";
|
|
@@ -230016,6 +230937,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
230016
230937
|
kind: "mutation",
|
|
230017
230938
|
auth: "admin"
|
|
230018
230939
|
}),
|
|
230940
|
+
/**
|
|
230941
|
+
* How far each draining location has got (D386). A pure READ of what the
|
|
230942
|
+
* last pressure sweep computed — it starts no work, and a location that is
|
|
230943
|
+
* not draining simply does not appear.
|
|
230944
|
+
*/
|
|
230945
|
+
listDrainProgress: require_sleep.method(zod.z.void(), zod.z.array(StorageDrainProgressSchema).readonly()),
|
|
230019
230946
|
testLocation: require_sleep.method(zod.z.object({ id: zod.z.string() }), zod.z.object({
|
|
230020
230947
|
ok: zod.z.boolean(),
|
|
230021
230948
|
error: zod.z.string().optional()
|
|
@@ -230145,6 +231072,37 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
230145
231072
|
history: require_sleep.method(zod.z.object({}), zod.z.array(StorageMigrationJobSchema).readonly(), { auth: "admin" })
|
|
230146
231073
|
}
|
|
230147
231074
|
};
|
|
231075
|
+
var StorageOccupancyReportSchema = zod.z.object({
|
|
231076
|
+
locationId: zod.z.string(),
|
|
231077
|
+
/** Bytes this provider holds on THAT location — not its eviction domain, and
|
|
231078
|
+
* not net of what it is willing to delete. */
|
|
231079
|
+
ownedBytes: zod.z.number().int().nonnegative(),
|
|
231080
|
+
/** When the provider last actually measured this. The orchestrator carries it
|
|
231081
|
+
* through so a UI can say how old the figure is instead of implying "now". */
|
|
231082
|
+
measuredAtMs: zod.z.number().int().nonnegative()
|
|
231083
|
+
});
|
|
231084
|
+
var storageOccupancyCapability = {
|
|
231085
|
+
name: "storage-occupancy",
|
|
231086
|
+
scope: "system",
|
|
231087
|
+
mode: "collection",
|
|
231088
|
+
internal: true,
|
|
231089
|
+
methods: {
|
|
231090
|
+
/**
|
|
231091
|
+
* Occupancy for the given locations, in ONE round trip.
|
|
231092
|
+
*
|
|
231093
|
+
* A provider answers only for the locations it actually holds bytes on and
|
|
231094
|
+
* OMITS the rest — an omitted location is "I hold nothing measurable here",
|
|
231095
|
+
* which the orchestrator merges as a contribution of nothing rather than as
|
|
231096
|
+
* a claim that the location is empty. Only a location no provider reports
|
|
231097
|
+
* at all stays unknown.
|
|
231098
|
+
*
|
|
231099
|
+
* This must be CHEAP and must never walk a filesystem: it is on the admin
|
|
231100
|
+
* UI's `listLocations` path. The owner keeps its own figure fresh (D224) and
|
|
231101
|
+
* answers from what it already has.
|
|
231102
|
+
*/
|
|
231103
|
+
getOccupancy: require_sleep.method(zod.z.object({ locationIds: zod.z.array(zod.z.string()).readonly() }), zod.z.array(StorageOccupancyReportSchema).readonly(), { auth: "admin" })
|
|
231104
|
+
}
|
|
231105
|
+
};
|
|
230148
231106
|
var ProviderInfoSchema = zod.z.discriminatedUnion("shouldSaveDiskSpace", [zod.z.object({
|
|
230149
231107
|
providerId: zod.z.string().min(1),
|
|
230150
231108
|
displayName: zod.z.string().min(1),
|
|
@@ -232104,94 +233062,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
232104
233062
|
* whether persisting is worth a SQLite commit. */
|
|
232105
233063
|
volatileStateFields: ["lastUpdated"]
|
|
232106
233064
|
};
|
|
232107
|
-
var NETWORK_LINK_TYPES = [
|
|
232108
|
-
"wifi",
|
|
232109
|
-
"ethernet",
|
|
232110
|
-
"cellular",
|
|
232111
|
-
"unknown"
|
|
232112
|
-
];
|
|
232113
|
-
var NetworkLinkStatusSchema = zod.z.object({
|
|
232114
|
-
/** The link the device is on. `'unknown'` = not read yet, not "no link". */
|
|
232115
|
-
type: zod.z.enum(NETWORK_LINK_TYPES),
|
|
232116
|
-
/**
|
|
232117
|
-
* Link quality, 0..100 inclusive, normalised by the provider from whatever
|
|
232118
|
-
* the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
|
|
232119
|
-
* KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
|
|
232120
|
-
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
232121
|
-
* SKIP a null rather than coerce it.
|
|
232122
|
-
*/
|
|
232123
|
-
signalPercent: zod.z.number().min(0).max(100).nullable(),
|
|
232124
|
-
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
232125
|
-
rssiDbm: zod.z.number().optional(),
|
|
232126
|
-
/** Network name of a wireless link, when the firmware reports it. */
|
|
232127
|
-
ssid: zod.z.string().optional(),
|
|
232128
|
-
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
232129
|
-
lastUpdated: zod.z.number()
|
|
232130
|
-
});
|
|
232131
|
-
var NETWORK_LINK_UNKNOWN = {
|
|
232132
|
-
type: "unknown",
|
|
232133
|
-
signalPercent: null,
|
|
232134
|
-
lastUpdated: 0
|
|
232135
|
-
};
|
|
232136
|
-
function signalPercentFromBars(bars, maxBars) {
|
|
232137
|
-
if (bars === void 0 || !Number.isFinite(bars) || maxBars <= 0) return null;
|
|
232138
|
-
if (bars < 0 || bars > maxBars) return null;
|
|
232139
|
-
return Math.round(bars / maxBars * 100);
|
|
232140
|
-
}
|
|
232141
|
-
function signalPercentFromRssi(rssiDbm) {
|
|
232142
|
-
if (rssiDbm === void 0 || !Number.isFinite(rssiDbm) || rssiDbm > 0) return null;
|
|
232143
|
-
return Math.round((Math.min(-50, Math.max(-100, rssiDbm)) + 100) * 2);
|
|
232144
|
-
}
|
|
232145
|
-
var networkLinkCapability = {
|
|
232146
|
-
name: "network-link",
|
|
232147
|
-
scope: "device",
|
|
232148
|
-
deviceNative: true,
|
|
232149
|
-
mode: "singleton",
|
|
232150
|
-
deviceTypes: [
|
|
232151
|
-
require_sleep.DeviceType.Camera,
|
|
232152
|
-
require_sleep.DeviceType.Sensor,
|
|
232153
|
-
require_sleep.DeviceType.Button,
|
|
232154
|
-
require_sleep.DeviceType.Switch,
|
|
232155
|
-
require_sleep.DeviceType.Light,
|
|
232156
|
-
require_sleep.DeviceType.Lock,
|
|
232157
|
-
require_sleep.DeviceType.Siren
|
|
232158
|
-
],
|
|
232159
|
-
methods: {},
|
|
232160
|
-
events: {
|
|
232161
|
-
/**
|
|
232162
|
-
* Emitted whenever the cached status changes (a link switch, a signal
|
|
232163
|
-
* reading that moved). Mirrored on the parent chain by the
|
|
232164
|
-
* DeviceEventPropagator like `battery.onStatusChanged`.
|
|
232165
|
-
*/
|
|
232166
|
-
onStatusChanged: { data: zod.z.object({
|
|
232167
|
-
deviceId: zod.z.number(),
|
|
232168
|
-
status: NetworkLinkStatusSchema
|
|
232169
|
-
}) }
|
|
232170
|
-
},
|
|
232171
|
-
status: {
|
|
232172
|
-
schema: NetworkLinkStatusSchema,
|
|
232173
|
-
kind: "push",
|
|
232174
|
-
empty: NETWORK_LINK_UNKNOWN
|
|
232175
|
-
},
|
|
232176
|
-
/**
|
|
232177
|
-
* Runtime-state slice — every provider stores the same shape under
|
|
232178
|
-
* `device.runtimeState['network-link']`, read once by the badge and the
|
|
232179
|
-
* Home Assistant projector regardless of the driver.
|
|
232180
|
-
*/
|
|
232181
|
-
runtimeState: NetworkLinkStatusSchema,
|
|
232182
|
-
/**
|
|
232183
|
-
* Runtime-state durability: **restored** — a link reading is slow to
|
|
232184
|
-
* change and a sleeping battery camera may not report for hours; the
|
|
232185
|
-
* restored slice is what the badge shows until the next read.
|
|
232186
|
-
*
|
|
232187
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
232188
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
232189
|
-
*/
|
|
232190
|
-
durability: "restored",
|
|
232191
|
-
/** Clock fields: written, but excluded from the compare that decides
|
|
232192
|
-
* whether persisting is worth a SQLite commit. */
|
|
232193
|
-
volatileStateFields: ["lastUpdated"]
|
|
232194
|
-
};
|
|
232195
233065
|
var BinaryStatusSchema = zod.z.object({
|
|
232196
233066
|
on: zod.z.boolean(),
|
|
232197
233067
|
/** Ms epoch of the last transition. 0 if never observed. */
|
|
@@ -235333,6 +236203,385 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
235333
236203
|
* whether persisting is worth a SQLite commit. */
|
|
235334
236204
|
volatileStateFields: ["lastFetchedAt"]
|
|
235335
236205
|
};
|
|
236206
|
+
var NavigationMoveCommandSchema = zod.z.object({
|
|
236207
|
+
pan: zod.z.number().min(-1).max(1).optional(),
|
|
236208
|
+
tilt: zod.z.number().min(-1).max(1).optional(),
|
|
236209
|
+
speed: zod.z.number().min(0).max(1).optional()
|
|
236210
|
+
});
|
|
236211
|
+
var NavigationActionIdSchema = zod.z.enum([
|
|
236212
|
+
"goHome",
|
|
236213
|
+
"locate",
|
|
236214
|
+
"spotClean",
|
|
236215
|
+
"findPet",
|
|
236216
|
+
"personFollow",
|
|
236217
|
+
"stop",
|
|
236218
|
+
"startClean",
|
|
236219
|
+
"pauseClean",
|
|
236220
|
+
"dockWash",
|
|
236221
|
+
"autoEmpty",
|
|
236222
|
+
"flashOn",
|
|
236223
|
+
"flashOff"
|
|
236224
|
+
]);
|
|
236225
|
+
var NavigationEntryKindSchema = zod.z.enum(["action", "sound"]);
|
|
236226
|
+
var NavigationActionEntrySchema = zod.z.object({
|
|
236227
|
+
id: zod.z.string(),
|
|
236228
|
+
kind: NavigationEntryKindSchema,
|
|
236229
|
+
label: zod.z.string(),
|
|
236230
|
+
icon: zod.z.string(),
|
|
236231
|
+
/** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
|
|
236232
|
+
soundId: zod.z.number().int().optional(),
|
|
236233
|
+
/** Per-device feature flag — render this entry only when true. */
|
|
236234
|
+
enabled: zod.z.boolean()
|
|
236235
|
+
});
|
|
236236
|
+
var NAVIGATION_ACTION_CATALOG = [
|
|
236237
|
+
{
|
|
236238
|
+
id: "startClean",
|
|
236239
|
+
kind: "action",
|
|
236240
|
+
label: "Start cleaning",
|
|
236241
|
+
icon: "play",
|
|
236242
|
+
enabled: true
|
|
236243
|
+
},
|
|
236244
|
+
{
|
|
236245
|
+
id: "pauseClean",
|
|
236246
|
+
kind: "action",
|
|
236247
|
+
label: "Pause cleaning",
|
|
236248
|
+
icon: "pause",
|
|
236249
|
+
enabled: true
|
|
236250
|
+
},
|
|
236251
|
+
{
|
|
236252
|
+
id: "stop",
|
|
236253
|
+
kind: "action",
|
|
236254
|
+
label: "Stop",
|
|
236255
|
+
icon: "hand",
|
|
236256
|
+
enabled: true
|
|
236257
|
+
},
|
|
236258
|
+
{
|
|
236259
|
+
id: "goHome",
|
|
236260
|
+
kind: "action",
|
|
236261
|
+
label: "Return to dock",
|
|
236262
|
+
icon: "home",
|
|
236263
|
+
enabled: true
|
|
236264
|
+
},
|
|
236265
|
+
{
|
|
236266
|
+
id: "locate",
|
|
236267
|
+
kind: "action",
|
|
236268
|
+
label: "Locate",
|
|
236269
|
+
icon: "bell",
|
|
236270
|
+
enabled: true
|
|
236271
|
+
},
|
|
236272
|
+
{
|
|
236273
|
+
id: "spotClean",
|
|
236274
|
+
kind: "action",
|
|
236275
|
+
label: "Spot clean here",
|
|
236276
|
+
icon: "crosshair",
|
|
236277
|
+
enabled: true
|
|
236278
|
+
},
|
|
236279
|
+
{
|
|
236280
|
+
id: "findPet",
|
|
236281
|
+
kind: "action",
|
|
236282
|
+
label: "Find pet",
|
|
236283
|
+
icon: "paw-print",
|
|
236284
|
+
enabled: true
|
|
236285
|
+
},
|
|
236286
|
+
{
|
|
236287
|
+
id: "personFollow",
|
|
236288
|
+
kind: "action",
|
|
236289
|
+
label: "Follow me",
|
|
236290
|
+
icon: "footprints",
|
|
236291
|
+
enabled: true
|
|
236292
|
+
},
|
|
236293
|
+
{
|
|
236294
|
+
id: "dockWash",
|
|
236295
|
+
kind: "action",
|
|
236296
|
+
label: "Wash mop",
|
|
236297
|
+
icon: "droplets",
|
|
236298
|
+
enabled: true
|
|
236299
|
+
},
|
|
236300
|
+
{
|
|
236301
|
+
id: "autoEmpty",
|
|
236302
|
+
kind: "action",
|
|
236303
|
+
label: "Empty dustbin",
|
|
236304
|
+
icon: "trash-2",
|
|
236305
|
+
enabled: true
|
|
236306
|
+
},
|
|
236307
|
+
{
|
|
236308
|
+
id: "flashOn",
|
|
236309
|
+
kind: "action",
|
|
236310
|
+
label: "Flash on",
|
|
236311
|
+
icon: "flashlight",
|
|
236312
|
+
enabled: true
|
|
236313
|
+
},
|
|
236314
|
+
{
|
|
236315
|
+
id: "flashOff",
|
|
236316
|
+
kind: "action",
|
|
236317
|
+
label: "Flash off",
|
|
236318
|
+
icon: "flashlight-off",
|
|
236319
|
+
enabled: true
|
|
236320
|
+
},
|
|
236321
|
+
{
|
|
236322
|
+
id: "sound:meow",
|
|
236323
|
+
kind: "sound",
|
|
236324
|
+
label: "Meow",
|
|
236325
|
+
icon: "cat",
|
|
236326
|
+
soundId: 684,
|
|
236327
|
+
enabled: true
|
|
236328
|
+
},
|
|
236329
|
+
{
|
|
236330
|
+
id: "sound:bark",
|
|
236331
|
+
kind: "sound",
|
|
236332
|
+
label: "Woof",
|
|
236333
|
+
icon: "dog",
|
|
236334
|
+
soundId: 685,
|
|
236335
|
+
enabled: true
|
|
236336
|
+
},
|
|
236337
|
+
{
|
|
236338
|
+
id: "sound:footsteps",
|
|
236339
|
+
kind: "sound",
|
|
236340
|
+
label: "Footsteps",
|
|
236341
|
+
icon: "footprints",
|
|
236342
|
+
soundId: 686,
|
|
236343
|
+
enabled: true
|
|
236344
|
+
},
|
|
236345
|
+
{
|
|
236346
|
+
id: "sound:purring",
|
|
236347
|
+
kind: "sound",
|
|
236348
|
+
label: "Purring",
|
|
236349
|
+
icon: "cat",
|
|
236350
|
+
soundId: 687,
|
|
236351
|
+
enabled: true
|
|
236352
|
+
},
|
|
236353
|
+
{
|
|
236354
|
+
id: "sound:tickTock",
|
|
236355
|
+
kind: "sound",
|
|
236356
|
+
label: "Tick-tock",
|
|
236357
|
+
icon: "clock",
|
|
236358
|
+
soundId: 688,
|
|
236359
|
+
enabled: true
|
|
236360
|
+
}
|
|
236361
|
+
];
|
|
236362
|
+
var NavigationPointSchema = zod.z.object({
|
|
236363
|
+
x: zod.z.number(),
|
|
236364
|
+
y: zod.z.number()
|
|
236365
|
+
});
|
|
236366
|
+
var NavigationFeaturesSchema = zod.z.object({
|
|
236367
|
+
move: zod.z.boolean(),
|
|
236368
|
+
stop: zod.z.boolean(),
|
|
236369
|
+
goToPoint: zod.z.boolean(),
|
|
236370
|
+
runAction: zod.z.boolean(),
|
|
236371
|
+
playSound: zod.z.boolean(),
|
|
236372
|
+
light: zod.z.boolean(),
|
|
236373
|
+
lightMode: zod.z.boolean()
|
|
236374
|
+
});
|
|
236375
|
+
var NavigationLightModeSchema = zod.z.enum(["auto", "manual"]);
|
|
236376
|
+
var NAVIGATION_LIGHT_LEVEL_MIN = 40;
|
|
236377
|
+
var NAVIGATION_LIGHT_LEVEL_MAX = 100;
|
|
236378
|
+
var NavigationModeSchema = zod.z.enum([
|
|
236379
|
+
"idle",
|
|
236380
|
+
"cleaning",
|
|
236381
|
+
"spot",
|
|
236382
|
+
"following",
|
|
236383
|
+
"goto",
|
|
236384
|
+
"returning",
|
|
236385
|
+
"paused",
|
|
236386
|
+
"unknown"
|
|
236387
|
+
]);
|
|
236388
|
+
var NavigationStatusSchema = zod.z.object({
|
|
236389
|
+
mode: NavigationModeSchema,
|
|
236390
|
+
following: zod.z.boolean(),
|
|
236391
|
+
flash: zod.z.boolean(),
|
|
236392
|
+
lightMode: NavigationLightModeSchema,
|
|
236393
|
+
lightLevel: zod.z.number().min(40).max(100),
|
|
236394
|
+
/** Ms epoch when the slice was last updated. */
|
|
236395
|
+
lastChangedAt: zod.z.number()
|
|
236396
|
+
});
|
|
236397
|
+
var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: zod.z.number() });
|
|
236398
|
+
var navigationCapability = {
|
|
236399
|
+
name: "navigation",
|
|
236400
|
+
scope: "device",
|
|
236401
|
+
deviceNative: true,
|
|
236402
|
+
mode: "singleton",
|
|
236403
|
+
deviceTypes: [require_sleep.DeviceType.Camera],
|
|
236404
|
+
deviceConfig: { ui: {
|
|
236405
|
+
kind: "widget",
|
|
236406
|
+
widgetId: "host/navigation-panel",
|
|
236407
|
+
tab: "navigation",
|
|
236408
|
+
topTab: true,
|
|
236409
|
+
label: "Navigation",
|
|
236410
|
+
order: 0
|
|
236411
|
+
} },
|
|
236412
|
+
methods: {
|
|
236413
|
+
/**
|
|
236414
|
+
* Momentary drive nudge (the robot moves). `protected` — mirrors
|
|
236415
|
+
* `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
|
|
236416
|
+
* path) works for any authenticated user, not admin-only. The UI sends
|
|
236417
|
+
* these at ~1 Hz while a control is held; the provider forwards each one to
|
|
236418
|
+
* a single drive write WITHOUT debouncing.
|
|
236419
|
+
*/
|
|
236420
|
+
move: require_sleep.method(NavigationMoveCommandSchema.extend({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
236421
|
+
/** Halt all motion immediately (zero drive vector). */
|
|
236422
|
+
stop: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
236423
|
+
/** Send the robot to a point on its live map. */
|
|
236424
|
+
goToPoint: require_sleep.method(NavigationPointSchema.extend({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
236425
|
+
/**
|
|
236426
|
+
* Enumerate the discrete controls THIS device supports (data-driven UI +
|
|
236427
|
+
* PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
|
|
236428
|
+
*/
|
|
236429
|
+
listActions: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(NavigationActionEntrySchema)),
|
|
236430
|
+
/**
|
|
236431
|
+
* Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
|
|
236432
|
+
* unsupported action ids are rejected by the provider.
|
|
236433
|
+
*/
|
|
236434
|
+
runAction: require_sleep.method(zod.z.object({
|
|
236435
|
+
deviceId: zod.z.number(),
|
|
236436
|
+
actionId: NavigationActionIdSchema
|
|
236437
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
236438
|
+
/** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
|
|
236439
|
+
playSound: require_sleep.method(zod.z.object({
|
|
236440
|
+
deviceId: zod.z.number(),
|
|
236441
|
+
soundId: zod.z.number().int()
|
|
236442
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
236443
|
+
/**
|
|
236444
|
+
* Turn the on-camera fill light on / off (the `OpenFullLight` control —
|
|
236445
|
+
* works anytime, no active stream required).
|
|
236446
|
+
*/
|
|
236447
|
+
setLightOn: require_sleep.method(zod.z.object({
|
|
236448
|
+
deviceId: zod.z.number(),
|
|
236449
|
+
on: zod.z.boolean()
|
|
236450
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
236451
|
+
/**
|
|
236452
|
+
* Set the fill-light mode (auto vs manual). `manual` optionally carries the
|
|
236453
|
+
* initial `level`. The auto/manual + level control is a CAMERA-service
|
|
236454
|
+
* action that generally needs an active camera stream/monitor session — the
|
|
236455
|
+
* UI shows the manual level slider ONLY when `mode === 'manual'`.
|
|
236456
|
+
*/
|
|
236457
|
+
setLightMode: require_sleep.method(zod.z.object({
|
|
236458
|
+
deviceId: zod.z.number(),
|
|
236459
|
+
mode: NavigationLightModeSchema,
|
|
236460
|
+
level: zod.z.number().min(40).max(100).optional()
|
|
236461
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
236462
|
+
/** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
|
|
236463
|
+
setLightLevel: require_sleep.method(zod.z.object({
|
|
236464
|
+
deviceId: zod.z.number(),
|
|
236465
|
+
level: zod.z.number().min(40).max(100)
|
|
236466
|
+
}), zod.z.void(), { kind: "mutation" }),
|
|
236467
|
+
/**
|
|
236468
|
+
* Per-device FEATURE-FLAG report for the general primitives — drives which
|
|
236469
|
+
* controls the UI shows (the per-entry flags for the dictionary come back on
|
|
236470
|
+
* `listActions`).
|
|
236471
|
+
*/
|
|
236472
|
+
getFeatures: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), NavigationFeaturesSchema)
|
|
236473
|
+
},
|
|
236474
|
+
events: { onStatusChanged: { data: zod.z.object({
|
|
236475
|
+
deviceId: zod.z.number(),
|
|
236476
|
+
status: NavigationStatusSchema
|
|
236477
|
+
}) } },
|
|
236478
|
+
status: {
|
|
236479
|
+
schema: NavigationStatusSchema,
|
|
236480
|
+
kind: "push"
|
|
236481
|
+
},
|
|
236482
|
+
/**
|
|
236483
|
+
* Runtime-state slice mirrored by the kernel. The navigation panel watches it
|
|
236484
|
+
* for live mode / follow / flash changes.
|
|
236485
|
+
*/
|
|
236486
|
+
runtimeState: NavigationRuntimeStateSchema,
|
|
236487
|
+
/**
|
|
236488
|
+
* Runtime-state durability: **session** — like `vacuum-control`, a restored
|
|
236489
|
+
* `mode: cleaning` / `following: true` is a robot that is not actually doing
|
|
236490
|
+
* that. The live handle re-publishes on connect.
|
|
236491
|
+
*
|
|
236492
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
236493
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
236494
|
+
*/
|
|
236495
|
+
durability: "session"
|
|
236496
|
+
};
|
|
236497
|
+
var NETWORK_LINK_TYPES = [
|
|
236498
|
+
"wifi",
|
|
236499
|
+
"ethernet",
|
|
236500
|
+
"cellular",
|
|
236501
|
+
"unknown"
|
|
236502
|
+
];
|
|
236503
|
+
var NetworkLinkStatusSchema = zod.z.object({
|
|
236504
|
+
/** The link the device is on. `'unknown'` = not read yet, not "no link". */
|
|
236505
|
+
type: zod.z.enum(NETWORK_LINK_TYPES),
|
|
236506
|
+
/**
|
|
236507
|
+
* Link quality, 0..100 inclusive, normalised by the provider from whatever
|
|
236508
|
+
* the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
|
|
236509
|
+
* KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
|
|
236510
|
+
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
236511
|
+
* SKIP a null rather than coerce it.
|
|
236512
|
+
*/
|
|
236513
|
+
signalPercent: zod.z.number().min(0).max(100).nullable(),
|
|
236514
|
+
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
236515
|
+
rssiDbm: zod.z.number().optional(),
|
|
236516
|
+
/** Network name of a wireless link, when the firmware reports it. */
|
|
236517
|
+
ssid: zod.z.string().optional(),
|
|
236518
|
+
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
236519
|
+
lastUpdated: zod.z.number()
|
|
236520
|
+
});
|
|
236521
|
+
var NETWORK_LINK_UNKNOWN = {
|
|
236522
|
+
type: "unknown",
|
|
236523
|
+
signalPercent: null,
|
|
236524
|
+
lastUpdated: 0
|
|
236525
|
+
};
|
|
236526
|
+
function signalPercentFromBars(bars, maxBars) {
|
|
236527
|
+
if (bars === void 0 || !Number.isFinite(bars) || maxBars <= 0) return null;
|
|
236528
|
+
if (bars < 0 || bars > maxBars) return null;
|
|
236529
|
+
return Math.round(bars / maxBars * 100);
|
|
236530
|
+
}
|
|
236531
|
+
function signalPercentFromRssi(rssiDbm) {
|
|
236532
|
+
if (rssiDbm === void 0 || !Number.isFinite(rssiDbm) || rssiDbm > 0) return null;
|
|
236533
|
+
return Math.round((Math.min(-50, Math.max(-100, rssiDbm)) + 100) * 2);
|
|
236534
|
+
}
|
|
236535
|
+
var networkLinkCapability = {
|
|
236536
|
+
name: "network-link",
|
|
236537
|
+
scope: "device",
|
|
236538
|
+
deviceNative: true,
|
|
236539
|
+
mode: "singleton",
|
|
236540
|
+
deviceTypes: [
|
|
236541
|
+
require_sleep.DeviceType.Camera,
|
|
236542
|
+
require_sleep.DeviceType.Sensor,
|
|
236543
|
+
require_sleep.DeviceType.Button,
|
|
236544
|
+
require_sleep.DeviceType.Switch,
|
|
236545
|
+
require_sleep.DeviceType.Light,
|
|
236546
|
+
require_sleep.DeviceType.Lock,
|
|
236547
|
+
require_sleep.DeviceType.Siren
|
|
236548
|
+
],
|
|
236549
|
+
methods: {},
|
|
236550
|
+
events: {
|
|
236551
|
+
/**
|
|
236552
|
+
* Emitted whenever the cached status changes (a link switch, a signal
|
|
236553
|
+
* reading that moved). Mirrored on the parent chain by the
|
|
236554
|
+
* DeviceEventPropagator like `battery.onStatusChanged`.
|
|
236555
|
+
*/
|
|
236556
|
+
onStatusChanged: { data: zod.z.object({
|
|
236557
|
+
deviceId: zod.z.number(),
|
|
236558
|
+
status: NetworkLinkStatusSchema
|
|
236559
|
+
}) }
|
|
236560
|
+
},
|
|
236561
|
+
status: {
|
|
236562
|
+
schema: NetworkLinkStatusSchema,
|
|
236563
|
+
kind: "push",
|
|
236564
|
+
empty: NETWORK_LINK_UNKNOWN
|
|
236565
|
+
},
|
|
236566
|
+
/**
|
|
236567
|
+
* Runtime-state slice — every provider stores the same shape under
|
|
236568
|
+
* `device.runtimeState['network-link']`, read once by the badge and the
|
|
236569
|
+
* Home Assistant projector regardless of the driver.
|
|
236570
|
+
*/
|
|
236571
|
+
runtimeState: NetworkLinkStatusSchema,
|
|
236572
|
+
/**
|
|
236573
|
+
* Runtime-state durability: **restored** — a link reading is slow to
|
|
236574
|
+
* change and a sleeping battery camera may not report for hours; the
|
|
236575
|
+
* restored slice is what the badge shows until the next read.
|
|
236576
|
+
*
|
|
236577
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
236578
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
236579
|
+
*/
|
|
236580
|
+
durability: "restored",
|
|
236581
|
+
/** Clock fields: written, but excluded from the compare that decides
|
|
236582
|
+
* whether persisting is worth a SQLite commit. */
|
|
236583
|
+
volatileStateFields: ["lastUpdated"]
|
|
236584
|
+
};
|
|
235336
236585
|
var StreamNetworkStatsSchema = zod.z.object({
|
|
235337
236586
|
nominalBitrateKbps: zod.z.number(),
|
|
235338
236587
|
observedBitrateKbps: zod.z.number(),
|
|
@@ -236904,297 +238153,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
236904
238153
|
*/
|
|
236905
238154
|
durability: "session"
|
|
236906
238155
|
};
|
|
236907
|
-
var NavigationMoveCommandSchema = zod.z.object({
|
|
236908
|
-
pan: zod.z.number().min(-1).max(1).optional(),
|
|
236909
|
-
tilt: zod.z.number().min(-1).max(1).optional(),
|
|
236910
|
-
speed: zod.z.number().min(0).max(1).optional()
|
|
236911
|
-
});
|
|
236912
|
-
var NavigationActionIdSchema = zod.z.enum([
|
|
236913
|
-
"goHome",
|
|
236914
|
-
"locate",
|
|
236915
|
-
"spotClean",
|
|
236916
|
-
"findPet",
|
|
236917
|
-
"personFollow",
|
|
236918
|
-
"stop",
|
|
236919
|
-
"startClean",
|
|
236920
|
-
"pauseClean",
|
|
236921
|
-
"dockWash",
|
|
236922
|
-
"autoEmpty",
|
|
236923
|
-
"flashOn",
|
|
236924
|
-
"flashOff"
|
|
236925
|
-
]);
|
|
236926
|
-
var NavigationEntryKindSchema = zod.z.enum(["action", "sound"]);
|
|
236927
|
-
var NavigationActionEntrySchema = zod.z.object({
|
|
236928
|
-
id: zod.z.string(),
|
|
236929
|
-
kind: NavigationEntryKindSchema,
|
|
236930
|
-
label: zod.z.string(),
|
|
236931
|
-
icon: zod.z.string(),
|
|
236932
|
-
/** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
|
|
236933
|
-
soundId: zod.z.number().int().optional(),
|
|
236934
|
-
/** Per-device feature flag — render this entry only when true. */
|
|
236935
|
-
enabled: zod.z.boolean()
|
|
236936
|
-
});
|
|
236937
|
-
var NAVIGATION_ACTION_CATALOG = [
|
|
236938
|
-
{
|
|
236939
|
-
id: "startClean",
|
|
236940
|
-
kind: "action",
|
|
236941
|
-
label: "Start cleaning",
|
|
236942
|
-
icon: "play",
|
|
236943
|
-
enabled: true
|
|
236944
|
-
},
|
|
236945
|
-
{
|
|
236946
|
-
id: "pauseClean",
|
|
236947
|
-
kind: "action",
|
|
236948
|
-
label: "Pause cleaning",
|
|
236949
|
-
icon: "pause",
|
|
236950
|
-
enabled: true
|
|
236951
|
-
},
|
|
236952
|
-
{
|
|
236953
|
-
id: "stop",
|
|
236954
|
-
kind: "action",
|
|
236955
|
-
label: "Stop",
|
|
236956
|
-
icon: "hand",
|
|
236957
|
-
enabled: true
|
|
236958
|
-
},
|
|
236959
|
-
{
|
|
236960
|
-
id: "goHome",
|
|
236961
|
-
kind: "action",
|
|
236962
|
-
label: "Return to dock",
|
|
236963
|
-
icon: "home",
|
|
236964
|
-
enabled: true
|
|
236965
|
-
},
|
|
236966
|
-
{
|
|
236967
|
-
id: "locate",
|
|
236968
|
-
kind: "action",
|
|
236969
|
-
label: "Locate",
|
|
236970
|
-
icon: "bell",
|
|
236971
|
-
enabled: true
|
|
236972
|
-
},
|
|
236973
|
-
{
|
|
236974
|
-
id: "spotClean",
|
|
236975
|
-
kind: "action",
|
|
236976
|
-
label: "Spot clean here",
|
|
236977
|
-
icon: "crosshair",
|
|
236978
|
-
enabled: true
|
|
236979
|
-
},
|
|
236980
|
-
{
|
|
236981
|
-
id: "findPet",
|
|
236982
|
-
kind: "action",
|
|
236983
|
-
label: "Find pet",
|
|
236984
|
-
icon: "paw-print",
|
|
236985
|
-
enabled: true
|
|
236986
|
-
},
|
|
236987
|
-
{
|
|
236988
|
-
id: "personFollow",
|
|
236989
|
-
kind: "action",
|
|
236990
|
-
label: "Follow me",
|
|
236991
|
-
icon: "footprints",
|
|
236992
|
-
enabled: true
|
|
236993
|
-
},
|
|
236994
|
-
{
|
|
236995
|
-
id: "dockWash",
|
|
236996
|
-
kind: "action",
|
|
236997
|
-
label: "Wash mop",
|
|
236998
|
-
icon: "droplets",
|
|
236999
|
-
enabled: true
|
|
237000
|
-
},
|
|
237001
|
-
{
|
|
237002
|
-
id: "autoEmpty",
|
|
237003
|
-
kind: "action",
|
|
237004
|
-
label: "Empty dustbin",
|
|
237005
|
-
icon: "trash-2",
|
|
237006
|
-
enabled: true
|
|
237007
|
-
},
|
|
237008
|
-
{
|
|
237009
|
-
id: "flashOn",
|
|
237010
|
-
kind: "action",
|
|
237011
|
-
label: "Flash on",
|
|
237012
|
-
icon: "flashlight",
|
|
237013
|
-
enabled: true
|
|
237014
|
-
},
|
|
237015
|
-
{
|
|
237016
|
-
id: "flashOff",
|
|
237017
|
-
kind: "action",
|
|
237018
|
-
label: "Flash off",
|
|
237019
|
-
icon: "flashlight-off",
|
|
237020
|
-
enabled: true
|
|
237021
|
-
},
|
|
237022
|
-
{
|
|
237023
|
-
id: "sound:meow",
|
|
237024
|
-
kind: "sound",
|
|
237025
|
-
label: "Meow",
|
|
237026
|
-
icon: "cat",
|
|
237027
|
-
soundId: 684,
|
|
237028
|
-
enabled: true
|
|
237029
|
-
},
|
|
237030
|
-
{
|
|
237031
|
-
id: "sound:bark",
|
|
237032
|
-
kind: "sound",
|
|
237033
|
-
label: "Woof",
|
|
237034
|
-
icon: "dog",
|
|
237035
|
-
soundId: 685,
|
|
237036
|
-
enabled: true
|
|
237037
|
-
},
|
|
237038
|
-
{
|
|
237039
|
-
id: "sound:footsteps",
|
|
237040
|
-
kind: "sound",
|
|
237041
|
-
label: "Footsteps",
|
|
237042
|
-
icon: "footprints",
|
|
237043
|
-
soundId: 686,
|
|
237044
|
-
enabled: true
|
|
237045
|
-
},
|
|
237046
|
-
{
|
|
237047
|
-
id: "sound:purring",
|
|
237048
|
-
kind: "sound",
|
|
237049
|
-
label: "Purring",
|
|
237050
|
-
icon: "cat",
|
|
237051
|
-
soundId: 687,
|
|
237052
|
-
enabled: true
|
|
237053
|
-
},
|
|
237054
|
-
{
|
|
237055
|
-
id: "sound:tickTock",
|
|
237056
|
-
kind: "sound",
|
|
237057
|
-
label: "Tick-tock",
|
|
237058
|
-
icon: "clock",
|
|
237059
|
-
soundId: 688,
|
|
237060
|
-
enabled: true
|
|
237061
|
-
}
|
|
237062
|
-
];
|
|
237063
|
-
var NavigationPointSchema = zod.z.object({
|
|
237064
|
-
x: zod.z.number(),
|
|
237065
|
-
y: zod.z.number()
|
|
237066
|
-
});
|
|
237067
|
-
var NavigationFeaturesSchema = zod.z.object({
|
|
237068
|
-
move: zod.z.boolean(),
|
|
237069
|
-
stop: zod.z.boolean(),
|
|
237070
|
-
goToPoint: zod.z.boolean(),
|
|
237071
|
-
runAction: zod.z.boolean(),
|
|
237072
|
-
playSound: zod.z.boolean(),
|
|
237073
|
-
light: zod.z.boolean(),
|
|
237074
|
-
lightMode: zod.z.boolean()
|
|
237075
|
-
});
|
|
237076
|
-
var NavigationLightModeSchema = zod.z.enum(["auto", "manual"]);
|
|
237077
|
-
var NAVIGATION_LIGHT_LEVEL_MIN = 40;
|
|
237078
|
-
var NAVIGATION_LIGHT_LEVEL_MAX = 100;
|
|
237079
|
-
var NavigationModeSchema = zod.z.enum([
|
|
237080
|
-
"idle",
|
|
237081
|
-
"cleaning",
|
|
237082
|
-
"spot",
|
|
237083
|
-
"following",
|
|
237084
|
-
"goto",
|
|
237085
|
-
"returning",
|
|
237086
|
-
"paused",
|
|
237087
|
-
"unknown"
|
|
237088
|
-
]);
|
|
237089
|
-
var NavigationStatusSchema = zod.z.object({
|
|
237090
|
-
mode: NavigationModeSchema,
|
|
237091
|
-
following: zod.z.boolean(),
|
|
237092
|
-
flash: zod.z.boolean(),
|
|
237093
|
-
lightMode: NavigationLightModeSchema,
|
|
237094
|
-
lightLevel: zod.z.number().min(40).max(100),
|
|
237095
|
-
/** Ms epoch when the slice was last updated. */
|
|
237096
|
-
lastChangedAt: zod.z.number()
|
|
237097
|
-
});
|
|
237098
|
-
var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: zod.z.number() });
|
|
237099
|
-
var navigationCapability = {
|
|
237100
|
-
name: "navigation",
|
|
237101
|
-
scope: "device",
|
|
237102
|
-
deviceNative: true,
|
|
237103
|
-
mode: "singleton",
|
|
237104
|
-
deviceTypes: [require_sleep.DeviceType.Camera],
|
|
237105
|
-
deviceConfig: { ui: {
|
|
237106
|
-
kind: "widget",
|
|
237107
|
-
widgetId: "host/navigation-panel",
|
|
237108
|
-
tab: "navigation",
|
|
237109
|
-
topTab: true,
|
|
237110
|
-
label: "Navigation",
|
|
237111
|
-
order: 0
|
|
237112
|
-
} },
|
|
237113
|
-
methods: {
|
|
237114
|
-
/**
|
|
237115
|
-
* Momentary drive nudge (the robot moves). `protected` — mirrors
|
|
237116
|
-
* `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
|
|
237117
|
-
* path) works for any authenticated user, not admin-only. The UI sends
|
|
237118
|
-
* these at ~1 Hz while a control is held; the provider forwards each one to
|
|
237119
|
-
* a single drive write WITHOUT debouncing.
|
|
237120
|
-
*/
|
|
237121
|
-
move: require_sleep.method(NavigationMoveCommandSchema.extend({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
237122
|
-
/** Halt all motion immediately (zero drive vector). */
|
|
237123
|
-
stop: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
237124
|
-
/** Send the robot to a point on its live map. */
|
|
237125
|
-
goToPoint: require_sleep.method(NavigationPointSchema.extend({ deviceId: zod.z.number() }), zod.z.void(), { kind: "mutation" }),
|
|
237126
|
-
/**
|
|
237127
|
-
* Enumerate the discrete controls THIS device supports (data-driven UI +
|
|
237128
|
-
* PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
|
|
237129
|
-
*/
|
|
237130
|
-
listActions: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(NavigationActionEntrySchema)),
|
|
237131
|
-
/**
|
|
237132
|
-
* Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
|
|
237133
|
-
* unsupported action ids are rejected by the provider.
|
|
237134
|
-
*/
|
|
237135
|
-
runAction: require_sleep.method(zod.z.object({
|
|
237136
|
-
deviceId: zod.z.number(),
|
|
237137
|
-
actionId: NavigationActionIdSchema
|
|
237138
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
237139
|
-
/** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
|
|
237140
|
-
playSound: require_sleep.method(zod.z.object({
|
|
237141
|
-
deviceId: zod.z.number(),
|
|
237142
|
-
soundId: zod.z.number().int()
|
|
237143
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
237144
|
-
/**
|
|
237145
|
-
* Turn the on-camera fill light on / off (the `OpenFullLight` control —
|
|
237146
|
-
* works anytime, no active stream required).
|
|
237147
|
-
*/
|
|
237148
|
-
setLightOn: require_sleep.method(zod.z.object({
|
|
237149
|
-
deviceId: zod.z.number(),
|
|
237150
|
-
on: zod.z.boolean()
|
|
237151
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
237152
|
-
/**
|
|
237153
|
-
* Set the fill-light mode (auto vs manual). `manual` optionally carries the
|
|
237154
|
-
* initial `level`. The auto/manual + level control is a CAMERA-service
|
|
237155
|
-
* action that generally needs an active camera stream/monitor session — the
|
|
237156
|
-
* UI shows the manual level slider ONLY when `mode === 'manual'`.
|
|
237157
|
-
*/
|
|
237158
|
-
setLightMode: require_sleep.method(zod.z.object({
|
|
237159
|
-
deviceId: zod.z.number(),
|
|
237160
|
-
mode: NavigationLightModeSchema,
|
|
237161
|
-
level: zod.z.number().min(40).max(100).optional()
|
|
237162
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
237163
|
-
/** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
|
|
237164
|
-
setLightLevel: require_sleep.method(zod.z.object({
|
|
237165
|
-
deviceId: zod.z.number(),
|
|
237166
|
-
level: zod.z.number().min(40).max(100)
|
|
237167
|
-
}), zod.z.void(), { kind: "mutation" }),
|
|
237168
|
-
/**
|
|
237169
|
-
* Per-device FEATURE-FLAG report for the general primitives — drives which
|
|
237170
|
-
* controls the UI shows (the per-entry flags for the dictionary come back on
|
|
237171
|
-
* `listActions`).
|
|
237172
|
-
*/
|
|
237173
|
-
getFeatures: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), NavigationFeaturesSchema)
|
|
237174
|
-
},
|
|
237175
|
-
events: { onStatusChanged: { data: zod.z.object({
|
|
237176
|
-
deviceId: zod.z.number(),
|
|
237177
|
-
status: NavigationStatusSchema
|
|
237178
|
-
}) } },
|
|
237179
|
-
status: {
|
|
237180
|
-
schema: NavigationStatusSchema,
|
|
237181
|
-
kind: "push"
|
|
237182
|
-
},
|
|
237183
|
-
/**
|
|
237184
|
-
* Runtime-state slice mirrored by the kernel. The navigation panel watches it
|
|
237185
|
-
* for live mode / follow / flash changes.
|
|
237186
|
-
*/
|
|
237187
|
-
runtimeState: NavigationRuntimeStateSchema,
|
|
237188
|
-
/**
|
|
237189
|
-
* Runtime-state durability: **session** — like `vacuum-control`, a restored
|
|
237190
|
-
* `mode: cleaning` / `following: true` is a robot that is not actually doing
|
|
237191
|
-
* that. The live handle re-publishes on connect.
|
|
237192
|
-
*
|
|
237193
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
237194
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
237195
|
-
*/
|
|
237196
|
-
durability: "session"
|
|
237197
|
-
};
|
|
237198
238156
|
var rebootCapability = {
|
|
237199
238157
|
name: "reboot",
|
|
237200
238158
|
scope: "device",
|
|
@@ -243303,6 +244261,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
243303
244261
|
storage: "storage",
|
|
243304
244262
|
storageEvictable: "storage-evictable",
|
|
243305
244263
|
storageMigration: "storage-migration",
|
|
244264
|
+
storageOccupancy: "storage-occupancy",
|
|
243306
244265
|
storageProvider: "storage-provider",
|
|
243307
244266
|
streamBroker: "stream-broker",
|
|
243308
244267
|
streamCatalog: "stream-catalog",
|
|
@@ -243847,6 +244806,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
243847
244806
|
key: "storageMigration",
|
|
243848
244807
|
name: "storage-migration"
|
|
243849
244808
|
},
|
|
244809
|
+
{
|
|
244810
|
+
key: "storageOccupancy",
|
|
244811
|
+
name: "storage-occupancy"
|
|
244812
|
+
},
|
|
243850
244813
|
{
|
|
243851
244814
|
key: "storageProvider",
|
|
243852
244815
|
name: "storage-provider"
|
|
@@ -244082,6 +245045,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
244082
245045
|
storageCapability,
|
|
244083
245046
|
storageEvictableCapability,
|
|
244084
245047
|
storageMigrationCapability,
|
|
245048
|
+
storageOccupancyCapability,
|
|
244085
245049
|
storageProviderCapability,
|
|
244086
245050
|
streamBrokerCapability,
|
|
244087
245051
|
streamCatalogCapability,
|
|
@@ -249054,6 +250018,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
249054
250018
|
addonId: null,
|
|
249055
250019
|
access: "view"
|
|
249056
250020
|
},
|
|
250021
|
+
"storage.listDrainProgress": {
|
|
250022
|
+
capName: "storage",
|
|
250023
|
+
capScope: "system",
|
|
250024
|
+
addonId: null,
|
|
250025
|
+
access: "view"
|
|
250026
|
+
},
|
|
249057
250027
|
"storage.listLocationDeclarations": {
|
|
249058
250028
|
capName: "storage",
|
|
249059
250029
|
capScope: "system",
|
|
@@ -249198,6 +250168,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
249198
250168
|
addonId: null,
|
|
249199
250169
|
access: "view"
|
|
249200
250170
|
},
|
|
250171
|
+
"storageOccupancy.getOccupancy": {
|
|
250172
|
+
capName: "storage-occupancy",
|
|
250173
|
+
capScope: "system",
|
|
250174
|
+
addonId: null,
|
|
250175
|
+
access: "view"
|
|
250176
|
+
},
|
|
249201
250177
|
"storageProvider.abortUpload": {
|
|
249202
250178
|
capName: "storage-provider",
|
|
249203
250179
|
capScope: "system",
|
|
@@ -250332,6 +251308,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
250332
251308
|
"storage",
|
|
250333
251309
|
"storage-evictable",
|
|
250334
251310
|
"storage-migration",
|
|
251311
|
+
"storage-occupancy",
|
|
250335
251312
|
"storage-provider",
|
|
250336
251313
|
"stream-broker",
|
|
250337
251314
|
"stream-catalog",
|
|
@@ -250476,6 +251453,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
250476
251453
|
"storage",
|
|
250477
251454
|
"storage-evictable",
|
|
250478
251455
|
"storage-migration",
|
|
251456
|
+
"storage-occupancy",
|
|
250479
251457
|
"storage-provider",
|
|
250480
251458
|
"stream-broker",
|
|
250481
251459
|
"system",
|
|
@@ -253426,6 +254404,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
253426
254404
|
listLocationDeclarations: (input) => dispatch("storage", "listLocationDeclarations", "query", input),
|
|
253427
254405
|
upsertLocation: (input) => dispatch("storage", "upsertLocation", "mutation", input),
|
|
253428
254406
|
deleteLocation: (input) => dispatch("storage", "deleteLocation", "mutation", input),
|
|
254407
|
+
listDrainProgress: (input) => dispatch("storage", "listDrainProgress", "query", input),
|
|
253429
254408
|
testLocation: (input) => dispatch("storage", "testLocation", "query", input),
|
|
253430
254409
|
listProviders: (input) => dispatch("storage", "listProviders", "query", input),
|
|
253431
254410
|
testConfig: (input) => dispatch("storage", "testConfig", "query", input)
|
|
@@ -256585,6 +257564,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
256585
257564
|
exports.IntercomStatusSchema = IntercomStatusSchema;
|
|
256586
257565
|
exports.KNOWN_CAP_NAMES = KNOWN_CAP_NAMES;
|
|
256587
257566
|
exports.KeyEventSchema = KeyEventSchema;
|
|
257567
|
+
exports.LEGACY_READ_ONLY_CONFIG_KEY = LEGACY_READ_ONLY_CONFIG_KEY;
|
|
256588
257568
|
exports.LOAD_CONTRIBUTION_ATTRIBUTIONS = LOAD_CONTRIBUTION_ATTRIBUTIONS;
|
|
256589
257569
|
exports.LOAD_CONTRIBUTION_ROLES = LOAD_CONTRIBUTION_ROLES;
|
|
256590
257570
|
exports.LOG_CHANNEL_TICK_MS = LOG_CHANNEL_TICK_MS;
|
|
@@ -257036,6 +258016,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257036
258016
|
exports.SOURCE_DEVICE_TYPES = SOURCE_DEVICE_TYPES;
|
|
257037
258017
|
exports.SOURCE_INFO_METADATA_KEY = SOURCE_INFO_METADATA_KEY;
|
|
257038
258018
|
exports.STORAGE_ACCESS_FALLBACK = STORAGE_ACCESS_FALLBACK;
|
|
258019
|
+
exports.STORAGE_BYTES_PER_GB = STORAGE_BYTES_PER_GB;
|
|
258020
|
+
exports.STORAGE_LOCATION_MODES = STORAGE_LOCATION_MODES;
|
|
257039
258021
|
exports.STREAM_PROFILE_META = STREAM_PROFILE_META;
|
|
257040
258022
|
exports.STREAM_QUALITY_LABELS = STREAM_QUALITY_LABELS;
|
|
257041
258023
|
exports.SUB_DETECTION_TYPES = SUB_DETECTION_TYPES;
|
|
@@ -257097,9 +258079,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257097
258079
|
exports.StorageCleanupJobSchema = StorageCleanupJobSchema;
|
|
257098
258080
|
exports.StorageCleanupPhaseSchema = StorageCleanupPhaseSchema;
|
|
257099
258081
|
exports.StorageCleanupStatusInputSchema = StorageCleanupStatusInputSchema;
|
|
258082
|
+
exports.StorageDrainProgressSchema = StorageDrainProgressSchema;
|
|
257100
258083
|
exports.StorageEndDownloadInputSchema = EndDownloadInputSchema;
|
|
258084
|
+
exports.StorageEvictionPolicySchema = StorageEvictionPolicySchema;
|
|
257101
258085
|
exports.StorageFinalizeUploadInputSchema = FinalizeUploadInputSchema;
|
|
257102
258086
|
exports.StorageLocationDeclarationSchema = StorageLocationDeclarationSchema;
|
|
258087
|
+
exports.StorageLocationModeSchema = StorageLocationModeSchema;
|
|
257103
258088
|
exports.StorageLocationRefSchema = StorageLocationRefSchema;
|
|
257104
258089
|
exports.StorageLocationSchema = StorageLocationSchema;
|
|
257105
258090
|
exports.StorageLocationTypeSchema = StorageLocationTypeSchema;
|
|
@@ -257123,6 +258108,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257123
258108
|
exports.StorageMigrationPlanSchema = StorageMigrationPlanSchema;
|
|
257124
258109
|
exports.StorageMigrationResidueSchema = StorageMigrationResidueSchema;
|
|
257125
258110
|
exports.StorageMigrationSourcesSchema = StorageMigrationSourcesSchema;
|
|
258111
|
+
exports.StorageOccupancyReportSchema = StorageOccupancyReportSchema;
|
|
257126
258112
|
exports.StorageProviderInfoSchema = ProviderInfoSchema;
|
|
257127
258113
|
exports.StorageReadChunkInputSchema = ReadChunkInputSchema;
|
|
257128
258114
|
exports.StorageTestLocationResultSchema = TestLocationResultSchema;
|
|
@@ -257404,6 +258390,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257404
258390
|
exports.event = require_sleep.event;
|
|
257405
258391
|
exports.eventEmitterCapability = eventEmitterCapability;
|
|
257406
258392
|
exports.eventsCapability = eventsCapability;
|
|
258393
|
+
exports.evictionPolicyForMode = evictionPolicyForMode;
|
|
258394
|
+
exports.evictionPolicyOfLocation = evictionPolicyOfLocation;
|
|
257407
258395
|
exports.expandCapMethods = require_sleep.expandCapMethods;
|
|
257408
258396
|
exports.extractNestedAddonId = extractNestedAddonId;
|
|
257409
258397
|
exports.extractSourceInfoFromMetadata = extractSourceInfoFromMetadata;
|
|
@@ -257419,6 +258407,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257419
258407
|
exports.formatForBackend = formatForBackend;
|
|
257420
258408
|
exports.formatForRuntime = formatForRuntime;
|
|
257421
258409
|
exports.gasCapability = gasCapability;
|
|
258410
|
+
exports.gbToBytes = gbToBytes;
|
|
257422
258411
|
exports.generateAutomationBlock = generateAutomationBlock;
|
|
257423
258412
|
exports.getAudioMacroClassIds = getAudioMacroClassIds;
|
|
257424
258413
|
exports.getByPath = getByPath;
|
|
@@ -257463,12 +258452,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257463
258452
|
exports.isSecretConfigField = isSecretConfigField;
|
|
257464
258453
|
exports.isSoftwareDecode = require_canonical_hash.isSoftwareDecode;
|
|
257465
258454
|
exports.isSourceCap = isSourceCap;
|
|
258455
|
+
exports.isStorageLocationMode = isStorageLocationMode;
|
|
257466
258456
|
exports.isSystemDelivery = isSystemDelivery;
|
|
257467
258457
|
exports.isVoidInput = isVoidInput;
|
|
257468
258458
|
exports.jobKindSchema = jobKindSchema;
|
|
257469
258459
|
exports.kebabToCamel = kebabToCamel;
|
|
257470
258460
|
exports.knownValues = knownValues;
|
|
257471
258461
|
exports.lawnMowerControlCapability = lawnMowerControlCapability;
|
|
258462
|
+
exports.legacyModeOf = legacyModeOf;
|
|
257472
258463
|
exports.lifecycleJobSchema = lifecycleJobSchema;
|
|
257473
258464
|
exports.lifecycleJobScopeSchema = lifecycleJobScopeSchema;
|
|
257474
258465
|
exports.lifecycleJobStateSchema = lifecycleJobStateSchema;
|
|
@@ -257491,6 +258482,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257491
258482
|
exports.markdownToHtmlLite = markdownToHtmlLite;
|
|
257492
258483
|
exports.markdownToText = markdownToText;
|
|
257493
258484
|
exports.maskUrlCredentials = maskUrlCredentials;
|
|
258485
|
+
exports.mayReadLocation = mayReadLocation;
|
|
258486
|
+
exports.mayWriteToLocation = mayWriteToLocation;
|
|
257494
258487
|
exports.mediaPlayerCapability = mediaPlayerCapability;
|
|
257495
258488
|
exports.mergeSourceInfo = mergeSourceInfo;
|
|
257496
258489
|
exports.meshNetworkCapability = meshNetworkCapability;
|
|
@@ -257498,6 +258491,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257498
258491
|
exports.methodAccessForHttpMethod = methodAccessForHttpMethod;
|
|
257499
258492
|
exports.metricsProviderCapability = metricsProviderCapability;
|
|
257500
258493
|
exports.migrateLegacyDeviceSignalConfig = migrateLegacyDeviceSignalConfig;
|
|
258494
|
+
exports.modeMayRead = modeMayRead;
|
|
258495
|
+
exports.modeMayWrite = modeMayWrite;
|
|
257501
258496
|
exports.modelConvertCapability = modelConvertCapability;
|
|
257502
258497
|
exports.modelDistributorCapability = modelDistributorCapability;
|
|
257503
258498
|
exports.modelFormatForRuntime = modelFormatForRuntime;
|
|
@@ -257593,6 +258588,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257593
258588
|
exports.resolveEgressDecodeHwAccel = resolveEgressDecodeHwAccel;
|
|
257594
258589
|
exports.resolveFormat = resolveFormat;
|
|
257595
258590
|
exports.resolveHydratedFieldValue = require_sleep.resolveHydratedFieldValue;
|
|
258591
|
+
exports.resolveLocationMode = resolveLocationMode;
|
|
257596
258592
|
exports.resolveMethodAuth = resolveMethodAuth;
|
|
257597
258593
|
exports.resolveModelFormat = resolveModelFormat;
|
|
257598
258594
|
exports.resolveMutate = resolveMutate;
|
|
@@ -257639,6 +258635,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257639
258635
|
exports.storageCapability = storageCapability;
|
|
257640
258636
|
exports.storageEvictableCapability = storageEvictableCapability;
|
|
257641
258637
|
exports.storageMigrationCapability = storageMigrationCapability;
|
|
258638
|
+
exports.storageOccupancyCapability = storageOccupancyCapability;
|
|
257642
258639
|
exports.storageProviderCapability = storageProviderCapability;
|
|
257643
258640
|
exports.streamBrokerCapability = streamBrokerCapability;
|
|
257644
258641
|
exports.streamCatalogCapability = streamCatalogCapability;
|
|
@@ -257646,6 +258643,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257646
258643
|
exports.streamPixels = streamPixels;
|
|
257647
258644
|
exports.streamQualityLabel = streamQualityLabel;
|
|
257648
258645
|
exports.stripRetiredBandPreBufferSec = stripRetiredBandPreBufferSec;
|
|
258646
|
+
exports.strippedOfLegacyReadOnly = strippedOfLegacyReadOnly;
|
|
257649
258647
|
exports.subKindsOf = subKindsOf;
|
|
257650
258648
|
exports.summarisePrivacyAudio = summarisePrivacyAudio;
|
|
257651
258649
|
exports.summarizeEffectiveScope = summarizeEffectiveScope;
|
|
@@ -257696,6 +258694,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
257696
258694
|
exports.wiringNodeHealthSchema = wiringNodeHealthSchema;
|
|
257697
258695
|
exports.wiringProbeKindSchema = wiringProbeKindSchema;
|
|
257698
258696
|
exports.wiringProbeResultSchema = wiringProbeResultSchema;
|
|
258697
|
+
exports.withLocationMode = withLocationMode;
|
|
257699
258698
|
exports.zodEntriesToConfigUI = zodEntriesToConfigUI;
|
|
257700
258699
|
exports.zoneAnalyticsCapability = zoneAnalyticsCapability;
|
|
257701
258700
|
exports.zoneRulesCapability = zoneRulesCapability;
|