@camstack/addon-pipeline-orchestrator 1.1.22 → 1.1.24

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.
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.1.25",
21
+ version: "1.1.26",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_orchestrator_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.1.21",
36
+ version: "1.1.22",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_orchestrator_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.1.25",
39
+ version: "1.1.26",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -99,7 +99,7 @@ async function r() {
99
99
  }
100
100
  },
101
101
  "@camstack/ui-library": {
102
- version: "1.1.21",
102
+ version: "1.1.22",
103
103
  scope: "default",
104
104
  shareConfig: {
105
105
  singleton: !0,
package/dist/index.js CHANGED
@@ -24982,6 +24982,15 @@ var OrchestratorDiagnosticsSchema = object({
24982
24982
  activeDetectionCount: number().int().min(0)
24983
24983
  });
24984
24984
  var pipelineOrchestratorActions = defineCustomActions({ dumpState: customAction(_void(), OrchestratorDiagnosticsSchema) });
24985
+ /**
24986
+ * Sentinel returned by `buildDetectionConfig` when the profile-slot READ
24987
+ * itself failed transiently (e.g. `listAllProfileSlots` discovery timeout
24988
+ * while the stream-broker is (re)starting) — as opposed to `null`, which
24989
+ * means "genuinely no assigned slot / not configured". Callers MUST treat
24990
+ * this differently from `null`: never stop active detection on a transient
24991
+ * read failure (the slots almost certainly still exist), and schedule a retry.
24992
+ */
24993
+ var TRANSIENT_SLOT_READ = Symbol("transient-slot-read");
24985
24994
  var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddon {
24986
24995
  /** This node's Moleculer nodeId (from this.ctx.kernel.localNodeId). */
24987
24996
  localNodeId = "hub";
@@ -25205,6 +25214,15 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
25205
25214
  loadShedResumeTimer = null;
25206
25215
  /** Per-camera guard so repeated low-fps snapshots don't stack relocate/pause. */
25207
25216
  shedInFlight = /* @__PURE__ */ new Set();
25217
+ /**
25218
+ * Per-device backoff for retrying {@link handleDeviceRegistered} after a
25219
+ * TRANSIENT profile-slot read failure ({@link TRANSIENT_SLOT_READ}). Timers
25220
+ * are `unref`'d and cleared on shutdown / device removal.
25221
+ */
25222
+ slotReadRetryTimers = /* @__PURE__ */ new Map();
25223
+ slotReadRetryAttempts = /* @__PURE__ */ new Map();
25224
+ static SLOT_READ_MAX_RETRIES = 6;
25225
+ static SLOT_READ_RETRY_BASE_MS = 500;
25208
25226
  /** Pending `scheduleReconcile` debounce timer. */
25209
25227
  reconcileTimer = null;
25210
25228
  /** True while `reconcileDispatch` is awaiting the RPC round-trip. */
@@ -25672,6 +25690,9 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
25672
25690
  clearTimeout(this.pendingRetryDebounceTimer);
25673
25691
  this.pendingRetryDebounceTimer = null;
25674
25692
  }
25693
+ for (const t of this.slotReadRetryTimers.values()) clearTimeout(t);
25694
+ this.slotReadRetryTimers.clear();
25695
+ this.slotReadRetryAttempts.clear();
25675
25696
  this.unsubDeviceRegistered?.();
25676
25697
  this.unsubDeviceRegistered = null;
25677
25698
  this.unsubDeviceUnregistered?.();
@@ -26195,14 +26216,15 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
26195
26216
  }
26196
26217
  /**
26197
26218
  * The camera's source-owner node — the ONE node that dials its real RTSP
26198
- * and hosts its broker. **Slice 1 (P2d): always the hub** (the
26199
- * stream-broker is a hub-only singleton). Slice 2 (T8) backs this with a
26200
- * persisted per-camera `assignSource`/`getSourceAssignment` cap surface so
26201
- * satellite owners (a camera only reachable from an agent's subnet) drop
26202
- * in without re-touching call sites.
26219
+ * and hosts its broker. Driven by the `ingestNode` CLUSTER ROLE
26220
+ * (placement-model §6): a single node ingests every camera and serves the
26221
+ * compressed restream. Defaults to the hub. (Arbitrary ingest ≠ hub is a
26222
+ * later phase; wiring the role here means no call site changes when it lands.
26223
+ * With `remoteSourcingNodes` empty the source-owner stays unmodeled, so this
26224
+ * has no runtime effect until the cross-node transport phase.)
26203
26225
  */
26204
26226
  assignSource(_deviceId) {
26205
- return this.localNodeId;
26227
+ return this.clusterRoles.ingestNode;
26206
26228
  }
26207
26229
  /**
26208
26230
  * The node that owns `deviceId`'s source pull, or `undefined` while
@@ -26469,6 +26491,13 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
26469
26491
  this.recordAudioAssignment(deviceId, this.localNodeId, false);
26470
26492
  return this.localNodeId;
26471
26493
  }
26494
+ if (!preferredNode) {
26495
+ const roleNode = this.clusterRoles.audioNode;
26496
+ if (loads.some((load) => load.nodeId === roleNode)) {
26497
+ this.recordAudioAssignment(deviceId, roleNode, false);
26498
+ return roleNode;
26499
+ }
26500
+ }
26472
26501
  const decision = balanceAudio({
26473
26502
  nodes: loads,
26474
26503
  preferredNode
@@ -26961,6 +26990,47 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
26961
26990
  }
26962
26991
  }
26963
26992
  /**
26993
+ * Bounded per-device retry of {@link handleDeviceRegistered} after a
26994
+ * TRANSIENT profile-slot read failure ({@link TRANSIENT_SLOT_READ}). The
26995
+ * read (`listAllProfileSlots`) can time out during a broker (re)start; we
26996
+ * must neither stop a running camera nor leave a not-yet-started camera
26997
+ * stranded — so re-run the registration handler with exponential backoff
26998
+ * until the read succeeds or the budget is exhausted. Reset on any
26999
+ * successful read ({@link clearSlotReadRetry}).
27000
+ */
27001
+ scheduleSlotReadRetry(deviceId) {
27002
+ const attempts = this.slotReadRetryAttempts.get(deviceId) ?? 0;
27003
+ if (attempts >= PipelineOrchestratorAddon.SLOT_READ_MAX_RETRIES) {
27004
+ this.ctx.logger.warn("slot-read retry budget exhausted — leaving detection as-is", { tags: { deviceId } });
27005
+ this.slotReadRetryAttempts.delete(deviceId);
27006
+ return;
27007
+ }
27008
+ const existing = this.slotReadRetryTimers.get(deviceId);
27009
+ if (existing) clearTimeout(existing);
27010
+ const delay = PipelineOrchestratorAddon.SLOT_READ_RETRY_BASE_MS * 2 ** attempts;
27011
+ this.slotReadRetryAttempts.set(deviceId, attempts + 1);
27012
+ const timer = setTimeout(() => {
27013
+ this.slotReadRetryTimers.delete(deviceId);
27014
+ this.handleDeviceRegistered(deviceId).catch((err) => {
27015
+ this.ctx.logger.debug("slot-read retry: handleDeviceRegistered failed", {
27016
+ tags: { deviceId },
27017
+ meta: { error: errMsg(err) }
27018
+ });
27019
+ });
27020
+ }, delay);
27021
+ if (typeof timer === "object" && timer !== null && "unref" in timer) timer.unref();
27022
+ this.slotReadRetryTimers.set(deviceId, timer);
27023
+ }
27024
+ /** Cancel any pending slot-read retry + reset the budget for a device. */
27025
+ clearSlotReadRetry(deviceId) {
27026
+ const existing = this.slotReadRetryTimers.get(deviceId);
27027
+ if (existing) {
27028
+ clearTimeout(existing);
27029
+ this.slotReadRetryTimers.delete(deviceId);
27030
+ }
27031
+ this.slotReadRetryAttempts.delete(deviceId);
27032
+ }
27033
+ /**
26964
27034
  * Coalesce bursts of capacity/eligibility/readiness signals into a single
26965
27035
  * `retryPendingDispatches` pass. Mirrors `scheduleReconcile`, but on a
26966
27036
  * dedicated (longer) debounce so a raise-cap / node-connect flurry doesn't
@@ -28837,7 +28907,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
28837
28907
  */
28838
28908
  async fetchAssignedProfiles(deviceId) {
28839
28909
  const api = this.api;
28840
- if (!api) return /* @__PURE__ */ new Map();
28910
+ if (!api) return null;
28841
28911
  try {
28842
28912
  const slots = await api.streamBroker.listAllProfileSlots.query();
28843
28913
  const out = /* @__PURE__ */ new Map();
@@ -28847,8 +28917,12 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
28847
28917
  out.set(slot.profile, slot.sourceCamStreamId);
28848
28918
  }
28849
28919
  return out;
28850
- } catch {
28851
- return /* @__PURE__ */ new Map();
28920
+ } catch (err) {
28921
+ this.ctx.logger.debug("fetchAssignedProfiles: slot read failed (transient) — treating as unknown", {
28922
+ tags: { deviceId },
28923
+ meta: { error: errMsg(err) }
28924
+ });
28925
+ return null;
28852
28926
  }
28853
28927
  }
