@camstack/system 1.2.471 → 1.2.473

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 (95) hide show
  1. package/dist/addon-runner.js +202 -8
  2. package/dist/addon-runner.mjs +203 -9
  3. package/dist/addon-utils.mjs +1 -1
  4. package/dist/{agent-https-bH6bKDeK.js → agent-https-B9Q3-IKd.js} +2 -5
  5. package/dist/{agent-https-DHBvFZJS.mjs → agent-https-DOwn4pqL.mjs} +3 -6
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +4 -4
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +5 -5
  10. package/dist/builtins/alerts/alerts.addon.js +1 -1
  11. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  12. package/dist/builtins/autotrack/index.js +1 -1
  13. package/dist/builtins/autotrack/index.mjs +1 -1
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  15. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +5 -5
  16. package/dist/builtins/calendars/calendars.addon.js +1 -1
  17. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  18. package/dist/builtins/camera-grid/index.js +1 -1
  19. package/dist/builtins/camera-grid/index.mjs +1 -1
  20. package/dist/builtins/composer/composer.addon.js +2 -2
  21. package/dist/builtins/composer/composer.addon.mjs +2 -2
  22. package/dist/builtins/console-logging/index.js +1 -1
  23. package/dist/builtins/console-logging/index.mjs +1 -1
  24. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  25. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  26. package/dist/builtins/device-history/device-history.addon.js +1 -1
  27. package/dist/builtins/device-history/device-history.addon.mjs +1 -1
  28. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  29. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  30. package/dist/builtins/hub-forwarder/index.js +1 -1
  31. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  32. package/dist/builtins/keychain/index.js +3 -3
  33. package/dist/builtins/keychain/index.mjs +3 -3
  34. package/dist/builtins/liveness-monitor/liveness-monitor.addon.d.ts +9 -25
  35. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +240 -39
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +237 -38
  37. package/dist/builtins/liveness-monitor/liveness-reporter.d.ts +28 -0
  38. package/dist/builtins/liveness-monitor/storage-root-access.d.ts +53 -0
  39. package/dist/builtins/local-auth/local-auth.addon.js +16 -2
  40. package/dist/builtins/local-auth/local-auth.addon.mjs +19 -5
  41. package/dist/builtins/local-auth/person-scopes.d.ts +7 -0
  42. package/dist/builtins/local-network/local-network.addon.js +2 -2
  43. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  44. package/dist/builtins/loki-logging/index.js +1 -1
  45. package/dist/builtins/loki-logging/index.mjs +1 -1
  46. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  47. package/dist/builtins/native-metrics/native-metrics.addon.mjs +10 -10
  48. package/dist/builtins/platform-probe/accelerator-facts-probe.d.ts +24 -0
  49. package/dist/builtins/platform-probe/index.js +110 -10
  50. package/dist/builtins/platform-probe/index.mjs +116 -17
  51. package/dist/builtins/platform-probe/platform-scorer.d.ts +10 -2
  52. package/dist/builtins/record-entries/index.js +1 -1
  53. package/dist/builtins/record-entries/index.mjs +1 -1
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  55. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  56. package/dist/builtins/snapshot/index.js +1 -1
  57. package/dist/builtins/snapshot/index.mjs +1 -1
  58. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  59. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +20 -20
  60. package/dist/builtins/sqlite-storage/index.js +1 -1
  61. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  62. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +3 -3
  63. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +3 -3
  64. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  65. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  66. package/dist/builtins/system-config/system-config.addon.js +3 -3
  67. package/dist/builtins/system-config/system-config.addon.mjs +115 -115
  68. package/dist/builtins/winston-logging/index.js +1 -1
  69. package/dist/builtins/winston-logging/index.mjs +1 -1
  70. package/dist/{composition-sources-B5uJZXZ1.js → composition-sources-Dj_nInUQ.js} +1 -1
  71. package/dist/{composition-sources-DRw_vBKH.mjs → composition-sources-cwD35HB0.mjs} +1 -1
  72. package/dist/{constants-CToE4OFp.js → constants-XkWSVvgY.js} +1 -1
  73. package/dist/{constants-5lA8Z9d8.mjs → constants-p5mPM1iL.mjs} +1 -1
  74. package/dist/{coral-accelerators-BW3OcrLD.mjs → coral-accelerators-DBlgWB8k.mjs} +6 -6
  75. package/dist/{custom-action-registry-BGYDjTqY.mjs → custom-action-registry-DZSCLBuT.mjs} +37 -37
  76. package/dist/{device-ca-nOCYVFS4.js → device-ca-BXzQ1pYj.js} +1 -1
  77. package/dist/{device-ca-URrs1lxK.mjs → device-ca-DyUtcpf4.mjs} +1 -1
  78. package/dist/{dist-BjcWEfRy.js → dist-Bmah6waF.js} +209 -266
  79. package/dist/{dist-5XUG7iYJ.mjs → dist-C9lbdBL5.mjs} +204 -267
  80. package/dist/file-host-worker.js +1 -1
  81. package/dist/file-host-worker.mjs +1 -1
  82. package/dist/{hub-mdns-ciao-zD3JojB_.mjs → hub-mdns-ciao-BemeCNGe.mjs} +2 -2
  83. package/dist/{hub-mdns-ciao-vEWhJ_SD.js → hub-mdns-ciao-CUr6ewsX.js} +2 -2
  84. package/dist/index.js +236 -16
  85. package/dist/index.mjs +455 -235
  86. package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +24 -4
  87. package/dist/kernel/hwaccel/vaapi-init-probe.d.ts +29 -0
  88. package/dist/kernel/moleculer/runner-process-group.d.ts +4 -0
  89. package/dist/{restore-archive-CN1pJTRn.mjs → restore-archive-C0aohmAK.mjs} +94 -94
  90. package/dist/{restore-archive-B64m2nAj.js → restore-archive-DbPmyjiM.js} +1 -1
  91. package/dist/{retired-settings-keys-C_Kjp-Bj.mjs → retired-settings-keys-CWp7ZSeA.mjs} +8 -0
  92. package/dist/{retired-settings-keys-CGUqX-Oz.js → retired-settings-keys-DorakcxV.js} +8 -0
  93. package/dist/{wal-maintenance-BNXVX-ot.js → wal-maintenance-BT8Ns0W3.js} +0 -0
  94. package/dist/{wal-maintenance-Bl2I-ZcR.mjs → wal-maintenance-BuB731DK.mjs} +0 -0
  95. package/package.json +6 -6
