open-agents-ai 0.103.98 → 0.103.99

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 +223 -169
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -35165,6 +35165,8 @@ var init_voice = __esm({
35165
35165
  * Accepts a path to a WAV/MP3/OGG/FLAC file (min 3 seconds).
35166
35166
  * Returns status message.
35167
35167
  */
35168
+ /** Filename of the last cloned voice (set by setCloneVoice for UI to locate new entry) */
35169
+ lastClonedFilename = null;
35168
35170
  async setCloneVoice(audioPath) {
35169
35171
  let p = audioPath.trim();
35170
35172
  if (p.startsWith("'") && p.endsWith("'") || p.startsWith('"') && p.endsWith('"')) {
@@ -35183,16 +35185,22 @@ var init_voice = __esm({
35183
35185
  if (!existsSync33(refsDir))
35184
35186
  mkdirSync13(refsDir, { recursive: true });
35185
35187
  const ext = audioPath.split(".").pop() || "wav";
35186
- const destPath = join41(refsDir, `custom-clone.${ext}`);
35188
+ const srcName = (audioPath.split("/").pop() ?? "clone").replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]/g, "-");
35189
+ const ts = Date.now().toString(36);
35190
+ const destFilename = `clone-${srcName}-${ts}.${ext}`;
35191
+ const destPath = join41(refsDir, destFilename);
35187
35192
  try {
35188
35193
  const data = readFileSync23(audioPath);
35189
35194
  writeFileSync13(destPath, data);
35190
35195
  } catch (err) {
35191
35196
  return `Failed to copy audio file: ${err instanceof Error ? err.message : String(err)}`;
35192
35197
  }
35198
+ const meta = this.loadCloneMeta();
35199
+ meta[destFilename] = srcName.replace(/[-_]/g, " ");
35200
+ this.saveCloneMeta(meta);
35193
35201
  this.luxttsCloneRef = destPath;
35194
- const filename = audioPath.split("/").pop() ?? audioPath;
35195
- return `Voice clone reference set: ${filename}. LuxTTS will use this voice.`;
35202
+ this.lastClonedFilename = destFilename;
35203
+ return `Voice clone reference set: ${destFilename}. LuxTTS will use this voice.`;
35196
35204
  }
35197
35205
  /**
35198
35206
  * Generate a synthetic voice clone reference from an existing ONNX voice model.
@@ -37172,7 +37180,9 @@ async function handleSlashCommand(input, ctx) {
37172
37180
  return "handled";
37173
37181
  }
37174
37182
  if (arg === "list" || arg === "ls" || arg === "voices") {
37183
+ ctx.setOverlayActive?.(true);
37175
37184
  await handleVoiceList(ctx);
37185
+ ctx.setOverlayActive?.(false);
37176
37186
  return "handled";
37177
37187
  }
37178
37188
  const msg = await ctx.voiceSetModel(arg);
@@ -38238,185 +38248,195 @@ function formatFileSize(bytes) {
38238
38248
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
38239
38249
  }
38240
38250
  async function handleVoiceMenu(ctx, save, hasLocal) {
38241
- const currentMode = ctx.voiceGetMode?.() ?? "action";
38242
38251
  const modeLabels = {
38243
38252
  chat: "Chat \u2014 speaks model responses",
38244
38253
  action: "Action \u2014 speaks tool narrations",
38245
38254
  verbose: "Verbose \u2014 speaks both"
38246
38255
  };
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)" : ""}`);
38256
+ while (true) {
38257
+ const isEnabled = ctx.voiceIsEnabled?.() ?? false;
38258
+ const currentMode = ctx.voiceGetMode?.() ?? "action";
38259
+ const currentModel = ctx.voiceGetModel?.() ?? "unknown";
38260
+ const enabledLabel = isEnabled ? selectColors.green("\u25CF Voice: Enabled") : selectColors.dim("\u25CB Voice: Disabled");
38261
+ const items = [
38262
+ { key: "header-status", label: selectColors.dim("\u2500\u2500\u2500 Voice Status \u2500\u2500\u2500") },
38263
+ { key: "toggle", label: isEnabled ? "Disable Voice" : "Enable Voice", detail: enabledLabel },
38264
+ { key: "header-config", label: selectColors.dim("\u2500\u2500\u2500 Configuration \u2500\u2500\u2500") },
38265
+ { key: "mode", label: "Voice Mode", detail: `${selectColors.green(currentMode)} \u2014 ${modeLabels[currentMode]}` },
38266
+ { key: "voice", label: "Change TTS Voice", detail: `current: ${selectColors.green(currentModel)}` },
38267
+ { key: "system", label: "Change TTS System", detail: "onnx / mlx / luxtts" },
38268
+ { key: "clone", label: "Clone Voice", detail: "drag & drop audio file" },
38269
+ { key: "list", label: "Manage Clone Voices", detail: "rename, play, delete" }
38270
+ ];
38271
+ ctx.setOverlayActive?.(true);
38272
+ const result = await tuiSelect({
38273
+ items,
38274
+ title: "Voice Configuration",
38275
+ rl: ctx.rl,
38276
+ availableRows: ctx.availableContentRows?.(),
38277
+ skipKeys: ["header-status", "header-config"]
38278
+ });
38279
+ ctx.setOverlayActive?.(false);
38280
+ if (!result.confirmed || !result.key)
38281
+ return;
38282
+ switch (result.key) {
38283
+ case "toggle": {
38284
+ const msg = await ctx.voiceToggle();
38285
+ const isOn = msg.toLowerCase().includes("enabled") || msg.toLowerCase().includes("on");
38286
+ save({ voice: isOn });
38287
+ continue;
38292
38288
  }
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)" : ""));
38289
+ case "mode": {
38290
+ ctx.setOverlayActive?.(true);
38291
+ const modeItems = [
38292
+ { key: "chat", label: "Chat", detail: "speaks streamed model text at normal pitch" },
38293
+ { key: "action", label: "Action", detail: "speaks tool call narrations with emotion pitch" },
38294
+ { key: "verbose", label: "Verbose", detail: "speaks both chat and actions" }
38295
+ ];
38296
+ const modeResult = await tuiSelect({
38297
+ items: modeItems,
38298
+ activeKey: currentMode,
38299
+ title: "Voice Mode",
38300
+ rl: ctx.rl,
38301
+ availableRows: ctx.availableContentRows?.()
38302
+ });
38303
+ ctx.setOverlayActive?.(false);
38304
+ if (modeResult.confirmed && modeResult.key) {
38305
+ const mode = modeResult.key;
38306
+ ctx.voiceSetMode?.(mode);
38307
+ save({ voiceMode: mode });
38308
+ }
38309
+ continue;
38319
38310
  }
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" });
38311
+ case "voice": {
38312
+ ctx.setOverlayActive?.(true);
38313
+ const voiceItems = [
38314
+ { key: "header-onnx", label: selectColors.dim("\u2500\u2500\u2500 ONNX Voices \u2500\u2500\u2500") },
38315
+ { key: "glados", label: "GLaDOS", detail: "Portal AI voice (ONNX)" },
38316
+ { key: "overwatch", label: "Overwatch", detail: "Overwatch announcer (ONNX)" },
38317
+ { key: "header-mlx", label: selectColors.dim("\u2500\u2500\u2500 MLX Voices (macOS) \u2500\u2500\u2500") },
38318
+ { key: "kokoro:af_heart", label: "Kokoro Heart", detail: "warm female (MLX)" },
38319
+ { key: "kokoro:af_bella", label: "Kokoro Bella", detail: "expressive female (MLX)" },
38320
+ { key: "kokoro:am_adam", label: "Kokoro Adam", detail: "male (MLX)" },
38321
+ { key: "kokoro:bf_emma", label: "Kokoro Emma", detail: "British female (MLX)" },
38322
+ { key: "header-clone", label: selectColors.dim("\u2500\u2500\u2500 Voice Clone \u2500\u2500\u2500") },
38323
+ { key: "luxtts", label: "LuxTTS", detail: "voice cloning engine" }
38324
+ ];
38325
+ const voiceResult = await tuiSelect({
38326
+ items: voiceItems,
38327
+ activeKey: currentModel,
38328
+ title: "Select TTS Voice",
38329
+ rl: ctx.rl,
38330
+ availableRows: ctx.availableContentRows?.(),
38331
+ skipKeys: ["header-onnx", "header-mlx", "header-clone"]
38332
+ });
38333
+ ctx.setOverlayActive?.(false);
38334
+ if (voiceResult.confirmed && voiceResult.key) {
38335
+ const msg = await ctx.voiceSetModel(voiceResult.key);
38336
+ save({ voice: true, voiceModel: voiceResult.key });
38337
+ }
38338
+ continue;
38331
38339
  }
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;
38340
+ case "system": {
38341
+ ctx.setOverlayActive?.(true);
38342
+ const sysItems = [
38343
+ { key: "onnx", label: "Piper ONNX", detail: "cross-platform, fast, offline" },
38344
+ { key: "luxtts", label: "LuxTTS", detail: "voice cloning, Python venv" },
38345
+ { key: "header-import", label: selectColors.dim("\u2500\u2500\u2500 Import \u2500\u2500\u2500") },
38346
+ { key: "import-onnx", label: "Import Piper Model", detail: "drag & drop .onnx + .onnx.json" }
38347
+ ];
38348
+ if (process.platform === "darwin") {
38349
+ sysItems.splice(1, 0, { key: "mlx", label: "MLX Audio", detail: "Apple Silicon, Kokoro voices" });
38350
+ }
38351
+ const sysResult = await tuiSelect({
38352
+ items: sysItems,
38353
+ title: "Select TTS System",
38354
+ rl: ctx.rl,
38355
+ availableRows: ctx.availableContentRows?.(),
38356
+ skipKeys: ["header-import"]
38357
+ });
38358
+ ctx.setOverlayActive?.(false);
38359
+ if (sysResult.confirmed && sysResult.key) {
38360
+ if (sysResult.key === "import-onnx") {
38361
+ ctx.setOverlayActive?.(true);
38362
+ const onnxDrop = await showDropPanel({
38363
+ title: "Import Piper Voice \u2014 Drop .onnx File",
38364
+ instruction: "Drag and drop the .onnx model file",
38365
+ allowedExtensions: [".onnx"],
38366
+ typeLabel: "ONNX model",
38367
+ rl: ctx.rl
38368
+ });
38369
+ ctx.setOverlayActive?.(false);
38370
+ if (!onnxDrop.confirmed || !onnxDrop.path) {
38371
+ continue;
38372
+ }
38373
+ ctx.setOverlayActive?.(true);
38374
+ const jsonDrop = await showDropPanel({
38375
+ title: "Import Piper Voice \u2014 Drop .onnx.json Config",
38376
+ instruction: "Drag and drop the .onnx.json config file for the model",
38377
+ allowedExtensions: [".json"],
38378
+ typeLabel: "JSON config",
38379
+ rl: ctx.rl
38380
+ });
38381
+ ctx.setOverlayActive?.(false);
38382
+ if (!jsonDrop.confirmed || !jsonDrop.path) {
38383
+ continue;
38384
+ }
38385
+ const { basename: basename16, join: pathJoin } = await import("node:path");
38386
+ const { copyFileSync: copyFileSync2, mkdirSync: mkdirSync21, existsSync: exists } = await import("node:fs");
38387
+ const { homedir: homedir15 } = await import("node:os");
38388
+ const modelName = basename16(onnxDrop.path, ".onnx").replace(/[^a-zA-Z0-9_-]/g, "-");
38389
+ const destDir = pathJoin(homedir15(), ".open-agents", "voice", "models", modelName);
38390
+ if (!exists(destDir))
38391
+ mkdirSync21(destDir, { recursive: true });
38392
+ copyFileSync2(onnxDrop.path, pathJoin(destDir, "model.onnx"));
38393
+ copyFileSync2(jsonDrop.path, pathJoin(destDir, "config.json"));
38394
+ const { registerCustomOnnxModel: registerCustomOnnxModel2 } = await Promise.resolve().then(() => (init_voice(), voice_exports));
38395
+ registerCustomOnnxModel2(modelName, modelName);
38396
+ await ctx.voiceSetModel(modelName);
38397
+ save({ voice: true, voiceModel: modelName });
38398
+ } else {
38399
+ const systemModelMap = {
38400
+ onnx: "glados",
38401
+ mlx: "kokoro:af_heart",
38402
+ luxtts: "luxtts"
38403
+ };
38404
+ const modelId = systemModelMap[sysResult.key] ?? "glados";
38405
+ await ctx.voiceSetModel(modelId);
38406
+ save({ voice: true, voiceModel: modelId });
38364
38407
  }
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
38408
  }
38409
+ continue;
38390
38410
  }
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;
38411
+ case "clone": {
38412
+ ctx.setOverlayActive?.(true);
38413
+ const dropResult = await showDropPanel({
38414
+ title: "Voice Clone \u2014 Drop Audio File",
38415
+ instruction: "Drag and drop an audio file to use as voice clone reference",
38416
+ allowedExtensions: [".wav", ".mp3", ".ogg", ".flac", ".m4a", ".opus", ".aac"],
38417
+ typeLabel: "Audio files",
38418
+ rl: ctx.rl
38419
+ });
38420
+ ctx.setOverlayActive?.(false);
38421
+ if (dropResult.confirmed && dropResult.path && ctx.voiceClone) {
38422
+ await ctx.voiceClone(dropResult.path);
38423
+ const newFilename = ctx.voiceGetLastClonedFilename?.();
38424
+ ctx.setOverlayActive?.(true);
38425
+ await handleVoiceList(ctx, newFilename ?? void 0);
38426
+ ctx.setOverlayActive?.(false);
38405
38427
  }
38406
- const msg = await ctx.voiceClone(dropResult.path);
38407
- renderInfo(msg);
38408
- } else {
38409
- renderInfo("Voice clone cancelled.");
38428
+ continue;
38429
+ }
38430
+ case "list": {
38431
+ ctx.setOverlayActive?.(true);
38432
+ await handleVoiceList(ctx);
38433
+ ctx.setOverlayActive?.(false);
38434
+ continue;
38410
38435
  }
38411
- break;
38412
- }
38413
- case "list": {
38414
- await handleVoiceList(ctx);
38415
- break;
38416
38436
  }
38417
38437
  }
38418
38438
  }
38419
- async function handleVoiceList(ctx) {
38439
+ async function handleVoiceList(ctx, focusFilename) {
38420
38440
  if (!ctx.voiceListClones) {
38421
38441
  renderWarning("Voice clone management not available in this context.");
38422
38442
  return;
@@ -38433,11 +38453,12 @@ async function handleVoiceList(ctx) {
38433
38453
  label: cl.name,
38434
38454
  detail: `${cl.filename} ${formatFileSize(cl.size)}`
38435
38455
  }));
38436
- const activeFilename = clones.find((cl) => cl.isActive)?.filename;
38456
+ const activeFilename = focusFilename ?? clones.find((cl) => cl.isActive)?.filename;
38457
+ let pendingAutoRename = focusFilename ?? null;
38437
38458
  const result = await tuiSelect({
38438
38459
  items,
38439
38460
  activeKey: activeFilename,
38440
- title: "Voice Clone References",
38461
+ title: focusFilename ? "Voice Clone References \u2014 Press any key to name your new voice" : "Voice Clone References",
38441
38462
  rl: ctx.rl,
38442
38463
  availableRows: ctx.availableContentRows?.(),
38443
38464
  customKeyHint: " e rename p play",
@@ -38449,6 +38470,22 @@ async function handleVoiceList(ctx) {
38449
38470
  }
38450
38471
  },
38451
38472
  onCustomKey: (item, key, helpers) => {
38473
+ if (pendingAutoRename && item.key === pendingAutoRename) {
38474
+ pendingAutoRename = null;
38475
+ const currentName = item.label;
38476
+ helpers.getInput("Name this voice:", currentName).then((newName) => {
38477
+ if (newName !== null && newName.trim() && newName.trim() !== currentName) {
38478
+ ctx.voiceRenameClone?.(item.key, newName.trim());
38479
+ const idx = items.findIndex((i) => i.key === item.key);
38480
+ if (idx >= 0) {
38481
+ helpers.updateItem(idx, { label: newName.trim() });
38482
+ }
38483
+ } else {
38484
+ helpers.render();
38485
+ }
38486
+ });
38487
+ return true;
38488
+ }
38452
38489
  if (key === "e" || key === "E") {
38453
38490
  const currentName = item.label;
38454
38491
  helpers.getInput("New name:", currentName).then((newName) => {
@@ -48620,6 +48657,8 @@ ${entry.fullContent}`
48620
48657
  let streamStartMs = 0;
48621
48658
  let streamTextBuffer = "";
48622
48659
  const contentWrite = (fn) => {
48660
+ if (overlayActive)
48661
+ return;
48623
48662
  if (statusBar?.isActive) {
48624
48663
  statusBar.beginContentWrite();
48625
48664
  fn();
@@ -49371,6 +49410,8 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
49371
49410
  }
49372
49411
  }
49373
49412
  function writeContent(fn) {
49413
+ if (overlayActive)
49414
+ return;
49374
49415
  if (statusBar.isActive) {
49375
49416
  statusBar.beginContentWrite();
49376
49417
  try {
@@ -49698,6 +49739,18 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
49698
49739
  voiceSetMode(mode) {
49699
49740
  voiceEngine.voiceMode = mode;
49700
49741
  },
49742
+ voiceIsEnabled() {
49743
+ return voiceEngine.enabled;
49744
+ },
49745
+ voiceGetModel() {
49746
+ return voiceEngine.modelId ?? "unknown";
49747
+ },
49748
+ voiceGetLastClonedFilename() {
49749
+ return voiceEngine.lastClonedFilename;
49750
+ },
49751
+ setOverlayActive(active) {
49752
+ overlayActive = active;
49753
+ },
49701
49754
  streamToggle() {
49702
49755
  streamEnabled = !streamEnabled;
49703
49756
  return streamEnabled;
@@ -51386,7 +51439,7 @@ async function runWithTUI(task, config, repoPath) {
51386
51439
  process.exit(1);
51387
51440
  }
51388
51441
  }
51389
- var taskManager;
51442
+ var overlayActive, taskManager;
51390
51443
  var init_interactive = __esm({
51391
51444
  "packages/cli/dist/tui/interactive.js"() {
51392
51445
  "use strict";
@@ -51422,6 +51475,7 @@ var init_interactive = __esm({
51422
51475
  init_telegram_bridge();
51423
51476
  init_status_bar();
51424
51477
  init_dist6();
51478
+ overlayActive = false;
51425
51479
  taskManager = new BackgroundTaskManager();
51426
51480
  }
51427
51481
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.98",
3
+ "version": "0.103.99",
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",