@camstack/addon-pipeline-orchestrator 1.2.263 → 1.2.265

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8036,7 +8036,59 @@ function errMsg$1(err) {
8036
8036
  return String(err);
8037
8037
  }
8038
8038
  //#endregion
8039
- //#region ../types/dist/composition-BK-GTFYb.mjs
8039
+ //#region ../types/dist/composition-Dq42XWp0.mjs
8040
+ /**
8041
+ * The ffmpeg build every CamStack node downloads, per platform and arch.
8042
+ *
8043
+ * ## Why a download, on every node, including the container
8044
+ *
8045
+ * The version of ffmpeg used to be CODE: baked into the image, changeable only
8046
+ * by rebuilding and rolling it. Now it is DATA — this file states the release,
8047
+ * and a node that boots with a different one downloads it. Changing ffmpeg is a
8048
+ * framework bump, not an image rebuild.
8049
+ *
8050
+ * It also settles the licence question by removing it. FFmpeg's licence is
8051
+ * chosen at compile time: the core is LGPL 2.1+, `--enable-gpl` makes it GPL
8052
+ * (for x264/x265), `--enable-version3` makes that v3, and `--enable-nonfree`
8053
+ * makes the result undistributable. These are GPL v3 builds, and we do not
8054
+ * distribute them: the user's own node fetches the archive from the project
8055
+ * that publishes it, so we convey no GPL work and inherit none of the
8056
+ * obligations that come with conveying one. That is the whole reason the image
8057
+ * no longer installs ffmpeg at all — not a technical preference.
8058
+ *
8059
+ * ## Why jellyfin-ffmpeg
8060
+ *
8061
+ * One vendor covering linux-amd64, linux-arm64, darwin-arm64 and darwin-x64
8062
+ * from one release, with the widest hardware surface of anything maintained:
8063
+ * VAAPI, QSV, NVENC/NVDEC, AMF, Vulkan, OpenCL and DRM on amd64, plus Rockchip
8064
+ * MPP on arm64. It reaches the vendor libraries through dlopen trampolines,
8065
+ * so the binary is not linked against a driver stack it may not find.
8066
+ *
8067
+ * **What it still needs from the host, measured 2026-09-19.** On the Unraid
8068
+ * HOST, which has no libva, `-vaapi_device` aborts the process outright —
8069
+ * `implib-gen: libva-drm.so.2: failed to load library ... Assertion '0 &&
8070
+ * "Assertion in generated code"' failed`. Inside our container, which installs
8071
+ * libva plus the iHD driver, the same command encodes: verified end to end with
8072
+ * `testsrc → format=nv12,hwupload → h264_vaapi`. So the image drops ffmpeg and
8073
+ * KEEPS the Intel media stack; the drivers are the part a binary cannot bring.
8074
+ *
8075
+ * ## Why each artifact carries a checksum and a claim
8076
+ *
8077
+ * The download is now the only source, so `sha256` is verified before anything
8078
+ * is extracted — jellyfin publishes no checksums on its release assets, so
8079
+ * these are ours, taken at the version bump. And `expectedHwaccels` is what the
8080
+ * build is supposed to be able to do: a binary that probes short of its own
8081
+ * claim is a source that changed under us, which is precisely what went
8082
+ * unnoticed for months when a static build turned out to carry no VAAPI at all
8083
+ * (D536).
8084
+ */
8085
+ /** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
8086
+ var FFMPEG_RELEASE = "8.1.2-5";
8087
+ var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
8088
+ function portable(target) {
8089
+ return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
8090
+ }
8091
+ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64");
8040
8092
  /**
8041
8093
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
8042
8094
  * valutatore puro del suo FRONTE.
@@ -14076,6 +14128,70 @@ var CompositionBlockStateSchema = object({
14076
14128
  reason: string().optional(),
14077
14129
  fields: array(CompositionFieldStateSchema)
14078
14130
  });
14131
+ var NUMBER = {
14132
+ kind: "fixed",
14133
+ result: ["number"]
14134
+ };
14135
+ var BOOLEAN = {
14136
+ kind: "fixed",
14137
+ result: ["boolean"]
14138
+ };
14139
+ Object.freeze({
14140
+ min: NUMBER,
14141
+ max: NUMBER,
14142
+ abs: NUMBER,
14143
+ floor: NUMBER,
14144
+ ceil: NUMBER,
14145
+ sqrt: NUMBER,
14146
+ round: NUMBER,
14147
+ pow: NUMBER,
14148
+ clamp: NUMBER,
14149
+ avg: NUMBER,
14150
+ sum: NUMBER,
14151
+ age: NUMBER,
14152
+ convert: NUMBER,
14153
+ coalesce: { kind: "coalesce" },
14154
+ any: BOOLEAN,
14155
+ all: BOOLEAN,
14156
+ within: BOOLEAN,
14157
+ latest: {
14158
+ kind: "fixed",
14159
+ result: ["number", "null"]
14160
+ },
14161
+ number: {
14162
+ kind: "fixed",
14163
+ result: ["number", "null"]
14164
+ },
14165
+ rose: {
14166
+ kind: "fixed",
14167
+ result: ["number", "null"]
14168
+ },
14169
+ count: NUMBER
14170
+ });
14171
+ `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
14172
+ /**
14173
+ * What a composition may BUILD on a device type (G2, D676): the caps a new
14174
+ * device of that type, or an existing one that lacks them, can be given. The
14175
+ * blockers are the validator's own refusal codes, and a spec compares this
14176
+ * listing with `planComposition` for every capability — the editor can never
14177
+ * offer a feature the save would refuse for the same reason.
14178
+ */
14179
+ var ComposableFieldSchema = object({
14180
+ path: string(),
14181
+ kind: CompositionFieldKindSchema,
14182
+ nullable: boolean(),
14183
+ required: boolean(),
14184
+ enumValues: array(string()).optional()
14185
+ });
14186
+ var ComposableCapabilitiesSchema = object({ caps: array(object({
14187
+ cap: string(),
14188
+ fields: array(ComposableFieldSchema),
14189
+ /** Present for an item-array cap (`consumables.items`). */
14190
+ itemArray: object({
14191
+ path: string(),
14192
+ keyField: string()
14193
+ }).optional()
14194
+ })) });
14079
14195
  /** Where a block runs. The operator chooses — a block driving a device on an
14080
14196
  * agent is the reason placement is not fixed to the hub. */
