open-agents-ai 0.103.98 → 0.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +286 -172
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33610,6 +33610,45 @@ var init_setup = __esm({
33610
33610
  }
33611
33611
  });
33612
33612
 
33613
+ // packages/cli/dist/tui/overlay-lock.js
33614
+ function isOverlayActive() {
33615
+ return _overlayActive;
33616
+ }
33617
+ function bufferIfOverlay(stream, data) {
33618
+ if (!_overlayActive)
33619
+ return false;
33620
+ _buffer.push({ stream, data });
33621
+ return true;
33622
+ }
33623
+ function enterOverlay() {
33624
+ _depth++;
33625
+ _overlayActive = true;
33626
+ }
33627
+ function leaveOverlay() {
33628
+ _depth = Math.max(0, _depth - 1);
33629
+ if (_depth === 0) {
33630
+ _overlayActive = false;
33631
+ const pending = _buffer;
33632
+ _buffer = [];
33633
+ for (const { stream, data } of pending) {
33634
+ if (stream === "stdout") {
33635
+ process.stdout.write(data);
33636
+ } else {
33637
+ process.stderr.write(data);
33638
+ }
33639
+ }
33640
+ }
33641
+ }
33642
+ var _overlayActive, _depth, _buffer;
33643
+ var init_overlay_lock = __esm({
33644
+ "packages/cli/dist/tui/overlay-lock.js"() {
33645
+ "use strict";
33646
+ _overlayActive = false;
33647
+ _depth = 0;
33648
+ _buffer = [];
33649
+ }
33650
+ });
33651
+
33613
33652
  // packages/cli/dist/tui/tui-select.js
