@camstack/addon-pipeline 1.2.186 → 1.2.187

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.
Files changed (35) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/detection-pipeline/index.js +5 -5
  4. package/dist/detection-pipeline/index.mjs +4 -4
  5. package/dist/{dist-yaetDPpW.mjs → dist-HWdaHLLK.mjs} +21 -0
  6. package/dist/{dist-CiVhPNTB.js → dist-e2OKAUGI.js} +21 -0
  7. package/dist/{lazy-sharp-D1PX1Psd.js → lazy-sharp-AJ7newgl.js} +1 -1
  8. package/dist/{local-frame-registry-CANBpRSg.mjs → local-frame-registry-Bki81rM0.mjs} +1 -1
  9. package/dist/{local-frame-registry-D5d1W1Vf.js → local-frame-registry-Cl1VUpx_.js} +1 -1
  10. package/dist/motion-wasm/index.js +2 -2
  11. package/dist/motion-wasm/index.mjs +1 -1
  12. package/dist/{node-CCRb0fqW.mjs → node-CwSBH-3M.mjs} +1 -1
  13. package/dist/{node-DGzZLg-c.js → node-Dw6FrDFS.js} +1 -1
  14. package/dist/pipeline-runner/index.js +166 -10
  15. package/dist/pipeline-runner/index.mjs +165 -9
  16. package/dist/{process-memory-BIy4e_mZ.mjs → process-memory-CnTmyk9s.mjs} +1 -1
  17. package/dist/{process-memory-C7f66zUT.js → process-memory-einpgBTr.js} +1 -1
  18. package/dist/recorder/index.js +2 -2
  19. package/dist/recorder/index.mjs +2 -2
  20. package/dist/{segment-demux-js-CViDx3UC.mjs → segment-demux-js-CuNHMubE.mjs} +1 -1
  21. package/dist/{segment-demux-js-CnqYsNPt.js → segment-demux-js-CyCdXcFZ.js} +1 -1
  22. package/dist/session-decode/decode-worker-child.js +2 -2
  23. package/dist/session-decode/decode-worker-child.mjs +1 -1
  24. package/dist/stream-broker/_stub.js +1 -1
  25. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CR6bzoE5.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CWa6ONcK.mjs} +3 -3
  26. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BC-nahrU.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D5XwN-xi.mjs} +1 -1
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-DgDPe3HR.mjs → hostInit-DUqxWCSo.mjs} +3 -3
  30. package/dist/stream-broker/index.js +70 -48
  31. package/dist/stream-broker/index.mjs +70 -48
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/dist/{worker-protocol-Db6BWHr-.js → worker-protocol-Bva8nEZO.js} +1 -1
  34. package/dist/{worker-protocol-BcKW7xgG.mjs → worker-protocol-DN1y2Vyk.mjs} +1 -1
  35. package/package.json +1 -1
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-CiVhPNTB.js");
6
- const require_process_memory = require("../process-memory-C7f66zUT.js");
5
+ const require_dist = require("../dist-e2OKAUGI.js");
6
+ const require_process_memory = require("../process-memory-einpgBTr.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_dist.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -1,6 +1,6 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { A as audioAnalysisCapability, It as hydrateSchema, S as PoolMemoryWatchdog, _t as resolvePoolMemoryPolicy, j as audioAnalyzerCapability, jt as BaseAddon, n as AUDIO_BACKEND_CHOICES, nt as mapAudioLabelToMacro, s as DEFAULT_AUDIO_ANALYZER_CONFIG, v as HF_BASE_URL, wt as errMsg } from "../dist-yaetDPpW.mjs";
3
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BIy4e_mZ.mjs";
2
+ import { A as audioAnalysisCapability, It as hydrateSchema, S as PoolMemoryWatchdog, _t as resolvePoolMemoryPolicy, j as audioAnalyzerCapability, jt as BaseAddon, n as AUDIO_BACKEND_CHOICES, nt as mapAudioLabelToMacro, s as DEFAULT_AUDIO_ANALYZER_CONFIG, v as HF_BASE_URL, wt as errMsg } from "../dist-HWdaHLLK.mjs";
3
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-CnTmyk9s.mjs";
4
4
  import * as fs from "node:fs";
5
5
  import * as path$1 from "node:path";
6
6
  import { downloadFile } from "@camstack/system/addon-utils";
@@ -2,12 +2,12 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-CiVhPNTB.js");
6
- const require_node = require("../node-DGzZLg-c.js");
7
- const require_local_frame_registry = require("../local-frame-registry-D5d1W1Vf.js");
8
- const require_lazy_sharp = require("../lazy-sharp-D1PX1Psd.js");
5
+ const require_dist = require("../dist-e2OKAUGI.js");
6
+ const require_node = require("../node-Dw6FrDFS.js");
7
+ const require_local_frame_registry = require("../local-frame-registry-Cl1VUpx_.js");
8
+ const require_lazy_sharp = require("../lazy-sharp-AJ7newgl.js");
9
9
  const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-DaUBcb13.js");
10
- const require_process_memory = require("../process-memory-C7f66zUT.js");
10
+ const require_process_memory = require("../process-memory-einpgBTr.js");
11
11
  let node_fs = require("node:fs");
12
12
  node_fs = require_dist.__toESM(node_fs);
13
13
  let node_path = require("node:path");
@@ -1,10 +1,10 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { Bt as nodePin, Ft as createEvent, It as hydrateSchema, Kt as array, O as YAMNET_TO_MACRO, Q as inferModelProvider, Qt as object, S as PoolMemoryWatchdog, St as supportedRuntimes$1, Vt as parseJsonUnknown, W as detectionPipelineCapability, Wt as sleep, Y as evaluateZoneRules, _t as resolvePoolMemoryPolicy, at as overlayClusterStepSettings, c as DEFAULT_CLUSTER_STEP_MODELS, ct as pickClusterStepSettings, dt as pipelineExecutorCapability, en as string, et as loadContributionCapability, ht as resolveClusterStepModelId, jt as BaseAddon, l as DEFAULT_CLUSTER_STEP_SETTINGS, m as DEVICE_BACKEND_TO_FORMAT, nn as EventCategory, q as enumerateInferenceDevices, st as pickClusterStepModels, t as APPLE_SA_TO_MACRO, tn as union, wt as errMsg, yt as runtimeDevices$1, z as defaultDeviceFor$1 } from "../dist-yaetDPpW.mjs";
3
- import { a as readProcessCost } from "../node-CCRb0fqW.mjs";
4
- import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep, t as localFrameRegistry, u as landmarkPrecisionVerdict } from "../local-frame-registry-CANBpRSg.mjs";
2
+ import { Bt as nodePin, Ft as createEvent, It as hydrateSchema, Kt as array, O as YAMNET_TO_MACRO, Q as inferModelProvider, Qt as object, S as PoolMemoryWatchdog, St as supportedRuntimes$1, Vt as parseJsonUnknown, W as detectionPipelineCapability, Wt as sleep, Y as evaluateZoneRules, _t as resolvePoolMemoryPolicy, at as overlayClusterStepSettings, c as DEFAULT_CLUSTER_STEP_MODELS, ct as pickClusterStepSettings, dt as pipelineExecutorCapability, en as string, et as loadContributionCapability, ht as resolveClusterStepModelId, jt as BaseAddon, l as DEFAULT_CLUSTER_STEP_SETTINGS, m as DEVICE_BACKEND_TO_FORMAT, nn as EventCategory, q as enumerateInferenceDevices, st as pickClusterStepModels, t as APPLE_SA_TO_MACRO, tn as union, wt as errMsg, yt as runtimeDevices$1, z as defaultDeviceFor$1 } from "../dist-HWdaHLLK.mjs";
3
+ import { a as readProcessCost } from "../node-CwSBH-3M.mjs";
4
+ import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep, t as localFrameRegistry, u as landmarkPrecisionVerdict } from "../local-frame-registry-Bki81rM0.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
6
6
  import { n as startEventLoopStallMonitor } from "../event-loop-stall-monitor-DXLNBMkY.mjs";