14081
14197
  var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
@@ -14240,7 +14356,7 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
14240
14356
  }), method(object({ blockId: string() }), CompositionGetReadSchema, { auth: "admin" }), method(object({}), CompositionListReadSchema, { auth: "admin" }), method(object({
14241
14357
  addonId: string().min(1),
14242
14358
  stableId: string().min(1)
14243
- }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({
14359
+ }), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({ deviceType: _enum(DeviceType) }), ComposableCapabilitiesSchema, { auth: "admin" }), method(object({
14244
14360
  composition: CompositionSchema,
14245
14361
  blockId: string().optional()
14246
14362
  }), CompositionValidationSchema, {
@@ -22180,6 +22296,21 @@ var ObjectEventSchema = object({
22180
22296
  keyFrameMediaId: number().int().optional(),
22181
22297
  /** Populated by B5 (recording playback URL for this event). */
22182
22298
  mediaUrl: string().optional(),
22299
+ /**
22300
+ * When the change this row records HAPPENED, epoch ms, when that is earlier
22301
+ * than {@link timestamp} and the producer could date it (D684).
22302
+ *
22303
+ * Set on a package PICK-UP row only: its `timestamp` is when the departure
22304
+ * was CONCLUDED — five minutes of unseen observed time after the parcel went
22305
+ * — and a notification's footage has to be cut around the pick-up, not the
22306
+ * empty doormat after it (D682). Stored so every path that rebuilds a
22307
+ * notification subject from the row (the boot reconcile, a dry-run) anchors
22308
+ * on the same instant the live hook did.
22309
+ *
22310
+ * `timestamp` keeps its meaning. Absent on every other row and on every
22311
+ * pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
22312
+ */
22313
+ observedAt: number().optional(),
22183
22314
  /** The parent track's key-event importance [0,1], propagated to every object
22184
22315
  * event of the track (so an event row can be sorted by importance without a
22185
22316
  * track join). Absent on legacy rows / before the track was scored. */
@@ -25683,6 +25814,16 @@ var NodeInferenceDevicesSchema = object({
25683
25814
  * Strictly the control plane: never sees a frame, never calls inference,
25684
25815
  * never drains queues. The runner does that work locally on each agent.
25685
25816
  */
25817
+ /**
25818
+ * The input of `setCameraSensors` (G5, D676): the ONE typed write path of a
25819
+ * camera's attached sensors (D672). `config: null` removes the attachment —
25820
+ * the camera reads `not-configured` again, never `unreadable`.
25821
+ */
25822
+ var SetCameraSensorsInputSchema = object({
25823
+ deviceId: number().int().nonnegative(),
25824
+ /** `null` removes the attachment: the camera goes back to `not-configured`. */
25825
+ config: CameraSensorsConfigSchema.nullable()
25826
+ });
25686
25827
  var pipelineOrchestratorCapability = {
25687
25828
  name: "pipeline-orchestrator",
25688
25829
  scope: "system",
@@ -26148,6 +26289,18 @@ var pipelineOrchestratorCapability = {
26148
26289
  */
26149
26290
  getCameraStatuses: method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()),
26150
26291
  /**
26292
+ * Attach sensors to a camera, or detach them with `config: null` (D672
26293
+ * configuration, G5/D676). Written to the orchestrator's per-device store
26294
+ * under the same key `applyDeviceSettingsPatch` writes, and announced as a
26295
+ * settings change so the camera-sensors bridge refreshes. Refused by name
26296
+ * — nothing written — for a device that cannot be read, does not exist,
26297
+ * is not a camera, or names itself as its own sensor.
26298
+ */
26299
+ setCameraSensors: method(SetCameraSensorsInputSchema, object({ success: literal(true) }), {
26300
+ kind: "mutation",
26301
+ auth: "admin"
26302
+ }),
26303
+ /**
26151
26304
  * Disk-wins fleet reconcile after a recordings wipe. Starts the walk in
26152
26305
  * the addon process and returns immediately with the job snapshot — the
26153
26306
  * work outlives the tRPC/UDS 60s timeout. Poll `getReconcileFromDiskStatus`.
@@ -37177,47 +37330,6 @@ var zoneRulesCapability = {
37177
37330
  */
37178
37331
  durability: "restored"
37179
37332
  };
37180
- var NUMBER = {
37181
- kind: "fixed",
37182
- result: ["number"]
37183
- };
37184
- var BOOLEAN = {
37185
- kind: "fixed",
37186
- result: ["boolean"]
37187
- };
37188
- Object.freeze({
37189
- min: NUMBER,
37190
- max: NUMBER,
37191
- abs: NUMBER,
37192
- floor: NUMBER,
37193
- ceil: NUMBER,
37194
- sqrt: NUMBER,
37195
- round: NUMBER,
37196
- pow: NUMBER,
37197
- clamp: NUMBER,
37198
- avg: NUMBER,
37199
- sum: NUMBER,
37200
- age: NUMBER,
37201
- convert: NUMBER,
37202
- coalesce: { kind: "coalesce" },
37203
- any: BOOLEAN,
37204
- all: BOOLEAN,
37205
- within: BOOLEAN,
37206
- latest: {
37207
- kind: "fixed",
37208
- result: ["number", "null"]
37209
- },
37210
- number: {
37211
- kind: "fixed",
37212
- result: ["number", "null"]
37213
- },
37214
- rose: {
37215
- kind: "fixed",
37216
- result: ["number", "null"]
37217
- },
37218
- count: NUMBER
37219
- });
37220
- `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
37221
37333
  /**
37222
37334
  * Accessory device helpers — shared across drivers.
37223
37335
  *
@@ -39668,6 +39780,12 @@ Object.freeze({
39668
39780
  addonId: null,
39669
39781
  access: "view"
39670
39782
  },
39783
+ "coreBlocks.listComposableCaps": {
39784
+ capName: "core-blocks",
39785
+ capScope: "system",
39786
+ addonId: null,
39787
+ access: "view"
39788
+ },
39671
39789
  "coreBlocks.listCompositions": {
39672
39790
  capName: "core-blocks",
39673
39791
  capScope: "system",
@@ -43352,6 +43470,12 @@ Object.freeze({
43352
43470
  addonId: null,
43353
43471
  access: "create"
43354
43472
  },
43473
+ "pipelineOrchestrator.setCameraSensors": {
43474
+ capName: "pipeline-orchestrator",
43475
+ capScope: "system",
43476
+ addonId: null,
43477
+ access: "create"
43478
+ },
43355
43479
  "pipelineOrchestrator.setCameraStepOverride": {
43356
43480
  capName: "pipeline-orchestrator",
43357
43481
  capScope: "system",
@@ -47062,6 +47186,11 @@ Object.freeze({
47062
47186
  form: "single",
47063
47187
  optional: false
47064
47188
  }],
47189
+ "pipelineOrchestrator.setCameraSensors": [{
47190
+ name: "deviceId",
47191
+ form: "single",
47192
+ optional: false
47193
+ }],
47065
47194
  "pipelineOrchestrator.setCameraStepOverride": [{
47066
47195
  name: "deviceId",
47067
47196
  form: "single",
@@ -49683,6 +49812,52 @@ var AudioSubscriptionController = class {
49683
49812
  }
49684
49813
  };
49685
49814
  //#endregion
49815
+ //#region src/camera-sensors/write.ts
49816
+ /**
49817
+ * The ONE typed write path of a camera's attached sensors (D672 config,
49818
+ * G5/D676) — `pipelineOrchestrator.setCameraSensors`.
49819
+ *
49820
+ * The input is already validated by the cap schema at the router. Here it is
49821
+ * refused BY NAME, and logged with the device tag, before anything is written
49822
+ * (D391): a device that could not be read (a failed read changes nothing,
49823
+ * D49), an unknown device, a non-camera, a camera named as its own sensor.
49824
+ * The value is then handed to `writeDeviceStore` under the CS-1 key, which the
49825
+ * addon binds to `applyDeviceSettingsPatch` — so the D672 patch gate, the
49826
+ * PARSED value, the store and the settings-updated event that refreshes the
49827
+ * bridge's resolver are ONE path, never a copy of it. `null` clears: the
49828
+ * device store keeps a null key, and `parseCameraSensorsSetting` reads it as
49829
+ * not-configured.
49830
+ */
49831
+ function refuse(deps, deviceId, reason) {
49832
+ deps.logger.warn("camera sensors write refused", {
49833
+ tags: { deviceId },
49834
+ meta: { reason }
49835
+ });
49836
+ throw new Error(`pipeline-orchestrator: camera sensors of ${deviceId} not saved — ${reason}`);
49837
+ }
49838
+ async function readOrRefuse(deps, deviceId) {
49839
+ let device;
49840
+ try {
49841
+ device = await deps.readDevice(deviceId);
49842
+ } catch (err) {
49843
+ return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$1(err)}`);
49844
+ }
49845
+ if (device === null) return refuse(deps, deviceId, `no device ${deviceId}`);
49846
+ return device;
49847
+ }
49848
+ async function writeCameraSensors(deps, input) {
49849
+ const { deviceId, config } = input;
49850
+ const device = await readOrRefuse(deps, deviceId);
49851
+ if (device.type !== DeviceType.Camera) return refuse(deps, deviceId, `device ${deviceId} is not a camera`);
49852
+ if (config !== null && config.sensors.some((s) => s.addonId === device.addonId && s.stableId === device.stableId)) return refuse(deps, deviceId, "a camera cannot be its own sensor");
49853
+ await deps.writeDeviceStore(deviceId, { [CAMERA_SENSORS_SETTINGS_KEY]: config });
49854
+ deps.logger.info("camera sensors saved", {
49855
+ tags: { deviceId },
49856
+ meta: { sensors: config === null ? 0 : config.sensors.length }
49857
+ });
49858
+ return { success: true };
49859
+ }
49860
+ //#endregion
49686
49861
  //#region src/disk-reconcile-fleet.ts
49687
49862
  async function reconcileFleetFromDisk(deps) {
49688
49863
  const deviceIds = await deps.listDeviceIds();
@@ -66200,6 +66375,37 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
66200
66375
  return this.deviceConfig.applyDeviceSettingsPatch(input);
66201
66376
  }
66202
66377
  /**
66378
+ * The one typed write path of a camera's attached sensors (G5, D676) — see
66379
+ * `camera-sensors/write.ts`. Its refusals run first; the store write then
66380
+ * goes THROUGH `applyDeviceSettingsPatch`, so the D672 patch gate, the
66381
+ * parsed value, the key and the `OrchestrationSettingsUpdated` event are the
66382
+ * one path a camera configured through the device page already takes.
66383
+ */
66384
+ async setCameraSensors(input) {
66385
+ const ctx = this.ctx;
66386
+ const deviceConfig = this.deviceConfig;
66387
+ if (deviceConfig === null) throw new Error("pipeline-orchestrator: not initialised — camera sensors not saved");
66388
+ const api = ctx.api;
66389
+ return writeCameraSensors({
66390
+ readDevice: async (deviceId) => {
66391
+ if (!api) throw new Error("the device registry is not reachable");
66392
+ const dev = await api.deviceManager.getDevice.query({ deviceId });
66393
+ return dev === null || dev === void 0 ? null : {
66394
+ type: dev.type,
66395
+ addonId: dev.addonId,
66396
+ stableId: dev.stableId
66397
+ };
66398
+ },
66399
+ writeDeviceStore: async (deviceId, patch) => {
66400
+ await deviceConfig.applyDeviceSettingsPatch({
66401
+ deviceId,
66402
+ patch
66403
+ });
66404
+ },
66405
+ logger: ctx.logger.child("camera-sensors")
66406
+ }, input);
66407
+ }
66408
+ /**
66203
66409
  * Apply the subset of global settings that affect runtime behaviour
66204
66410
  * (balancer thresholds, broker timeout, failover policy) into the
66205
66411
  * in-memory state so subsequent `balance()` / `handleNodeDisconnect`