omnius 1.0.4 → 1.0.6

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
@@ -250369,10 +250369,19 @@ function inferImageGenerationBackend(model, requested) {
250369
250369
  return "sdcpp";
250370
250370
  return "diffusers";
250371
250371
  }
250372
+ function imageGenerationDir(repoRoot = ".") {
250373
+ return join36(repoRoot, ".omnius", "image-gen");
250374
+ }
250375
+ function diffusersVenvDir(repoRoot = ".") {
250376
+ return join36(imageGenerationDir(repoRoot), ".venv");
250377
+ }
250378
+ function sdcppVenvDir(repoRoot = ".") {
250379
+ return join36(imageGenerationDir(repoRoot), ".venv-sdcpp");
250380
+ }
250372
250381
  function imageGenerationSetupPlan(backend, repoRoot = ".", model) {
250373
- const imageDir = join36(repoRoot, ".omnius", "image-gen");
250382
+ const imageDir = imageGenerationDir(repoRoot);
250374
250383
  if (backend === "ollama") {
250375
- const chosen = model && model !== "auto" ? model : "x/z-image-turbo";
250384
+ const chosen = model && model !== "auto" ? model : DEFAULT_OLLAMA_IMAGE_MODEL;
250376
250385
  return {
250377
250386
  backend,
250378
250387
  title: "Ollama image generation",
@@ -250387,26 +250396,30 @@ function imageGenerationSetupPlan(backend, repoRoot = ".", model) {
250387
250396
  };
250388
250397
  }
250389
250398
  if (backend === "diffusers") {
250399
+ const venvDir2 = diffusersVenvDir(repoRoot);
250400
+ const chosen = model && model !== "auto" ? model : DEFAULT_DIFFUSERS_IMAGE_MODEL;
250390
250401
  return {
250391
250402
  backend,
250392
250403
  title: "Python Diffusers image generation",
250393
250404
  commands: [
250394
- `python3 -m venv ${join36(imageDir, "venv")}`,
250395
- `${join36(imageDir, "venv", "bin", "python")} -m pip install -U pip torch diffusers transformers accelerate safetensors pillow`,
250396
- `omnius /image "a compact robot painter" --backend diffusers --model ${model && model !== "auto" ? model : "segmind/tiny-sd"}`
250405
+ `python3 -m venv ${venvDir2}`,
250406
+ `${venvPython(venvDir2)} -m pip install -U pip ${DIFFUSERS_PYTHON_PACKAGES.join(" ")}`,
250407
+ `omnius /image "a compact robot painter" --backend diffusers --model ${chosen}`
250397
250408
  ],
250398
250409
  notes: [
250399
- "Good lightweight candidates: segmind/tiny-sd, nota-ai/bk-sdm-tiny-2m, SimianLuo/LCM_Dreamshaper_v7, stabilityai/sd-turbo.",
250410
+ `Default first-run model: ${DEFAULT_DIFFUSERS_IMAGE_MODEL}. Good lighter candidates: stabilityai/sd-turbo, segmind/tiny-sd, nota-ai/bk-sdm-tiny-2m.`,
250411
+ "The venv, Hugging Face cache, Torch cache, and pip cache stay under .omnius/image-gen.",
250400
250412
  "The runner script is created automatically at .omnius/image-gen/diffusers_text2image.py."
250401
250413
  ]
250402
250414
  };
250403
250415
  }
250416
+ const venvDir = sdcppVenvDir(repoRoot);
250404
250417
  return {
250405
250418
  backend,
250406
250419
  title: "stable-diffusion.cpp Python image generation",
250407
250420
  commands: [
250408
- `python3 -m venv ${join36(imageDir, "venv-sdcpp")}`,
250409
- `${join36(imageDir, "venv-sdcpp", "bin", "python")} -m pip install -U pip stable-diffusion-cpp-python pillow`,
250421
+ `python3 -m venv ${venvDir}`,
250422
+ `${venvPython(venvDir)} -m pip install -U pip ${SDCPP_PYTHON_PACKAGES.join(" ")}`,
250410
250423
  `omnius /image "a compact robot painter" --backend sdcpp --model /absolute/path/to/model.gguf`
250411
250424
  ],
250412
250425
  notes: [
@@ -250450,17 +250463,75 @@ function trimProcessText(text, max = 1800) {
250450
250463
  return clean3;
250451
250464
  return clean3.slice(0, max - 20) + "\n... (truncated)";
250452
250465
  }
250453
- function pythonFor(repoRoot, kind, explicit) {
250466
+ function imageGenerationPythonEnv(repoRoot) {
250467
+ const root = imageGenerationDir(repoRoot);
250468
+ const hf = join36(root, "huggingface");
250469
+ return {
250470
+ PYTHONUNBUFFERED: "1",
250471
+ HF_HOME: hf,
250472
+ HUGGINGFACE_HUB_CACHE: join36(hf, "hub"),
250473
+ TRANSFORMERS_CACHE: join36(hf, "transformers"),
250474
+ DIFFUSERS_CACHE: join36(hf, "diffusers"),
250475
+ TORCH_HOME: join36(root, "torch"),
250476
+ XDG_CACHE_HOME: join36(root, "cache"),
250477
+ PIP_CACHE_DIR: join36(root, "pip-cache")
250478
+ };
250479
+ }
250480
+ async function ensureImageGenerationCacheDirs(repoRoot) {
250481
+ const env2 = imageGenerationPythonEnv(repoRoot);
250482
+ await Promise.all([
250483
+ imageGenerationDir(repoRoot),
250484
+ env2["HF_HOME"],
250485
+ env2["HUGGINGFACE_HUB_CACHE"],
250486
+ env2["TRANSFORMERS_CACHE"],
250487
+ env2["DIFFUSERS_CACHE"],
250488
+ env2["TORCH_HOME"],
250489
+ env2["XDG_CACHE_HOME"],
250490
+ env2["PIP_CACHE_DIR"]
250491
+ ].filter((value2) => Boolean(value2)).map((dir) => mkdir11(dir, { recursive: true })));
250492
+ }
250493
+ async function pythonCanImport(command, code8, repoRoot, env2) {
250494
+ const result = await runProcess2(command, ["-c", code8], { cwd: repoRoot, timeoutMs: 6e4, env: env2 });
250495
+ return result.code === 0;
250496
+ }
250497
+ async function ensurePythonFor(repoRoot, kind, explicit) {
250498
+ const pythonEnv = imageGenerationPythonEnv(repoRoot);
250499
+ await ensureImageGenerationCacheDirs(repoRoot);
250454
250500
  if (explicit)
250455
- return explicit;
250456
- const env2 = process.env["OMNIUS_IMAGE_PYTHON"];
250457
- if (env2)
250458
- return env2;
250459
- const venv = kind === "diffusers" ? join36(repoRoot, ".omnius", "image-gen", "venv", "bin", "python") : join36(repoRoot, ".omnius", "image-gen", "venv-sdcpp", "bin", "python");
250460
- return existsSync23(venv) ? venv : "python3";
250501
+ return { command: explicit, env: pythonEnv };
250502
+ const configuredPython = process.env["OMNIUS_IMAGE_PYTHON"];
250503
+ if (configuredPython)
250504
+ return { command: configuredPython, env: pythonEnv };
250505
+ const venvDir = kind === "diffusers" ? diffusersVenvDir(repoRoot) : sdcppVenvDir(repoRoot);
250506
+ const command = venvPython(venvDir);
250507
+ if (!existsSync23(command)) {
250508
+ const created = await runProcess2("python3", ["-m", "venv", venvDir], { cwd: repoRoot, timeoutMs: 18e4, env: pythonEnv });
250509
+ if (created.code !== 0) {
250510
+ throw new Error(`Failed to create image-generation venv at ${venvDir}.
250511
+ ${trimProcessText(created.stderr || created.stdout)}`);
250512
+ }
250513
+ }
250514
+ const importCheck = kind === "diffusers" ? "import torch, diffusers, PIL\nfrom diffusers import AutoPipelineForText2Image\n" : "import stable_diffusion_cpp, PIL\n";
250515
+ if (await pythonCanImport(command, importCheck, repoRoot, pythonEnv)) {
250516
+ return { command, env: pythonEnv };
250517
+ }
250518
+ const packages = kind === "diffusers" ? DIFFUSERS_PYTHON_PACKAGES : SDCPP_PYTHON_PACKAGES;
250519
+ const pip = await runProcess2(command, ["-m", "pip", "install", "-U", "pip", ...packages], {
250520
+ cwd: repoRoot,
250521
+ timeoutMs: 18e5,
250522
+ env: pythonEnv
250523
+ });
250524
+ if (pip.code !== 0) {
250525
+ throw new Error(`Failed to install ${kind} image-generation packages into ${venvDir}.
250526
+ ${trimProcessText(pip.stderr || pip.stdout)}`);
250527
+ }
250528
+ if (!await pythonCanImport(command, importCheck, repoRoot, pythonEnv)) {
250529
+ throw new Error(`Image-generation Python environment at ${venvDir} was created, but required ${kind} imports still fail.`);
250530
+ }
250531
+ return { command, env: pythonEnv };
250461
250532
  }
250462
250533
  async function ensureRunner(repoRoot, kind) {
250463
- const dir = join36(repoRoot, ".omnius", "image-gen");
250534
+ const dir = imageGenerationDir(repoRoot);
250464
250535
  await mkdir11(dir, { recursive: true });
250465
250536
  const script = kind === "diffusers" ? join36(dir, "diffusers_text2image.py") : join36(dir, "sdcpp_text2image.py");
250466
250537
  await writeFile16(script, kind === "diffusers" ? DIFFUSERS_RUNNER : SDCPP_RUNNER, "utf8");
@@ -250494,16 +250565,39 @@ function parseRunnerJson(stdout) {
250494
250565
  }
250495
250566
  return null;
250496
250567
  }
250497
- var IMAGE_GENERATION_MODEL_PRESETS, OLLAMA_IMAGE_MODELS, DIFFUSERS_RUNNER, SDCPP_RUNNER, ImageGenerateTool;
250568
+ var DEFAULT_DIFFUSERS_IMAGE_MODEL, DEFAULT_OLLAMA_IMAGE_MODEL, DIFFUSERS_PYTHON_PACKAGES, SDCPP_PYTHON_PACKAGES, IMAGE_GENERATION_MODEL_PRESETS, OLLAMA_IMAGE_MODELS, DIFFUSERS_RUNNER, SDCPP_RUNNER, ImageGenerateTool;
250498
250569
  var init_image_generate = __esm({
250499
250570
  "packages/execution/dist/tools/image-generate.js"() {
250500
250571
  "use strict";
250572
+ init_venv_paths();
250573
+ DEFAULT_DIFFUSERS_IMAGE_MODEL = "stabilityai/sdxl-turbo";
250574
+ DEFAULT_OLLAMA_IMAGE_MODEL = "x/z-image-turbo";
250575
+ DIFFUSERS_PYTHON_PACKAGES = [
250576
+ "torch",
250577
+ "diffusers",
250578
+ "transformers",
250579
+ "accelerate",
250580
+ "safetensors",
250581
+ "pillow",
250582
+ "sentencepiece",
250583
+ "protobuf"
250584
+ ];
250585
+ SDCPP_PYTHON_PACKAGES = [
250586
+ "stable-diffusion-cpp-python",
250587
+ "pillow"
250588
+ ];
250501
250589
  IMAGE_GENERATION_MODEL_PRESETS = [
250502
250590
  {
250503
- id: "x/z-image-turbo",
250591
+ id: DEFAULT_OLLAMA_IMAGE_MODEL,
250504
250592
  label: "Z-Image Turbo",
250505
250593
  backend: "ollama",
250506
250594
  install: "ollama pull x/z-image-turbo",
250595
+ category: "Modern deployable",
250596
+ sizeClass: "6B-class efficient image model",
250597
+ quality: "Modern high-quality output with a practical inference footprint; below FLUX.1 dev/SD3.5 Large for peak photorealism.",
250598
+ minVramGB: 16,
250599
+ recommendedVramGB: 24,
250600
+ deployment: "Ollama model path; good high-end consumer GPU target.",
250507
250601
  steps: 8,
250508
250602
  width: 1024,
250509
250603
  height: 1024,
@@ -250514,16 +250608,161 @@ var init_image_generate = __esm({
250514
250608
  label: "FLUX.2 Klein",
250515
250609
  backend: "ollama",
250516
250610
  install: "ollama pull x/flux2-klein",
250611
+ category: "Modern deployable",
250612
+ sizeClass: "4B compact FLUX-family",
250613
+ quality: "Modern FLUX-family quality in a smaller package; useful when full FLUX.1 is too heavy.",
250614
+ minVramGB: 12,
250615
+ recommendedVramGB: 16,
250616
+ deployment: "Ollama model path for practical local experimentation.",
250517
250617
  steps: 8,
250518
250618
  width: 1024,
250519
250619
  height: 1024,
250520
250620
  note: "Compact FLUX-family Ollama path for interactive local generation."
250521
250621
  },
250622
+ {
250623
+ id: "black-forest-labs/FLUX.1-dev",
250624
+ label: "FLUX.1 dev",
250625
+ backend: "diffusers",
250626
+ install: 'python .omnius/image-gen/diffusers_text2image.py --model black-forest-labs/FLUX.1-dev --steps 28 --guidance 3.5 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250627
+ category: "Primary hyper-realistic baseline",
250628
+ sizeClass: "12B rectified-flow transformer",
250629
+ quality: "Top-tier open-weight photorealism, prompt adherence, texture detail, composition, and typography.",
250630
+ minVramGB: 24,
250631
+ recommendedVramGB: 48,
250632
+ deployment: "Heavy. Best with Diffusers CPU offload, FP8/quantized variants, ComfyUI, multi-GPU, or cloud GPU workers.",
250633
+ steps: 28,
250634
+ guidance: 3.5,
250635
+ width: 1024,
250636
+ height: 1024,
250637
+ note: "Primary serious-generation baseline for maximum photorealism."
250638
+ },
250639
+ {
250640
+ id: "stabilityai/stable-diffusion-3.5-large",
250641
+ label: "Stable Diffusion 3.5 Large",
250642
+ backend: "diffusers",
250643
+ install: 'python .omnius/image-gen/diffusers_text2image.py --model stabilityai/stable-diffusion-3.5-large --steps 28 --guidance 4.5 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250644
+ category: "Primary hyper-realistic baseline",
250645
+ sizeClass: "8B MMDiT",
250646
+ quality: "Serious open Stable Diffusion ecosystem baseline with strong realism, complex prompt understanding, typography, and controllability.",
250647
+ minVramGB: 24,
250648
+ recommendedVramGB: 40,
250649
+ deployment: "Best local candidate for SD/LoRA/ControlNet-style workflows; use offload or quantization below high-VRAM GPUs.",
250650
+ steps: 28,
250651
+ guidance: 4.5,
250652
+ width: 1024,
250653
+ height: 1024,
250654
+ note: "Primary serious-generation baseline for the Stable Diffusion ecosystem."
250655
+ },
250656
+ {
250657
+ id: "black-forest-labs/FLUX.1-schnell",
250658
+ label: "FLUX.1 schnell",
250659
+ backend: "diffusers",
250660
+ install: 'python .omnius/image-gen/diffusers_text2image.py --model black-forest-labs/FLUX.1-schnell --steps 4 --guidance 0 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250661
+ category: "Fast large-model iteration",
250662
+ sizeClass: "12B rectified-flow transformer",
250663
+ quality: "FLUX-style output with fewer steps; better for rapid iteration than absolute peak quality.",
250664
+ minVramGB: 16,
250665
+ recommendedVramGB: 24,
250666
+ deployment: "Use for fast prompt iteration; verify current license terms before commercial use.",
250667
+ steps: 4,
250668
+ guidance: 0,
250669
+ width: 1024,
250670
+ height: 1024,
250671
+ note: "Fast FLUX-family iteration path."
250672
+ },
250673
+ {
250674
+ id: "stabilityai/stable-diffusion-3.5-large-turbo",
250675
+ label: "SD3.5 Large Turbo",
250676
+ backend: "diffusers",
250677
+ install: 'python .omnius/image-gen/diffusers_text2image.py --model stabilityai/stable-diffusion-3.5-large-turbo --steps 4 --guidance 0 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250678
+ category: "Fast large-model iteration",
250679
+ sizeClass: "8B distilled MMDiT",
250680
+ quality: "SD3.5-family quality optimized for fewer inference steps; throughput over peak fidelity.",
250681
+ minVramGB: 16,
250682
+ recommendedVramGB: 24,
250683
+ deployment: "Good for interactive SD3.5-family concepting with offload/quantization when needed.",
250684
+ steps: 4,
250685
+ guidance: 0,
250686
+ width: 1024,
250687
+ height: 1024,
250688
+ note: "Fast SD3.5-family iteration path."
250689
+ },
250690
+ {
250691
+ id: "Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers",
250692
+ label: "HunyuanDiT v1.2",
250693
+ backend: "diffusers",
250694
+ install: 'python .omnius/image-gen/diffusers_text2image.py --model Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers --steps 30 --guidance 7.5 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250695
+ category: "Large multilingual diffusion",
250696
+ sizeClass: "Large DiT text-to-image",
250697
+ quality: "Strong bilingual English/Chinese prompt understanding with detailed, realistic multi-resolution output.",
250698
+ minVramGB: 24,
250699
+ recommendedVramGB: 40,
250700
+ deployment: "Significant GPU memory requirements; prefer Diffusers-compatible variants and offload on smaller GPUs.",
250701
+ steps: 30,
250702
+ guidance: 7.5,
250703
+ width: 1024,
250704
+ height: 1024,
250705
+ note: "Large DiT option for bilingual and detailed realism workflows."
250706
+ },
250707
+ {
250708
+ id: "Tongyi-MAI/Z-Image-Turbo",
250709
+ label: "Z-Image-Turbo",
250710
+ backend: "diffusers",
250711
+ install: 'python .omnius/image-gen/diffusers_text2image.py --model Tongyi-MAI/Z-Image-Turbo --steps 8 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250712
+ category: "Modern deployable",
250713
+ sizeClass: "6B image generation model",
250714
+ quality: "Efficient newer large-model quality; useful below full FLUX/SD3.5 hardware budgets.",
250715
+ minVramGB: 16,
250716
+ recommendedVramGB: 24,
250717
+ deployment: "Candidate for high-end consumer GPUs and optimized runtimes.",
250718
+ steps: 8,
250719
+ width: 1024,
250720
+ height: 1024,
250721
+ note: "Efficient modern large image model."
250722
+ },
250723
+ {
250724
+ id: "black-forest-labs/FLUX.2-klein-4B",
250725
+ label: "FLUX.2 Klein 4B",
250726
+ backend: "diffusers",
250727
+ install: 'python .omnius/image-gen/diffusers_text2image.py --model black-forest-labs/FLUX.2-klein-4B --steps 8 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250728
+ category: "Modern deployable",
250729
+ sizeClass: "4B compact FLUX-family",
250730
+ quality: "Bridge between practical deployment and modern FLUX-family visual quality.",
250731
+ minVramGB: 12,
250732
+ recommendedVramGB: 16,
250733
+ deployment: "Better fit for consumer GPU experimentation than 8B-12B baselines.",
250734
+ steps: 8,
250735
+ width: 1024,
250736
+ height: 1024,
250737
+ note: "More deployable compact FLUX-family model."
250738
+ },
250739
+ {
250740
+ id: "deepseek-ai/Janus-Pro-7B",
250741
+ label: "Janus-Pro-7B",
250742
+ backend: "diffusers",
250743
+ install: "experimental research model; use a dedicated Janus pipeline/runtime rather than the generic Diffusers text-to-image runner",
250744
+ category: "Experimental multimodal research",
250745
+ sizeClass: "7B multimodal image generation model",
250746
+ quality: "Relevant research model, but not a classic diffusion baseline for production image pipelines.",
250747
+ minVramGB: 16,
250748
+ recommendedVramGB: 24,
250749
+ deployment: "Experimental/non-classic diffusion-adjacent; list for awareness, not a default production path.",
250750
+ steps: 20,
250751
+ width: 1024,
250752
+ height: 1024,
250753
+ note: "Experimental multimodal generation research model."
250754
+ },
250522
250755
  {
250523
250756
  id: "segmind/tiny-sd",
250524
250757
  label: "Segmind Tiny-SD",
250525
250758
  backend: "diffusers",
250526
250759
  install: 'python .omnius/image-gen/diffusers_text2image.py --model segmind/tiny-sd --prompt "..." --output .omnius/images/out.png',
250760
+ category: "Lightweight smoke test",
250761
+ sizeClass: "Small SD-compatible",
250762
+ quality: "Fast validation model; not a serious photorealism baseline.",
250763
+ minVramGB: 4,
250764
+ recommendedVramGB: 8,
250765
+ deployment: "Use to verify the local Diffusers stack works before pulling large models.",
250527
250766
  steps: 20,
250528
250767
  guidance: 7,
250529
250768
  width: 512,
@@ -250535,6 +250774,12 @@ var init_image_generate = __esm({
250535
250774
  label: "BK-SDM Tiny 2M",
250536
250775
  backend: "diffusers",
250537
250776
  install: 'python .omnius/image-gen/diffusers_text2image.py --model nota-ai/bk-sdm-tiny-2m --prompt "..." --output .omnius/images/out.png',
250777
+ category: "Lightweight smoke test",
250778
+ sizeClass: "Compressed SD-compatible",
250779
+ quality: "Very small and practical; quality is mainly for tests and rough drafts.",
250780
+ minVramGB: 4,
250781
+ recommendedVramGB: 8,
250782
+ deployment: "Low-friction compressed Stable Diffusion-style model.",
250538
250783
  steps: 20,
250539
250784
  guidance: 7,
250540
250785
  width: 512,
@@ -250546,6 +250791,12 @@ var init_image_generate = __esm({
250546
250791
  label: "BK-SDM Small 2M",
250547
250792
  backend: "diffusers",
250548
250793
  install: 'python .omnius/image-gen/diffusers_text2image.py --model nota-ai/bk-sdm-small-2m --prompt "..." --output .omnius/images/out.png',
250794
+ category: "Lightweight smoke test",
250795
+ sizeClass: "Compressed SD-compatible",
250796
+ quality: "Slightly better compressed-SD quality than tiny variants; still not a high-fidelity baseline.",
250797
+ minVramGB: 4,
250798
+ recommendedVramGB: 8,
250799
+ deployment: "Small quality/size tradeoff for weak hardware.",
250549
250800
  steps: 20,
250550
250801
  guidance: 7,
250551
250802
  width: 512,
@@ -250557,6 +250808,12 @@ var init_image_generate = __esm({
250557
250808
  label: "LCM DreamShaper v7",
250558
250809
  backend: "diffusers",
250559
250810
  install: 'python .omnius/image-gen/diffusers_text2image.py --model SimianLuo/LCM_Dreamshaper_v7 --steps 4 --prompt "..." --output .omnius/images/out.png',
250811
+ category: "Fast iteration",
250812
+ sizeClass: "Few-step SD-compatible",
250813
+ quality: "Good for low-latency concepting; below SDXL/SD3.5/FLUX for photoreal detail.",
250814
+ minVramGB: 6,
250815
+ recommendedVramGB: 8,
250816
+ deployment: "Few-step latent-consistency route.",
250560
250817
  steps: 4,
250561
250818
  guidance: 8,
250562
250819
  width: 512,
@@ -250568,6 +250825,12 @@ var init_image_generate = __esm({
250568
250825
  label: "SD-Turbo",
250569
250826
  backend: "diffusers",
250570
250827
  install: 'python .omnius/image-gen/diffusers_text2image.py --model stabilityai/sd-turbo --steps 1 --guidance 0 --prompt "..." --output .omnius/images/out.png',
250828
+ category: "Fast iteration",
250829
+ sizeClass: "One-to-four-step SD",
250830
+ quality: "Fast SD-family output; useful for iteration but lower ceiling than SDXL Turbo and large baselines.",
250831
+ minVramGB: 6,
250832
+ recommendedVramGB: 8,
250833
+ deployment: "Check Stability license for your use case.",
250571
250834
  steps: 1,
250572
250835
  guidance: 0,
250573
250836
  width: 512,
@@ -250575,10 +250838,16 @@ var init_image_generate = __esm({
250575
250838
  note: "One-to-four-step Stable Diffusion family model; check Stability license."
250576
250839
  },
250577
250840
  {
250578
- id: "stabilityai/sdxl-turbo",
250841
+ id: DEFAULT_DIFFUSERS_IMAGE_MODEL,
250579
250842
  label: "SDXL-Turbo",
250580
250843
  backend: "diffusers",
250581
250844
  install: 'python .omnius/image-gen/diffusers_text2image.py --model stabilityai/sdxl-turbo --steps 1 --guidance 0 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250845
+ category: "Default local generation",
250846
+ sizeClass: "Few-step SDXL",
250847
+ quality: "Strong fast default for local image generation; not as realistic as FLUX.1 dev or SD3.5 Large, but much more practical.",
250848
+ minVramGB: 8,
250849
+ recommendedVramGB: 12,
250850
+ deployment: "Auto-installed first-run Diffusers default.",
250582
250851
  steps: 1,
250583
250852
  guidance: 0,
250584
250853
  width: 1024,
@@ -250590,6 +250859,12 @@ var init_image_generate = __esm({
250590
250859
  label: "Sana Sprint 0.6B",
250591
250860
  backend: "diffusers",
250592
250861
  install: 'python .omnius/image-gen/diffusers_text2image.py --model Efficient-Large-Model/Sana_Sprint_0.6B_1024px_diffusers --steps 4 --guidance 0 --width 1024 --height 1024 --prompt "..." --output .omnius/images/out.png',
250862
+ category: "Modern efficient",
250863
+ sizeClass: "0.6B efficient diffusion",
250864
+ quality: "Modern efficient output under smaller compute budgets; below primary large baselines.",
250865
+ minVramGB: 8,
250866
+ recommendedVramGB: 12,
250867
+ deployment: "Efficient Diffusers path for consumer VRAM.",
250593
250868
  steps: 4,
250594
250869
  guidance: 0,
250595
250870
  width: 1024,
@@ -250601,6 +250876,12 @@ var init_image_generate = __esm({
250601
250876
  label: "stable-diffusion.cpp local checkpoint",
250602
250877
  backend: "sdcpp",
250603
250878
  install: 'python .omnius/image-gen/sdcpp_text2image.py --model-path /path/to/model.gguf --prompt "..." --output .omnius/images/out.png',
250879
+ category: "Local checkpoint/GGUF",
250880
+ sizeClass: "Depends on checkpoint",
250881
+ quality: "Quality depends entirely on the local checkpoint or GGUF variant.",
250882
+ minVramGB: 0,
250883
+ recommendedVramGB: 8,
250884
+ deployment: "CPU/GGUF/checkpoint route for custom local workflows.",
250604
250885
  steps: 20,
250605
250886
  width: 512,
250606
250887
  height: 512,
@@ -250623,6 +250904,21 @@ def _device():
250623
250904
  return "mps"
250624
250905
  return "cpu"
250625
250906
 
250907
+ def _pipeline_class(model):
250908
+ lowered = model.lower()
250909
+ if "flux" in lowered:
250910
+ from diffusers import FluxPipeline
250911
+ return FluxPipeline
250912
+ if "stable-diffusion-3.5" in lowered or "stable_diffusion_3" in lowered or "sd3" in lowered:
250913
+ from diffusers import StableDiffusion3Pipeline
250914
+ return StableDiffusion3Pipeline
250915
+ from diffusers import AutoPipelineForText2Image
250916
+ return AutoPipelineForText2Image
250917
+
250918
+ def _large_model(model):
250919
+ lowered = model.lower()
250920
+ return any(token in lowered for token in ["flux.1", "flux.2", "stable-diffusion-3.5", "hunyuan", "z-image", "janus"])
250921
+
250626
250922
  def main():
250627
250923
  parser = argparse.ArgumentParser()
250628
250924
  parser.add_argument("--model", required=True)
@@ -250639,7 +250935,6 @@ def main():
250639
250935
 
250640
250936
  t0 = time.perf_counter()
250641
250937
  import torch
250642
- from diffusers import AutoPipelineForText2Image
250643
250938
 
250644
250939
  device = _device() if args.device == "auto" else args.device
250645
250940
  dtype = torch.float16 if device == "cuda" else torch.float32
@@ -250648,17 +250943,25 @@ def main():
250648
250943
  kwargs["variant"] = args.variant
250649
250944
 
250650
250945
  try:
250651
- pipe = AutoPipelineForText2Image.from_pretrained(args.model, **kwargs)
250946
+ pipeline_cls = _pipeline_class(args.model)
250947
+ pipe = pipeline_cls.from_pretrained(args.model, **kwargs)
250652
250948
  except Exception:
250653
250949
  kwargs.pop("variant", None)
250654
- pipe = AutoPipelineForText2Image.from_pretrained(args.model, **kwargs)
250950
+ pipeline_cls = _pipeline_class(args.model)
250951
+ pipe = pipeline_cls.from_pretrained(args.model, **kwargs)
250655
250952
 
250656
250953
  if hasattr(pipe, "enable_attention_slicing"):
250657
250954
  try:
250658
250955
  pipe.enable_attention_slicing()
250659
250956
  except Exception:
250660
250957
  pass
250661
- pipe = pipe.to(device)
250958
+ if device == "cuda" and _large_model(args.model) and hasattr(pipe, "enable_model_cpu_offload"):
250959
+ try:
250960
+ pipe.enable_model_cpu_offload()
250961
+ except Exception:
250962
+ pipe = pipe.to(device)
250963
+ else:
250964
+ pipe = pipe.to(device)
250662
250965
 
250663
250966
  generator = None
250664
250967
  if args.seed is not None:
@@ -250739,7 +251042,7 @@ if __name__ == "__main__":
250739
251042
  `;
250740
251043
  ImageGenerateTool = class {
250741
251044
  name = "generate_image";
250742
- description = "Generate an image from a text prompt using a local image-generation backend. Supports Ollama image models (x/z-image-turbo, x/flux2-klein), Python Diffusers models (Tiny-SD, BK-SDM, LCM, SD-Turbo, Sana Sprint), and stable-diffusion.cpp local checkpoints/GGUF. Saves a PNG under .omnius/images and returns the file path.";
251045
+ description = "Generate an image from a text prompt using a local image-generation backend. Supports Ollama image models (x/z-image-turbo, x/flux2-klein), Python Diffusers models (SDXL Turbo default, FLUX.1 dev, SD3.5 Large, Tiny-SD, LCM, Sana Sprint), and stable-diffusion.cpp local checkpoints/GGUF. Saves a PNG under .omnius/images and returns the file path.";
250743
251046
  parameters = {
250744
251047
  type: "object",
250745
251048
  properties: {
@@ -250838,11 +251141,11 @@ if __name__ == "__main__":
250838
251141
  let backend = inferImageGenerationBackend(requestedModel, requestedBackend);
250839
251142
  let model = requestedModel;
250840
251143
  if (backend === "auto") {
250841
- model = await this.findImageGenModel() ?? void 0;
250842
- backend = model ? "ollama" : "diffusers";
251144
+ backend = "diffusers";
251145
+ model = DEFAULT_DIFFUSERS_IMAGE_MODEL;
250843
251146
  }
250844
251147
  if (!model) {
250845
- model = backend === "diffusers" ? "segmind/tiny-sd" : "x/z-image-turbo";
251148
+ model = backend === "diffusers" ? DEFAULT_DIFFUSERS_IMAGE_MODEL : DEFAULT_OLLAMA_IMAGE_MODEL;
250846
251149
  }
250847
251150
  if (backend === "ollama") {
250848
251151
  return await this.generateWithOllama({ prompt, model, width, height, steps, start: start2 });
@@ -250934,7 +251237,23 @@ ${errText.slice(0, 800)}`,
250934
251237
  const runner = await ensureRunner(this.cwd, "diffusers");
250935
251238
  await mkdir11(join36(this.cwd, ".omnius", "images"), { recursive: true });
250936
251239
  const filepath = outputPath(this.cwd);
250937
- const command = pythonFor(this.cwd, "diffusers", typeof args.python === "string" ? args.python : void 0);
251240
+ let python;
251241
+ try {
251242
+ python = await ensurePythonFor(this.cwd, "diffusers", typeof args.python === "string" ? args.python : void 0);
251243
+ } catch (err) {
251244
+ const plan = imageGenerationSetupPlan("diffusers", this.cwd, args.model);
251245
+ return {
251246
+ success: false,
251247
+ output: [
251248
+ `Diffusers setup failed before image generation.`,
251249
+ err instanceof Error ? err.message : String(err),
251250
+ "",
251251
+ "Setup path:",
251252
+ ...plan.commands.map((cmd) => ` ${cmd}`)
251253
+ ].filter(Boolean).join("\n"),
251254
+ durationMs: performance.now() - args.start
251255
+ };
251256
+ }
250938
251257
  const argv = [
250939
251258
  runner,
250940
251259
  "--model",
@@ -250954,7 +251273,7 @@ ${errText.slice(0, 800)}`,
250954
251273
  ];
250955
251274
  if (args.seed !== void 0)
250956
251275
  argv.push("--seed", String(args.seed));
250957
- const result = await runProcess2(command, argv, { cwd: this.cwd, timeoutMs: 9e5 });
251276
+ const result = await runProcess2(python.command, argv, { cwd: this.cwd, timeoutMs: 9e5, env: python.env });
250958
251277
  if (result.code !== 0 || !existsSync23(filepath)) {
250959
251278
  const plan = imageGenerationSetupPlan("diffusers", this.cwd, args.model);
250960
251279
  return {
@@ -251008,7 +251327,23 @@ ${errText.slice(0, 800)}`,
251008
251327
  const runner = await ensureRunner(this.cwd, "sdcpp");
251009
251328
  await mkdir11(join36(this.cwd, ".omnius", "images"), { recursive: true });
251010
251329
  const filepath = outputPath(this.cwd);
251011
- const command = pythonFor(this.cwd, "sdcpp", typeof args.python === "string" ? args.python : void 0);
251330
+ let python;
251331
+ try {
251332
+ python = await ensurePythonFor(this.cwd, "sdcpp", typeof args.python === "string" ? args.python : void 0);
251333
+ } catch (err) {
251334
+ const plan = imageGenerationSetupPlan("sdcpp", this.cwd, args.model);
251335
+ return {
251336
+ success: false,
251337
+ output: [
251338
+ `stable-diffusion.cpp setup failed before image generation.`,
251339
+ err instanceof Error ? err.message : String(err),
251340
+ "",
251341
+ "Setup path:",
251342
+ ...plan.commands.map((cmd) => ` ${cmd}`)
251343
+ ].filter(Boolean).join("\n"),
251344
+ durationMs: performance.now() - args.start
251345
+ };
251346
+ }
251012
251347
  const argv = [
251013
251348
  runner,
251014
251349
  "--model-path",
@@ -251026,7 +251361,7 @@ ${errText.slice(0, 800)}`,
251026
251361
  ];
251027
251362
  if (args.seed !== void 0)
251028
251363
  argv.push("--seed", String(args.seed));
251029
- const result = await runProcess2(command, argv, { cwd: this.cwd, timeoutMs: 9e5 });
251364
+ const result = await runProcess2(python.command, argv, { cwd: this.cwd, timeoutMs: 9e5, env: python.env });
251030
251365
  if (result.code !== 0 || !existsSync23(filepath)) {
251031
251366
  const plan = imageGenerationSetupPlan("sdcpp", this.cwd, args.model);
251032
251367
  return {
@@ -512881,6 +513216,8 @@ __export(dist_exports, {
512881
513216
  CreateToolTool: () => CreateToolTool,
512882
513217
  CronAgentTool: () => CronAgentTool,
512883
513218
  CustomTool: () => CustomTool,
513219
+ DEFAULT_DIFFUSERS_IMAGE_MODEL: () => DEFAULT_DIFFUSERS_IMAGE_MODEL,
513220
+ DEFAULT_OLLAMA_IMAGE_MODEL: () => DEFAULT_OLLAMA_IMAGE_MODEL,
512884
513221
  DESKTOP_DEPS: () => DESKTOP_DEPS,
512885
513222
  DebateTool: () => DebateTool,
512886
513223
  DesktopClickTool: () => DesktopClickTool,
@@ -513002,6 +513339,7 @@ __export(dist_exports, {
513002
513339
  deleteTodos: () => deleteTodos,
513003
513340
  detectElevationMethod: () => detectElevationMethod,
513004
513341
  detectSearchProvider: () => detectSearchProvider,
513342
+ diffusersVenvDir: () => diffusersVenvDir,
513005
513343
  discoverPlugins: () => discoverPlugins,
513006
513344
  discoverSkills: () => discoverSkills,
513007
513345
  emitIndexed: () => emitIndexed,
@@ -513034,6 +513372,7 @@ __export(dist_exports, {
513034
513372
  getWorkingNotesSummary: () => getWorkingNotesSummary,
513035
513373
  getWorktreeSession: () => getWorktreeSession,
513036
513374
  hashGeneratedArtifactContent: () => hashGeneratedArtifactContent,
513375
+ imageGenerationDir: () => imageGenerationDir,
513037
513376
  imageGenerationSetupPlan: () => imageGenerationSetupPlan,
513038
513377
  inferImageGenerationBackend: () => inferImageGenerationBackend,
513039
513378
  isFortemiAvailable: () => isFortemiAvailable,
@@ -513084,6 +513423,7 @@ __export(dist_exports, {
513084
513423
  saveCustomToolDefinition: () => saveCustomToolDefinition,
513085
513424
  saveMcpServerToConfig: () => saveMcpServerToConfig,
513086
513425
  savePacket: () => savePacket,
513426
+ sdcppVenvDir: () => sdcppVenvDir,
513087
513427
  serializeMap: () => serializeMap,
513088
513428
  setChangeLogSession: () => setChangeLogSession,
513089
513429
  setSudoPassword: () => setSudoPassword,
@@ -526549,6 +526889,14 @@ var init_app_state = __esm({
526549
526889
  });
526550
526890
 
526551
526891
  // packages/orchestrator/dist/streaming-executor.js
526892
+ function stableValueKey(value2) {
526893
+ if (value2 === null || typeof value2 !== "object")
526894
+ return JSON.stringify(value2);
526895
+ if (Array.isArray(value2))
526896
+ return `[${value2.map(stableValueKey).join(",")}]`;
526897
+ const record = value2;
526898
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableValueKey(record[key])}`).join(",")}}`;
526899
+ }
526552
526900
  var StreamingToolExecutor;
526553
526901
  var init_streaming_executor = __esm({
526554
526902
  "packages/orchestrator/dist/streaming-executor.js"() {
@@ -526700,6 +527048,62 @@ var init_streaming_executor = __esm({
526700
527048
  return true;
526701
527049
  return false;
526702
527050
  }
527051
+ entryFingerprint(entry) {
527052
+ return `${entry.name}:${stableValueKey(entry.args)}`;
527053
+ }
527054
+ findPriorEquivalent(entry) {
527055
+ const entryIdx = this.insertionOrder.indexOf(entry.id);
527056
+ if (entryIdx <= 0 || !entry.finalized)
527057
+ return null;
527058
+ const fp = this.entryFingerprint(entry);
527059
+ for (let i2 = 0; i2 < entryIdx; i2++) {
527060
+ const prior = this.tools.get(this.insertionOrder[i2]);
527061
+ if (!prior || !prior.finalized)
527062
+ continue;
527063
+ if (this.entryFingerprint(prior) === fp)
527064
+ return prior;
527065
+ }
527066
+ return null;
527067
+ }
527068
+ cloneDuplicateResult(prior) {
527069
+ if (!prior.result)
527070
+ return null;
527071
+ const prefix = `[DUPLICATE STREAM TOOL CALL — reused result from ${prior.id}]
527072
+ `;
527073
+ return {
527074
+ success: prior.result.success,
527075
+ output: prior.result.output ? `${prefix}${prior.result.output}` : prior.result.output,
527076
+ error: prior.result.error
527077
+ };
527078
+ }
527079
+ mirrorPriorEquivalent(entry) {
527080
+ const prior = this.findPriorEquivalent(entry);
527081
+ if (!prior)
527082
+ return false;
527083
+ if ((prior.state === "completed" || prior.state === "failed" || prior.state === "yielded") && prior.result) {
527084
+ entry.state = prior.result.success ? "completed" : "failed";
527085
+ entry.result = this.cloneDuplicateResult(prior) ?? prior.result;
527086
+ entry.startedAt = prior.startedAt;
527087
+ entry.completedAt = prior.completedAt ?? Date.now();
527088
+ return true;
527089
+ }
527090
+ if (prior.state === "executing" && prior.promise) {
527091
+ entry.state = "executing";
527092
+ entry.startedAt = prior.startedAt ?? Date.now();
527093
+ entry.promise = prior.promise.then(() => {
527094
+ entry.result = this.cloneDuplicateResult(prior) ?? {
527095
+ success: false,
527096
+ output: "",
527097
+ error: "Duplicate streaming tool call could not reuse prior result"
527098
+ };
527099
+ entry.state = entry.result.success ? "completed" : "failed";
527100
+ entry.completedAt = Date.now();
527101
+ this.processQueue();
527102
+ });
527103
+ return true;
527104
+ }
527105
+ return false;
527106
+ }
526703
527107
  /**
526704
527108
  * Process the queue in insertion order.
526705
527109
  * Starts tools that can execute, stops at first exclusive tool that must wait.
@@ -526709,6 +527113,8 @@ var init_streaming_executor = __esm({
526709
527113
  const entry = this.tools.get(id);
526710
527114
  if (!entry || entry.state !== "queued")
526711
527115
  continue;
527116
+ if (this.mirrorPriorEquivalent(entry))
527117
+ continue;
526712
527118
  if (this.canExecute(entry)) {
526713
527119
  this.startExecution(entry);
526714
527120
  } else if (!entry.concurrencySafe) {
@@ -535466,6 +535872,7 @@ ${sr.result.output}`;
535466
535872
  this.emit({
535467
535873
  type: "assistant_text",
535468
535874
  content: summary,
535875
+ source: "task_complete_summary",
535469
535876
  turn,
535470
535877
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
535471
535878
  });
@@ -535506,6 +535913,7 @@ ${sr.result.output}`;
535506
535913
  this.emit({
535507
535914
  type: "assistant_text",
535508
535915
  content: summary,
535916
+ source: "task_complete_summary",
535509
535917
  turn,
535510
535918
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
535511
535919
  });
@@ -535581,6 +535989,7 @@ ${sr.result.output}`;
535581
535989
  this.emit({
535582
535990
  type: "assistant_text",
535583
535991
  content: summary,
535992
+ source: "task_complete_summary",
535584
535993
  turn,
535585
535994
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
535586
535995
  });
@@ -536306,6 +536715,7 @@ Full content available via: repl_exec(code="data = retrieve('${handleId}')") or
536306
536715
  this.emit({
536307
536716
  type: "assistant_text",
536308
536717
  content: summary,
536718
+ source: "task_complete_summary",
536309
536719
  turn,
536310
536720
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
536311
536721
  });
@@ -540152,6 +540562,7 @@ ${description}`
540152
540562
  this.emit({
540153
540563
  type: "assistant_text",
540154
540564
  content: cleanContent,
540565
+ source: "model_visible_text",
540155
540566
  turn,
540156
540567
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
540157
540568
  });
@@ -550151,7 +550562,7 @@ var init_command_registry = __esm({
550151
550562
  ["/image <prompt>", "Generate an image from a prompt and show an ASCII preview"],
550152
550563
  ["/image --model <model> <prompt>", "Generate with an explicit image model"],
550153
550564
  ["/image setup <ollama|diffusers|sdcpp>", "Show setup commands for an image-generation backend"],
550154
- ["/image models", "List known lightweight/local image-generation model presets"],
550565
+ ["/image list", "List image models by category, quality, size, and hardware fit"],
550155
550566
  ["/call", "Start voice call session (cloudflared tunnel + ASR/TTS)"],
550156
550567
  ["/hangup", "End active call session"],
550157
550568
  ["/queue <prompt>", "Queue a prompt for the next turn without interrupting the current run"],
@@ -572210,14 +572621,16 @@ ${preview.ascii}`;
572210
572621
  function extractSavedImagePath(text, repoRoot) {
572211
572622
  const patterns = [
572212
572623
  /Image generated:\s*([^\n\r]+)/i,
572624
+ /Screenshot saved:\s*([^\n\r]+)/i,
572625
+ /Screenshot:\s*([^\n\r]+)/i,
572213
572626
  /Saved to:\s*([^\n\r]+)/i,
572214
572627
  /Image attached:\s*([^\n\r]+)/i,
572215
- /image saved at\s+([^\n\r.]+)/i
572628
+ /image saved at\s+([^\n\r]+)/i
572216
572629
  ];
572217
572630
  for (const pattern of patterns) {
572218
572631
  const match = text.match(pattern);
572219
572632
  if (!match?.[1]) continue;
572220
- const raw = match[1].trim().replace(/^["']|["']$/g, "");
572633
+ const raw = match[1].trim().replace(/\s+\([^)]+\)\s*$/g, "").replace(/^["']|["']$/g, "");
572221
572634
  const candidate = raw.startsWith("/") ? raw : resolve37(repoRoot, raw);
572222
572635
  if (existsSync93(candidate)) return candidate;
572223
572636
  }
@@ -582555,18 +582968,123 @@ function parseImageNumber(value2) {
582555
582968
  const n2 = Number(value2);
582556
582969
  return Number.isFinite(n2) ? n2 : void 0;
582557
582970
  }
582971
+ function rateImagePresetForHardware(preset, specs) {
582972
+ const min = preset.minVramGB ?? (preset.backend === "sdcpp" ? 0 : 8);
582973
+ const recommended = preset.recommendedVramGB ?? Math.max(min, 12);
582974
+ const vram = specs.gpuVramGB;
582975
+ const ram = Math.max(specs.availableRamGB, specs.totalRamGB * 0.65);
582976
+ if (preset.id === "deepseek-ai/Janus-Pro-7B") {
582977
+ const base3 = vram >= recommended ? 65 : vram >= min ? 50 : 25;
582978
+ return {
582979
+ score: base3,
582980
+ label: base3 >= 60 ? "experimental" : "not turnkey",
582981
+ note: "Experimental multimodal model; use a dedicated Janus runtime, not the generic Diffusers runner."
582982
+ };
582983
+ }
582984
+ if (min <= 0) {
582985
+ const score2 = vram >= recommended ? 85 : vram > 0 ? 70 : ram >= 32 ? 45 : 25;
582986
+ return {
582987
+ score: score2,
582988
+ label: score2 >= 80 ? "comfortable" : score2 >= 60 ? "workable" : "checkpoint-dependent",
582989
+ note: vram > 0 ? "Fit depends on the local checkpoint/GGUF size and runtime flags." : "CPU/GGUF path is possible but will be slow; choose small quantized checkpoints."
582990
+ };
582991
+ }
582992
+ let score;
582993
+ let label;
582994
+ let note;
582995
+ if (vram >= recommended) {
582996
+ score = Math.min(100, Math.round(90 + Math.min(10, (vram - recommended) / Math.max(1, recommended) * 10)));
582997
+ label = "excellent";
582998
+ note = `Runs well on ${vram.toFixed(0)}GB VRAM; enough headroom for ${preset.sizeClass ?? "this model"}.`;
582999
+ } else if (vram >= min) {
583000
+ score = Math.round(70 + (vram - min) / Math.max(1, recommended - min) * 18);
583001
+ label = "comfortable";
583002
+ note = `Fits the stated ${min.toFixed(0)}GB minimum; expect lower batching/headroom than the ${recommended.toFixed(0)}GB target.`;
583003
+ } else if (vram > 0 && ram >= recommended * 1.5) {
583004
+ score = 52;
583005
+ label = "offload";
583006
+ note = `Below ${min.toFixed(0)}GB VRAM; may work with CPU offload or quantization, but latency and reliability will suffer.`;
583007
+ } else if (ram >= recommended * 2) {
583008
+ score = 35;
583009
+ label = "cloud/quant";
583010
+ note = "System RAM is large enough for experiments, but VRAM is below target; prefer quantized runtimes or cloud GPU.";
583011
+ } else {
583012
+ score = 18;
583013
+ label = "too heavy";
583014
+ note = `Needs roughly ${min.toFixed(0)}GB VRAM minimum and ${recommended.toFixed(0)}GB recommended.`;
583015
+ }
583016
+ return { score, label, note };
583017
+ }
583018
+ function imageFitIcon(score) {
583019
+ if (score >= 85) return c3.green("✔");
583020
+ if (score >= 60) return c3.green("◐");
583021
+ if (score >= 40) return c3.yellow("△");
583022
+ return c3.red("✖");
583023
+ }
583024
+ function wrapImageListText(text, width = 94) {
583025
+ const words = text.split(/\s+/).filter(Boolean);
583026
+ const lines = [];
583027
+ let line = "";
583028
+ for (const word2 of words) {
583029
+ if (!line) line = word2;
583030
+ else if (line.length + 1 + word2.length <= width) line += ` ${word2}`;
583031
+ else {
583032
+ lines.push(line);
583033
+ line = word2;
583034
+ }
583035
+ }
583036
+ if (line) lines.push(line);
583037
+ return lines.length > 0 ? lines : [""];
583038
+ }
583039
+ function renderImagePresetDetail(prefix, text) {
583040
+ const [first2, ...rest] = wrapImageListText(text, 92);
583041
+ renderInfo(`${prefix}${first2}`);
583042
+ for (const line of rest) renderInfo(`${" ".repeat(prefix.length)}${line}`);
583043
+ }
583044
+ function renderImageModelList() {
583045
+ const specs = detectSystemSpecs();
583046
+ const hardware = `${specs.totalRamGB.toFixed(0)}GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)}GB VRAM (${specs.gpuName || "NVIDIA GPU"})` : " + no NVIDIA VRAM detected");
583047
+ renderInfo(`Image models for this hardware: ${hardware}`);
583048
+ renderInfo("Fit legend: 85+ excellent, 60+ comfortable, 40+ offload/quantized, below 40 heavy/cloud.");
583049
+ renderInfo("Primary hyper-realistic baselines: FLUX.1 dev and Stable Diffusion 3.5 Large.");
583050
+ const byCategory = /* @__PURE__ */ new Map();
583051
+ for (const preset of IMAGE_GENERATION_MODEL_PRESETS) {
583052
+ const category = preset.category ?? "Other";
583053
+ const list = byCategory.get(category) ?? [];
583054
+ list.push(preset);
583055
+ byCategory.set(category, list);
583056
+ }
583057
+ for (const [category, presets] of byCategory) {
583058
+ renderInfo("");
583059
+ renderInfo(c3.bold(category));
583060
+ for (const preset of presets) {
583061
+ const fit2 = rateImagePresetForHardware(preset, specs);
583062
+ const primary = category === "Primary hyper-realistic baseline" ? c3.cyan(" ★") : "";
583063
+ renderInfo(`${imageFitIcon(fit2.score)} ${String(fit2.score).padStart(3)}/100 ${c3.bold(preset.label)}${primary}`);
583064
+ renderInfo(` id: ${preset.id}`);
583065
+ renderInfo(` type: ${preset.backend} · ${preset.sizeClass ?? "unknown size"} · ${fit2.label}`);
583066
+ renderImagePresetDetail(" quality: ", preset.quality ?? preset.note);
583067
+ renderImagePresetDetail(" fit: ", fit2.note);
583068
+ if (preset.deployment) renderImagePresetDetail(" deploy: ", preset.deployment);
583069
+ }
583070
+ }
583071
+ }
582558
583072
  async function showImageModelsMenu(ctx3, hasLocal) {
582559
583073
  const settings = resolveSettings(ctx3.repoRoot);
583074
+ const specs = detectSystemSpecs();
582560
583075
  const items = [
582561
583076
  { key: "setup:ollama", label: "Setup Ollama", detail: "Pull x/z-image-turbo or x/flux2-klein" },
582562
- { key: "setup:diffusers", label: "Setup Diffusers", detail: "Python venv for Tiny-SD, BK-SDM, SD-Turbo, Sana" },
583077
+ { key: "setup:diffusers", label: "Setup Diffusers", detail: "Auto-installs SDXL Turbo under .omnius/image-gen/.venv" },
582563
583078
  { key: "setup:sdcpp", label: "Setup stable-diffusion.cpp", detail: "CPU/GGUF/checkpoint route" },
582564
583079
  { key: "hdr:models", label: selectColors.dim("─── Models ───") },
582565
- ...IMAGE_GENERATION_MODEL_PRESETS.map((preset) => ({
582566
- key: `model:${preset.id}`,
582567
- label: preset.label,
582568
- detail: `${preset.backend} · ${preset.id} · ${preset.note}`
582569
- }))
583080
+ ...IMAGE_GENERATION_MODEL_PRESETS.map((preset) => {
583081
+ const fit2 = rateImagePresetForHardware(preset, specs);
583082
+ return {
583083
+ key: `model:${preset.id}`,
583084
+ label: preset.label,
583085
+ detail: `${fit2.score}/100 ${fit2.label} · ${preset.category ?? preset.backend} · ${preset.sizeClass ?? preset.id}`
583086
+ };
583087
+ })
582570
583088
  ];
582571
583089
  const result = await tuiSelect({
582572
583090
  items,
@@ -582602,10 +583120,8 @@ async function handleImageCommand(ctx3, arg, hasLocal) {
582602
583120
  await showImageModelsMenu(ctx3, hasLocal);
582603
583121
  return "handled";
582604
583122
  }
582605
- if (parsed.subcommand === "models") {
582606
- for (const preset of IMAGE_GENERATION_MODEL_PRESETS) {
582607
- renderInfo(`${preset.id} [${preset.backend}] ${preset.note}`);
582608
- }
583123
+ if (parsed.subcommand === "models" || parsed.subcommand === "list") {
583124
+ renderImageModelList();
582609
583125
  return "handled";
582610
583126
  }
582611
583127
  if (parsed.subcommand === "setup") {
@@ -582617,7 +583133,7 @@ async function handleImageCommand(ctx3, arg, hasLocal) {
582617
583133
  for (const note of plan.notes) renderInfo(`- ${note}`);
582618
583134
  return "handled";
582619
583135
  }
582620
- const model = String(parsed.flags["model"] ?? settings.imageModel ?? "auto");
583136
+ const model = String(parsed.flags["model"] ?? settings.imageModel ?? DEFAULT_DIFFUSERS_IMAGE_MODEL);
582621
583137
  const backend = String(parsed.flags["backend"] ?? settings.imageBackend ?? inferImageGenerationBackend(model, void 0));
582622
583138
  const tool = new ImageGenerateTool(ctx3.repoRoot, ctx3.config.backendUrl);
582623
583139
  const prompt = parsed.prompt;
@@ -593406,6 +593922,13 @@ function sanitizeTelegramProgressText(text, maxLength) {
593406
593922
  function compactTelegramVisibleText(text) {
593407
593923
  return stripTelegramHiddenThinking(text).replace(/\s+/g, " ").trim();
593408
593924
  }
593925
+ function stableTelegramValueKey(value2) {
593926
+ if (value2 === void 0) return "undefined";
593927
+ if (value2 === null || typeof value2 !== "object") return JSON.stringify(value2) ?? String(value2);
593928
+ if (Array.isArray(value2)) return `[${value2.map(stableTelegramValueKey).join(",")}]`;
593929
+ const record = value2;
593930
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableTelegramValueKey(record[key])}`).join(",")}}`;
593931
+ }
593409
593932
  function isTelegramPotentialNoReplyPrefix(text) {
593410
593933
  const lower = compactTelegramVisibleText(text).toLowerCase();
593411
593934
  return Boolean(lower) && "no_reply".startsWith(lower);
@@ -593436,7 +593959,39 @@ function cleanTelegramVisibleReply(text, options2 = {}) {
593436
593959
  if (!clean3) return "";
593437
593960
  if (options2.suppressPotentialNoReplyPrefix && isTelegramPotentialNoReplyPrefix(clean3)) return "";
593438
593961
  if (isTelegramInternalStatusText(clean3)) return "";
593439
- return clean3;
593962
+ return dedupeTelegramVisibleReply(clean3);
593963
+ }
593964
+ function dedupeTelegramVisibleReply(text) {
593965
+ const paragraphs = text.split(/\n{2,}/);
593966
+ const seenParagraphs = /* @__PURE__ */ new Set();
593967
+ const collapsedParagraphs = [];
593968
+ for (const paragraph of paragraphs) {
593969
+ const clean3 = paragraph.trim();
593970
+ if (!clean3) continue;
593971
+ const key = compactTelegramVisibleText(clean3).toLowerCase();
593972
+ if (seenParagraphs.has(key)) continue;
593973
+ seenParagraphs.add(key);
593974
+ collapsedParagraphs.push(clean3);
593975
+ }
593976
+ const paragraphCollapsed = collapsedParagraphs.join("\n\n");
593977
+ const sentenceLike = paragraphCollapsed.match(/[^.!?]+[.!?]+|[^.!?]+$/g);
593978
+ if (!sentenceLike || sentenceLike.length < 3) return paragraphCollapsed;
593979
+ const seenSentences = /* @__PURE__ */ new Set();
593980
+ const out = [];
593981
+ let duplicates = 0;
593982
+ for (const raw of sentenceLike) {
593983
+ const sentence = raw.trim();
593984
+ if (!sentence) continue;
593985
+ const key = sentence.replace(/\s+/g, " ").toLowerCase();
593986
+ if (seenSentences.has(key)) {
593987
+ duplicates++;
593988
+ continue;
593989
+ }
593990
+ seenSentences.add(key);
593991
+ out.push(sentence);
593992
+ }
593993
+ if (duplicates === 0) return paragraphCollapsed;
593994
+ return out.join(" ").trim();
593440
593995
  }
593441
593996
  function truncateTelegramContext(text, maxLength) {
593442
593997
  const trimmed = text.trim();
@@ -593560,11 +594115,10 @@ function selectTelegramFinalResponse(args) {
593560
594115
  args.assistantText
593561
594116
  ].map((candidate) => cleanTelegramVisibleReply(candidate || "")).filter(Boolean);
593562
594117
  if (visibleCandidates.length > 0) {
593563
- return visibleCandidates.reduce(
593564
- (best, current) => current.length > best.length ? current : best
593565
- );
594118
+ return visibleCandidates[0];
593566
594119
  }
593567
- return cleanTelegramVisibleReply(args.summary || "");
594120
+ void args.summary;
594121
+ return "";
593568
594122
  }
593569
594123
  function formatTelegramProgressEvent(event) {
593570
594124
  if (event.type === "tool_call" && event.toolName === "task_complete") return null;
@@ -593853,6 +594407,26 @@ function telegramImageMime(media) {
593853
594407
  if (ext === ".tif" || ext === ".tiff") return "image/tiff";
593854
594408
  return "image/jpeg";
593855
594409
  }
594410
+ function extractTelegramMentionedUsernames(message2, text) {
594411
+ const usernames = /* @__PURE__ */ new Set();
594412
+ const entities = [
594413
+ ...Array.isArray(message2.entities) ? message2.entities : [],
594414
+ ...Array.isArray(message2.caption_entities) ? message2.caption_entities : []
594415
+ ];
594416
+ for (const entity of entities) {
594417
+ if (!entity || typeof entity !== "object") continue;
594418
+ if (entity.type === "mention") {
594419
+ const offset = Number(entity.offset);
594420
+ const length4 = Number(entity.length);
594421
+ if (!Number.isFinite(offset) || !Number.isFinite(length4) || length4 <= 1) continue;
594422
+ const mention = text.slice(offset, offset + length4).replace(/^@/, "").trim();
594423
+ if (mention) usernames.add(mention);
594424
+ } else if (entity.type === "text_mention" && typeof entity.user?.username === "string") {
594425
+ usernames.add(entity.user.username);
594426
+ }
594427
+ }
594428
+ return [...usernames];
594429
+ }
593856
594430
  function normalizeTelegramUpdate(update2) {
593857
594431
  const sourceUpdateType = update2.guest_message ? "guest_message" : update2.message ? "message" : null;
593858
594432
  if (!sourceUpdateType) return null;
@@ -593885,6 +594459,9 @@ function normalizeTelegramUpdate(update2) {
593885
594459
  isDirectMessages: Boolean(message2.chat?.is_direct_messages),
593886
594460
  parentChatId: message2.chat?.parent_chat?.id ?? message2.direct_messages_topic?.parent_topic?.id,
593887
594461
  replyToMessageId: message2.reply_to_message?.message_id,
594462
+ replyToUsername: message2.reply_to_message?.from?.username ?? message2.reply_to_message?.sender_chat?.username,
594463
+ replyToBot: Boolean(message2.reply_to_message?.from?.is_bot),
594464
+ mentionedUsernames: extractTelegramMentionedUsernames(message2, text),
593888
594465
  sourceUpdateType
593889
594466
  };
593890
594467
  }
@@ -593943,10 +594520,10 @@ function renderTelegramStart(botUsername, adminId, mode = "auto") {
593943
594520
  if (adminId) {
593944
594521
  process.stdout.write(` ${c3.dim(`Admin: ${adminId} (full memory + tools)`)}
593945
594522
  `);
593946
- process.stdout.write(` ${c3.dim("Public users: light memory + web search only")}
594523
+ process.stdout.write(` ${c3.dim("Public users: scoped memory + web + per-chat creative file/image/audio tools")}
593947
594524
  `);
593948
594525
  }
593949
- process.stdout.write(` ${c3.dim("Safety filter: ACTIVE — public channel mode")}
594526
+ process.stdout.write(` ${c3.dim("Safety filter: ACTIVE — public channel mode; creative writes are sandboxed under .omnius/telegram-creative/<chat>")}
593950
594527
  `);
593951
594528
  process.stdout.write(` ${c3.dim("Use /telegram to toggle off, or /telegram stop")}
593952
594529
 
@@ -594222,6 +594799,8 @@ Telegram response contract:
594222
594799
  groupSkipLogAt = /* @__PURE__ */ new Map();
594223
594800
  /** Telegram interaction routing profile */
594224
594801
  interactionMode = "auto";
594802
+ /** Actual model context window discovered by the main TUI. */
594803
+ contextWindowSize = 0;
594225
594804
  /** Event handler for forwarding sub-agent events to parent TUI */
594226
594805
  onSubAgentEvent = null;
594227
594806
  /** Tool policy config — user overrides from config */
@@ -594272,6 +594851,9 @@ Telegram response contract:
594272
594851
  getInteractionMode() {
594273
594852
  return this.interactionMode;
594274
594853
  }
594854
+ setContextWindowSize(size) {
594855
+ this.contextWindowSize = Number.isFinite(size) && size > 0 ? Math.trunc(size) : 0;
594856
+ }
594275
594857
  /** Update tool policy config at runtime (e.g., from /disable command) */
594276
594858
  setToolPolicyConfig(config) {
594277
594859
  this.toolPolicyConfig = config;
@@ -594773,6 +595355,7 @@ ${lines.join("\n")}`);
594773
595355
  return sections.join("\n\n");
594774
595356
  }
594775
595357
  maybeLogTelegramGroupSkip(msg, reason) {
595358
+ if (process.env["OMNIUS_TELEGRAM_DEBUG_SKIPS"] !== "1") return;
594776
595359
  const sessionKey = this.sessionKeyForMessage(msg);
594777
595360
  const now = Date.now();
594778
595361
  const last2 = this.groupSkipLogAt.get(sessionKey) ?? 0;
@@ -594780,16 +595363,27 @@ ${lines.join("\n")}`);
594780
595363
  this.groupSkipLogAt.set(sessionKey, now);
594781
595364
  this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, `${reason} (context retained)`));
594782
595365
  }
595366
+ telegramMessageAddressesBot(msg) {
595367
+ const bot = this.state.botUsername.trim().replace(/^@/, "").toLowerCase();
595368
+ if (!bot) return false;
595369
+ const mentioned = (msg.mentionedUsernames ?? []).some(
595370
+ (name10) => name10.trim().replace(/^@/, "").toLowerCase() === bot
595371
+ );
595372
+ if (mentioned) return true;
595373
+ if (msg.replyToUsername && msg.replyToUsername.trim().replace(/^@/, "").toLowerCase() === bot) return true;
595374
+ return false;
595375
+ }
594783
595376
  async inferTelegramInteractionDecision(msg, toolContext) {
594784
595377
  const config = this.agentConfig;
594785
595378
  const forcedRoute = this.interactionMode === "chat" || this.interactionMode === "action" ? this.interactionMode : null;
594786
595379
  const isGroup = msg.chatType !== "private";
595380
+ const addressesBot = this.telegramMessageAddressesBot(msg);
594787
595381
  if (!config) {
594788
595382
  return {
594789
- route: forcedRoute ?? "action",
594790
- shouldReply: !isGroup,
595383
+ route: forcedRoute ?? (isGroup ? "action" : "chat"),
595384
+ shouldReply: !isGroup || addressesBot,
594791
595385
  confidence: 0,
594792
- reason: isGroup ? "router inference unavailable; public group fails closed without keyword heuristics" : "router inference unavailable; private chat defaults to reply",
595386
+ reason: isGroup ? addressesBot ? "router inference unavailable; Telegram message directly addresses the bot" : "router inference unavailable; public group fails closed without keyword heuristics" : "router inference unavailable; private chat defaults to quick reply",
594793
595387
  source: "inference-unavailable"
594794
595388
  };
594795
595389
  }
@@ -594810,6 +595404,7 @@ ${lines.join("\n")}`);
594810
595404
  `Route meanings:`,
594811
595405
  `- chat: a short conversational answer can be produced without tools.`,
594812
595406
  `- action: tools, workspace context, media processing, web lookup, delegation, or a multi-step agent loop may be needed.`,
595407
+ `Route discipline: greetings, acknowledgements, casual tone/style discussion, and simple conversational questions are chat. Use action only when the message asks you to inspect, create, change, send, remember, search, analyze media, or otherwise do tool-backed work.`,
594813
595408
  ``,
594814
595409
  `Reply discretion: infer from the live thread, speaker relationships, direct mentions, replies, tone, and current message. Do not use static keyword rules.`,
594815
595410
  `Private chats: should_reply is normally true.`,
@@ -594818,9 +595413,12 @@ ${lines.join("\n")}`);
594818
595413
  ``,
594819
595414
  `Tool context: ${toolContext}`,
594820
595415
  `Bot username: ${this.state.botUsername || "unknown"}`,
595416
+ `Current message directly addresses this bot: ${addressesBot ? "yes" : "no"}`,
594821
595417
  `Current chat type: ${msg.chatType}`,
594822
595418
  `Current sender: ${telegramSpeakerLabel(msg)}`,
594823
595419
  msg.replyToMessageId ? `Current message replies to message_id ${msg.replyToMessageId}` : "",
595420
+ msg.replyToUsername ? `Current message replies to @${msg.replyToUsername}` : "",
595421
+ (msg.mentionedUsernames ?? []).length > 0 ? `Current message mentions: ${(msg.mentionedUsernames ?? []).map((name10) => `@${name10}`).join(", ")}` : "",
594824
595422
  msg.media ? `Current message has media: ${summarizeTelegramMessageAttachments(msg)}` : "",
594825
595423
  ``,
594826
595424
  context2,
@@ -594840,7 +595438,7 @@ ${msg.text}`
594840
595438
  tools: [],
594841
595439
  temperature: 0,
594842
595440
  maxTokens: 220,
594843
- timeoutMs: Math.min(config.timeoutMs ?? 3e4, 15e3),
595441
+ timeoutMs: Math.min(Math.max(config.timeoutMs ?? 3e4, 5e3), 15e3),
594844
595442
  think: false
594845
595443
  });
594846
595444
  const text = result.choices[0]?.message?.content ?? "";
@@ -594851,10 +595449,10 @@ ${msg.text}`
594851
595449
  } catch {
594852
595450
  }
594853
595451
  return {
594854
- route: forcedRoute ?? "action",
594855
- shouldReply: !isGroup,
595452
+ route: forcedRoute ?? (isGroup ? "action" : "chat"),
595453
+ shouldReply: !isGroup || addressesBot,
594856
595454
  confidence: 0,
594857
- reason: isGroup ? "router inference failed; public group fails closed without keyword heuristics" : "router inference failed; private chat defaults to reply",
595455
+ reason: isGroup ? addressesBot ? "router inference failed; Telegram message directly addresses the bot" : "router inference failed; public group fails closed without keyword heuristics" : "router inference failed; private chat defaults to quick reply",
594858
595456
  source: "inference-unavailable"
594859
595457
  };
594860
595458
  }
@@ -594868,6 +595466,19 @@ ${msg.text}`
594868
595466
  return `Workspace context unavailable: ${reason}`;
594869
595467
  }
594870
595468
  }
595469
+ telegramFallbackCompactionThreshold(modelTier) {
595470
+ if (modelTier === "small") return 12e3;
595471
+ if (modelTier === "medium") return 24e3;
595472
+ return 4e4;
595473
+ }
595474
+ telegramWorkspaceBudget(profile) {
595475
+ if (this.contextWindowSize > 0) {
595476
+ const ratio = profile === "chat" ? 0.08 : 0.12;
595477
+ const floor = profile === "chat" ? 16e3 : 24e3;
595478
+ return Math.max(floor, Math.floor(this.contextWindowSize * ratio));
595479
+ }
595480
+ return profile === "chat" ? 16e3 : 24e3;
595481
+ }
594871
595482
  buildPrimaryTuiSessionContext(telegramSessionId) {
594872
595483
  const primarySessionId = process.env["OMNIUS_SESSION_ID"] || process.env["OMNIUS_TUI_SESSION_ID"] || "";
594873
595484
  if (!primarySessionId || primarySessionId === telegramSessionId) return "";
@@ -594925,7 +595536,7 @@ ${ADMIN_CHAT_PROFILE_PROMPT}`);
594925
595536
  if (primarySessionContext) sections.push(`## Primary TUI Session State
594926
595537
 
594927
595538
  ${primarySessionContext}`);
594928
- const workspaceContext = this.buildTelegramWorkspaceContext(modelTier, profile === "chat" ? 16e3 : 24e3);
595539
+ const workspaceContext = this.buildTelegramWorkspaceContext(modelTier, this.telegramWorkspaceBudget(profile));
594929
595540
  if (workspaceContext) sections.push(`## Workspace Context
594930
595541
 
594931
595542
  ${workspaceContext}`);
@@ -595316,7 +595927,8 @@ ${mediaContext}`;
595316
595927
  toolContext,
595317
595928
  pendingMessages: [],
595318
595929
  creativeWorkspaceRoot: this.creativeWorkspaceRootForMessage(msg, toolContext),
595319
- generatedArtifacts: []
595930
+ generatedArtifacts: [],
595931
+ surfacedToolCallFingerprints: /* @__PURE__ */ new Set()
595320
595932
  };
595321
595933
  this.subAgents.set(sessionKey, subAgent);
595322
595934
  this.refreshActiveTelegramInteractionCount();
@@ -595413,7 +596025,8 @@ ${mediaContext}`;
595413
596025
  toolContext,
595414
596026
  pendingMessages: [],
595415
596027
  creativeWorkspaceRoot: this.creativeWorkspaceRootForMessage(msg, toolContext),
595416
- generatedArtifacts: []
596028
+ generatedArtifacts: [],
596029
+ surfacedToolCallFingerprints: /* @__PURE__ */ new Set()
595417
596030
  };
595418
596031
  this.subAgents.set(sessionKey, subAgent);
595419
596032
  this.refreshActiveTelegramInteractionCount();
@@ -595684,6 +596297,7 @@ ${mediaContext}` : ""}`
595684
596297
  const isGroup = msg.chatType !== "private";
595685
596298
  const creativeWorkspace = subAgent.creativeWorkspaceRoot ? formatTelegramCreativeWorkspacePrompt(subAgent.creativeWorkspaceRoot) : "";
595686
596299
  const sessionContext = this.buildTelegramSessionContext(msg, ctx3, profile, modelTier);
596300
+ const contextWindowSize = this.contextWindowSize;
595687
596301
  const backend = new OllamaAgenticBackend(
595688
596302
  config.backendUrl,
595689
596303
  config.model,
@@ -595695,7 +596309,8 @@ ${mediaContext}` : ""}`
595695
596309
  temperature: 0.3,
595696
596310
  requestTimeoutMs: config.timeoutMs,
595697
596311
  taskTimeoutMs: isAdminDM ? config.timeoutMs * 3 : config.timeoutMs,
595698
- compactionThreshold: modelTier === "small" ? 8e3 : 16e3,
596312
+ compactionThreshold: this.telegramFallbackCompactionThreshold(modelTier),
596313
+ contextWindowSize,
595699
596314
  modelTier,
595700
596315
  streamEnabled: true,
595701
596316
  dynamicContext: sessionContext.context,
@@ -595714,8 +596329,20 @@ ${mediaContext}` : ""}`
595714
596329
  runner.registerTools(tools);
595715
596330
  runner.onEvent((event) => {
595716
596331
  if (subAgent.aborted) return;
595717
- this.onSubAgentEvent?.(msg.chatId, msg.username, event);
596332
+ let suppressExternalEvent = false;
596333
+ if (event.type === "tool_call" && event.toolName) {
596334
+ const fp = `${event.toolName}:${stableTelegramValueKey(event.toolArgs ?? {})}`;
596335
+ if (subAgent.surfacedToolCallFingerprints.has(fp)) {
596336
+ suppressExternalEvent = true;
596337
+ } else {
596338
+ subAgent.surfacedToolCallFingerprints.add(fp);
596339
+ }
596340
+ }
596341
+ if (!suppressExternalEvent) {
596342
+ this.onSubAgentEvent?.(msg.chatId, msg.username, event);
596343
+ }
595718
596344
  if (event.type === "tool_call" && event.toolName) {
596345
+ if (suppressExternalEvent) return;
595719
596346
  const argsPreview = event.toolArgs ? JSON.stringify(event.toolArgs).slice(0, 100) : "";
595720
596347
  this.subAgentViewCallbacks?.onWrite(subAgent.viewId, `tool: ${event.toolName}(${argsPreview})`);
595721
596348
  } else if (event.type === "tool_result" && event.toolName) {
@@ -595728,8 +596355,11 @@ ${mediaContext}` : ""}`
595728
596355
  }
595729
596356
  } else if (event.type === "status" && event.content) {
595730
596357
  this.subAgentViewCallbacks?.onWrite(subAgent.viewId, `status: ${event.content}`);
595731
- } else if (event.type === "assistant_text" && event.content) {
596358
+ } else if (event.type === "assistant_text" && event.content && event.source !== "task_complete_summary") {
595732
596359
  subAgent.assistantText = event.content;
596360
+ } else if (event.type === "stream_start") {
596361
+ subAgent.accumulated = "";
596362
+ subAgent.streamText = "";
595733
596363
  } else if (event.type === "stream_end" && event.content) {
595734
596364
  subAgent.streamText = event.content;
595735
596365
  }
@@ -595792,6 +596422,8 @@ ${msg.text}`;
595792
596422
  const toolHint = [
595793
596423
  "You have access to isolated per-chat memory (memory_write, memory_read, memory_search) scoped to this conversation.",
595794
596424
  "You can remember facts about users and retrieve them later. You also have web_search and web_fetch to look up information.",
596425
+ "If the user asks you to create or send a file, image, or audio artifact, use the scoped creative tools. The bridge will attach generated files back to Telegram when tool results record them.",
596426
+ "For image generation requests, decide from the conversation whether generate_image is appropriate; do not ask the user to use a hardcoded shortcut when the request is clear.",
595795
596427
  creativeWorkspace
595796
596428
  ].filter(Boolean).join("\n\n");
595797
596429
  userPrompt = `${systemPrompt}${discretionPrompt}
@@ -595939,6 +596571,13 @@ ${creativeWorkspace}` : ""}`;
595939
596571
  fullSubAgentTool
595940
596572
  ];
595941
596573
  const allTools = context2 === "telegram-admin-dm" ? adminTools : sharedReadMemoryWebTools;
596574
+ if (this.contextWindowSize > 0) {
596575
+ for (const tool of allTools) {
596576
+ if ("setContextWindowSize" in tool && typeof tool.setContextWindowSize === "function") {
596577
+ tool.setContextWindowSize(this.contextWindowSize);
596578
+ }
596579
+ }
596580
+ }
595942
596581
  let adaptedTools = allTools.map((tool) => adaptTool5(tool, todoSessionId));
595943
596582
  adaptedTools = applyToolPolicy(adaptedTools, context2, this.toolPolicyConfig);
595944
596583
  if (context2 !== "telegram-admin-dm") {
@@ -621769,13 +622408,14 @@ async function renderAsciiPreviewForImage(imagePath, displayPath, title, writer)
621769
622408
  }
621770
622409
  }
621771
622410
  async function renderAsciiPreviewForToolResult(toolName, output, repoRoot, writer) {
621772
- if (toolName !== "camera_capture" || !output) return;
622411
+ if (!output) return;
621773
622412
  try {
621774
622413
  const { extractSavedImagePath: extractSavedImagePath2 } = await Promise.resolve().then(() => (init_image_ascii_preview(), image_ascii_preview_exports));
621775
622414
  const imagePath = extractSavedImagePath2(output, repoRoot);
621776
622415
  if (!imagePath) return;
621777
622416
  const displayPath = relative13(repoRoot, imagePath).startsWith("..") ? imagePath : relative13(repoRoot, imagePath);
621778
- await renderAsciiPreviewForImage(imagePath, displayPath, "Camera frame", writer);
622417
+ const title = toolName === "generate_image" ? "Generated image" : toolName === "screenshot" ? "Screenshot" : toolName === "camera_capture" ? "Camera frame" : "Image";
622418
+ await renderAsciiPreviewForImage(imagePath, displayPath, title, writer);
621779
622419
  } catch {
621780
622420
  }
621781
622421
  }
@@ -622926,7 +623566,7 @@ ${entry.fullContent}`
622926
623566
  }
622927
623567
  });
622928
623568
  }
622929
- if (event.success && event.toolName === "camera_capture") {
623569
+ if (event.success) {
622930
623570
  void renderAsciiPreviewForToolResult(event.toolName, event.content ?? "", repoRoot, contentWrite);
622931
623571
  }
622932
623572
  if (voice?.enabled && voice.voiceMode === "voicechat" && _voiceChatSession2?.isActive && event.toolName === "task_complete") {
@@ -624211,6 +624851,7 @@ ${result.summary}`
624211
624851
  resolvedContextWindowSize = ctxSize;
624212
624852
  statusBar.setContextWindowSize(ctxSize);
624213
624853
  setActiveTaskContextWindowSize(ctxSize);
624854
+ telegramBridge?.setContextWindowSize(ctxSize);
624214
624855
  }
624215
624856
  }).catch(() => {
624216
624857
  });
@@ -626139,6 +626780,9 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
626139
626780
  currentConfig,
626140
626781
  repoRoot
626141
626782
  );
626783
+ if (resolvedContextWindowSize > 0) {
626784
+ telegramBridge.setContextWindowSize(resolvedContextWindowSize);
626785
+ }
626142
626786
  telegramBridge.setInteractionMode(savedSettings.telegramMode ?? "auto");
626143
626787
  if (adminId) {
626144
626788
  telegramBridge.setAdmin(adminId);
@@ -626180,7 +626824,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
626180
626824
  }
626181
626825
  });
626182
626826
  telegramBridge.setOnSubAgentEvent((chatId, username, event) => {
626183
- if (event.type === "tool_call" && event.toolName) {
626827
+ if (event.type === "tool_call" && event.toolName && event.toolName !== "task_complete") {
626184
626828
  const argsPreview = event.toolArgs ? JSON.stringify(event.toolArgs).slice(0, 60) : "";
626185
626829
  writeContent(
626186
626830
  () => renderTelegramSubAgentToolCall(
@@ -626189,7 +626833,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
626189
626833
  argsPreview
626190
626834
  )
626191
626835
  );
626192
- } else if (event.type === "status" && event.content) {
626836
+ } else if (event.type === "status" && event.content && process.env["OMNIUS_TELEGRAM_DEBUG_STATUS"] === "1") {
626193
626837
  writeContent(
626194
626838
  () => renderTelegramSubAgentEvent(username, event.content)
626195
626839
  );
@@ -627207,6 +627851,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
627207
627851
  setContextWindowSize: (size) => {
627208
627852
  resolvedContextWindowSize = size;
627209
627853
  statusBar.setContextWindowSize(size);
627854
+ telegramBridge?.setContextWindowSize(size);
627210
627855
  },
627211
627856
  setCapabilities: (caps) => {
627212
627857
  resolvedCaps = caps;