@camstack/system 1.1.24 → 1.1.26

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.
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-XWJwKYDx.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-CCmnJmNf.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as resolveAddonClass, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps, yt as installManifestNativeDeps } from "./manifest-python-deps-CPJXzrZt.mjs";
1
+ import { F as createUdsLoggerWithControl, I as LocalChildClient, i as createUdsAddonContext, it as setWorkerNativeCapsChangeListener, nt as getWorkerNativeCapSnapshot, ot as validateProviderRegistrations, t as installManifestPythonDeps, tt as getWorkerNativeCapProvider, vt as installManifestNativeDeps, yt as resolveAddonClass } from "./manifest-python-deps-DPA7N9UP.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as fs from "node:fs";
@@ -1,21 +1,32 @@
1
1
  import { ProviderRegistration, BaseAddon } from '@camstack/types';
2
2
  export { PlatformScorer } from './platform-scorer.js';
3
3
  export { InferenceConfigResolver } from './inference-config-resolver.js';
4
- export { HardwareEncoderProber } from './hardware-encoder-probe.js';
5
- export { HardwareDecodeAccelProber } from './hardware-decode-accel-probe.js';
6
4
  export declare class PlatformProbeNativeAddon extends BaseAddon {
7
5
  private scorer;
8
- private encoderProber;
9
- private decodeAccelProber;
10
6
  private cachedCaps;
7
+ /**
8
+ * Per-boot generation stamp for the manual readiness emissions below.
9
+ * Constant for the lifetime of this addon instance (== one process boot);
10
+ * consumer-side registries derive a monotonic epoch from generation
11
+ * transitions. Mirrors `BaseAddon._readinessGeneration` (private there).
12
+ */
13
+ private readonly readinessGeneration;
11
14
  constructor();
12
15
  /**
13
- * Resolve the ffmpeg binary the encoder probe should test, from the cluster
14
- * `ffmpeg` config section (`binaryPath`). The probe MUST exercise the same
15
- * binary the broker/recorder spawn, or it may report encoders for a different
16
- * ffmpeg. Defaults to PATH `ffmpeg` on any miss.
16
+ * Manual readiness protocol. The provider registers synchronously from
17
+ * `onInitialize`, but the REAL hardware + EP scoring is the async
18
+ * `probePromise` (embedded-Python install included). The BaseAddon
19
+ * auto-emit would flip `ready` at registration time — BEFORE any
20
+ * accelerator is visible — so probe-gated consumers (detection-pipeline
21
+ * engine auto-pick) would read accelerator-blind results and stick on
22
+ * onnx-CPU. Instead: `starting` at init, the single authoritative
23
+ * `ready` once the probe resolves, `down` on shutdown.
17
24
  */
18
- private resolveFfmpegBinaryPath;
25
+ protected get autoEmitReadiness(): boolean;
26
+ /** Bare cluster node id — readiness is scoped `{type:'node', nodeId}`. */
27
+ private bareLocalNodeId;
28
+ /** Emit a `system.ready-state` transition for the platform-probe cap. */
29
+ private emitProbeReadiness;
19
30
  protected onInitialize(): Promise<ProviderRegistration[]>;
20
31
  protected onShutdown(): Promise<void>;
21
32
  }
@@ -18,7 +18,7 @@ node_os = require_chunk.__toESM(node_os);
18
18
  * entire Node process (WS handshakes, tRPC subscriptions, metrics — all
19
19
  * stalled). Keeping it async turns the probe into a true background task.
20
20
  */
21
- var execFileAsync$2 = (0, node_util.promisify)(node_child_process.execFile);
21
+ var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
22
22
  /** Minimal no-op logger for default parameter */
