@camstack/addon-pipeline-orchestrator 1.1.49 → 1.1.51
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/assets/viewer/.viewer-version +1 -1
- package/assets/viewer/_expo/static/js/web/Haptics-46503cea14b9857c70d67ef803f14a29.js +3 -0
- package/assets/viewer/_expo/static/js/web/index-864eb219c6810859b34768e62dd81a03.js +15997 -0
- package/assets/viewer/index.html +1 -1
- package/dist/_stub.js +2 -2
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-BEIKqOjC.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DIMFPAv-.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DGSDXlX8.mjs +26 -0
- package/dist/{_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CVV0TbM1.mjs → _virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Bdia6t2j.mjs} +1 -1
- package/dist/{hostInit-CCmoLG4T.mjs → hostInit-IhwxrfKe.mjs} +3 -3
- package/dist/index.js +375 -44
- package/dist/index.mjs +375 -44
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/assets/viewer/_expo/static/js/web/index-5b6b7a6f9c54fd4f38efb799e5f75952.js +0 -1544
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D0br6Ap8.mjs +0 -26
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
//#region ../types/dist/event-category-
|
|
4
|
+
//#region ../types/dist/event-category-D4HJq7Mw.mjs
|
|
5
5
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
6
6
|
EventCategory["SystemBoot"] = "system.boot";
|
|
7
7
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -154,6 +154,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
154
154
|
/** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
|
|
155
155
|
* thumb is a scrub gap the recorder's keyframe backfill covers. */
|
|
156
156
|
EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
|
|
157
|
+
/** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
|
|
158
|
+
* progress bar the client reconciles via `recordingExport.getExport`. */
|
|
159
|
+
EventCategory["RecordingExportProgress"] = "recording.export.progress";
|
|
160
|
+
EventCategory["RecordingExportCompleted"] = "recording.export.completed";
|
|
161
|
+
EventCategory["RecordingExportFailed"] = "recording.export.failed";
|
|
157
162
|
EventCategory["DetectionEvent"] = "detection.event";
|
|
158
163
|
EventCategory["SessionTrackNew"] = "session.track.new";
|
|
159
164
|
EventCategory["SessionTrackExpired"] = "session.track.expired";
|
|
@@ -443,6 +448,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
443
448
|
*/
|
|
444
449
|
EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
|
|
445
450
|
/**
|
|
451
|
+
* Fired by `addon-post-analysis` when a package-drop is confirmed inside
|
|
452
|
+
* a package zone — a newly-appeared stationary object of a package class
|
|
453
|
+
* that cleared the class / zone / dwell / size gates. Payload:
|
|
454
|
+
* `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
|
|
455
|
+
* entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
|
|
456
|
+
* Telemetry (D8): the durable record is the `package-events` store row;
|
|
457
|
+
* this bus topic drives notifier rules + live UI. See
|
|
458
|
+
* docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
|
|
459
|
+
*/
|
|
460
|
+
EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
|
|
461
|
+
/**
|
|
462
|
+
* Fired by `addon-post-analysis` when a previously-delivered package
|
|
463
|
+
* leaves its zone (the stationary entry departed — moved or swept).
|
|
464
|
+
* Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
|
|
465
|
+
* `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
|
|
466
|
+
* Telemetry (D8). See package-zones-design §5.2.
|
|
467
|
+
*/
|
|
468
|
+
EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
|
|
469
|
+
/**
|
|
446
470
|
* Fired by `addon-post-analysis` whenever a gallery face row changes:
|
|
447
471
|
* `kind:'buffered'` a new detected face was persisted, `'assigned'` /
|
|
448
472
|
* `'unassigned'` its identity link changed, `'deleted'` the row was
|
|
@@ -5181,6 +5205,25 @@ function _instanceof(cls, params = {}) {
|
|
|
5181
5205
|
};
|
|
5182
5206
|
return inst;
|
|
5183
5207
|
}
|
|
5208
|
+
//#endregion
|
|
5209
|
+
//#region ../../node_modules/zod/v4/classic/compat.js
|
|
5210
|
+
/** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
|
|
5211
|
+
var ZodIssueCode = {
|
|
5212
|
+
invalid_type: "invalid_type",
|
|
5213
|
+
too_big: "too_big",
|
|
5214
|
+
too_small: "too_small",
|
|
5215
|
+
invalid_format: "invalid_format",
|
|
5216
|
+
not_multiple_of: "not_multiple_of",
|
|
5217
|
+
unrecognized_keys: "unrecognized_keys",
|
|
5218
|
+
invalid_union: "invalid_union",
|
|
5219
|
+
invalid_key: "invalid_key",
|
|
5220
|
+
invalid_element: "invalid_element",
|
|
5221
|
+
invalid_value: "invalid_value",
|
|
5222
|
+
custom: "custom"
|
|
5223
|
+
};
|
|
5224
|
+
/** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
|
|
5225
|
+
var ZodFirstPartyTypeKind;
|
|
5226
|
+
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5184
5227
|
Object.fromEntries([
|
|
5185
5228
|
{
|
|
5186
5229
|
id: "overview",
|
|
@@ -6466,6 +6509,19 @@ function scopeKey(scope) {
|
|
|
6466
6509
|
case "device": return `device:${scope.deviceId}`;
|
|
6467
6510
|
}
|
|
6468
6511
|
}
|
|
6512
|
+
/**
|
|
6513
|
+
* Producer transition order within ONE generation: `starting → ready →
|
|
6514
|
+
* down`. Used by `hydrate` to accept only forward moves for
|
|
6515
|
+
* same-generation snapshot records (a snapshot can be ahead of a lost
|
|
6516
|
+
* delta, never behind it, within one producer generation).
|
|
6517
|
+
*/
|
|
6518
|
+
function stateRank(state) {
|
|
6519
|
+
switch (state) {
|
|
6520
|
+
case "starting": return 0;
|
|
6521
|
+
case "ready": return 1;
|
|
6522
|
+
case "down": return 2;
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6469
6525
|
function scopesEqual(a, b) {
|
|
6470
6526
|
if (a.type !== b.type) return false;
|
|
6471
6527
|
if (a.type === "global" || b.type === "global") return true;
|
|
@@ -6516,43 +6572,70 @@ var ReadinessRegistry = class {
|
|
|
6516
6572
|
return Array.from(this.snapshot.values());
|
|
6517
6573
|
}
|
|
6518
6574
|
/**
|
|
6519
|
-
* Hydrate the snapshot from an authoritative source
|
|
6520
|
-
*
|
|
6521
|
-
*
|
|
6522
|
-
*
|
|
6523
|
-
*
|
|
6524
|
-
*
|
|
6575
|
+
* Hydrate the snapshot from an authoritative source — a RECONCILE, not
|
|
6576
|
+
* an add-only merge (D8: readiness events are telemetry and may be
|
|
6577
|
+
* lost; the on-reconnect/periodic snapshot re-pull is the repair path,
|
|
6578
|
+
* so it MUST be able to advance a stale record — the 2026-07-17
|
|
6579
|
+
* "still awaiting platform-probe forever" wedge was this method
|
|
6580
|
+
* skipping every already-seen key).
|
|
6525
6581
|
*
|
|
6526
|
-
*
|
|
6527
|
-
* derived from
|
|
6528
|
-
*
|
|
6529
|
-
*
|
|
6582
|
+
* - **Unseen keys** are added (epoch 1 — mirrors the value the
|
|
6583
|
+
* consumer would have derived from the first observed `ready`).
|
|
6584
|
+
* - **Existing keys** are UPDATED when the authoritative record
|
|
6585
|
+
* carries a different generation (producer restart / synthetic-down
|
|
6586
|
+
* superseded — epoch bumps on a new-generation `ready`, mirroring
|
|
6587
|
+
* `ingest`), or a FORWARD state move within the same generation
|
|
6588
|
+
* (`starting → ready → down`; within one producer generation a
|
|
6589
|
+
* snapshot can only be ahead of a lost delta, never behind it).
|
|
6590
|
+
* - **Never a same-generation regress** — a snapshot pulled just
|
|
6591
|
+
* before a live delta landed must not un-ready a record (the next
|
|
6592
|
+
* reconcile round converges it anyway).
|
|
6593
|
+
* - **Never a record this process is authoritative for** — node-scoped
|
|
6594
|
+
* records for our own nodeId, and device/global records we emitted,
|
|
6595
|
+
* keep local truth (an authority's stale copy of OUR record must
|
|
6596
|
+
* not regress us).
|
|
6597
|
+
*
|
|
6598
|
+
* Every applied entry dispatches a one-shot transition to matching
|
|
6599
|
+
* subscriptions so pending `awaitReady` callers unblock without having
|
|
6600
|
+
* to wait for a fresh event.
|
|
6530
6601
|
*/
|
|
6531
6602
|
hydrate(records) {
|
|
6532
6603
|
const now = this.now();
|
|
6533
6604
|
for (const record of records) {
|
|
6534
6605
|
const key = readinessKey(record.capName, record.scope);
|
|
6535
|
-
|
|
6606
|
+
const prev = this.snapshot.get(key) ?? null;
|
|
6607
|
+
let epoch;
|
|
6608
|
+
let durationInPrevState;
|
|
6609
|
+
if (prev !== null) {
|
|
6610
|
+
if (this.isLocallyAuthoritative(prev)) continue;
|
|
6611
|
+
const sameGeneration = prev.generation === record.generation;
|
|
6612
|
+
if (sameGeneration && stateRank(record.state) <= stateRank(prev.state)) continue;
|
|
6613
|
+
epoch = !sameGeneration && record.state === "ready" ? prev.epoch + 1 : prev.epoch;
|
|
6614
|
+
durationInPrevState = Math.max(0, now - prev.lastChange);
|
|
6615
|
+
} else {
|
|
6616
|
+
epoch = 1;
|
|
6617
|
+
durationInPrevState = 0;
|
|
6618
|
+
}
|
|
6536
6619
|
const hydrated = {
|
|
6537
6620
|
capName: record.capName,
|
|
6538
6621
|
scope: record.scope,
|
|
6539
6622
|
state: record.state,
|
|
6540
6623
|
generation: record.generation,
|
|
6541
|
-
epoch
|
|
6624
|
+
epoch,
|
|
6542
6625
|
lastChange: now,
|
|
6543
6626
|
sourceNodeId: record.sourceNodeId
|
|
6544
6627
|
};
|
|
6545
6628
|
this.snapshot.set(key, hydrated);
|
|
6546
|
-
if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated, gen=${record.generation.slice(0, 6)})`);
|
|
6629
|
+
if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated${prev !== null ? " update" : ""}, gen=${record.generation.slice(0, 6)})`);
|
|
6547
6630
|
const transition = {
|
|
6548
6631
|
capName: record.capName,
|
|
6549
6632
|
scope: record.scope,
|
|
6550
6633
|
state: record.state,
|
|
6551
|
-
epoch
|
|
6634
|
+
epoch,
|
|
6552
6635
|
generation: record.generation,
|
|
6553
6636
|
sourceNodeId: "hydrated",
|
|
6554
6637
|
ts: now,
|
|
6555
|
-
durationInPrevState
|
|
6638
|
+
durationInPrevState
|
|
6556
6639
|
};
|
|
6557
6640
|
for (const sub of this.subscriptions) {
|
|
6558
6641
|
if (sub.capName !== record.capName) continue;
|
|
@@ -6565,6 +6648,17 @@ var ReadinessRegistry = class {
|
|
|
6565
6648
|
}
|
|
6566
6649
|
}
|
|
6567
6650
|
}
|
|
6651
|
+
/**
|
|
6652
|
+
* True when THIS process is the origin authority for `record` — a
|
|
6653
|
+
* snapshot pulled from elsewhere must never overwrite it:
|
|
6654
|
+
* - node-scoped records whose `scope.nodeId` is our own node id
|
|
6655
|
+
* (this process — or a child bridged onto our bus — emits them);
|
|
6656
|
+
* - device/global-scoped records whose latest transition WE emitted.
|
|
6657
|
+
*/
|
|
6658
|
+
isLocallyAuthoritative(record) {
|
|
6659
|
+
if (record.scope.type === "node") return record.scope.nodeId === this.sourceNodeId;
|
|
6660
|
+
return record.sourceNodeId === this.sourceNodeId;
|
|
6661
|
+
}
|
|
6568
6662
|
/** Shallow copy of the full snapshot — mainly for diagnostics/tests. */
|
|
6569
6663
|
getAll() {
|
|
6570
6664
|
return new Map(this.snapshot);
|
|
@@ -7651,6 +7745,24 @@ var RecordingRetentionSchema = object({
|
|
|
7651
7745
|
maxSizeGb: number().min(0).optional()
|
|
7652
7746
|
});
|
|
7653
7747
|
/**
|
|
7748
|
+
* Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
|
|
7749
|
+
* sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
|
|
7750
|
+
* previews at. Five graduated steps; absent on a config = `standard` (the
|
|
7751
|
+
* shipped default, matching `sheet-geometry`/`sheet-composer`).
|
|
7752
|
+
*
|
|
7753
|
+
* Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
|
|
7754
|
+
* Each window's index sidecar carries its own tile dims, so a camera whose
|
|
7755
|
+
* preset changed over time renders every historical window at the dims it was
|
|
7756
|
+
* written with.
|
|
7757
|
+
*/
|
|
7758
|
+
var ScrubThumbnailPresetSchema = _enum([
|
|
7759
|
+
"minimal",
|
|
7760
|
+
"low",
|
|
7761
|
+
"standard",
|
|
7762
|
+
"high",
|
|
7763
|
+
"max"
|
|
7764
|
+
]);
|
|
7765
|
+
/**
|
|
7654
7766
|
* The full per-camera recording intent — the wire shape of a RecordingTarget.
|
|
7655
7767
|
*
|
|
7656
7768
|
* `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
|
|
@@ -7687,7 +7799,14 @@ var RecordingConfigSchema = object({
|
|
|
7687
7799
|
* derived into bands once via `migrateConfigToBands`.
|
|
7688
7800
|
*/
|
|
7689
7801
|
bands: array(RecordingBandSchema).optional(),
|
|
7690
|
-
retention: RecordingRetentionSchema.optional()
|
|
7802
|
+
retention: RecordingRetentionSchema.optional(),
|
|
7803
|
+
/**
|
|
7804
|
+
* Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
|
|
7805
|
+
* timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
|
|
7806
|
+
* windows only — existing sheets are immutable, and each window's index
|
|
7807
|
+
* carries its own tile dims so mixed-preset history renders correctly.
|
|
7808
|
+
*/
|
|
7809
|
+
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7691
7810
|
});
|
|
7692
7811
|
/**
|
|
7693
7812
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
@@ -13320,7 +13439,11 @@ array(ZoneRuleSchema).readonly();
|
|
|
13320
13439
|
* Extend the enum here when a new gating consumer comes online (audio
|
|
13321
13440
|
* gating, alert filtering, …) — no other surface needs to change.
|
|
13322
13441
|
*/
|
|
13323
|
-
var ZoneRuleStageEnum = _enum([
|
|
13442
|
+
var ZoneRuleStageEnum = _enum([
|
|
13443
|
+
"motion",
|
|
13444
|
+
"detection",
|
|
13445
|
+
"package"
|
|
13446
|
+
]);
|
|
13324
13447
|
/**
|
|
13325
13448
|
* Zone rules capability — per-camera CRUD over the {@link ZoneRule}
|
|
13326
13449
|
* arrays that decide how each pipeline stage uses the polygon zones.
|
|
@@ -13364,16 +13487,24 @@ var zoneRulesCapability = {
|
|
|
13364
13487
|
})
|
|
13365
13488
|
},
|
|
13366
13489
|
/**
|
|
13367
|
-
* Runtime-state slice —
|
|
13490
|
+
* Runtime-state slice — every stage mirrored together so consumers
|
|
13368
13491
|
* see one reactive handle (`device.state.zoneRules.value`) instead
|
|
13369
|
-
* of
|
|
13370
|
-
* `{motion, detection}` shape, so subscribers always get the
|
|
13492
|
+
* of one per stage. Bulk-replace mutations on any stage write the full
|
|
13493
|
+
* `{motion, detection, package}` shape, so subscribers always get the
|
|
13371
13494
|
* complete current set. Consumers that only care about one stage
|
|
13372
13495
|
* just read the matching property.
|
|
13496
|
+
*
|
|
13497
|
+
* `package` backs the package-drop detector — a package zone is a
|
|
13498
|
+
* `ZoneRule` on the `'package'` stage referencing drawn polygons
|
|
13499
|
+
* (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
|
|
13500
|
+
* The orchestrator provider that writes this stage lands in a later
|
|
13501
|
+
* slice; until then the mirror carries only `{motion, detection}` and
|
|
13502
|
+
* consumers read `package` as absent (treat as `[]`).
|
|
13373
13503
|
*/
|
|
13374
13504
|
runtimeState: object({
|
|
13375
13505
|
motion: array(ZoneRuleSchema).readonly(),
|
|
13376
|
-
detection: array(ZoneRuleSchema).readonly()
|
|
13506
|
+
detection: array(ZoneRuleSchema).readonly(),
|
|
13507
|
+
package: array(ZoneRuleSchema).readonly()
|
|
13377
13508
|
})
|
|
13378
13509
|
};
|
|
13379
13510
|
var ProviderStatusSchema = object({
|
|
@@ -16622,6 +16753,7 @@ var EventKindIconSchema = _enum([
|
|
|
16622
16753
|
"smoke",
|
|
16623
16754
|
"water",
|
|
16624
16755
|
"button",
|
|
16756
|
+
"package",
|
|
16625
16757
|
"generic"
|
|
16626
16758
|
]);
|
|
16627
16759
|
var EventKindCategorySchema = _enum([
|
|
@@ -16629,7 +16761,8 @@ var EventKindCategorySchema = _enum([
|
|
|
16629
16761
|
"audio",
|
|
16630
16762
|
"detection",
|
|
16631
16763
|
"sensor",
|
|
16632
|
-
"custom"
|
|
16764
|
+
"custom",
|
|
16765
|
+
"package"
|
|
16633
16766
|
]);
|
|
16634
16767
|
var EventKindDescriptorSchema = object({
|
|
16635
16768
|
/** Stable kind id (e.g. 'motion', 'person', 'contact'). */
|
|
@@ -20486,6 +20619,108 @@ method(object({
|
|
|
20486
20619
|
auth: "admin"
|
|
20487
20620
|
});
|
|
20488
20621
|
/**
|
|
20622
|
+
* `recordingExport` cap — render a footage time range into a single downloadable
|
|
20623
|
+
* MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
|
|
20624
|
+
* bounded lifetime with a durable history, auto-expiry, and optional
|
|
20625
|
+
* delete-after-download.
|
|
20626
|
+
*
|
|
20627
|
+
* Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
|
|
20628
|
+
* recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
|
|
20629
|
+
* default `hub`) registers it. Device-scoped methods carry `deviceId` in their
|
|
20630
|
+
* input and dispatch to that single provider; the render runs on the node that
|
|
20631
|
+
* owns the footage (no cross-node segment transfer). Download rides the
|
|
20632
|
+
* framework addon data-plane. State persists in a DurableState blob (NOT
|
|
20633
|
+
* SQLite); history rows survive file deletion for audit.
|
|
20634
|
+
*/
|
|
20635
|
+
/** Playback-speed multiplier for the render (1 = realtime). */
|
|
20636
|
+
var ExportSpeedSchema = number().min(.25).max(32);
|
|
20637
|
+
/** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
|
|
20638
|
+
var ExportTimelapseSchema = object({
|
|
20639
|
+
everyMs: number().int().positive(),
|
|
20640
|
+
outputFps: number().int().min(1).max(60).optional()
|
|
20641
|
+
});
|
|
20642
|
+
/**
|
|
20643
|
+
* Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
|
|
20644
|
+
* is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
|
|
20645
|
+
* silent. `maxLifeMs` bounds how long the finished file is kept;
|
|
20646
|
+
* `deleteAfterDownload` removes it shortly after the first complete download.
|
|
20647
|
+
*/
|
|
20648
|
+
var ExportOptionsSchema = object({
|
|
20649
|
+
speed: ExportSpeedSchema.optional(),
|
|
20650
|
+
timelapse: ExportTimelapseSchema.optional(),
|
|
20651
|
+
includeAudio: boolean(),
|
|
20652
|
+
maxLifeMs: number().int().positive(),
|
|
20653
|
+
deleteAfterDownload: boolean(),
|
|
20654
|
+
title: string().max(200).optional()
|
|
20655
|
+
}).superRefine((v, ctx) => {
|
|
20656
|
+
if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
|
|
20657
|
+
code: ZodIssueCode.custom,
|
|
20658
|
+
message: "speed and timelapse are mutually exclusive",
|
|
20659
|
+
path: ["timelapse"]
|
|
20660
|
+
});
|
|
20661
|
+
});
|
|
20662
|
+
var ExportStateSchema = _enum([
|
|
20663
|
+
"queued",
|
|
20664
|
+
"rendering",
|
|
20665
|
+
"ready",
|
|
20666
|
+
"failed",
|
|
20667
|
+
"expired",
|
|
20668
|
+
"deleted"
|
|
20669
|
+
]);
|
|
20670
|
+
/** One export job / history row. */
|
|
20671
|
+
var ExportRecordSchema = object({
|
|
20672
|
+
id: string(),
|
|
20673
|
+
deviceId: number(),
|
|
20674
|
+
profile: string(),
|
|
20675
|
+
fromMs: number(),
|
|
20676
|
+
toMs: number(),
|
|
20677
|
+
options: ExportOptionsSchema,
|
|
20678
|
+
state: ExportStateSchema,
|
|
20679
|
+
/** 0–100 while rendering; null otherwise. */
|
|
20680
|
+
progressPct: number().nullable(),
|
|
20681
|
+
/** File size once ready; null before. */
|
|
20682
|
+
fileBytes: number().nullable(),
|
|
20683
|
+
expiresAt: number(),
|
|
20684
|
+
deleteAfterDownload: boolean(),
|
|
20685
|
+
/** Epoch of the first complete download; null until then. */
|
|
20686
|
+
downloadedAt: number().nullable(),
|
|
20687
|
+
createdAt: number(),
|
|
20688
|
+
/** User id/name that requested the export. */
|
|
20689
|
+
createdBy: string(),
|
|
20690
|
+
/** Failure reason when state is 'failed'; null otherwise. */
|
|
20691
|
+
error: string().nullable()
|
|
20692
|
+
});
|
|
20693
|
+
/** Candidate download URLs (LAN first, then operator extra hosts). */
|
|
20694
|
+
var ExportDownloadSchema = object({
|
|
20695
|
+
url: string(),
|
|
20696
|
+
endpoints: array(string())
|
|
20697
|
+
});
|
|
20698
|
+
method(object({
|
|
20699
|
+
deviceId: number(),
|
|
20700
|
+
profile: string(),
|
|
20701
|
+
fromMs: number(),
|
|
20702
|
+
toMs: number(),
|
|
20703
|
+
options: ExportOptionsSchema
|
|
20704
|
+
}), ExportRecordSchema, {
|
|
20705
|
+
kind: "mutation",
|
|
20706
|
+
auth: "protected"
|
|
20707
|
+
}), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
|
|
20708
|
+
kind: "query",
|
|
20709
|
+
auth: "protected"
|
|
20710
|
+
}), method(object({ exportId: string() }), ExportRecordSchema, {
|
|
20711
|
+
kind: "query",
|
|
20712
|
+
auth: "protected"
|
|
20713
|
+
}), method(object({ exportId: string() }), ExportRecordSchema, {
|
|
20714
|
+
kind: "mutation",
|
|
20715
|
+
auth: "protected"
|
|
20716
|
+
}), method(object({ exportId: string() }), ExportRecordSchema, {
|
|
20717
|
+
kind: "mutation",
|
|
20718
|
+
auth: "protected"
|
|
20719
|
+
}), method(object({ exportId: string() }), ExportDownloadSchema, {
|
|
20720
|
+
kind: "query",
|
|
20721
|
+
auth: "protected"
|
|
20722
|
+
});
|
|
20723
|
+
/**
|
|
20489
20724
|
* One publishable camera stream as its OWNING PROVIDER describes it — the same
|
|
20490
20725
|
* payload the (legacy) push `streamBroker.publishCameraStream` carried, minus
|
|
20491
20726
|
* `deviceId`.
|
|
@@ -24409,6 +24644,42 @@ Object.freeze({
|
|
|
24409
24644
|
addonId: null,
|
|
24410
24645
|
access: "create"
|
|
24411
24646
|
},
|
|
24647
|
+
"recordingExport.cancelExport": {
|
|
24648
|
+
capName: "recordingExport",
|
|
24649
|
+
capScope: "system",
|
|
24650
|
+
addonId: null,
|
|
24651
|
+
access: "create"
|
|
24652
|
+
},
|
|
24653
|
+
"recordingExport.createExport": {
|
|
24654
|
+
capName: "recordingExport",
|
|
24655
|
+
capScope: "system",
|
|
24656
|
+
addonId: null,
|
|
24657
|
+
access: "create"
|
|
24658
|
+
},
|
|
24659
|
+
"recordingExport.deleteExport": {
|
|
24660
|
+
capName: "recordingExport",
|
|
24661
|
+
capScope: "system",
|
|
24662
|
+
addonId: null,
|
|
24663
|
+
access: "delete"
|
|
24664
|
+
},
|
|
24665
|
+
"recordingExport.getDownloadUrl": {
|
|
24666
|
+
capName: "recordingExport",
|
|
24667
|
+
capScope: "system",
|
|
24668
|
+
addonId: null,
|
|
24669
|
+
access: "view"
|
|
24670
|
+
},
|
|
24671
|
+
"recordingExport.getExport": {
|
|
24672
|
+
capName: "recordingExport",
|
|
24673
|
+
capScope: "system",
|
|
24674
|
+
addonId: null,
|
|
24675
|
+
access: "view"
|
|
24676
|
+
},
|
|
24677
|
+
"recordingExport.listExports": {
|
|
24678
|
+
capName: "recordingExport",
|
|
24679
|
+
capScope: "system",
|
|
24680
|
+
addonId: null,
|
|
24681
|
+
access: "view"
|
|
24682
|
+
},
|
|
24412
24683
|
"sceneMonitor.captureReference": {
|
|
24413
24684
|
capName: "scene-monitor",
|
|
24414
24685
|
capScope: "device",
|
|
@@ -30490,6 +30761,27 @@ var PIPELINE_EXECUTOR_CAP = "pipeline-executor";
|
|
|
30490
30761
|
var PipelineSettingsStore = class PipelineSettingsStore {
|
|
30491
30762
|
deps;
|
|
30492
30763
|
static CATALOG_CACHE_TTL_MS = 3e4;
|
|
30764
|
+
/**
|
|
30765
|
+
* One reconcile round for {@link awaitExecutorCatalog}: the BOUNDED
|
|
30766
|
+
* registry wait between two authoritative provider probes. Matches the
|
|
30767
|
+
* registry's own "still awaiting" diagnostic cadence.
|
|
30768
|
+
*/
|
|
30769
|
+
static EXECUTOR_RECONCILE_ROUND_MS = 3e4;
|
|
30770
|
+
/**
|
|
30771
|
+
* Pacing between provider probes when the registry claims `ready` but
|
|
30772
|
+
* the provider is not answering yet (stale registry record) — without
|
|
30773
|
+
* it that combination would spin hot.
|
|
30774
|
+
*/
|
|
30775
|
+
static EXECUTOR_READY_PROBE_PACE_MS = 2e3;
|
|
30776
|
+
/**
|
|
30777
|
+
* Round bound for {@link seedAgentSettingsFromCatalog}: seeding is
|
|
30778
|
+
* invoked from event handlers (`handleDetectionPipelineReadiness`) that
|
|
30779
|
+
* must not accumulate unbounded loops across provider flaps — on
|
|
30780
|
+
* exhaustion it returns `null` and the periodic pending-retry sweep /
|
|
30781
|
+
* the next readiness epoch retries. `waitForAgentAndCatalog` keeps the
|
|
30782
|
+
* UNBOUNDED reconcile (cameras are never dispatched with empty steps).
|
|
30783
|
+
*/
|
|
30784
|
+
static SEED_MAX_RECONCILE_ROUNDS = 4;
|
|
30493
30785
|
constructor(deps) {
|
|
30494
30786
|
this.deps = deps;
|
|
30495
30787
|
}
|
|
@@ -30756,11 +31048,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
30756
31048
|
* absent.
|
|
30757
31049
|
*/
|
|
30758
31050
|
async seedAgentSettingsFromCatalog(nodeId) {
|
|
30759
|
-
await this.
|
|
30760
|
-
type: "node",
|
|
30761
|
-
nodeId
|
|
30762
|
-
});
|
|
30763
|
-
const catalog = await this.getCatalogForAgent(nodeId);
|
|
31051
|
+
const catalog = await this.awaitExecutorCatalog(nodeId, { maxRounds: PipelineSettingsStore.SEED_MAX_RECONCILE_ROUNDS });
|
|
30764
31052
|
if (!catalog) return null;
|
|
30765
31053
|
const existing = (await this.readAgentSettingsMap())[nodeId];
|
|
30766
31054
|
const nextAddonDefaults = seedAgentAddonDefaults(existing?.addonDefaults ?? {}, catalog);
|
|
@@ -30864,15 +31152,64 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
30864
31152
|
return resolved;
|
|
30865
31153
|
}
|
|
30866
31154
|
/**
|
|
30867
|
-
*
|
|
31155
|
+
* Loss-proof `pipeline-executor` gate (D8 — reconcile against the
|
|
31156
|
+
* authority, never wait solely on an event).
|
|
31157
|
+
*
|
|
31158
|
+
* Readiness events are telemetry and MAY be lost across process/node
|
|
31159
|
+
* hops (2026-07-17 incident: the agent's executor came back callable
|
|
31160
|
+
* after an update, its `ready` delta never reached the hub registry,
|
|
31161
|
+
* and the former `acquireCapability(…, Infinity)` gate wedged every
|
|
31162
|
+
* dispatch forever). Each round of this loop therefore:
|
|
31163
|
+
*
|
|
31164
|
+
* 1. Probes the PROVIDER itself — `getCatalogForAgent` is a live
|
|
31165
|
+
* `pipelineExecutor.getSchema` RPC routed to `nodeId` (30s success
|
|
31166
|
+
* cache). The provider answering IS readiness, whatever the local
|
|
31167
|
+
* registry believes.
|
|
31168
|
+
* 2. On a miss, falls back to a BOUNDED registry wait
|
|
31169
|
+
* (`EXECUTOR_RECONCILE_ROUND_MS`) — the event-driven fast path that
|
|
31170
|
+
* keeps the healthy cold-boot case latency-free (the wait resolves
|
|
31171
|
+
* the instant the `ready` delta lands). A lost event costs at most
|
|
31172
|
+
* one round, never forever.
|
|
31173
|
+
*
|
|
31174
|
+
* If the registry claims `ready` but the probe still misses (stale
|
|
31175
|
+
* record for a dead provider), rounds pace at
|
|
31176
|
+
* `EXECUTOR_READY_PROBE_PACE_MS` instead of spinning hot.
|
|
31177
|
+
*
|
|
31178
|
+
* Returns `null` only when `maxRounds` is exhausted (bounded callers)
|
|
31179
|
+
* or the store was disposed mid-wait.
|
|
31180
|
+
*/
|
|
31181
|
+
async awaitExecutorCatalog(nodeId, opts = {}) {
|
|
31182
|
+
const maxRounds = opts.maxRounds ?? Number.POSITIVE_INFINITY;
|
|
31183
|
+
const startedAt = Date.now();
|
|
31184
|
+
for (let round = 1; !this.disposed; round++) {
|
|
31185
|
+
const catalog = await this.getCatalogForAgent(nodeId);
|
|
31186
|
+
if (catalog) return catalog;
|
|
31187
|
+
if (round >= maxRounds) return null;
|
|
31188
|
+
if (round > 1) this.deps.logger.warn("executor reconcile: provider not answering yet — waiting one more round", {
|
|
31189
|
+
tags: { nodeId },
|
|
31190
|
+
meta: {
|
|
31191
|
+
round,
|
|
31192
|
+
elapsedMs: Date.now() - startedAt
|
|
31193
|
+
}
|
|
31194
|
+
});
|
|
31195
|
+
const readyPerRegistry = await this.deps.acquireCapability(PIPELINE_EXECUTOR_CAP, {
|
|
31196
|
+
type: "node",
|
|
31197
|
+
nodeId
|
|
31198
|
+
}, { timeoutMs: PipelineSettingsStore.EXECUTOR_RECONCILE_ROUND_MS }).then(() => true, () => false);
|
|
31199
|
+
if (this.disposed) break;
|
|
31200
|
+
if (readyPerRegistry) await sleep$1(PipelineSettingsStore.EXECUTOR_READY_PROBE_PACE_MS);
|
|
31201
|
+
}
|
|
31202
|
+
return null;
|
|
31203
|
+
}
|
|
31204
|
+
/**
|
|
31205
|
+
* Block until `pipeline-executor` answers on `nodeId` AND
|
|
30868
31206
|
* `getCatalogForAgent` returns a non-null catalog AND `agentSettings`
|
|
30869
31207
|
* for that node has populated `addonDefaults`.
|
|
30870
31208
|
*
|
|
30871
|
-
*
|
|
30872
|
-
*
|
|
30873
|
-
*
|
|
30874
|
-
*
|
|
30875
|
-
* propagation lag.
|
|
31209
|
+
* The executor gate is {@link awaitExecutorCatalog} — reconcile-driven
|
|
31210
|
+
* and UNBOUNDED here (cameras must never be dispatched with empty
|
|
31211
|
+
* steps), but it converges as soon as the provider answers even when
|
|
31212
|
+
* every readiness event was lost.
|
|
30876
31213
|
*
|
|
30877
31214
|
* Smell #20 fix: the retry loop used to be `while (true)` with no exit
|
|
30878
31215
|
* — a resolve in flight at addon shutdown would spin against a torn-
|
|
@@ -30882,16 +31219,9 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
30882
31219
|
* timing (2s backoff, same retry conditions) is unchanged.
|
|
30883
31220
|
*/
|
|
30884
31221
|
async waitForAgentAndCatalog(nodeId) {
|
|
30885
|
-
await this.deps.acquireCapability(PIPELINE_EXECUTOR_CAP, {
|
|
30886
|
-
type: "node",
|
|
30887
|
-
nodeId
|
|
30888
|
-
});
|
|
30889
31222
|
while (!this.disposed) {
|
|
30890
|
-
const catalog = await this.
|
|
30891
|
-
if (!catalog)
|
|
30892
|
-
await sleep$1(2e3);
|
|
30893
|
-
continue;
|
|
30894
|
-
}
|
|
31223
|
+
const catalog = await this.awaitExecutorCatalog(nodeId);
|
|
31224
|
+
if (!catalog) break;
|
|
30895
31225
|
let agent = (await this.readAgentSettingsMap())[nodeId];
|
|
30896
31226
|
if (!agent || Object.keys(agent.addonDefaults ?? {}).length === 0) {
|
|
30897
31227
|
if (!await this.seedAgentSettingsFromCatalog(nodeId)) {
|
|
@@ -31917,7 +32247,8 @@ var RulesArraySchema = array(ZoneRuleSchema);
|
|
|
31917
32247
|
*/
|
|
31918
32248
|
var ZoneRulesBlockSchema = object({
|
|
31919
32249
|
motion: unknown().optional(),
|
|
31920
|
-
detection: unknown().optional()
|
|
32250
|
+
detection: unknown().optional(),
|
|
32251
|
+
package: unknown().optional()
|
|
31921
32252
|
}).passthrough();
|
|
31922
32253
|
var ZoneRulesProvider = class {
|
|
31923
32254
|
ctx;
|
package/dist/remoteEntry.js
CHANGED
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DIMFPAv-.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline-orchestrator",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.51",
|
|
4
4
|
"description": "Hub-side camera-to-agent load balancer — tracks runner capacity and dispatches attachCamera calls to the optimal pipeline-runner instance",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|