@@ -7447,7 +7447,7 @@ function toKebab(name) {
7447
7447
  return name.replace(/[A-Z]/g, (m, i) => (i > 0 ? "-" : "") + m.toLowerCase());
7448
7448
  }
7449
7449
  /**
7450
- * Parse an `op.path` (e.g. `"pipelineExecutor.getDefaultSteps"`) into
7450
+ * Parse an `op.path` (e.g. `"pipelineExecutor.getAvailableEngines"`) into
7451
7451
  * the capability name (kebab-case) and the method name.
7452
7452
  */
7453
7453
  function parsePath(path) {
@@ -8094,6 +8094,148 @@ var nodeCoralFs = {
8094
8094
  }
8095
8095
  };
8096
8096
  //#endregion
8097
+ //#region src/kernel/hwaccel/vaapi-init-probe.ts
8098
+ /**
8099
+ * Can libva initialise the VA driver on this node's render node? (D926)
8100
+ *
8101
+ * Measured 2026-10-23 (intel-1.2.509, hub + little-unraid): Intel's iHD 25.2.4
8102
+ * exports only `__vaDriverInit_1_22`, the container's Ubuntu libva 2.20 looks
8103
+ * for `_1_20`, and `vaInitialize` failed — while the resolver kept offering
8104
+ * `qsv, vaapi`. Every session tried, failed and fell back to software: 722
8105
+ * `hwaccel 'vaapi' failed` lines a day and nothing that named the cause.
8106
+ *
8107
+ * One `vainfo --display drm --device <node>` per process answers it. A failure
8108
+ * reads the driver's exported `__vaDriverInit_1_N` so the verdict names the gap
8109
+ * (`driver iHD needs VA-API >= 1.22, libva has 1.20`). No `vainfo` on the host
8110
+ * (a bare install) is `unknown` — the resolver then behaves as it always did.
8111
+ */
8112
+ var VAINFO_TIMEOUT_MS = 1e4;
8113
+ var INIT_SYMBOL = /__vaDriverInit_(\d+)_(\d+)/g;
8114
+ var VA_API_VERSION = /VA-API version:? (\d+)\.(\d+)/;
8115
+ var DRIVER_OPEN = /Trying to open (\S+_drv_video\.so)/g;
8116
+ function newestInitVersion(symbols) {
8117
+ let best = null;
8118
+ for (const symbol of symbols) for (const m of symbol.matchAll(INIT_SYMBOL)) {
8119
+ const v = {
8120
+ major: Number(m[1]),
8121
+ minor: Number(m[2])
8122
+ };
8123
+ if (!best || v.major > best.major || v.major === best.major && v.minor > best.minor) best = v;
8124
+ }
8125
+ return best;
8126
+ }
8127
+ function lastMatch(text, re) {
8128
+ let last = null;
8129
+ for (const m of text.matchAll(re)) last = m[1] ?? null;
8130
+ return last;
8131
+ }
8132
+ function lastLine(text) {
8133
+ const lines = text.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
8134
+ return lines[lines.length - 1] ?? "no output";
8135
+ }
8136
+ function driverName(driverPath) {
8137
+ if (!driverPath) return "unknown";
8138
+ return (0, node_path.basename)(driverPath).replace(/_drv_video\.so$/, "");
8139
+ }
8140
+ /** Classify one vainfo run plus the driver's exported init symbols. Pure. */
8141
+ function classifyVainfo(run, driverSymbols) {
8142
+ const va = VA_API_VERSION.exec(run.output);
8143
+ const vaApiVersion = va ? `${va[1]}.${va[2]}` : null;
8144
+ const driverPath = lastMatch(run.output, DRIVER_OPEN);
8145
+ const needs = newestInitVersion(driverSymbols);
8146
+ const driverNeedsVaApi = needs ? `${needs.major}.${needs.minor}` : null;
8147
+ const base = {
8148
+ vaApiVersion,
8149
+ driverPath,
8150
+ driverNeedsVaApi
8151
+ };
8152
+ if (run.spawnError !== null) return {
8153
+ ...base,
8154
+ state: "unknown",
8155
+ summary: `vaapi: unknown (vainfo not run: ${run.spawnError})`
8156
+ };
8157
+ if (run.exitCode === 0) return {
8158
+ ...base,
8159
+ state: "ok",
8160
+ summary: `vaapi: ok (VA-API ${vaApiVersion ?? "?"}, driver ${driverName(driverPath)})`
8161
+ };
8162
+ const reason = needs !== null && va !== null && (needs.major > Number(va[1]) || needs.major === Number(va[1]) && needs.minor > Number(va[2])) ? `driver ${driverName(driverPath)} needs VA-API >= ${driverNeedsVaApi}, libva has ${vaApiVersion}` : lastLine(run.output);
8163
+ return {
8164
+ ...base,
8165
+ state: "init-failed",
8166
+ summary: `vaapi: init-failed (${reason})`
8167
+ };
8168
+ }
8169
+ /** Run `vainfo` against one render node; never throws. */
8170
+ function runVainfo(renderNode) {
8171
+ return new Promise((resolve) => {
8172
+ (0, node_child_process.execFile)("vainfo", [
8173
+ "--display",
8174
+ "drm",
8175
+ "--device",
8176
+ renderNode
8177
+ ], {
8178
+ timeout: VAINFO_TIMEOUT_MS,
8179
+ encoding: "utf8"
8180
+ }, (err, stdout, stderr) => {
8181
+ const output = `${stdout}\n${stderr}`;
8182
+ if (err === null) {
8183
+ resolve({
8184
+ exitCode: 0,
8185
+ output,
8186
+ spawnError: null
8187
+ });
8188
+ return;
8189
+ }
8190
+ const code = err.code;
8191
+ if (typeof code === "number") {
8192
+ resolve({
8193
+ exitCode: code,
8194
+ output,
8195
+ spawnError: null
8196
+ });
8197
+ return;
8198
+ }
8199
+ resolve({
8200
+ exitCode: null,
8201
+ output,
8202
+ spawnError: code ?? (err.killed ? "timeout" : err.message)
8203
+ });
8204
+ });
8205
+ });
8206
+ }
8207
+ /**
8208
+ * The `__vaDriverInit_N_M` names in a driver file, streamed (iHD is ~43 MB),
8209
+ * with an overlap so a name split across two chunks is still found.
8210
+ */
8211
+ function readDriverInitSymbols(driverPath) {
8212
+ return new Promise((resolve) => {
8213
+ const found = /* @__PURE__ */ new Set();
8214
+ let carry = "";
8215
+ const stream = (0, node_fs.createReadStream)(driverPath, {
8216
+ encoding: "latin1",
8217
+ highWaterMark: 64 * 1024
8218
+ });
8219
+ stream.on("data", (chunk) => {
8220
+ const text = carry + String(chunk);
8221
+ for (const m of text.matchAll(INIT_SYMBOL)) if (m.index + m[0].length < text.length) found.add(m[0]);
8222
+ carry = text.slice(-32);
8223
+ });
8224
+ stream.on("error", () => resolve([]));
8225
+ stream.on("end", () => {
8226
+ for (const m of carry.matchAll(INIT_SYMBOL)) found.add(m[0]);
8227
+ resolve([...found].sort());
8228
+ });
8229
+ });
8230
+ }
8231
+ /** Probe one render node: vainfo, and on failure the driver's init symbols. */
8232
+ async function probeVaapiInit(renderNode) {
8233
+ const run = await runVainfo(renderNode);
8234
+ if (run.spawnError !== null || run.exitCode === 0) return classifyVainfo(run, []);
8235
+ const driverPath = lastMatch(run.output, DRIVER_OPEN);
8236
+ return classifyVainfo(run, driverPath ? await readDriverInitSymbols(driverPath) : []);
8237
+ }
8238
+ //#endregion
8097
8239
  //#region src/kernel/hwaccel/hwaccel-resolver.ts
