@livx.cc/agentx 0.99.102 → 0.99.104

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
@@ -9331,7 +9331,14 @@ var OverlapJudge = class {
9331
9331
  };
9332
9332
  function overlapModelFor(ai, env = process.env) {
9333
9333
  const model = env.AGENTX_CHANNEL_DECIDER_MODEL || void 0;
9334
- return model || env.GROQ_API_KEY ? fastOverlapModel(ai, model) : void 0;
9334
+ if (!model && !env.GROQ_API_KEY) return void 0;
9335
+ const m = fastOverlapModel(ai, model);
9336
+ const t0 = performance.now();
9337
+ void m("yeah", AbortSignal.timeout(1e4)).then(
9338
+ () => log19.debug(`fast model warm in ${Math.round(performance.now() - t0)}ms`),
9339
+ (e) => log19.warn(`fast model warm-up failed (the first overlap verdict may be late): ${e?.message ?? e}`)
9340
+ );
9341
+ return m;
9335
9342
  }
9336
9343
  var softBarge = (mode) => mode !== "turn";
9337
9344
 
@@ -13606,14 +13613,8 @@ function bindAckInstinct(v, instinct) {
13606
13613
  }
13607
13614
  function bindSecondChannel(v, sc, live) {
13608
13615
  const o = v.options;
13609
- const { onPartial, onUtterance, onState, onBargeIn, onBackchannel } = o;
13610
- const { show } = sc.options;
13616
+ const { onPartial, onUtterance, onState, onBargeIn } = o;
13611
13617
  let own = false;
13612
- let blip = "";
13613
- o.onBackchannel = (p) => {
13614
- blip = p;
13615
- onBackchannel(p);
13616
- };
13617
13618
  const take = v.takeInterruptedReply.bind(v);
13618
13619
  v.takeInterruptedReply = () => {
13619
13620
  const r = take();
@@ -13621,7 +13622,7 @@ function bindSecondChannel(v, sc, live) {
13621
13622
  return r.queued.length ? { ...r, full: "", heard: "" } : null;
13622
13623
  };
13623
13624
  o.onPartial = (t) => {
13624
- if (live() && !v.audible) sc.onInterim(t);
13625
+ if (live() && (own || !v.audible)) sc.onInterim(t);
13625
13626
  onPartial(t);
13626
13627
  };
13627
13628
  o.onUtterance = ((t, meta) => {
@@ -13644,25 +13645,34 @@ function bindSecondChannel(v, sc, live) {
13644
13645
  if (live()) sc.noteReply(t);
13645
13646
  return speakDelta(t, ...rest);
13646
13647
  });
13648
+ Object.defineProperty(sc.options, "aec", { get: () => v.usingAec, set: () => {
13649
+ }, enumerable: true, configurable: true });
13647
13650
  Object.assign(sc.options, {
13648
- aec: v.usingAec,
13649
13651
  speak: (text, kind) => {
13650
13652
  if (!live() || v.audible) return false;
13651
- if (kind === "backchannel") {
13652
- if (sc.language !== "en") return false;
13653
- blip = "";
13654
- v.fireBackchannel();
13655
- return;
13656
- }
13653
+ if (kind === "backchannel") return speakBlip(v, text);
13657
13654
  own = true;
13658
13655
  v.speakFiller(text);
13659
13656
  return v.awaitIdle().finally(() => {
13660
13657
  own = false;
13661
13658
  });
13662
- },
13663
- show: (text, kind) => show(kind === "backchannel" && blip ? blip : text, kind)
13659
+ }
13664
13660
  });
13665
13661
  }
13662
+ function speakBlip(v, text) {
13663
+ const E = VoiceEngine;
13664
+ const pool = E.BACKCHANNELS;
13665
+ E.BACKCHANNELS = [text];
13666
+ try {
13667
+ v.fireBackchannel();
13668
+ return true;
13669
+ } catch (e) {
13670
+ log28.warn(`backchannel blip failed \u2014 line dropped: ${e.message}`);
13671
+ return false;
13672
+ } finally {
13673
+ E.BACKCHANNELS = pool;
13674
+ }
13675
+ }
13666
13676
  function channelInput(sc, m) {
13667
13677
  if (m.type === "interim") {
13668
13678
  if (typeof m.aec === "boolean") sc.options.aec = m.aec;
@@ -13917,8 +13927,10 @@ function requestOpener(text) {
13917
13927
  return ASK.includes(w[0]) || WANT.test(w.slice(0, 4).join(" ")) || new RegExp("^\u05EA\\p{Script=Hebrew}{2,}$", "u").test(w[0]);
13918
13928
  }
13919
13929
  function pendingQuestion(text, lastWords = 6) {
13920
- const t = text.toLowerCase().replace(/[^\p{L}\p{N}'\s]+/gu, " ").split(/\s+/).filter(Boolean).slice(-lastWords).join(" ");
13921
- return /\b(can|could|would|will|do|does|did|is|are|should)\s+(you|we|i|it|there|they|he|she)\b/.test(t) || /(^|\s)(האם|אתה יכול|את יכולה)(\s|$)/u.test(t);
13930
+ const all = text.toLowerCase().replace(/[^\p{L}\p{N}'\s.!?]+/gu, " ");
13931
+ const sentence = all.split(/[.!?]/).at(-1).split(/\s+/).filter(Boolean);
13932
+ const t = sentence.slice(-lastWords).join(" ");
13933
+ return /\b(can|could|would|will|do|does|did|is|are|should)\s+(you|we|i|it|there|they|he|she)\b/.test(t) || /(^|\s)(האם|אתה יכול|את יכולה)(\s|$)/u.test(t) || /\b(what|why|how|where|when|who|which)\s+(do|does|did|is|are|should|would|can|could|will)\s+(you|we|i|it|they|he|she)\b/.test(sentence.slice(-4 * lastWords).join(" "));
13922
13934
  }
13923
13935
  var SCRIPTS = [
13924
13936
  [new RegExp("\\p{Script=Hebrew}", "gu"), "he"],
@@ -14194,10 +14206,13 @@ var CHANNEL_MODES = {
14194
14206
  generateFor: ["backchannel", "bridge"],
14195
14207
  reactionGapMs: 6e3,
14196
14208
  generateBudgetMs: 400,
14209
+ // A breath must outlast the word cadence: at ~2.5 words/s interims arrive every ~400ms, so a 250ms "breath" fired after
14210
+ // every word and only the 1.2s gaps held it (~25 lines per 30s, live probe). Lines ≥2.5s apart (light after light 3s), ≤6 in any 30s.
14197
14211
  backchannelMinWords: 5,
14198
- backchannelGapMs: 1200,
14199
- minGapMs: 1200,
14200
- backchannelPauseMs: 250
14212
+ backchannelGapMs: 3e3,
14213
+ minGapMs: 2500,
14214
+ backchannelPauseMs: 500,
14215
+ maxPerWindow: 6
14201
14216
  }
14202
14217
  };
14203
14218
  var CHANNEL_MODE_NAMES = Object.keys(CHANNEL_MODES);
@@ -14246,6 +14261,9 @@ var SecondChannelOptions = class {
14246
14261
  /** Backchannel after backchannel needs this gap (~4-6 cues in a 30s monologue — Para phone call 2026-10-13: 5s left
14247
14262
  * one cue in a long statement). */
14248
14263
  backchannelGapMs = 3e3;
14264
+ /** Soft cap: at most this many backchannels in any `windowMs` (0 = none). */
14265
+ maxPerWindow = 0;
14266
+ windowMs = 3e4;
14249
14267
  /** The user resumes within this long of their last words after a line of theirs was dispatched: the same statement goes
14250
14268
  * on (an STT endpoint mid-monologue) — its words still count toward the backchannel guards, and it is no fresh request. */
14251
14269
  continueMs = 3e3;
@@ -14390,12 +14408,7 @@ var SecondChannel = class {
14390
14408
  }
14391
14409
  get o() {
14392
14410
  const o = this.options;
14393
- if (o.mode !== "chatty" || o.aec) return o;
14394
- if (!this.degradedLogged) {
14395
- this.degradedLogged = true;
14396
- log32.info("chatty mode without echo cancellation \u2014 normal timing (our lines would re-trigger the mic)");
14397
- }
14398
- return { ...o, ...this.normalTiming };
14411
+ return o.mode !== "chatty" || o.aec ? o : { ...o, ...this.normalTiming };
14399
14412
  }
14400
14413
  /** The user's live partial for the current turn (the whole partial, not a delta). */
14401
14414
  onInterim(text) {
@@ -14533,10 +14546,17 @@ var SecondChannel = class {
14533
14546
  allowed(k) {
14534
14547
  if (!this.o.kinds.includes(k)) return false;
14535
14548
  if (this.audioLocked || this.inflight || this.o.now() - this.lastAt < this.o.minGapMs || k === this.lastKind && (k !== "backchannel" || this.o.now() - this.lastAt < this.o.backchannelGapMs)) return false;
14536
- if (k === "backchannel") return this.o.aec && this.userSpeaking && this.main === "idle";
14549
+ if (k === "backchannel") return this.o.aec && this.userSpeaking && this.main === "idle" && !this.capped();
14537
14550
  if (k === "filler") return !this.userSpeaking && this.main === "idle";
14538
14551
  return (this.main === "queued" || this.main === "thinking") && !this.bridged;
14539
14552
  }
14553
+ bcTimes = [];
14554
+ capped() {
14555
+ if (!this.o.maxPerWindow) return false;
14556
+ const from = this.o.now() - this.o.windowMs;
14557
+ this.bcTimes = this.bcTimes.filter((t) => t > from);
14558
+ return this.bcTimes.length >= this.o.maxPerWindow;
14559
+ }
14540
14560
  /** 0ms rule: the candidate line for this trigger, or silence. */
14541
14561
  rule(trigger, pauseMs = 0, breath = false, cue = null) {
14542
14562
  const w = words2(this.heard);
@@ -14579,6 +14599,11 @@ var SecondChannel = class {
14579
14599
  const n0 = words2(this.heard).length;
14580
14600
  this.decidedAt = n0;
14581
14601
  if (!(this.o.anticipate && this.plan && trigger === "interim")) cue = null;
14602
+ const opts = this.options;
14603
+ if (opts.mode === "chatty" && !opts.aec && !this.degradedLogged) {
14604
+ this.degradedLogged = true;
14605
+ log32.info("chatty mode without echo cancellation \u2014 normal timing (our lines would re-trigger the mic)");
14606
+ }
14582
14607
  const rule = this.rule(trigger, pauseMs, breath, cue);
14583
14608
  if (rule === "silence") return;
14584
14609
  this.busy = true;
@@ -14658,6 +14683,7 @@ var SecondChannel = class {
14658
14683
  if (r === false) return false;
14659
14684
  this.lastAt = this.o.now();
14660
14685
  this.lastKind = kind;
14686
+ if (kind === "backchannel") this.bcTimes.push(this.lastAt);
14661
14687
  if (kind === "bridge") this.bridged = true;
14662
14688
  this.recent = [...this.recent, text].slice(-this.o.avoidLines);
14663
14689
  this.live = text.trim();