33614
33653
  function ansi3(code, text) {
33615
33654
  return isTTY3 ? `\x1B[${code}m${text}\x1B[0m` : text;
@@ -33704,6 +33743,7 @@ function tuiSelect(opts) {
33704
33743
  }
33705
33744
  stdin.resume();
33706
33745
  process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H\x1B[?25l");
33746
+ enterOverlay();
33707
33747
  function clampScroll(displayList) {
33708
33748
  const cursorPos = displayList.indexOf(cursor);
33709
33749
  if (cursorPos < 0)
@@ -33797,6 +33837,7 @@ function tuiSelect(opts) {
33797
33837
  function cleanup() {
33798
33838
  stdin.removeListener("data", onData);
33799
33839
  process.stdout.removeListener("resize", onResize);
33840
+ leaveOverlay();
33800
33841
  process.stdout.write("\x1B[?1049l\x1B[?25h");
33801
33842
  if (typeof stdin.setRawMode === "function") {
33802
33843
  stdin.setRawMode(hadRawMode ?? false);
@@ -34052,6 +34093,7 @@ var isTTY3, selectColors;
34052
34093
  var init_tui_select = __esm({
34053
34094
  "packages/cli/dist/tui/tui-select.js"() {
34054
34095
  "use strict";
34096
+ init_overlay_lock();
34055
34097
  isTTY3 = process.stdout.isTTY ?? false;
34056
34098
  selectColors = {
34057
34099
  orange: (t) => fg2562(208, t),
@@ -34094,6 +34136,7 @@ function showDropPanel(opts) {
34094
34136
  }
34095
34137
  stdin.resume();
34096
34138
  process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H\x1B[?25h");
34139
+ enterOverlay();
34097
34140
  let inputBuf = "";
34098
34141
  let errorMsg = "";
34099
34142
  function render() {
@@ -34137,6 +34180,7 @@ function showDropPanel(opts) {
34137
34180
  }
34138
34181
  function cleanup() {
34139
34182
  stdin.removeListener("data", onData);
34183
+ leaveOverlay();
34140
34184
  process.stdout.write("\x1B[?1049l\x1B[?25h");
34141
34185
  if (typeof stdin.setRawMode === "function") {
34142
34186
  stdin.setRawMode(hadRawMode ?? false);
@@ -34212,6 +34256,7 @@ var isTTY4, dc;
34212
34256
  var init_drop_panel = __esm({
34213
34257
  "packages/cli/dist/tui/drop-panel.js"() {
34214
34258
  "use strict";
34259
+ init_overlay_lock();
34215
34260
  isTTY4 = process.stdout.isTTY ?? false;
34216
34261
  dc = {
34217
34262
  bold: (t) => ansi4("1", t),
@@ -35165,6 +35210,8 @@ var init_voice = __esm({
35165
35210
  * Accepts a path to a WAV/MP3/OGG/FLAC file (min 3 seconds).
35166
35211
  * Returns status message.
35167
35212
  */
35213
+ /** Filename of the last cloned voice (set by setCloneVoice for UI to locate new entry) */
35214
+ lastClonedFilename = null;
35168
35215
  async setCloneVoice(audioPath) {
35169
35216
  let p = audioPath.trim();
35170
35217
  if (p.startsWith("'") && p.endsWith("'") || p.startsWith('"') && p.endsWith('"')) {
@@ -35183,16 +35230,22 @@ var init_voice = __esm({
35183
35230
  if (!existsSync33(refsDir))
35184
35231
  mkdirSync13(refsDir, { recursive: true });
35185
35232
  const ext = audioPath.split(".").pop() || "wav";
35186
- const destPath = join41(refsDir, `custom-clone.${ext}`);
35233
+ const srcName = (audioPath.split("/").pop() ?? "clone").replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]/g, "-");
35234
+ const ts = Date.now().toString(36);
35235
+ const destFilename = `clone-${srcName}-${ts}.${ext}`;
35236
+ const destPath = join41(refsDir, destFilename);
35187
35237
  try {
35188
35238
  const data = readFileSync23(audioPath);
35189
35239
  writeFileSync13(destPath, data);
35190
35240
  } catch (err) {
35191
35241
  return `Failed to copy audio file: ${err instanceof Error ? err.message : String(err)}`;
35192
35242
  }
35243
+ const meta = this.loadCloneMeta();
35244
+ meta[destFilename] = srcName.replace(/[-_]/g, " ");
35245
+ this.saveCloneMeta(meta);
35193
35246
  this.luxttsCloneRef = destPath;
35194
- const filename = audioPath.split("/").pop() ?? audioPath;
35195
- return `Voice clone reference set: ${filename}. LuxTTS will use this voice.`;
35247
+ this.lastClonedFilename = destFilename;
35248
+ return `Voice clone reference set: ${destFilename}. LuxTTS will use this voice.`;
35196
35249
  }
35197
35250
  /**
35198
35251
  * Generate a synthetic voice clone reference from an existing ONNX voice model.
@@ -38238,185 +38291,177 @@ function formatFileSize(bytes) {
38238
38291
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
38239
38292
  }
38240
38293
  async function handleVoiceMenu(ctx, save, hasLocal) {
38241
- const currentMode = ctx.voiceGetMode?.() ?? "action";
38242
38294
  const modeLabels = {
38243
38295
  chat: "Chat \u2014 speaks model responses",
38244
38296
  action: "Action \u2014 speaks tool narrations",
38245
38297
  verbose: "Verbose \u2014 speaks both"
38246
38298
  };
38247
- const items = [
38248
- { key: "header-status", label: selectColors.dim("\u2500\u2500\u2500 Voice Status \u2500\u2500\u2500") },
38249
- { key: "toggle", label: "Enable / Disable Voice", detail: "toggle on or off" },
38250
- { key: "header-config", label: selectColors.dim("\u2500\u2500\u2500 Configuration \u2500\u2500\u2500") },
38251
- { key: "mode", label: "Voice Mode", detail: modeLabels[currentMode] },
38252
- { key: "voice", label: "Change TTS Voice", detail: "select voice model" },
38253
- { key: "system", label: "Change TTS System", detail: "onnx / mlx / luxtts" },
38254
- { key: "clone", label: "Clone Voice", detail: "drag & drop audio file" },
38255
- { key: "list", label: "Manage Clone Voices", detail: "rename, play, delete" }
38256
- ];
38257
- const result = await tuiSelect({
38258
- items,
38259
- title: "Voice Configuration",
38260
- rl: ctx.rl,
38261
- availableRows: ctx.availableContentRows?.(),
38262
- skipKeys: ["header-status", "header-config"]
38263
- });
38264
- if (!result.confirmed || !result.key)
38265
- return;
38266
- switch (result.key) {
38267
- case "toggle": {
38268
- const msg = await ctx.voiceToggle();
38269
- const isOn = msg.toLowerCase().includes("enabled") || msg.toLowerCase().includes("on");
38270
- save({ voice: isOn });
38271
- renderInfo(msg + (hasLocal ? " (project-local)" : ""));
38272
- break;
38273
- }
38274
- case "mode": {
38275
- const modeItems = [
38276
- { key: "chat", label: "Chat", detail: "speaks streamed model text at normal pitch" },
38277
- { key: "action", label: "Action", detail: "speaks tool call narrations with emotion pitch" },
38278
- { key: "verbose", label: "Verbose", detail: "speaks both chat and actions" }
38279
- ];
38280
- const modeResult = await tuiSelect({
38281
- items: modeItems,
38282
- activeKey: currentMode,
38283
- title: "Voice Mode",
38284
- rl: ctx.rl,
38285
- availableRows: ctx.availableContentRows?.()
38286
- });
38287
- if (modeResult.confirmed && modeResult.key) {
38288
- const mode = modeResult.key;
38289
- ctx.voiceSetMode?.(mode);
38290
- save({ voiceMode: mode });
38291
- renderInfo(`Voice mode: ${modeLabels[mode]}${hasLocal ? " (project-local)" : ""}`);
38299
+ while (true) {
38300
+ const isEnabled = ctx.voiceIsEnabled?.() ?? false;
38301
+ const currentMode = ctx.voiceGetMode?.() ?? "action";
38302
+ const currentModel = ctx.voiceGetModel?.() ?? "unknown";
38303
+ const enabledLabel = isEnabled ? selectColors.green("\u25CF Voice: Enabled") : selectColors.dim("\u25CB Voice: Disabled");
38304
+ const items = [
38305
+ { key: "header-status", label: selectColors.dim("\u2500\u2500\u2500 Voice Status \u2500\u2500\u2500") },
38306
+ { key: "toggle", label: isEnabled ? "Disable Voice" : "Enable Voice", detail: enabledLabel },
38307
+ { key: "header-config", label: selectColors.dim("\u2500\u2500\u2500 Configuration \u2500\u2500\u2500") },
38308
+ { key: "mode", label: "Voice Mode", detail: `${selectColors.green(currentMode)} \u2014 ${modeLabels[currentMode]}` },
38309
+ { key: "voice", label: "Change TTS Voice", detail: `current: ${selectColors.green(currentModel)}` },
38310
+ { key: "system", label: "Change TTS System", detail: "onnx / mlx / luxtts" },
38311
+ { key: "clone", label: "Clone Voice", detail: "drag & drop audio file" },
38312
+ { key: "list", label: "Manage Clone Voices", detail: "rename, play, delete" }
38313
+ ];
38314
+ const result = await tuiSelect({
38315
+ items,
38316
+ title: "Voice Configuration",
38317
+ rl: ctx.rl,
38318
+ availableRows: ctx.availableContentRows?.(),
38319
+ skipKeys: ["header-status", "header-config"]
38320
+ });
38321
+ if (!result.confirmed || !result.key)
38322
+ return;
38323
+ switch (result.key) {
38324
+ case "toggle": {
38325
+ const msg = await ctx.voiceToggle();
38326
+ const isOn = msg.toLowerCase().includes("enabled") || msg.toLowerCase().includes("on");
38327
+ save({ voice: isOn });
38328
+ continue;
38292
38329
  }
38293
- break;
38294
- }
38295
- case "voice": {
38296
- const voiceItems = [
38297
- { key: "header-onnx", label: selectColors.dim("\u2500\u2500\u2500 ONNX Voices \u2500\u2500\u2500") },
38298
- { key: "glados", label: "GLaDOS", detail: "Portal AI voice (ONNX)" },
38299
- { key: "overwatch", label: "Overwatch", detail: "Overwatch announcer (ONNX)" },
38300
- { key: "header-mlx", label: selectColors.dim("\u2500\u2500\u2500 MLX Voices (macOS) \u2500\u2500\u2500") },
38301
- { key: "kokoro:af_heart", label: "Kokoro Heart", detail: "warm female (MLX)" },
38302
- { key: "kokoro:af_bella", label: "Kokoro Bella", detail: "expressive female (MLX)" },
38303
- { key: "kokoro:am_adam", label: "Kokoro Adam", detail: "male (MLX)" },
38304
- { key: "kokoro:bf_emma", label: "Kokoro Emma", detail: "British female (MLX)" },
38305
- { key: "header-clone", label: selectColors.dim("\u2500\u2500\u2500 Voice Clone \u2500\u2500\u2500") },
38306
- { key: "luxtts", label: "LuxTTS", detail: "voice cloning engine" }
38307
- ];
38308
- const voiceResult = await tuiSelect({
38309
- items: voiceItems,
38310
- title: "Select TTS Voice",
38311
- rl: ctx.rl,
38312
- availableRows: ctx.availableContentRows?.(),
38313
- skipKeys: ["header-onnx", "header-mlx", "header-clone"]
38314
- });
38315
- if (voiceResult.confirmed && voiceResult.key) {
38316
- const msg = await ctx.voiceSetModel(voiceResult.key);
38317
- save({ voice: true, voiceModel: voiceResult.key });
38318
- renderInfo(msg + (hasLocal ? " (project-local)" : ""));
38330
+ case "mode": {
38331
+ const modeItems = [
38332
+ { key: "chat", label: "Chat", detail: "speaks streamed model text at normal pitch" },
38333
+ { key: "action", label: "Action", detail: "speaks tool call narrations with emotion pitch" },
38334
+ { key: "verbose", label: "Verbose", detail: "speaks both chat and actions" }
38335
+ ];
38336
+ const modeResult = await tuiSelect({
38337
+ items: modeItems,
38338
+ activeKey: currentMode,
38339
+ title: "Voice Mode",
38340
+ rl: ctx.rl,
38341
+ availableRows: ctx.availableContentRows?.()
38342
+ });
38343
+ if (modeResult.confirmed && modeResult.key) {
38344
+ const mode = modeResult.key;
38345
+ ctx.voiceSetMode?.(mode);
38346
+ save({ voiceMode: mode });
38347
+ }
38348
+ continue;
38319
38349
  }
38320
- break;
38321
- }
38322
- case "system": {
38323
- const sysItems = [
38324
- { key: "onnx", label: "Piper ONNX", detail: "cross-platform, fast, offline" },
38325
- { key: "luxtts", label: "LuxTTS", detail: "voice cloning, Python venv" },
38326
- { key: "header-import", label: selectColors.dim("\u2500\u2500\u2500 Import \u2500\u2500\u2500") },
38327
- { key: "import-onnx", label: "Import Piper Model", detail: "drag & drop .onnx + .onnx.json" }
38328
- ];
38329
- if (process.platform === "darwin") {
38330
- sysItems.splice(1, 0, { key: "mlx", label: "MLX Audio", detail: "Apple Silicon, Kokoro voices" });
38350
+ case "voice": {
38351
+ const voiceItems = [
38352
+ { key: "header-onnx", label: selectColors.dim("\u2500\u2500\u2500 ONNX Voices \u2500\u2500\u2500") },
38353
+ { key: "glados", label: "GLaDOS", detail: "Portal AI voice (ONNX)" },
38354
+ { key: "overwatch", label: "Overwatch", detail: "Overwatch announcer (ONNX)" },
38355
+ { key: "header-mlx", label: selectColors.dim("\u2500\u2500\u2500 MLX Voices (macOS) \u2500\u2500\u2500") },
38356
+ { key: "kokoro:af_heart", label: "Kokoro Heart", detail: "warm female (MLX)" },
38357
+ { key: "kokoro:af_bella", label: "Kokoro Bella", detail: "expressive female (MLX)" },
38358
+ { key: "kokoro:am_adam", label: "Kokoro Adam", detail: "male (MLX)" },
38359
+ { key: "kokoro:bf_emma", label: "Kokoro Emma", detail: "British female (MLX)" },
38360
+ { key: "header-clone", label: selectColors.dim("\u2500\u2500\u2500 Voice Clone \u2500\u2500\u2500") },
38361
+ { key: "luxtts", label: "LuxTTS", detail: "voice cloning engine" }
38362
+ ];
38363
+ const voiceResult = await tuiSelect({
38364
+ items: voiceItems,
38365
+ activeKey: currentModel,
38366
+ title: "Select TTS Voice",
38367
+ rl: ctx.rl,
38368
+ availableRows: ctx.availableContentRows?.(),
38369
+ skipKeys: ["header-onnx", "header-mlx", "header-clone"]
38370
+ });
38371
+ if (voiceResult.confirmed && voiceResult.key) {
38372
+ await ctx.voiceSetModel(voiceResult.key);
38373
+ save({ voice: true, voiceModel: voiceResult.key });
38374
+ }
38375
+ continue;
38331
38376
  }
38332
- const sysResult = await tuiSelect({
38333
- items: sysItems,
38334
- title: "Select TTS System",
38335
- rl: ctx.rl,
38336
- availableRows: ctx.availableContentRows?.(),
38337
- skipKeys: ["header-import"]
38338
- });
38339
- if (sysResult.confirmed && sysResult.key) {
38340
- if (sysResult.key === "import-onnx") {
38341
- renderInfo("First, drop the .onnx model file:");
38342
- const onnxDrop = await showDropPanel({
38343
- title: "Import Piper Voice \u2014 Drop .onnx File",
38344
- instruction: "Drag and drop the .onnx model file",
38345
- allowedExtensions: [".onnx"],
38346
- typeLabel: "ONNX model",
38347
- rl: ctx.rl
38348
- });
38349
- if (!onnxDrop.confirmed || !onnxDrop.path) {
38350
- renderInfo("Import cancelled.");
38351
- break;
38352
- }
38353
- renderInfo("Now drop the .onnx.json config file:");
38354
- const jsonDrop = await showDropPanel({
38355
- title: "Import Piper Voice \u2014 Drop .onnx.json Config",
38356
- instruction: "Drag and drop the .onnx.json config file for the model",
38357
- allowedExtensions: [".json"],
38358
- typeLabel: "JSON config",
38359
- rl: ctx.rl
38360
- });
38361
- if (!jsonDrop.confirmed || !jsonDrop.path) {
38362
- renderInfo("Import cancelled.");
38363
- break;
38377
+ case "system": {
38378
+ const sysItems = [
38379
+ { key: "onnx", label: "Piper ONNX", detail: "cross-platform, fast, offline" },
38380
+ { key: "luxtts", label: "LuxTTS", detail: "voice cloning, Python venv" },
38381
+ { key: "header-import", label: selectColors.dim("\u2500\u2500\u2500 Import \u2500\u2500\u2500") },
38382
+ { key: "import-onnx", label: "Import Piper Model", detail: "drag & drop .onnx + .onnx.json" }
38383
+ ];
38384
+ if (process.platform === "darwin") {
38385
+ sysItems.splice(1, 0, { key: "mlx", label: "MLX Audio", detail: "Apple Silicon, Kokoro voices" });
38386
+ }
38387
+ const sysResult = await tuiSelect({
38388
+ items: sysItems,
38389
+ title: "Select TTS System",
38390
+ rl: ctx.rl,
38391
+ availableRows: ctx.availableContentRows?.(),
38392
+ skipKeys: ["header-import"]
38393
+ });
38394
+ if (sysResult.confirmed && sysResult.key) {
38395
+ if (sysResult.key === "import-onnx") {
38396
+ const onnxDrop = await showDropPanel({
38397
+ title: "Import Piper Voice \u2014 Drop .onnx File",
38398
+ instruction: "Drag and drop the .onnx model file",
38399
+ allowedExtensions: [".onnx"],
38400
+ typeLabel: "ONNX model",
38401
+ rl: ctx.rl
38402
+ });
38403
+ if (!onnxDrop.confirmed || !onnxDrop.path) {
38404
+ continue;
38405
+ }
38406
+ const jsonDrop = await showDropPanel({
38407
+ title: "Import Piper Voice \u2014 Drop .onnx.json Config",
38408
+ instruction: "Drag and drop the .onnx.json config file for the model",
38409
+ allowedExtensions: [".json"],
38410
+ typeLabel: "JSON config",
38411
+ rl: ctx.rl
38412
+ });
38413
+ if (!jsonDrop.confirmed || !jsonDrop.path) {
38414
+ continue;
38415
+ }
38416
+ const { basename: basename16, join: pathJoin } = await import("node:path");
38417
+ const { copyFileSync: copyFileSync2, mkdirSync: mkdirSync21, existsSync: exists } = await import("node:fs");
38418
+ const { homedir: homedir15 } = await import("node:os");
38419
+ const modelName = basename16(onnxDrop.path, ".onnx").replace(/[^a-zA-Z0-9_-]/g, "-");
38420
+ const destDir = pathJoin(homedir15(), ".open-agents", "voice", "models", modelName);
38421
+ if (!exists(destDir))
38422
+ mkdirSync21(destDir, { recursive: true });
38423
+ copyFileSync2(onnxDrop.path, pathJoin(destDir, "model.onnx"));
38424
+ copyFileSync2(jsonDrop.path, pathJoin(destDir, "config.json"));
38425
+ const { registerCustomOnnxModel: registerCustomOnnxModel2 } = await Promise.resolve().then(() => (init_voice(), voice_exports));
38426
+ registerCustomOnnxModel2(modelName, modelName);
38427
+ await ctx.voiceSetModel(modelName);
38428
+ save({ voice: true, voiceModel: modelName });
38429
+ } else {
38430
+ const systemModelMap = {
38431
+ onnx: "glados",
38432
+ mlx: "kokoro:af_heart",
38433
+ luxtts: "luxtts"
38434
+ };
38435
+ const modelId = systemModelMap[sysResult.key] ?? "glados";
38436
+ await ctx.voiceSetModel(modelId);
38437
+ save({ voice: true, voiceModel: modelId });
38364
38438
  }
38365
- const { basename: basename16, join: pathJoin } = await import("node:path");
38366
- const { copyFileSync: copyFileSync2, mkdirSync: mkdirSync21, existsSync: exists } = await import("node:fs");
38367
- const { homedir: homedir15 } = await import("node:os");
38368
- const modelName = basename16(onnxDrop.path, ".onnx").replace(/[^a-zA-Z0-9_-]/g, "-");
38369
- const destDir = pathJoin(homedir15(), ".open-agents", "voice", "models", modelName);
38370
- if (!exists(destDir))
38371
- mkdirSync21(destDir, { recursive: true });
38372
- copyFileSync2(onnxDrop.path, pathJoin(destDir, "model.onnx"));
38373
- copyFileSync2(jsonDrop.path, pathJoin(destDir, "config.json"));
38374
- const { registerCustomOnnxModel: registerCustomOnnxModel2 } = await Promise.resolve().then(() => (init_voice(), voice_exports));
38375
- registerCustomOnnxModel2(modelName, modelName);
38376
- const msg = await ctx.voiceSetModel(modelName);
38377
- save({ voice: true, voiceModel: modelName });
38378
- renderInfo(`Imported Piper model "${modelName}". ${msg}`);
38379
- } else {
38380
- const systemModelMap = {
38381
- onnx: "glados",
38382
- mlx: "kokoro:af_heart",
38383
- luxtts: "luxtts"
38384
- };
38385
- const modelId = systemModelMap[sysResult.key] ?? "glados";
38386
- const msg = await ctx.voiceSetModel(modelId);
38387
- save({ voice: true, voiceModel: modelId });
38388
- renderInfo(msg + (hasLocal ? " (project-local)" : ""));
38389
38439
  }
38440
+ continue;
38390
38441
  }
38391
- break;
38392
- }
38393
- case "clone": {
38394
- const dropResult = await showDropPanel({
38395
- title: "Voice Clone \u2014 Drop Audio File",
38396
- instruction: "Drag and drop an audio file to use as voice clone reference",
38397
- allowedExtensions: [".wav", ".mp3", ".ogg", ".flac", ".m4a", ".opus", ".aac"],
38398
- typeLabel: "Audio files",
38399
- rl: ctx.rl
38400
- });
38401
- if (dropResult.confirmed && dropResult.path) {
38402
- if (!ctx.voiceClone) {
38403
- renderWarning("Voice cloning not available in this context.");
38404
- break;
38442
+ case "clone": {
38443
+ const dropResult = await showDropPanel({
38444
+ title: "Voice Clone \u2014 Drop Audio File",
38445
+ instruction: "Drag and drop an audio file to use as voice clone reference",
38446
+ allowedExtensions: [".wav", ".mp3", ".ogg", ".flac", ".m4a", ".opus", ".aac"],
38447
+ typeLabel: "Audio files",
38448
+ rl: ctx.rl
38449
+ });
38450
+ if (dropResult.confirmed && dropResult.path && ctx.voiceClone) {
38451
+ await ctx.voiceClone(dropResult.path);
38452
+ const newFilename = ctx.voiceGetLastClonedFilename?.();
38453
+ await handleVoiceList(ctx, newFilename ?? void 0);
38405
38454
  }
38406
- const msg = await ctx.voiceClone(dropResult.path);
38407
- renderInfo(msg);
38408
- } else {
38409
- renderInfo("Voice clone cancelled.");
38455
+ continue;
38456
+ }
38457
+ case "list": {
38458
+ await handleVoiceList(ctx);
38459
+ continue;
38410
38460
  }
38411
- break;
38412
- }
38413
- case "list": {
38414
- await handleVoiceList(ctx);
38415
- break;
38416
38461
  }
38417
38462
  }
38418
38463
  }
38419
- async function handleVoiceList(ctx) {
38464
+ async function handleVoiceList(ctx, focusFilename) {
38420
38465
  if (!ctx.voiceListClones) {
38421
38466
  renderWarning("Voice clone management not available in this context.");
38422
38467
  return;
@@ -38433,11 +38478,12 @@ async function handleVoiceList(ctx) {
38433
38478
  label: cl.name,
38434
38479
  detail: `${cl.filename} ${formatFileSize(cl.size)}`
38435
38480
  }));
38436
- const activeFilename = clones.find((cl) => cl.isActive)?.filename;
38481
+ const activeFilename = focusFilename ?? clones.find((cl) => cl.isActive)?.filename;
38482
+ let pendingAutoRename = focusFilename ?? null;
38437
38483
  const result = await tuiSelect({
38438
38484
  items,
38439
38485
  activeKey: activeFilename,
38440
- title: "Voice Clone References",
38486
+ title: focusFilename ? "Voice Clone References \u2014 Press any key to name your new voice" : "Voice Clone References",
38441
38487
  rl: ctx.rl,
38442
38488
  availableRows: ctx.availableContentRows?.(),
38443
38489
  customKeyHint: " e rename p play",
@@ -38449,6 +38495,22 @@ async function handleVoiceList(ctx) {
38449
38495
  }
38450
38496
  },
38451
38497
  onCustomKey: (item, key, helpers) => {
38498
+ if (pendingAutoRename && item.key === pendingAutoRename) {
38499
+ pendingAutoRename = null;
38500
+ const currentName = item.label;
38501
+ helpers.getInput("Name this voice:", currentName).then((newName) => {
38502
+ if (newName !== null && newName.trim() && newName.trim() !== currentName) {
38503
+ ctx.voiceRenameClone?.(item.key, newName.trim());
38504
+ const idx = items.findIndex((i) => i.key === item.key);
38505
+ if (idx >= 0) {
38506
+ helpers.updateItem(idx, { label: newName.trim() });
38507
+ }
38508
+ } else {
38509
+ helpers.render();
38510
+ }
38511
+ });
38512
+ return true;
38513
+ }
38452
38514
  if (key === "e" || key === "E") {
38453
38515
  const currentName = item.label;
38454
38516
  helpers.getInput("New name:", currentName).then((newName) => {
@@ -48373,14 +48435,18 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
48373
48435
  }
48374
48436
  backend = new CascadeBackend(config.model, [primaryEndpoint, ...fallbackEndpoints], {
48375
48437
  onSwitch: (_from, to, reason) => {
48376
- process.stderr.write(`
48438
+ const msg = `
48377
48439
  [cascade] Failover \u2192 ${to.label ?? to.url}: ${reason}
48378
- `);
48440
+ `;
48441
+ if (!bufferIfOverlay("stderr", msg))
48442
+ process.stderr.write(msg);
48379
48443
  },
48380
48444
  onProbeResult: (ep, success) => {
48381
48445
  if (success) {
48382
- process.stderr.write(`[cascade] Primary recovered: ${ep.label ?? ep.url}
48383
- `);
48446
+ const msg = `[cascade] Primary recovered: ${ep.label ?? ep.url}
48447
+ `;
48448
+ if (!bufferIfOverlay("stderr", msg))
48449
+ process.stderr.write(msg);
48384
48450
  }
48385
48451
  }
48386
48452
  });
@@ -48620,6 +48686,22 @@ ${entry.fullContent}`
48620
48686
  let streamStartMs = 0;
48621
48687
  let streamTextBuffer = "";
48622
48688
  const contentWrite = (fn) => {
48689
+ if (isOverlayActive()) {
48690
+ const origWrite = process.stdout.write;
48691
+ const chunks = [];
48692
+ process.stdout.write = ((chunk) => {
48693
+ chunks.push(chunk);
48694
+ return true;
48695
+ });
48696
+ try {
48697
+ fn();
48698
+ } finally {
48699
+ process.stdout.write = origWrite;
48700
+ }
48701
+ for (const chunk of chunks)
48702
+ bufferIfOverlay("stdout", chunk);
48703
+ return;
48704
+ }
48623
48705
  if (statusBar?.isActive) {
48624
48706
  statusBar.beginContentWrite();
48625
48707
  fn();
@@ -49371,6 +49453,22 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
49371
49453
  }
49372
49454
  }
49373
49455
  function writeContent(fn) {
49456
+ if (isOverlayActive()) {
49457
+ const origWrite = process.stdout.write;
49458
+ const chunks = [];
49459
+ process.stdout.write = ((chunk) => {
49460
+ chunks.push(chunk);
49461
+ return true;
49462
+ });
49463
+ try {
49464
+ fn();
49465
+ } finally {
49466
+ process.stdout.write = origWrite;
49467
+ }
49468
+ for (const chunk of chunks)
49469
+ bufferIfOverlay("stdout", chunk);
49470
+ return;
49471
+ }
49374
49472
  if (statusBar.isActive) {
49375
49473
  statusBar.beginContentWrite();
49376
49474
  try {
@@ -49698,6 +49796,21 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
49698
49796
  voiceSetMode(mode) {
49699
49797
  voiceEngine.voiceMode = mode;
49700
49798
  },
49799
+ voiceIsEnabled() {
49800
+ return voiceEngine.enabled;
49801
+ },
49802
+ voiceGetModel() {
49803
+ return voiceEngine.modelId ?? "unknown";
49804
+ },
49805
+ voiceGetLastClonedFilename() {
49806
+ return voiceEngine.lastClonedFilename;
49807
+ },
49808
+ setOverlayActive(active) {
49809
+ if (active)
49810
+ enterOverlay();
49811
+ else
49812
+ leaveOverlay();
49813
+ },
49701
49814
  streamToggle() {
49702
49815
  streamEnabled = !streamEnabled;
49703
49816
  return streamEnabled;
@@ -51422,6 +51535,7 @@ var init_interactive = __esm({
51422
51535
  init_telegram_bridge();
51423
51536
  init_status_bar();
51424
51537
  init_dist6();
51538
+ init_overlay_lock();
51425
51539
  taskManager = new BackgroundTaskManager();
51426
51540
  }
51427
51541
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.98",
3
+ "version": "0.104.0",
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",