8098
8240
  /**
8099
8241
  * Platform-probe for hardware video decode acceleration.
@@ -8163,7 +8305,23 @@ function probePlatform() {
8163
8305
  coral: classifyCoralAccelerator(nodeCoralFs)
8164
8306
  };
8165
8307
  }
8166
- function pickBackendsForPlatform(signals) {
8308
+ /** The backends that open their device through libva on Linux. */
8309
+ var LIBVA_BACKENDS = ["vaapi", "qsv"];
8310
+ /**
8311
+ * The preference list for these signals. A `vaapi` verdict of `init-failed`
8312
+ * (libva cannot initialise the driver — D926) removes every libva-backed
8313
+ * backend and names the cause in the rationale; `ok`, `unknown` or `null`
8314
+ * change nothing.
8315
+ */
8316
+ function pickBackendsForPlatform(signals, vaapi = null) {
8317
+ const res = pickBackendsFromSignals(signals);
8318
+ if (signals.platform !== "linux" || vaapi?.state !== "init-failed") return res;
8319
+ return {
8320
+ preferred: res.preferred.filter((b) => !LIBVA_BACKENDS.includes(b)),
8321
+ rationale: `${res.rationale} ${vaapi.summary} — vaapi/qsv withheld, decode runs in software.`
8322
+ };
8323
+ }
8324
+ function pickBackendsFromSignals(signals) {
8167
8325
  switch (signals.platform) {
8168
8326
  case "darwin": return {
8169
8327
  preferred: ["videotoolbox"],
@@ -8224,13 +8382,49 @@ function resolveHwAccel(prefer) {
8224
8382
  };
8225
8383
  return pickBackendsForPlatform(probePlatform());
8226
8384
  }
8385
+ var RENDER_NODE = "/dev/dri/renderD128";
8386
+ /** Process-wide: the VA init failure is logged once per process, not per resolver. */
8387
+ var PROCESS_VAAPI_REPORT = { reported: false };
8388
+ /** Process-wide: hub-main builds one resolver per in-process addon; vainfo runs once. */
8389
+ var PROCESS_VAAPI_PROBES = /* @__PURE__ */ new Map();
8390
+ function probeVaapiOncePerProcess(renderNode) {
8391
+ const known = PROCESS_VAAPI_PROBES.get(renderNode);
8392
+ if (known) return known;
8393
+ const probe = probeVaapiInit(renderNode);
8394
+ PROCESS_VAAPI_PROBES.set(renderNode, probe);
8395
+ return probe;
8396
+ }
8227
8397
  /**
8228
8398
  * Build the `IKernelHwAccel` provider used to populate `ctx.kernel.hwaccel`.
8229
- * Thin async wrapper around the sync `resolveHwAccel` function so the
8230
- * contract matches the per-node `$hwaccel.resolve` Moleculer action.
8231
- */
8232
- function createKernelHwAccel() {
8233
- return { resolve: async (prefer) => resolveHwAccel(prefer) };
8399
+ * An auto resolve on a Linux host with a render node asks libva ONCE per
8400
+ * provider whether it can initialise the driver (D926); an explicit override
8401
+ * is never second-guessed.
8402
+ */
8403
+ function createKernelHwAccel(options = {}) {
8404
+ const probeSignals = options.probeSignals ?? probePlatform;
8405
+ const probeVaapi = options.probeVaapi ?? probeVaapiOncePerProcess;
8406
+ const reportOnce = options.reportOnce ?? PROCESS_VAAPI_REPORT;
8407
+ let vaapi = null;
8408
+ const vaapiVerdict = async () => {
8409
+ if (vaapi === null) vaapi = probeVaapi(RENDER_NODE);
8410
+ const status = await vaapi;
8411
+ if (status.state === "init-failed" && !reportOnce.reported) {
8412
+ reportOnce.reported = true;
8413
+ options.logger?.error(`hwaccel: ${status.summary} — vaapi/qsv withheld, decode runs in software`, { meta: {
8414
+ renderNode: RENDER_NODE,
8415
+ vaApiVersion: status.vaApiVersion,
8416
+ driverPath: status.driverPath,
8417
+ driverNeedsVaApi: status.driverNeedsVaApi
8418
+ } });
8419
+ }
8420
+ return status;
8421
+ };
8422
+ return { resolve: async (prefer) => {
8423
+ if (prefer === "none" || prefer !== null && prefer !== void 0 && prefer.length > 0) return resolveHwAccel(prefer);
8424
+ const signals = probeSignals();
8425
+ if (signals.platform !== "linux" || !signals.hasRenderNode) return pickBackendsForPlatform(signals);
8426
+ return pickBackendsForPlatform(signals, await vaapiVerdict());
8427
+ } };
8234
8428
  }
8235
8429
  //#endregion
8236
8430
  //#region src/kernel/inference/inference-resolver.ts
@@ -10575,7 +10769,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
10575
10769
  value
10576
10770
  })
10577
10771
  };
