omnius 1.0.11 → 1.0.12

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
@@ -181,7 +181,7 @@ function printWarning(message2) {
181
181
  `);
182
182
  }
183
183
  function printInfo(message2) {
184
- process.stdout.write(`${c.cyan("")} ${message2}
184
+ process.stdout.write(`${c.cyan("i")} ${message2}
185
185
  `);
186
186
  }
187
187
  function printKeyValue(key, value2, indent = 0) {
@@ -229,7 +229,7 @@ var init_output = __esm({
229
229
  isStderrTTY = process.stderr.isTTY;
230
230
  c = {
231
231
  bold: (t2) => ansi("1", t2, isTTY),
232
- dim: (t2) => ansi("2", t2, isTTY),
232
+ dim: (t2) => ansi("38;5;250", t2, isTTY),
233
233
  red: (t2) => ansi("31", t2, isTTY),
234
234
  green: (t2) => ansi("32", t2, isTTY),
235
235
  yellow: (t2) => ansi("33", t2, isTTY),
@@ -250335,6 +250335,36 @@ import { spawn as spawn9 } from "node:child_process";
250335
250335
  import { existsSync as existsSync23, statSync as statSync8 } from "node:fs";
250336
250336
  import { chmod as chmod3, mkdir as mkdir11, writeFile as writeFile16 } from "node:fs/promises";
250337
250337
  import { join as join36, resolve as resolve18 } from "node:path";
250338
+ function parsePercent(text) {
250339
+ const match = text.match(/\b(\d{1,3})%\b/);
250340
+ if (!match)
250341
+ return void 0;
250342
+ const value2 = Number(match[1]);
250343
+ if (!Number.isFinite(value2))
250344
+ return void 0;
250345
+ return Math.max(0, Math.min(100, value2));
250346
+ }
250347
+ function cleanProgressText(text) {
250348
+ return text.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
250349
+ }
250350
+ function parseStructuredProgress(text) {
250351
+ const trimmed = text.trim();
250352
+ if (!trimmed.startsWith("{"))
250353
+ return null;
250354
+ try {
250355
+ const parsed = JSON.parse(trimmed);
250356
+ if (parsed["omnius_progress"] !== true)
250357
+ return null;
250358
+ const stage = String(parsed["stage"] ?? "process");
250359
+ const message2 = String(parsed["message"] ?? "").trim();
250360
+ const percent = typeof parsed["percent"] === "number" ? parsed["percent"] : void 0;
250361
+ if (!message2)
250362
+ return null;
250363
+ return { stage, message: message2, percent };
250364
+ } catch {
250365
+ return null;
250366
+ }
250367
+ }
250338
250368
  function numberArg(value2, fallback) {
250339
250369
  const n2 = Number(value2);
250340
250370
  return Number.isFinite(n2) && n2 > 0 ? n2 : fallback;
@@ -250430,6 +250460,7 @@ function imageGenerationSetupPlan(backend, repoRoot = ".", model) {
250430
250460
  }
250431
250461
  async function runProcess2(command, args, options2) {
250432
250462
  return new Promise((resolveProcess) => {
250463
+ const startedAt2 = Date.now();
250433
250464
  const child = spawn9(command, args, {
250434
250465
  cwd: options2.cwd,
250435
250466
  env: { ...process.env, ...options2.env },
@@ -250437,22 +250468,66 @@ async function runProcess2(command, args, options2) {
250437
250468
  });
250438
250469
  let stdout = "";
250439
250470
  let stderr = "";
250471
+ let lastProgress = "";
250472
+ let lastProgressAt = 0;
250473
+ const emitProgress = (stream, raw) => {
250474
+ if (!options2.onProgress)
250475
+ return;
250476
+ const parts = raw.split(/[\r\n]+/).filter((part) => part.trim());
250477
+ for (const part of parts.length > 0 ? parts : [raw]) {
250478
+ const clean3 = cleanProgressText(part);
250479
+ if (!clean3)
250480
+ continue;
250481
+ const now = Date.now();
250482
+ const structured = parseStructuredProgress(clean3);
250483
+ if (clean3 === lastProgress && now - lastProgressAt < 750)
250484
+ continue;
250485
+ lastProgress = clean3;
250486
+ lastProgressAt = now;
250487
+ if (structured) {
250488
+ options2.onProgress({ ...structured, elapsedMs: now - startedAt2 });
250489
+ } else {
250490
+ options2.onProgress({
250491
+ stage: stream === "stderr" ? "download" : "process",
250492
+ message: clean3.length > 220 ? clean3.slice(0, 217) + "..." : clean3,
250493
+ percent: parsePercent(clean3),
250494
+ elapsedMs: now - startedAt2
250495
+ });
250496
+ }
250497
+ }
250498
+ };
250440
250499
  const timer = setTimeout(() => {
250441
250500
  child.kill("SIGTERM");
250442
250501
  }, options2.timeoutMs);
250443
250502
  timer.unref();
250503
+ const heartbeat = setInterval(() => {
250504
+ if (!options2.onProgress || !options2.progressLabel)
250505
+ return;
250506
+ options2.onProgress({
250507
+ stage: "process",
250508
+ message: `${options2.progressLabel} still running`,
250509
+ elapsedMs: Date.now() - startedAt2
250510
+ });
250511
+ }, 5e3);
250512
+ heartbeat.unref();
250444
250513
  child.stdout?.on("data", (chunk) => {
250445
- stdout += chunk.toString();
250514
+ const text = chunk.toString();
250515
+ stdout += text;
250516
+ emitProgress("stdout", text);
250446
250517
  });
250447
250518
  child.stderr?.on("data", (chunk) => {
250448
- stderr += chunk.toString();
250519
+ const text = chunk.toString();
250520
+ stderr += text;
250521
+ emitProgress("stderr", text);
250449
250522
  });
250450
250523
  child.on("error", (err) => {
250451
250524
  clearTimeout(timer);
250525
+ clearInterval(heartbeat);
250452
250526
  resolveProcess({ code: 127, stdout, stderr: stderr + String(err.message || err) });
250453
250527
  });
250454
250528
  child.on("close", (code8) => {
250455
250529
  clearTimeout(timer);
250530
+ clearInterval(heartbeat);
250456
250531
  resolveProcess({ code: code8, stdout, stderr });
250457
250532
  });
250458
250533
  });
@@ -250468,6 +250543,8 @@ function imageGenerationPythonEnv(repoRoot) {
250468
250543
  const hf = join36(root, "huggingface");
250469
250544
  return {
250470
250545
  PYTHONUNBUFFERED: "1",
250546
+ HF_HUB_DISABLE_PROGRESS_BARS: "0",
250547
+ TQDM_DISABLE: "0",
250471
250548
  HF_HOME: hf,
250472
250549
  HUGGINGFACE_HUB_CACHE: join36(hf, "hub"),
250473
250550
  TRANSFORMERS_CACHE: join36(hf, "transformers"),
@@ -250494,7 +250571,7 @@ async function pythonCanImport(command, code8, repoRoot, env2) {
250494
250571
  const result = await runProcess2(command, ["-c", code8], { cwd: repoRoot, timeoutMs: 6e4, env: env2 });
250495
250572
  return result.code === 0;
250496
250573
  }
250497
- async function ensurePythonFor(repoRoot, kind, explicit) {
250574
+ async function ensurePythonFor(repoRoot, kind, explicit, onProgress) {
250498
250575
  const pythonEnv = imageGenerationPythonEnv(repoRoot);
250499
250576
  await ensureImageGenerationCacheDirs(repoRoot);
250500
250577
  if (explicit)
@@ -250505,7 +250582,14 @@ async function ensurePythonFor(repoRoot, kind, explicit) {
250505
250582
  const venvDir = kind === "diffusers" ? diffusersVenvDir(repoRoot) : sdcppVenvDir(repoRoot);
250506
250583
  const command = venvPython(venvDir);
250507
250584
  if (!existsSync23(command)) {
250508
- const created = await runProcess2("python3", ["-m", "venv", venvDir], { cwd: repoRoot, timeoutMs: 18e4, env: pythonEnv });
250585
+ onProgress?.({ stage: "setup", message: `Creating image-generation Python environment at ${venvDir}` });
250586
+ const created = await runProcess2("python3", ["-m", "venv", venvDir], {
250587
+ cwd: repoRoot,
250588
+ timeoutMs: 18e4,
250589
+ env: pythonEnv,
250590
+ progressLabel: "Creating image-generation Python environment",
250591
+ onProgress
250592
+ });
250509
250593
  if (created.code !== 0) {
250510
250594
  throw new Error(`Failed to create image-generation venv at ${venvDir}.
250511
250595
  ${trimProcessText(created.stderr || created.stdout)}`);
@@ -250516,10 +250600,13 @@ ${trimProcessText(created.stderr || created.stdout)}`);
250516
250600
  return { command, env: pythonEnv };
250517
250601
  }
250518
250602
  const packages = kind === "diffusers" ? DIFFUSERS_PYTHON_PACKAGES : SDCPP_PYTHON_PACKAGES;
250519
- const pip = await runProcess2(command, ["-m", "pip", "install", "-U", "pip", ...packages], {
250603
+ onProgress?.({ stage: "setup", message: `Installing ${kind} image-generation Python packages` });
250604
+ const pip = await runProcess2(command, ["-m", "pip", "install", "--progress-bar", "on", "-U", "pip", ...packages], {
250520
250605
  cwd: repoRoot,
250521
250606
  timeoutMs: 18e5,
250522
- env: pythonEnv
250607
+ env: pythonEnv,
250608
+ progressLabel: `Installing ${kind} image-generation Python packages`,
250609
+ onProgress
250523
250610
  });
250524
250611
  if (pip.code !== 0) {
250525
250612
  throw new Error(`Failed to install ${kind} image-generation packages into ${venvDir}.
@@ -250893,9 +250980,16 @@ var init_image_generate = __esm({
250893
250980
  import argparse
250894
250981
  import json
250895
250982
  import os
250983
+ import sys
250896
250984
  import time
250897
250985
  from pathlib import Path
250898
250986
 
250987
+ def _progress(stage, message, percent=None):
250988
+ payload = {"omnius_progress": True, "stage": stage, "message": message}
250989
+ if percent is not None:
250990
+ payload["percent"] = percent
250991
+ print(json.dumps(payload), file=sys.stderr, flush=True)
250992
+
250899
250993
  def _device():
250900
250994
  import torch
250901
250995
  if torch.cuda.is_available():
@@ -250943,12 +251037,15 @@ def main():
250943
251037
  kwargs["variant"] = args.variant
250944
251038
 
250945
251039
  try:
251040
+ _progress("load", f"loading model {args.model}")
250946
251041
  pipeline_cls = _pipeline_class(args.model)
250947
251042
  pipe = pipeline_cls.from_pretrained(args.model, **kwargs)
250948
251043
  except Exception:
251044
+ _progress("load", f"retrying model load without variant for {args.model}")
250949
251045
  kwargs.pop("variant", None)
250950
251046
  pipeline_cls = _pipeline_class(args.model)
250951
251047
  pipe = pipeline_cls.from_pretrained(args.model, **kwargs)
251048
+ _progress("load", f"model loaded on {device}")
250952
251049
 
250953
251050
  if hasattr(pipe, "enable_attention_slicing"):
250954
251051
  try:
@@ -250976,14 +251073,17 @@ def main():
250976
251073
  "height": args.height,
250977
251074
  }
250978
251075
  try:
251076
+ _progress("generate", f"generating {args.width}x{args.height} image with {args.steps} steps")
250979
251077
  image = pipe(**call_kwargs).images[0]
250980
251078
  except TypeError:
250981
251079
  call_kwargs.pop("width", None)
250982
251080
  call_kwargs.pop("height", None)
251081
+ _progress("generate", f"generating image with model-native dimensions and {args.steps} steps")
250983
251082
  image = pipe(**call_kwargs).images[0]
250984
251083
 
250985
251084
  out = Path(args.output)
250986
251085
  out.parent.mkdir(parents=True, exist_ok=True)
251086
+ _progress("save", f"saving image to {out}")
250987
251087
  image.save(out)
250988
251088
  print(json.dumps({
250989
251089
  "ok": True,
@@ -251094,10 +251194,27 @@ if __name__ == "__main__":
251094
251194
  cwd;
251095
251195
  ollamaUrl;
251096
251196
  cachedImageModel = null;
251197
+ progressHandler = null;
251198
+ lastProgressMessage = "";
251199
+ lastProgressAt = 0;
251097
251200
  constructor(cwd4, ollamaUrl = "http://localhost:11434") {
251098
251201
  this.cwd = cwd4;
251099
251202
  this.ollamaUrl = ollamaUrl.replace(/\/v1\/?$/, "").replace(/\/$/, "");
251100
251203
  }
251204
+ setProgressCallback(handler) {
251205
+ this.progressHandler = handler;
251206
+ }
251207
+ emitProgress(event) {
251208
+ if (!this.progressHandler)
251209
+ return;
251210
+ const now = Date.now();
251211
+ const key = `${event.stage}:${event.percent ?? ""}:${event.message}`;
251212
+ if (key === this.lastProgressMessage && now - this.lastProgressAt < 750)
251213
+ return;
251214
+ this.lastProgressMessage = key;
251215
+ this.lastProgressAt = now;
251216
+ this.progressHandler(event);
251217
+ }
251101
251218
  async execute(args) {
251102
251219
  const start2 = performance.now();
251103
251220
  const action = String(args["action"] ?? "generate");
@@ -251239,7 +251356,7 @@ ${errText.slice(0, 800)}`,
251239
251356
  const filepath = outputPath(this.cwd);
251240
251357
  let python;
251241
251358
  try {
251242
- python = await ensurePythonFor(this.cwd, "diffusers", typeof args.python === "string" ? args.python : void 0);
251359
+ python = await ensurePythonFor(this.cwd, "diffusers", typeof args.python === "string" ? args.python : void 0, (event) => this.emitProgress(event));
251243
251360
  } catch (err) {
251244
251361
  const plan = imageGenerationSetupPlan("diffusers", this.cwd, args.model);
251245
251362
  return {
@@ -251273,7 +251390,14 @@ ${errText.slice(0, 800)}`,
251273
251390
  ];
251274
251391
  if (args.seed !== void 0)
251275
251392
  argv.push("--seed", String(args.seed));
251276
- const result = await runProcess2(python.command, argv, { cwd: this.cwd, timeoutMs: 9e5, env: python.env });
251393
+ this.emitProgress({ stage: "load", message: `Starting image generation with ${args.model}` });
251394
+ const result = await runProcess2(python.command, argv, {
251395
+ cwd: this.cwd,
251396
+ timeoutMs: 9e5,
251397
+ env: python.env,
251398
+ progressLabel: `Downloading/loading ${args.model}`,
251399
+ onProgress: (event) => this.emitProgress(event)
251400
+ });
251277
251401
  if (result.code !== 0 || !existsSync23(filepath)) {
251278
251402
  const plan = imageGenerationSetupPlan("diffusers", this.cwd, args.model);
251279
251403
  return {
@@ -535558,7 +535682,7 @@ Respond with EXACTLY this structure before your next tool call:
535558
535682
  this.emit({
535559
535683
  type: "tool_result",
535560
535684
  toolName: tc.name,
535561
- content: output.slice(0, 200),
535685
+ content: this.toolResultEventContent(tc.name, output),
535562
535686
  success: result.success,
535563
535687
  turn,
535564
535688
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -536667,7 +536791,7 @@ Full content available via: repl_exec(code="data = retrieve('${handleId}')") or
536667
536791
  this.emit({
536668
536792
  type: "tool_result",
536669
536793
  toolName: tc.name,
536670
- content: output.slice(0, 200),
536794
+ content: this.toolResultEventContent(tc.name, output),
536671
536795
  success: result.success,
536672
536796
  turn,
536673
536797
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -537511,6 +537635,12 @@ ${marker}` : marker);
537511
537635
  *
537512
537636
  * This replaces scattered post-hoc truncation with a single normalization point.
537513
537637
  */
537638
+ toolResultEventContent(toolName, output) {
537639
+ if (toolName === "generate_image" || toolName === "screenshot" || toolName === "camera_capture" || /(?:Image generated|Screenshot saved|Saved to|Output saved to):?\s+/i.test(output)) {
537640
+ return output.slice(0, 2e3);
537641
+ }
537642
+ return output.slice(0, 200);
537643
+ }
537514
537644
  normalizeToolOutput(result, toolName, args, turn) {
537515
537645
  const { toolOutputMaxChars: maxLen } = this.contextLimits();
537516
537646
  const modelContent = result.llmContent ?? result.output;
@@ -545376,7 +545506,7 @@ var init_spinner = __esm({
545376
545506
  info(message2) {
545377
545507
  this.stop();
545378
545508
  const msg = message2 ?? this._text;
545379
- process.stdout.write(`\x1B[36mℹ\x1B[0m ${msg}
545509
+ process.stdout.write(`\x1B[36mi\x1B[0m ${msg}
545380
545510
  `);
545381
545511
  return this;
545382
545512
  }
@@ -550194,8 +550324,8 @@ var init_theme = __esm({
550194
550324
  // terminal default foreground
550195
550325
  textPrimary: -1,
550196
550326
  // terminal default foreground
550197
- textDim: 245,
550198
- // slightly dim
550327
+ textDim: 250,
550328
+ // readable dim text without ANSI faint
550199
550329
  boxColor: -1
550200
550330
  // terminal default foreground
550201
550331
  },
@@ -550207,8 +550337,8 @@ var init_theme = __esm({
550207
550337
  // teal
550208
550338
  textPrimary: 37,
550209
550339
  // teal
550210
- textDim: 240,
550211
- // grey
550340
+ textDim: 250,
550341
+ // readable grey
550212
550342
  boxColor: 37
550213
550343
  // teal
550214
550344
  },
@@ -550217,7 +550347,7 @@ var init_theme = __esm({
550217
550347
  bg: -1,
550218
550348
  accent: 37,
550219
550349
  textPrimary: 252,
550220
- textDim: 245,
550350
+ textDim: 250,
550221
550351
  boxColor: 252
550222
550352
  }
550223
550353
  };
@@ -551457,7 +551587,7 @@ ${icon} \x1B[38;5;198m${message2}\x1B[0m
551457
551587
  function renderInfo(message2) {
551458
551588
  const redir = _contentWriteHook?.redirect?.();
551459
551589
  const dim = dimFg();
551460
- const icon = `${dim}🛈\x1B[0m`;
551590
+ const icon = `${dim}i\x1B[0m`;
551461
551591
  const text = `${icon} ${dim}${message2}\x1B[0m
551462
551592
  `;
551463
551593
  if (redir) {
@@ -551746,7 +551876,7 @@ var init_render = __esm({
551746
551876
  isTTY2 = process.stdout.isTTY ?? false;
551747
551877
  c3 = {
551748
551878
  bold: (t2) => ansi2("1", t2),
551749
- dim: (t2) => ansi2("2", t2),
551879
+ dim: (t2) => isTTY2 ? `${dimFg()}${t2}\x1B[0m` : t2,
551750
551880
  italic: (t2) => ansi2("3", t2),
551751
551881
  red: (t2) => ansi2("31", t2),
551752
551882
  green: (t2) => ansi2("32", t2),
@@ -551761,10 +551891,10 @@ var init_render = __esm({
551761
551891
  ui = {
551762
551892
  /** Primary text — lighter grey (252) for main content */
551763
551893
  primary: (t2) => fg256(252, t2),
551764
- /** Sub-text — darker grey (245) for secondary info */
551765
- sub: (t2) => fg256(245, t2),
551766
- /** Dim text — very dark grey (240) for hints/placeholders */
551767
- hint: (t2) => fg256(240, t2),
551894
+ /** Sub-text — readable grey for secondary info */
551895
+ sub: (t2) => fg256(tuiTextDim(), t2),
551896
+ /** Dim text — readable grey for hints/placeholders */
551897
+ hint: (t2) => fg256(tuiTextDim(), t2),
551768
551898
  /** Error text — magenta (bright) for errors */
551769
551899
  error: (t2) => fg256(198, t2),
551770
551900
  /** Warning text — warm orange for warnings */
@@ -551801,14 +551931,14 @@ var init_render = __esm({
551801
551931
  // light peach
551802
551932
  link: 111,
551803
551933
  // periwinkle
551804
- blockquote: 245,
551805
- // grey
551806
- hr: 240,
551807
- // dark grey
551808
- listBullet: 245,
551809
- // grey
551810
- tableBar: 245
551811
- // grey
551934
+ blockquote: 250,
551935
+ // readable grey
551936
+ hr: 250,
551937
+ // readable grey
551938
+ listBullet: 250,
551939
+ // readable grey
551940
+ tableBar: 250
551941
+ // readable grey
551812
551942
  };
551813
551943
  TOOL_ICONS = {
551814
551944
  file_read: "📄",
@@ -559067,7 +559197,7 @@ var init_daemon_registry = __esm({
559067
559197
  bar += `${PANEL_BG} ${BTN_BG}\x1B[${fgColor}m ${label} \x1B[${dotColor}m●\x1B[${fgColor}m `;
559068
559198
  }
559069
559199
  if (!bar) {
559070
- return `${PANEL_BG} \x1B[38;5;240m⠀ no active daemons`;
559200
+ return `${PANEL_BG} \x1B[38;5;250m⠀ no active daemons`;
559071
559201
  }
559072
559202
  return bar + PANEL_BG;
559073
559203
  }
@@ -562478,7 +562608,7 @@ ${CONTENT_BG_SEQ}`);
562478
562608
  if (fullLine.length <= availWidth) {
562479
562609
  let displayLine;
562480
562610
  if (ghost) {
562481
- displayLine = fullLine + `\x1B[7m\x1B[38;5;${TEXT_DIM}m${ghost[0]}\x1B[0m${PANEL_BG_SEQ}\x1B[2m\x1B[38;5;${TEXT_DIM}m${ghost.slice(1)}\x1B[0m${PANEL_BG_SEQ}`;
562611
+ displayLine = fullLine + `\x1B[7m\x1B[38;5;${TEXT_DIM}m${ghost[0]}\x1B[0m${PANEL_BG_SEQ}\x1B[38;5;${TEXT_DIM}m${ghost.slice(1)}\x1B[0m${PANEL_BG_SEQ}`;
562482
562612
  } else {
562483
562613
  displayLine = _StatusBar.insertVisualCursor(fullLine, cursorPos);
562484
562614
  }
@@ -563677,13 +563807,13 @@ var init_tui_select = __esm({
563677
563807
  selectColors = {
563678
563808
  orange: (t2) => fg2563(208, t2),
563679
563809
  green: (t2) => ansi3("32", t2),
563680
- dim: (t2) => ansi3("2", t2),
563810
+ dim: (t2) => fg2563(tuiTextDim(), t2),
563681
563811
  bold: (t2) => ansi3("1", t2),
563682
563812
  cyan: (t2) => ansi3("36", t2),
563683
563813
  /** Lighter grey for filter matches (252 = near-white) */
563684
563814
  matchLight: (t2) => fg2563(252, t2),
563685
- /** Dark grey for non-matching items (240 = dark grey) */
563686
- matchDark: (t2) => fg2563(240, t2)
563815
+ /** Readable grey for non-matching items */
563816
+ matchDark: (t2) => fg2563(tuiTextDim(), t2)
563687
563817
  };
563688
563818
  }
563689
563819
  });
@@ -568228,7 +568358,7 @@ var init_drop_panel = __esm({
568228
568358
  isTTY4 = process.stdout.isTTY ?? false;
568229
568359
  dc = {
568230
568360
  bold: (t2) => ansi4("1", t2),
568231
- dim: (t2) => ansi4("2", t2),
568361
+ dim: (t2) => ansi4("38;5;250", t2),
568232
568362
  cyan: (t2) => ansi4("36", t2),
568233
568363
  green: (t2) => ansi4("32", t2),
568234
568364
  red: (t2) => ansi4("31", t2),
@@ -570727,7 +570857,7 @@ async function connectRPC(state, neovimPkg, cols) {
570727
570857
  state.outputChanId = chanId;
570728
570858
  await nvim.request("nvim_chan_send", [
570729
570859
  chanId,
570730
- "\x1B[36m── Agent Output ──\x1B[0m\r\n\r\n\x1B[2mAgent activity will appear here.\x1B[0m\r\n\x1B[2mCtrl+N toggles focus to input.\x1B[0m\r\n"
570860
+ "\x1B[36m── Agent Output ──\x1B[0m\r\n\r\n\x1B[38;5;250mAgent activity will appear here.\x1B[0m\r\n\x1B[38;5;250mCtrl+N toggles focus to input.\x1B[0m\r\n"
570731
570861
  ]);
570732
570862
  await nvim.request("nvim_win_set_option", [win.id, "number", false]);
570733
570863
  await nvim.request("nvim_win_set_option", [win.id, "relativenumber", false]);
@@ -572637,6 +572767,8 @@ ${preview.ascii}`;
572637
572767
  function extractSavedImagePath(text, repoRoot) {
572638
572768
  const patterns = [
572639
572769
  /Image generated:\s*([^\n\r]+)/i,
572770
+ /Image generated at\s+([^\s\n\r]+\.(?:png|jpg|jpeg|webp|gif))/i,
572771
+ /Output saved to\s+([^\s\n\r]+\.(?:png|jpg|jpeg|webp|gif))/i,
572640
572772
  /Screenshot saved:\s*([^\n\r]+)/i,
572641
572773
  /Screenshot:\s*([^\n\r]+)/i,
572642
572774
  /Saved to:\s*([^\n\r]+)/i,
@@ -583153,6 +583285,9 @@ async function handleImageCommand(ctx3, arg, hasLocal) {
583153
583285
  const backend = String(parsed.flags["backend"] ?? settings.imageBackend ?? inferImageGenerationBackend(model, void 0));
583154
583286
  const tool = new ImageGenerateTool(ctx3.repoRoot, ctx3.config.backendUrl);
583155
583287
  const prompt = parsed.prompt;
583288
+ tool.setProgressCallback((event) => {
583289
+ renderInfo(formatImageGenerationProgress(event));
583290
+ });
583156
583291
  renderInfo(`Generating image with ${model} (${backend})...`);
583157
583292
  const result = await tool.execute({
583158
583293
  prompt,
@@ -583175,13 +583310,31 @@ async function handleImageCommand(ctx3, arg, hasLocal) {
583175
583310
  if (imagePath) {
583176
583311
  const preview = await buildImageAsciiPreview2(imagePath);
583177
583312
  const displayPath = relative11(ctx3.repoRoot, imagePath).startsWith("..") ? imagePath : relative11(ctx3.repoRoot, imagePath);
583178
- if (preview) renderImageAsciiPreview("Generated image", displayPath, preview.ascii, preview.renderer);
583313
+ if (preview) {
583314
+ renderImageAsciiPreview("Generated image", displayPath, preview.ascii, preview.renderer);
583315
+ } else {
583316
+ renderWarning(`Generated image preview unavailable for ${displayPath}.`);
583317
+ }
583179
583318
  renderInfo(`File: ${imagePath}`);
583319
+ } else {
583320
+ renderWarning("Generated image preview skipped: no saved image path was found in the tool output.");
583180
583321
  }
583181
- } catch {
583322
+ } catch (err) {
583323
+ renderWarning(`Generated image preview failed: ${err instanceof Error ? err.message : String(err)}`);
583182
583324
  }
583183
583325
  return "handled";
583184
583326
  }
583327
+ function formatImageGenerationProgress(event) {
583328
+ const pct = event.percent;
583329
+ const elapsed = event.elapsedMs && event.elapsedMs > 1500 ? ` ${Math.round(event.elapsedMs / 1e3)}s` : "";
583330
+ if (typeof pct === "number") {
583331
+ const width = 20;
583332
+ const filled = Math.max(0, Math.min(width, Math.round(pct / 100 * width)));
583333
+ const bar = `${"#".repeat(filled)}${"-".repeat(width - filled)}`;
583334
+ return `Image ${event.stage}: [${bar}] ${pct}% ${event.message}${elapsed}`;
583335
+ }
583336
+ return `Image ${event.stage}: ${event.message}${elapsed}`;
583337
+ }
583185
583338
  async function showHelpMenu(ctx3) {
583186
583339
  const slashCommands = getSlashHelpEntries();
583187
583340
  const groups = /* @__PURE__ */ new Map();
@@ -589051,13 +589204,13 @@ function fg2564(code8, text) {
589051
589204
  return isTTY8 ? `\x1B[38;5;${code8}m${text}\x1B[0m` : text;
589052
589205
  }
589053
589206
  function dimText(text) {
589054
- return isTTY8 ? `\x1B[2m${text}\x1B[0m` : text;
589207
+ return isTTY8 ? `\x1B[38;5;${tuiTextDim()}m${text}\x1B[0m` : text;
589055
589208
  }
589056
589209
  function italicText(text) {
589057
589210
  return isTTY8 ? `\x1B[3m${text}\x1B[0m` : text;
589058
589211
  }
589059
589212
  function dimItalic(text) {
589060
- return isTTY8 ? `\x1B[2;3m${text}\x1B[0m` : text;
589213
+ return isTTY8 ? `\x1B[3m\x1B[38;5;${tuiTextDim()}m${text}\x1B[0m` : text;
589061
589214
  }
589062
589215
  function boldText(text) {
589063
589216
  return isTTY8 ? `\x1B[1m${text}\x1B[0m` : text;
@@ -589068,6 +589221,7 @@ var init_stream_renderer = __esm({
589068
589221
  "use strict";
589069
589222
  init_layout2();
589070
589223
  init_text_selection();
589224
+ init_theme();
589071
589225
  isTTY8 = process.stdout.isTTY ?? false;
589072
589226
  PASTEL = {
589073
589227
  key: 222,
@@ -589082,14 +589236,14 @@ var init_stream_renderer = __esm({
589082
589236
  // grey-blue — null
589083
589237
  bracket: 75,
589084
589238
  // soft blue — { } [ ]
589085
- colon: 245,
589086
- // neutral grey — : ,
589239
+ colon: 250,
589240
+ // readable grey — : ,
589087
589241
  keyword: 117,
589088
589242
  // sky blue — function, return, if, else
589089
- comment: 243,
589090
- // dim grey — // comments
589091
- thinking: 245,
589092
- // neutral grey for thinking tokens
589243
+ comment: 250,
589244
+ // readable grey — // comments
589245
+ thinking: 250,
589246
+ // readable grey for thinking tokens
589093
589247
  toolArg: 111,
589094
589248
  // dim periwinkle for tool arg tokens
589095
589249
  // Markdown
@@ -589103,10 +589257,10 @@ var init_stream_renderer = __esm({
589103
589257
  // light peach — `code`
589104
589258
  link: 111,
589105
589259
  // periwinkle — [link](url)
589106
- blockquote: 245,
589107
- // grey — > quote
589108
- hr: 240,
589109
- // dark grey — ---
589260
+ blockquote: 250,
589261
+ // readable grey — > quote
589262
+ hr: 250,
589263
+ // readable grey — ---
589110
589264
  // Diff / patch
589111
589265
  diffAdded: 114,
589112
589266
  // mint green — + lines
@@ -589116,8 +589270,8 @@ var init_stream_renderer = __esm({
589116
589270
  // blue — @@ headers
589117
589271
  diffMeta: 222,
589118
589272
  // gold — --- +++ file headers
589119
- diffContext: 245,
589120
- // grey — context lines
589273
+ diffContext: 250,
589274
+ // readable grey — context lines
589121
589275
  // Shell
589122
589276
  shellVar: 222,
589123
589277
  // gold — $VAR
@@ -593433,7 +593587,7 @@ function formatTelegramCreativeWorkspacePrompt(root) {
593433
593587
  "Creative file tools are scoped to that folder only.",
593434
593588
  "Allowed: create new files, read/list files in this workspace, and edit/patch files already created in this workspace.",
593435
593589
  "Forbidden: delete files, access paths outside this workspace, mutate the project tree, run shell commands, or touch system state.",
593436
- "When you create an artifact for Telegram, the bridge attaches files recorded by tool results. Refer to the attachment naturally; do not expose filesystem paths unless the admin explicitly asks."
593590
+ "When a user asks for an artifact to be sent, create it here and then call telegram_send_file. The bridge also auto-attaches recorded artifacts as a fallback. Refer to the attachment naturally; do not expose filesystem paths unless the admin explicitly asks."
593437
593591
  ].join("\n");
593438
593592
  }
593439
593593
  function collectGeneratedArtifactPathsFromText(text, root) {
@@ -594530,6 +594684,14 @@ function mimeForPath(path11, fallbackKind) {
594530
594684
  if (fallbackKind === "video") return "video/mp4";
594531
594685
  return "application/octet-stream";
594532
594686
  }
594687
+ function normalizeTelegramSendKind(rawKind, path11) {
594688
+ const requested = typeof rawKind === "string" ? rawKind.trim().toLowerCase() : "auto";
594689
+ if (requested === "photo") return "image";
594690
+ if (requested === "image" || requested === "animation" || requested === "audio" || requested === "voice" || requested === "video" || requested === "document") {
594691
+ return requested;
594692
+ }
594693
+ return classifyMedia(path11) ?? "document";
594694
+ }
594533
594695
  function renderTelegramStart(botUsername, adminId, mode = "auto") {
594534
594696
  process.stdout.write(`
594535
594697
  ${c3.cyan("✈")} ${c3.bold("Telegram Bridge")} connected as @${botUsername}
@@ -596443,7 +596605,7 @@ ${msg.text}`;
596443
596605
  const toolHint = [
596444
596606
  "You have access to isolated per-chat memory (memory_write, memory_read, memory_search) scoped to this conversation.",
596445
596607
  "You can remember facts about users and retrieve them later. You also have web_search and web_fetch to look up information.",
596446
- "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.",
596608
+ "If the user asks you to create or send a file, image, or audio artifact, create it with the scoped creative tools and call telegram_send_file to upload it. The bridge still auto-attaches generated files as a fallback when tool results record them.",
596447
596609
  "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.",
596448
596610
  creativeWorkspace
596449
596611
  ].filter(Boolean).join("\n\n");
@@ -596589,7 +596751,8 @@ ${creativeWorkspace}` : ""}`;
596589
596751
  new ImpactAnalysisTool(repoRoot),
596590
596752
  new CodeNeighborsTool(repoRoot),
596591
596753
  new ProcessHealthTool(),
596592
- fullSubAgentTool
596754
+ fullSubAgentTool,
596755
+ this.buildTelegramSendFileTool(context2, repoRoot, chatId)
596593
596756
  ];
596594
596757
  const allTools = context2 === "telegram-admin-dm" ? adminTools : sharedReadMemoryWebTools;
596595
596758
  if (this.contextWindowSize > 0) {
@@ -596622,9 +596785,122 @@ ${creativeWorkspace}` : ""}`;
596622
596785
  this.agentConfig?.backendUrl
596623
596786
  ).map((tool) => adaptTool5(tool, todoSessionId));
596624
596787
  adaptedTools.push(...creativeTools);
596788
+ adaptedTools.push(adaptTool5(this.buildTelegramSendFileTool(context2, repoRoot, chatId), todoSessionId));
596625
596789
  }
596626
596790
  return [...adaptedTools, taskComplete];
596627
596791
  }
596792
+ buildTelegramSendFileTool(context2, repoRoot, currentChatId) {
596793
+ const bridge = this;
596794
+ const adminDm = context2 === "telegram-admin-dm";
596795
+ const scopedRoot = adminDm ? void 0 : telegramCreativeWorkspaceRoot(repoRoot, currentChatId);
596796
+ return {
596797
+ name: "telegram_send_file",
596798
+ description: adminDm ? "Upload an existing local file to a Telegram chat. Admin DM scope may target the current chat, a numeric chat/user id, or a @username if the bot is allowed to message it. This only sends the file; it does not create, edit, or delete files." : `Upload an existing file from this chat's scoped creative workspace to the current Telegram chat. Public/group scope cannot target other chats and cannot send files outside ${scopedRoot}.`,
596799
+ parameters: {
596800
+ type: "object",
596801
+ properties: {
596802
+ path: {
596803
+ type: "string",
596804
+ description: adminDm ? "Local file path to send. Relative paths resolve from the repo root; absolute paths are allowed for admin DM." : "File path inside the scoped creative workspace."
596805
+ },
596806
+ chat_id: {
596807
+ type: "string",
596808
+ description: "Admin DM only. Optional target chat/user id or @username. Defaults to the current Telegram chat."
596809
+ },
596810
+ user_id: {
596811
+ type: "string",
596812
+ description: "Admin DM only. Optional numeric Telegram user id to send to, if the bot can message that user."
596813
+ },
596814
+ kind: {
596815
+ type: "string",
596816
+ enum: ["auto", "image", "photo", "document", "audio", "voice", "video", "animation"],
596817
+ description: "How Telegram should send the file. Defaults to auto based on extension."
596818
+ },
596819
+ caption: {
596820
+ type: "string",
596821
+ description: "Optional Telegram caption."
596822
+ },
596823
+ reply_to_message_id: {
596824
+ type: "number",
596825
+ description: "Optional Telegram message id to reply to."
596826
+ }
596827
+ },
596828
+ required: ["path"]
596829
+ },
596830
+ async execute(args) {
596831
+ const start2 = performance.now();
596832
+ const rawPath = typeof args["path"] === "string" ? args["path"].trim() : "";
596833
+ if (!rawPath) {
596834
+ return { success: false, output: "", error: "path is required", durationMs: performance.now() - start2 };
596835
+ }
596836
+ const target = bridge.resolveTelegramFileTarget(args, currentChatId, adminDm);
596837
+ if (!target.ok) {
596838
+ return { success: false, output: "", error: target.error, durationMs: performance.now() - start2 };
596839
+ }
596840
+ const file = bridge.resolveTelegramFilePath(rawPath, repoRoot, scopedRoot);
596841
+ if (!file.ok) {
596842
+ return { success: false, output: "", error: file.error, durationMs: performance.now() - start2 };
596843
+ }
596844
+ const kind = normalizeTelegramSendKind(args["kind"], file.path);
596845
+ const caption = typeof args["caption"] === "string" ? args["caption"].trim().slice(0, 1024) : void 0;
596846
+ const replyTo = Number(args["reply_to_message_id"]);
596847
+ try {
596848
+ const messageId = await bridge.sendTelegramFileToChat(target.chatId, file.path, {
596849
+ kind,
596850
+ caption: caption || void 0,
596851
+ replyToMessageId: Number.isFinite(replyTo) && replyTo > 0 ? Math.floor(replyTo) : void 0
596852
+ });
596853
+ return {
596854
+ success: true,
596855
+ output: `Sent Telegram file: ${basename22(file.path)} as ${kind} to ${String(target.chatId)}${messageId ? ` (message_id ${messageId})` : ""}`,
596856
+ llmContent: `Sent ${basename22(file.path)} to Telegram as ${kind}.`,
596857
+ durationMs: performance.now() - start2,
596858
+ mutated: false,
596859
+ mutatedFiles: []
596860
+ };
596861
+ } catch (err) {
596862
+ return {
596863
+ success: false,
596864
+ output: "",
596865
+ error: `Telegram file upload failed: ${err instanceof Error ? err.message : String(err)}`,
596866
+ durationMs: performance.now() - start2
596867
+ };
596868
+ }
596869
+ }
596870
+ };
596871
+ }
596872
+ resolveTelegramFileTarget(args, currentChatId, adminDm) {
596873
+ const rawTarget = args["chat_id"] ?? args["target_chat_id"] ?? args["user_id"] ?? args["username"];
596874
+ if (!adminDm) {
596875
+ if (rawTarget !== void 0 && String(rawTarget).trim() && String(rawTarget).trim() !== String(currentChatId)) {
596876
+ return { ok: false, error: "Public/group telegram_send_file cannot target another chat. It may only send to the current chat." };
596877
+ }
596878
+ if (currentChatId === void 0) return { ok: false, error: "Current Telegram chat id is unavailable." };
596879
+ return { ok: true, chatId: currentChatId };
596880
+ }
596881
+ if (rawTarget === void 0 || !String(rawTarget).trim()) {
596882
+ if (currentChatId === void 0) return { ok: false, error: "No target chat_id/user_id provided and current chat id is unavailable." };
596883
+ return { ok: true, chatId: currentChatId };
596884
+ }
596885
+ const target = String(rawTarget).trim();
596886
+ if (/^-?\d+$/.test(target)) return { ok: true, chatId: target };
596887
+ const cleanUsername = target.replace(/^@/, "");
596888
+ if (/^[A-Za-z0-9_]{5,32}$/.test(cleanUsername)) {
596889
+ return { ok: true, chatId: `@${cleanUsername}` };
596890
+ }
596891
+ return { ok: false, error: "Expected chat_id/user_id as digits, or a valid @username." };
596892
+ }
596893
+ resolveTelegramFilePath(rawPath, repoRoot, scopedRoot) {
596894
+ const base3 = scopedRoot ? resolve39(scopedRoot) : resolve39(repoRoot);
596895
+ const trimmed = rawPath.trim().replace(/^["']|["']$/g, "");
596896
+ const abs = isAbsolute6(trimmed) ? resolve39(trimmed) : resolve39(base3, trimmed);
596897
+ if (scopedRoot && !isPathInside(base3, abs)) {
596898
+ return { ok: false, error: `Public/group telegram_send_file can only send files inside ${base3}.` };
596899
+ }
596900
+ if (!existsSync105(abs)) return { ok: false, error: `File does not exist: ${trimmed}` };
596901
+ if (!statSync35(abs).isFile()) return { ok: false, error: `Path is not a file: ${trimmed}` };
596902
+ return { ok: true, path: abs };
596903
+ }
596628
596904
  /** Check if a message is from the admin user (uses fromUserId, NOT chatId) */
596629
596905
  isAdminUser(msg) {
596630
596906
  if (!this.adminUserId) return false;
@@ -596852,17 +597128,41 @@ ${visionContext}]`;
596852
597128
  }
596853
597129
  return options2.html ? this.sendMessageHTML(msg.chatId, text, options2.replyToMessageId) : this.sendMessage(msg.chatId, text);
596854
597130
  }
596855
- async sendMediaReference(chatId, media) {
597131
+ async sendTelegramFileToChat(chatId, path11, options2) {
597132
+ const abs = resolve39(path11);
597133
+ if (!existsSync105(abs) || !statSync35(abs).isFile()) {
597134
+ throw new Error(`File does not exist or is not a regular file: ${path11}`);
597135
+ }
597136
+ return this.sendMediaReferenceStrict(chatId, {
597137
+ original: abs,
597138
+ value: abs,
597139
+ kind: options2.kind,
597140
+ source: "file",
597141
+ audioAsVoice: options2.kind === "voice"
597142
+ }, options2);
597143
+ }
597144
+ async sendMediaReference(chatId, media, options2 = {}) {
597145
+ try {
597146
+ return await this.sendMediaReferenceStrict(chatId, media, options2);
597147
+ } catch {
597148
+ return null;
597149
+ }
597150
+ }
597151
+ async sendMediaReferenceStrict(chatId, media, options2 = {}) {
596856
597152
  const { method, field } = mediaTelegramMethod(media.kind);
597153
+ const caption = options2.caption?.trim();
596857
597154
  if (media.source === "url") {
596858
597155
  const result2 = await this.apiCall(method, {
596859
597156
  chat_id: chatId,
596860
- [field]: media.value
597157
+ [field]: media.value,
597158
+ ...caption ? { caption } : {},
597159
+ ...options2.replyToMessageId ? { reply_to_message_id: options2.replyToMessageId } : {}
596861
597160
  });
597161
+ if (result2.ok === false) throw new Error(String(result2.description || `Telegram ${method} failed`));
596862
597162
  this.state.messagesSent++;
596863
597163
  return result2.result?.message_id ?? null;
596864
597164
  }
596865
- if (!existsSync105(media.value)) return null;
597165
+ if (!existsSync105(media.value)) throw new Error(`File does not exist: ${media.value}`);
596866
597166
  const buffer2 = readFileSync86(media.value);
596867
597167
  const boundary = `----omnius-media-${Date.now()}-${Math.random().toString(36).slice(2)}`;
596868
597168
  const filename = basename22(media.value);
@@ -596878,6 +597178,8 @@ ${visionContext}]`;
596878
597178
  `));
596879
597179
  };
596880
597180
  addField("chat_id", String(chatId));
597181
+ if (caption) addField("caption", caption);
597182
+ if (options2.replyToMessageId) addField("reply_to_message_id", String(options2.replyToMessageId));
596881
597183
  parts.push(Buffer.from(`--${boundary}\r
596882
597184
  `));
596883
597185
  parts.push(Buffer.from(
@@ -596904,7 +597206,7 @@ Content-Type: ${contentType}\r
596904
597206
  this.state.messagesSent++;
596905
597207
  return result.result?.message_id ?? null;
596906
597208
  }
596907
- return null;
597209
+ throw new Error(String(result.description || `Telegram ${method} failed`));
596908
597210
  }
596909
597211
  async sendGeneratedArtifactsFromSubAgent(msg, subAgent, finalText, includeMentioned) {
596910
597212
  const root = subAgent.creativeWorkspaceRoot;
@@ -622423,23 +622725,44 @@ async function renderAsciiPreviewForImage(imagePath, displayPath, title, writer)
622423
622725
  formatImageAsciiContext: formatImageAsciiContext2
622424
622726
  } = await Promise.resolve().then(() => (init_image_ascii_preview(), image_ascii_preview_exports));
622425
622727
  const preview = await buildImageAsciiPreview2(imagePath);
622426
- if (!preview) return "";
622728
+ if (!preview) {
622729
+ writer(() => renderWarning(`Image ASCII preview unavailable for ${displayPath}.`));
622730
+ return "";
622731
+ }
622427
622732
  writer(() => renderImageAsciiPreview(title, displayPath, preview.ascii, preview.renderer));
622428
622733
  return formatImageAsciiContext2(preview, displayPath);
622429
622734
  } catch {
622430
622735
  return "";
622431
622736
  }
622432
622737
  }
622738
+ function formatImageGenerationProgress2(event) {
622739
+ const elapsed = event.elapsedMs && event.elapsedMs > 1500 ? ` ${Math.round(event.elapsedMs / 1e3)}s` : "";
622740
+ if (typeof event.percent === "number") {
622741
+ const width = 20;
622742
+ const filled = Math.max(0, Math.min(width, Math.round(event.percent / 100 * width)));
622743
+ const bar = `${"#".repeat(filled)}${"-".repeat(width - filled)}`;
622744
+ return `Image ${event.stage}: [${bar}] ${event.percent}% ${event.message}${elapsed}`;
622745
+ }
622746
+ return `Image ${event.stage}: ${event.message}${elapsed}`;
622747
+ }
622433
622748
  async function renderAsciiPreviewForToolResult(toolName, output, repoRoot, writer) {
622434
622749
  if (!output) return;
622435
622750
  try {
622436
622751
  const { extractSavedImagePath: extractSavedImagePath2 } = await Promise.resolve().then(() => (init_image_ascii_preview(), image_ascii_preview_exports));
622437
622752
  const imagePath = extractSavedImagePath2(output, repoRoot);
622438
- if (!imagePath) return;
622753
+ if (!imagePath) {
622754
+ if (toolName === "generate_image") {
622755
+ writer(() => renderWarning("Generated image preview skipped: no saved image path was found in the tool output."));
622756
+ }
622757
+ return;
622758
+ }
622439
622759
  const displayPath = relative14(repoRoot, imagePath).startsWith("..") ? imagePath : relative14(repoRoot, imagePath);
622440
622760
  const title = toolName === "generate_image" ? "Generated image" : toolName === "screenshot" ? "Screenshot" : toolName === "camera_capture" ? "Camera frame" : "Image";
622441
622761
  await renderAsciiPreviewForImage(imagePath, displayPath, title, writer);
622442
- } catch {
622762
+ } catch (err) {
622763
+ if (toolName === "generate_image") {
622764
+ writer(() => renderWarning(`Generated image preview failed: ${err instanceof Error ? err.message : String(err)}`));
622765
+ }
622443
622766
  }
622444
622767
  }
622445
622768
  async function runSelfImprovementCycle(repoRoot) {
@@ -623413,6 +623736,14 @@ ${entry.fullContent}`
623413
623736
  fn();
623414
623737
  }
623415
623738
  };
623739
+ for (const tool of tools) {
623740
+ const maybeProgressTool = tool;
623741
+ if (typeof maybeProgressTool.setProgressCallback === "function") {
623742
+ maybeProgressTool.setProgressCallback((event) => {
623743
+ contentWrite(() => renderInfo(formatImageGenerationProgress2(event)));
623744
+ });
623745
+ }
623746
+ }
623416
623747
  runner.onEvent((event) => {
623417
623748
  emotionEngine?.appraise(event);
623418
623749
  switch (event.type) {
@@ -623589,7 +623920,7 @@ ${entry.fullContent}`
623589
623920
  }
623590
623921
  });
623591
623922
  }
623592
- if (event.success) {
623923
+ if (event.success || event.toolName === "generate_image") {
623593
623924
  void renderAsciiPreviewForToolResult(event.toolName, event.content ?? "", repoRoot, contentWrite);
623594
623925
  }
623595
623926
  if (voice?.enabled && voice.voiceMode === "voicechat" && _voiceChatSession2?.isActive && event.toolName === "task_complete") {
@@ -623718,7 +624049,7 @@ ${entry.fullContent}`
623718
624049
  if (_apiCallbacks?.onStatus)
623719
624050
  _apiCallbacks.onStatus(event.content ?? "");
623720
624051
  if (isNeovimActive()) {
623721
- writeToNeovimOutput(`\x1B[2m${event.content ?? ""}\x1B[0m\r
624052
+ writeToNeovimOutput(`\x1B[38;5;250m${event.content ?? ""}\x1B[0m\r
623722
624053
  `);
623723
624054
  } else {
623724
624055
  contentWrite(() => renderInfo(event.content ?? ""));
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.11",
9
+ "version": "1.0.12",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",