omnius 1.0.186 → 1.0.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.
package/dist/index.js CHANGED
@@ -25161,11 +25161,154 @@ ${content}`
25161
25161
  }
25162
25162
  });
25163
25163
 
25164
+ // packages/execution/dist/tools/cuda-device-filter.js
25165
+ import { execFileSync as execFileSync3 } from "node:child_process";
25166
+ function cleanEnvValue(value2) {
25167
+ const trimmed = value2?.trim();
25168
+ return trimmed ? trimmed : void 0;
25169
+ }
25170
+ function envFlagEnabled(value2) {
25171
+ return /^(1|true|yes|on)$/i.test(value2?.trim() ?? "");
25172
+ }
25173
+ function parseCudaComputeCapability(value2) {
25174
+ const match = value2?.match(/(\d+(?:\.\d+)?)/);
25175
+ if (!match)
25176
+ return null;
25177
+ const parsed = Number(match[1]);
25178
+ return Number.isFinite(parsed) ? parsed : null;
25179
+ }
25180
+ function modalityEnvPrefix(modality) {
25181
+ return `OMNIUS_${modality.toUpperCase()}`;
25182
+ }
25183
+ function parseMinCudaComputeCapability(modality, env2) {
25184
+ const prefix = modalityEnvPrefix(modality);
25185
+ const parsed = parseCudaComputeCapability(env2[`${prefix}_MIN_CUDA_CC`]) ?? parseCudaComputeCapability(env2["OMNIUS_MEDIA_MIN_CUDA_CC"]);
25186
+ return parsed && parsed > 0 ? parsed : DEFAULT_MEDIA_MIN_CUDA_COMPUTE_CAPABILITY;
25187
+ }
25188
+ function splitCudaVisibleDevices(value2) {
25189
+ return (value2 ?? "").split(",").map((part) => part.trim()).filter(Boolean);
25190
+ }
25191
+ function parseCudaDeviceInfo(text) {
25192
+ const devices = [];
25193
+ for (const line of text.split(/\r?\n/)) {
25194
+ const trimmed = line.trim();
25195
+ if (!trimmed)
25196
+ continue;
25197
+ const parts = trimmed.split(",").map((part) => part.trim());
25198
+ if (parts.length < 4)
25199
+ continue;
25200
+ const index = Number.parseInt(parts.shift() ?? "", 10);
25201
+ const capability = parseCudaComputeCapability(parts.pop());
25202
+ const uuid = cleanEnvValue(parts.shift());
25203
+ const name10 = cleanEnvValue(parts.join(", "));
25204
+ if (!Number.isFinite(index) || index < 0)
25205
+ continue;
25206
+ devices.push({ index, uuid, name: name10, computeCapability: capability });
25207
+ }
25208
+ return devices;
25209
+ }
25210
+ function detectCudaDevices() {
25211
+ try {
25212
+ const out = execFileSync3("nvidia-smi", ["--query-gpu=index,uuid,name,compute_cap", "--format=csv,noheader,nounits"], {
25213
+ encoding: "utf8",
25214
+ timeout: 5e3,
25215
+ stdio: ["ignore", "pipe", "ignore"]
25216
+ });
25217
+ return parseCudaDeviceInfo(out);
25218
+ } catch {
25219
+ return [];
25220
+ }
25221
+ }
25222
+ function resolveMediaCudaVisibleDevicesForEnv(args) {
25223
+ const env2 = args.env ?? process.env;
25224
+ const prefix = modalityEnvPrefix(args.modality);
25225
+ const explicit = cleanEnvValue(env2[`${prefix}_CUDA_VISIBLE_DEVICES`]) ?? cleanEnvValue(env2["OMNIUS_MEDIA_CUDA_VISIBLE_DEVICES"]) ?? (args.modality === "audio" ? cleanEnvValue(env2["OMNIUS_AUDIO_GPU"]) : void 0);
25226
+ if (explicit)
25227
+ return explicit;
25228
+ const current = cleanEnvValue(env2["CUDA_VISIBLE_DEVICES"]);
25229
+ if (envFlagEnabled(env2[`${prefix}_DISABLE_CUDA_FILTER`]) || envFlagEnabled(env2["OMNIUS_MEDIA_DISABLE_CUDA_FILTER"])) {
25230
+ return current;
25231
+ }
25232
+ const devices = args.devices ?? detectCudaDevices();
25233
+ const minComputeCapability = args.minComputeCapability ?? parseMinCudaComputeCapability(args.modality, env2);
25234
+ const compatible = devices.filter((device) => device.computeCapability !== null && device.computeCapability >= minComputeCapability);
25235
+ if (compatible.length === 0)
25236
+ return current;
25237
+ const compatibleTokens = /* @__PURE__ */ new Set();
25238
+ for (const device of compatible) {
25239
+ compatibleTokens.add(String(device.index));
25240
+ if (device.uuid)
25241
+ compatibleTokens.add(device.uuid);
25242
+ }
25243
+ if (current) {
25244
+ const requested = splitCudaVisibleDevices(current);
25245
+ const canFilter = requested.length > 0 && requested.every((token) => /^\d+$/.test(token) || token.startsWith("GPU-"));
25246
+ if (!canFilter)
25247
+ return current;
25248
+ const kept = requested.filter((token) => compatibleTokens.has(token));
25249
+ return (kept.length > 0 ? kept : compatible.map((device) => String(device.index))).join(",");
25250
+ }
25251
+ return compatible.map((device) => String(device.index)).join(",");
25252
+ }
25253
+ function mediaBrokerGpuIndexIsCompatible(gpuIndex, modality, env2 = process.env, devices = detectCudaDevices()) {
25254
+ const prefix = modalityEnvPrefix(modality);
25255
+ if (envFlagEnabled(env2[`${prefix}_DISABLE_CUDA_FILTER`]) || envFlagEnabled(env2["OMNIUS_MEDIA_DISABLE_CUDA_FILTER"])) {
25256
+ return true;
25257
+ }
25258
+ if (devices.length === 0)
25259
+ return true;
25260
+ const minComputeCapability = parseMinCudaComputeCapability(modality, env2);
25261
+ const device = devices.find((candidate) => candidate.index === gpuIndex);
25262
+ if (!device || device.computeCapability === null)
25263
+ return true;
25264
+ return device.computeCapability >= minComputeCapability;
25265
+ }
25266
+ function applyMediaCudaDeviceFilterToEnv(env2, modality) {
25267
+ const cudaVisibleDevices = resolveMediaCudaVisibleDevicesForEnv({ modality, env: env2 });
25268
+ if (cudaVisibleDevices) {
25269
+ env2["CUDA_VISIBLE_DEVICES"] = cudaVisibleDevices;
25270
+ env2["PYTORCH_NVML_BASED_CUDA_CHECK"] = process.env["PYTORCH_NVML_BASED_CUDA_CHECK"] ?? "1";
25271
+ }
25272
+ return env2;
25273
+ }
25274
+ var DEFAULT_MEDIA_MIN_CUDA_COMPUTE_CAPABILITY;
25275
+ var init_cuda_device_filter = __esm({
25276
+ "packages/execution/dist/tools/cuda-device-filter.js"() {
25277
+ "use strict";
25278
+ DEFAULT_MEDIA_MIN_CUDA_COMPUTE_CAPABILITY = 7.5;
25279
+ }
25280
+ });
25281
+
25164
25282
  // packages/execution/dist/tools/transcribe-tool.js
25165
25283
  import { existsSync as existsSync29, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, readFileSync as readFileSync22, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "node:fs";
25166
25284
  import { join as join32, basename as basename6, extname as extname3, resolve as resolve17 } from "node:path";
25167
25285
  import { homedir as homedir10 } from "node:os";
25168
- import { execFileSync as execFileSync3, execSync as execSync15 } from "node:child_process";
25286
+ import { execFileSync as execFileSync4, execSync as execSync15 } from "node:child_process";
25287
+ function transcriptionPythonEnv(extra = {}) {
25288
+ const env2 = { ...process.env, ...extra };
25289
+ applyMediaCudaDeviceFilterToEnv(env2, "asr");
25290
+ return env2;
25291
+ }
25292
+ async function withProcessEnv(env2, fn) {
25293
+ const previous = /* @__PURE__ */ new Map();
25294
+ for (const [key, value2] of Object.entries(env2)) {
25295
+ previous.set(key, process.env[key]);
25296
+ if (value2 === void 0)
25297
+ delete process.env[key];
25298
+ else
25299
+ process.env[key] = value2;
25300
+ }
25301
+ try {
25302
+ return await fn();
25303
+ } finally {
25304
+ for (const [key, value2] of previous) {
25305
+ if (value2 === void 0)
25306
+ delete process.env[key];
25307
+ else
25308
+ process.env[key] = value2;
25309
+ }
25310
+ }
25311
+ }
25169
25312
  function whisperRamEstimate(model) {
25170
25313
  const m2 = model.toLowerCase();
25171
25314
  if (m2.includes("large"))
@@ -25251,6 +25394,7 @@ var init_transcribe_tool = __esm({
25251
25394
  "use strict";
25252
25395
  init_model_broker();
25253
25396
  init_network_egress_policy();
25397
+ init_cuda_device_filter();
25254
25398
  AUDIO_EXTS = /* @__PURE__ */ new Set([
25255
25399
  ".mp3",
25256
25400
  ".wav",
@@ -25353,13 +25497,13 @@ var init_transcribe_tool = __esm({
25353
25497
  return this.execViaCli(filePath, model, diarize, start2);
25354
25498
  }
25355
25499
  try {
25356
- const result = await tc.transcribe(filePath, {
25500
+ const result = await withProcessEnv(transcriptionPythonEnv(), () => tc.transcribe(filePath, {
25357
25501
  model,
25358
25502
  format: "json",
25359
25503
  diarize,
25360
25504
  wordTimestamps: true
25361
25505
  // Always get timestamps for structured output
25362
- });
25506
+ }));
25363
25507
  const transcriptDir = join32(this.workingDir, ".omnius", "transcripts");
25364
25508
  mkdirSync13(transcriptDir, { recursive: true });
25365
25509
  const fileBase = basename6(filePath).replace(/\.[^.]+$/, "");
@@ -25453,7 +25597,8 @@ var init_transcribe_tool = __esm({
25453
25597
  timeout: 3e5,
25454
25598
  // 5 min max
25455
25599
  cwd: this.workingDir,
25456
- stdio: ["pipe", "pipe", "pipe"]
25600
+ stdio: ["pipe", "pipe", "pipe"],
25601
+ env: transcriptionPythonEnv()
25457
25602
  });
25458
25603
  return {
25459
25604
  success: true,
@@ -25522,7 +25667,7 @@ var init_transcribe_tool = __esm({
25522
25667
  }
25523
25668
  tmpFile = `${tmpBase}.mp3`;
25524
25669
  try {
25525
- execFileSync3("yt-dlp", ["-x", "--audio-format", "mp3", "--audio-quality", "5", "-o", `${tmpBase}.%(ext)s`, url], { timeout: 3e5, stdio: ["pipe", "pipe", "pipe"] });
25670
+ execFileSync4("yt-dlp", ["-x", "--audio-format", "mp3", "--audio-quality", "5", "-o", `${tmpBase}.%(ext)s`, url], { timeout: 3e5, stdio: ["pipe", "pipe", "pipe"] });
25526
25671
  if (!existsSync29(tmpFile)) {
25527
25672
  const files = readdirSync13(tmpDir).filter((f2) => f2.startsWith(`download-`) && f2 !== ".gitkeep");
25528
25673
  const match = files.find((f2) => f2.includes(basename6(tmpBase)));
@@ -25647,13 +25792,13 @@ ${result.output}`,
25647
25792
  try {
25648
25793
  let title = "download";
25649
25794
  try {
25650
- title = execFileSync3("yt-dlp", ["--get-title", url], { timeout: 15e3, stdio: "pipe" }).toString().trim().replace(/[<>:"/\\|?*]/g, "_").slice(0, 100);
25795
+ title = execFileSync4("yt-dlp", ["--get-title", url], { timeout: 15e3, stdio: "pipe" }).toString().trim().replace(/[<>:"/\\|?*]/g, "_").slice(0, 100);
25651
25796
  } catch {
25652
25797
  }
25653
25798
  if (format3 === "mp4") {
25654
25799
  const outPath = join32(outputDir, `${title}.mp4`);
25655
25800
  const outTemplate = join32(outputDir, `${title}.%(ext)s`);
25656
- execFileSync3("yt-dlp", [
25801
+ execFileSync4("yt-dlp", [
25657
25802
  "-f",
25658
25803
  "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
25659
25804
  "--merge-output-format",
@@ -25673,7 +25818,7 @@ Format: mp4`,
25673
25818
  } else {
25674
25819
  const outPath = join32(outputDir, `${title}.mp3`);
25675
25820
  const outTemplate = join32(outputDir, `${title}.%(ext)s`);
25676
- execFileSync3("yt-dlp", ["-x", "--audio-format", "mp3", "--audio-quality", "0", "-o", outTemplate, url], { timeout: 6e5, stdio: "pipe", cwd: outputDir });
25821
+ execFileSync4("yt-dlp", ["-x", "--audio-format", "mp3", "--audio-quality", "0", "-o", outTemplate, url], { timeout: 6e5, stdio: "pipe", cwd: outputDir });
25677
25822
  const actualPath = existsSync29(outPath) ? outPath : outTemplate.replace("%(ext)s", "mp3");
25678
25823
  return {
25679
25824
  success: true,
@@ -259246,7 +259391,9 @@ function formatDiffusersFailure(stderrOrStdout) {
259246
259391
  Note: ${note}`)].filter(Boolean).join("");
259247
259392
  }
259248
259393
  function imageGenerationPythonEnv(_repoRoot) {
259249
- return unifiedPythonEnv();
259394
+ const env2 = unifiedPythonEnv();
259395
+ applyMediaCudaDeviceFilterToEnv(env2, "image");
259396
+ return env2;
259250
259397
  }
259251
259398
  function approxImageDownloadBytes(preset) {
259252
259399
  if (preset?.approxDownloadGB)
@@ -259389,6 +259536,7 @@ var init_image_generate = __esm({
259389
259536
  init_venv_paths();
259390
259537
  init_model_store();
259391
259538
  init_hf_media_models();
259539
+ init_cuda_device_filter();
259392
259540
  DEFAULT_DIFFUSERS_IMAGE_MODEL = "Efficient-Large-Model/SANA1.5_1.6B_1024px_diffusers";
259393
259541
  DEFAULT_OLLAMA_IMAGE_MODEL = "x/flux2-klein";
259394
259542
  LEGACY_SDXL_TURBO_MODEL = "stabilityai/sdxl-turbo";
@@ -260746,7 +260894,14 @@ ${errText.slice(0, 800)}`,
260746
260894
  this.emitProgress({ stage: "load", message: `Starting image generation with ${args.model}` });
260747
260895
  const runnerEnv = { ...python.env };
260748
260896
  if (this._brokerGpuIndex !== null) {
260749
- runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
260897
+ if (mediaBrokerGpuIndexIsCompatible(this._brokerGpuIndex, "image", runnerEnv)) {
260898
+ runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
260899
+ } else {
260900
+ this.emitProgress({
260901
+ stage: "setup",
260902
+ message: `Broker selected CUDA GPU ${this._brokerGpuIndex}, but image CUDA filtering excluded it; using CUDA_VISIBLE_DEVICES=${runnerEnv["CUDA_VISIBLE_DEVICES"] ?? "default"}`
260903
+ });
260904
+ }
260750
260905
  }
260751
260906
  const result = await runProcess2(python.command, argv, {
260752
260907
  cwd: this.cwd,
@@ -260948,6 +261103,7 @@ __export(audio_generate_exports, {
260948
261103
  AudioGenerateTool: () => AudioGenerateTool,
260949
261104
  DEFAULT_MUSIC_MODEL: () => DEFAULT_MUSIC_MODEL,
260950
261105
  DEFAULT_SOUND_MODEL: () => DEFAULT_SOUND_MODEL,
261106
+ audioBrokerGpuIndexIsCompatible: () => audioBrokerGpuIndexIsCompatible,
260951
261107
  audioGenerationDir: () => audioGenerationDir,
260952
261108
  audioGenerationFallbackCandidates: () => audioGenerationFallbackCandidates,
260953
261109
  audioGenerationModelPresets: () => audioGenerationModelPresets,
@@ -260958,9 +261114,11 @@ __export(audio_generate_exports, {
260958
261114
  findNonGatedAudioFallback: () => findNonGatedAudioFallback,
260959
261115
  getAudioGenerationPreset: () => getAudioGenerationPreset,
260960
261116
  inferAudioGenerationBackend: () => inferAudioGenerationBackend,
260961
- isAudioPresetGated: () => isAudioPresetGated
261117
+ isAudioPresetGated: () => isAudioPresetGated,
261118
+ parseAudioCudaDeviceInfo: () => parseAudioCudaDeviceInfo,
261119
+ resolveAudioCudaVisibleDevicesForEnv: () => resolveAudioCudaVisibleDevicesForEnv
260962
261120
  });
260963
- import { execFileSync as execFileSync4, spawn as spawn8 } from "node:child_process";
261121
+ import { spawn as spawn8 } from "node:child_process";
260964
261122
  import { existsSync as existsSync32, readdirSync as readdirSync14, statSync as statSync13 } from "node:fs";
260965
261123
  import { chmod as chmod4, mkdir as mkdir13, writeFile as writeFile18 } from "node:fs/promises";
260966
261124
  import { join as join42 } from "node:path";
@@ -261025,25 +261183,36 @@ function backendPackages(backend) {
261025
261183
  return TANGOFLUX_PACKAGES;
261026
261184
  return DIFFUSERS_AUDIO_PACKAGES;
261027
261185
  }
261186
+ function splitCudaVisibleDevices2(value2) {
261187
+ return (value2 ?? "").split(",").map((part) => part.trim()).filter(Boolean);
261188
+ }
261189
+ function parseAudioCudaDeviceInfo(text) {
261190
+ return parseCudaDeviceInfo(text);
261191
+ }
261192
+ function detectAudioCudaDevices() {
261193
+ return detectCudaDevices();
261194
+ }
261195
+ function resolveAudioCudaVisibleDevicesForEnv(args = {}) {
261196
+ return resolveMediaCudaVisibleDevicesForEnv({
261197
+ modality: "audio",
261198
+ env: args.env,
261199
+ devices: args.devices,
261200
+ minComputeCapability: args.minComputeCapability
261201
+ });
261202
+ }
261203
+ function audioBrokerGpuIndexIsCompatible(gpuIndex, env2 = process.env, devices = detectAudioCudaDevices()) {
261204
+ return mediaBrokerGpuIndexIsCompatible(gpuIndex, "audio", env2, devices);
261205
+ }
261028
261206
  function detectLegacyCudaComputeCapability() {
261029
- try {
261030
- const out = execFileSync4("nvidia-smi", ["--query-gpu=compute_cap,name", "--format=csv,noheader,nounits"], {
261031
- encoding: "utf8",
261032
- timeout: 5e3,
261033
- stdio: ["ignore", "pipe", "ignore"]
261034
- }).trim();
261035
- const first2 = out.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
261036
- const match = first2?.match(/^(\d+)\.(\d+)\s*,?\s*(.*)$/);
261037
- if (!match)
261038
- return null;
261039
- const major = Number(match[1]);
261040
- const minor = Number(match[2]);
261041
- if (!Number.isFinite(major) || !Number.isFinite(minor))
261042
- return null;
261043
- return { major, minor, name: match[3]?.trim() || void 0 };
261044
- } catch {
261207
+ const devices = detectAudioCudaDevices();
261208
+ const visible = new Set(splitCudaVisibleDevices2(resolveAudioCudaVisibleDevicesForEnv({ devices })));
261209
+ const candidates = visible.size > 0 ? devices.filter((device) => visible.has(String(device.index)) || device.uuid && visible.has(device.uuid)) : devices;
261210
+ const legacy = candidates.find((device) => device.computeCapability !== null && isLegacyCudaCapability(Math.floor(device.computeCapability), Math.round(device.computeCapability % 1 * 10)));
261211
+ if (!legacy || legacy.computeCapability === null)
261045
261212
  return null;
261046
- }
261213
+ const major = Math.floor(legacy.computeCapability);
261214
+ const minor = Math.round(legacy.computeCapability % 1 * 10);
261215
+ return { major, minor, name: legacy.name };
261047
261216
  }
261048
261217
  function isLegacyCudaCapability(major, minor) {
261049
261218
  return major < 7 || major === 7 && minor < 5;
@@ -261087,7 +261256,9 @@ function backendImportCheck(backend) {
261087
261256
  return "import torch, diffusers, scipy\nfrom diffusers import AudioLDMPipeline\n";
261088
261257
  }
261089
261258
  function audioGenerationPythonEnv(_repoRoot) {
261090
- return unifiedPythonEnv();
261259
+ const env2 = unifiedPythonEnv();
261260
+ applyMediaCudaDeviceFilterToEnv(env2, "audio");
261261
+ return env2;
261091
261262
  }
261092
261263
  function approxAudioDownloadBytes(preset) {
261093
261264
  if (preset?.approxDownloadGB)
@@ -261341,8 +261512,8 @@ function formatAudioSetupFailure(backend, text) {
261341
261512
  if (lowered.includes("cuda") && lowered.includes("not available")) {
261342
261513
  notes2.push("CUDA was not available to the selected Python environment; install a Torch build matching this machine's CUDA runtime or use CPU-compatible settings.");
261343
261514
  }
261344
- if (lowered.includes("cudnn version") && lowered.includes("sm < 7.5")) {
261345
- notes2.push("The installed PyTorch wheel uses cuDNN 9 on a legacy CUDA GPU. Omnius now repairs audio-generation venvs by reinstalling PyTorch 2.3.1 from the cu118 index for SM < 7.5 hardware.");
261515
+ if (lowered.includes("cudnn") && lowered.includes("incompatible") || lowered.includes("sm < 7.5") || lowered.includes("not compatible") && (lowered.includes("sm_") || lowered.includes("compute capability"))) {
261516
+ notes2.push(`The installed PyTorch wheel is touching a legacy CUDA GPU. Audio generation auto-filters CUDA devices below SM 7.5; current resolved CUDA_VISIBLE_DEVICES=${resolveAudioCudaVisibleDevicesForEnv() ?? "unset"}. Override with OMNIUS_AUDIO_CUDA_VISIBLE_DEVICES or disable with OMNIUS_AUDIO_DISABLE_CUDA_FILTER=1.`);
261346
261517
  }
261347
261518
  return [body, ...notes2.map((note) => `
261348
261519
  ${note}`)].filter(Boolean).join("");
@@ -261733,6 +261904,7 @@ var init_audio_generate = __esm({
261733
261904
  init_venv_paths();
261734
261905
  init_model_store();
261735
261906
  init_hf_media_models();
261907
+ init_cuda_device_filter();
261736
261908
  DEFAULT_SOUND_MODEL = "cvssp/audioldm-s-full-v2";
261737
261909
  DEFAULT_MUSIC_MODEL = "facebook/musicgen-small";
261738
261910
  DIFFUSERS_AUDIO_PACKAGES = [
@@ -262094,9 +262266,9 @@ var init_audio_generate = __esm({
262094
262266
  import argparse, json, os, sys, time
262095
262267
  from pathlib import Path
262096
262268
 
262097
- # Broker-picked GPU pinning — must run before importing torch.
262098
- _omnius_gpu = os.environ.get("OMNIUS_GPU_INDEX", "").strip()
262099
- if _omnius_gpu and "CUDA_VISIBLE_DEVICES" not in os.environ:
262269
+ # Broker/audio GPU pinning — must run before importing torch.
262270
+ _omnius_gpu = os.environ.get("OMNIUS_GPU_INDEX", "").strip() or os.environ.get("OMNIUS_AUDIO_GPU", "").strip()
262271
+ if _omnius_gpu:
262100
262272
  os.environ["CUDA_VISIBLE_DEVICES"] = _omnius_gpu
262101
262273
 
262102
262274
  def _format_bytes(value):
@@ -262239,9 +262411,14 @@ if __name__ == "__main__":
262239
262411
  main()
262240
262412
  `;
262241
262413
  AUDIOCRAFT_RUNNER = String.raw`#!/usr/bin/env python3
262242
- import argparse, json, sys, time
262414
+ import argparse, json, os, sys, time
262243
262415
  from pathlib import Path
262244
262416
 
262417
+ # Broker/audio GPU pinning — must run before importing torch/audiocraft.
262418
+ _omnius_gpu = os.environ.get("OMNIUS_GPU_INDEX", "").strip() or os.environ.get("OMNIUS_AUDIO_GPU", "").strip()
262419
+ if _omnius_gpu:
262420
+ os.environ["CUDA_VISIBLE_DEVICES"] = _omnius_gpu
262421
+
262245
262422
  def _progress(stage, message, percent=None):
262246
262423
  payload = {"omnius_progress": True, "stage": stage, "message": message}
262247
262424
  if percent is not None:
@@ -262295,9 +262472,9 @@ if __name__ == "__main__":
262295
262472
  import argparse, json, os, sys, time
262296
262473
  from pathlib import Path
262297
262474
 
262298
- # Broker-picked GPU pinning — must run before importing torch.
262299
- _omnius_gpu = os.environ.get("OMNIUS_GPU_INDEX", "").strip()
262300
- if _omnius_gpu and "CUDA_VISIBLE_DEVICES" not in os.environ:
262475
+ # Broker/audio GPU pinning — must run before importing torch.
262476
+ _omnius_gpu = os.environ.get("OMNIUS_GPU_INDEX", "").strip() or os.environ.get("OMNIUS_AUDIO_GPU", "").strip()
262477
+ if _omnius_gpu:
262301
262478
  os.environ["CUDA_VISIBLE_DEVICES"] = _omnius_gpu
262302
262479
 
262303
262480
  def _format_bytes(value):
@@ -262411,9 +262588,14 @@ if __name__ == "__main__":
262411
262588
  main()
262412
262589
  `;
262413
262590
  TANGOFLUX_RUNNER = String.raw`#!/usr/bin/env python3
262414
- import argparse, json, sys, time
262591
+ import argparse, json, os, sys, time
262415
262592
  from pathlib import Path
262416
262593
 
262594
+ # Broker/audio GPU pinning — must run before importing torch/tangoflux.
262595
+ _omnius_gpu = os.environ.get("OMNIUS_GPU_INDEX", "").strip() or os.environ.get("OMNIUS_AUDIO_GPU", "").strip()
262596
+ if _omnius_gpu:
262597
+ os.environ["CUDA_VISIBLE_DEVICES"] = _omnius_gpu
262598
+
262417
262599
  def _format_bytes(value):
262418
262600
  try:
262419
262601
  n = float(value)
@@ -262885,7 +263067,14 @@ if __name__ == "__main__":
262885
263067
  this.emitProgress({ stage: "load", message: `Starting ${args.kind} generation with ${args.model}` });
262886
263068
  const runnerEnv = { ...python.env };
262887
263069
  if (this._brokerGpuIndex !== null) {
262888
- runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
263070
+ if (audioBrokerGpuIndexIsCompatible(this._brokerGpuIndex, runnerEnv)) {
263071
+ runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
263072
+ } else {
263073
+ this.emitProgress({
263074
+ stage: "setup",
263075
+ message: `Broker selected CUDA GPU ${this._brokerGpuIndex}, but audio CUDA filtering excluded it; using CUDA_VISIBLE_DEVICES=${runnerEnv["CUDA_VISIBLE_DEVICES"] ?? "default"}`
263076
+ });
263077
+ }
262889
263078
  }
262890
263079
  const result = await runProcess3(python.command, argv, {
262891
263080
  cwd: this.cwd,
@@ -263359,6 +263548,7 @@ function resolveHfToken() {
263359
263548
  }
263360
263549
  function videoGenerationPythonEnv(_repoRoot) {
263361
263550
  const env2 = unifiedPythonEnv();
263551
+ applyMediaCudaDeviceFilterToEnv(env2, "video");
263362
263552
  const token = resolveHfToken();
263363
263553
  if (token) {
263364
263554
  env2["HF_TOKEN"] = token;
@@ -263717,6 +263907,7 @@ var init_video_generate = __esm({
263717
263907
  init_venv_paths();
263718
263908
  init_model_store();
263719
263909
  init_hf_media_models();
263910
+ init_cuda_device_filter();
263720
263911
  DEFAULT_DIFFUSERS_VIDEO_MODEL = "Efficient-Large-Model/SANA-Video_2B_480p";
263721
263912
  SANA_VIDEO_480P_MODEL = "Efficient-Large-Model/SANA-Video_2B_480p";
263722
263913
  SANA_VIDEO_720P_MODEL = "Efficient-Large-Model/SANA-Video_2B_720p";
@@ -265555,7 +265746,14 @@ ${llmAnnotation}` : result.llmContent;
265555
265746
  runnerEnv["HUGGING_FACE_HUB_TOKEN"] = effectiveToken;
265556
265747
  }
265557
265748
  if (this._brokerGpuIndex !== null) {
265558
- runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
265749
+ if (mediaBrokerGpuIndexIsCompatible(this._brokerGpuIndex, "video", runnerEnv)) {
265750
+ runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
265751
+ } else {
265752
+ this.emitProgress({
265753
+ stage: "setup",
265754
+ message: `Broker selected CUDA GPU ${this._brokerGpuIndex}, but video CUDA filtering excluded it; using CUDA_VISIBLE_DEVICES=${runnerEnv["CUDA_VISIBLE_DEVICES"] ?? "default"}`
265755
+ });
265756
+ }
265559
265757
  }
265560
265758
  const argv = [
265561
265759
  runner,
@@ -266682,6 +266880,11 @@ import { readFileSync as readFileSync24, existsSync as existsSync34, statSync as
266682
266880
  import { execSync as execSync16, spawn as spawn10, spawnSync as spawnSync4 } from "node:child_process";
266683
266881
  import { resolve as resolve23, extname as extname6, basename as basename8, dirname as dirname9, join as join44 } from "node:path";
266684
266882
  import { fileURLToPath as fileURLToPath4 } from "node:url";
266883
+ function visionPythonEnv(extra = {}) {
266884
+ const env2 = { ...process.env, ...extra };
266885
+ applyMediaCudaDeviceFilterToEnv(env2, "vision");
266886
+ return env2;
266887
+ }
266685
266888
  async function probeStation(endpoint) {
266686
266889
  try {
266687
266890
  const healthUrl = endpoint.replace(/\/v1\/?$/, "/health");
@@ -266752,7 +266955,8 @@ async function autoLaunchStation(port = 2020) {
266752
266955
  return false;
266753
266956
  return new Promise((resolvePromise) => {
266754
266957
  const child = spawn10(pythonBin, [launcherScript, "--port", String(port)], {
266755
- stdio: ["ignore", "pipe", "pipe"]
266958
+ stdio: ["ignore", "pipe", "pipe"],
266959
+ env: visionPythonEnv()
266756
266960
  });
266757
266961
  stationProcess = child;
266758
266962
  const cleanupStation = () => {
@@ -267067,7 +267271,11 @@ function tryHuggingFacePointBackend(options2) {
267067
267271
  hfPointUnavailable = "Python not found";
267068
267272
  return null;
267069
267273
  }
267070
- const deps = spawnSync4(python, ["-c", "import torch, transformers, PIL"], { stdio: "pipe", timeout: 1e4 });
267274
+ const deps = spawnSync4(python, ["-c", "import torch, transformers, PIL"], {
267275
+ stdio: "pipe",
267276
+ timeout: 1e4,
267277
+ env: visionPythonEnv()
267278
+ });
267071
267279
  if (deps.status !== 0) {
267072
267280
  hfPointUnavailable = bufferishToString3(deps.stderr) || "Python dependencies torch, transformers, and pillow are not importable";
267073
267281
  return null;
@@ -267114,7 +267322,7 @@ print(json.dumps(last_result))
267114
267322
  encoding: "utf8",
267115
267323
  stdio: ["pipe", "pipe", "pipe"],
267116
267324
  timeout: Math.max(options2.timeoutMs ?? 6e4, 3e5),
267117
- env: { ...process.env }
267325
+ env: visionPythonEnv()
267118
267326
  });
267119
267327
  if (run2.status !== 0) {
267120
267328
  hfPointUnavailable = run2.stderr || run2.stdout || "Hugging Face Moondream point backend failed";
@@ -267167,6 +267375,7 @@ var init_vision = __esm({
267167
267375
  "packages/execution/dist/tools/vision.js"() {
267168
267376
  "use strict";
267169
267377
  init_model_broker();
267378
+ init_cuda_device_filter();
267170
267379
  moondreamClient = null;
267171
267380
  moondreamError = null;
267172
267381
  stationProcess = null;
@@ -520577,6 +520786,11 @@ import { execFileSync as execFileSync5, execSync as execSync30, spawn as spawn15
520577
520786
  import { copyFileSync as copyFileSync3, existsSync as existsSync47, statSync as statSync23, writeFileSync as writeFileSync19, mkdirSync as mkdirSync20, readdirSync as readdirSync18, writeSync } from "node:fs";
520578
520787
  import { basename as basename15, extname as extname10, isAbsolute as isAbsolute2, join as join63 } from "node:path";
520579
520788
  import { homedir as homedir16, tmpdir as tmpdir11 } from "node:os";
520789
+ function ttsPythonEnv(extra = {}) {
520790
+ const env2 = { ...process.env, ...extra };
520791
+ applyMediaCudaDeviceFilterToEnv(env2, "tts");
520792
+ return env2;
520793
+ }
520580
520794
  function hasCommand3(command) {
520581
520795
  try {
520582
520796
  if (process.platform === "win32") {
@@ -521144,7 +521358,7 @@ function ensureLuxttsDaemon() {
521144
521358
  const daemon = spawn15(venvPy, [inferScript], {
521145
521359
  stdio: ["pipe", "pipe", "pipe"],
521146
521360
  cwd: tmpdir11(),
521147
- env: { ...process.env, LUXTTS_REPO_PATH: repoDir }
521361
+ env: ttsPythonEnv({ LUXTTS_REPO_PATH: repoDir })
521148
521362
  });
521149
521363
  _luxttsDaemon = daemon;
521150
521364
  _luxttsBuffer = "";
@@ -521224,6 +521438,7 @@ var init_audio_playback = __esm({
521224
521438
  "packages/execution/dist/tools/audio-playback.js"() {
521225
521439
  "use strict";
521226
521440
  init_hf_media_models();
521441
+ init_cuda_device_filter();
521227
521442
  _luxttsDaemon = null;
521228
521443
  _luxttsReady = false;
521229
521444
  _luxttsRequestId = 0;
@@ -521605,7 +521820,7 @@ ${tried.map((line) => `- ${line}`).join("\n")}`,
521605
521820
  execFileSync5(venvPy, ["-c", pyScript, JSON.stringify({ text, output: outputPath3, clone_ref: cloneRef, repo: repoDir, speed })], {
521606
521821
  stdio: "pipe",
521607
521822
  timeout: 12e4,
521608
- env: { ...process.env, LUXTTS_REPO_PATH: repoDir }
521823
+ env: ttsPythonEnv({ LUXTTS_REPO_PATH: repoDir })
521609
521824
  });
521610
521825
  return `${basename15(cloneRef)} (LuxTTS standalone)`;
521611
521826
  }
@@ -521619,7 +521834,8 @@ ${tried.map((line) => `- ${line}`).join("\n")}`,
521619
521834
  input: JSON.stringify({ text, output_path: outputPath3, voice_name: voice, lang, speed, total_step: totalStep }),
521620
521835
  encoding: "utf8",
521621
521836
  stdio: ["pipe", "pipe", "pipe"],
521622
- timeout: 18e4
521837
+ timeout: 18e4,
521838
+ env: ttsPythonEnv()
521623
521839
  });
521624
521840
  const line = stdout.trim().split(/\r?\n/).pop() || "";
521625
521841
  const parsed = JSON.parse(line);
@@ -523041,10 +523257,16 @@ import { execSync as execSync36 } from "node:child_process";
523041
523257
  import { existsSync as existsSync50, mkdirSync as mkdirSync22, writeFileSync as writeFileSync20, readFileSync as readFileSync35 } from "node:fs";
523042
523258
  import { join as join65 } from "node:path";
523043
523259
  import { homedir as homedir17, tmpdir as tmpdir13 } from "node:os";
523260
+ function audioAnalysisPythonEnv(extra = {}) {
523261
+ const env2 = { ...process.env, ...extra };
523262
+ applyMediaCudaDeviceFilterToEnv(env2, "asr");
523263
+ return env2;
523264
+ }
523044
523265
  var VENV_DIR, VENV_PIP, VENV_PYTHON, AudioAnalyzeTool;
523045
523266
  var init_audio_analyze = __esm({
523046
523267
  "packages/execution/dist/tools/audio-analyze.js"() {
523047
523268
  "use strict";
523269
+ init_cuda_device_filter();
523048
523270
  VENV_DIR = join65(homedir17(), ".omnius", "audio-ml-venv");
523049
523271
  VENV_PIP = join65(VENV_DIR, "bin", "pip");
523050
523272
  VENV_PYTHON = join65(VENV_DIR, "bin", "python3");
@@ -523334,15 +523556,15 @@ Context saved to: ${contextFile}`,
523334
523556
  /** Ensure Python venv with required packages */
523335
523557
  async ensureVenv(packages) {
523336
523558
  if (!existsSync50(VENV_PYTHON)) {
523337
- execSync36(`python3 -m venv ${VENV_DIR}`, { timeout: 3e4, stdio: "pipe" });
523559
+ execSync36(`python3 -m venv ${VENV_DIR}`, { timeout: 3e4, stdio: "pipe", env: audioAnalysisPythonEnv() });
523338
523560
  }
523339
523561
  for (const pkg of packages) {
523340
523562
  const importName = pkg.replace(/[<>=!].*/g, "").replace(/-/g, "_");
523341
523563
  try {
523342
- execSync36(`${VENV_PYTHON} -c "import ${importName}"`, { timeout: 1e4, stdio: "pipe" });
523564
+ execSync36(`${VENV_PYTHON} -c "import ${importName}"`, { timeout: 1e4, stdio: "pipe", env: audioAnalysisPythonEnv() });
523343
523565
  } catch {
523344
523566
  try {
523345
- execSync36(`${VENV_PIP} install "${pkg}"`, { timeout: 3e5, stdio: "pipe" });
523567
+ execSync36(`${VENV_PIP} install "${pkg}"`, { timeout: 3e5, stdio: "pipe", env: audioAnalysisPythonEnv() });
523346
523568
  } catch {
523347
523569
  }
523348
523570
  }
@@ -523356,7 +523578,7 @@ Context saved to: ${contextFile}`,
523356
523578
  const output = execSync36(`${VENV_PYTHON} ${scriptFile}`, {
523357
523579
  encoding: "utf8",
523358
523580
  timeout: 3e5,
523359
- env: { ...process.env, TF_CPP_MIN_LOG_LEVEL: "3", TF_ENABLE_ONEDNN_OPTS: "0", PYTHONUNBUFFERED: "1" }
523581
+ env: audioAnalysisPythonEnv({ TF_CPP_MIN_LOG_LEVEL: "3", TF_ENABLE_ONEDNN_OPTS: "0", PYTHONUNBUFFERED: "1" })
523360
523582
  });
523361
523583
  try {
523362
523584
  const result = JSON.parse(output.trim().split("\n").pop());
@@ -524192,10 +524414,16 @@ import { execSync as execSync38 } from "node:child_process";
524192
524414
  import { existsSync as existsSync52, mkdirSync as mkdirSync24, writeFileSync as writeFileSync22, readFileSync as readFileSync37 } from "node:fs";
524193
524415
  import { join as join67 } from "node:path";
524194
524416
  import { homedir as homedir19, tmpdir as tmpdir15 } from "node:os";
524417
+ function visualMemoryPythonEnv(extra = {}) {
524418
+ const env2 = { ...process.env, ...extra };
524419
+ applyMediaCudaDeviceFilterToEnv(env2, "vision");
524420
+ return env2;
524421
+ }
524195
524422
  var VMEM_DIR, VENV_DIR2, VENV_PY, VENV_PIP2, VisualMemoryTool;
524196
524423
  var init_visual_memory = __esm({
524197
524424
  "packages/execution/dist/tools/visual-memory.js"() {
524198
524425
  "use strict";
524426
+ init_cuda_device_filter();
524199
524427
  VMEM_DIR = join67(homedir19(), ".omnius", "visual-memory");
524200
524428
  VENV_DIR2 = join67(homedir19(), ".omnius", "vision-ml-venv");
524201
524429
  VENV_PY = join67(VENV_DIR2, "bin", "python3");
@@ -524723,19 +524951,23 @@ ${objects.join("\n") || " (none taught)"}`,
524723
524951
  async ensureVenv() {
524724
524952
  if (existsSync52(VENV_PY)) {
524725
524953
  try {
524726
- execSync38(`${VENV_PY} -c "import insightface, transformers, torch"`, { timeout: 15e3, stdio: "pipe" });
524954
+ execSync38(`${VENV_PY} -c "import insightface, transformers, torch"`, {
524955
+ timeout: 15e3,
524956
+ stdio: "pipe",
524957
+ env: visualMemoryPythonEnv()
524958
+ });
524727
524959
  return true;
524728
524960
  } catch {
524729
524961
  }
524730
524962
  }
524731
524963
  try {
524732
524964
  if (!existsSync52(VENV_PY)) {
524733
- execSync38(`python3 -m venv ${VENV_DIR2}`, { timeout: 3e4, stdio: "pipe" });
524965
+ execSync38(`python3 -m venv ${VENV_DIR2}`, { timeout: 3e4, stdio: "pipe", env: visualMemoryPythonEnv() });
524734
524966
  }
524735
- execSync38(`${VENV_PIP2} install "setuptools<81" wheel`, { timeout: 6e4, stdio: "pipe" });
524736
- execSync38(`${VENV_PIP2} install torch torchvision`, { timeout: 6e5, stdio: "pipe" });
524737
- execSync38(`${VENV_PIP2} install insightface onnxruntime opencv-python-headless`, { timeout: 3e5, stdio: "pipe" });
524738
- execSync38(`${VENV_PIP2} install transformers pillow`, { timeout: 3e5, stdio: "pipe" });
524967
+ execSync38(`${VENV_PIP2} install "setuptools<81" wheel`, { timeout: 6e4, stdio: "pipe", env: visualMemoryPythonEnv() });
524968
+ execSync38(`${VENV_PIP2} install torch torchvision`, { timeout: 6e5, stdio: "pipe", env: visualMemoryPythonEnv() });
524969
+ execSync38(`${VENV_PIP2} install insightface onnxruntime opencv-python-headless`, { timeout: 3e5, stdio: "pipe", env: visualMemoryPythonEnv() });
524970
+ execSync38(`${VENV_PIP2} install transformers pillow`, { timeout: 3e5, stdio: "pipe", env: visualMemoryPythonEnv() });
524739
524971
  return true;
524740
524972
  } catch {
524741
524973
  return false;
@@ -524748,7 +524980,7 @@ ${objects.join("\n") || " (none taught)"}`,
524748
524980
  const output = execSync38(`${VENV_PY} ${scriptFile}`, {
524749
524981
  encoding: "utf8",
524750
524982
  timeout: timeoutMs,
524751
- env: { ...process.env, PYTHONUNBUFFERED: "1" }
524983
+ env: visualMemoryPythonEnv({ PYTHONUNBUFFERED: "1" })
524752
524984
  });
524753
524985
  const lastLine = output.trim().split("\n").pop() || "{}";
524754
524986
  return JSON.parse(lastLine);
@@ -525364,6 +525596,11 @@ import { existsSync as existsSync54, mkdirSync as mkdirSync26, writeFileSync as
525364
525596
  import { dirname as dirname15, join as join69, resolve as resolve35 } from "node:path";
525365
525597
  import { tmpdir as tmpdir17, homedir as homedir21 } from "node:os";
525366
525598
  import { fileURLToPath as fileURLToPath8 } from "node:url";
525599
+ function asrPythonEnv(extra = {}) {
525600
+ const env2 = { ...process.env, ...extra };
525601
+ applyMediaCudaDeviceFilterToEnv(env2, "asr");
525602
+ return env2;
525603
+ }
525367
525604
  function _findNemotronScript() {
525368
525605
  const candidates = [];
525369
525606
  try {
@@ -525399,6 +525636,7 @@ var init_asr_listen = __esm({
525399
525636
  "packages/execution/dist/tools/asr-listen.js"() {
525400
525637
  "use strict";
525401
525638
  init_hf_media_models();
525639
+ init_cuda_device_filter();
525402
525640
  AsrListenTool = class {
525403
525641
  name = "asr_listen";
525404
525642
  description = "Record from microphone and transcribe speech to text. Backends: 'whisper' (default, battle-tested openai-whisper / faster-whisper), 'nemotron' (nvidia/nemotron-speech-streaming-en-0.6b — faster streaming), or 'parallel' (runs BOTH engines on the same audio and returns a side-by-side comparison with per-engine latency and character counts). Actions: 'listen' to record + transcribe in one step, 'transcribe' to run on an existing file. Use this when you need to HEAR what a human is saying — ask a question via audio_playback speak, then use asr_listen to capture and transcribe their response.";
@@ -525633,7 +525871,7 @@ print(json.dumps({"ok": False, "error": "No whisper backend available"}))
525633
525871
  const output = execSync40(`"${pyPath}" "${scriptFile}"`, {
525634
525872
  encoding: "utf8",
525635
525873
  timeout: 12e4,
525636
- env: { ...process.env, PYTHONUNBUFFERED: "1" }
525874
+ env: asrPythonEnv({ PYTHONUNBUFFERED: "1" })
525637
525875
  }).trim();
525638
525876
  const lines = output.split("\n");
525639
525877
  for (let i2 = lines.length - 1; i2 >= 0; i2--) {
@@ -525682,7 +525920,8 @@ print(json.dumps({"ok": False, "error": "No whisper backend available"}))
525682
525920
  const result = spawnSync7("python3", [script, "--file", audioFile], {
525683
525921
  encoding: "utf8",
525684
525922
  timeout: 6e5,
525685
- stdio: ["ignore", "pipe", "pipe"]
525923
+ stdio: ["ignore", "pipe", "pipe"],
525924
+ env: asrPythonEnv()
525686
525925
  });
525687
525926
  if (result.error) {
525688
525927
  return {
@@ -530791,6 +531030,7 @@ __export(dist_exports, {
530791
531030
  CustomTool: () => CustomTool,
530792
531031
  DEFAULT_DIFFUSERS_IMAGE_MODEL: () => DEFAULT_DIFFUSERS_IMAGE_MODEL,
530793
531032
  DEFAULT_DIFFUSERS_VIDEO_MODEL: () => DEFAULT_DIFFUSERS_VIDEO_MODEL,
531033
+ DEFAULT_MEDIA_MIN_CUDA_COMPUTE_CAPABILITY: () => DEFAULT_MEDIA_MIN_CUDA_COMPUTE_CAPABILITY,
530794
531034
  DEFAULT_MUSIC_MODEL: () => DEFAULT_MUSIC_MODEL,
530795
531035
  DEFAULT_OLLAMA_IMAGE_MODEL: () => DEFAULT_OLLAMA_IMAGE_MODEL,
530796
531036
  DEFAULT_SOUND_MODEL: () => DEFAULT_SOUND_MODEL,
@@ -530905,6 +531145,7 @@ __export(dist_exports, {
530905
531145
  addProjectConstraint: () => addProjectConstraint,
530906
531146
  addSessionConstraint: () => addSessionConstraint,
530907
531147
  aliasTool: () => aliasTool,
531148
+ applyMediaCudaDeviceFilterToEnv: () => applyMediaCudaDeviceFilterToEnv,
530908
531149
  applyPatch: () => applyPatch,
530909
531150
  applyToolResultTriage: () => applyToolResultTriage,
530910
531151
  artifactManifestFromBytes: () => artifactManifestFromBytes,
@@ -530945,6 +531186,7 @@ __export(dist_exports, {
530945
531186
  defaultExtensionForMime: () => defaultExtensionForMime,
530946
531187
  deleteMediaModelAdapter: () => deleteMediaModelAdapter,
530947
531188
  deleteTodos: () => deleteTodos,
531189
+ detectCudaDevices: () => detectCudaDevices,
530948
531190
  detectElevationMethod: () => detectElevationMethod,
530949
531191
  detectLegacyCaches: () => detectLegacyCaches,
530950
531192
  detectSearchProvider: () => detectSearchProvider,
@@ -531046,6 +531288,7 @@ __export(dist_exports, {
531046
531288
  markSessionValidated: () => markSessionValidated,
531047
531289
  measureRepoCacheBytes: () => measureRepoCacheBytes,
531048
531290
  mediaBackendCompatibleWithModality: () => mediaBackendCompatibleWithModality,
531291
+ mediaBrokerGpuIndexIsCompatible: () => mediaBrokerGpuIndexIsCompatible,
531049
531292
  mediaMimeFromPath: () => mediaMimeFromPath,
531050
531293
  mediaModelCatalogDir: () => mediaModelCatalogDir,
531051
531294
  mediaModelSlug: () => mediaModelSlug,
@@ -531056,6 +531299,8 @@ __export(dist_exports, {
531056
531299
  normalizeSponsorMediaConfig: () => normalizeSponsorMediaConfig,
531057
531300
  omniusHomeDir: () => omniusHomeDir,
531058
531301
  packetPath: () => packetPath,
531302
+ parseCudaComputeCapability: () => parseCudaComputeCapability,
531303
+ parseCudaDeviceInfo: () => parseCudaDeviceInfo,
531059
531304
  parseMcpMarkdown: () => parseMcpMarkdown,
531060
531305
  parseMcpToolName: () => parseMcpToolName,
531061
531306
  parseSponsorMediaCapability: () => parseSponsorMediaCapability,
@@ -531078,6 +531323,7 @@ __export(dist_exports, {
531078
531323
  renderCustomToolDocs: () => renderCustomToolDocs,
531079
531324
  resetDepCache: () => resetDepCache,
531080
531325
  resetMoondreamClient: () => resetMoondreamClient,
531326
+ resolveMediaCudaVisibleDevicesForEnv: () => resolveMediaCudaVisibleDevicesForEnv,
531081
531327
  resolveMediaModel: () => resolveMediaModel,
531082
531328
  resolveSecret: () => resolveSecret,
531083
531329
  revokeSecret: () => revokeSecret,
@@ -531199,6 +531445,7 @@ var init_dist5 = __esm({
531199
531445
  init_embedding_store();
531200
531446
  init_image_generate();
531201
531447
  init_audio_generate();
531448
+ init_cuda_device_filter();
531202
531449
  init_model_store();
531203
531450
  init_video_generate();
531204
531451
  init_sponsor_media();
@@ -589502,6 +589749,7 @@ ${CONTENT_BG_SEQ}`);
589502
589749
  });
589503
589750
 
589504
589751
  // packages/cli/src/tui/tui-select.ts
589752
+ import { AsyncLocalStorage } from "node:async_hooks";
589505
589753
  function ansi3(code8, text) {
589506
589754
  return isTTY2 ? `\x1B[${code8}m${text}\x1B[0m` : text;
589507
589755
  }
@@ -589511,6 +589759,48 @@ function fg2563(code8, text) {
589511
589759
  function stripAnsi3(s2) {
589512
589760
  return s2.replace(/\x1B\[[0-9;]*m/g, "");
589513
589761
  }
589762
+ function stripTerminalControl(s2) {
589763
+ return s2.replace(/\x1B(?:\[[\d;?]*[ -/]*[@-~]|\][^\x07\x1B]*(?:\x07|\x1B\\)?|[@-Z\\-_])/g, "");
589764
+ }
589765
+ function isNonInteractiveSelectSurface() {
589766
+ return Boolean(nonInteractiveSelectSurface.getStore());
589767
+ }
589768
+ function runWithNonInteractiveSelectSurface(fn, opts = {}) {
589769
+ return nonInteractiveSelectSurface.run(opts, fn);
589770
+ }
589771
+ function renderNonInteractiveSelect(opts, currentTitle, skipSet) {
589772
+ const surface = nonInteractiveSelectSurface.getStore();
589773
+ const maxItems = Math.max(1, surface?.maxItems ?? 30);
589774
+ const lines = [];
589775
+ if (currentTitle) lines.push(stripTerminalControl(stripAnsi3(currentTitle)));
589776
+ if (lines.length) lines.push("");
589777
+ let idx = 1;
589778
+ let shown = 0;
589779
+ let omitted = 0;
589780
+ for (const item of opts.items) {
589781
+ const isSkip = skipSet.has(item.key);
589782
+ const labelPlain = stripTerminalControl(stripAnsi3(item.label)).trim();
589783
+ const detailPlain = item.detail ? stripTerminalControl(stripAnsi3(item.detail)).trim() : "";
589784
+ if (isSkip) {
589785
+ if (labelPlain) lines.push(labelPlain);
589786
+ continue;
589787
+ }
589788
+ if (shown >= maxItems) {
589789
+ omitted++;
589790
+ idx++;
589791
+ continue;
589792
+ }
589793
+ const num = String(idx).padStart(2, " ");
589794
+ const detail = detailPlain ? ` - ${detailPlain}` : "";
589795
+ lines.push(` ${num}. ${labelPlain}${detail}`);
589796
+ shown++;
589797
+ idx++;
589798
+ }
589799
+ if (omitted > 0) lines.push(` ... ${omitted} more`);
589800
+ if (opts.customKeyHint) lines.push("", stripTerminalControl(stripAnsi3(opts.customKeyHint)));
589801
+ lines.push("", surface?.hint ?? "(non-interactive: menu shown as text; open the TUI for selection)");
589802
+ process.stdout.write(lines.join("\n").trimEnd() + "\n");
589803
+ }
589514
589804
  function defaultRenderRow(item, focused, isActive) {
589515
589805
  const marker = isActive ? selectColors.green("●") : focused ? selectColors.blue("●") : selectColors.dim("○");
589516
589806
  const label = focused ? selectColors.blue(selectColors.bold(item.label)) : isActive ? selectColors.green(item.label) : item.label;
@@ -589535,27 +589825,8 @@ function tuiSelect(opts) {
589535
589825
  if (items.length === 0) {
589536
589826
  return Promise.resolve({ confirmed: false, key: null, index: -1 });
589537
589827
  }
589538
- if (!process.stdin.isTTY && process.env["OMNIUS_TUI_FORCE_INTERACTIVE"] !== "1") {
589539
- const lines = [];
589540
- if (currentTitle) lines.push(currentTitle);
589541
- if (lines.length) lines.push("");
589542
- let idx = 1;
589543
- for (const item of items) {
589544
- const isSkip = skipSet.has(item.key);
589545
- const labelPlain = stripAnsi3(item.label);
589546
- const detailPlain = item.detail ? stripAnsi3(item.detail) : "";
589547
- if (isSkip) {
589548
- lines.push(labelPlain);
589549
- } else {
589550
- const num = String(idx).padStart(2, " ");
589551
- const detail = detailPlain ? ` — ${detailPlain}` : "";
589552
- lines.push(` ${num}. ${labelPlain}${detail}`);
589553
- idx++;
589554
- }
589555
- }
589556
- if (opts.customKeyHint) lines.push("", opts.customKeyHint);
589557
- lines.push("", "(non-interactive: list shown above; pick options by re-running this command from the TUI)");
589558
- process.stdout.write(lines.join("\n") + "\n");
589828
+ if (isNonInteractiveSelectSurface() || !process.stdin.isTTY && process.env["OMNIUS_TUI_FORCE_INTERACTIVE"] !== "1") {
589829
+ renderNonInteractiveSelect(opts, currentTitle, skipSet);
589559
589830
  return Promise.resolve({ confirmed: false, key: null, index: -1 });
589560
589831
  }
589561
589832
  const isSkippable = (idx) => skipSet.has(items[idx].key);
@@ -590136,7 +590407,7 @@ ${tuiBgSeq()}`);
590136
590407
  }
590137
590408
  });
590138
590409
  }
590139
- var isTTY2, MENU_ACTIVE_GREEN_256, selectColors;
590410
+ var isTTY2, MENU_ACTIVE_GREEN_256, selectColors, nonInteractiveSelectSurface;
590140
590411
  var init_tui_select = __esm({
590141
590412
  "packages/cli/src/tui/tui-select.ts"() {
590142
590413
  "use strict";
@@ -590156,6 +590427,7 @@ var init_tui_select = __esm({
590156
590427
  /** Readable grey for non-matching items */
590157
590428
  matchDark: (t2) => fg2563(tuiTextDim(), t2)
590158
590429
  };
590430
+ nonInteractiveSelectSurface = new AsyncLocalStorage();
590159
590431
  }
590160
590432
  });
590161
590433
 
@@ -590182,12 +590454,17 @@ import { join as join114, dirname as dirname32 } from "node:path";
590182
590454
  import { homedir as homedir36 } from "node:os";
590183
590455
  import { execSync as execSync50, spawn as spawn26 } from "node:child_process";
590184
590456
  import { fileURLToPath as fileURLToPath15 } from "node:url";
590457
+ function personaplexPythonEnv(extra = {}) {
590458
+ const env2 = { ...process.env, ...extra };
590459
+ applyMediaCudaDeviceFilterToEnv(env2, "voice");
590460
+ return env2;
590461
+ }
590185
590462
  function execAsync(cmd, opts = {}) {
590186
590463
  return new Promise((resolve59, reject) => {
590187
590464
  const child = spawn26("bash", ["-c", cmd], {
590188
590465
  stdio: ["ignore", "pipe", "pipe"],
590189
590466
  timeout: opts.timeout ?? 3e5,
590190
- env: opts.env ?? process.env
590467
+ env: personaplexPythonEnv(opts.env ?? {})
590191
590468
  });
590192
590469
  let stdout = "";
590193
590470
  let stderr = "";
@@ -590261,7 +590538,8 @@ function detectPersonaPlexCapability() {
590261
590538
  try {
590262
590539
  execSync50('python3 -c "import torch; assert torch.cuda.is_available()"', {
590263
590540
  timeout: 1e4,
590264
- stdio: "pipe"
590541
+ stdio: "pipe",
590542
+ env: personaplexPythonEnv()
590265
590543
  });
590266
590544
  } catch {
590267
590545
  const tier2 = selectWeightTier(vramGB);
@@ -590438,7 +590716,8 @@ async function installPersonaPlex(onInfo, weightTier) {
590438
590716
  const sitePackages = execSync50(`"${python}" -c "import moshi, os; print(os.path.dirname(moshi.__file__))"`, {
590439
590717
  encoding: "utf8",
590440
590718
  timeout: 5e3,
590441
- stdio: "pipe"
590719
+ stdio: "pipe",
590720
+ env: personaplexPythonEnv()
590442
590721
  }).trim();
590443
590722
  const serverFile = join114(sitePackages, "server.py");
590444
590723
  if (existsSync99(serverFile)) {
@@ -590455,7 +590734,8 @@ async function installPersonaPlex(onInfo, weightTier) {
590455
590734
  const sitePackages = execSync50(`"${python}" -c "import moshi, os; print(os.path.dirname(moshi.__file__))"`, {
590456
590735
  encoding: "utf8",
590457
590736
  timeout: 5e3,
590458
- stdio: "pipe"
590737
+ stdio: "pipe",
590738
+ env: personaplexPythonEnv()
590459
590739
  }).trim();
590460
590740
  const loadersFile = join114(sitePackages, "models", "loaders.py");
590461
590741
  if (existsSync99(loadersFile)) {
@@ -590559,7 +590839,8 @@ $2if filename.endswith(".safetensors"):`
590559
590839
  const sitePackages2 = execSync50(`"${python}" -c "import moshi, os; print(os.path.dirname(moshi.__file__))"`, {
590560
590840
  encoding: "utf8",
590561
590841
  timeout: 5e3,
590562
- stdio: "pipe"
590842
+ stdio: "pipe",
590843
+ env: personaplexPythonEnv()
590563
590844
  }).trim();
590564
590845
  const hybridDest = join114(sitePackages2, "hybrid_agent.py");
590565
590846
  const serverDest = join114(sitePackages2, "server.py");
@@ -590693,7 +590974,7 @@ async function startPersonaPlexDaemon(onInfo) {
590693
590974
  try {
590694
590975
  const weightPath = execSync50(
590695
590976
  `"${venvPython2}" -c "from huggingface_hub import hf_hub_download; print(hf_hub_download('${repoInfo.repo}', '${repoInfo.file}', token=False))"`,
590696
- { encoding: "utf8", timeout: 6e4, stdio: "pipe" }
590977
+ { encoding: "utf8", timeout: 6e4, stdio: "pipe", env: personaplexPythonEnv() }
590697
590978
  ).trim();
590698
590979
  if (existsSync99(weightPath)) {
590699
590980
  if (!existsSync99(cachedBf16)) {
@@ -590706,7 +590987,7 @@ state = {k: v.to(torch.bfloat16) if v.is_floating_point() else v for k, v in sta
590706
590987
  save_file(state, '${cachedBf16}')
590707
590988
  print('Converted')
590708
590989
  "`,
590709
- { timeout: 18e4, stdio: "pipe" }
590990
+ { timeout: 18e4, stdio: "pipe", env: personaplexPythonEnv() }
590710
590991
  );
590711
590992
  }
590712
590993
  if (existsSync99(cachedBf16)) {
@@ -590732,13 +591013,13 @@ print('Converted')
590732
591013
  try {
590733
591014
  const weightPath = execSync50(
590734
591015
  `"${venvPython2}" -c "from huggingface_hub import hf_hub_download; print(hf_hub_download('${repoInfo.repo}', '${repoInfo.file}'${repoInfo.needsToken ? "" : ", token=False"}))"`,
590735
- { encoding: "utf8", timeout: 3e4, stdio: "pipe" }
591016
+ { encoding: "utf8", timeout: 3e4, stdio: "pipe", env: personaplexPythonEnv() }
590736
591017
  ).trim();
590737
591018
  if (existsSync99(dequantScript) && existsSync99(weightPath)) {
590738
591019
  try {
590739
591020
  execSync50(
590740
591021
  `"${venvPython2}" "${dequantScript}" --input "${weightPath}" --output "${cachedBf16}"`,
590741
- { timeout: 3e5, stdio: "pipe" }
591022
+ { timeout: 3e5, stdio: "pipe", env: personaplexPythonEnv() }
590742
591023
  );
590743
591024
  if (existsSync99(cachedBf16)) {
590744
591025
  extraArgs.push("--moshi-weight", cachedBf16);
@@ -590751,7 +591032,7 @@ print('Converted')
590751
591032
  try {
590752
591033
  const mimiPath = execSync50(
590753
591034
  `"${venvPython2}" -c "from huggingface_hub import hf_hub_download; print(hf_hub_download('${repoInfo.repo}', 'tokenizer-e351c8d8-checkpoint125.safetensors', token=False))"`,
590754
- { encoding: "utf8", timeout: 3e4, stdio: "pipe" }
591035
+ { encoding: "utf8", timeout: 3e4, stdio: "pipe", env: personaplexPythonEnv() }
590755
591036
  ).trim();
590756
591037
  if (existsSync99(mimiPath)) extraArgs.push("--mimi-weight", mimiPath);
590757
591038
  } catch {
@@ -590759,7 +591040,7 @@ print('Converted')
590759
591040
  try {
590760
591041
  const tokPath = execSync50(
590761
591042
  `"${venvPython2}" -c "from huggingface_hub import hf_hub_download; print(hf_hub_download('${repoInfo.repo}', 'tokenizer_spm_32k_3.model', token=False))"`,
590762
- { encoding: "utf8", timeout: 3e4, stdio: "pipe" }
591043
+ { encoding: "utf8", timeout: 3e4, stdio: "pipe", env: personaplexPythonEnv() }
590763
591044
  ).trim();
590764
591045
  if (existsSync99(tokPath)) extraArgs.push("--tokenizer", tokPath);
590765
591046
  } catch {
@@ -590814,7 +591095,7 @@ print('Converted')
590814
591095
  ];
590815
591096
  if (hybridEnabled) serverArgs.push("--hybrid");
590816
591097
  if (needsOffload) serverArgs.push("--cpu-offload");
590817
- const serverEnv = { ...process.env };
591098
+ const serverEnv = personaplexPythonEnv();
590818
591099
  if (hybridEnabled) {
590819
591100
  serverEnv["HYBRID_ENABLED"] = "1";
590820
591101
  serverEnv["HYBRID_LLM_MODEL"] = ollamaModel;
@@ -590959,7 +591240,7 @@ async function clonePersonaPlexVoice(inputWav, voiceName, onInfo) {
590959
591240
  "cuda"
590960
591241
  ], {
590961
591242
  stdio: ["ignore", "pipe", "pipe"],
590962
- env: { ...process.env },
591243
+ env: personaplexPythonEnv(),
590963
591244
  cwd: PERSONAPLEX_DIR
590964
591245
  });
590965
591246
  let output = "";
@@ -591135,6 +591416,7 @@ var init_personaplex = __esm({
591135
591416
  init_render();
591136
591417
  init_daemon_registry();
591137
591418
  init_typed_node_events();
591419
+ init_dist5();
591138
591420
  WEIGHT_REPOS = {
591139
591421
  original: { repo: "nvidia/personaplex-7b-v1", file: "model.safetensors", sizeGB: 15.6, needsToken: true },
591140
591422
  nf4: { repo: "cudabenchmarktest/personaplex-7b-nf4", file: "model-nf4.safetensors", sizeGB: 4.1, needsToken: false },
@@ -599715,6 +599997,11 @@ import {
599715
599997
  spawn as nodeSpawn
599716
599998
  } from "node:child_process";
599717
599999
  import { createRequire as createRequire6 } from "node:module";
600000
+ function voicePythonEnv(extra = {}) {
600001
+ const env2 = { ...process.env, ...extra };
600002
+ applyMediaCudaDeviceFilterToEnv(env2, "tts");
600003
+ return env2;
600004
+ }
599718
600005
  function sanitizeForTTS(text) {
599719
600006
  return text.replace(/^#{1,6}\s+/gm, "").replace(/\*{1,3}([^*]+)\*{1,3}/g, "$1").replace(/_{1,3}([^_]+)_{1,3}/g, "$1").replace(/~~([^~]+)~~/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/```[\s\S]*?```/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/^[\s]*[-*+]\s+/gm, "").replace(/^[\s]*\d+\.\s+/gm, "").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}$/gm, "").replace(/\[[ xX]\]\s*/g, "").replace(/[\u{1F600}-\u{1F64F}]/gu, "").replace(/[\u{1F300}-\u{1F5FF}]/gu, "").replace(/[\u{1F680}-\u{1F6FF}]/gu, "").replace(/[\u{1F1E0}-\u{1F1FF}]/gu, "").replace(/[\u{2600}-\u{26FF}]/gu, "").replace(/[\u{2700}-\u{27BF}]/gu, "").replace(/[\u{FE00}-\u{FE0F}]/gu, "").replace(/[\u{1F900}-\u{1F9FF}]/gu, "").replace(/[\u{1FA00}-\u{1FA6F}]/gu, "").replace(/[\u{1FA70}-\u{1FAFF}]/gu, "").replace(/[\u{200D}]/gu, "").replace(/[\u{20E3}]/gu, "").replace(/[✓✔✗✘✕✖⚠️⏸⏹⏵●○◆◇■□▪▫►▼▲◀⬆⬇⬅➡↑↓←→⇐⇒⇑⇓]/g, "").replace(/[─━│┃┌┐└┘├┤┬┴┼╔╗╚╝╠╣╦╩╬⎿⎾▕▏⏐░▒▓█⠀-⣿]/g, "").replace(/\s{2,}/g, " ").trim();
599720
600007
  }
@@ -600620,6 +600907,7 @@ var init_voice = __esm({
600620
600907
  init_typed_node_events();
600621
600908
  init_render();
600622
600909
  init_daemon_registry();
600910
+ init_dist5();
600623
600911
  VOICE_MODELS = {
600624
600912
  glados: {
600625
600913
  id: "glados",
@@ -602134,7 +602422,8 @@ except Exception as exc:
602134
602422
  return new Promise((resolve59, reject) => {
602135
602423
  const proc = nodeSpawn("sh", ["-c", command], {
602136
602424
  stdio: ["ignore", "pipe", "pipe"],
602137
- cwd: tmpdir20()
602425
+ cwd: tmpdir20(),
602426
+ env: voicePythonEnv()
602138
602427
  });
602139
602428
  let stdout = "";
602140
602429
  let stderr = "";
@@ -602909,7 +603198,7 @@ if __name__ == '__main__':
602909
603198
  const venvPy = luxttsVenvPy2();
602910
603199
  if (!existsSync109(venvPy)) return false;
602911
603200
  return new Promise((resolve59) => {
602912
- const env2 = { ...process.env, LUXTTS_REPO_PATH: luxttsRepoDir2() };
603201
+ const env2 = voicePythonEnv({ LUXTTS_REPO_PATH: luxttsRepoDir2() });
602913
603202
  const daemon = nodeSpawn(venvPy, [luxttsInferScript2()], {
602914
603203
  stdio: ["pipe", "pipe", "pipe"],
602915
603204
  cwd: tmpdir20(),
@@ -625604,6 +625893,146 @@ var init_telegram_stats_menu = __esm({
625604
625893
  }
625605
625894
  });
625606
625895
 
625896
+ // packages/cli/src/tui/telegram-command-menu.ts
625897
+ function isBareTelegramGenerativeCommand(input) {
625898
+ const trimmed = input.trim();
625899
+ if (!trimmed.startsWith("/")) return false;
625900
+ const parts = trimmed.split(/\s+/);
625901
+ const name10 = (parts[0] ?? "").slice(1).split("@")[0]?.toLowerCase() ?? "";
625902
+ return parts.length === 1 && GENERATIVE_COMMANDS.has(name10);
625903
+ }
625904
+ function buildTelegramCommandMenuItems(scope) {
625905
+ const commands = listCommandRegistry({ includePlanned: false }).filter((cmd) => cmd.implementationStatus === "implemented").filter((cmd) => scope === "admin" || ["help", "start"].includes(cmd.name));
625906
+ const seen = /* @__PURE__ */ new Set();
625907
+ const items = [];
625908
+ for (const cmd of commands) {
625909
+ const signature = cmd.signatures[0]?.signature;
625910
+ if (!signature || seen.has(cmd.name)) continue;
625911
+ seen.add(cmd.name);
625912
+ items.push({
625913
+ label: `/${cmd.name}`,
625914
+ command: `/${cmd.name}`,
625915
+ description: cmd.signatures[0]?.description ?? signature,
625916
+ adminOnly: scope === "admin"
625917
+ });
625918
+ }
625919
+ return items.sort((a2, b) => a2.label.localeCompare(b.label));
625920
+ }
625921
+ function buildTelegramGenerativeMenuItems(commandName) {
625922
+ const name10 = commandName.replace(/^\//, "").toLowerCase();
625923
+ if (!GENERATIVE_COMMANDS.has(name10)) return [];
625924
+ const title = name10[0].toUpperCase() + name10.slice(1);
625925
+ return [
625926
+ { label: `${title} models`, command: `/${name10} list`, description: `List available ${name10} models and hardware fit.` },
625927
+ { label: `${title} setup`, command: `/${name10} setup`, description: `Show setup commands for the ${name10} backend.` }
625928
+ ];
625929
+ }
625930
+ function encodeTelegramCommandMenuCallback(action, value2) {
625931
+ const data = `${CALLBACK_PREFIX2}:${action[0]}:${value2}`;
625932
+ return Buffer.byteLength(data, "utf8") <= MAX_CALLBACK_DATA_BYTES ? data : data.slice(0, MAX_CALLBACK_DATA_BYTES);
625933
+ }
625934
+ function decodeTelegramCommandMenuCallback(data) {
625935
+ const parts = data.split(":");
625936
+ if (parts.length !== 3 || parts[0] !== CALLBACK_PREFIX2) return null;
625937
+ const action = parts[1] === "p" ? "page" : parts[1] === "r" ? "run" : parts[1] === "c" ? "close" : null;
625938
+ if (!action) return null;
625939
+ return { action, value: parts[2] ?? "" };
625940
+ }
625941
+ function renderTelegramCommandMenu(state) {
625942
+ const totalPages = Math.max(1, Math.ceil(state.items.length / PAGE_SIZE2));
625943
+ const page2 = Math.max(0, Math.min(state.page, totalPages - 1));
625944
+ const start2 = page2 * PAGE_SIZE2;
625945
+ const visible = state.items.slice(start2, start2 + PAGE_SIZE2);
625946
+ const title = state.kind === "generative" ? "Generative command" : "Commands";
625947
+ const scope = state.scope === "admin" ? "admin" : "public";
625948
+ const lines = [
625949
+ `<b>${escapeHTML3(title)}</b>`,
625950
+ `<i>${escapeHTML3(scope)} scope - page ${page2 + 1}/${totalPages}</i>`,
625951
+ "",
625952
+ ...visible.flatMap((item) => [
625953
+ `<code>${escapeHTML3(item.command)}</code>`,
625954
+ escapeHTML3(item.description)
625955
+ ])
625956
+ ];
625957
+ const keyboard = visible.map((item, offset) => [{
625958
+ text: item.label.slice(0, 32),
625959
+ callback_data: encodeTelegramCommandMenuCallback("run", start2 + offset)
625960
+ }]);
625961
+ const nav = [];
625962
+ nav.push({ text: "Close", callback_data: encodeTelegramCommandMenuCallback("close", 0) });
625963
+ if (page2 > 0) nav.push({ text: "Prev", callback_data: encodeTelegramCommandMenuCallback("page", page2 - 1) });
625964
+ nav.push({ text: `${page2 + 1}/${totalPages}`, callback_data: encodeTelegramCommandMenuCallback("page", page2) });
625965
+ if (page2 < totalPages - 1) nav.push({ text: "Next", callback_data: encodeTelegramCommandMenuCallback("page", page2 + 1) });
625966
+ keyboard.push(nav);
625967
+ return { text: lines.join("\n"), reply_markup: { inline_keyboard: keyboard } };
625968
+ }
625969
+ function handleTelegramCommandMenuCallback(data, state, now = Date.now()) {
625970
+ const decoded = decodeTelegramCommandMenuCallback(data);
625971
+ if (!decoded) return null;
625972
+ if (state.expiresAt <= now) return null;
625973
+ if (decoded.action === "close") return { close: true };
625974
+ if (decoded.action === "page") {
625975
+ const totalPages = Math.max(1, Math.ceil(state.items.length / PAGE_SIZE2));
625976
+ const page2 = Math.max(0, Math.min(Number.parseInt(decoded.value, 10) || 0, totalPages - 1));
625977
+ const newState = { ...state, page: page2 };
625978
+ return { newState, render: renderTelegramCommandMenu(newState) };
625979
+ }
625980
+ const index = Number.parseInt(decoded.value, 10);
625981
+ const item = Number.isFinite(index) ? state.items[index] : void 0;
625982
+ return item ? { command: item.command } : null;
625983
+ }
625984
+ function escapeHTML3(text) {
625985
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
625986
+ }
625987
+ var CALLBACK_PREFIX2, PAGE_SIZE2, TTL_MS, MAX_CALLBACK_DATA_BYTES, GENERATIVE_COMMANDS, TelegramCommandMenuStateStore;
625988
+ var init_telegram_command_menu = __esm({
625989
+ "packages/cli/src/tui/telegram-command-menu.ts"() {
625990
+ "use strict";
625991
+ init_command_registry();
625992
+ CALLBACK_PREFIX2 = "ocm";
625993
+ PAGE_SIZE2 = 8;
625994
+ TTL_MS = 10 * 60 * 1e3;
625995
+ MAX_CALLBACK_DATA_BYTES = 64;
625996
+ GENERATIVE_COMMANDS = /* @__PURE__ */ new Set(["image", "video", "sound", "music"]);
625997
+ TelegramCommandMenuStateStore = class {
625998
+ states = /* @__PURE__ */ new Map();
625999
+ key(chatId, messageId) {
626000
+ return `${chatId}:${messageId}`;
626001
+ }
626002
+ create(input, now = Date.now()) {
626003
+ return {
626004
+ ...input,
626005
+ createdAt: now,
626006
+ expiresAt: now + TTL_MS
626007
+ };
626008
+ }
626009
+ set(state) {
626010
+ this.states.set(this.key(state.chatId, state.messageId), state);
626011
+ }
626012
+ get(chatId, messageId, now = Date.now()) {
626013
+ const state = this.states.get(this.key(chatId, messageId));
626014
+ if (!state) return void 0;
626015
+ if (state.expiresAt <= now) {
626016
+ this.delete(chatId, messageId);
626017
+ return void 0;
626018
+ }
626019
+ return state;
626020
+ }
626021
+ delete(chatId, messageId) {
626022
+ this.states.delete(this.key(chatId, messageId));
626023
+ }
626024
+ prune(now = Date.now()) {
626025
+ for (const [key, state] of this.states) {
626026
+ if (state.expiresAt <= now) this.states.delete(key);
626027
+ }
626028
+ }
626029
+ clear() {
626030
+ this.states.clear();
626031
+ }
626032
+ };
626033
+ }
626034
+ });
626035
+
625607
626036
  // packages/cli/src/tui/telegram-creative-tools.ts
625608
626037
  import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes23 } from "node:crypto";
625609
626038
  import {
@@ -630996,6 +631425,7 @@ var init_telegram_bridge = __esm({
630996
631425
  init_command_registry();
630997
631426
  init_telegram_help_menu();
630998
631427
  init_telegram_stats_menu();
631428
+ init_telegram_command_menu();
630999
631429
  init_scoped_personality();
631000
631430
  init_voice_soul();
631001
631431
  init_telegram_creative_tools();
@@ -631606,6 +632036,8 @@ Telegram link integrity contract:
631606
632036
  statsMenuTimers = null;
631607
632037
  /** Prune expired stats menu states every 5 minutes */
631608
632038
  statsMenuPruneTimer = null;
632039
+ /** Telegram-native command and generative command menus */
632040
+ telegramCommandMenuStates = new TelegramCommandMenuStateStore();
631609
632041
  /** Command handler for admin DM slash commands (wired from interactive.ts) */
631610
632042
  commandHandler = null;
631611
632043
  /** Callback fired after a Telegram user completes the TUI-only admin auth challenge */
@@ -631938,6 +632370,10 @@ Telegram link integrity contract:
631938
632370
  const name10 = this.telegramSlashName(input);
631939
632371
  return name10 === "help" || name10 === "h" || name10 === "commands" || name10 === "cmds";
631940
632372
  }
632373
+ isTelegramCommandsMenuCommand(input) {
632374
+ const name10 = this.telegramSlashName(input);
632375
+ return name10 === "commands" || name10 === "cmds";
632376
+ }
631941
632377
  isTelegramStatsCommand(input) {
631942
632378
  const name10 = this.telegramSlashName(input);
631943
632379
  return name10 === "stats" || name10 === "metrics";
@@ -632531,6 +632967,49 @@ ${message2}`)
632531
632967
  this.helpMenuTimers.startTimer(state);
632532
632968
  }
632533
632969
  }
632970
+ async replyWithTelegramCommandMenu(msg, isAdmin, kind, commandName) {
632971
+ const scope = isAdmin ? "admin" : "public";
632972
+ const items = kind === "generative" ? buildTelegramGenerativeMenuItems(commandName ?? "") : buildTelegramCommandMenuItems(scope);
632973
+ if (items.length === 0) {
632974
+ await this.replyToTelegramMessage(msg, "No Telegram command menu entries are available.");
632975
+ return;
632976
+ }
632977
+ if (msg.guestQueryId || !isAdmin) {
632978
+ const lines = items.slice(0, 24).map((item) => `${item.command} - ${item.description}`);
632979
+ const text = ["Available commands:", "", ...lines].join("\n");
632980
+ if (msg.guestQueryId) {
632981
+ await this.answerGuestQuery(msg.guestQueryId, text);
632982
+ } else {
632983
+ await this.replyToTelegramMessage(msg, text);
632984
+ }
632985
+ return;
632986
+ }
632987
+ const previewState = this.telegramCommandMenuStates.create({
632988
+ chatId: msg.chatId,
632989
+ messageId: 0,
632990
+ invokerMessageId: msg.messageId,
632991
+ fromUserId: msg.fromUserId ?? 0,
632992
+ scope,
632993
+ kind,
632994
+ page: 0,
632995
+ items
632996
+ });
632997
+ const menu = renderTelegramCommandMenu(previewState);
632998
+ const sent = await this.apiCall("sendMessage", {
632999
+ chat_id: msg.chatId,
633000
+ text: menu.text,
633001
+ parse_mode: "HTML",
633002
+ reply_markup: JSON.stringify(menu.reply_markup),
633003
+ ...msg.chatType !== "private" ? { reply_to_message_id: msg.messageId } : {}
633004
+ });
633005
+ if (sent.ok && sent.result?.message_id) {
633006
+ this.telegramCommandMenuStates.prune();
633007
+ this.telegramCommandMenuStates.set({
633008
+ ...previewState,
633009
+ messageId: sent.result.message_id
633010
+ });
633011
+ }
633012
+ }
632534
633013
  collectSessionMetricsSnapshot() {
632535
633014
  if (this._metricsProvider) {
632536
633015
  try {
@@ -637339,6 +637818,7 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
637339
637818
  this.telegramActiveWorkGenerations.clear();
637340
637819
  this.telegramActiveWorkStartedAtMs.clear();
637341
637820
  this.telegramAdminLivePanels.clear();
637821
+ this.telegramCommandMenuStates.clear();
637342
637822
  this.flushTelegramViewWrites();
637343
637823
  this.flushTelegramTuiWrites();
637344
637824
  this.telegramActiveInferences.clear();
@@ -637717,6 +638197,10 @@ ${summary}` : ""
637717
638197
  return;
637718
638198
  }
637719
638199
  const isAdmin = this.isAdminUser(msg);
638200
+ if (msg.text.trim().startsWith("/") && this.isTelegramCommandsMenuCommand(normalizedCommandText)) {
638201
+ await this.replyWithTelegramCommandMenu(msg, isAdmin, "commands");
638202
+ return;
638203
+ }
637720
638204
  if (msg.text.trim().startsWith("/") && this.isTelegramHelpCommand(normalizedCommandText)) {
637721
638205
  await this.replyWithTelegramHelp(msg, isAdmin);
637722
638206
  return;
@@ -637751,6 +638235,10 @@ ${summary}` : ""
637751
638235
  const toolContext = this.resolveToolContext(msg, isAdmin);
637752
638236
  const isAdminDM = toolContext === "telegram-admin-dm";
637753
638237
  const sessionKey = this.sessionKeyForMessage(msg);
638238
+ if (isAdminDM && isBareTelegramGenerativeCommand(normalizedCommandText)) {
638239
+ await this.replyWithTelegramCommandMenu(msg, isAdmin, "generative", telegramSlash);
638240
+ return;
638241
+ }
637754
638242
  if (msg.text.trim().startsWith("/") && TELEGRAM_REMINDER_SLASH_COMMANDS.has(telegramSlash)) {
637755
638243
  await this.handleTelegramReminderSlash(msg, normalizedCommandText, toolContext);
637756
638244
  return;
@@ -640485,6 +640973,90 @@ Scoped workspace: ${scopedRoot}`,
640485
640973
  return Boolean(result.ok);
640486
640974
  }
640487
640975
  async handleTelegramCallbackQuery(callback) {
640976
+ const commandMenuDecoded = decodeTelegramCommandMenuCallback(callback.data);
640977
+ if (commandMenuDecoded) {
640978
+ let answerText2 = "";
640979
+ let alert2 = false;
640980
+ let answered = false;
640981
+ try {
640982
+ const chatId = callback.chatId;
640983
+ const messageId = callback.messageId;
640984
+ if (!chatId || !messageId) {
640985
+ answerText2 = "Cannot identify menu message.";
640986
+ alert2 = true;
640987
+ return;
640988
+ }
640989
+ const menuState = this.telegramCommandMenuStates.get(chatId, messageId);
640990
+ if (!menuState) {
640991
+ answerText2 = "This command menu expired. Send /commands for a fresh one.";
640992
+ alert2 = true;
640993
+ return;
640994
+ }
640995
+ const isAdmin = this.isAdminActor(callback.fromUserId, callback.username);
640996
+ if (callback.fromUserId !== menuState.fromUserId && !isAdmin) {
640997
+ answerText2 = "Only the user who opened this menu can use it.";
640998
+ alert2 = true;
640999
+ return;
641000
+ }
641001
+ if (!isAdmin) {
641002
+ answerText2 = "That command requires Telegram admin authentication.";
641003
+ alert2 = true;
641004
+ return;
641005
+ }
641006
+ const result = handleTelegramCommandMenuCallback(callback.data, menuState);
641007
+ if (!result) {
641008
+ answerText2 = "Unknown or expired command menu action.";
641009
+ alert2 = true;
641010
+ return;
641011
+ }
641012
+ if (result.close) {
641013
+ this.telegramCommandMenuStates.delete(chatId, messageId);
641014
+ await this.apiCall("deleteMessage", { chat_id: chatId, message_id: messageId }).catch(() => {
641015
+ });
641016
+ if (menuState.invokerMessageId) {
641017
+ await this.apiCall("deleteMessage", { chat_id: chatId, message_id: menuState.invokerMessageId }).catch(() => {
641018
+ });
641019
+ }
641020
+ answered = await this.answerCallbackQuery(callback.id).catch(() => false);
641021
+ return;
641022
+ }
641023
+ if (result.render && result.newState) {
641024
+ this.telegramCommandMenuStates.set(result.newState);
641025
+ await this.apiCall("editMessageText", {
641026
+ chat_id: chatId,
641027
+ message_id: messageId,
641028
+ text: result.render.text,
641029
+ parse_mode: "HTML",
641030
+ reply_markup: JSON.stringify(result.render.reply_markup)
641031
+ });
641032
+ return;
641033
+ }
641034
+ if (result.command) {
641035
+ if (!this.commandHandler) {
641036
+ answerText2 = "No command handler is available.";
641037
+ alert2 = true;
641038
+ return;
641039
+ }
641040
+ answered = await this.answerCallbackQuery(callback.id, `Running ${result.command}...`).catch(() => false);
641041
+ const output = await this.commandHandler(result.command);
641042
+ if (output) {
641043
+ await this.sendMessageHTML(chatId, convertMarkdownToTelegramHTML(output));
641044
+ }
641045
+ return;
641046
+ }
641047
+ } catch (err) {
641048
+ answerText2 = err instanceof Error ? err.message : String(err);
641049
+ alert2 = true;
641050
+ } finally {
641051
+ if (answered) {
641052
+ } else if (answerText2) {
641053
+ await this.answerCallbackQuery(callback.id, answerText2.slice(0, 180), alert2).catch(() => false);
641054
+ } else {
641055
+ await this.answerCallbackQuery(callback.id).catch(() => false);
641056
+ }
641057
+ }
641058
+ return;
641059
+ }
640488
641060
  const helpDecoded = decodeHelpCallback(callback.data);
640489
641061
  if (helpDecoded) {
640490
641062
  let answerText2 = "";
@@ -673904,14 +674476,24 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
673904
674476
  telegramBridge.setCommandHandler(async (input) => {
673905
674477
  const captured = [];
673906
674478
  const origWrite = process.stdout.write;
673907
- process.stdout.write = function(chunk, ..._args) {
674479
+ process.stdout.write = function(chunk, ...args) {
673908
674480
  if (typeof chunk === "string") {
673909
674481
  captured.push(chunk);
674482
+ } else if (Buffer.isBuffer(chunk)) {
674483
+ captured.push(chunk.toString("utf8"));
673910
674484
  }
674485
+ const cb = args.find((arg) => typeof arg === "function");
674486
+ if (cb) cb();
673911
674487
  return true;
673912
674488
  };
673913
674489
  try {
673914
- const result = await handleSlashCommand(input, commandCtx);
674490
+ const result = await runWithNonInteractiveSelectSurface(
674491
+ () => handleSlashCommand(input, commandCtx),
674492
+ {
674493
+ maxItems: 24,
674494
+ hint: "(Telegram: interactive menu shown as text; use concrete slash arguments or open the TUI to select)"
674495
+ }
674496
+ );
673915
674497
  process.stdout.write = origWrite;
673916
674498
  if (statusBar.isActive) statusBar.handleResize();
673917
674499
  if (result === "exit") {
@@ -673925,7 +674507,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
673925
674507
  return `Skill invoked: ${result.name}`;
673926
674508
  }
673927
674509
  const raw = captured.join("");
673928
- const clean5 = raw.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B[()][A-Z0-9]/g, "").replace(/\x1B\[?\??[0-9;]*[a-zA-Z]/g, "").replace(/\x1B/g, "").replace(/[─━│┃┌┐└┘├┤┬┴┼╔╗╚╝╠╣╦╩╬⎿⎾▕▏⏐]/g, "").replace(/\n{3,}/g, "\n\n").trim();
674510
+ const clean5 = raw.replace(/\x1B(?:\[[\d;?]*[ -/]*[@-~]|\][^\x07\x1B]*(?:\x07|\x1B\\)?|[@-Z\\-_])/g, "").replace(/\x1B/g, "").replace(/[─━│┃┌┐└┘├┤┬┴┼╔╗╚╝╠╣╦╩╬⎿⎾▕▏⏐]/g, "").replace(/\n{3,}/g, "\n\n").trim();
673929
674511
  if (!clean5) return null;
673930
674512
  return clean5.length > 3900 ? clean5.slice(0, 3900) + "\n..." : clean5;
673931
674513
  } catch (err) {