23
23
  var noopLogger = {
24
24
  debug() {},
@@ -46,7 +46,7 @@ async function getAvailableRAM_MB() {
46
46
  const platform = node_os.platform();
47
47
  try {
48
48
  if (platform === "darwin") {
49
- const { stdout: output } = await execFileAsync$2("vm_stat", [], {
49
+ const { stdout: output } = await execFileAsync("vm_stat", [], {
50
50
  encoding: "utf8",
51
51
  timeout: 3e3
52
52
  });
@@ -171,7 +171,7 @@ var PlatformScorer = class {
171
171
  }
172
172
  if (platform === "linux") {
173
173
  try {
174
- const { stdout } = await execFileAsync$2("nvidia-smi", ["--query-gpu=name,memory.total", "--format=csv,noheader"], {
174
+ const { stdout } = await execFileAsync("nvidia-smi", ["--query-gpu=name,memory.total", "--format=csv,noheader"], {
175
175
  encoding: "utf8",
176
176
  timeout: 5e3
177
177
  });
@@ -237,7 +237,7 @@ var PlatformScorer = class {
237
237
  "python"
238
238
  ];
239
239
  for (const cmd of candidates) try {
240
- await execFileAsync$2(cmd, ["--version"], { timeout: 5e3 });
240
+ await execFileAsync(cmd, ["--version"], { timeout: 5e3 });
241
241
  return cmd;
242
242
  } catch (err) {
243
243
  this.logger.debug(`Python command "${cmd}" not found`, { meta: { error: (0, _camstack_types.errMsg)(err) } });
@@ -335,296 +335,91 @@ var InferenceConfigResolver = class {
335
335
  }
336
336
  };
337
337
  //#endregion
338
- //#region src/builtins/platform-probe/hardware-encoder-probe.ts
339
- var execFileAsync$1 = (0, node_util.promisify)(node_child_process.execFile);
340
- function buildCandidates(hardware) {
341
- const out = [];
342
- if (hardware.platform === "darwin") {
343
- out.push({
344
- encoder: "h264_videotoolbox",
345
- codec: "H264",
346
- family: "videotoolbox"
347
- });
348
- out.push({
349
- encoder: "hevc_videotoolbox",
350
- codec: "H265",
351
- family: "videotoolbox"
352
- });
353
- }
354
- if (hardware.platform === "linux") {
355
- if (hardware.gpu?.type === "nvidia") {
356
- out.push({
357
- encoder: "h264_nvenc",
358
- codec: "H264",
359
- family: "nvenc"
360
- });
361
- out.push({
362
- encoder: "hevc_nvenc",
363
- codec: "H265",
364
- family: "nvenc"
365
- });
366
- }
367
- if (hardware.gpu?.type === "intel" || hardware.gpu?.type === "amd" || !hardware.gpu) {
368
- out.push({
369
- encoder: "h264_vaapi",
370
- codec: "H264",
371
- family: "vaapi"
372
- });
373
- out.push({
374
- encoder: "hevc_vaapi",
375
- codec: "H265",
376
- family: "vaapi"
377
- });
378
- }
379
- }
380
- return out;
381
- }
382
- async function runTestEncode(candidate, opts) {
383
- const ffmpeg = opts.ffmpegPath ?? "ffmpeg";
384
- const timeout = opts.timeoutMs ?? 8e3;
385
- const nullSink = node_os.platform() === "win32" ? "NUL" : "/dev/null";
386
- const baseArgs = [
387
- "-hide_banner",
388
- "-loglevel",
389
- "error"
390
- ];
391
- const inArgs = [
392
- "-f",
393
- "lavfi",
394
- "-i",
395
- "testsrc=duration=0.04:size=16x16:rate=25"
396
- ];
397
- let outArgs;
398
- if (candidate.family === "vaapi") outArgs = [
399
- "-vaapi_device",
400
- "/dev/dri/renderD128",
401
- "-vf",
402
- "format=nv12,hwupload",
403
- "-c:v",
404
- candidate.encoder,
405
- "-frames:v",
406
- "1",
407
- "-f",
408
- "null",
409
- nullSink
410
- ];
411
- else outArgs = [
412
- "-c:v",
413
- candidate.encoder,
414
- "-frames:v",
415
- "1",
416
- "-f",
417
- "null",
418
- nullSink
419
- ];
420
- try {
421
- await execFileAsync$1(ffmpeg, [
422
- ...baseArgs,
423
- ...inArgs,
424
- ...outArgs
425
- ], {
426
- timeout,
427
- encoding: "utf8"
428
- });
429
- return {
430
- encoder: candidate.encoder,
431
- codec: candidate.codec,
432
- family: candidate.family,
433
- available: true
434
- };
435
- } catch (err) {
436
- return {
437
- encoder: candidate.encoder,
438
- codec: candidate.codec,
439
- family: candidate.family,
440
- available: false,
441
- reason: (0, _camstack_types.errMsg)(err)
442
- };
443
- }
444
- }
445
- var HardwareEncoderProber = class {
446
- cached = null;
447
- inflight = null;
448
- logger;
449
- ffmpegPath;
450
- constructor(logger, ffmpegPath = "ffmpeg") {
451
- this.logger = logger;
452
- this.ffmpegPath = ffmpegPath;
453
- }
454
- getCached() {
455
- return this.cached;
456
- }
457
- async probe(hardware, options = {}) {
458
- if (!options.force && this.cached) return this.cached;
459
- if (this.inflight) return this.inflight;
460
- this.inflight = this.runProbe(hardware).then((res) => {
461
- this.cached = res;
462
- this.inflight = null;
463
- return res;
464
- }).catch((err) => {
465
- this.inflight = null;
466
- throw err;
467
- });
468
- return this.inflight;
469
- }
470
- async runProbe(hardware) {
471
- const candidates = buildCandidates(hardware);
472
- const start = Date.now();
473
- this.logger.info("Probing hardware encoders", { meta: {
474
- platform: hardware.platform,
475
- arch: hardware.arch,
476
- candidates: candidates.length
477
- } });
478
- const probes = await Promise.all(candidates.map((c) => runTestEncode(c, { ffmpegPath: this.ffmpegPath })));
479
- probes.push({
480
- encoder: "libx264",
481
- codec: "H264",
482
- family: "software",
483
- available: true
484
- });
485
- probes.push({
486
- encoder: "libx265",
487
- codec: "H265",
488
- family: "software",
489
- available: true
490
- });
491
- const pickDefault = (codec) => {
492
- const hw = probes.find((p) => p.codec === codec && p.available && p.family !== "software");
493
- if (hw) return hw.encoder;
494
- return codec === "H264" ? "libx264" : "libx265";
495
- };
496
- const result = {
497
- encoders: probes,
498
- defaultH264: pickDefault("H264"),
499
- defaultH265: pickDefault("H265"),
500
- probedAt: Date.now()
501
- };
502
- const elapsed = Date.now() - start;
503
- this.logger.info("Hardware encoder probe complete", { meta: {
504
- elapsedMs: elapsed,
505
- defaultH264: result.defaultH264,
506
- defaultH265: result.defaultH265,
507
- availableHw: probes.filter((p) => p.available && p.family !== "software").map((p) => p.encoder)
508
- } });
509
- return result;
510
- }
511
- };
512
- //#endregion
513
- //#region src/builtins/platform-probe/hardware-decode-accel-probe.ts
514
- var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
515
- var defaultExecFn = async (ffmpegPath, args) => {
516
- const { stdout } = await execFileAsync(ffmpegPath, [...args], {
517
- timeout: 8e3,
518
- encoding: "utf8"
519
- });
520
- return { stdout };
521
- };
338
+ //#region src/builtins/platform-probe/index.ts
522
339
  /**
523
- * Parse the output of `ffmpeg -hwaccels`. The listing looks like:
524
- *
525
- * Hardware acceleration methods:
526
- * videotoolbox
527
- *
528
- * The header line is dropped; remaining non-empty trimmed lines are the
529
- * method names, lowercased and de-duplicated (order preserved).
340
+ * The decode-hwaccel backends `ctx.kernel.hwaccel.resolve` accepts. The cap
341
+ * input enum is WIDER (it also carries EP-only names — coreml/openvino/… —
342
+ * shared with other probe surfaces), so the provider param must stay
343
+ * `string`-typed to satisfy the `InferProvider` contract. We narrow it here
344
+ * with a type guard instead of a cast: any value that is not a known decode
345
+ * backend (or the `'none'` sentinel) resolves to `null` auto-probe.
530
346
  */
531
- function parseHwAccelsOutput(stdout) {
532
- const seen = /* @__PURE__ */ new Set();
533
- const methods = [];
534
- for (const raw of stdout.split("\n")) {
535
- const line = raw.trim().toLowerCase();
536
- if (line.length === 0) continue;
537
- if (line.endsWith("methods:")) continue;
538
- if (seen.has(line)) continue;
539
- seen.add(line);
540
- methods.push(line);
541
- }
542
- return methods;
347
+ var HWACCEL_DECODE_BACKENDS = [
348
+ "videotoolbox",
349
+ "cuda",
350
+ "nvdec",
351
+ "vaapi",
352
+ "qsv",
353
+ "d3d11va",
354
+ "dxva2",
355
+ "amf",
356
+ "vdpau",
357
+ "drm"
358
+ ];
359
+ function isHwAccelBackend(value) {
360
+ return HWACCEL_DECODE_BACKENDS.includes(value);
361
+ }
362
+ function narrowHwAccelPrefer(value) {
363
+ if (value === "none") return "none";
364
+ if (typeof value === "string" && isHwAccelBackend(value)) return value;
365
+ return null;
543
366
  }
544
- /**
545
- * Probes which `-hwaccel` decode backends the configured ffmpeg binary
546
- * supports. Mirrors {@link HardwareEncoderProber} (configured binary, cached,
547
- * single-flight, forceable). Never throws: a probe failure yields an empty
548
- * method list so callers fall back to software decode.
549
- */
550
- var HardwareDecodeAccelProber = class {
551
- cached = null;
552
- inflight = null;
553
- logger;
554
- ffmpegPath;
555
- execFn;
556
- constructor(logger, ffmpegPath = "ffmpeg", execFn = defaultExecFn) {
557
- this.logger = logger;
558
- this.ffmpegPath = ffmpegPath;
559
- this.execFn = execFn;
560
- }
561
- getCached() {
562
- return this.cached;
563
- }
564
- async probe(options = {}) {
565
- if (!options.force && this.cached) return this.cached;
566
- if (this.inflight) return this.inflight;
567
- this.inflight = this.runProbe().then((res) => {
568
- this.cached = res;
569
- this.inflight = null;
570
- return res;
571
- }).catch((err) => {
572
- this.inflight = null;
573
- throw err;
574
- });
575
- return this.inflight;
576
- }
577
- async runProbe() {
578
- try {
579
- const { stdout } = await this.execFn(this.ffmpegPath, ["-hide_banner", "-hwaccels"]);
580
- const methods = parseHwAccelsOutput(stdout);
581
- this.logger.info("Hardware decode-accel probe complete", { meta: { methods } });
582
- return {
583
- methods,
584
- probedAt: Date.now()
585
- };
586
- } catch (err) {
587
- this.logger.warn("Hardware decode-accel probe failed — assuming none available", { meta: { error: (0, _camstack_types.errMsg)(err) } });
588
- return {
589
- methods: [],
590
- probedAt: Date.now()
591
- };
592
- }
593
- }
594
- };
595
- //#endregion
596
- //#region src/builtins/platform-probe/index.ts
597
367
  var PlatformProbeNativeAddon = class extends _camstack_types.BaseAddon {
598
368
  scorer = null;
599
- encoderProber = null;
600
- decodeAccelProber = null;
601
369
  cachedCaps = null;
370
+ /**
371
+ * Per-boot generation stamp for the manual readiness emissions below.
372
+ * Constant for the lifetime of this addon instance (== one process boot);
373
+ * consumer-side registries derive a monotonic epoch from generation
374
+ * transitions. Mirrors `BaseAddon._readinessGeneration` (private there).
375
+ */
376
+ readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
602
377
  constructor() {
603
378
  super({});
604
379
  }
605
380
  /**
606
- * Resolve the ffmpeg binary the encoder probe should test, from the cluster
607
- * `ffmpeg` config section (`binaryPath`). The probe MUST exercise the same
608
- * binary the broker/recorder spawn, or it may report encoders for a different
609
- * ffmpeg. Defaults to PATH `ffmpeg` on any miss.
381
+ * Manual readiness protocol. The provider registers synchronously from
382
+ * `onInitialize`, but the REAL hardware + EP scoring is the async
383
+ * `probePromise` (embedded-Python install included). The BaseAddon
384
+ * auto-emit would flip `ready` at registration time — BEFORE any
385
+ * accelerator is visible — so probe-gated consumers (detection-pipeline
386
+ * engine auto-pick) would read accelerator-blind results and stick on
387
+ * onnx-CPU. Instead: `starting` at init, the single authoritative
388
+ * `ready` once the probe resolves, `down` on shutdown.
610
389
  */
611
- async resolveFfmpegBinaryPath() {
390
+ get autoEmitReadiness() {
391
+ return false;
392
+ }
393
+ /** Bare cluster node id — readiness is scoped `{type:'node', nodeId}`. */
394
+ bareLocalNodeId() {
395
+ const raw = this.ctx.kernel?.localNodeId ?? "hub";
396
+ return raw.includes("/") ? raw.split("/")[0] : raw;
397
+ }
398
+ /** Emit a `system.ready-state` transition for the platform-probe cap. */
399
+ emitProbeReadiness(state) {
400
+ const ctx = this.ctxIfReady;
401
+ if (!ctx) return;
402
+ const nodeId = this.bareLocalNodeId();
612
403
  try {
613
- const bp = (await this.ctx.settings?.getSection("ffmpeg") ?? {})["binaryPath"];
614
- return typeof bp === "string" && bp.trim().length > 0 ? bp : "ffmpeg";
615
- } catch {
616
- return "ffmpeg";
617
- }
404
+ (0, _camstack_types.emitReadiness)(ctx.eventBus, {
405
+ capName: _camstack_types.platformProbeCapability.name,
406
+ scope: {
407
+ type: "node",
408
+ nodeId
409
+ },
410
+ state,
411
+ generation: this.readinessGeneration,
412
+ sourceNodeId: nodeId
413
+ });
414
+ } catch {}
618
415
  }
619
416
  async onInitialize() {
417
+ this.emitProbeReadiness("starting");
620
418
  const embeddedPython = await this.ctx.deps.ensurePython().catch((err) => {
621
419
  this.ctx.logger.debug("ensurePython unavailable for platform probe", { meta: { error: (0, _camstack_types.errMsg)(err) } });
622
420
  return null;
623
421
  });
624
422
  this.scorer = new PlatformScorer(this.ctx.logger, embeddedPython);
625
- const ffmpegPath = await this.resolveFfmpegBinaryPath();
626
- this.encoderProber = new HardwareEncoderProber(this.ctx.logger, ffmpegPath);
627
- this.decodeAccelProber = new HardwareDecodeAccelProber(this.ctx.logger, ffmpegPath);
628
423
  const emitPhase = (phase, payload) => {
629
424
  this.ctx.eventBus?.emit({
630
425
  id: `platform-probe-${phase}-${Date.now()}`,
@@ -651,6 +446,7 @@ var PlatformProbeNativeAddon = class extends _camstack_types.BaseAddon {
651
446
  bestReason: caps.bestScore.reason,
652
447
  bestScore: caps.bestScore.score
653
448
  } });
449
+ this.emitProbeReadiness("ready");
654
450
  return caps;
655
451
  }).catch((err) => {
656
452
  const msg = (0, _camstack_types.errMsg)(err);
@@ -676,46 +472,26 @@ var PlatformProbeNativeAddon = class extends _camstack_types.BaseAddon {
676
472
  },
677
473
  resolveHwAccel: async (input) => {
678
474
  const hwaccel = this.ctx.kernel.hwaccel;
679
- if (!hwaccel) return { preferred: [] };
680
- return { preferred: (await hwaccel.resolve(input.prefer ?? null)).preferred };
681
- },
682
- getHardwareEncoders: async () => {
683
- const prober = this.encoderProber;
684
- if (!prober) throw new Error("Hardware encoder prober not initialized");
685
- const cached = prober.getCached();
686
- if (cached) return cached;
687
- const caps = await getCaps();
688
- return prober.probe(caps.hardware);
689
- },
690
- refreshHardwareEncoders: async () => {
691
- const prober = this.encoderProber;
692
- if (!prober) throw new Error("Hardware encoder prober not initialized");
693
- const caps = await getCaps();
694
- return prober.probe(caps.hardware, { force: true });
695
- },
696
- getHardwareDecodeAccels: async () => {
697
- const prober = this.decodeAccelProber;
698
- if (!prober) throw new Error("Hardware decode-accel prober not initialized");
699
- return prober.probe();
700
- },
701
- refreshHardwareDecodeAccels: async () => {
702
- const prober = this.decodeAccelProber;
703
- if (!prober) throw new Error("Hardware decode-accel prober not initialized");
704
- return prober.probe({ force: true });
475
+ if (!hwaccel) return {
476
+ preferred: [],
477
+ rationale: "kernel hwaccel unavailable"
478
+ };
479
+ const res = await hwaccel.resolve(narrowHwAccelPrefer(input.prefer));
480
+ return {
481
+ preferred: res.preferred,
482
+ rationale: res.rationale
483
+ };
705
484
  }
706
485
  }
707
486
  }];
708
487
  }
709
488
  async onShutdown() {
489
+ this.emitProbeReadiness("down");
710
490
  this.scorer = null;
711
- this.encoderProber = null;
712
- this.decodeAccelProber = null;
713
491
  this.cachedCaps = null;
714
492
  }
715
493
  };
716
494
  //#endregion
717
- exports.HardwareDecodeAccelProber = HardwareDecodeAccelProber;
718
- exports.HardwareEncoderProber = HardwareEncoderProber;
719
495
  exports.InferenceConfigResolver = InferenceConfigResolver;
720
496
  exports.PlatformProbeNativeAddon = PlatformProbeNativeAddon;
721
497
  exports.default = PlatformProbeNativeAddon;