@livx.cc/agentx 0.96.13 → 0.96.15

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/cli.js CHANGED
@@ -7147,6 +7147,23 @@ function resolveAecBinary() {
7147
7147
  }
7148
7148
  return bin;
7149
7149
  }
7150
+ function openMicSettings() {
7151
+ if (process.platform !== "darwin") return;
7152
+ try {
7153
+ spawnSync2("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
7154
+ } catch {
7155
+ }
7156
+ }
7157
+ function micPermissionStatus() {
7158
+ const bin = resolveAecBinary();
7159
+ if (!bin) return null;
7160
+ const r = spawnSync2(bin, ["--check-mic"], { encoding: "utf8" });
7161
+ const out = (r.stdout ?? "").trim();
7162
+ if (out === "authorized") return "authorized";
7163
+ if (out === "denied" || out === "restricted") return "denied";
7164
+ if (out === "notDetermined") return "notDetermined";
7165
+ return null;
7166
+ }
7150
7167
  function aecUnavailableHint() {
7151
7168
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
7152
7169
  if (resolveAecBinary()) return null;
@@ -7200,8 +7217,10 @@ var AecDuplexAudio = class {
7200
7217
  }
7201
7218
  bin;
7202
7219
  aec = true;
7220
+ onFatal;
7203
7221
  proc = null;
7204
7222
  stopped = false;
7223
+ micDenied = false;
7205
7224
  bytesWritten = 0;
7206
7225
  startedAt = 0;
7207
7226
  // --- AudioSource ---
@@ -7214,7 +7233,17 @@ var AecDuplexAudio = class {
7214
7233
  });
7215
7234
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
7216
7235
  this.proc.stderr.on("data", (d) => {
7217
- for (const ln of String(d).split("\n")) if (ln.trim()) log16.debug(`mic-aec: ${ln.trim()}`);
7236
+ for (const ln of String(d).split("\n")) {
7237
+ const s = ln.trim();
7238
+ if (!s) continue;
7239
+ if (/mic access granted:\s*false/i.test(s)) {
7240
+ if (!this.micDenied) {
7241
+ this.micDenied = true;
7242
+ openMicSettings();
7243
+ this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
7244
+ }
7245
+ } else log16.debug(`mic-aec: ${s}`);
7246
+ }
7218
7247
  });
7219
7248
  }
7220
7249
  stop() {
@@ -7314,6 +7343,7 @@ var VoiceIOOptions = class extends VoiceEngineOptions {
7314
7343
  cartesiaVoiceId = process.env.CARTESIA_VOICE_ID ?? "";
7315
7344
  };
7316
7345
  var VoiceIO = class extends VoiceEngine {
7346
+ duplexSource;
7317
7347
  constructor(options) {
7318
7348
  const o = { ...new VoiceIOOptions(), ...options };
7319
7349
  const bin = !o.stt || !o.player ? resolveAecBinary() : null;
@@ -7328,6 +7358,12 @@ var VoiceIO = class extends VoiceEngine {
7328
7358
  overlapEnergyHold: process.env.OVERLAP_ENERGY_HOLD === "1" || o.overlapEnergyHold
7329
7359
  // textless residue pre-pause: opt-in (hiccup source)
7330
7360
  });
7361
+ this.duplexSource = duplex;
7362
+ }
7363
+ /** Host hook for an unrecoverable audio-source failure (e.g. mic permission denied). Only the duplex
7364
+ * AEC source can hit it; a no-op otherwise. */
7365
+ set onFatal(fn) {
7366
+ if (this.duplexSource) this.duplexSource.onFatal = fn;
7331
7367
  }
7332
7368
  /** ready = keys present (AEC vs heuristic is decided at start()) */
7333
7369
  static available(env = process.env) {
@@ -12015,6 +12051,22 @@ ${task}`;
12015
12051
  const keys = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY", "GROQ_API_KEY"].filter((k) => process.env[k]);
12016
12052
  keys.length ? ok(`provider keys: ${keys.join(", ")}`) : bad("no provider keys set (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY)");
12017
12053
  process.env.BODIFY_API_KEY && process.env.BODIFY_APP_ID ? ok(`bodify secrets: ${process.env.BODIFY_APP_ID}`) : warn("bodify secrets: not configured (set BODIFY_API_KEY + BODIFY_APP_ID)");
12054
+ {
12055
+ const { spawnSync: spawnSync7 } = await import("child_process");
12056
+ const has = (cmd) => spawnSync7("which", [cmd]).status === 0;
12057
+ if (!VoiceIO.available()) warn("voice: keys missing (SONIOX_API_KEY / CARTESIA_API_KEY / CARTESIA_VOICE_ID) \u2014 voice disabled");
12058
+ else {
12059
+ ok("voice: keys present");
12060
+ has("ffmpeg") ? ok("voice: ffmpeg installed") : bad("voice: ffmpeg missing \u2014 `brew install ffmpeg`");
12061
+ if (process.platform === "darwin") {
12062
+ has("swiftc") ? ok("voice: swiftc present (AEC echo cancellation)") : warn("voice: no swiftc \u2014 `xcode-select --install` for echo cancellation (else heuristic tier)");
12063
+ const mic = micPermissionStatus();
12064
+ if (mic === "authorized") ok("voice: microphone permission granted");
12065
+ else if (mic === "denied") bad("voice: microphone permission DENIED \u2014 System Settings \u2192 Privacy & Security \u2192 Microphone, then restart your terminal");
12066
+ else if (mic === "notDetermined") warn("voice: microphone permission not yet granted \u2014 you'll be prompted on first --voice");
12067
+ }
12068
+ }
12069
+ }
12018
12070
  const info = getModelInfo(work.model);
12019
12071
  info?.pricing ? ok(`model ${work.model} \u2014 priced (${info.pricing.inputCostPer1K}/${info.pricing.outputCostPer1K} per 1k in/out)`) : warn(`model ${work.model} \u2014 no pricing in the catalog (costs will show ~$0; verify the id)`);
12020
12072
  const cfgFiles = ["ts", "js", "json"].flatMap((e) => [`${cwd}/.agent/config.${e}`, `${homedir9()}/.agent/config.${e}`]).filter((p) => existsSync9(p));
@@ -13068,6 +13120,17 @@ ${out}
13068
13120
  }).finally(() => editorRef?.redrawNow());
13069
13121
  }
13070
13122
  });
13123
+ voiceIO.onFatal = (msg) => {
13124
+ err(yellow(`
13125
+ \u26A0 voice off \u2014 ${msg}
13126
+ `));
13127
+ if (voiceIO) {
13128
+ voiceIO.stop();
13129
+ voiceIO = void 0;
13130
+ voicePartial = "";
13131
+ editorRef?.redrawNow();
13132
+ }
13133
+ };
13071
13134
  try {
13072
13135
  await voiceIO.start();
13073
13136
  const inDev = voiceIO.usingAec ? detectedInputDevice() : null;