10578
- const hwaccel = createKernelHwAccel();
10772
+ const hwaccel = createKernelHwAccel({ logger: scopedLogger });
10579
10773
  const inferenceEngine = createKernelInferenceEngine();
10580
10774
  return {
10581
10775
  localNodeId: nodeId,
@@ -2,7 +2,7 @@ import { register } from "node:module";
2
2
  import * as fs$1 from "node:fs";
3
3
  import fs, { createReadStream, createWriteStream, existsSync, readFileSync, readdirSync } from "node:fs";
4
4
  import * as path from "node:path";
5
- import { dirname, isAbsolute, join } from "node:path";
5
+ import { basename, dirname, isAbsolute, join } from "node:path";
6
6
  import { BOOT_RECOVERY_BACKOFF_MS, CALLER_STAMPED_CAPABILITIES, DATAPLANE_SECRET_HEADER, DEVICE_CHILDREN_BATCH_MAX, DeviceType, DisposerChain, ErrorI18nPayloadSchema, EventCategory, PEER_BYTES_CHUNK_BYTES, PEER_BYTES_MAX_OUTSTANDING, PEER_BYTES_STALL_MS, PEER_BYTES_TICKET_TTL_MS, ReadinessRegistry, ReadinessTimeoutError, addonSliceHandleApi, asJsonObject, asString, collectZodSecretPaths, createDeviceProxy, createEventBusSliceSource, createRecordsAccess, createSecretsAccess, describeCustomActions, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, isCollectionArrayMethodName, isProviderOptionalMethodName, maskUrlCredentials, normalizeAddonInitResult, readErrorI18n, readNodePin, resolveSecretViewsIn, scopeKey, scrubKnownSecrets, withCapInputDefaults } from "@camstack/types/addon";
7
7
  import { execFile } from "node:child_process";
8
8
  import { promisify } from "node:util";
@@ -7423,7 +7423,7 @@ function toKebab(name) {
7423
7423
  return name.replace(/[A-Z]/g, (m, i) => (i > 0 ? "-" : "") + m.toLowerCase());
7424
7424
  }
7425
7425
  /**
7426
- * Parse an `op.path` (e.g. `"pipelineExecutor.getDefaultSteps"`) into
7426
+ * Parse an `op.path` (e.g. `"pipelineExecutor.getAvailableEngines"`) into
7427
7427
  * the capability name (kebab-case) and the method name.
7428
7428
  */
7429
7429
  function parsePath(path) {
@@ -8070,6 +8070,148 @@ var nodeCoralFs = {
8070
8070
  }
8071
8071
  };
8072
8072
  //#endregion
8073
+ //#region src/kernel/hwaccel/vaapi-init-probe.ts
8074
+ /**
8075
+ * Can libva initialise the VA driver on this node's render node? (D926)
8076
+ *
8077
+ * Measured 2026-10-23 (intel-1.2.509, hub + little-unraid): Intel's iHD 25.2.4
8078
+ * exports only `__vaDriverInit_1_22`, the container's Ubuntu libva 2.20 looks
8079
+ * for `_1_20`, and `vaInitialize` failed — while the resolver kept offering
8080
+ * `qsv, vaapi`. Every session tried, failed and fell back to software: 722
8081
+ * `hwaccel 'vaapi' failed` lines a day and nothing that named the cause.
8082
+ *
8083
+ * One `vainfo --display drm --device <node>` per process answers it. A failure
8084
+ * reads the driver's exported `__vaDriverInit_1_N` so the verdict names the gap
8085
+ * (`driver iHD needs VA-API >= 1.22, libva has 1.20`). No `vainfo` on the host
8086
+ * (a bare install) is `unknown` — the resolver then behaves as it always did.
8087
+ */
8088
+ var VAINFO_TIMEOUT_MS = 1e4;
8089
+ var INIT_SYMBOL = /__vaDriverInit_(\d+)_(\d+)/g;
8090
+ var VA_API_VERSION = /VA-API version:? (\d+)\.(\d+)/;
8091
+ var DRIVER_OPEN = /Trying to open (\S+_drv_video\.so)/g;
8092
+ function newestInitVersion(symbols) {
8093
+ let best = null;
8094
+ for (const symbol of symbols) for (const m of symbol.matchAll(INIT_SYMBOL)) {
8095
+ const v = {
8096
+ major: Number(m[1]),
8097
+ minor: Number(m[2])
8098
+ };
8099
+ if (!best || v.major > best.major || v.major === best.major && v.minor > best.minor) best = v;
8100
+ }
8101
+ return best;
8102
+ }
8103
+ function lastMatch(text, re) {
8104
+ let last = null;
8105
+ for (const m of text.matchAll(re)) last = m[1] ?? null;
8106
+ return last;
8107
+ }
8108
+ function lastLine(text) {
8109
+ const lines = text.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
8110
+ return lines[lines.length - 1] ?? "no output";
8111
+ }
8112
+ function driverName(driverPath) {
8113
+ if (!driverPath) return "unknown";
8114
+ return basename(driverPath).replace(/_drv_video\.so$/, "");
8115
+ }
8116
+ /** Classify one vainfo run plus the driver's exported init symbols. Pure. */
8117
+ function classifyVainfo(run, driverSymbols) {
8118
+ const va = VA_API_VERSION.exec(run.output);
8119
+ const vaApiVersion = va ? `${va[1]}.${va[2]}` : null;
8120
+ const driverPath = lastMatch(run.output, DRIVER_OPEN);
8121
+ const needs = newestInitVersion(driverSymbols);
8122
+ const driverNeedsVaApi = needs ? `${needs.major}.${needs.minor}` : null;
8123
+ const base = {
8124
+ vaApiVersion,
8125
+ driverPath,
8126
+ driverNeedsVaApi
8127
+ };
8128
+ if (run.spawnError !== null) return {
8129
+ ...base,
8130
+ state: "unknown",
8131
+ summary: `vaapi: unknown (vainfo not run: ${run.spawnError})`
8132
+ };
8133
+ if (run.exitCode === 0) return {
8134
+ ...base,
8135
+ state: "ok",
8136
+ summary: `vaapi: ok (VA-API ${vaApiVersion ?? "?"}, driver ${driverName(driverPath)})`
8137
+ };
8138
+ const reason = needs !== null && va !== null && (needs.major > Number(va[1]) || needs.major === Number(va[1]) && needs.minor > Number(va[2])) ? `driver ${driverName(driverPath)} needs VA-API >= ${driverNeedsVaApi}, libva has ${vaApiVersion}` : lastLine(run.output);
8139
+ return {
8140
+ ...base,
8141
+ state: "init-failed",
8142
+ summary: `vaapi: init-failed (${reason})`
8143
+ };
8144
+ }
8145
+ /** Run `vainfo` against one render node; never throws. */
8146
+ function runVainfo(renderNode) {
8147
+ return new Promise((resolve) => {
8148
+ execFile("vainfo", [
8149
+ "--display",
8150
+ "drm",
8151
+ "--device",
8152
+ renderNode
8153
+ ], {
8154
+ timeout: VAINFO_TIMEOUT_MS,
8155
+ encoding: "utf8"
8156
+ }, (err, stdout, stderr) => {
8157
+ const output = `${stdout}\n${stderr}`;
8158
+ if (err === null) {
8159
+ resolve({
8160
+ exitCode: 0,
8161
+ output,
8162
+ spawnError: null
8163
+ });
8164
+ return;
8165
+ }
8166
+ const code = err.code;
8167
+ if (typeof code === "number") {
8168
+ resolve({
8169
+ exitCode: code,
8170
+ output,
8171
+ spawnError: null
8172
+ });
8173
+ return;
8174
+ }
8175
+ resolve({
8176
+ exitCode: null,
8177
+ output,
8178
+ spawnError: code ?? (err.killed ? "timeout" : err.message)
8179
+ });
8180
+ });
8181
+ });
8182
+ }
8183
+ /**
8184
+ * The `__vaDriverInit_N_M` names in a driver file, streamed (iHD is ~43 MB),
8185
+ * with an overlap so a name split across two chunks is still found.
8186
+ */
8187
+ function readDriverInitSymbols(driverPath) {
8188
+ return new Promise((resolve) => {
8189
+ const found = /* @__PURE__ */ new Set();
8190
+ let carry = "";
8191
+ const stream = createReadStream(driverPath, {
8192
+ encoding: "latin1",
8193
+ highWaterMark: 64 * 1024
8194
+ });
8195
+ stream.on("data", (chunk) => {
8196
+ const text = carry + String(chunk);
8197
+ for (const m of text.matchAll(INIT_SYMBOL)) if (m.index + m[0].length < text.length) found.add(m[0]);
8198
+ carry = text.slice(-32);
8199
+ });
8200
+ stream.on("error", () => resolve([]));
8201
+ stream.on("end", () => {
8202
+ for (const m of carry.matchAll(INIT_SYMBOL)) found.add(m[0]);
8203
+ resolve([...found].sort());
8204
+ });
8205
+ });
8206
+ }
8207
+ /** Probe one render node: vainfo, and on failure the driver's init symbols. */
8208
+ async function probeVaapiInit(renderNode) {
8209
+ const run = await runVainfo(renderNode);
8210
+ if (run.spawnError !== null || run.exitCode === 0) return classifyVainfo(run, []);
8211
+ const driverPath = lastMatch(run.output, DRIVER_OPEN);
8212
+ return classifyVainfo(run, driverPath ? await readDriverInitSymbols(driverPath) : []);
8213
+ }
8214
+ //#endregion
8073
8215
  //#region src/kernel/hwaccel/hwaccel-resolver.ts
8074
8216
  /**
8075
8217
  * Platform-probe for hardware video decode acceleration.
@@ -8139,7 +8281,23 @@ function probePlatform() {
8139
8281
  coral: classifyCoralAccelerator(nodeCoralFs)
8140
8282
  };
8141
8283
  }
8142
- function pickBackendsForPlatform(signals) {
8284
+ /** The backends that open their device through libva on Linux. */
8285
+ var LIBVA_BACKENDS = ["vaapi", "qsv"];
8286
+ /**
8287
+ * The preference list for these signals. A `vaapi` verdict of `init-failed`
8288
+ * (libva cannot initialise the driver — D926) removes every libva-backed
8289
+ * backend and names the cause in the rationale; `ok`, `unknown` or `null`
8290
+ * change nothing.
8291
+ */
8292
+ function pickBackendsForPlatform(signals, vaapi = null) {
8293
+ const res = pickBackendsFromSignals(signals);
8294
+ if (signals.platform !== "linux" || vaapi?.state !== "init-failed") return res;
8295
+ return {
8296
+ preferred: res.preferred.filter((b) => !LIBVA_BACKENDS.includes(b)),
8297
+ rationale: `${res.rationale} ${vaapi.summary} — vaapi/qsv withheld, decode runs in software.`
8298
+ };
8299
+ }
8300
+ function pickBackendsFromSignals(signals) {
8143
8301
  switch (signals.platform) {
8144
8302
  case "darwin": return {
8145
8303
  preferred: ["videotoolbox"],
@@ -8200,13 +8358,49 @@ function resolveHwAccel(prefer) {
8200
8358
  };
8201
8359
  return pickBackendsForPlatform(probePlatform());
8202
8360
  }
8361
+ var RENDER_NODE = "/dev/dri/renderD128";
8362
+ /** Process-wide: the VA init failure is logged once per process, not per resolver. */
8363
+ var PROCESS_VAAPI_REPORT = { reported: false };
8364
+ /** Process-wide: hub-main builds one resolver per in-process addon; vainfo runs once. */
8365
+ var PROCESS_VAAPI_PROBES = /* @__PURE__ */ new Map();
8366
+ function probeVaapiOncePerProcess(renderNode) {
8367
+ const known = PROCESS_VAAPI_PROBES.get(renderNode);
8368
+ if (known) return known;
8369
+ const probe = probeVaapiInit(renderNode);
8370
+ PROCESS_VAAPI_PROBES.set(renderNode, probe);
8371
+ return probe;
8372
+ }
8203
8373
  /**
8204
8374
  * Build the `IKernelHwAccel` provider used to populate `ctx.kernel.hwaccel`.
8205
- * Thin async wrapper around the sync `resolveHwAccel` function so the
8206
- * contract matches the per-node `$hwaccel.resolve` Moleculer action.
8207
- */
8208
- function createKernelHwAccel() {
8209
- return { resolve: async (prefer) => resolveHwAccel(prefer) };
8375
+ * An auto resolve on a Linux host with a render node asks libva ONCE per
8376
+ * provider whether it can initialise the driver (D926); an explicit override
8377
+ * is never second-guessed.
8378
+ */
8379
+ function createKernelHwAccel(options = {}) {
8380
+ const probeSignals = options.probeSignals ?? probePlatform;
8381
+ const probeVaapi = options.probeVaapi ?? probeVaapiOncePerProcess;
8382
+ const reportOnce = options.reportOnce ?? PROCESS_VAAPI_REPORT;
8383
+ let vaapi = null;
8384
+ const vaapiVerdict = async () => {
8385
+ if (vaapi === null) vaapi = probeVaapi(RENDER_NODE);
8386
+ const status = await vaapi;
8387
+ if (status.state === "init-failed" && !reportOnce.reported) {
8388
+ reportOnce.reported = true;
8389
+ options.logger?.error(`hwaccel: ${status.summary} — vaapi/qsv withheld, decode runs in software`, { meta: {
8390
+ renderNode: RENDER_NODE,
8391
+ vaApiVersion: status.vaApiVersion,
8392
+ driverPath: status.driverPath,
8393
+ driverNeedsVaApi: status.driverNeedsVaApi
8394
+ } });
8395
+ }
8396
+ return status;
8397
+ };
8398
+ return { resolve: async (prefer) => {
8399
+ if (prefer === "none" || prefer !== null && prefer !== void 0 && prefer.length > 0) return resolveHwAccel(prefer);
8400
+ const signals = probeSignals();
8401
+ if (signals.platform !== "linux" || !signals.hasRenderNode) return pickBackendsForPlatform(signals);
8402
+ return pickBackendsForPlatform(signals, await vaapiVerdict());
8403
+ } };
8210
8404
  }
8211
8405
  //#endregion
8212
8406
  //#region src/kernel/inference/inference-resolver.ts
@@ -10551,7 +10745,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
10551
10745
  value
10552
10746
  })
10553
10747
  };
10554
- const hwaccel = createKernelHwAccel();
10748
+ const hwaccel = createKernelHwAccel({ logger: scopedLogger });
10555
10749
  const inferenceEngine = createKernelInferenceEngine();
10556
10750
  return {
10557
10751
  localNodeId: nodeId,
@@ -1,3 +1,3 @@
1
- import { C as isModelDownloaded, S as isModelDownloadHttpError, _ as downloadFile, a as startEventLoopStallMonitor, f as ModelDownloadService, g as deleteModelFromDisk, h as collectModelFiles, i as attributeStall, l as parseRangeHeader, m as adoptModelRevisions, n as STALL_REPORT_MS, o as createFileDataPlaneHandler, p as ModelDownloadHttpError, r as STALL_TICK_MS, s as contentTypeFor, t as CustomActionRegistry, w as resolveFormatRevision, x as getModelFilePath, y as ensureModel } from "./custom-action-registry-BGYDjTqY.mjs";
1
+ import { C as isModelDownloaded, S as isModelDownloadHttpError, _ as downloadFile, a as startEventLoopStallMonitor, f as ModelDownloadService, g as deleteModelFromDisk, h as collectModelFiles, i as attributeStall, l as parseRangeHeader, m as adoptModelRevisions, n as STALL_REPORT_MS, o as createFileDataPlaneHandler, p as ModelDownloadHttpError, r as STALL_TICK_MS, s as contentTypeFor, t as CustomActionRegistry, w as resolveFormatRevision, x as getModelFilePath, y as ensureModel } from "./custom-action-registry-DZSCLBuT.mjs";
2
2
  import { i as renderCourtesyOverlayJpeg, n as courtesyCacheKey, r as renderCourtesyJpeg, t as COURTESY_DEFAULT_WIDTH } from "./snapshot-courtesy-CyFs1M3I.mjs";
3
3
  export { COURTESY_DEFAULT_WIDTH, CustomActionRegistry, ModelDownloadHttpError, ModelDownloadService, STALL_REPORT_MS, STALL_TICK_MS, adoptModelRevisions, attributeStall, collectModelFiles, contentTypeFor, courtesyCacheKey, createFileDataPlaneHandler, deleteModelFromDisk, downloadFile, ensureModel, getModelFilePath, isModelDownloadHttpError, isModelDownloaded, parseRangeHeader, renderCourtesyJpeg, renderCourtesyOverlayJpeg, resolveFormatRevision, startEventLoopStallMonitor };
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_dist = require("./dist-BjcWEfRy.js");
2
+ const require_dist = require("./dist-Bmah6waF.js");
3
3
  const require_x509_es = require("./x509.es-C4cOo48M.js");
4
4
  let zod = require("zod");
5
5
  let node_crypto = require("node:crypto");
@@ -917,14 +917,11 @@ function installAgentHttps(dataDir, input) {
917
917
  if (!leaf.verify(ca.publicKey)) throw new AgentHttpsInstallError("the certificate is not signed by the CA that came with it");
918
918
  if (new Date(leaf.validTo).getTime() <= Date.now()) throw new AgentHttpsInstallError("the certificate is already expired");
919
919
  (0, node_fs.mkdirSync)(paths.dir, { recursive: true });
920
- const tmp = `${paths.bundlePath}.tmp`;
921
- (0, _camstack_types_node.writeOwnerOnlyFileSync)(tmp, JSON.stringify({
920
+ (0, _camstack_types_node.writeOwnerOnlyFileSync)(paths.bundlePath, JSON.stringify({
922
921
  keyPem,
923
922
  certPem: input.certPem,
924
923
  caCertPem: input.caCertPem
925
924
  }));
926
- (0, node_fs.renameSync)(tmp, paths.bundlePath);
927
- (0, node_fs.chmodSync)(paths.bundlePath, 384);
928
925
  writeFileAtomic(paths.certPath, input.certPem.endsWith("\n") ? input.certPem : `${input.certPem}\n`);
929
926
  return {
930
927
  keyPem,
@@ -1,4 +1,4 @@
1
- import { Ki as defineRecordKinds } from "./dist-5XUG7iYJ.mjs";
1
+ import { qi as defineRecordKinds } from "./dist-C9lbdBL5.mjs";
2
2
  import { S as backdatedNotBefore, _ as NameConstraints, a as KeyUsageFlags, c as Pkcs10CertificateRequest, d as SubjectKeyIdentifierExtension, f as X509Certificate$1, g as GeneralSubtrees, h as GeneralSubtree, i as Extension, l as Pkcs10CertificateRequestGenerator, m as cryptoProvider, n as BasicConstraintsExtension, o as KeyUsagesExtension, p as X509CertificateGenerator, r as ExtendedKeyUsageExtension, s as PemConverter, t as AuthorityKeyIdentifierExtension, u as SubjectAlternativeNameExtension, v as GeneralName, x as AsnConvert } from "./x509.es-DK__ETIe.mjs";
3
3
  import { z } from "zod";
4
4
  import { X509Certificate, createHash, createPrivateKey, randomBytes, webcrypto } from "node:crypto";
@@ -6,7 +6,7 @@ import { writeOwnerOnlyFileSync } from "@camstack/types/node";
6
6
  import { isIP } from "node:net";
7
7
  import { join } from "node:path";
8
8
  import * as os$1 from "node:os";
9
- import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
9
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
10
  var MeshNodeCertificateBodySchema = z.object({
11
11
  nodeId: z.string(),
12
12
  serial: z.string(),
@@ -915,14 +915,11 @@ function installAgentHttps(dataDir, input) {
915
915
  if (!leaf.verify(ca.publicKey)) throw new AgentHttpsInstallError("the certificate is not signed by the CA that came with it");
916
916
  if (new Date(leaf.validTo).getTime() <= Date.now()) throw new AgentHttpsInstallError("the certificate is already expired");
917
917
  mkdirSync(paths.dir, { recursive: true });
918
- const tmp = `${paths.bundlePath}.tmp`;
919
- writeOwnerOnlyFileSync(tmp, JSON.stringify({
918
+ writeOwnerOnlyFileSync(paths.bundlePath, JSON.stringify({
920
919
  keyPem,
921
920
  certPem: input.certPem,
922
921
  caCertPem: input.caCertPem
923
922
  }));
924
- renameSync(tmp, paths.bundlePath);
925
- chmodSync(paths.bundlePath, 384);
926
923
  writeFileAtomic(paths.certPath, input.certPem.endsWith("\n") ? input.certPem : `${input.certPem}\n`);
927
924
  return {
928
925
  keyPem,
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BjcWEfRy.js");
6
+ const require_dist = require("../../dist-Bmah6waF.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,8 +1,8 @@
1
- import { $r as errMsg, Da as EventCategory, _t as addonPagesCapability, ei as BaseAddon } from "../../dist-5XUG7iYJ.mjs";
1
+ import { Oa as EventCategory, _t as addonPagesCapability, ei as errMsg, ti as BaseAddon } from "../../dist-C9lbdBL5.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
5
- import * as fs from "node:fs";
5
+ import * as fs$1 from "node:fs";
6
6
  //#region src/builtins/addon-pages-aggregator/dedupe-pages.ts
7
7
  /** Stable identity for an addon page, independent of which node reported it. */
8
8
  function pageKey(info) {
@@ -211,11 +211,11 @@ var AddonPagesAggregatorAddon = class extends BaseAddon {
211
211
  const bundlePath = this.resolveBundlePath(addonId, bundle);
212
212
  if (bundlePath === null) return `/api/addon-pages/${addonId}/${bundle}?v=${Math.floor(Date.now())}`;
213
213
  try {
214
- const stat = fs.statSync(bundlePath);
214
+ const stat = fs$1.statSync(bundlePath);
215
215
  const cacheKey = `${bundlePath}:${stat.size}:${stat.mtimeMs}`;
216
216
  let v = this.bundleHashCache.get(cacheKey);
217
217
  if (v === void 0) {
218
- v = createHash("sha1").update(fs.readFileSync(bundlePath)).digest("hex").slice(0, 12);
218
+ v = createHash("sha1").update(fs$1.readFileSync(bundlePath)).digest("hex").slice(0, 12);
219
219
  this.bundleHashCache.set(cacheKey, v);
220
220
  }
221
221
  return `/api/addon-pages/${addonId}/${bundle}?v=${v}`;
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BjcWEfRy.js");
6
+ const require_dist = require("../../dist-Bmah6waF.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,8 +1,8 @@
1
- import { $r as errMsg, Da as EventCategory, ei as BaseAddon, yt as addonWidgetsCapability } from "../../dist-5XUG7iYJ.mjs";
1
+ import { Oa as EventCategory, ei as errMsg, ti as BaseAddon, yt as addonWidgetsCapability } from "../../dist-C9lbdBL5.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
5
- import * as fs from "node:fs";
5
+ import * as fs$1 from "node:fs";
6
6
  //#region src/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.ts
7
7
  /**
8
8
  * Addon Widgets Aggregator — hub-local builtin that owns the singleton
@@ -204,9 +204,9 @@ var AddonWidgetsAggregatorAddon = class extends BaseAddon {
204
204
  if (entryPath === null) return String(Math.floor(Date.now()));
205
205
  try {
206
206
  const dir = path$1.dirname(entryPath);
207
- const files = fs.readdirSync(dir).filter((f) => fs.statSync(path$1.join(dir, f)).isFile()).sort();
207
+ const files = fs$1.readdirSync(dir).filter((f) => fs$1.statSync(path$1.join(dir, f)).isFile()).sort();
208
208
  const cacheKey = `${dir}#${files.map((f) => {
209
- const s = fs.statSync(path$1.join(dir, f));
209
+ const s = fs$1.statSync(path$1.join(dir, f));
210
210
  return `${f}:${s.size}:${s.mtimeMs}`;
211
211
  }).join("|")}`;
212
212
  const cached = this.bundleHashCache.get(cacheKey);
@@ -214,7 +214,7 @@ var AddonWidgetsAggregatorAddon = class extends BaseAddon {
214
214
  const hash = createHash("sha1");
215
215
  for (const f of files) {
216
216
  hash.update(f);
217
- hash.update(fs.readFileSync(path$1.join(dir, f)));
217
+ hash.update(fs$1.readFileSync(path$1.join(dir, f)));
218
218
  }
219
219
  const digest = hash.digest("hex").slice(0, 12);
220
220
  this.bundleHashCache.set(cacheKey, digest);
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BjcWEfRy.js");
6
+ const require_dist = require("../../dist-Bmah6waF.js");
7
7
  const require_read_all_rows$1 = require("../../read-all-rows-CVoO1ONM.js");
8
8
  //#region src/builtins/alerts/alert-episode-policy.ts
9
9
  /**
@@ -1,4 +1,4 @@
1
- import { $r as errMsg, Da as EventCategory, Ki as defineRecordKinds, St as alertsCapability, Ui as createEvent, bn as isAlertEpisodeSeverity, bt as alertEpisodeId, ei as BaseAddon, i as AlertSchema, xt as alertEpisodePayload, yn as isAlertEpisodeOpen } from "../../dist-5XUG7iYJ.mjs";
1
+ import { Oa as EventCategory, St as alertsCapability, Wi as createEvent, bn as isAlertEpisodeSeverity, bt as alertEpisodeId, ei as errMsg, i as AlertSchema, qi as defineRecordKinds, ti as BaseAddon, xt as alertEpisodePayload, yn as isAlertEpisodeOpen } from "../../dist-C9lbdBL5.mjs";
2
2
  import { t as require_read_all_rows } from "../../read-all-rows-TaPqdWNF.mjs";
3
3
  //#region src/builtins/alerts/alert-episode-policy.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BjcWEfRy.js");
6
+ const require_dist = require("../../dist-Bmah6waF.js");
7
7
  //#region src/builtins/autotrack/autotrack-config.ts
8
8
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
9
9
  deadZoneFraction: .5,