7
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BIy4e_mZ.mjs";
7
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-CnTmyk9s.mjs";
8
8
  import * as fs from "node:fs";
9
9
  import * as path$1 from "node:path";
10
10
  import { dirname, join } from "node:path";
@@ -22996,6 +22996,14 @@ var AgentPipelineSettingsSchema = object({
22996
22996
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
22997
22997
  ingest: boolean().optional(),
22998
22998
  /**
22999
+ * This node's decode RAM budget, MB. Wins over the cluster
23000
+ * `nodeDecodeBudgetMb`; absent, the runner derives a share of the node's
23001
+ * own RAM unless the operator moved the cluster value (D365).
23002
+ */
23003
+ decodeBudgetMb: number().int().min(1024).max(65536).optional(),
23004
+ /** This node's live decode-worker cap. Wins over the cluster `maxLiveDecodeWorkers`. */
23005
+ maxLiveDecodeWorkers: number().int().min(1).max(32).optional(),
23006
+ /**
22999
23007
  * Operator override for the LAN host a cross-node decoder dials to reach
23000
23008
  * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
23001
23009
  * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
@@ -23457,6 +23465,13 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
23457
23465
  }), object({ success: literal(true) }), {
23458
23466
  kind: "mutation",
23459
23467
  auth: "admin"
23468
+ }), method(object({
23469
+ agentNodeId: string(),
23470
+ decodeBudgetMb: number().int().min(1024).max(65536).nullable().optional(),
23471
+ maxLiveDecodeWorkers: number().int().min(1).max(32).nullable().optional()
23472
+ }), object({ success: literal(true) }), {
23473
+ kind: "mutation",
23474
+ auth: "admin"
23460
23475
  }), method(object({
23461
23476
  agentNodeId: string(),
23462
23477
  reachableHost: string().nullable()
@@ -36190,6 +36205,12 @@ Object.freeze({
36190
36205
  addonId: null,
36191
36206
  access: "create"
36192
36207
  },
36208
+ "pipelineOrchestrator.setAgentDecodeLimits": {
36209
+ capName: "pipeline-orchestrator",
36210
+ capScope: "system",
36211
+ addonId: null,
36212
+ access: "create"
36213
+ },
36193
36214
  "pipelineOrchestrator.setAgentDetectWeight": {
36194
36215
  capName: "pipeline-orchestrator",
36195
36216
  capScope: "system",
@@ -23019,6 +23019,14 @@ var AgentPipelineSettingsSchema = object({
23019
23019
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
23020
23020
  ingest: boolean().optional(),
23021
23021
  /**
23022
+ * This node's decode RAM budget, MB. Wins over the cluster
23023
+ * `nodeDecodeBudgetMb`; absent, the runner derives a share of the node's
23024
+ * own RAM unless the operator moved the cluster value (D365).
23025
+ */
23026
+ decodeBudgetMb: number().int().min(1024).max(65536).optional(),
23027
+ /** This node's live decode-worker cap. Wins over the cluster `maxLiveDecodeWorkers`. */
23028
+ maxLiveDecodeWorkers: number().int().min(1).max(32).optional(),
23029
+ /**
23022
23030
  * Operator override for the LAN host a cross-node decoder dials to reach
23023
23031
  * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
23024
23032
  * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
@@ -23480,6 +23488,13 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
23480
23488
  }), object({ success: literal(true) }), {
23481
23489
  kind: "mutation",
23482
23490
  auth: "admin"
23491
+ }), method(object({
23492
+ agentNodeId: string(),
23493
+ decodeBudgetMb: number().int().min(1024).max(65536).nullable().optional(),
23494
+ maxLiveDecodeWorkers: number().int().min(1).max(32).nullable().optional()
23495
+ }), object({ success: literal(true) }), {
23496
+ kind: "mutation",
23497
+ auth: "admin"
23483
23498
  }), method(object({
23484
23499
  agentNodeId: string(),
23485
23500
  reachableHost: string().nullable()
@@ -36213,6 +36228,12 @@ Object.freeze({
36213
36228
  addonId: null,
36214
36229
  access: "create"
36215
36230
  },
36231
+ "pipelineOrchestrator.setAgentDecodeLimits": {
36232
+ capName: "pipeline-orchestrator",
36233
+ capScope: "system",
36234
+ addonId: null,
36235
+ access: "create"
36236
+ },
36216
36237
  "pipelineOrchestrator.setAgentDetectWeight": {
36217
36238
  capName: "pipeline-orchestrator",
36218
36239
  capScope: "system",
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-CiVhPNTB.js");
1
+ const require_dist = require("./dist-e2OKAUGI.js");
2
2
  let node_path = require("node:path");
3
3
  node_path = require_dist.__toESM(node_path);
4
4
  let node_url = require("node:url");
@@ -1,4 +1,4 @@
1
- import { M as audioKindId, Z as hfModelUrl, a as COCO_80_LABELS, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-yaetDPpW.mjs";
1
+ import { M as audioKindId, Z as hfModelUrl, a as COCO_80_LABELS, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-HWdaHLLK.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-CiVhPNTB.js");
1
+ const require_dist = require("./dist-e2OKAUGI.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-CiVhPNTB.js");
6
- const require_lazy_sharp = require("../lazy-sharp-D1PX1Psd.js");
5
+ const require_dist = require("../dist-e2OKAUGI.js");
6
+ const require_lazy_sharp = require("../lazy-sharp-AJ7newgl.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_path = require("node:path");
9
9
  //#region src/motion-wasm/wasm-motion-detector.ts
@@ -1,4 +1,4 @@
1
- import { It as hydrateSchema, Pt as DeviceType, Y as evaluateZoneRules, it as motionDetectionCapability, jt as BaseAddon } from "../dist-yaetDPpW.mjs";
1
+ import { It as hydrateSchema, Pt as DeviceType, Y as evaluateZoneRules, it as motionDetectionCapability, jt as BaseAddon } from "../dist-HWdaHLLK.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -1,4 +1,4 @@
1
- import { Dt as buildFfmpegArgs, Et as Fmp4BoxSplitter, kt as isSoftwareDecode, wt as errMsg } from "./dist-yaetDPpW.mjs";
1
+ import { Dt as buildFfmpegArgs, Et as Fmp4BoxSplitter, kt as isSoftwareDecode, wt as errMsg } from "./dist-HWdaHLLK.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import "node:fs";
4
4
  import "node:path";
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-CiVhPNTB.js");
1
+ const require_dist = require("./dist-e2OKAUGI.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  let node_fs = require("node:fs");
4
4
  node_fs = require_dist.__toESM(node_fs, 1);
@@ -2,12 +2,12 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-CiVhPNTB.js");
6
- const require_node = require("../node-DGzZLg-c.js");
7
- const require_local_frame_registry = require("../local-frame-registry-D5d1W1Vf.js");
8
- const require_worker_protocol = require("../worker-protocol-Db6BWHr-.js");
5
+ const require_dist = require("../dist-e2OKAUGI.js");
6
+ const require_node = require("../node-Dw6FrDFS.js");
7
+ const require_local_frame_registry = require("../local-frame-registry-Cl1VUpx_.js");
8
+ const require_worker_protocol = require("../worker-protocol-Bva8nEZO.js");
9
9
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
10
- const require_lazy_sharp = require("../lazy-sharp-D1PX1Psd.js");
10
+ const require_lazy_sharp = require("../lazy-sharp-AJ7newgl.js");
11
11
  const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
12
12
  const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-DaUBcb13.js");
13
13
  const require_remote_restream = require("../remote-restream-CO36Sr30.js");
@@ -3381,6 +3381,115 @@ var NativeLeaseSettingsSource = class {
3381
3381
  }
3382
3382
  }
3383
3383
  };
3384
+ function pickLimits(settings) {
3385
+ if (settings === null) return {};
3386
+ const budget = settings.decodeBudgetMb;
3387
+ const workers = settings.maxLiveDecodeWorkers;
3388
+ return {
3389
+ ...typeof budget === "number" && Number.isFinite(budget) ? { decodeBudgetMb: budget } : {},
3390
+ ...typeof workers === "number" && Number.isFinite(workers) ? { maxLiveDecodeWorkers: workers } : {}
3391
+ };
3392
+ }
3393
+ function sameLimits(a, b) {
3394
+ return a.decodeBudgetMb === b.decodeBudgetMb && a.maxLiveDecodeWorkers === b.maxLiveDecodeWorkers;
3395
+ }
3396
+ var NodeDecodeLimitsSource = class {
3397
+ limits = {};
3398
+ lastReadAtMs = Number.NEGATIVE_INFINITY;
3399
+ inFlight = null;
3400
+ logger;
3401
+ nodeId;
3402
+ now;
3403
+ ttlMs;
3404
+ constructor(options) {
3405
+ this.logger = options.logger;
3406
+ this.nodeId = options.nodeId;
3407
+ this.now = options.now ?? (() => Date.now());
3408
+ this.ttlMs = options.ttlMs ?? 3e4;
3409
+ }
3410
+ current() {
3411
+ return this.limits;
3412
+ }
3413
+ async refresh(api) {
3414
+ if (api === void 0) return this.limits;
3415
+ if (this.now() - this.lastReadAtMs < this.ttlMs) return this.limits;
3416
+ this.inFlight ??= this.read(api).finally(() => {
3417
+ this.inFlight = null;
3418
+ });
3419
+ await this.inFlight;
3420
+ return this.limits;
3421
+ }
3422
+ async read(api) {
3423
+ try {
3424
+ const next = pickLimits(await api.pipelineOrchestrator.getAgentSettings.query({ agentNodeId: this.nodeId }));
3425
+ this.lastReadAtMs = this.now();
3426
+ if (sameLimits(next, this.limits)) return;
3427
+ this.logger.info("node decode limits changed — the admission resizes on the next dial", { meta: {
3428
+ nodeId: this.nodeId,
3429
+ from: { ...this.limits },
3430
+ to: { ...next }
3431
+ } });
3432
+ this.limits = next;
3433
+ } catch (err) {
3434
+ this.logger.warn("node decode limits read failed — keeping the last known value", { meta: {
3435
+ nodeId: this.nodeId,
3436
+ inUse: { ...this.limits },
3437
+ error: err instanceof Error ? err.message : String(err)
3438
+ } });
3439
+ }
3440
+ }
3441
+ };
3442
+ //#endregion
3443
+ //#region src/session-decode/node-decode-budget.ts
3444
+ /**
3445
+ * The decode RAM budget and worker cap THIS node runs under.
3446
+ *
3447
+ * `nodeDecodeBudgetMb` and `maxLiveDecodeWorkers` are cluster settings — one
3448
+ * number for a 64 GB hub and a 16 GB Mac alike. Until 2026-09-05 the shipped
3449
+ * 8192 MB was the hub's number applied everywhere; with the footprint fixed
3450
+ * (D361) that admits 8 workers on the Mac, whose workers measure RSS p90
3451
+ * 952 MB on a machine already at 80% — 7.6 GB of decode on 16 GB is a swap
3452
+ * away from a stall — while the hub leaves 56 GB idle.
3453
+ *
3454
+ * Three sources, in order, and the winner is NAMED so a log line can say why:
3455
+ * 1. the node's own agent setting (`decodeBudgetMb` / `maxLiveDecodeWorkers`
3456
+ * in `AgentPipelineSettings`) — the operator sized this node;
3457
+ * 2. the cluster setting when the operator MOVED it off the shipped default
3458
+ * (a value equal to the default reads as "nobody chose", the same rule the
3459
+ * native-lease knobs use, `readKnob`);
3460
+ * 3. otherwise a share of the node's RAM, clamped — the shipped default
3461
+ * becomes a per-node derivation instead of the hub's number.
3462
+ */
3463
+ var DECODE_BUDGET_RAM_SHARE = .5;
3464
+ var DECODE_BUDGET_MIN_MB = 2048;
3465
+ var DECODE_BUDGET_MAX_MB = 16384;
3466
+ function derivedDecodeBudgetMb(totalMemMb) {
3467
+ const share = Math.round(totalMemMb * DECODE_BUDGET_RAM_SHARE);
3468
+ return Math.max(DECODE_BUDGET_MIN_MB, Math.min(DECODE_BUDGET_MAX_MB, share));
3469
+ }
3470
+ function resolveNodeDecodeLimits(input) {
3471
+ const budget = input.node.decodeBudgetMb !== void 0 ? {
3472
+ budgetMb: input.node.decodeBudgetMb,
3473
+ budgetSource: "node"
3474
+ } : input.clusterBudgetMb !== input.defaultBudgetMb ? {
3475
+ budgetMb: input.clusterBudgetMb,
3476
+ budgetSource: "cluster"
3477
+ } : {
3478
+ budgetMb: derivedDecodeBudgetMb(input.totalMemMb),
3479
+ budgetSource: "derived"
3480
+ };
3481
+ const workers = input.node.maxLiveDecodeWorkers !== void 0 ? {
3482
+ maxWorkers: input.node.maxLiveDecodeWorkers,
3483
+ maxWorkersSource: "node"
3484
+ } : {
3485
+ maxWorkers: input.clusterMaxWorkers,
3486
+ maxWorkersSource: "cluster"
3487
+ };
3488
+ return {
3489
+ ...budget,
3490
+ ...workers
3491
+ };
3492
+ }
3384
3493
  //#endregion
3385
3494
  //#region src/session-decode/on-demand-jpeg-fallback.ts
3386
3495
  /** Single content-sized JPEG requested after a detection, never a continuous raw. */
@@ -5652,6 +5761,13 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
5652
5761
  async onInitialize() {
5653
5762
  const raw = this.ctx.kernel.localNodeId ?? this.ctx.id;
5654
5763
  this.nodeId = raw.includes("/") ? raw.split("/")[0] : raw;
5764
+ this.nodeLimits = new NodeDecodeLimitsSource({
5765
+ logger: {
5766
+ info: (message, extras) => this.ctxIfReady?.logger.info(message, extras),
5767
+ warn: (message, extras) => this.ctxIfReady?.logger.warn(message, extras)
5768
+ },
5769
+ nodeId: this.nodeId
5770
+ });
5655
5771
  this.retainedFrames = new RetainedFrameStore({
5656
5772
  nodeId: this.nodeId,
5657
5773
  logger: this.ctx.logger.child("retained-frames"),
@@ -5682,10 +5798,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
5682
5798
  noteInferenceOutcome: (deviceId, reason) => this.failureReport.noteInference(deviceId, reason)
5683
5799
  });
5684
5800
  this.spawnGate = new Semaphore(Math.max(1, this.config.maxConcurrentDecodeSpawns));
5801
+ const decodeLimits = this.resolvedNodeDecodeLimits();
5802
+ this.lastAppliedDecodeLimits = decodeLimits;
5685
5803
  this.liveWorkers = new LiveDecodeWorkerAdmission({
5686
- limit: this.config.maxLiveDecodeWorkers,
5804
+ limit: decodeLimits.maxWorkers,
5687
5805
  logger: this.ctx.logger,
5688
- nodeBudgetMb: this.config.nodeDecodeBudgetMb,
5806
+ nodeBudgetMb: decodeLimits.budgetMb,
5689
5807
  workerFootprintMb: decodeWorkerFootprintMb(this.nativeLease.current())
5690
5808
  });
5691
5809
  this.runner.timingSampler.setLogger(this.ctx.logger.child("timing"));
@@ -6907,6 +7025,45 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
6907
7025
  async refreshNativeLeaseSettings() {
6908
7026
  await this.nativeLease.refresh(this.ctxIfReady?.api);
6909
7027
  this.liveWorkers?.resizeFootprint(decodeWorkerFootprintMb(this.nativeLease.current()));
7028
+ await this.nodeLimits?.refresh(this.ctxIfReady?.api);
7029
+ this.applyNodeDecodeLimits("lease-refresh");
7030
+ }
7031
+ nodeLimits = null;
7032
+ lastAppliedDecodeLimits = null;
7033
+ /**
7034
+ * The budget and worker cap THIS node runs under (D365): its own agent
7035
+ * setting, else the cluster value the operator moved, else a share of its
7036
+ * RAM. One resolver for construction, the lease refresh and a settings
7037
+ * change — computing it in two places is how the count and the budget
7038
+ * drifted apart before.
7039
+ */
7040
+ resolvedNodeDecodeLimits() {
7041
+ return resolveNodeDecodeLimits({
7042
+ clusterBudgetMb: this.config.nodeDecodeBudgetMb,
7043
+ clusterMaxWorkers: this.config.maxLiveDecodeWorkers,
7044
+ defaultBudgetMb: DEFAULT_CONFIG.nodeDecodeBudgetMb,
7045
+ defaultMaxWorkers: DEFAULT_CONFIG.maxLiveDecodeWorkers,
7046
+ node: this.nodeLimits?.current() ?? {},
7047
+ totalMemMb: Math.round(node_os.totalmem() / 1048576)
7048
+ });
7049
+ }
7050
+ /** Resize the admission to the resolved limits; one info line per change. */
7051
+ applyNodeDecodeLimits(reason) {
7052
+ const limits = this.resolvedNodeDecodeLimits();
7053
+ const last = this.lastAppliedDecodeLimits;
7054
+ this.liveWorkers?.resize(limits.maxWorkers);
7055
+ this.liveWorkers?.resizeBudget(limits.budgetMb);
7056
+ if (last !== null && last.budgetMb === limits.budgetMb && last.maxWorkers === limits.maxWorkers && last.budgetSource === limits.budgetSource) return;
7057
+ this.lastAppliedDecodeLimits = limits;
7058
+ this.ctxIfReady?.logger.info("node decode limits in force", { meta: {
7059
+ reason,
7060
+ budgetMb: limits.budgetMb,
7061
+ budgetSource: limits.budgetSource,
7062
+ maxWorkers: limits.maxWorkers,
7063
+ maxWorkersSource: limits.maxWorkersSource,
7064
+ totalMemMb: Math.round(node_os.totalmem() / 1048576),
7065
+ decodeCeilingInForce: this.liveWorkers?.ceiling
7066
+ } });
6910
7067
  }
6911
7068
  /** A warn sink that survives a not-yet-ready ctx (boot-order dispatches). */
6912
7069
  detailSubtreeLogger() {
@@ -8456,7 +8613,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8456
8613
  type: "slider",
8457
8614
  key: "nodeDecodeBudgetMb",
8458
8615
  label: "Decode worker RAM budget (MB)",
8459
- description: "RAM this node lets live decode workers commit. The number of workers admitted is derived from it: budget divided by one worker's worst case — what the lease store can hold (the smaller of the lease budget and held frames × 25 MB, one 4K frame), plus the tile and scene budgets, plus ~350 MB of baseline. At the shipped lease knobs (8 held frames) that is ~660 MB and the default admits 8, the count cap. Raising held frames or the lease budget per worker therefore LOWERS how many run at once, instead of silently authorising more memory.",
8616
+ description: "Cluster-wide fallback for the RAM a node lets live decode workers commit. Each node resolves its own number: its agent setting (decodeBudgetMb) wins; otherwise this value when moved off the default; otherwise half the node's RAM (2-16 GB). The number of workers admitted is derived from it: budget divided by one worker's worst case — what the lease store can hold (the smaller of the lease budget and held frames × 25 MB, one 4K frame), plus the tile and scene budgets, plus ~350 MB of baseline, ~660 MB at the shipped knobs. Raising held frames or the lease budget per worker therefore LOWERS how many run at once, instead of silently authorising more memory.",
8460
8617
  min: 1024,
8461
8618
  max: 32768,
8462
8619
  step: 512,
@@ -8469,8 +8626,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8469
8626
  async onConfigChanged() {
8470
8627
  this.runner?.updateLimits(this.config);
8471
8628
  this.spawnGate?.resize(Math.max(1, this.config.maxConcurrentDecodeSpawns));
8472
- this.liveWorkers?.resize(this.config.maxLiveDecodeWorkers);
8473
- this.liveWorkers?.resizeBudget(this.config.nodeDecodeBudgetMb);
8629
+ this.applyNodeDecodeLimits("settings");
8474
8630
  this.ctx.logger.info("pipeline-runner tuning updated", { meta: {
8475
8631
  maxQueueDepth: this.config.maxQueueDepth,
8476
8632
  maxConcurrentInferences: this.config.maxConcurrentInferences,
@@ -1,7 +1,7 @@
1
- import { B as defineCustomActions, Bt as nodePin, Ft as createEvent, Gt as _enum, H as deriveDetailCropRect, Kt as array, L as customAction, Qt as object, X as failureContributionCapability, Xt as literal, Yt as lazy, Zt as number, _ as FailureCounters, en as string, et as loadContributionCapability, ft as pipelineRunnerCapability, jt as BaseAddon, lt as pickDetailCropConvention, m as DEVICE_BACKEND_TO_FORMAT, nn as EventCategory, p as DEFAULT_NATIVE_LEASE_SETTINGS, qt as boolean, u as DEFAULT_DETAIL_CROP_CONVENTION, ut as pickNativeLeaseOverride, wt as errMsg } from "../dist-yaetDPpW.mjs";
2
- import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-CCRb0fqW.mjs";
3
- import { c as getStepDefinition, n as resolveFrameViewGeometry, t as localFrameRegistry } from "../local-frame-registry-CANBpRSg.mjs";
4
- import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-BcKW7xgG.mjs";
1
+ import { B as defineCustomActions, Bt as nodePin, Ft as createEvent, Gt as _enum, H as deriveDetailCropRect, Kt as array, L as customAction, Qt as object, X as failureContributionCapability, Xt as literal, Yt as lazy, Zt as number, _ as FailureCounters, en as string, et as loadContributionCapability, ft as pipelineRunnerCapability, jt as BaseAddon, lt as pickDetailCropConvention, m as DEVICE_BACKEND_TO_FORMAT, nn as EventCategory, p as DEFAULT_NATIVE_LEASE_SETTINGS, qt as boolean, u as DEFAULT_DETAIL_CROP_CONVENTION, ut as pickNativeLeaseOverride, wt as errMsg } from "../dist-HWdaHLLK.mjs";
2
+ import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-CwSBH-3M.mjs";
3
+ import { c as getStepDefinition, n as resolveFrameViewGeometry, t as localFrameRegistry } from "../local-frame-registry-Bki81rM0.mjs";
4
+ import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-DN1y2Vyk.mjs";
5
5
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
6
6
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
7
7
  import { n as withDetectionIntent } from "../restream-intent-B4BXZra7.mjs";
@@ -3374,6 +3374,115 @@ var NativeLeaseSettingsSource = class {
3374
3374
  }
3375
3375
  }
3376
3376
  };
3377
+ function pickLimits(settings) {
3378
+ if (settings === null) return {};
3379
+ const budget = settings.decodeBudgetMb;
3380
+ const workers = settings.maxLiveDecodeWorkers;
3381
+ return {
3382
+ ...typeof budget === "number" && Number.isFinite(budget) ? { decodeBudgetMb: budget } : {},
3383
+ ...typeof workers === "number" && Number.isFinite(workers) ? { maxLiveDecodeWorkers: workers } : {}
3384
+ };
3385
+ }
3386
+ function sameLimits(a, b) {
3387
+ return a.decodeBudgetMb === b.decodeBudgetMb && a.maxLiveDecodeWorkers === b.maxLiveDecodeWorkers;
3388
+ }
3389
+ var NodeDecodeLimitsSource = class {
3390
+ limits = {};
3391
+ lastReadAtMs = Number.NEGATIVE_INFINITY;
3392
+ inFlight = null;
3393
+ logger;
3394
+ nodeId;
3395
+ now;
3396
+ ttlMs;
3397
+ constructor(options) {
3398
+ this.logger = options.logger;
3399
+ this.nodeId = options.nodeId;
3400
+ this.now = options.now ?? (() => Date.now());
3401
+ this.ttlMs = options.ttlMs ?? 3e4;
3402
+ }
3403
+ current() {
3404
+ return this.limits;
3405
+ }
3406
+ async refresh(api) {
3407
+ if (api === void 0) return this.limits;
3408
+ if (this.now() - this.lastReadAtMs < this.ttlMs) return this.limits;
3409
+ this.inFlight ??= this.read(api).finally(() => {
3410
+ this.inFlight = null;
3411
+ });
3412
+ await this.inFlight;
3413
+ return this.limits;
3414
+ }
3415
+ async read(api) {
3416
+ try {
3417
+ const next = pickLimits(await api.pipelineOrchestrator.getAgentSettings.query({ agentNodeId: this.nodeId }));
3418
+ this.lastReadAtMs = this.now();
3419
+ if (sameLimits(next, this.limits)) return;
3420
+ this.logger.info("node decode limits changed — the admission resizes on the next dial", { meta: {
3421
+ nodeId: this.nodeId,
3422
+ from: { ...this.limits },
3423
+ to: { ...next }
3424
+ } });
3425
+ this.limits = next;
3426
+ } catch (err) {
3427
+ this.logger.warn("node decode limits read failed — keeping the last known value", { meta: {
3428
+ nodeId: this.nodeId,
3429
+ inUse: { ...this.limits },
3430
+ error: err instanceof Error ? err.message : String(err)
3431
+ } });
3432
+ }
3433
+ }
3434
+ };
3435
+ //#endregion
3436
+ //#region src/session-decode/node-decode-budget.ts
3437
+ /**
3438
+ * The decode RAM budget and worker cap THIS node runs under.
3439
+ *
3440
+ * `nodeDecodeBudgetMb` and `maxLiveDecodeWorkers` are cluster settings — one
3441
+ * number for a 64 GB hub and a 16 GB Mac alike. Until 2026-09-05 the shipped
3442
+ * 8192 MB was the hub's number applied everywhere; with the footprint fixed
3443
+ * (D361) that admits 8 workers on the Mac, whose workers measure RSS p90
3444
+ * 952 MB on a machine already at 80% — 7.6 GB of decode on 16 GB is a swap
3445
+ * away from a stall — while the hub leaves 56 GB idle.
3446
+ *
3447
+ * Three sources, in order, and the winner is NAMED so a log line can say why:
3448
+ * 1. the node's own agent setting (`decodeBudgetMb` / `maxLiveDecodeWorkers`
3449
+ * in `AgentPipelineSettings`) — the operator sized this node;
3450
+ * 2. the cluster setting when the operator MOVED it off the shipped default
3451
+ * (a value equal to the default reads as "nobody chose", the same rule the
3452
+ * native-lease knobs use, `readKnob`);
3453
+ * 3. otherwise a share of the node's RAM, clamped — the shipped default
3454
+ * becomes a per-node derivation instead of the hub's number.
3455
+ */
3456
+ var DECODE_BUDGET_RAM_SHARE = .5;
3457
+ var DECODE_BUDGET_MIN_MB = 2048;
3458
+ var DECODE_BUDGET_MAX_MB = 16384;
3459
+ function derivedDecodeBudgetMb(totalMemMb) {
3460
+ const share = Math.round(totalMemMb * DECODE_BUDGET_RAM_SHARE);
3461
+ return Math.max(DECODE_BUDGET_MIN_MB, Math.min(DECODE_BUDGET_MAX_MB, share));
3462
+ }
3463
+ function resolveNodeDecodeLimits(input) {
3464
+ const budget = input.node.decodeBudgetMb !== void 0 ? {
3465
+ budgetMb: input.node.decodeBudgetMb,
3466
+ budgetSource: "node"
3467
+ } : input.clusterBudgetMb !== input.defaultBudgetMb ? {
3468
+ budgetMb: input.clusterBudgetMb,
3469
+ budgetSource: "cluster"
3470
+ } : {
3471
+ budgetMb: derivedDecodeBudgetMb(input.totalMemMb),
3472
+ budgetSource: "derived"
3473
+ };
3474
+ const workers = input.node.maxLiveDecodeWorkers !== void 0 ? {
3475
+ maxWorkers: input.node.maxLiveDecodeWorkers,
3476
+ maxWorkersSource: "node"
3477
+ } : {
3478
+ maxWorkers: input.clusterMaxWorkers,
3479
+ maxWorkersSource: "cluster"
3480
+ };
3481
+ return {
3482
+ ...budget,
3483
+ ...workers
3484
+ };
3485
+ }
3377
3486
  //#endregion
3378
3487
  //#region src/session-decode/on-demand-jpeg-fallback.ts
3379
3488
  /** Single content-sized JPEG requested after a detection, never a continuous raw. */
@@ -5645,6 +5754,13 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
5645
5754
  async onInitialize() {
5646
5755
  const raw = this.ctx.kernel.localNodeId ?? this.ctx.id;
5647
5756
  this.nodeId = raw.includes("/") ? raw.split("/")[0] : raw;
5757
+ this.nodeLimits = new NodeDecodeLimitsSource({
5758
+ logger: {
5759
+ info: (message, extras) => this.ctxIfReady?.logger.info(message, extras),
5760
+ warn: (message, extras) => this.ctxIfReady?.logger.warn(message, extras)
5761
+ },
5762
+ nodeId: this.nodeId
5763
+ });
5648
5764
  this.retainedFrames = new RetainedFrameStore({
5649
5765
  nodeId: this.nodeId,
5650
5766
  logger: this.ctx.logger.child("retained-frames"),
@@ -5675,10 +5791,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
5675
5791
  noteInferenceOutcome: (deviceId, reason) => this.failureReport.noteInference(deviceId, reason)
5676
5792
  });
5677
5793
  this.spawnGate = new Semaphore(Math.max(1, this.config.maxConcurrentDecodeSpawns));
5794
+ const decodeLimits = this.resolvedNodeDecodeLimits();
5795
+ this.lastAppliedDecodeLimits = decodeLimits;
5678
5796
  this.liveWorkers = new LiveDecodeWorkerAdmission({
5679
- limit: this.config.maxLiveDecodeWorkers,
5797
+ limit: decodeLimits.maxWorkers,
5680
5798
  logger: this.ctx.logger,
5681
- nodeBudgetMb: this.config.nodeDecodeBudgetMb,
5799
+ nodeBudgetMb: decodeLimits.budgetMb,
5682
5800
  workerFootprintMb: decodeWorkerFootprintMb(this.nativeLease.current())
5683
5801
  });
5684
5802
  this.runner.timingSampler.setLogger(this.ctx.logger.child("timing"));
@@ -6900,6 +7018,45 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
6900
7018
  async refreshNativeLeaseSettings() {
6901
7019
  await this.nativeLease.refresh(this.ctxIfReady?.api);
6902
7020
  this.liveWorkers?.resizeFootprint(decodeWorkerFootprintMb(this.nativeLease.current()));
7021
+ await this.nodeLimits?.refresh(this.ctxIfReady?.api);
7022
+ this.applyNodeDecodeLimits("lease-refresh");
7023
+ }
7024
+ nodeLimits = null;
7025
+ lastAppliedDecodeLimits = null;
7026
+ /**
7027
+ * The budget and worker cap THIS node runs under (D365): its own agent
7028
+ * setting, else the cluster value the operator moved, else a share of its
7029
+ * RAM. One resolver for construction, the lease refresh and a settings
7030
+ * change — computing it in two places is how the count and the budget
7031
+ * drifted apart before.
7032
+ */
7033
+ resolvedNodeDecodeLimits() {
7034
+ return resolveNodeDecodeLimits({
7035
+ clusterBudgetMb: this.config.nodeDecodeBudgetMb,
7036
+ clusterMaxWorkers: this.config.maxLiveDecodeWorkers,
7037
+ defaultBudgetMb: DEFAULT_CONFIG.nodeDecodeBudgetMb,
7038
+ defaultMaxWorkers: DEFAULT_CONFIG.maxLiveDecodeWorkers,
7039
+ node: this.nodeLimits?.current() ?? {},
7040
+ totalMemMb: Math.round(os.totalmem() / 1048576)
7041
+ });
7042
+ }
7043
+ /** Resize the admission to the resolved limits; one info line per change. */
7044
+ applyNodeDecodeLimits(reason) {
7045
+ const limits = this.resolvedNodeDecodeLimits();
7046
+ const last = this.lastAppliedDecodeLimits;
7047
+ this.liveWorkers?.resize(limits.maxWorkers);
7048
+ this.liveWorkers?.resizeBudget(limits.budgetMb);
7049
+ if (last !== null && last.budgetMb === limits.budgetMb && last.maxWorkers === limits.maxWorkers && last.budgetSource === limits.budgetSource) return;
7050
+ this.lastAppliedDecodeLimits = limits;
7051
+ this.ctxIfReady?.logger.info("node decode limits in force", { meta: {
7052
+ reason,
7053
+ budgetMb: limits.budgetMb,
7054
+ budgetSource: limits.budgetSource,
7055
+ maxWorkers: limits.maxWorkers,
7056
+ maxWorkersSource: limits.maxWorkersSource,
7057
+ totalMemMb: Math.round(os.totalmem() / 1048576),
7058
+ decodeCeilingInForce: this.liveWorkers?.ceiling
7059
+ } });
6903
7060
  }
6904
7061
  /** A warn sink that survives a not-yet-ready ctx (boot-order dispatches). */
6905
7062
  detailSubtreeLogger() {
@@ -8449,7 +8606,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8449
8606
  type: "slider",
8450
8607
  key: "nodeDecodeBudgetMb",
8451
8608
  label: "Decode worker RAM budget (MB)",
8452
- description: "RAM this node lets live decode workers commit. The number of workers admitted is derived from it: budget divided by one worker's worst case — what the lease store can hold (the smaller of the lease budget and held frames × 25 MB, one 4K frame), plus the tile and scene budgets, plus ~350 MB of baseline. At the shipped lease knobs (8 held frames) that is ~660 MB and the default admits 8, the count cap. Raising held frames or the lease budget per worker therefore LOWERS how many run at once, instead of silently authorising more memory.",
8609
+ description: "Cluster-wide fallback for the RAM a node lets live decode workers commit. Each node resolves its own number: its agent setting (decodeBudgetMb) wins; otherwise this value when moved off the default; otherwise half the node's RAM (2-16 GB). The number of workers admitted is derived from it: budget divided by one worker's worst case — what the lease store can hold (the smaller of the lease budget and held frames × 25 MB, one 4K frame), plus the tile and scene budgets, plus ~350 MB of baseline, ~660 MB at the shipped knobs. Raising held frames or the lease budget per worker therefore LOWERS how many run at once, instead of silently authorising more memory.",
8453
8610
  min: 1024,
8454
8611
  max: 32768,
8455
8612
  step: 512,
@@ -8462,8 +8619,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8462
8619
  async onConfigChanged() {
8463
8620
  this.runner?.updateLimits(this.config);
8464
8621
  this.spawnGate?.resize(Math.max(1, this.config.maxConcurrentDecodeSpawns));
8465
- this.liveWorkers?.resize(this.config.maxLiveDecodeWorkers);
8466
- this.liveWorkers?.resizeBudget(this.config.nodeDecodeBudgetMb);
8622
+ this.applyNodeDecodeLimits("settings");
8467
8623
  this.ctx.logger.info("pipeline-runner tuning updated", { meta: {
8468
8624
  maxQueueDepth: this.config.maxQueueDepth,
8469
8625
  maxConcurrentInferences: this.config.maxConcurrentInferences,
@@ -1,4 +1,4 @@
1
- import { ot as parseProcStatus } from "./dist-yaetDPpW.mjs";
1
+ import { ot as parseProcStatus } from "./dist-HWdaHLLK.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile } from "node:child_process";
4
4
  //#region src/shared/node-topology-platform.ts