omnius 1.0.187 → 1.0.189

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
@@ -1412,6 +1412,18 @@ var init_tool_executor = __esm({
1412
1412
  import { EventEmitter } from "node:events";
1413
1413
  import { totalmem, freemem } from "node:os";
1414
1414
  import { exec } from "node:child_process";
1415
+ function dedupeLoadedModels(models) {
1416
+ const seen = /* @__PURE__ */ new Set();
1417
+ const out = [];
1418
+ for (const model of models) {
1419
+ const key = `${model.host}:${model.name}`;
1420
+ if (seen.has(key))
1421
+ continue;
1422
+ seen.add(key);
1423
+ out.push(model);
1424
+ }
1425
+ return out;
1426
+ }
1415
1427
  function ramSnapshotMB() {
1416
1428
  const total = Math.round(totalmem() / (1024 * 1024));
1417
1429
  const free = Math.round(freemem() / (1024 * 1024));
@@ -1690,6 +1702,104 @@ var init_model_broker = __esm({
1690
1702
  this.emit("rejected", spec, reason);
1691
1703
  return { kind: "reject", reason };
1692
1704
  }
1705
+ /**
1706
+ * Acquire a short-lived load lease for media/subprocess generation.
1707
+ *
1708
+ * Media generation often needs to temporarily free VRAM occupied by Ollama
1709
+ * chat models. This helper refreshes Ollama state, asks the broker what must
1710
+ * be evicted, unloads those Ollama models with keep_alive=0, and returns a
1711
+ * lease whose release() clears transient inflight state, unloads any
1712
+ * Ollama-hosted requested model, and warms the evicted Ollama models again.
1713
+ */
1714
+ async acquireTransientModelLoad(spec, options2 = {}) {
1715
+ const reason = options2.reason ?? `${spec.domain}-transient-load`;
1716
+ const evictedModels = [];
1717
+ let gpuIndex = null;
1718
+ let admitted = false;
1719
+ await this.pollOnce().catch(() => {
1720
+ });
1721
+ for (let attempt = 0; attempt < 4; attempt++) {
1722
+ const decision2 = await this.ensureModelLoadable(spec);
1723
+ if (decision2.kind === "wait-for-inflight") {
1724
+ const waited = await decision2.promise.catch((err) => ({
1725
+ kind: "reject",
1726
+ reason: err instanceof Error ? err.message : String(err)
1727
+ }));
1728
+ if (waited.kind === "ok") {
1729
+ gpuIndex = waited.gpuIndex ?? null;
1730
+ admitted = true;
1731
+ break;
1732
+ }
1733
+ if (waited.kind === "evict") {
1734
+ for (const target of waited.evictTargets) {
1735
+ if (await this.evict(target.host, target.name, reason))
1736
+ evictedModels.push(target);
1737
+ }
1738
+ await this.pollOnce().catch(() => {
1739
+ });
1740
+ continue;
1741
+ }
1742
+ if (waited.kind === "degrade")
1743
+ return waited;
1744
+ if (waited.kind === "reject")
1745
+ return waited;
1746
+ continue;
1747
+ }
1748
+ if (decision2.kind === "ok") {
1749
+ gpuIndex = decision2.gpuIndex ?? null;
1750
+ admitted = true;
1751
+ break;
1752
+ }
1753
+ if (decision2.kind === "evict") {
1754
+ for (const target of decision2.evictTargets) {
1755
+ const before = this._loaded.get(`${target.host}:${target.name}`) ?? target;
1756
+ if (await this.evict(target.host, target.name, reason)) {
1757
+ evictedModels.push(before);
1758
+ }
1759
+ }
1760
+ await this.pollOnce().catch(() => {
1761
+ });
1762
+ continue;
1763
+ }
1764
+ if (decision2.kind === "degrade")
1765
+ return decision2;
1766
+ return decision2;
1767
+ }
1768
+ if (!admitted) {
1769
+ return {
1770
+ kind: "reject",
1771
+ reason: `could not acquire transient load lease for ${spec.host}:${spec.name} after repeated evictions`
1772
+ };
1773
+ }
1774
+ const evictedOllamaModels = dedupeLoadedModels(evictedModels.filter((m2) => m2.host === "ollama"));
1775
+ const broker = this;
1776
+ let released = false;
1777
+ return {
1778
+ kind: "ok",
1779
+ lease: {
1780
+ spec,
1781
+ gpuIndex,
1782
+ evictedModels: dedupeLoadedModels(evictedModels),
1783
+ evictedOllamaModels,
1784
+ async release() {
1785
+ if (released)
1786
+ return;
1787
+ released = true;
1788
+ broker.clearInflight(spec.host, spec.name);
1789
+ if ((options2.unloadRequestedOllama ?? true) && spec.host === "ollama") {
1790
+ await broker.unloadOllamaModel(spec.name, `${reason}-complete`).catch(() => false);
1791
+ }
1792
+ if (options2.restoreOllama !== false && evictedOllamaModels.length > 0) {
1793
+ await broker.restoreOllamaModels(evictedOllamaModels, {
1794
+ keepAlive: options2.restoreKeepAlive ?? "30m"
1795
+ });
1796
+ }
1797
+ await broker.pollOnce().catch(() => {
1798
+ });
1799
+ }
1800
+ }
1801
+ };
1802
+ }
1693
1803
  /**
1694
1804
  * Register a model that has been successfully loaded.
1695
1805
  * Callers MUST call this after a successful load so the broker can track LRU.
@@ -1743,6 +1853,66 @@ var init_model_broker = __esm({
1743
1853
  this.emit("evicted", m2, reason);
1744
1854
  return actively;
1745
1855
  }
1856
+ /** Force-unload an Ollama model even when it is not currently tracked. */
1857
+ async unloadOllamaModel(modelName, reason = "ollama-unload") {
1858
+ const key = `ollama:${modelName}`;
1859
+ const existing = this._loaded.get(key);
1860
+ const ok3 = await this.ollamaUnload(modelName).catch(() => false);
1861
+ if (existing) {
1862
+ this._loaded.delete(key);
1863
+ this.emit("evicted", existing, reason);
1864
+ }
1865
+ return ok3;
1866
+ }
1867
+ /** Best-effort warm/reload of an Ollama model after temporary eviction. */
1868
+ async warmOllamaModel(modelName, keepAlive = "30m") {
1869
+ const bodies = [
1870
+ { model: modelName, prompt: "", stream: false, keep_alive: keepAlive, options: { num_predict: 0 } },
1871
+ { model: modelName, prompt: "", stream: false, keep_alive: keepAlive, options: { num_predict: 1 } }
1872
+ ];
1873
+ for (const body of bodies) {
1874
+ try {
1875
+ const res = await fetch(`${this._ollamaBaseUrl}/api/generate`, {
1876
+ method: "POST",
1877
+ headers: { "Content-Type": "application/json" },
1878
+ body: JSON.stringify(body),
1879
+ signal: AbortSignal.timeout(12e4)
1880
+ });
1881
+ if (!res.ok)
1882
+ continue;
1883
+ await this.refreshOllamaPs().catch(() => {
1884
+ });
1885
+ return true;
1886
+ } catch {
1887
+ }
1888
+ }
1889
+ try {
1890
+ const res = await fetch(`${this._ollamaBaseUrl}/api/generate`, {
1891
+ method: "POST",
1892
+ headers: { "Content-Type": "application/json" },
1893
+ body: JSON.stringify({
1894
+ model: modelName,
1895
+ stream: false,
1896
+ keep_alive: keepAlive
1897
+ }),
1898
+ signal: AbortSignal.timeout(12e4)
1899
+ });
1900
+ if (!res.ok)
1901
+ return false;
1902
+ await this.refreshOllamaPs().catch(() => {
1903
+ });
1904
+ return true;
1905
+ } catch {
1906
+ return false;
1907
+ }
1908
+ }
1909
+ /** Restore a set of previously evicted Ollama models, oldest first. */
1910
+ async restoreOllamaModels(models, options2 = {}) {
1911
+ const unique2 = dedupeLoadedModels(models.filter((m2) => m2.host === "ollama")).sort((a2, b) => a2.lastUsedAt - b.lastUsedAt);
1912
+ for (const model of unique2) {
1913
+ await this.warmOllamaModel(model.name, options2.keepAlive ?? "30m").catch(() => false);
1914
+ }
1915
+ }
1746
1916
  // ------------------------------------------------------------------
1747
1917
  // Internal — Ollama
1748
1918
  // ------------------------------------------------------------------
@@ -1885,7 +2055,7 @@ var init_model_broker = __esm({
1885
2055
  );
1886
2056
  const idle = (m2) => now - m2.lastUsedAt > this.idleEvictMs;
1887
2057
  const onTargetGpu = (m2) => req2.targetGpu === void 0 || req2.targetGpu === null ? true : m2.gpuIndex === req2.targetGpu;
1888
- const evictable = [...this._loaded.values()].filter((m2) => m2.priority <= req2.requestingPriority).filter(sameDomainOk).filter(onTargetGpu).sort((a2, b) => {
2058
+ const evictable = [...this._loaded.values()].filter((m2) => m2.priority <= req2.requestingPriority).filter(sameDomainOk).filter(onTargetGpu).filter((m2) => !this.hasActiveSlotForModel(m2)).sort((a2, b) => {
1889
2059
  const aIdle = idle(a2) ? 0 : 1;
1890
2060
  const bIdle = idle(b) ? 0 : 1;
1891
2061
  if (aIdle !== bIdle)
@@ -1931,6 +2101,13 @@ var init_model_broker = __esm({
1931
2101
  n2++;
1932
2102
  return n2;
1933
2103
  }
2104
+ hasActiveSlotForModel(model) {
2105
+ for (const slot of this._activeSlots.values()) {
2106
+ if (slot.model === model.name)
2107
+ return true;
2108
+ }
2109
+ return false;
2110
+ }
1934
2111
  // ------------------------------------------------------------------
1935
2112
  // Internal — fallback resolution
1936
2113
  // ------------------------------------------------------------------
@@ -22761,6 +22938,17 @@ function evictModelsToFreeSpace(args) {
22761
22938
  writeMeta(meta);
22762
22939
  return { evicted, bytesFreed, finalFreeBytes: disk.freeBytes };
22763
22940
  }
22941
+ function estimateReclaimableCacheBytes(keepRepos) {
22942
+ const keep = new Set(keepRepos ?? []);
22943
+ let total = 0;
22944
+ for (const entry of readMeta().entries) {
22945
+ if (keep.has(entry.repo))
22946
+ continue;
22947
+ const measured = measureRepoCacheBytes(entry.repo);
22948
+ total += Math.max(0, measured || entry.sizeBytes || 0);
22949
+ }
22950
+ return total;
22951
+ }
22764
22952
  function ensureDiskSpaceForDownload(args) {
22765
22953
  ensureUnifiedCacheDirs();
22766
22954
  const safetyMargin = args.safetyMarginBytes ?? 1 * 1024 ** 3;
@@ -22769,6 +22957,10 @@ function ensureDiskSpaceForDownload(args) {
22769
22957
  if (disk.freeBytes >= target) {
22770
22958
  return { ok: true, evicted: [], freeBytes: disk.freeBytes };
22771
22959
  }
22960
+ const reclaimableBytes = estimateReclaimableCacheBytes(args.keepRepos);
22961
+ if (disk.freeBytes + reclaimableBytes < target) {
22962
+ throw new InsufficientDiskSpaceError(args.approxDownloadBytes, disk.freeBytes, disk.totalBytes, []);
22963
+ }
22772
22964
  const evictionResult = evictModelsToFreeSpace({
22773
22965
  neededBytes: args.approxDownloadBytes,
22774
22966
  keepRepos: args.keepRepos,
@@ -259208,6 +259400,12 @@ function imageCandidateFor(model, requestedBackend) {
259208
259400
  preset: getImageGenerationPreset(resolved)
259209
259401
  };
259210
259402
  }
259403
+ function imageCandidateHost(candidate) {
259404
+ return candidate.backend === "ollama" ? "ollama" : "diffusers-py";
259405
+ }
259406
+ function imageCandidateEstimatedVramMB(candidate) {
259407
+ return candidate.preset?.minVramGB !== void 0 ? Math.ceil(candidate.preset.minVramGB * 1024) : void 0;
259408
+ }
259211
259409
  function imageGenerationFallbackCandidates(requestedModel, requestedBackend, allowFallback = true) {
259212
259410
  const ladder = imageGenerationQualityLadder();
259213
259411
  const candidates = [];
@@ -259511,9 +259709,15 @@ function annotateImageFallbackSuccess(result, failed, winner) {
259511
259709
  ...failed.map((attempt, index) => ` ${formatImageAttempt(attempt.candidate, attempt.reason, index)}`),
259512
259710
  ""
259513
259711
  ].join("\n");
259712
+ const llmPrefix = [
259713
+ `Fallback ladder used ${winner.model} [${winner.backend}] after ${failed.length} failed attempt(s).`,
259714
+ ...failed.map((attempt, index) => formatImageAttempt(attempt.candidate, attempt.reason, index))
259715
+ ].join("\n");
259514
259716
  return {
259515
259717
  ...result,
259516
- output: prefix + result.output
259718
+ output: prefix + result.output,
259719
+ llmContent: result.llmContent ? `${llmPrefix}
259720
+ ${result.llmContent}` : result.llmContent
259517
259721
  };
259518
259722
  }
259519
259723
  function parseRunnerJson(stdout) {
@@ -260321,6 +260525,45 @@ if __name__ == "__main__":
260321
260525
  this.lastProgressAt = now;
260322
260526
  this.progressHandler(event);
260323
260527
  }
260528
+ async acquireTransientLoadLease(args) {
260529
+ if (!args.candidate)
260530
+ return null;
260531
+ const broker = getModelBroker();
260532
+ const decision2 = await broker.acquireTransientModelLoad({
260533
+ name: args.candidate.model,
260534
+ domain: "image-gen",
260535
+ host: imageCandidateHost(args.candidate),
260536
+ owner: "image-generate-tool",
260537
+ estimatedVramMB: imageCandidateEstimatedVramMB(args.candidate)
260538
+ }, {
260539
+ reason: args.reason,
260540
+ restoreOllama: true,
260541
+ unloadRequestedOllama: true
260542
+ });
260543
+ if (decision2.kind === "reject") {
260544
+ return {
260545
+ success: false,
260546
+ output: "",
260547
+ error: `Image generation blocked by resource broker: ${decision2.reason}`,
260548
+ durationMs: performance.now() - args.start
260549
+ };
260550
+ }
260551
+ if (decision2.kind === "degrade") {
260552
+ return {
260553
+ success: false,
260554
+ output: "",
260555
+ error: `Image generation needs a broker fallback (${decision2.fallback.name}), but image candidate fallback must be selected by the image ladder: ${decision2.reason}`,
260556
+ durationMs: performance.now() - args.start
260557
+ };
260558
+ }
260559
+ if (decision2.lease.evictedOllamaModels.length > 0) {
260560
+ this.emitProgress({
260561
+ stage: "setup",
260562
+ message: `Temporarily unloaded ${decision2.lease.evictedOllamaModels.length} Ollama inference model(s) to free VRAM for image generation`
260563
+ });
260564
+ }
260565
+ return { lease: decision2.lease, gpuIndex: decision2.lease.gpuIndex };
260566
+ }
260324
260567
  async execute(args) {
260325
260568
  const start2 = performance.now();
260326
260569
  const action = String(args["action"] ?? "generate");
@@ -260363,33 +260606,6 @@ if __name__ == "__main__":
260363
260606
  const requestedBackend = args["backend"] ? String(args["backend"]) : this.defaultBackend;
260364
260607
  const seed = optionalNumberArg(args["seed"]);
260365
260608
  const candidates = imageGenerationFallbackCandidates(requestedModel, requestedBackend, generationFallbackEnabled(args));
260366
- const broker = getModelBroker();
260367
- const firstCandidate = candidates[0];
260368
- let brokerGpuIndex = null;
260369
- if (firstCandidate) {
260370
- const decision2 = await broker.ensureModelLoadable({
260371
- name: firstCandidate.model,
260372
- domain: "image-gen",
260373
- host: firstCandidate.backend === "ollama" ? "ollama" : "diffusers-py",
260374
- owner: "image-generate-tool"
260375
- });
260376
- if (decision2.kind === "evict") {
260377
- for (const target of decision2.evictTargets) {
260378
- await broker.evict(target.host, target.name, "image-gen-needs-room");
260379
- }
260380
- brokerGpuIndex = decision2.gpuIndex ?? null;
260381
- } else if (decision2.kind === "ok") {
260382
- brokerGpuIndex = decision2.gpuIndex ?? null;
260383
- } else if (decision2.kind === "reject") {
260384
- return {
260385
- success: false,
260386
- output: "",
260387
- error: `Image generation blocked by resource broker: ${decision2.reason}`,
260388
- durationMs: performance.now() - start2
260389
- };
260390
- }
260391
- }
260392
- this._brokerGpuIndex = brokerGpuIndex;
260393
260609
  try {
260394
260610
  return await this.generateCandidateLadder({ candidates, prompt, args, seed, start: start2 });
260395
260611
  } catch (err) {
@@ -260408,7 +260624,30 @@ if __name__ == "__main__":
260408
260624
  stage: "setup",
260409
260625
  message: `Preparing image model ${candidate.model} (${candidate.backend}) [${index + 1}/${args.candidates.length}]`
260410
260626
  });
260411
- const result = candidate.backend === "ollama" ? await this.prewarmOllama({ model: candidate.model, start: args.start }) : candidate.backend === "sdcpp" ? await this.prewarmSdCpp({ model: candidate.model, start: args.start, python: args.args["python"] }) : await this.prewarmDiffusers({ model: candidate.model, start: args.start, python: args.args["python"] });
260627
+ const leaseDecision = await this.acquireTransientLoadLease({
260628
+ candidate,
260629
+ reason: "image-prewarm-needs-room",
260630
+ start: args.start
260631
+ });
260632
+ if (leaseDecision && "success" in leaseDecision) {
260633
+ failed.push({ candidate, reason: summarizeToolResult(leaseDecision) });
260634
+ if (index < args.candidates.length - 1) {
260635
+ this.emitProgress({
260636
+ stage: "setup",
260637
+ message: `${candidate.model} did not fit current resources; trying ${args.candidates[index + 1].model}`
260638
+ });
260639
+ }
260640
+ continue;
260641
+ }
260642
+ const lease = leaseDecision?.lease;
260643
+ this._brokerGpuIndex = leaseDecision?.gpuIndex ?? null;
260644
+ let result;
260645
+ try {
260646
+ result = candidate.backend === "ollama" ? await this.prewarmOllama({ model: candidate.model, start: args.start }) : candidate.backend === "sdcpp" ? await this.prewarmSdCpp({ model: candidate.model, start: args.start, python: args.args["python"] }) : await this.prewarmDiffusers({ model: candidate.model, start: args.start, python: args.args["python"] });
260647
+ } finally {
260648
+ await lease?.release();
260649
+ this._brokerGpuIndex = null;
260650
+ }
260412
260651
  if (result.success)
260413
260652
  return annotateImageFallbackSuccess(result, failed, candidate);
260414
260653
  failed.push({ candidate, reason: summarizeToolResult(result) });
@@ -260447,7 +260686,30 @@ if __name__ == "__main__":
260447
260686
  message: `Using image model ${candidate.model} (${candidate.backend}) [${index + 1}/${args.candidates.length}]`
260448
260687
  });
260449
260688
  const promptForCandidate = expansionEnabled ? await this.expandPromptForCandidate(args.prompt, candidate, index, args.candidates.length) : args.prompt;
260450
- const result = candidate.backend === "ollama" ? await this.generateWithOllama({ prompt: promptForCandidate, model: candidate.model, width, height, steps, start: args.start }) : candidate.backend === "sdcpp" ? await this.generateWithSdCpp({ prompt: promptForCandidate, model: candidate.model, width, height, steps, seed: args.seed, start: args.start, python: args.args["python"] }) : await this.generateWithDiffusers({ prompt: promptForCandidate, model: candidate.model, width, height, steps, guidance, seed: args.seed, start: args.start, python: args.args["python"] });
260689
+ const leaseDecision = await this.acquireTransientLoadLease({
260690
+ candidate,
260691
+ reason: "image-gen-needs-room",
260692
+ start: args.start
260693
+ });
260694
+ if (leaseDecision && "success" in leaseDecision) {
260695
+ failed.push({ candidate, reason: summarizeToolResult(leaseDecision) });
260696
+ if (index < args.candidates.length - 1) {
260697
+ this.emitProgress({
260698
+ stage: "setup",
260699
+ message: `${candidate.model} did not fit current resources; falling back to ${args.candidates[index + 1].model}`
260700
+ });
260701
+ }
260702
+ continue;
260703
+ }
260704
+ const lease = leaseDecision?.lease;
260705
+ this._brokerGpuIndex = leaseDecision?.gpuIndex ?? null;
260706
+ let result;
260707
+ try {
260708
+ result = candidate.backend === "ollama" ? await this.generateWithOllama({ prompt: promptForCandidate, model: candidate.model, width, height, steps, start: args.start }) : candidate.backend === "sdcpp" ? await this.generateWithSdCpp({ prompt: promptForCandidate, model: candidate.model, width, height, steps, seed: args.seed, start: args.start, python: args.args["python"] }) : await this.generateWithDiffusers({ prompt: promptForCandidate, model: candidate.model, width, height, steps, guidance, seed: args.seed, start: args.start, python: args.args["python"] });
260709
+ } finally {
260710
+ await lease?.release();
260711
+ this._brokerGpuIndex = null;
260712
+ }
260451
260713
  if (result.success) {
260452
260714
  await this.writeImageSidecar(result, {
260453
260715
  originalPrompt: args.prompt,
@@ -260671,6 +260933,17 @@ ${errText.slice(0, 1200)}`,
260671
260933
  }
260672
260934
  ensureUnifiedCacheDirs();
260673
260935
  this.emitProgress({ stage: "load", message: `Downloading/loading image model ${args.model}` });
260936
+ const runnerEnv = { ...python.env };
260937
+ if (this._brokerGpuIndex !== null) {
260938
+ if (mediaBrokerGpuIndexIsCompatible(this._brokerGpuIndex, "image", runnerEnv)) {
260939
+ runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
260940
+ } else {
260941
+ this.emitProgress({
260942
+ stage: "setup",
260943
+ message: `Broker selected CUDA GPU ${this._brokerGpuIndex}, but image CUDA filtering excluded it; using CUDA_VISIBLE_DEVICES=${runnerEnv["CUDA_VISIBLE_DEVICES"] ?? "default"}`
260944
+ });
260945
+ }
260946
+ }
260674
260947
  const result = await runProcess2(python.command, [
260675
260948
  runner,
260676
260949
  "--model",
@@ -260683,7 +260956,7 @@ ${errText.slice(0, 1200)}`,
260683
260956
  ], {
260684
260957
  cwd: this.cwd,
260685
260958
  timeoutMs: 18e5,
260686
- env: python.env,
260959
+ env: runnerEnv,
260687
260960
  progressLabel: `Downloading/loading ${args.model}`,
260688
260961
  onProgress: (event) => this.emitProgress(event)
260689
260962
  });
@@ -261735,6 +262008,18 @@ function audioCandidateFor(kind, model, requestedBackend) {
261735
262008
  preset: getAudioGenerationPreset(model, kind)
261736
262009
  };
261737
262010
  }
262011
+ function audioCandidateHost(candidate) {
262012
+ if (candidate.backend === "project")
262013
+ return null;
262014
+ if (candidate.backend === "audiocraft")
262015
+ return "audiocraft";
262016
+ if (candidate.backend === "tangoflux")
262017
+ return "tangoflux";
262018
+ return "diffusers-py";
262019
+ }
262020
+ function audioCandidateEstimatedVramMB(candidate) {
262021
+ return candidate.preset ? Math.ceil(candidate.preset.minVramGB * 1024) : void 0;
262022
+ }
261738
262023
  function audioGenerationFallbackCandidates(kind, requestedModel, requestedBackend, allowFallback = true) {
261739
262024
  const ladder = audioGenerationQualityLadder(kind);
261740
262025
  const candidates = [];
@@ -261891,9 +262176,15 @@ function annotateAudioFallbackSuccess(result, failed, winner) {
261891
262176
  ...failed.map((attempt, index) => ` ${formatAudioAttempt(attempt.candidate, attempt.reason, index)}`),
261892
262177
  ""
261893
262178
  ].join("\n");
262179
+ const llmPrefix = [
262180
+ `Fallback ladder used ${winner.model} [${winner.backend}] after ${failed.length} failed attempt(s).`,
262181
+ ...failed.map((attempt, index) => formatAudioAttempt(attempt.candidate, attempt.reason, index))
262182
+ ].join("\n");
261894
262183
  return {
261895
262184
  ...result,
261896
- output: prefix + result.output
262185
+ output: prefix + result.output,
262186
+ llmContent: result.llmContent ? `${llmPrefix}
262187
+ ${result.llmContent}` : result.llmContent
261897
262188
  };
261898
262189
  }
261899
262190
  var DEFAULT_SOUND_MODEL, DEFAULT_MUSIC_MODEL, DIFFUSERS_AUDIO_PACKAGES, TRANSFORMERS_AUDIO_PACKAGES, AUDIOCRAFT_PACKAGES, STABLE_AUDIO_PACKAGES, TANGOFLUX_PACKAGES, AUDIO_GENERATION_MODEL_PRESETS, SOUND_GENERATION_QUALITY_LADDER, MUSIC_GENERATION_QUALITY_LADDER, DIFFUSERS_AUDIO_RUNNER, AUDIOCRAFT_RUNNER, TRANSFORMERS_AUDIO_RUNNER, TANGOFLUX_RUNNER, AudioGenerateTool;
@@ -262730,6 +263021,48 @@ if __name__ == "__main__":
262730
263021
  this.lastProgressAt = now;
262731
263022
  this.progressHandler(event);
262732
263023
  }
263024
+ async acquireTransientLoadLease(args) {
263025
+ if (!args.candidate)
263026
+ return null;
263027
+ const host = audioCandidateHost(args.candidate);
263028
+ if (!host)
263029
+ return null;
263030
+ const broker = getModelBroker();
263031
+ const decision2 = await broker.acquireTransientModelLoad({
263032
+ name: args.candidate.model,
263033
+ domain: args.kind,
263034
+ host,
263035
+ owner: `audio-generate-tool/${args.kind}`,
263036
+ estimatedVramMB: audioCandidateEstimatedVramMB(args.candidate)
263037
+ }, {
263038
+ reason: args.reason,
263039
+ restoreOllama: true,
263040
+ unloadRequestedOllama: false
263041
+ });
263042
+ if (decision2.kind === "reject") {
263043
+ return {
263044
+ success: false,
263045
+ output: "",
263046
+ error: `${args.kind === "music" ? "Music" : "Sound"} generation blocked by resource broker: ${decision2.reason}`,
263047
+ durationMs: performance.now() - args.start
263048
+ };
263049
+ }
263050
+ if (decision2.kind === "degrade") {
263051
+ return {
263052
+ success: false,
263053
+ output: "",
263054
+ error: `${args.kind === "music" ? "Music" : "Sound"} generation needs a broker fallback (${decision2.fallback.name}), but audio candidate fallback must be selected by the audio ladder: ${decision2.reason}`,
263055
+ durationMs: performance.now() - args.start
263056
+ };
263057
+ }
263058
+ if (decision2.lease.evictedOllamaModels.length > 0) {
263059
+ this.emitProgress({
263060
+ stage: "setup",
263061
+ message: `Temporarily unloaded ${decision2.lease.evictedOllamaModels.length} Ollama inference model(s) to free VRAM for ${args.kind} generation`
263062
+ });
263063
+ }
263064
+ return { lease: decision2.lease, gpuIndex: decision2.lease.gpuIndex };
263065
+ }
262733
263066
  async prewarmPythonBackend(args) {
262734
263067
  const runner = await ensureAudioRunner(this.cwd, args.runnerBackend);
262735
263068
  let python;
@@ -262776,6 +263109,17 @@ if __name__ == "__main__":
262776
263109
  }
262777
263110
  ensureUnifiedCacheDirs();
262778
263111
  this.emitProgress({ stage: "load", message: `Downloading/loading ${args.kind} model ${args.model}` });
263112
+ const runnerEnv = { ...python.env };
263113
+ if (this._brokerGpuIndex !== null) {
263114
+ if (audioBrokerGpuIndexIsCompatible(this._brokerGpuIndex, runnerEnv)) {
263115
+ runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
263116
+ } else {
263117
+ this.emitProgress({
263118
+ stage: "setup",
263119
+ message: `Broker selected CUDA GPU ${this._brokerGpuIndex}, but audio CUDA filtering excluded it; using CUDA_VISIBLE_DEVICES=${runnerEnv["CUDA_VISIBLE_DEVICES"] ?? "default"}`
263120
+ });
263121
+ }
263122
+ }
262779
263123
  const result = await runProcess3(python.command, [
262780
263124
  runner,
262781
263125
  "--kind",
@@ -262792,7 +263136,7 @@ if __name__ == "__main__":
262792
263136
  ], {
262793
263137
  cwd: this.cwd,
262794
263138
  timeoutMs: 18e5,
262795
- env: python.env,
263139
+ env: runnerEnv,
262796
263140
  progressLabel: `Downloading/loading ${args.model}`,
262797
263141
  onProgress: (event) => this.emitProgress(event)
262798
263142
  });
@@ -262872,33 +263216,6 @@ if __name__ == "__main__":
262872
263216
  const candidates = audioGenerationFallbackCandidates(kind, requestedModel, requestedBackend, generationFallbackEnabled2(args));
262873
263217
  const seed = optionalNumberArg2(args["seed"]);
262874
263218
  const playback = playbackRequested(args);
262875
- const broker = getModelBroker();
262876
- const firstCandidate = candidates[0];
262877
- let brokerGpuIndex = null;
262878
- if (firstCandidate) {
262879
- const decision2 = await broker.ensureModelLoadable({
262880
- name: firstCandidate.model,
262881
- domain: kind === "music" ? "music" : "sound",
262882
- host: firstCandidate.backend === "audiocraft" ? "audiocraft" : firstCandidate.backend === "tangoflux" ? "tangoflux" : firstCandidate.backend === "transformers" ? "diffusers-py" : "diffusers-py",
262883
- owner: `audio-generate-tool/${kind}`
262884
- });
262885
- if (decision2.kind === "evict") {
262886
- for (const target of decision2.evictTargets) {
262887
- await broker.evict(target.host, target.name, `${kind}-gen-needs-room`);
262888
- }
262889
- brokerGpuIndex = decision2.gpuIndex ?? null;
262890
- } else if (decision2.kind === "ok") {
262891
- brokerGpuIndex = decision2.gpuIndex ?? null;
262892
- } else if (decision2.kind === "reject") {
262893
- return {
262894
- success: false,
262895
- output: "",
262896
- error: `${kind === "music" ? "Music" : "Sound"} generation blocked by resource broker: ${decision2.reason}`,
262897
- durationMs: performance.now() - start2
262898
- };
262899
- }
262900
- }
262901
- this._brokerGpuIndex = brokerGpuIndex;
262902
263219
  try {
262903
263220
  return await this.generateCandidateLadder({ kind, candidates, prompt, args, seed, playback, start: start2 });
262904
263221
  } catch (err) {
@@ -262918,15 +263235,39 @@ if __name__ == "__main__":
262918
263235
  stage: "setup",
262919
263236
  message: `Preparing ${args.kind} model ${candidate.model} (${candidate.backend}) [${index + 1}/${args.candidates.length}]`
262920
263237
  });
262921
- const result = candidate.backend === "project" ? this.projectProfileResult(args.kind, candidate, args.start) : await this.prewarmPythonBackend({
263238
+ const leaseDecision = await this.acquireTransientLoadLease({
262922
263239
  kind: args.kind,
262923
- backend: candidate.backend,
262924
- runnerBackend: candidate.backend,
262925
- model: candidate.model,
262926
- duration,
262927
- start: args.start,
262928
- python: args.args["python"]
262929
- });
263240
+ candidate,
263241
+ reason: `${args.kind}-prewarm-needs-room`,
263242
+ start: args.start
263243
+ });
263244
+ if (leaseDecision && "success" in leaseDecision) {
263245
+ failed.push({ candidate, reason: summarizeToolResult2(leaseDecision) });
263246
+ if (index < args.candidates.length - 1) {
263247
+ this.emitProgress({
263248
+ stage: "setup",
263249
+ message: `${candidate.model} did not fit current resources; trying ${args.candidates[index + 1].model}`
263250
+ });
263251
+ }
263252
+ continue;
263253
+ }
263254
+ const lease = leaseDecision?.lease;
263255
+ this._brokerGpuIndex = leaseDecision?.gpuIndex ?? null;
263256
+ let result;
263257
+ try {
263258
+ result = candidate.backend === "project" ? this.projectProfileResult(args.kind, candidate, args.start) : await this.prewarmPythonBackend({
263259
+ kind: args.kind,
263260
+ backend: candidate.backend,
263261
+ runnerBackend: candidate.backend,
263262
+ model: candidate.model,
263263
+ duration,
263264
+ start: args.start,
263265
+ python: args.args["python"]
263266
+ });
263267
+ } finally {
263268
+ await lease?.release();
263269
+ this._brokerGpuIndex = null;
263270
+ }
262930
263271
  if (result.success)
262931
263272
  return annotateAudioFallbackSuccess(result, failed, candidate);
262932
263273
  failed.push({ candidate, reason: summarizeToolResult2(result) });
@@ -262954,19 +263295,43 @@ if __name__ == "__main__":
262954
263295
  stage: "setup",
262955
263296
  message: `Using ${args.kind} model ${candidate.model} (${candidate.backend}) [${index + 1}/${args.candidates.length}]`
262956
263297
  });
262957
- const result = candidate.backend === "project" ? this.projectProfileResult(args.kind, candidate, args.start) : await this.generateWithPythonBackend({
263298
+ const leaseDecision = await this.acquireTransientLoadLease({
262958
263299
  kind: args.kind,
262959
- backend: candidate.backend,
262960
- runnerBackend: candidate.backend,
262961
- prompt: args.prompt,
262962
- model: candidate.model,
262963
- duration,
262964
- steps,
262965
- seed: args.seed,
262966
- playback: args.playback,
262967
- start: args.start,
262968
- python: args.args["python"]
262969
- });
263300
+ candidate,
263301
+ reason: `${args.kind}-gen-needs-room`,
263302
+ start: args.start
263303
+ });
263304
+ if (leaseDecision && "success" in leaseDecision) {
263305
+ failed.push({ candidate, reason: summarizeToolResult2(leaseDecision) });
263306
+ if (index < args.candidates.length - 1) {
263307
+ this.emitProgress({
263308
+ stage: "setup",
263309
+ message: `${candidate.model} did not fit current resources; falling back to ${args.candidates[index + 1].model}`
263310
+ });
263311
+ }
263312
+ continue;
263313
+ }
263314
+ const lease = leaseDecision?.lease;
263315
+ this._brokerGpuIndex = leaseDecision?.gpuIndex ?? null;
263316
+ let result;
263317
+ try {
263318
+ result = candidate.backend === "project" ? this.projectProfileResult(args.kind, candidate, args.start) : await this.generateWithPythonBackend({
263319
+ kind: args.kind,
263320
+ backend: candidate.backend,
263321
+ runnerBackend: candidate.backend,
263322
+ prompt: args.prompt,
263323
+ model: candidate.model,
263324
+ duration,
263325
+ steps,
263326
+ seed: args.seed,
263327
+ playback: args.playback,
263328
+ start: args.start,
263329
+ python: args.args["python"]
263330
+ });
263331
+ } finally {
263332
+ await lease?.release();
263333
+ this._brokerGpuIndex = null;
263334
+ }
262970
263335
  if (result.success)
262971
263336
  return annotateAudioFallbackSuccess(result, failed, candidate);
262972
263337
  failed.push({ candidate, reason: summarizeToolResult2(result) });
@@ -263306,6 +263671,12 @@ function videoCandidateFor(model, requestedBackend, requestedKind) {
263306
263671
  }
263307
263672
  return { model, backend, preset };
263308
263673
  }
263674
+ function videoCandidateHost(candidate) {
263675
+ return candidate.backend === "comfyui" ? "comfyui" : "diffusers-py";
263676
+ }
263677
+ function videoCandidateEstimatedVramMB(candidate) {
263678
+ return candidate.preset ? Math.ceil(candidate.preset.minVramGB * 1024) : void 0;
263679
+ }
263309
263680
  function videoGenerationFallbackCandidates(requestedModel, requestedBackend, requestedKind, allowFallback = true, options2 = {}) {
263310
263681
  const preferAudioVideo = Boolean(options2.preferNativeAudioVideo);
263311
263682
  const baseLadderIds = preferAudioVideo ? [...VIDEO_AUDIO_QUALITY_LADDER, ...VIDEO_GENERATION_QUALITY_LADDER] : VIDEO_GENERATION_QUALITY_LADDER;
@@ -263871,9 +264242,15 @@ function annotateVideoFallbackSuccess(result, failed, winner) {
263871
264242
  ...failed.map((attempt, index) => ` ${formatVideoAttempt(attempt.candidate, attempt.reason, index)}`),
263872
264243
  ""
263873
264244
  ].join("\n");
264245
+ const llmPrefix = [
264246
+ `Fallback ladder used ${winner.model} [${winner.backend}] after ${failed.length} failed attempt(s).`,
264247
+ ...failed.map((attempt, index) => formatVideoAttempt(attempt.candidate, attempt.reason, index))
264248
+ ].join("\n");
263874
264249
  return {
263875
264250
  ...result,
263876
- output: prefix + result.output
264251
+ output: prefix + result.output,
264252
+ llmContent: result.llmContent ? `${llmPrefix}
264253
+ ${result.llmContent}` : result.llmContent
263877
264254
  };
263878
264255
  }
263879
264256
  function parseRunnerJson3(stdout) {
@@ -265240,6 +265617,45 @@ if __name__ == "__main__":
265240
265617
  this.lastProgressAt = now;
265241
265618
  this.progressHandler(event);
265242
265619
  }
265620
+ async acquireTransientLoadLease(args) {
265621
+ if (!args.candidate)
265622
+ return null;
265623
+ const broker = getModelBroker();
265624
+ const decision2 = await broker.acquireTransientModelLoad({
265625
+ name: args.candidate.model,
265626
+ domain: "video-gen",
265627
+ host: videoCandidateHost(args.candidate),
265628
+ owner: "video-generate-tool",
265629
+ estimatedVramMB: videoCandidateEstimatedVramMB(args.candidate)
265630
+ }, {
265631
+ reason: args.reason,
265632
+ restoreOllama: true,
265633
+ unloadRequestedOllama: false
265634
+ });
265635
+ if (decision2.kind === "reject") {
265636
+ return {
265637
+ success: false,
265638
+ output: "",
265639
+ error: `Video generation blocked by resource broker: ${decision2.reason}`,
265640
+ durationMs: performance.now() - args.start
265641
+ };
265642
+ }
265643
+ if (decision2.kind === "degrade") {
265644
+ return {
265645
+ success: false,
265646
+ output: "",
265647
+ error: `Video generation needs a broker fallback (${decision2.fallback.name}), but video candidate fallback must be selected by the video ladder: ${decision2.reason}`,
265648
+ durationMs: performance.now() - args.start
265649
+ };
265650
+ }
265651
+ if (decision2.lease.evictedOllamaModels.length > 0) {
265652
+ this.emitProgress({
265653
+ stage: "setup",
265654
+ message: `Temporarily unloaded ${decision2.lease.evictedOllamaModels.length} Ollama inference model(s) to free VRAM for video generation`
265655
+ });
265656
+ }
265657
+ return { lease: decision2.lease, gpuIndex: decision2.lease.gpuIndex };
265658
+ }
265243
265659
  async execute(args) {
265244
265660
  const start2 = performance.now();
265245
265661
  const action = String(args["action"] ?? "generate");
@@ -265295,35 +265711,6 @@ if __name__ == "__main__":
265295
265711
  const withAudio = booleanArg3(args["with_audio"], false);
265296
265712
  const audioInput = typeof args["audio_input"] === "string" && args["audio_input"].trim() ? String(args["audio_input"]).trim() : void 0;
265297
265713
  const candidates = videoGenerationFallbackCandidates(requestedModel, requestedBackend, inferredKind, generationFallbackEnabled3(args), { preferNativeAudioVideo: withAudio || Boolean(audioInput) });
265298
- const broker = getModelBroker();
265299
- const firstCandidate = candidates[0];
265300
- let brokerGpuIndex = null;
265301
- if (firstCandidate) {
265302
- const preset = firstCandidate.preset;
265303
- const decision2 = await broker.ensureModelLoadable({
265304
- name: firstCandidate.model,
265305
- domain: "video-gen",
265306
- host: firstCandidate.backend === "comfyui" ? "comfyui" : "diffusers-py",
265307
- owner: "video-generate-tool",
265308
- estimatedVramMB: preset ? preset.minVramGB * 1024 : void 0
265309
- });
265310
- if (decision2.kind === "evict") {
265311
- for (const target of decision2.evictTargets) {
265312
- await broker.evict(target.host, target.name, "video-gen-needs-room");
265313
- }
265314
- brokerGpuIndex = decision2.gpuIndex ?? null;
265315
- } else if (decision2.kind === "ok") {
265316
- brokerGpuIndex = decision2.gpuIndex ?? null;
265317
- } else if (decision2.kind === "reject") {
265318
- return {
265319
- success: false,
265320
- output: "",
265321
- error: `Video generation blocked by resource broker: ${decision2.reason}`,
265322
- durationMs: performance.now() - start2
265323
- };
265324
- }
265325
- }
265326
- this._brokerGpuIndex = brokerGpuIndex;
265327
265714
  if (candidates.length === 0) {
265328
265715
  return {
265329
265716
  success: false,
@@ -265373,7 +265760,30 @@ if __name__ == "__main__":
265373
265760
  failed.push({ candidate, reason: "ComfyUI backend not yet implemented." });
265374
265761
  continue;
265375
265762
  }
265376
- const result = await this.prewarmDiffusers({ candidate, start: args.start, python: args.args["python"] });
265763
+ const leaseDecision = await this.acquireTransientLoadLease({
265764
+ candidate,
265765
+ reason: "video-prewarm-needs-room",
265766
+ start: args.start
265767
+ });
265768
+ if (leaseDecision && "success" in leaseDecision) {
265769
+ failed.push({ candidate, reason: summarizeToolResult3(leaseDecision) });
265770
+ if (index < args.candidates.length - 1) {
265771
+ this.emitProgress({
265772
+ stage: "setup",
265773
+ message: `${candidate.model} did not fit current resources; trying ${args.candidates[index + 1].model}`
265774
+ });
265775
+ }
265776
+ continue;
265777
+ }
265778
+ const lease = leaseDecision?.lease;
265779
+ this._brokerGpuIndex = leaseDecision?.gpuIndex ?? null;
265780
+ let result;
265781
+ try {
265782
+ result = await this.prewarmDiffusers({ candidate, start: args.start, python: args.args["python"] });
265783
+ } finally {
265784
+ await lease?.release();
265785
+ this._brokerGpuIndex = null;
265786
+ }
265377
265787
  if (result.success)
265378
265788
  return annotateVideoFallbackSuccess(result, failed, candidate);
265379
265789
  failed.push({ candidate, reason: summarizeToolResult3(result) });
@@ -265459,26 +265869,48 @@ if __name__ == "__main__":
265459
265869
  start: args.start
265460
265870
  });
265461
265871
  } else {
265462
- result = await this.generateWithDiffusers({
265463
- prompt: promptForCandidate,
265464
- model: candidate.model,
265465
- preset,
265466
- kind: args.kind,
265467
- imageArg: args.imageArg,
265468
- audioInput: args.audioInput,
265469
- width,
265470
- height,
265471
- numFrames,
265472
- fps,
265473
- steps,
265474
- guidance,
265475
- negativePrompt,
265476
- seed: args.seed,
265477
- hfToken: hfTokenOverride,
265478
- autoAcceptLicense,
265479
- start: args.start,
265480
- python: args.args["python"]
265872
+ const leaseDecision = await this.acquireTransientLoadLease({
265873
+ candidate,
265874
+ reason: "video-gen-needs-room",
265875
+ start: args.start
265481
265876
  });
265877
+ if (leaseDecision && "success" in leaseDecision) {
265878
+ failed.push({ candidate, reason: summarizeToolResult3(leaseDecision) });
265879
+ if (index < args.candidates.length - 1) {
265880
+ this.emitProgress({
265881
+ stage: "setup",
265882
+ message: `${candidate.model} did not fit current resources; falling back to ${args.candidates[index + 1].model}`
265883
+ });
265884
+ }
265885
+ continue;
265886
+ }
265887
+ const lease = leaseDecision?.lease;
265888
+ this._brokerGpuIndex = leaseDecision?.gpuIndex ?? null;
265889
+ try {
265890
+ result = await this.generateWithDiffusers({
265891
+ prompt: promptForCandidate,
265892
+ model: candidate.model,
265893
+ preset,
265894
+ kind: args.kind,
265895
+ imageArg: args.imageArg,
265896
+ audioInput: args.audioInput,
265897
+ width,
265898
+ height,
265899
+ numFrames,
265900
+ fps,
265901
+ steps,
265902
+ guidance,
265903
+ negativePrompt,
265904
+ seed: args.seed,
265905
+ hfToken: hfTokenOverride,
265906
+ autoAcceptLicense,
265907
+ start: args.start,
265908
+ python: args.args["python"]
265909
+ });
265910
+ } finally {
265911
+ await lease?.release();
265912
+ this._brokerGpuIndex = null;
265913
+ }
265482
265914
  }
265483
265915
  let nativeAudio = preset.nativeAudioVideo === true;
265484
265916
  let audioPath;
@@ -265670,6 +266102,17 @@ ${llmAnnotation}` : result.llmContent;
265670
266102
  }
265671
266103
  ensureUnifiedCacheDirs();
265672
266104
  this.emitProgress({ stage: "load", message: `Downloading/loading video model ${args.candidate.model}` });
266105
+ const runnerEnv = { ...python.env };
266106
+ if (this._brokerGpuIndex !== null) {
266107
+ if (mediaBrokerGpuIndexIsCompatible(this._brokerGpuIndex, "video", runnerEnv)) {
266108
+ runnerEnv["OMNIUS_GPU_INDEX"] = String(this._brokerGpuIndex);
266109
+ } else {
266110
+ this.emitProgress({
266111
+ stage: "setup",
266112
+ message: `Broker selected CUDA GPU ${this._brokerGpuIndex}, but video CUDA filtering excluded it; using CUDA_VISIBLE_DEVICES=${runnerEnv["CUDA_VISIBLE_DEVICES"] ?? "default"}`
266113
+ });
266114
+ }
266115
+ }
265673
266116
  const result = await runProcess4(python.command, [
265674
266117
  runner,
265675
266118
  "--model",
@@ -265685,7 +266128,7 @@ ${llmAnnotation}` : result.llmContent;
265685
266128
  ], {
265686
266129
  cwd: this.cwd,
265687
266130
  timeoutMs: 18e5,
265688
- env: python.env,
266131
+ env: runnerEnv,
265689
266132
  progressLabel: `Downloading/loading ${args.candidate.model}`,
265690
266133
  onProgress: (event) => this.emitProgress(event)
265691
266134
  });
@@ -568924,6 +569367,78 @@ var init_spinner = __esm({
568924
569367
  }
568925
569368
  });
568926
569369
 
569370
+ // packages/cli/src/tui/generative-progress.ts
569371
+ function generationKindForToolName(toolName) {
569372
+ if (toolName === "generate_image") return "image";
569373
+ if (toolName === "generate_audio") return "audio";
569374
+ if (toolName === "generate_video") return "video";
569375
+ if (toolName === "generate_tts" || toolName === "create_audio_file") return "tts";
569376
+ return null;
569377
+ }
569378
+ function formatGenerativeProgress(kind, event, options2 = {}) {
569379
+ const width = Math.max(8, Math.min(32, options2.width ?? (options2.surface === "telegram" ? 12 : 20)));
569380
+ const label = kindLabel(kind);
569381
+ const stage = stageLabel(event.stage);
569382
+ const pct = finitePercent(event.percent);
569383
+ const bytes = formatProgressBytes(event);
569384
+ const elapsed = formatElapsed(event.elapsedMs);
569385
+ const message2 = compactProgressMessage(event.message);
569386
+ if (typeof pct === "number") {
569387
+ const filled = Math.max(0, Math.min(width, Math.round(pct / 100 * width)));
569388
+ const bar = `${"#".repeat(filled)}${"-".repeat(width - filled)}`;
569389
+ return `${label} ${stage}: [${bar}] ${pct}% ${message2}${bytes}${elapsed}`;
569390
+ }
569391
+ return `${label} ${stage}: ${message2}${bytes}${elapsed}`;
569392
+ }
569393
+ function kindLabel(kind) {
569394
+ if (kind === "tts") return "TTS";
569395
+ return kind.slice(0, 1).toUpperCase() + kind.slice(1);
569396
+ }
569397
+ function stageLabel(stage) {
569398
+ const normalized = String(stage || "process").trim().toLowerCase();
569399
+ if (normalized === "setup") return "setup";
569400
+ if (normalized === "download") return "download";
569401
+ if (normalized === "load") return "load";
569402
+ if (normalized === "generate") return "infer";
569403
+ if (normalized === "save") return "save";
569404
+ if (normalized === "thumbnail") return "thumbnail";
569405
+ if (normalized === "hf_token_required") return "auth";
569406
+ return "process";
569407
+ }
569408
+ function finitePercent(value2) {
569409
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) return void 0;
569410
+ return Math.max(0, Math.min(100, Math.round(value2)));
569411
+ }
569412
+ function formatProgressBytes(event) {
569413
+ if (typeof event.totalBytes !== "number" || !Number.isFinite(event.totalBytes) || event.totalBytes <= 0) {
569414
+ return "";
569415
+ }
569416
+ const downloaded = typeof event.downloadedBytes === "number" && Number.isFinite(event.downloadedBytes) ? Math.max(0, event.downloadedBytes) : 0;
569417
+ return ` (${formatBytes3(downloaded)} / ${formatBytes3(event.totalBytes)})`;
569418
+ }
569419
+ function formatElapsed(elapsedMs2) {
569420
+ if (typeof elapsedMs2 !== "number" || !Number.isFinite(elapsedMs2) || elapsedMs2 <= 1500) return "";
569421
+ return ` ${Math.round(elapsedMs2 / 1e3)}s`;
569422
+ }
569423
+ function compactProgressMessage(message2) {
569424
+ return String(message2 || "working").replace(/\s+/g, " ").trim().slice(0, 220);
569425
+ }
569426
+ function formatBytes3(value2) {
569427
+ const units = ["B", "KB", "MB", "GB", "TB"];
569428
+ let amount = Math.max(0, value2);
569429
+ let idx = 0;
569430
+ while (amount >= 1024 && idx < units.length - 1) {
569431
+ amount /= 1024;
569432
+ idx++;
569433
+ }
569434
+ return idx === 0 ? `${Math.round(amount)}B` : `${amount.toFixed(1)}${units[idx]}`;
569435
+ }
569436
+ var init_generative_progress = __esm({
569437
+ "packages/cli/src/tui/generative-progress.ts"() {
569438
+ "use strict";
569439
+ }
569440
+ });
569441
+
568927
569442
  // packages/cli/src/api/py-embed.ts
568928
569443
  var py_embed_exports = {};
568929
569444
  __export(py_embed_exports, {
@@ -575515,7 +576030,7 @@ async function fetchOllamaModels(baseUrl) {
575515
576030
  const family = m2.details?.family;
575516
576031
  return {
575517
576032
  name: m2.name,
575518
- size: formatBytes3(m2.size),
576033
+ size: formatBytes4(m2.size),
575519
576034
  sizeBytes: m2.size,
575520
576035
  modified: formatRelativeTime(m2.modified_at),
575521
576036
  parameterSize: m2.details?.parameter_size,
@@ -575961,7 +576476,7 @@ async function queryModelCapabilities(baseUrl, modelName) {
575961
576476
  return caps;
575962
576477
  }
575963
576478
  }
575964
- function formatBytes3(bytes) {
576479
+ function formatBytes4(bytes) {
575965
576480
  if (bytes < 1024) return `${bytes} B`;
575966
576481
  const units = ["KB", "MB", "GB", "TB"];
575967
576482
  let size = bytes;
@@ -595166,7 +595681,7 @@ function formatWorkspaceExplorer(result) {
595166
595681
  const width = Math.max(12, ...result.entries.map((entry) => entry.path.length));
595167
595682
  for (const entry of result.entries) {
595168
595683
  lines.push(
595169
- ` ${entry.path.padEnd(Math.min(width, 70)).slice(0, 70)} ${entry.kind.padEnd(6)} ${formatBytes4(entry.sizeBytes).padStart(8)}`
595684
+ ` ${entry.path.padEnd(Math.min(width, 70)).slice(0, 70)} ${entry.kind.padEnd(6)} ${formatBytes5(entry.sizeBytes).padStart(8)}`
595170
595685
  );
595171
595686
  }
595172
595687
  lines.push("");
@@ -595188,7 +595703,7 @@ function previewWorkspaceFile(root, relPath, options2 = {}) {
595188
595703
  return [
595189
595704
  "",
595190
595705
  ` File Preview: ${relPath}`,
595191
- ` Size: ${formatBytes4(st.size)} (too large for inline preview)`,
595706
+ ` Size: ${formatBytes5(st.size)} (too large for inline preview)`,
595192
595707
  ""
595193
595708
  ].join("\n");
595194
595709
  }
@@ -595199,7 +595714,7 @@ function previewWorkspaceFile(root, relPath, options2 = {}) {
595199
595714
  return [
595200
595715
  "",
595201
595716
  ` File Preview: ${relPath}`,
595202
- ` Size: ${formatBytes4(st.size)} Lines: ${rawLines.length}${rawLines.length > maxLines ? " (truncated)" : ""}`,
595717
+ ` Size: ${formatBytes5(st.size)} Lines: ${rawLines.length}${rawLines.length > maxLines ? " (truncated)" : ""}`,
595203
595718
  "",
595204
595719
  ...visible.map((line, idx) => ` ${String(idx + 1).padStart(gutter)} | ${line}`),
595205
595720
  ""
@@ -595230,7 +595745,7 @@ function scoreWorkspaceFile(entry, query) {
595230
595745
  }
595231
595746
  return score;
595232
595747
  }
595233
- function formatBytes4(bytes) {
595748
+ function formatBytes5(bytes) {
595234
595749
  if (bytes < 1024) return `${bytes} B`;
595235
595750
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
595236
595751
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
@@ -600895,7 +601410,7 @@ function describeTaskComplete(summary, completed, personality = 2, _stark = fals
600895
601410
  }
600896
601411
  return `Task completed, but no summary was generated to describe the outcome`;
600897
601412
  }
600898
- function formatBytes5(bytes) {
601413
+ function formatBytes6(bytes) {
600899
601414
  if (bytes < 1024) return `${bytes}B`;
600900
601415
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
600901
601416
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
@@ -603605,7 +604120,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`
603605
604120
  const pct = Math.round(received / contentLength * 100);
603606
604121
  if (pct === 25 || pct === 50 || pct === 75 || pct === 100) {
603607
604122
  renderInfo(
603608
- ` ${pct}% (${formatBytes5(received)} / ${formatBytes5(contentLength)})`
604123
+ ` ${pct}% (${formatBytes6(received)} / ${formatBytes6(contentLength)})`
603609
604124
  );
603610
604125
  }
603611
604126
  }
@@ -603613,7 +604128,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`
603613
604128
  const fullBuffer = Buffer.concat(chunks);
603614
604129
  writeFileSync54(onnxPath, fullBuffer);
603615
604130
  renderInfo(
603616
- `${model.label} model downloaded (${formatBytes5(fullBuffer.length)}).`
604131
+ `${model.label} model downloaded (${formatBytes6(fullBuffer.length)}).`
603617
604132
  );
603618
604133
  }
603619
604134
  }
@@ -611742,15 +612257,7 @@ async function handleImageCommand(ctx3, arg, hasLocal) {
611742
612257
  return "handled";
611743
612258
  }
611744
612259
  function formatImageGenerationProgress(event) {
611745
- const pct = event.percent;
611746
- const elapsed = event.elapsedMs && event.elapsedMs > 1500 ? ` ${Math.round(event.elapsedMs / 1e3)}s` : "";
611747
- if (typeof pct === "number") {
611748
- const width = 20;
611749
- const filled = Math.max(0, Math.min(width, Math.round(pct / 100 * width)));
611750
- const bar = `${"#".repeat(filled)}${"-".repeat(width - filled)}`;
611751
- return `Image ${event.stage}: [${bar}] ${pct}% ${event.message}${elapsed}`;
611752
- }
611753
- return `Image ${event.stage}: ${event.message}${elapsed}`;
612260
+ return formatGenerativeProgress("image", event);
611754
612261
  }
611755
612262
  function rateVideoPresetForHardware(preset, specs) {
611756
612263
  const min = preset.minVramGB;
@@ -612030,15 +612537,7 @@ async function handleVideoCommand(ctx3, arg, hasLocal) {
612030
612537
  return "handled";
612031
612538
  }
612032
612539
  function formatVideoGenerationProgress(event) {
612033
- const pct = event.percent;
612034
- const elapsed = event.elapsedMs && event.elapsedMs > 1500 ? ` ${Math.round(event.elapsedMs / 1e3)}s` : "";
612035
- if (typeof pct === "number") {
612036
- const width = 20;
612037
- const filled = Math.max(0, Math.min(width, Math.round(pct / 100 * width)));
612038
- const bar = `${"#".repeat(filled)}${"-".repeat(width - filled)}`;
612039
- return `Video ${event.stage}: [${bar}] ${pct}% ${event.message}${elapsed}`;
612040
- }
612041
- return `Video ${event.stage}: ${event.message}${elapsed}`;
612540
+ return formatGenerativeProgress("video", event);
612042
612541
  }
612043
612542
  function activeAudioModel(settings, kind) {
612044
612543
  return kind === "music" ? settings.musicModel : settings.soundModel;
@@ -612346,16 +612845,7 @@ async function handleAudioGenerationCommand(ctx3, arg, hasLocal, kind) {
612346
612845
  return "handled";
612347
612846
  }
612348
612847
  function formatAudioGenerationProgress(event) {
612349
- const pct = event.percent;
612350
- const elapsed = event.elapsedMs && event.elapsedMs > 1500 ? ` ${Math.round(event.elapsedMs / 1e3)}s` : "";
612351
- const bytes = typeof event.totalBytes === "number" && event.totalBytes > 0 ? ` (${formatFileSize(event.downloadedBytes ?? 0)} / ${formatFileSize(event.totalBytes)})` : "";
612352
- if (typeof pct === "number") {
612353
- const width = 20;
612354
- const filled = Math.max(0, Math.min(width, Math.round(pct / 100 * width)));
612355
- const bar = `${"#".repeat(filled)}${"-".repeat(width - filled)}`;
612356
- return `Audio ${event.stage}: [${bar}] ${pct}% ${event.message}${bytes}${elapsed}`;
612357
- }
612358
- return `Audio ${event.stage}: ${event.message}${bytes}${elapsed}`;
612848
+ return formatGenerativeProgress("audio", event);
612359
612849
  }
612360
612850
  async function showHelpMenu(ctx3) {
612361
612851
  const slashCommands = getSlashHelpEntries();
@@ -617504,6 +617994,7 @@ var init_commands = __esm({
617504
617994
  "use strict";
617505
617995
  init_model_picker();
617506
617996
  init_render();
617997
+ init_generative_progress();
617507
617998
  init_command_registry();
617508
617999
  init_hf_token_prompt();
617509
618000
  init_dist5();
@@ -626249,6 +626740,10 @@ function scopedTool(base3, root, mode) {
626249
626740
  if (typeof baseSetExpander === "function") {
626250
626741
  wrapper.setPromptExpander = (expander) => baseSetExpander.call(base3, expander);
626251
626742
  }
626743
+ const baseSetProgress = base3.setProgressCallback;
626744
+ if (typeof baseSetProgress === "function") {
626745
+ wrapper.setProgressCallback = (handler) => baseSetProgress.call(base3, handler);
626746
+ }
626252
626747
  return wrapper;
626253
626748
  }
626254
626749
  function withTelegramAutoAttachmentNotice(result, artifactCount) {
@@ -626557,6 +627052,16 @@ var init_telegram_creative_tools = __esm({
626557
627052
  },
626558
627053
  required: []
626559
627054
  };
627055
+ progressHandler = null;
627056
+ setProgressCallback(handler) {
627057
+ this.progressHandler = handler;
627058
+ }
627059
+ emitProgress(start2, event) {
627060
+ try {
627061
+ this.progressHandler?.({ ...event, elapsedMs: performance.now() - start2 });
627062
+ } catch {
627063
+ }
627064
+ }
626560
627065
  async execute(args) {
626561
627066
  const start2 = performance.now();
626562
627067
  const text = typeof args["text"] === "string" && args["text"].trim() ? args["text"].trim() : typeof args["input"] === "string" && args["input"].trim() ? args["input"].trim() : typeof args["prompt"] === "string" && args["prompt"].trim() ? args["prompt"].trim() : "";
@@ -626590,8 +627095,10 @@ var init_telegram_creative_tools = __esm({
626590
627095
  }
626591
627096
  let result;
626592
627097
  try {
627098
+ this.emitProgress(start2, { stage: "setup", message: "Preparing scoped TTS audio file" });
626593
627099
  await mkdir19(dirname37(guarded.path.abs), { recursive: true });
626594
627100
  const tts = new TtsGenerateTool();
627101
+ this.emitProgress(start2, { stage: "load", message: "Starting TTS backend" });
626595
627102
  result = await tts.execute({
626596
627103
  text,
626597
627104
  output: guarded.path.abs,
@@ -626624,6 +627131,7 @@ ${(result.error || result.output || "").slice(0, 1200)}`,
626624
627131
  }
626625
627132
  rememberCreated(this.root, guarded.path.abs);
626626
627133
  const sizeKB = Math.round(statSync43(guarded.path.abs).size / 1024);
627134
+ this.emitProgress(start2, { stage: "save", message: `Saved scoped audio file (${sizeKB}KB)` });
626627
627135
  return withTelegramAutoAttachmentNotice({
626628
627136
  success: true,
626629
627137
  output: `Created audio file: ${guarded.path.abs} (${sizeKB}KB WAV)
@@ -631246,7 +631754,13 @@ function normalizeTelegramCallbackQuery(update2) {
631246
631754
  data
631247
631755
  };
631248
631756
  }
631249
- function adaptTool5(tool, todoSessionId) {
631757
+ function adaptTool5(tool, todoSessionId, progress) {
631758
+ const progressTool = tool;
631759
+ if (generationKindForToolName(tool.name) && typeof progressTool.setProgressCallback === "function") {
631760
+ progressTool.setProgressCallback((event) => {
631761
+ progress?.onProgress(tool.name, event);
631762
+ });
631763
+ }
631250
631764
  return {
631251
631765
  name: tool.name,
631252
631766
  description: tool.description,
@@ -631258,7 +631772,15 @@ function adaptTool5(tool, todoSessionId) {
631258
631772
  }
631259
631773
  try {
631260
631774
  const result = await tool.execute(args);
631775
+ progress?.complete(tool.name, result);
631261
631776
  return { success: result.success, output: result.output, error: result.error, llmContent: result.llmContent };
631777
+ } catch (err) {
631778
+ progress?.complete(tool.name, {
631779
+ success: false,
631780
+ output: "",
631781
+ error: err instanceof Error ? err.message : String(err)
631782
+ });
631783
+ throw err;
631262
631784
  } finally {
631263
631785
  if (todoSessionId && (tool.name === "todo_write" || tool.name === "todo_read")) {
631264
631786
  setTodoSessionId(previousTodoSession);
@@ -631429,6 +631951,7 @@ var init_telegram_bridge = __esm({
631429
631951
  init_scoped_personality();
631430
631952
  init_voice_soul();
631431
631953
  init_telegram_creative_tools();
631954
+ init_generative_progress();
631432
631955
  init_omnius_directory();
631433
631956
  init_stimulation();
631434
631957
  init_pid_controller();
@@ -637910,6 +638433,91 @@ ${TELEGRAM_PUBLIC_ORCHESTRATOR_CONTRACT}`);
637910
638433
  async sendChatAction(chatId, action) {
637911
638434
  await this.apiCall("sendChatAction", { chat_id: chatId, action });
637912
638435
  }
638436
+ createTelegramGenerativeProgressBridge(chatId, msg) {
638437
+ if (chatId === void 0) return void 0;
638438
+ const states = /* @__PURE__ */ new Map();
638439
+ const stateFor = (toolName) => {
638440
+ let state = states.get(toolName);
638441
+ if (!state) {
638442
+ state = {
638443
+ messageId: null,
638444
+ pump: null,
638445
+ queuedHtml: null,
638446
+ lastRenderedAt: 0
638447
+ };
638448
+ states.set(toolName, state);
638449
+ }
638450
+ return state;
638451
+ };
638452
+ const kindTitle = (toolName) => {
638453
+ const kind = generationKindForToolName(toolName);
638454
+ if (!kind) return "Generation";
638455
+ if (kind === "tts") return "TTS audio";
638456
+ return `${kind.slice(0, 1).toUpperCase()}${kind.slice(1)}`;
638457
+ };
638458
+ const enqueue = (state, html) => {
638459
+ state.queuedHtml = html;
638460
+ if (state.pump) return;
638461
+ state.pump = (async () => {
638462
+ while (state.queuedHtml) {
638463
+ const nextHtml = state.queuedHtml;
638464
+ state.queuedHtml = null;
638465
+ if (state.messageId === null) {
638466
+ const messageId = await this.sendLiveMessage(chatId, nextHtml, msg?.messageId);
638467
+ if (!messageId) return;
638468
+ state.messageId = messageId;
638469
+ } else {
638470
+ await this.editLiveMessage(chatId, state.messageId, nextHtml);
638471
+ }
638472
+ }
638473
+ })().catch(() => {
638474
+ }).finally(() => {
638475
+ state.pump = null;
638476
+ if (state.queuedHtml) enqueue(state, state.queuedHtml);
638477
+ });
638478
+ };
638479
+ const progressHtml = (toolName, event) => {
638480
+ const kind = generationKindForToolName(toolName);
638481
+ if (!kind) return null;
638482
+ const rendered = redactTelegramLocalPaths(formatGenerativeProgress(kind, event, { surface: "telegram" }));
638483
+ return `<b>${escapeTelegramHTML(kindTitle(toolName))} generation</b>
638484
+ <code>${escapeTelegramHTML(rendered)}</code>`;
638485
+ };
638486
+ const completeHtml = (toolName, result) => {
638487
+ if (!generationKindForToolName(toolName)) return null;
638488
+ if (result.success) {
638489
+ return `<b>${escapeTelegramHTML(kindTitle(toolName))} generation complete</b>`;
638490
+ }
638491
+ const reason = redactTelegramLocalPaths((result.error || result.output || "Generation failed").slice(0, 900));
638492
+ return `<b>${escapeTelegramHTML(kindTitle(toolName))} generation failed</b>
638493
+ <code>${escapeTelegramHTML(reason)}</code>`;
638494
+ };
638495
+ return {
638496
+ onProgress: (toolName, event) => {
638497
+ const html = progressHtml(toolName, event);
638498
+ if (!html) return;
638499
+ const state = stateFor(toolName);
638500
+ const now = Date.now();
638501
+ const stage = String(event.stage || "process");
638502
+ const percent = typeof event.percent === "number" && Number.isFinite(event.percent) ? Math.round(event.percent) : void 0;
638503
+ const terminalStage = stage === "save" || stage === "thumbnail" || stage === "hf_token_required";
638504
+ const shouldRender = state.lastRenderedAt === 0 || stage !== state.lastStage || typeof percent === "number" && (state.lastPercent === void 0 || Math.abs(percent - state.lastPercent) >= 5) || now - state.lastRenderedAt >= 3500 || terminalStage;
638505
+ if (!shouldRender) return;
638506
+ state.lastRenderedAt = now;
638507
+ state.lastStage = stage;
638508
+ state.lastPercent = percent;
638509
+ enqueue(state, html);
638510
+ },
638511
+ complete: (toolName, result) => {
638512
+ if (!generationKindForToolName(toolName)) return;
638513
+ const state = states.get(toolName);
638514
+ if (!state && result.success) return;
638515
+ const html = completeHtml(toolName, result);
638516
+ if (!html) return;
638517
+ enqueue(state ?? stateFor(toolName), html);
638518
+ }
638519
+ };
638520
+ }
637913
638521
  // ── Live message streaming (editMessageText pattern) ─────────────────
637914
638522
  /**
637915
638523
  * Send a placeholder message that will be progressively edited with
@@ -639942,6 +640550,7 @@ Scoped workspace: ${scopedRoot}`,
639942
640550
  const imageDefaults = this.imageGenerationDefaultsForRepo(repoRoot);
639943
640551
  const audioDefaults = this.audioGenerationDefaultsForRepo(repoRoot);
639944
640552
  const videoDefaults = this.videoGenerationDefaultsForRepo(repoRoot);
640553
+ const generativeProgress = this.createTelegramGenerativeProgressBridge(chatId, msg);
639945
640554
  const taskComplete = {
639946
640555
  name: "task_complete",
639947
640556
  description: "Internal completion signal for Telegram runs. Put the actual user-facing reply in assistant text before calling this. Use summary 'no_reply' only to silently skip responding; never write that sentinel as assistant text.",
@@ -640088,7 +640697,7 @@ Scoped workspace: ${scopedRoot}`,
640088
640697
  }
640089
640698
  }
640090
640699
  }
640091
- const unfilteredAdaptedTools = allTools.map((tool) => adaptTool5(tool, todoSessionId));
640700
+ const unfilteredAdaptedTools = allTools.map((tool) => adaptTool5(tool, todoSessionId, generativeProgress));
640092
640701
  let adaptedTools = unfilteredAdaptedTools;
640093
640702
  adaptedTools = applyToolPolicy(adaptedTools, context2, this.toolPolicyConfig);
640094
640703
  if (context2 === "telegram-admin-dm") {
@@ -640103,9 +640712,9 @@ Scoped workspace: ${scopedRoot}`,
640103
640712
  imageDefaults,
640104
640713
  audioDefaults,
640105
640714
  videoDefaults
640106
- ).map((tool) => adaptTool5(tool, todoSessionId));
640715
+ ).map((tool) => adaptTool5(tool, todoSessionId, generativeProgress));
640107
640716
  adaptedTools.push(...creativeTools);
640108
- adaptedTools.push(adaptTool5(this.buildTelegramSendFileTool(context2, repoRoot, chatId, msg), todoSessionId));
640717
+ adaptedTools.push(adaptTool5(this.buildTelegramSendFileTool(context2, repoRoot, chatId, msg), todoSessionId, generativeProgress));
640109
640718
  adaptedTools = this.filterNonAdminTelegramTools(adaptedTools);
640110
640719
  adaptedTools = adaptedTools.map((tool) => this.applyTelegramPublicQuota(tool, context2, chatId, msg));
640111
640720
  }
@@ -642850,7 +643459,7 @@ function buildShellLiveBlockLines(state, width) {
642850
643459
  const w = Math.max(36, width);
642851
643460
  const inner = Math.max(1, w - 4);
642852
643461
  const elapsed = Math.max(0, Date.now() - state.startedAt);
642853
- const status = state.status === "running" ? `live ${formatElapsed(elapsed)}` : state.status;
643462
+ const status = state.status === "running" ? `live ${formatElapsed2(elapsed)}` : state.status;
642854
643463
  const title = ` Shell ${status} `;
642855
643464
  const top = `╭${fitWithFill(`─${title}`, w - 2, "─")}╮`;
642856
643465
  const bottom = `╰${"─".repeat(w - 2)}╯`;
@@ -642895,7 +643504,7 @@ function fitWithFill(value2, width, fill) {
642895
643504
  if (chars.length > width) return chars.slice(0, width).join("");
642896
643505
  return value2 + fill.repeat(width - chars.length);
642897
643506
  }
642898
- function formatElapsed(ms) {
643507
+ function formatElapsed2(ms) {
642899
643508
  const seconds = Math.floor(ms / 1e3);
642900
643509
  if (seconds < 60) return `${seconds}s`;
642901
643510
  const minutes = Math.floor(seconds / 60);
@@ -668394,6 +669003,12 @@ function getVersion4() {
668394
669003
  return "0.0.0";
668395
669004
  }
668396
669005
  function adaptTool6(tool) {
669006
+ const progressTool = tool;
669007
+ if (generationKindForToolName(tool.name) && typeof progressTool.setProgressCallback === "function") {
669008
+ progressTool.setProgressCallback((event) => {
669009
+ _generativeProgressSink?.(tool.name, event);
669010
+ });
669011
+ }
668397
669012
  return {
668398
669013
  name: tool.name,
668399
669014
  description: tool.description,
@@ -668704,6 +669319,17 @@ function audioGenerationDefaultsForRepo(repoRoot) {
668704
669319
  function createConfiguredAudioGenerateTool(repoRoot) {
668705
669320
  return new AudioGenerateTool(repoRoot, audioGenerationDefaultsForRepo(repoRoot));
668706
669321
  }
669322
+ function videoGenerationDefaultsForRepo(repoRoot) {
669323
+ const settings = resolveSettings(repoRoot);
669324
+ return {
669325
+ model: typeof settings.videoModel === "string" && settings.videoModel.trim() ? settings.videoModel : void 0,
669326
+ backend: settings.videoBackend,
669327
+ defaultKind: settings.videoKind
669328
+ };
669329
+ }
669330
+ function createConfiguredVideoGenerateTool(repoRoot) {
669331
+ return new VideoGenerateTool(repoRoot, videoGenerationDefaultsForRepo(repoRoot));
669332
+ }
668707
669333
  function buildSubAgentTools(repoRoot, config) {
668708
669334
  return [
668709
669335
  // File + search
@@ -668802,6 +669428,7 @@ function buildSubAgentTools(repoRoot, config) {
668802
669428
  new CameraCaptureTool(),
668803
669429
  createConfiguredImageGenerateTool(repoRoot, config.backendUrl),
668804
669430
  createConfiguredAudioGenerateTool(repoRoot),
669431
+ createConfiguredVideoGenerateTool(repoRoot),
668805
669432
  // Hardware sensors + radios (read-only scans)
668806
669433
  new GpsLocationTool(),
668807
669434
  new WifiControlTool(),
@@ -668889,6 +669516,8 @@ function buildTools(repoRoot, config, contextWindowSize, modelTier) {
668889
669516
  createConfiguredImageGenerateTool(repoRoot, config.backendUrl),
668890
669517
  // Sound/music Generation — local Diffusers/AudioCraft/Stable Audio paths
668891
669518
  createConfiguredAudioGenerateTool(repoRoot),
669519
+ // Video Generation — local Diffusers/ComfyUI video pipelines
669520
+ createConfiguredVideoGenerateTool(repoRoot),
668892
669521
  // Structured file reading (CSV, JSON, Markdown, binary detection)
668893
669522
  new StructuredReadTool(repoRoot),
668894
669523
  // Vision tools (Moondream — desktop awareness + point-and-click)
@@ -669551,16 +670180,6 @@ async function renderAsciiPreviewForImage(imagePath, displayPath, title, writer)
669551
670180
  return "";
669552
670181
  }
669553
670182
  }
669554
- function formatImageGenerationProgress2(event) {
669555
- const elapsed = event.elapsedMs && event.elapsedMs > 1500 ? ` ${Math.round(event.elapsedMs / 1e3)}s` : "";
669556
- if (typeof event.percent === "number") {
669557
- const width = 20;
669558
- const filled = Math.max(0, Math.min(width, Math.round(event.percent / 100 * width)));
669559
- const bar = `${"#".repeat(filled)}${"-".repeat(width - filled)}`;
669560
- return `Image ${event.stage}: [${bar}] ${event.percent}% ${event.message}${elapsed}`;
669561
- }
669562
- return `Image ${event.stage}: ${event.message}${elapsed}`;
669563
- }
669564
670183
  async function renderAsciiPreviewForToolResult(toolName, output, repoRoot, writer) {
669565
670184
  if (!output) return;
669566
670185
  try {
@@ -670701,14 +671320,11 @@ ${entry.fullContent}`
670701
671320
  fn();
670702
671321
  }
670703
671322
  };
670704
- for (const tool of tools) {
670705
- const maybeProgressTool = tool;
670706
- if (typeof maybeProgressTool.setProgressCallback === "function") {
670707
- maybeProgressTool.setProgressCallback((event) => {
670708
- contentWrite(() => renderInfo(formatImageGenerationProgress2(event)));
670709
- });
670710
- }
670711
- }
671323
+ _generativeProgressSink = (toolName, event) => {
671324
+ const kind = generationKindForToolName(toolName);
671325
+ if (!kind) return;
671326
+ contentWrite(() => renderInfo(formatGenerativeProgress(kind, event, { surface: "tui" })));
671327
+ };
670712
671328
  let liveShellBlock = null;
670713
671329
  const scheduleLiveShellRepaint = () => {
670714
671330
  if (!liveShellBlock || liveShellBlock.repaintTimer || !statusBar?.isActive)
@@ -677484,7 +678100,7 @@ Rules:
677484
678100
  process.exit(1);
677485
678101
  }
677486
678102
  }
677487
- var NEXUS_DIRECTORY_ORIGIN3, NEXUS_AGENT_DIRECTORY_URL, NEXUS_SPONSORS_URL3, _interactiveSessionActive, _interactiveSessionReason, _voiceChatSession2, taskManager, _apiCallbacks, _shellToolRef, _replToolRef, _fullSubAgentToolRef, _agentToolRef, _sendMessageToolRef, _agentLifecycleMgr, _activeRunnerRef, _parentRunnerForArchive, _wireSubAgentCallbacks, _wireAgentToolCallbacks, _wireSubAgentToolCallbacks, _autoUpdatedThisSession, _mcpManager, _pluginManager, _mcpTools, SELF_IMPROVE_INTERVAL, _tasksSinceImprove;
678103
+ var NEXUS_DIRECTORY_ORIGIN3, NEXUS_AGENT_DIRECTORY_URL, NEXUS_SPONSORS_URL3, _generativeProgressSink, _interactiveSessionActive, _interactiveSessionReason, _voiceChatSession2, taskManager, _apiCallbacks, _shellToolRef, _replToolRef, _fullSubAgentToolRef, _agentToolRef, _sendMessageToolRef, _agentLifecycleMgr, _activeRunnerRef, _parentRunnerForArchive, _wireSubAgentCallbacks, _wireAgentToolCallbacks, _wireSubAgentToolCallbacks, _autoUpdatedThisSession, _mcpManager, _pluginManager, _mcpTools, SELF_IMPROVE_INTERVAL, _tasksSinceImprove;
677488
678104
  var init_interactive = __esm({
677489
678105
  "packages/cli/src/tui/interactive.ts"() {
677490
678106
  "use strict";
@@ -677493,6 +678109,7 @@ var init_interactive = __esm({
677493
678109
  init_dist8();
677494
678110
  init_dist8();
677495
678111
  init_dist5();
678112
+ init_generative_progress();
677496
678113
  init_dist();
677497
678114
  init_listen();
677498
678115
  init_voice_session();
@@ -677543,6 +678160,7 @@ var init_interactive = __esm({
677543
678160
  NEXUS_DIRECTORY_ORIGIN3 = (process.env["OMNIUS_NEXUS_DIRECTORY_ORIGIN"] || process.env["OMNIUS_NEXUS_SIGNALING_SERVER"] || "https://openagents.nexus").replace(/\/+$/, "");
677544
678161
  NEXUS_AGENT_DIRECTORY_URL = `${NEXUS_DIRECTORY_ORIGIN3}/api/v1/directory`;
677545
678162
  NEXUS_SPONSORS_URL3 = `${NEXUS_DIRECTORY_ORIGIN3}/api/v1/sponsors`;
678163
+ _generativeProgressSink = null;
677546
678164
  _interactiveSessionActive = false;
677547
678165
  _interactiveSessionReason = "";
677548
678166
  _voiceChatSession2 = null;