28854
28928
  /**
@@ -28867,6 +28941,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
28867
28941
  */
28868
28942
  async buildDetectionConfig(deviceId) {
28869
28943
  const assigned = await this.fetchAssignedProfiles(deviceId);
28944
+ if (assigned === null) return TRANSIENT_SLOT_READ;
28870
28945
  if (assigned.size === 0) return null;
28871
28946
  const resolved = await this.resolveDeviceDetectionSettings(deviceId);
28872
28947
  if (!resolved) return null;
@@ -29010,6 +29085,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
29010
29085
  }
29011
29086
  /** Stop detection and purge all persisted config for a removed device. */
29012
29087
  async handleDeviceUnregistered(deviceId) {
29088
+ this.clearSlotReadRetry(deviceId);
29013
29089
  await this.stopDetection(deviceId);
29014
29090
  this.zonesProvider?.forgetDevice(deviceId);
29015
29091
  this.zoneRulesProvider?.forgetDevice(deviceId);
@@ -29031,6 +29107,12 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
29031
29107
  const log = this.ctx.logger.withTags({ deviceId });
29032
29108
  log.info("handleDeviceRegistered", { meta: { phase: "handleDeviceRegistered" } });
29033
29109
  const config = await this.buildDetectionConfig(deviceId);
29110
+ if (config === TRANSIENT_SLOT_READ) {
29111
+ log.warn("Profile-slot read failed transiently — keeping detection, scheduling retry");
29112
+ this.scheduleSlotReadRetry(deviceId);
29113
+ return;
29114
+ }
29115
+ this.clearSlotReadRetry(deviceId);
29034
29116
  log.info("[pipeline-orchestrator] buildDetectionConfig", config ? { meta: {
29035
29117
  enabled: config.enabled,
29036
29118
  motionStreamId: config.motionStreamId,
@@ -29096,6 +29178,12 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
29096
29178
  const log = this.ctx.logger.withTags({ deviceId });
29097
29179
  if (this.activeDetections.has(deviceId)) await this.stopDetection(deviceId);
29098
29180
  const config = await this.buildDetectionConfig(deviceId);
29181
+ if (config === TRANSIENT_SLOT_READ) {
29182
+ log.warn("Settings changed — slot read failed transiently, scheduling retry");
29183
+ this.scheduleSlotReadRetry(deviceId);
29184
+ return;
29185
+ }
29186
+ this.clearSlotReadRetry(deviceId);
29099
29187
  if (!config) {
29100
29188
  log.info("Settings changed — no assigned slot, detection stopped");
29101
29189
  return;
package/dist/index.mjs CHANGED
@@ -24978,6 +24978,15 @@ var OrchestratorDiagnosticsSchema = object({
24978
24978
  activeDetectionCount: number().int().min(0)
24979
24979
  });
24980
24980
  var pipelineOrchestratorActions = defineCustomActions({ dumpState: customAction(_void(), OrchestratorDiagnosticsSchema) });
24981
+ /**
24982
+ * Sentinel returned by `buildDetectionConfig` when the profile-slot READ
24983
+ * itself failed transiently (e.g. `listAllProfileSlots` discovery timeout
24984
+ * while the stream-broker is (re)starting) — as opposed to `null`, which
24985
+ * means "genuinely no assigned slot / not configured". Callers MUST treat
24986
+ * this differently from `null`: never stop active detection on a transient
24987
+ * read failure (the slots almost certainly still exist), and schedule a retry.
24988
+ */
24989
+ var TRANSIENT_SLOT_READ = Symbol("transient-slot-read");
24981
24990
  var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddon {
24982
24991
  /** This node's Moleculer nodeId (from this.ctx.kernel.localNodeId). */
24983
24992
  localNodeId = "hub";
@@ -25201,6 +25210,15 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
25201
25210
  loadShedResumeTimer = null;
25202
25211
  /** Per-camera guard so repeated low-fps snapshots don't stack relocate/pause. */
25203
25212
  shedInFlight = /* @__PURE__ */ new Set();
25213
+ /**
25214
+ * Per-device backoff for retrying {@link handleDeviceRegistered} after a
25215
+ * TRANSIENT profile-slot read failure ({@link TRANSIENT_SLOT_READ}). Timers
25216
+ * are `unref`'d and cleared on shutdown / device removal.
25217
+ */
25218
+ slotReadRetryTimers = /* @__PURE__ */ new Map();
25219
+ slotReadRetryAttempts = /* @__PURE__ */ new Map();
25220
+ static SLOT_READ_MAX_RETRIES = 6;
25221
+ static SLOT_READ_RETRY_BASE_MS = 500;
25204
25222
  /** Pending `scheduleReconcile` debounce timer. */
25205
25223
  reconcileTimer = null;
25206
25224
  /** True while `reconcileDispatch` is awaiting the RPC round-trip. */
@@ -25668,6 +25686,9 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
25668
25686
  clearTimeout(this.pendingRetryDebounceTimer);
25669
25687
  this.pendingRetryDebounceTimer = null;
25670
25688
  }
25689
+ for (const t of this.slotReadRetryTimers.values()) clearTimeout(t);
25690
+ this.slotReadRetryTimers.clear();
25691
+ this.slotReadRetryAttempts.clear();
25671
25692
  this.unsubDeviceRegistered?.();
25672
25693
  this.unsubDeviceRegistered = null;
25673
25694
  this.unsubDeviceUnregistered?.();
@@ -26191,14 +26212,15 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
26191
26212
  }
26192
26213
  /**
26193
26214
  * The camera's source-owner node — the ONE node that dials its real RTSP
26194
- * and hosts its broker. **Slice 1 (P2d): always the hub** (the
26195
- * stream-broker is a hub-only singleton). Slice 2 (T8) backs this with a
26196
- * persisted per-camera `assignSource`/`getSourceAssignment` cap surface so
26197
- * satellite owners (a camera only reachable from an agent's subnet) drop
26198
- * in without re-touching call sites.
26215
+ * and hosts its broker. Driven by the `ingestNode` CLUSTER ROLE
26216
+ * (placement-model §6): a single node ingests every camera and serves the
26217
+ * compressed restream. Defaults to the hub. (Arbitrary ingest ≠ hub is a
26218
+ * later phase; wiring the role here means no call site changes when it lands.
26219
+ * With `remoteSourcingNodes` empty the source-owner stays unmodeled, so this
26220
+ * has no runtime effect until the cross-node transport phase.)
26199
26221
  */
26200
26222
  assignSource(_deviceId) {
26201
- return this.localNodeId;
26223
+ return this.clusterRoles.ingestNode;
26202
26224
  }
26203
26225
  /**
26204
26226
  * The node that owns `deviceId`'s source pull, or `undefined` while
@@ -26465,6 +26487,13 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
26465
26487
  this.recordAudioAssignment(deviceId, this.localNodeId, false);
26466
26488
  return this.localNodeId;
26467
26489
  }
26490
+ if (!preferredNode) {
26491
+ const roleNode = this.clusterRoles.audioNode;
26492
+ if (loads.some((load) => load.nodeId === roleNode)) {
26493
+ this.recordAudioAssignment(deviceId, roleNode, false);
26494
+ return roleNode;
26495
+ }
26496
+ }
26468
26497
  const decision = balanceAudio({
26469
26498
  nodes: loads,
26470
26499
  preferredNode
@@ -26957,6 +26986,47 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
26957
26986
  }
26958
26987
  }
26959
26988
  /**
26989
+ * Bounded per-device retry of {@link handleDeviceRegistered} after a
26990
+ * TRANSIENT profile-slot read failure ({@link TRANSIENT_SLOT_READ}). The
26991
+ * read (`listAllProfileSlots`) can time out during a broker (re)start; we
26992
+ * must neither stop a running camera nor leave a not-yet-started camera
26993
+ * stranded — so re-run the registration handler with exponential backoff
26994
+ * until the read succeeds or the budget is exhausted. Reset on any
26995
+ * successful read ({@link clearSlotReadRetry}).
26996
+ */
26997
+ scheduleSlotReadRetry(deviceId) {
26998
+ const attempts = this.slotReadRetryAttempts.get(deviceId) ?? 0;
26999
+ if (attempts >= PipelineOrchestratorAddon.SLOT_READ_MAX_RETRIES) {
27000
+ this.ctx.logger.warn("slot-read retry budget exhausted — leaving detection as-is", { tags: { deviceId } });
27001
+ this.slotReadRetryAttempts.delete(deviceId);
27002
+ return;
27003
+ }
27004
+ const existing = this.slotReadRetryTimers.get(deviceId);
27005
+ if (existing) clearTimeout(existing);
27006
+ const delay = PipelineOrchestratorAddon.SLOT_READ_RETRY_BASE_MS * 2 ** attempts;
27007
+ this.slotReadRetryAttempts.set(deviceId, attempts + 1);
27008
+ const timer = setTimeout(() => {
27009
+ this.slotReadRetryTimers.delete(deviceId);
27010
+ this.handleDeviceRegistered(deviceId).catch((err) => {
27011
+ this.ctx.logger.debug("slot-read retry: handleDeviceRegistered failed", {
27012
+ tags: { deviceId },
27013
+ meta: { error: errMsg(err) }
27014
+ });
27015
+ });
27016
+ }, delay);
27017
+ if (typeof timer === "object" && timer !== null && "unref" in timer) timer.unref();
27018
+ this.slotReadRetryTimers.set(deviceId, timer);
27019
+ }
27020
+ /** Cancel any pending slot-read retry + reset the budget for a device. */
27021
+ clearSlotReadRetry(deviceId) {
27022
+ const existing = this.slotReadRetryTimers.get(deviceId);
27023
+ if (existing) {
27024
+ clearTimeout(existing);
27025
+ this.slotReadRetryTimers.delete(deviceId);
27026
+ }
27027
+ this.slotReadRetryAttempts.delete(deviceId);
27028
+ }
27029
+ /**
26960
27030
  * Coalesce bursts of capacity/eligibility/readiness signals into a single
26961
27031
  * `retryPendingDispatches` pass. Mirrors `scheduleReconcile`, but on a
26962
27032
  * dedicated (longer) debounce so a raise-cap / node-connect flurry doesn't
@@ -28833,7 +28903,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
28833
28903
  */
28834
28904
  async fetchAssignedProfiles(deviceId) {
28835
28905
  const api = this.api;
28836
- if (!api) return /* @__PURE__ */ new Map();
28906
+ if (!api) return null;
28837
28907
  try {
28838
28908
  const slots = await api.streamBroker.listAllProfileSlots.query();
28839
28909
  const out = /* @__PURE__ */ new Map();
@@ -28843,8 +28913,12 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
28843
28913
  out.set(slot.profile, slot.sourceCamStreamId);
28844
28914
  }
28845
28915
  return out;
28846
- } catch {
28847
- return /* @__PURE__ */ new Map();
28916
+ } catch (err) {
28917
+ this.ctx.logger.debug("fetchAssignedProfiles: slot read failed (transient) — treating as unknown", {
28918
+ tags: { deviceId },
28919
+ meta: { error: errMsg(err) }
28920
+ });
28921
+ return null;
28848
28922
  }
28849
28923
  }
28850
28924
  /**
@@ -28863,6 +28937,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
28863
28937
  */
28864
28938
  async buildDetectionConfig(deviceId) {
28865
28939
  const assigned = await this.fetchAssignedProfiles(deviceId);
28940
+ if (assigned === null) return TRANSIENT_SLOT_READ;
28866
28941
  if (assigned.size === 0) return null;
28867
28942
  const resolved = await this.resolveDeviceDetectionSettings(deviceId);
28868
28943
  if (!resolved) return null;
@@ -29006,6 +29081,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
29006
29081
  }
29007
29082
  /** Stop detection and purge all persisted config for a removed device. */
29008
29083
  async handleDeviceUnregistered(deviceId) {
29084
+ this.clearSlotReadRetry(deviceId);
29009
29085
  await this.stopDetection(deviceId);
29010
29086
  this.zonesProvider?.forgetDevice(deviceId);
29011
29087
  this.zoneRulesProvider?.forgetDevice(deviceId);
@@ -29027,6 +29103,12 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
29027
29103
  const log = this.ctx.logger.withTags({ deviceId });
29028
29104
  log.info("handleDeviceRegistered", { meta: { phase: "handleDeviceRegistered" } });
29029
29105
  const config = await this.buildDetectionConfig(deviceId);
29106
+ if (config === TRANSIENT_SLOT_READ) {
29107
+ log.warn("Profile-slot read failed transiently — keeping detection, scheduling retry");
29108
+ this.scheduleSlotReadRetry(deviceId);
29109
+ return;
29110
+ }
29111
+ this.clearSlotReadRetry(deviceId);
29030
29112
  log.info("[pipeline-orchestrator] buildDetectionConfig", config ? { meta: {
29031
29113
  enabled: config.enabled,
29032
29114
  motionStreamId: config.motionStreamId,
@@ -29092,6 +29174,12 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
29092
29174
  const log = this.ctx.logger.withTags({ deviceId });
29093
29175
  if (this.activeDetections.has(deviceId)) await this.stopDetection(deviceId);
29094
29176
  const config = await this.buildDetectionConfig(deviceId);
29177
+ if (config === TRANSIENT_SLOT_READ) {
29178
+ log.warn("Settings changed — slot read failed transiently, scheduling retry");
29179
+ this.scheduleSlotReadRetry(deviceId);
29180
+ return;
29181
+ }
29182
+ this.clearSlotReadRetry(deviceId);
29095
29183
  if (!config) {
29096
29184
  log.info("Settings changed — no assigned slot, detection stopped");
29097
29185
  return;
@@ -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-CQmCy3VK.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-CngJtoGC.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.22",
3
+ "version": "1.1.24",
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",