@livx.cc/agentx 0.99.112 → 0.99.113

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
@@ -9328,14 +9328,40 @@ var ACK = new Set("yeah yea yes yep yup ya ok okay k right sure true totally exa
9328
9328
  var LEAD = /* @__PURE__ */ new Set(["and", "so", "um", "uh", "er", "well", "oh"]);
9329
9329
  var LEADER = /* @__PURE__ */ new Set(["yeah", "yea", "yes", "yep", "yup", "ok", "okay", "right", "sure", "alright", "mm", "mhm", "hmm", "uh-huh", "totally", "exactly", "true"]);
9330
9330
  var PHRASES = ["got it", "i see", "makes sense", "of course", "for sure", "all right", "that's right", "that's true", "thats right", "thats true", "love it", "i know", "me too", "uh huh"];
9331
+ var CONTINUE = /^(?:(?:and|so|oh) )?(?:and|so|really|seriously|then what|and then|go on|keep going|ואז|באמת|נו)$/;
9332
+ var AGREE = new Set("yeah yea yes yep yup right sure true totally exactly definitely absolutely agreed correct indeed \u05DB\u05DF \u05E0\u05DB\u05D5\u05DF \u05D1\u05D3\u05D9\u05D5\u05E7 \u05D1\u05E8\u05D5\u05E8 \u05D1\u05D8\u05D7 \u05D0\u05DB\u05DF \u05DC\u05D2\u05DE\u05E8\u05D9 \u05DE\u05E1\u05DB\u05D9\u05DD \u05DE\u05E1\u05DB\u05D9\u05DE\u05D4 \u05DE\u05DE\u05E9".split(" "));
9333
+ var AGREE_PHRASES = ["of course", "for sure", "that's right", "that's true", "thats right", "thats true", "makes sense", "i agree"];
9334
+ var HE_ACK = new Set("\u05DB\u05DF \u05E0\u05DB\u05D5\u05DF \u05D1\u05D3\u05D9\u05D5\u05E7 \u05D1\u05E8\u05D5\u05E8 \u05D1\u05D8\u05D7 \u05D0\u05DB\u05DF \u05DC\u05D2\u05DE\u05E8\u05D9 \u05DE\u05DE\u05E9 \u05D0\u05D5\u05E7\u05D9\u05D9 \u05D0\u05D5\u05E7\u05D9 \u05E1\u05D1\u05D1\u05D4 \u05D9\u05E4\u05D4 \u05DE\u05E2\u05D5\u05DC\u05D4 \u05D5\u05D5\u05D0\u05D5 \u05D0\u05D4\u05D4 \u05D0\u05D4 \u05D4\u05DE\u05DE \u05DE\u05DE\u05DE".split(" "));
9335
+ function cueKind(text) {
9336
+ const t = norm2(text);
9337
+ if (!t) return null;
9338
+ if (CONTINUE.test(t)) return "continue";
9339
+ let r = t;
9340
+ for (const p of AGREE_PHRASES) if (r.includes(p)) return "agree";
9341
+ r = r.split(/[\s-]+/).filter((w) => !LEAD.has(w)).join(" ");
9342
+ return r.split(" ").some((w) => AGREE.has(w)) ? "agree" : null;
9343
+ }
9344
+ var ASIDES = {
9345
+ en: { agree: ["Oh yeah, definitely.", "Yeah, exactly.", "Right, yeah."], continue: ["Yeah, so\u2026", "So, yeah\u2026"] },
9346
+ he: { agree: ["\u05DB\u05DF, \u05D1\u05D3\u05D9\u05D5\u05E7.", "\u05DB\u05DF, \u05DE\u05DE\u05E9.", "\u05E0\u05DB\u05D5\u05DF, \u05DB\u05DF."], continue: ["\u05DB\u05DF, \u05D0\u05D6\u2026", "\u05D0\u05D6 \u05DB\u05DF\u2026"] }
9347
+ };
9348
+ function overlapAside(text, lang, avoid = "", pick = (n) => Math.floor(Math.random() * n)) {
9349
+ const kind = cueKind(text);
9350
+ const l = new RegExp("\\p{Script=Hebrew}", "u").test(text) ? "he" : /[a-z]/i.test(text) ? "en" : lang;
9351
+ const pool = kind && l ? ASIDES[l]?.[kind] : void 0;
9352
+ if (!pool?.length) return null;
9353
+ const fresh = pool.filter((x) => x !== avoid);
9354
+ return fresh[pick(fresh.length)] ?? null;
9355
+ }
9331
9356
  function overlapRule(text) {
9357
+ if (CONTINUE.test(norm2(text))) return "keep";
9332
9358
  if (/\?/.test(text)) return "cut";
9333
9359
  let t = norm2(text);
9334
9360
  if (!t) return void 0;
9335
9361
  if (HARD.test(t)) return "cut";
9336
9362
  for (const p of PHRASES) t = t.split(p).join(" ");
9337
9363
  const words4 = t.split(/[\s-]+/).filter(Boolean);
9338
- const ack = (w) => ACK.has(w) || /^(?:ha)+h?$|^(?:he)+h?$|^m+(?:hm+)?$|^h?m+$/.test(w);
9364
+ const ack = (w) => ACK.has(w) || HE_ACK.has(w) || /^(?:ha)+h?$|^(?:he)+h?$|^m+(?:hm+)?$|^h?m+$/.test(w);
9339
9365
  if (words4.every(ack)) return "keep";
9340
9366
  const lead2 = words4.findIndex((w) => !LEAD.has(w));
9341
9367
  if (lead2 >= 0 && LEADER.has(words4[lead2]) && words4.slice(lead2).filter((w) => !ack(w) && !LEAD.has(w)).length <= 1) return "keep";
@@ -13616,6 +13642,8 @@ var VoiceCallOptions = class {
13616
13642
  onMetric = () => {
13617
13643
  };
13618
13644
  now = () => performance.now();
13645
+ /** An aside (aside()) needs this long since the last one — a cue answered every time sounds scripted. */
13646
+ asideGapMs = 8e3;
13619
13647
  };
13620
13648
  var VoiceCall = class {
13621
13649
  constructor(options) {
@@ -13659,6 +13687,7 @@ var VoiceCall = class {
13659
13687
  judge;
13660
13688
  cur;
13661
13689
  lang = null;
13690
+ lastAside = { text: "", at: -Infinity };
13662
13691
  lastAudibleAt = -Infinity;
13663
13692
  firstFinalAt = null;
13664
13693
  // the first final of the turn being formed (hold/merge)
@@ -13767,6 +13796,19 @@ var VoiceCall = class {
13767
13796
  if (d.verdict === "cut") this.barge();
13768
13797
  return d.verdict;
13769
13798
  }
13799
+ /** The fill channel's spoken aside for an overlap judged 'keep' ("yeah" → "Oh yeah, definitely."): the host plays it
13800
+ * while the reply is held, then resumes the same reply (bod-voice decideOverlap's aside). ≤4 canned words, never a
13801
+ * claim, the cue's language; null = stay quiet (a plain listener cue, too soon after the last, or no line). It is a
13802
+ * fill line: never MAIN history. */
13803
+ aside(text) {
13804
+ const now4 = this.o.now();
13805
+ if (now4 - this.lastAside.at < this.o.asideGapMs) return null;
13806
+ const line = overlapAside(text, this.lang, this.lastAside.text);
13807
+ if (!line) return null;
13808
+ this.lastAside = { text: line, at: now4 };
13809
+ this.o.onMetric({ name: "fill", kind: "aside", text: line, lang: this.lang });
13810
+ return line;
13811
+ }
13770
13812
  /** Did the last barge cut OUR fill line `text`? (hosts strip it from the cut reply — it is not MAIN's speech) */
13771
13813
  spoke(text) {
13772
13814
  return this.fill.spoke(text);
@@ -15355,7 +15397,9 @@ function bindVoiceCall(v, call, live, decideMs = 900) {
15355
15397
  if (!live() || ev.kind !== "overlap_pause") return;
15356
15398
  const heard = String(ev.text ?? "");
15357
15399
  void call.overlap(heard).then((verdict) => {
15358
- if (live()) v.decideOverlap(verdict === "keep" ? "keep_talking" : "yield", heard);
15400
+ if (!live()) return;
15401
+ if (verdict === "keep") v.decideOverlap("keep_talking", heard, call.aside(heard) ?? void 0);
15402
+ else v.decideOverlap("yield", heard);
15359
15403
  });
15360
15404
  };
15361
15405
  Object.defineProperty(call.fill.options, "aec", { get: () => v.usingAec, set: () => {
@@ -15457,6 +15501,8 @@ var VoiceIO = class extends VoiceEngine {
15457
15501
  ...o,
15458
15502
  stt: o.stt ?? new SonioxSTT({ auth: o.sonioxApiKey, source: duplex ?? new NodeMicSource(), terms: o.sttTerms }),
15459
15503
  tts: o.tts ?? new CartesiaTTS({ auth: o.cartesiaApiKey, voiceId: o.cartesiaVoiceId }),
15504
+ asideTts: o.tts ? null : new CartesiaTTS({ auth: o.cartesiaApiKey, voiceId: o.cartesiaVoiceId }),
15505
+ // asides (VoiceCall.aside): a 2nd client — the reply's context may still stream
15460
15506
  player: o.player ?? duplex ?? new Player(),
15461
15507
  bargeRmsMult: Number(process.env.BARGE_RMS_MULT || o.bargeRmsMult),
15462
15508
  bargeRmsFloor: Number(process.env.BARGE_RMS_FLOOR || o.bargeRmsFloor),
@@ -22467,11 +22513,14 @@ function channelOptions(ai, model) {
22467
22513
  onEvent: (e) => log40.debug(`channel ${e.trigger}: rule=${e.rule} choice=${e.choice ?? "-"} \u2192 ${e.acted}${e.text ? ` "${e.text}"` : ""}${e.cue ? " (planned cue)" : ""} ${e.ms}ms`)
22468
22514
  };
22469
22515
  }
22470
- function overlapResponder(ai, mode, out) {
22516
+ function overlapResponder(ai, mode, out, aside) {
22471
22517
  const judge = softBarge(mode) ? new OverlapJudge({ model: overlapModelFor(ai) }) : void 0;
22472
22518
  return (m) => {
22473
22519
  const text = String(m.text ?? "");
22474
- void (judge ? judge.judge(text) : Promise.resolve({ verdict: "cut", by: "mode", ms: 0 })).then((d) => out({ type: "overlap", ...m.id !== void 0 ? { id: m.id } : {}, verdict: d.verdict, by: d.by, ms: d.ms }));
22520
+ void (judge ? judge.judge(text) : Promise.resolve({ verdict: "cut", by: "mode", ms: 0 })).then((d) => {
22521
+ const line = d.verdict === "keep" ? aside?.(text) : null;
22522
+ out({ type: "overlap", ...m.id !== void 0 ? { id: m.id } : {}, verdict: d.verdict, by: d.by, ms: d.ms, ...line ? { aside: line } : {} });
22523
+ });
22475
22524
  };
22476
22525
  }
22477
22526
  async function duplexServe(args, ai, cfg, cwd) {
@@ -22607,7 +22656,7 @@ async function channelServe(args, ai, cfg) {
22607
22656
  const overlap2 = overlapResponder(ai, args.channelMode, (o) => {
22608
22657
  if (o.verdict === "cut") call.barge();
22609
22658
  out(o);
22610
- });
22659
+ }, (t) => call.aside(t));
22611
22660
  out({ type: "ready", model, channelMode: args.channelMode, softBarge: softBarge(args.channelMode), call: true, ...notes.length ? { notes } : {} });
22612
22661
  const rl = createInterface({ input: process.stdin });
22613
22662
  rl.on("line", (line) => {