@livx.cc/agentx 0.96.16 → 0.96.18
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 +69 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/native/mic-aec.swift +25 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5500,6 +5500,11 @@ var VoiceEngineOptions = class {
|
|
|
5500
5500
|
* as a barge and abort the fresh turn (live: mid-sentence self-interruption + steps=1→steps=0 double
|
|
5501
5501
|
* abort). Short enough that a genuine immediate barge ("no wait—") still lands right after. */
|
|
5502
5502
|
bargeGraceMs = 600;
|
|
5503
|
+
/** Barge-in (talk over the assistant to interrupt). true = full-duplex (needs echo cancellation, or
|
|
5504
|
+
* the assistant's own TTS bleeds back and self-interrupts). false = HALF-DUPLEX: the engine is deaf
|
|
5505
|
+
* while audible (speaking + drain tail), so echo can never become a phantom turn — the right mode
|
|
5506
|
+
* when there's no AEC (e.g. the non-VPIO mic fallback) and no headphones. Cost: can't interrupt. */
|
|
5507
|
+
bargeIn = true;
|
|
5503
5508
|
/** Filler phrase spoken when holding for an incomplete utterance ('' disables). */
|
|
5504
5509
|
holdFiller = "";
|
|
5505
5510
|
/** Called when the engine holds an incomplete utterance (host can render a visual cue). */
|
|
@@ -5593,6 +5598,10 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5593
5598
|
get usingAec() {
|
|
5594
5599
|
return this.stt.usingAec;
|
|
5595
5600
|
}
|
|
5601
|
+
/** Flip barge-in at runtime (e.g. the mic fell back to non-VPIO → go half-duplex so echo can't leak). */
|
|
5602
|
+
setBargeIn(on) {
|
|
5603
|
+
this.options.bargeIn = on;
|
|
5604
|
+
}
|
|
5596
5605
|
idleWaiters = [];
|
|
5597
5606
|
setState(s) {
|
|
5598
5607
|
if (this.state === s) return;
|
|
@@ -5744,6 +5753,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5744
5753
|
}
|
|
5745
5754
|
handlePartial(text) {
|
|
5746
5755
|
if (this.speaking) {
|
|
5756
|
+
if (!this.options.bargeIn) return;
|
|
5747
5757
|
if (now() < this.bargeGraceUntil) {
|
|
5748
5758
|
if (!this.echoActive() || (this.usingAec ? this.genuine(text) : this.novelWords(text).length >= 1)) this.options.onPartial(text);
|
|
5749
5759
|
return;
|
|
@@ -5819,7 +5829,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5819
5829
|
this.stt.reset();
|
|
5820
5830
|
return;
|
|
5821
5831
|
}
|
|
5822
|
-
if (this.echoActive() && (this.usingAec ? !this.genuine(text) : this.novelWords(text).length < 2)) {
|
|
5832
|
+
if (this.echoActive() && (!this.options.bargeIn || (this.usingAec ? !this.genuine(text) : this.novelWords(text).length < 2))) {
|
|
5823
5833
|
this.stt.reset();
|
|
5824
5834
|
return;
|
|
5825
5835
|
}
|
|
@@ -7267,22 +7277,55 @@ var AecDuplexAudio = class {
|
|
|
7267
7277
|
this.bin = bin;
|
|
7268
7278
|
}
|
|
7269
7279
|
bin;
|
|
7270
|
-
|
|
7280
|
+
/** Mutable: starts true (VPIO/AEC). Flips false if we fall back to non-VPIO capture (heuristic tier). */
|
|
7281
|
+
_aec = true;
|
|
7282
|
+
get aec() {
|
|
7283
|
+
return this._aec;
|
|
7284
|
+
}
|
|
7271
7285
|
onFatal;
|
|
7286
|
+
/** Fired once when capture degrades to the non-VPIO (no-AEC) fallback — the host switches to
|
|
7287
|
+
* half-duplex so the assistant's own TTS can't bleed back as a phantom turn. */
|
|
7288
|
+
onDegrade;
|
|
7272
7289
|
proc = null;
|
|
7273
7290
|
stopped = false;
|
|
7274
7291
|
micDenied = false;
|
|
7292
|
+
noVpio = false;
|
|
7293
|
+
// currently running the non-VPIO fallback
|
|
7294
|
+
triedFallback = false;
|
|
7295
|
+
// one-shot guard
|
|
7296
|
+
gotChunk = false;
|
|
7297
|
+
// any mic audio since (re)spawn?
|
|
7298
|
+
onChunk = () => {
|
|
7299
|
+
};
|
|
7300
|
+
fallbackTimer = null;
|
|
7275
7301
|
bytesWritten = 0;
|
|
7276
7302
|
startedAt = 0;
|
|
7277
7303
|
// --- AudioSource ---
|
|
7278
7304
|
start(onChunk) {
|
|
7279
|
-
this.
|
|
7305
|
+
this.onChunk = onChunk;
|
|
7306
|
+
if (process.env.MIC_NO_VPIO === "1") {
|
|
7307
|
+
this.noVpio = true;
|
|
7308
|
+
this.triedFallback = true;
|
|
7309
|
+
this._aec = false;
|
|
7310
|
+
this.onDegrade?.();
|
|
7311
|
+
}
|
|
7312
|
+
this.spawnHelper();
|
|
7313
|
+
}
|
|
7314
|
+
/** (Re)spawn the helper. On the first spawn, arm a fast watchdog: if VPIO delivers NO audio within
|
|
7315
|
+
* ~2.5s, the VP input tap is dead on this machine (seen on macOS 26.5.x) — respawn once with
|
|
7316
|
+
* MIC_NO_VPIO=1 (plain capture, heuristic echo) so the mic actually works instead of starving STT. */
|
|
7317
|
+
spawnHelper() {
|
|
7318
|
+
const env = this.noVpio ? { ...process.env, MIC_NO_VPIO: "1" } : process.env;
|
|
7319
|
+
this.proc = spawn2(this.bin, [], { stdio: ["pipe", "pipe", "pipe"], env });
|
|
7280
7320
|
this.proc.stdin.on("error", () => {
|
|
7281
7321
|
});
|
|
7282
7322
|
this.proc.on("exit", (c) => {
|
|
7283
7323
|
if (c && !this.stopped) log16.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
7284
7324
|
});
|
|
7285
|
-
this.proc.stdout.on("data", (chunk) =>
|
|
7325
|
+
this.proc.stdout.on("data", (chunk) => {
|
|
7326
|
+
this.gotChunk = true;
|
|
7327
|
+
this.onChunk(chunk);
|
|
7328
|
+
});
|
|
7286
7329
|
this.proc.stderr.on("data", (d) => {
|
|
7287
7330
|
for (const ln of String(d).split("\n")) {
|
|
7288
7331
|
const s = ln.trim();
|
|
@@ -7296,9 +7339,22 @@ var AecDuplexAudio = class {
|
|
|
7296
7339
|
} else log16.debug(`mic-aec: ${s}`);
|
|
7297
7340
|
}
|
|
7298
7341
|
});
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7342
|
+
if (!this.noVpio && !this.triedFallback) {
|
|
7343
|
+
this.fallbackTimer = setTimeout(() => {
|
|
7344
|
+
if (this.stopped || this.gotChunk) return;
|
|
7345
|
+
this.triedFallback = true;
|
|
7346
|
+
this.noVpio = true;
|
|
7347
|
+
this._aec = false;
|
|
7348
|
+
log16.warn("mic-aec: VPIO delivered no audio in 2.5s \u2014 falling back to non-VPIO capture (no AEC \u2192 half-duplex, no barge-in)");
|
|
7349
|
+
this.onDegrade?.();
|
|
7350
|
+
this.killProc();
|
|
7351
|
+
this.spawnHelper();
|
|
7352
|
+
}, 2500);
|
|
7353
|
+
this.fallbackTimer.unref?.();
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
/** Kill the current child WITHOUT marking the whole source stopped (used for the fallback respawn). */
|
|
7357
|
+
killProc() {
|
|
7302
7358
|
const p = this.proc;
|
|
7303
7359
|
this.proc = null;
|
|
7304
7360
|
if (!p) return;
|
|
@@ -7310,6 +7366,11 @@ var AecDuplexAudio = class {
|
|
|
7310
7366
|
}
|
|
7311
7367
|
}, 500).unref?.();
|
|
7312
7368
|
}
|
|
7369
|
+
stop() {
|
|
7370
|
+
this.stopped = true;
|
|
7371
|
+
if (this.fallbackTimer) clearTimeout(this.fallbackTimer);
|
|
7372
|
+
this.killProc();
|
|
7373
|
+
}
|
|
7313
7374
|
// --- AudioSink (frame writer; same played/drain byte-math as the ffplay Player) ---
|
|
7314
7375
|
frame(payload) {
|
|
7315
7376
|
const stdin = this.proc?.stdin;
|
|
@@ -7410,6 +7471,7 @@ var VoiceIO = class extends VoiceEngine {
|
|
|
7410
7471
|
// textless residue pre-pause: opt-in (hiccup source)
|
|
7411
7472
|
});
|
|
7412
7473
|
this.duplexSource = duplex;
|
|
7474
|
+
if (duplex) duplex.onDegrade = () => this.setBargeIn(false);
|
|
7413
7475
|
}
|
|
7414
7476
|
/** Host hook for an unrecoverable audio failure — mic permission denied (duplex source) or no mic
|
|
7415
7477
|
* audio at all (STT watchdog). Routed to whichever can detect it. */
|