@camstack/addon-pipeline-orchestrator 1.1.42 → 1.1.43

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.40",
21
+ version: "1.1.41",
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.32",
36
+ version: "1.1.33",
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.40",
39
+ version: "1.1.41",
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.32",
102
+ version: "1.1.33",
103
103
  scope: "default",
104
104
  shareConfig: {
105
105
  singleton: !0,
package/dist/index.js CHANGED
@@ -11001,6 +11001,7 @@ var PipelineAddonSchemaSchema = object({
11001
11001
  defaultModelId: string(),
11002
11002
  defaultModelIdByFormat: record(string(), string()).optional(),
11003
11003
  enabledByDefault: boolean().optional(),
11004
+ backfillIntoExistingOverrides: boolean().optional(),
11004
11005
  defaultConfidence: number(),
11005
11006
  group: string().optional(),
11006
11007
  configSchema: array(ConfigFieldBridge).readonly().optional()
@@ -28914,7 +28915,7 @@ function resolvePipeline(agentSettings, cameraSettings, agentNodeId, catalog) {
28914
28915
  const cam = cameraSettings ?? {};
28915
28916
  const wholesale = cam.pipelineByAgent?.[agentNodeId];
28916
28917
  if (wholesale) return {
28917
- steps: wholesale.steps,
28918
+ steps: mergeEnabledByDefaultSteps(wholesale.steps, catalog),
28918
28919
  audio: wholesale.audio ? {
28919
28920
  modelId: wholesale.audio.modelId,
28920
28921
  enabled: wholesale.audio.enabled
@@ -29021,6 +29022,94 @@ function buildTreeFromAddons(enabled, catalog) {
29021
29022
  }
29022
29023
  return roots;
29023
29024
  }
29025
+ /**
29026
+ * Back-fill catalog steps EXPLICITLY flagged `backfillIntoExistingOverrides`
29027
+ * but ENTIRELY ABSENT from an existing step tree — the wholesale-override
29028
+ * analogue of {@link seedAgentAddonDefaults}, which already grows per-agent
29029
+ * `addonDefaults` when the catalog adds a new addon.
29030
+ *
29031
+ * Why it's needed: a `pipelineByAgent` wholesale override SNAPSHOTS the full
29032
+ * tree. A step introduced AFTER the snapshot (e.g. `clip-embedding` for object
29033
+ * semantic search) would otherwise never reach that camera.
29034
+ *
29035
+ * Why the flag (not `enabledByDefault`): absence-from-a-snapshot is ambiguous —
29036
+ * it can mean "predates this step" OR "operator deliberately pruned it". A
29037
+ * broad "back-fill every default-on step" would resurrect deliberate removals
29038
+ * of long-existing steps (face-detection, etc.), an operator-intent
29039
+ * regression. Only a step that opts in via `backfillIntoExistingOverrides`
29040
+ * (set on genuinely-new default steps ONLY) re-enters existing overrides.
29041
+ *
29042
+ * Non-destructive by design:
29043
+ * - Only steps whose id is absent from the WHOLE tree are added. A step the
29044
+ * operator has already seen — present anywhere, even `enabled: false` —
29045
+ * is left exactly as-is (its enabled flag is honoured).
29046
+ * - Only `backfillIntoExistingOverrides === true` addons are candidates.
29047
+ * Every other step (including long-standing default-on members) is left
29048
+ * untouched, so a deliberately-pruned override stays pruned.
29049
+ * - Audio-classifier addons are skipped — audio lives in
29050
+ * `CameraPipelineConfig.audio`, never in the video `steps` tree.
29051
+ * - A candidate attaches by catalog compat (its `inputClasses` overlap a
29052
+ * present node's `outputClasses`), mirroring {@link buildTreeFromAddons}.
29053
+ * A candidate with non-empty `inputClasses` and NO compatible parent is
29054
+ * dropped (never re-rooted) so a crop-scoped embedder never regresses to
29055
+ * running on the full frame. Empty `inputClasses` ⇒ a genuine root step.
29056
+ */
29057
+ function mergeEnabledByDefaultSteps(steps, catalog) {
29058
+ if (steps.length === 0) return steps;
29059
+ const present = /* @__PURE__ */ new Set();
29060
+ const collect = (nodes) => {
29061
+ for (const n of nodes) {
29062
+ present.add(n.addonId);
29063
+ if (n.children?.length) collect(n.children);
29064
+ }
29065
+ };
29066
+ collect(steps);
29067
+ const catalogById = /* @__PURE__ */ new Map();
29068
+ for (const slot of catalog.slots) for (const addon of slot.addons) catalogById.set(addon.id, addon);
29069
+ const newRoots = [];
29070
+ const childrenByParent = /* @__PURE__ */ new Map();
29071
+ for (const slot of catalog.slots) {
29072
+ if (slot.id === "audio-classifier") continue;
29073
+ for (const addon of slot.addons) {
29074
+ if (addon.backfillIntoExistingOverrides !== true) continue;
29075
+ if (present.has(addon.id)) continue;
29076
+ const node = {
29077
+ addonId: addon.id,
29078
+ enabled: true,
29079
+ children: []
29080
+ };
29081
+ const inputs = addon.inputClasses;
29082
+ if (!inputs || inputs.length === 0) {
29083
+ newRoots.push(node);
29084
+ continue;
29085
+ }
29086
+ let parentId = null;
29087
+ for (const presentId of present) {
29088
+ const meta = catalogById.get(presentId);
29089
+ if (!meta) continue;
29090
+ if (meta.outputClasses.some((c) => inputs.includes(c))) {
29091
+ parentId = presentId;
29092
+ break;
29093
+ }
29094
+ }
29095
+ if (!parentId) continue;
29096
+ const list = childrenByParent.get(parentId) ?? [];
29097
+ list.push(node);
29098
+ childrenByParent.set(parentId, list);
29099
+ }
29100
+ }
29101
+ if (childrenByParent.size === 0 && newRoots.length === 0) return steps;
29102
+ const attach = (nodes) => nodes.map((n) => {
29103
+ const rebuiltChildren = attach(n.children ?? []);
29104
+ const extra = childrenByParent.get(n.addonId) ?? [];
29105
+ const mergedChildren = [...rebuiltChildren, ...extra];
29106
+ return mergedChildren.length > 0 ? {
29107
+ ...n,
29108
+ children: mergedChildren
29109
+ } : n;
29110
+ });
29111
+ return [...attach(steps), ...newRoots];
29112
+ }
29024
29113
  //#endregion
29025
29114
  //#region src/orchestrator-store-schemas.ts
29026
29115
  /**
@@ -29463,13 +29552,16 @@ var PipelineSettingsStore = class PipelineSettingsStore {
29463
29552
  }
29464
29553
  const cam = (await this.readCameraSettingsMap())[String(deviceId)] ?? null;
29465
29554
  const wholesale = cam?.pipelineByAgent?.[agentNodeId];
29466
- if (wholesale) return {
29467
- steps: wholesale.steps,
29468
- audio: wholesale.audio ? {
29469
- modelId: wholesale.audio.modelId,
29470
- enabled: wholesale.audio.enabled
29471
- } : null
29472
- };
29555
+ if (wholesale) {
29556
+ const catalog = await this.getCatalogForAgent(agentNodeId);
29557
+ return {
29558
+ steps: catalog ? mergeEnabledByDefaultSteps(wholesale.steps, catalog) : wholesale.steps,
29559
+ audio: wholesale.audio ? {
29560
+ modelId: wholesale.audio.modelId,
29561
+ enabled: wholesale.audio.enabled
29562
+ } : null
29563
+ };
29564
+ }
29473
29565
  const { agent, catalog } = await this.waitForAgentAndCatalog(agentNodeId);
29474
29566
  const resolved = resolvePipeline(agent, cam, agentNodeId, catalog);
29475
29567
  if (resolved.steps.length === 0) this.deps.logger.info("resolvePipelineForDevice → empty (all addons disabled)", {
package/dist/index.mjs CHANGED
@@ -10973,6 +10973,7 @@ var PipelineAddonSchemaSchema = object({
10973
10973
  defaultModelId: string(),
10974
10974
  defaultModelIdByFormat: record(string(), string()).optional(),
10975
10975
  enabledByDefault: boolean().optional(),
10976
+ backfillIntoExistingOverrides: boolean().optional(),
10976
10977
  defaultConfidence: number(),
10977
10978
  group: string().optional(),
10978
10979
  configSchema: array(ConfigFieldBridge).readonly().optional()
@@ -28886,7 +28887,7 @@ function resolvePipeline(agentSettings, cameraSettings, agentNodeId, catalog) {
28886
28887
  const cam = cameraSettings ?? {};
28887
28888
  const wholesale = cam.pipelineByAgent?.[agentNodeId];
28888
28889
  if (wholesale) return {
28889
- steps: wholesale.steps,
28890
+ steps: mergeEnabledByDefaultSteps(wholesale.steps, catalog),
28890
28891
  audio: wholesale.audio ? {
28891
28892
  modelId: wholesale.audio.modelId,
28892
28893
  enabled: wholesale.audio.enabled
@@ -28993,6 +28994,94 @@ function buildTreeFromAddons(enabled, catalog) {
28993
28994
  }
28994
28995
  return roots;
28995
28996
  }
28997
+ /**
28998
+ * Back-fill catalog steps EXPLICITLY flagged `backfillIntoExistingOverrides`
28999
+ * but ENTIRELY ABSENT from an existing step tree — the wholesale-override
29000
+ * analogue of {@link seedAgentAddonDefaults}, which already grows per-agent
29001
+ * `addonDefaults` when the catalog adds a new addon.
29002
+ *
29003
+ * Why it's needed: a `pipelineByAgent` wholesale override SNAPSHOTS the full
29004
+ * tree. A step introduced AFTER the snapshot (e.g. `clip-embedding` for object
29005
+ * semantic search) would otherwise never reach that camera.
29006
+ *
29007
+ * Why the flag (not `enabledByDefault`): absence-from-a-snapshot is ambiguous —
29008
+ * it can mean "predates this step" OR "operator deliberately pruned it". A
29009
+ * broad "back-fill every default-on step" would resurrect deliberate removals
29010
+ * of long-existing steps (face-detection, etc.), an operator-intent
29011
+ * regression. Only a step that opts in via `backfillIntoExistingOverrides`
29012
+ * (set on genuinely-new default steps ONLY) re-enters existing overrides.
29013
+ *
29014
+ * Non-destructive by design:
29015
+ * - Only steps whose id is absent from the WHOLE tree are added. A step the
29016
+ * operator has already seen — present anywhere, even `enabled: false` —
29017
+ * is left exactly as-is (its enabled flag is honoured).
29018
+ * - Only `backfillIntoExistingOverrides === true` addons are candidates.
29019
+ * Every other step (including long-standing default-on members) is left
29020
+ * untouched, so a deliberately-pruned override stays pruned.
29021
+ * - Audio-classifier addons are skipped — audio lives in
29022
+ * `CameraPipelineConfig.audio`, never in the video `steps` tree.
29023
+ * - A candidate attaches by catalog compat (its `inputClasses` overlap a
29024
+ * present node's `outputClasses`), mirroring {@link buildTreeFromAddons}.
29025
+ * A candidate with non-empty `inputClasses` and NO compatible parent is
29026
+ * dropped (never re-rooted) so a crop-scoped embedder never regresses to
29027
+ * running on the full frame. Empty `inputClasses` ⇒ a genuine root step.
29028
+ */
29029
+ function mergeEnabledByDefaultSteps(steps, catalog) {
29030
+ if (steps.length === 0) return steps;
29031
+ const present = /* @__PURE__ */ new Set();
29032
+ const collect = (nodes) => {
29033
+ for (const n of nodes) {
29034
+ present.add(n.addonId);
29035
+ if (n.children?.length) collect(n.children);
29036
+ }
29037
+ };
29038
+ collect(steps);
29039
+ const catalogById = /* @__PURE__ */ new Map();
29040
+ for (const slot of catalog.slots) for (const addon of slot.addons) catalogById.set(addon.id, addon);
29041
+ const newRoots = [];
29042
+ const childrenByParent = /* @__PURE__ */ new Map();
29043
+ for (const slot of catalog.slots) {
29044
+ if (slot.id === "audio-classifier") continue;
29045
+ for (const addon of slot.addons) {
29046
+ if (addon.backfillIntoExistingOverrides !== true) continue;
29047
+ if (present.has(addon.id)) continue;
29048
+ const node = {
29049
+ addonId: addon.id,
29050
+ enabled: true,
29051
+ children: []
29052
+ };
29053
+ const inputs = addon.inputClasses;
29054
+ if (!inputs || inputs.length === 0) {
29055
+ newRoots.push(node);
29056
+ continue;
29057
+ }
29058
+ let parentId = null;
29059
+ for (const presentId of present) {
29060
+ const meta = catalogById.get(presentId);
29061
+ if (!meta) continue;
29062
+ if (meta.outputClasses.some((c) => inputs.includes(c))) {
29063
+ parentId = presentId;
29064
+ break;
29065
+ }
29066
+ }
29067
+ if (!parentId) continue;
29068
+ const list = childrenByParent.get(parentId) ?? [];
29069
+ list.push(node);
29070
+ childrenByParent.set(parentId, list);
29071
+ }
29072
+ }
29073
+ if (childrenByParent.size === 0 && newRoots.length === 0) return steps;
29074
+ const attach = (nodes) => nodes.map((n) => {
29075
+ const rebuiltChildren = attach(n.children ?? []);
29076
+ const extra = childrenByParent.get(n.addonId) ?? [];
29077
+ const mergedChildren = [...rebuiltChildren, ...extra];
29078
+ return mergedChildren.length > 0 ? {
29079
+ ...n,
29080
+ children: mergedChildren
29081
+ } : n;
29082
+ });
29083
+ return [...attach(steps), ...newRoots];
29084
+ }
28996
29085
  //#endregion
28997
29086
  //#region src/orchestrator-store-schemas.ts
28998
29087
  /**
@@ -29435,13 +29524,16 @@ var PipelineSettingsStore = class PipelineSettingsStore {
29435
29524
  }
29436
29525
  const cam = (await this.readCameraSettingsMap())[String(deviceId)] ?? null;
29437
29526
  const wholesale = cam?.pipelineByAgent?.[agentNodeId];
29438
- if (wholesale) return {
29439
- steps: wholesale.steps,
29440
- audio: wholesale.audio ? {
29441
- modelId: wholesale.audio.modelId,
29442
- enabled: wholesale.audio.enabled
29443
- } : null
29444
- };
29527
+ if (wholesale) {
29528
+ const catalog = await this.getCatalogForAgent(agentNodeId);
29529
+ return {
29530
+ steps: catalog ? mergeEnabledByDefaultSteps(wholesale.steps, catalog) : wholesale.steps,
29531
+ audio: wholesale.audio ? {
29532
+ modelId: wholesale.audio.modelId,
29533
+ enabled: wholesale.audio.enabled
29534
+ } : null
29535
+ };
29536
+ }
29445
29537
  const { agent, catalog } = await this.waitForAgentAndCatalog(agentNodeId);
29446
29538
  const resolved = resolvePipeline(agent, cam, agentNodeId, catalog);
29447
29539
  if (resolved.steps.length === 0) this.deps.logger.info("resolvePipelineForDevice → empty (all addons disabled)", {
@@ -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-D0Ou9G9F.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DWO6Xr0a.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.42",
3
+ "version": "1.1.43",
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",