@camstack/system 1.2.61 → 1.2.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-aggregation.d.ts +49 -2
- package/dist/builtins/device-manager/device-manager.addon.js +60 -5
- package/dist/builtins/device-manager/device-manager.addon.mjs +60 -5
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-BN681PgO.js → dist-DgPUarcs.js} +135 -0
- package/dist/{dist-L6QPJWsr.mjs → dist-DnKpFaP1.mjs} +135 -0
- package/dist/index.js +207 -4
- package/dist/index.mjs +204 -5
- package/dist/kernel/addon-residue.d.ts +49 -0
- package/dist/kernel/index.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Nt as parseJsonObject, f as StorageLocationTypeSchema, it as storageCapability, nt as settingsStoreCapability, pt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Nt as parseJsonObject, f as StorageLocationTypeSchema, it as storageCapability, nt as settingsStoreCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
|
|
2
2
|
import * as path$1 from "node:path";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import { buildStorageLocationRegistry } from "@camstack/system";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DgPUarcs.js");
|
|
7
7
|
//#region src/builtins/system-config/system-config.addon.ts
|
|
8
8
|
/**
|
|
9
9
|
* Built-in `system-config` addon — Phase 4 of the settings redesign.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { At as hydrateSchema, ft as errMsg, pt as BaseAddon } from "../../dist-
|
|
1
|
+
import { At as hydrateSchema, ft as errMsg, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
|
|
2
2
|
//#region src/builtins/system-config/system-config.addon.ts
|
|
3
3
|
/**
|
|
4
4
|
* Built-in `system-config` addon — Phase 4 of the settings redesign.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DgPUarcs.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
let node_path = require("node:path");
|
|
9
9
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-
|
|
1
|
+
import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
import * as path$1 from "node:path";
|
|
4
4
|
import path from "node:path";
|
|
@@ -5434,6 +5434,69 @@ var StreamFormatSchema = zod.z.enum([
|
|
|
5434
5434
|
"mjpeg",
|
|
5435
5435
|
"rtsp"
|
|
5436
5436
|
]);
|
|
5437
|
+
/** A container `produceEventMedia` can emit. */
|
|
5438
|
+
var EventMediaKindSchema = zod.z.enum(["mp4", "gif"]);
|
|
5439
|
+
/**
|
|
5440
|
+
* One produced artifact, referenced by HANDLE.
|
|
5441
|
+
*
|
|
5442
|
+
* Never inline bytes: a produced clip is 200 KB–5 MB and every consumer of this
|
|
5443
|
+
* method is in another runner ([D9](../../../../docs/decisions/adr-0009.md),
|
|
5444
|
+
* [D18](../../../../docs/decisions/adr-0018.md) — cross-process media is fetched
|
|
5445
|
+
* on demand, compressed, by handle). `bytes` is here so a caller can decide
|
|
5446
|
+
* whether it wants the fetch at all.
|
|
5447
|
+
*/
|
|
5448
|
+
var EventMediaArtifactSchema = zod.z.object({
|
|
5449
|
+
kind: EventMediaKindSchema,
|
|
5450
|
+
/** Opaque, single-camera, short-lived. Redeem with `fetchEventMedia`. */
|
|
5451
|
+
handle: zod.z.string(),
|
|
5452
|
+
/**
|
|
5453
|
+
* The node holding the bytes — the ROUTING key for `fetchEventMedia`.
|
|
5454
|
+
*
|
|
5455
|
+
* `stream-broker` is a singleton cap and an unpinned call never leaves the
|
|
5456
|
+
* hub, so a handle produced on an agent's broker would be redeemed against
|
|
5457
|
+
* the hub's store and come back `null`. Same contract, same field name and
|
|
5458
|
+
* the same reason as `FrameHandleSchema.nodeId`: the producer stamps where it
|
|
5459
|
+
* lives and the consumer pins to it.
|
|
5460
|
+
*/
|
|
5461
|
+
nodeId: zod.z.string(),
|
|
5462
|
+
mime: zod.z.string(),
|
|
5463
|
+
bytes: zod.z.number().int(),
|
|
5464
|
+
width: zod.z.number().int(),
|
|
5465
|
+
height: zod.z.number().int()
|
|
5466
|
+
});
|
|
5467
|
+
/**
|
|
5468
|
+
* What a production actually covered — the answer to the only question an
|
|
5469
|
+
* operator asks about a notification clip.
|
|
5470
|
+
*
|
|
5471
|
+
* `fromTs`/`toTs` are WALL CLOCK, derived from the ring's own packet timeline,
|
|
5472
|
+
* so a caller can state "this clip starts 4.1 s before the event" instead of
|
|
5473
|
+
* inferring it from a duration. A production whose `fromTs` is later than the
|
|
5474
|
+
* event is a production with no pre-roll, and that is exactly the defect this
|
|
5475
|
+
* method exists to make visible rather than plausible.
|
|
5476
|
+
*/
|
|
5477
|
+
var EventMediaCoverageSchema = zod.z.object({
|
|
5478
|
+
fromTs: zod.z.number(),
|
|
5479
|
+
toTs: zod.z.number(),
|
|
5480
|
+
/** Encoded packets in the muxed window. */
|
|
5481
|
+
packets: zod.z.number().int()
|
|
5482
|
+
});
|
|
5483
|
+
/**
|
|
5484
|
+
* The result of ONE cut, in every container the caller asked for.
|
|
5485
|
+
*
|
|
5486
|
+
* Every artifact in `media` came out of the SAME window of the SAME rendition —
|
|
5487
|
+
* that is the whole reason this is one method rather than one call per format.
|
|
5488
|
+
* A consumer attaching a gif and a video can no longer show two different
|
|
5489
|
+
* moments, because it never chose two sources.
|
|
5490
|
+
*/
|
|
5491
|
+
var EventMediaProductionSchema = zod.z.object({
|
|
5492
|
+
media: zod.z.array(EventMediaArtifactSchema).readonly(),
|
|
5493
|
+
coverage: EventMediaCoverageSchema,
|
|
5494
|
+
/** The rendition actually cut from — what the default or the fallback chose. */
|
|
5495
|
+
profile: CamProfileSchema,
|
|
5496
|
+
/** `copy` = the camera's own H.264, untouched. `encode` = re-encoded (H.265
|
|
5497
|
+
* source, a downscale, or a playback rate other than 1). */
|
|
5498
|
+
video: zod.z.enum(["copy", "encode"])
|
|
5499
|
+
});
|
|
5437
5500
|
var RtspRestreamEntrySchema = zod.z.object({
|
|
5438
5501
|
brokerId: zod.z.string(),
|
|
5439
5502
|
url: zod.z.string(),
|
|
@@ -5871,6 +5934,66 @@ var streamBrokerCapability = {
|
|
|
5871
5934
|
kind: "mutation",
|
|
5872
5935
|
auth: "admin"
|
|
5873
5936
|
}),
|
|
5937
|
+
/**
|
|
5938
|
+
* THE production point for event media — one window, every container.
|
|
5939
|
+
*
|
|
5940
|
+
* `renderPreBufferClip` renders ONE format per call, so a consumer wanting a
|
|
5941
|
+
* gif and a video made two calls and got two windows. On 2026-08-08 the
|
|
5942
|
+
* operator received exactly that: a gif around a doorbell press and an mp4
|
|
5943
|
+
* of the six seconds after it. This method cuts once and derives the rest,
|
|
5944
|
+
* so "the attachments agree" is a property of the service instead of a
|
|
5945
|
+
* discipline every consumer has to keep.
|
|
5946
|
+
*
|
|
5947
|
+
* It is also the only place that owns the packets. The broker already
|
|
5948
|
+
* retains an AnnexB ring per camera (`setClipRetention`, D32) — the
|
|
5949
|
+
* prebuffer — so a production reaches BACKWARDS from the event with no
|
|
5950
|
+
* process to spawn and nothing kept warm. It waits out `postSeconds`, muxes
|
|
5951
|
+
* the window once (COPYING the camera's own H.264 whenever nothing is being
|
|
5952
|
+
* changed), derives any gif from that same mux, and returns HANDLES.
|
|
5953
|
+
*
|
|
5954
|
+
* Intended for every future consumer, not just notifications: a clips API, a
|
|
5955
|
+
* retrain export and an event export all want the same window of the same
|
|
5956
|
+
* camera and must not each grow their own renderer.
|
|
5957
|
+
*/
|
|
5958
|
+
produceEventMedia: method(zod.z.object({
|
|
5959
|
+
deviceId: zod.z.number(),
|
|
5960
|
+
/** Absent = the largest H.264 rendition at or below 1080p, which is
|
|
5961
|
+
* also the one that can be copied. Falls back to whatever the ring
|
|
5962
|
+
* actually retained, and the answer says which. */
|
|
5963
|
+
profile: CamProfileSchema.optional(),
|
|
5964
|
+
aroundMs: zod.z.number(),
|
|
5965
|
+
preSeconds: zod.z.number().min(0).max(20).default(4),
|
|
5966
|
+
postSeconds: zod.z.number().min(0).max(20).default(6),
|
|
5967
|
+
kinds: zod.z.array(EventMediaKindSchema).min(1).default(["mp4"]),
|
|
5968
|
+
/** GIF geometry. The video keeps the source's own. */
|
|
5969
|
+
gifMaxWidth: zod.z.number().int().min(120).max(1280).default(640),
|
|
5970
|
+
gifFps: zod.z.number().int().min(1).max(15).default(8),
|
|
5971
|
+
/**
|
|
5972
|
+
* Playback rate, applied to EVERY container so they stay one clip.
|
|
5973
|
+
* `1` is real time and is what allows the copy branch.
|
|
5974
|
+
*/
|
|
5975
|
+
speed: zod.z.number().min(1).max(8).default(1)
|
|
5976
|
+
}), EventMediaProductionSchema, {
|
|
5977
|
+
kind: "mutation",
|
|
5978
|
+
auth: "admin"
|
|
5979
|
+
}),
|
|
5980
|
+
/**
|
|
5981
|
+
* Redeem a {@link EventMediaArtifactSchema} handle for its bytes.
|
|
5982
|
+
*
|
|
5983
|
+
* Separate from the production on purpose (D18): the producing runner keeps
|
|
5984
|
+
* the artifact for a few minutes, and a consumer that only wanted the
|
|
5985
|
+
* coverage never moves a megabyte. `null` once it has expired — a handle is
|
|
5986
|
+
* short-lived by design and a caller that waited too long must see that
|
|
5987
|
+
* rather than a zero-length file.
|
|
5988
|
+
*/
|
|
5989
|
+
fetchEventMedia: method(zod.z.object({ handle: zod.z.string() }), zod.z.object({
|
|
5990
|
+
base64: zod.z.string(),
|
|
5991
|
+
mime: zod.z.string(),
|
|
5992
|
+
bytes: zod.z.number().int()
|
|
5993
|
+
}).nullable(), {
|
|
5994
|
+
kind: "mutation",
|
|
5995
|
+
auth: "admin"
|
|
5996
|
+
}),
|
|
5874
5997
|
listAllCameraStreams: method(zod.z.void(), zod.z.array(CameraStreamSchema).readonly()),
|
|
5875
5998
|
listAllProfileSlots: method(zod.z.void(), zod.z.array(ProfileSlotSchema).readonly()),
|
|
5876
5999
|
getBrokerStats: method(zod.z.object({ brokerId: zod.z.string() }), BrokerStatsSchema),
|
|
@@ -31235,6 +31358,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
31235
31358
|
addonId: null,
|
|
31236
31359
|
access: "create"
|
|
31237
31360
|
},
|
|
31361
|
+
"streamBroker.fetchEventMedia": {
|
|
31362
|
+
capName: "stream-broker",
|
|
31363
|
+
capScope: "system",
|
|
31364
|
+
addonId: null,
|
|
31365
|
+
access: "create"
|
|
31366
|
+
},
|
|
31238
31367
|
"streamBroker.getAllRtspEntries": {
|
|
31239
31368
|
capName: "stream-broker",
|
|
31240
31369
|
capScope: "system",
|
|
@@ -31319,6 +31448,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
31319
31448
|
addonId: null,
|
|
31320
31449
|
access: "create"
|
|
31321
31450
|
},
|
|
31451
|
+
"streamBroker.produceEventMedia": {
|
|
31452
|
+
capName: "stream-broker",
|
|
31453
|
+
capScope: "system",
|
|
31454
|
+
addonId: null,
|
|
31455
|
+
access: "create"
|
|
31456
|
+
},
|
|
31322
31457
|
"streamBroker.publishCameraStream": {
|
|
31323
31458
|
capName: "stream-broker",
|
|
31324
31459
|
capScope: "system",
|
|
@@ -5434,6 +5434,69 @@ var StreamFormatSchema = z.enum([
|
|
|
5434
5434
|
"mjpeg",
|
|
5435
5435
|
"rtsp"
|
|
5436
5436
|
]);
|
|
5437
|
+
/** A container `produceEventMedia` can emit. */
|
|
5438
|
+
var EventMediaKindSchema = z.enum(["mp4", "gif"]);
|
|
5439
|
+
/**
|
|
5440
|
+
* One produced artifact, referenced by HANDLE.
|
|
5441
|
+
*
|
|
5442
|
+
* Never inline bytes: a produced clip is 200 KB–5 MB and every consumer of this
|
|
5443
|
+
* method is in another runner ([D9](../../../../docs/decisions/adr-0009.md),
|
|
5444
|
+
* [D18](../../../../docs/decisions/adr-0018.md) — cross-process media is fetched
|
|
5445
|
+
* on demand, compressed, by handle). `bytes` is here so a caller can decide
|
|
5446
|
+
* whether it wants the fetch at all.
|
|
5447
|
+
*/
|
|
5448
|
+
var EventMediaArtifactSchema = z.object({
|
|
5449
|
+
kind: EventMediaKindSchema,
|
|
5450
|
+
/** Opaque, single-camera, short-lived. Redeem with `fetchEventMedia`. */
|
|
5451
|
+
handle: z.string(),
|
|
5452
|
+
/**
|
|
5453
|
+
* The node holding the bytes — the ROUTING key for `fetchEventMedia`.
|
|
5454
|
+
*
|
|
5455
|
+
* `stream-broker` is a singleton cap and an unpinned call never leaves the
|
|
5456
|
+
* hub, so a handle produced on an agent's broker would be redeemed against
|
|
5457
|
+
* the hub's store and come back `null`. Same contract, same field name and
|
|
5458
|
+
* the same reason as `FrameHandleSchema.nodeId`: the producer stamps where it
|
|
5459
|
+
* lives and the consumer pins to it.
|
|
5460
|
+
*/
|
|
5461
|
+
nodeId: z.string(),
|
|
5462
|
+
mime: z.string(),
|
|
5463
|
+
bytes: z.number().int(),
|
|
5464
|
+
width: z.number().int(),
|
|
5465
|
+
height: z.number().int()
|
|
5466
|
+
});
|
|
5467
|
+
/**
|
|
5468
|
+
* What a production actually covered — the answer to the only question an
|
|
5469
|
+
* operator asks about a notification clip.
|
|
5470
|
+
*
|
|
5471
|
+
* `fromTs`/`toTs` are WALL CLOCK, derived from the ring's own packet timeline,
|
|
5472
|
+
* so a caller can state "this clip starts 4.1 s before the event" instead of
|
|
5473
|
+
* inferring it from a duration. A production whose `fromTs` is later than the
|
|
5474
|
+
* event is a production with no pre-roll, and that is exactly the defect this
|
|
5475
|
+
* method exists to make visible rather than plausible.
|
|
5476
|
+
*/
|
|
5477
|
+
var EventMediaCoverageSchema = z.object({
|
|
5478
|
+
fromTs: z.number(),
|
|
5479
|
+
toTs: z.number(),
|
|
5480
|
+
/** Encoded packets in the muxed window. */
|
|
5481
|
+
packets: z.number().int()
|
|
5482
|
+
});
|
|
5483
|
+
/**
|
|
5484
|
+
* The result of ONE cut, in every container the caller asked for.
|
|
5485
|
+
*
|
|
5486
|
+
* Every artifact in `media` came out of the SAME window of the SAME rendition —
|
|
5487
|
+
* that is the whole reason this is one method rather than one call per format.
|
|
5488
|
+
* A consumer attaching a gif and a video can no longer show two different
|
|
5489
|
+
* moments, because it never chose two sources.
|
|
5490
|
+
*/
|
|
5491
|
+
var EventMediaProductionSchema = z.object({
|
|
5492
|
+
media: z.array(EventMediaArtifactSchema).readonly(),
|
|
5493
|
+
coverage: EventMediaCoverageSchema,
|
|
5494
|
+
/** The rendition actually cut from — what the default or the fallback chose. */
|
|
5495
|
+
profile: CamProfileSchema,
|
|
5496
|
+
/** `copy` = the camera's own H.264, untouched. `encode` = re-encoded (H.265
|
|
5497
|
+
* source, a downscale, or a playback rate other than 1). */
|
|
5498
|
+
video: z.enum(["copy", "encode"])
|
|
5499
|
+
});
|
|
5437
5500
|
var RtspRestreamEntrySchema = z.object({
|
|
5438
5501
|
brokerId: z.string(),
|
|
5439
5502
|
url: z.string(),
|
|
@@ -5871,6 +5934,66 @@ var streamBrokerCapability = {
|
|
|
5871
5934
|
kind: "mutation",
|
|
5872
5935
|
auth: "admin"
|
|
5873
5936
|
}),
|
|
5937
|
+
/**
|
|
5938
|
+
* THE production point for event media — one window, every container.
|
|
5939
|
+
*
|
|
5940
|
+
* `renderPreBufferClip` renders ONE format per call, so a consumer wanting a
|
|
5941
|
+
* gif and a video made two calls and got two windows. On 2026-08-08 the
|
|
5942
|
+
* operator received exactly that: a gif around a doorbell press and an mp4
|
|
5943
|
+
* of the six seconds after it. This method cuts once and derives the rest,
|
|
5944
|
+
* so "the attachments agree" is a property of the service instead of a
|
|
5945
|
+
* discipline every consumer has to keep.
|
|
5946
|
+
*
|
|
5947
|
+
* It is also the only place that owns the packets. The broker already
|
|
5948
|
+
* retains an AnnexB ring per camera (`setClipRetention`, D32) — the
|
|
5949
|
+
* prebuffer — so a production reaches BACKWARDS from the event with no
|
|
5950
|
+
* process to spawn and nothing kept warm. It waits out `postSeconds`, muxes
|
|
5951
|
+
* the window once (COPYING the camera's own H.264 whenever nothing is being
|
|
5952
|
+
* changed), derives any gif from that same mux, and returns HANDLES.
|
|
5953
|
+
*
|
|
5954
|
+
* Intended for every future consumer, not just notifications: a clips API, a
|
|
5955
|
+
* retrain export and an event export all want the same window of the same
|
|
5956
|
+
* camera and must not each grow their own renderer.
|
|
5957
|
+
*/
|
|
5958
|
+
produceEventMedia: method(z.object({
|
|
5959
|
+
deviceId: z.number(),
|
|
5960
|
+
/** Absent = the largest H.264 rendition at or below 1080p, which is
|
|
5961
|
+
* also the one that can be copied. Falls back to whatever the ring
|
|
5962
|
+
* actually retained, and the answer says which. */
|
|
5963
|
+
profile: CamProfileSchema.optional(),
|
|
5964
|
+
aroundMs: z.number(),
|
|
5965
|
+
preSeconds: z.number().min(0).max(20).default(4),
|
|
5966
|
+
postSeconds: z.number().min(0).max(20).default(6),
|
|
5967
|
+
kinds: z.array(EventMediaKindSchema).min(1).default(["mp4"]),
|
|
5968
|
+
/** GIF geometry. The video keeps the source's own. */
|
|
5969
|
+
gifMaxWidth: z.number().int().min(120).max(1280).default(640),
|
|
5970
|
+
gifFps: z.number().int().min(1).max(15).default(8),
|
|
5971
|
+
/**
|
|
5972
|
+
* Playback rate, applied to EVERY container so they stay one clip.
|
|
5973
|
+
* `1` is real time and is what allows the copy branch.
|
|
5974
|
+
*/
|
|
5975
|
+
speed: z.number().min(1).max(8).default(1)
|
|
5976
|
+
}), EventMediaProductionSchema, {
|
|
5977
|
+
kind: "mutation",
|
|
5978
|
+
auth: "admin"
|
|
5979
|
+
}),
|
|
5980
|
+
/**
|
|
5981
|
+
* Redeem a {@link EventMediaArtifactSchema} handle for its bytes.
|
|
5982
|
+
*
|
|
5983
|
+
* Separate from the production on purpose (D18): the producing runner keeps
|
|
5984
|
+
* the artifact for a few minutes, and a consumer that only wanted the
|
|
5985
|
+
* coverage never moves a megabyte. `null` once it has expired — a handle is
|
|
5986
|
+
* short-lived by design and a caller that waited too long must see that
|
|
5987
|
+
* rather than a zero-length file.
|
|
5988
|
+
*/
|
|
5989
|
+
fetchEventMedia: method(z.object({ handle: z.string() }), z.object({
|
|
5990
|
+
base64: z.string(),
|
|
5991
|
+
mime: z.string(),
|
|
5992
|
+
bytes: z.number().int()
|
|
5993
|
+
}).nullable(), {
|
|
5994
|
+
kind: "mutation",
|
|
5995
|
+
auth: "admin"
|
|
5996
|
+
}),
|
|
5874
5997
|
listAllCameraStreams: method(z.void(), z.array(CameraStreamSchema).readonly()),
|
|
5875
5998
|
listAllProfileSlots: method(z.void(), z.array(ProfileSlotSchema).readonly()),
|
|
5876
5999
|
getBrokerStats: method(z.object({ brokerId: z.string() }), BrokerStatsSchema),
|
|
@@ -31235,6 +31358,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
31235
31358
|
addonId: null,
|
|
31236
31359
|
access: "create"
|
|
31237
31360
|
},
|
|
31361
|
+
"streamBroker.fetchEventMedia": {
|
|
31362
|
+
capName: "stream-broker",
|
|
31363
|
+
capScope: "system",
|
|
31364
|
+
addonId: null,
|
|
31365
|
+
access: "create"
|
|
31366
|
+
},
|
|
31238
31367
|
"streamBroker.getAllRtspEntries": {
|
|
31239
31368
|
capName: "stream-broker",
|
|
31240
31369
|
capScope: "system",
|
|
@@ -31319,6 +31448,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
31319
31448
|
addonId: null,
|
|
31320
31449
|
access: "create"
|
|
31321
31450
|
},
|
|
31451
|
+
"streamBroker.produceEventMedia": {
|
|
31452
|
+
capName: "stream-broker",
|
|
31453
|
+
capScope: "system",
|
|
31454
|
+
addonId: null,
|
|
31455
|
+
access: "create"
|
|
31456
|
+
},
|
|
31322
31457
|
"streamBroker.publishCameraStream": {
|
|
31323
31458
|
capName: "stream-broker",
|
|
31324
31459
|
capScope: "system",
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("./dist-
|
|
3
|
+
const require_dist = require("./dist-DgPUarcs.js");
|
|
4
4
|
const require_model_download_service = require("./model-download-service-Cpc6SWzV.js");
|
|
5
5
|
const require_manifest_python_deps = require("./manifest-python-deps-d4acYXVV.js");
|
|
6
6
|
const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
|
|
@@ -3356,7 +3356,12 @@ var MANIFEST_VERSION = 1;
|
|
|
3356
3356
|
* workspace source. Trusting the dirent there finds zero addons on disk, which
|
|
3357
3357
|
* with pruning would empty a developer's manifest on the next boot.
|
|
3358
3358
|
*/
|
|
3359
|
-
|
|
3359
|
+
/** `@camstack/system` → `system`: the directory a package must live in. */
|
|
3360
|
+
function packageDirName$1(declaredName) {
|
|
3361
|
+
const slash = declaredName.lastIndexOf("/");
|
|
3362
|
+
return slash === -1 ? declaredName : declaredName.slice(slash + 1);
|
|
3363
|
+
}
|
|
3364
|
+
function isDirLike$1(p) {
|
|
3360
3365
|
try {
|
|
3361
3366
|
return node_fs.statSync(p).isDirectory();
|
|
3362
3367
|
} catch {
|
|
@@ -3490,10 +3495,10 @@ var AddonManifest = class {
|
|
|
3490
3495
|
/** Package name → version, for every ADDON package actually installed. */
|
|
3491
3496
|
const onDisk = /* @__PURE__ */ new Map();
|
|
3492
3497
|
for (const scope of node_fs.readdirSync(addonsDir, { withFileTypes: true })) {
|
|
3493
|
-
if (!scope.name.startsWith("@") || !isDirLike(node_path.join(addonsDir, scope.name))) continue;
|
|
3498
|
+
if (!scope.name.startsWith("@") || !isDirLike$1(node_path.join(addonsDir, scope.name))) continue;
|
|
3494
3499
|
const scopeDir = node_path.join(addonsDir, scope.name);
|
|
3495
3500
|
for (const pkg of node_fs.readdirSync(scopeDir, { withFileTypes: true })) {
|
|
3496
|
-
if (!isDirLike(node_path.join(scopeDir, pkg.name))) continue;
|
|
3501
|
+
if (!isDirLike$1(node_path.join(scopeDir, pkg.name))) continue;
|
|
3497
3502
|
const pkgJsonPath = node_path.join(scopeDir, pkg.name, "package.json");
|
|
3498
3503
|
if (!node_fs.existsSync(pkgJsonPath)) continue;
|
|
3499
3504
|
let pkgJson;
|
|
@@ -3507,6 +3512,7 @@ var AddonManifest = class {
|
|
|
3507
3512
|
if (!pkgJson.name || !pkgJson.version) continue;
|
|
3508
3513
|
const addons = pkgJson.camstack?.addons;
|
|
3509
3514
|
if (!Array.isArray(addons) || addons.length === 0) continue;
|
|
3515
|
+
if (packageDirName$1(pkgJson.name) !== pkg.name) continue;
|
|
3510
3516
|
onDisk.set(pkgJson.name, pkgJson.version);
|
|
3511
3517
|
}
|
|
3512
3518
|
}
|
|
@@ -5024,6 +5030,199 @@ function isDirEntry(entry, parentDir) {
|
|
|
5024
5030
|
return false;
|
|
5025
5031
|
}
|
|
5026
5032
|
//#endregion
|
|
5033
|
+
//#region src/kernel/addon-residue.ts
|
|
5034
|
+
/**
|
|
5035
|
+
* Residue quarantine for the ONE addon root.
|
|
5036
|
+
*
|
|
5037
|
+
* ── The bug class this closes ──────────────────────────────────────────────
|
|
5038
|
+
* `<dataDir>/addons/@camstack/<pkg>` is the only directory a node loads addons
|
|
5039
|
+
* from. Renaming a package inside it does NOT archive it — the directory name
|
|
5040
|
+
* is decoration, and three independent consumers key on the `package.json`
|
|
5041
|
+
* they find inside:
|
|
5042
|
+
*
|
|
5043
|
+
* - `AddonLoader.loadFromDirectory` walks `@scope/*` and loads anything with
|
|
5044
|
+
* a `package.json`; its skip list is keyed on the PACKAGE name, so
|
|
5045
|
+
* `@camstack/system.bak-pre-unshadow` was never skipped;
|
|
5046
|
+
* - the agent's `resolveAddonPackageDirs` scans on the same rule;
|
|
5047
|
+
* - `AddonManifest.reconcileFromDisk` does `onDisk.set(pkgJson.name, …)`,
|
|
5048
|
+
* so the backup's version becomes the version the manifest — and every
|
|
5049
|
+
* surface reading it — REPORTS.
|
|
5050
|
+
*
|
|
5051
|
+
* Measured 2026-08-07/08 on the live cluster:
|
|
5052
|
+
*
|
|
5053
|
+
* little-unraid `@camstack/system.bak-pre-unshadow` — no package.json left,
|
|
5054
|
+
* so not loadable, but the agent main process held five live
|
|
5055
|
+
* mappings of `better_sqlite3.node` out of it (D87).
|
|
5056
|
+
* macmini `@camstack/system.bak-pre-1272-1786129145` — still declares
|
|
5057
|
+
* `@camstack/system@1.2.3` (17 days old, 97 MB). The data root
|
|
5058
|
+
* has NO `addons/@camstack/system` directory at all, yet the
|
|
5059
|
+
* manifest reports `@camstack/system 1.2.3` — read straight
|
|
5060
|
+
* out of the backup. The live process still had the backup's
|
|
5061
|
+
* `better_sqlite3.node` mapped `txt` against a 1.2.61 closure.
|
|
5062
|
+
*
|
|
5063
|
+
* ── Why quarantine and not delete ─────────────────────────────────────────
|
|
5064
|
+
* The same evening, a cleanup that DELETED what it judged to be a shadow copy
|
|
5065
|
+
* removed the only builtins an agent had and gutted the node. So this never
|
|
5066
|
+
* deletes: it renames out of the scanned root into `.quarantine/`, logs every
|
|
5067
|
+
* move by name, reason and both paths, and leaves the bytes recoverable. A
|
|
5068
|
+
* silent sweep reads as "nothing was here", which is the failure mode that
|
|
5069
|
+
* made the original one unobservable.
|
|
5070
|
+
*
|
|
5071
|
+
* `.quarantine` sits beside `.staging` / `.backups` / `.evicting` under the
|
|
5072
|
+
* addon root: dot-prefixed, carrying no `package.json` at its top level, so no
|
|
5073
|
+
* scanner descends into it.
|
|
5074
|
+
*/
|
|
5075
|
+
/** Where residue is moved to, relative to the addon root. */
|
|
5076
|
+
var QUARANTINE_DIRNAME = ".quarantine";
|
|
5077
|
+
/**
|
|
5078
|
+
* A backup marker: a `.bak` / `.old` / `.orig` segment, not merely those
|
|
5079
|
+
* letters. `.backups` — the installer's own rollback store — must NOT match,
|
|
5080
|
+
* and it is also excluded by the dot rule below; the pattern stays tight
|
|
5081
|
+
* anyway so the two guards are independent.
|
|
5082
|
+
*/
|
|
5083
|
+
var BACKUP_MARKER = /\.(bak|old|orig)(?![a-z])/i;
|
|
5084
|
+
/** Installer-owned stores and node's own tree. Never candidates. */
|
|
5085
|
+
function isReservedDir(dirName) {
|
|
5086
|
+
return dirName.startsWith(".") || dirName === "node_modules";
|
|
5087
|
+
}
|
|
5088
|
+
/** `@camstack/system` → `system`; `foo` → `foo`. */
|
|
5089
|
+
function packageDirName(declaredName) {
|
|
5090
|
+
const slash = declaredName.lastIndexOf("/");
|
|
5091
|
+
return slash === -1 ? declaredName : declaredName.slice(slash + 1);
|
|
5092
|
+
}
|
|
5093
|
+
/**
|
|
5094
|
+
* Is this directory residue? Returns the verdict, or null to keep it.
|
|
5095
|
+
*
|
|
5096
|
+
* A directory with no `package.json` is NOT residue on that ground alone —
|
|
5097
|
+
* `models/`, a half-finished copy, a scratch dir are all absence, not a
|
|
5098
|
+
* rename. Only a backup marker condemns one.
|
|
5099
|
+
*/
|
|
5100
|
+
function classifyAddonDir(candidate) {
|
|
5101
|
+
const { dirName, declaredName } = candidate;
|
|
5102
|
+
if (isReservedDir(dirName)) return null;
|
|
5103
|
+
if (BACKUP_MARKER.test(dirName)) return {
|
|
5104
|
+
dirName,
|
|
5105
|
+
declaredName,
|
|
5106
|
+
reason: "backup-residue"
|
|
5107
|
+
};
|
|
5108
|
+
if (declaredName !== null && packageDirName(declaredName) !== dirName) return {
|
|
5109
|
+
dirName,
|
|
5110
|
+
declaredName,
|
|
5111
|
+
reason: "name-mismatch"
|
|
5112
|
+
};
|
|
5113
|
+
return null;
|
|
5114
|
+
}
|
|
5115
|
+
function selectAddonResidue(candidates) {
|
|
5116
|
+
const out = [];
|
|
5117
|
+
for (const candidate of candidates) {
|
|
5118
|
+
const verdict = classifyAddonDir(candidate);
|
|
5119
|
+
if (verdict !== null) out.push(verdict);
|
|
5120
|
+
}
|
|
5121
|
+
return out;
|
|
5122
|
+
}
|
|
5123
|
+
function readDeclaredName(dir) {
|
|
5124
|
+
try {
|
|
5125
|
+
const parsed = JSON.parse(node_fs.readFileSync(node_path.join(dir, "package.json"), "utf-8"));
|
|
5126
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
5127
|
+
const name = parsed.name;
|
|
5128
|
+
return typeof name === "string" ? name : null;
|
|
5129
|
+
} catch {
|
|
5130
|
+
return null;
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
function readDeclaredVersion(dir) {
|
|
5134
|
+
try {
|
|
5135
|
+
const parsed = JSON.parse(node_fs.readFileSync(node_path.join(dir, "package.json"), "utf-8"));
|
|
5136
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
5137
|
+
const version = parsed.version;
|
|
5138
|
+
return typeof version === "string" ? version : null;
|
|
5139
|
+
} catch {
|
|
5140
|
+
return null;
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
function isDirLike(p) {
|
|
5144
|
+
try {
|
|
5145
|
+
return node_fs.statSync(p).isDirectory();
|
|
5146
|
+
} catch {
|
|
5147
|
+
return false;
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
/** `<scope>__<dir>`, suffixed until free — two sweeps must not overwrite. */
|
|
5151
|
+
function freeQuarantineTarget(store, scope, dirName) {
|
|
5152
|
+
const base = scope === null ? dirName : `${scope.replace("@", "")}__${dirName}`;
|
|
5153
|
+
let candidate = node_path.join(store, base);
|
|
5154
|
+
let n = 1;
|
|
5155
|
+
while (node_fs.existsSync(candidate)) {
|
|
5156
|
+
candidate = node_path.join(store, `${base}-${n}`);
|
|
5157
|
+
n += 1;
|
|
5158
|
+
}
|
|
5159
|
+
return candidate;
|
|
5160
|
+
}
|
|
5161
|
+
/**
|
|
5162
|
+
* Move every residue directory under `addonsDir` into `addonsDir/.quarantine`.
|
|
5163
|
+
*
|
|
5164
|
+
* Runs at boot, BEFORE anything scans the root or seeds into it — a residue
|
|
5165
|
+
* that is still present when the loader walks has already been installed.
|
|
5166
|
+
* Never throws, never deletes; a move that fails is reported and left in
|
|
5167
|
+
* place, which is strictly better than a half-moved package.
|
|
5168
|
+
*/
|
|
5169
|
+
function quarantineAddonResidue(addonsDir, log) {
|
|
5170
|
+
const quarantined = [];
|
|
5171
|
+
const failed = [];
|
|
5172
|
+
if (!node_fs.existsSync(addonsDir)) return {
|
|
5173
|
+
quarantined,
|
|
5174
|
+
failed
|
|
5175
|
+
};
|
|
5176
|
+
const store = node_path.join(addonsDir, QUARANTINE_DIRNAME);
|
|
5177
|
+
const scan = (parent, scope) => {
|
|
5178
|
+
let names;
|
|
5179
|
+
try {
|
|
5180
|
+
names = node_fs.readdirSync(parent);
|
|
5181
|
+
} catch {
|
|
5182
|
+
return;
|
|
5183
|
+
}
|
|
5184
|
+
for (const dirName of names) {
|
|
5185
|
+
const full = node_path.join(parent, dirName);
|
|
5186
|
+
if (!isDirLike(full)) continue;
|
|
5187
|
+
if (scope === null && dirName.startsWith("@")) {
|
|
5188
|
+
scan(full, dirName);
|
|
5189
|
+
continue;
|
|
5190
|
+
}
|
|
5191
|
+
const verdict = classifyAddonDir({
|
|
5192
|
+
dirName,
|
|
5193
|
+
declaredName: readDeclaredName(full)
|
|
5194
|
+
});
|
|
5195
|
+
if (verdict === null) continue;
|
|
5196
|
+
const version = readDeclaredVersion(full);
|
|
5197
|
+
const to = freeQuarantineTarget(store, scope, dirName);
|
|
5198
|
+
try {
|
|
5199
|
+
node_fs.mkdirSync(store, { recursive: true });
|
|
5200
|
+
node_fs.renameSync(full, to);
|
|
5201
|
+
} catch (err) {
|
|
5202
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
5203
|
+
failed.push({
|
|
5204
|
+
...verdict,
|
|
5205
|
+
from: full,
|
|
5206
|
+
error
|
|
5207
|
+
});
|
|
5208
|
+
log(`[addons] residue quarantine FAILED for ${full} (${verdict.reason}) — ${error}. It stays on the addon root and will be loaded, scanned and reported as an install.`);
|
|
5209
|
+
continue;
|
|
5210
|
+
}
|
|
5211
|
+
quarantined.push({
|
|
5212
|
+
...verdict,
|
|
5213
|
+
from: full,
|
|
5214
|
+
to
|
|
5215
|
+
});
|
|
5216
|
+
log(`[addons] quarantined ${verdict.reason} ${full} → ${to} (declares ${verdict.declaredName ?? "no package name"}${version === null ? "" : `@${version}`}). A renamed package dir is INSTALLED, not archived — nothing was deleted.`);
|
|
5217
|
+
}
|
|
5218
|
+
};
|
|
5219
|
+
scan(addonsDir, null);
|
|
5220
|
+
return {
|
|
5221
|
+
quarantined,
|
|
5222
|
+
failed
|
|
5223
|
+
};
|
|
5224
|
+
}
|
|
5225
|
+
//#endregion
|
|
5027
5226
|
//#region src/kernel/workspace-detect.ts
|
|
5028
5227
|
/**
|
|
5029
5228
|
* Detect whether we're running inside the monorepo workspace.
|
|
@@ -94723,6 +94922,7 @@ Object.defineProperty(exports, "PYTHON_VERSION", {
|
|
|
94723
94922
|
exports.PipelineRunner = PipelineRunner;
|
|
94724
94923
|
exports.PipelineValidator = PipelineValidator;
|
|
94725
94924
|
exports.PythonEnvManager = PythonEnvManager;
|
|
94925
|
+
exports.QUARANTINE_DIRNAME = QUARANTINE_DIRNAME;
|
|
94726
94926
|
exports.RESTART_MARKER_FILE = RESTART_MARKER_FILE;
|
|
94727
94927
|
exports.RUNTIME_DEFAULTS = require_dist.RUNTIME_DEFAULTS;
|
|
94728
94928
|
exports.ReadinessRegistry = require_dist.ReadinessRegistry;
|
|
@@ -94775,6 +94975,7 @@ exports.capActionName = require_manifest_python_deps.capActionName;
|
|
|
94775
94975
|
exports.capActionSuffix = require_manifest_python_deps.capActionSuffix;
|
|
94776
94976
|
exports.capBareAction = require_manifest_python_deps.capBareAction;
|
|
94777
94977
|
exports.capServiceName = require_manifest_python_deps.capServiceName;
|
|
94978
|
+
exports.classifyAddonDir = classifyAddonDir;
|
|
94778
94979
|
exports.classifyCapRoute = require_manifest_python_deps.classifyCapRoute;
|
|
94779
94980
|
exports.clearPendingRestart = clearPendingRestart;
|
|
94780
94981
|
exports.clusterEventTopic = require_manifest_python_deps.clusterEventTopic;
|
|
@@ -94916,6 +95117,7 @@ exports.parseCapAction = require_manifest_python_deps.parseCapAction;
|
|
|
94916
95117
|
exports.parseRangeHeader = require_model_download_service.parseRangeHeader;
|
|
94917
95118
|
exports.parseTokenizedUrl = require_model_download_service.parseTokenizedUrl;
|
|
94918
95119
|
exports.proxyToUpstream = proxyToUpstream;
|
|
95120
|
+
exports.quarantineAddonResidue = quarantineAddonResidue;
|
|
94919
95121
|
exports.readPendingRestart = readPendingRestart;
|
|
94920
95122
|
exports.readinessKey = require_dist.readinessKey;
|
|
94921
95123
|
exports.registerEventBusService = require_manifest_python_deps.registerEventBusService;
|
|
@@ -94926,6 +95128,7 @@ exports.runNpm = require_manifest_python_deps.runNpm;
|
|
|
94926
95128
|
exports.scheduleSelfRestart = scheduleSelfRestart;
|
|
94927
95129
|
exports.scopeKey = require_dist.scopeKey;
|
|
94928
95130
|
exports.scopesAllowDeviceCap = scopesAllowDeviceCap;
|
|
95131
|
+
exports.selectAddonResidue = selectAddonResidue;
|
|
94929
95132
|
exports.serializeTypedArrays = require_manifest_python_deps.serializeTypedArrays;
|
|
94930
95133
|
exports.setHubConnected = require_manifest_python_deps.setHubConnected;
|
|
94931
95134
|
exports.setNodeEventInterest = require_manifest_python_deps.setNodeEventInterest;
|