@livx.cc/agentx 0.99.120 → 0.99.121

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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { o as RouterState, H as Hooks, p as RunResult, R as ReasoningEffort, C as ChannelMode, A as Agent } from './secondChannel-DepBV0Zv.js';
2
+ import { o as RouterState, H as Hooks, p as RunResult, R as ReasoningEffort, C as ChannelMode, A as Agent } from './secondChannel-D33Wvruo.js';
3
3
  import { IFilesystem } from '@livx.cc/wcli/core';
4
4
  import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-DZHZIyxh.js';
5
5
 
package/dist/cli.js CHANGED
@@ -10027,8 +10027,17 @@ function isPhatic(text, bank) {
10027
10027
  return !!q2 && (t === q2 || t.startsWith(q2 + " "));
10028
10028
  });
10029
10029
  }
10030
+ var CLOSER = new Set("ok okay k cool great nice perfect awesome amazing lovely excellent alright fine good thanks thank thx you so much very a lot bye goodbye cheers later see ya night got it i that that's thats is it's its all for now sounds understood noted makes sense oh ah wow right then just \u05EA\u05D5\u05D3\u05D4 \u05E8\u05D1\u05D4 \u05E1\u05D1\u05D1\u05D4 \u05D0\u05D5\u05E7\u05D9\u05D9 \u05D0\u05D5\u05E7\u05D9 \u05D1\u05E1\u05D3\u05E8 \u05D9\u05D5\u05E4\u05D9 \u05D0\u05D7\u05DC\u05D4 \u05DE\u05E2\u05D5\u05DC\u05D4 \u05DE\u05E6\u05D5\u05D9\u05DF \u05DE\u05D2\u05E0\u05D9\u05D1 \u05E0\u05D4\u05D3\u05E8 \u05D1\u05D9\u05D9 \u05DC\u05D4\u05EA\u05E8\u05D0\u05D5\u05EA \u05D8\u05D5\u05D1 \u05D6\u05D4\u05D5 \u05D6\u05D4 \u05D4\u05DB\u05DC \u05D1\u05D9\u05E0\u05EA\u05D9\u05D9\u05DD \u05D4\u05D1\u05E0\u05EA\u05D9 \u05DE\u05DE\u05E9 \u05DC\u05DA \u05D0\u05D6".split(" "));
10031
+ function turnIntent(text) {
10032
+ const t = text.trim();
10033
+ if (/\?[\s"')\]]*$/.test(t)) return "ask";
10034
+ const w = norm3(t).split(" ").filter(Boolean);
10035
+ const asks = requestOpener(t) && !/^ת/.test(lead(t)[0] ?? "") || pendingQuestion(t);
10036
+ if (w.length && w.length <= 8 && w.every((x) => CLOSER.has(x)) && !asks) return "closer";
10037
+ return asks || requestOpener(t) ? "ask" : "say";
10038
+ }
10030
10039
  var SKIP = new Set("um uh uhm erm hmm mm oh ok okay so no yes yeah yep well hey hi and but right alright actually like now then wait hang on me speaker \u05D0\u05D5\u05E7\u05D9\u05D9 \u05D8\u05D5\u05D1 \u05D0\u05D6 \u05DC\u05D0 \u05DB\u05DF \u05E8\u05D2\u05E2 \u05D0\u05D4 \u05D0\u05DE \u05D9\u05D0\u05DC\u05DC\u05D4 \u05D5".split(" "));
10031
- var ASK = "what whats how why where when who which whose is are was were do does did can could would will should shall have has may isnt arent dont doesnt didnt cant wont check open show tell find search look send run go lets please take give make write explain select focus order try compare list use set activate highlight outline capture start stop read play call book create add remove delete put type click close switch help get summarize translate mark keep remind schedule \u05DE\u05D4 \u05D0\u05D9\u05DA \u05DC\u05DE\u05D4 \u05D0\u05D9\u05E4\u05D4 \u05DE\u05EA\u05D9 \u05DE\u05D9 \u05D4\u05D0\u05DD \u05DB\u05DE\u05D4 \u05EA\u05DF \u05EA\u05E0\u05D9 \u05EA\u05D2\u05D9\u05D3 \u05EA\u05D2\u05D9\u05D3\u05D9 \u05D1\u05D5\u05D0 \u05D1\u05D5\u05D0\u05D9".split(" ");
10040
+ var ASK = "what whats how why where when who which whose is are was were do does did can could would will should shall have has may isnt arent dont doesnt didnt cant wont check open show tell find search look send run go lets please take give make write explain select focus order try compare list use set activate highlight outline capture start stop read play call book create add remove delete put type click close switch help get name recommend suggest summarize translate mark keep remind schedule \u05DE\u05D4 \u05D0\u05D9\u05DA \u05DC\u05DE\u05D4 \u05D0\u05D9\u05E4\u05D4 \u05DE\u05EA\u05D9 \u05DE\u05D9 \u05D4\u05D0\u05DD \u05DB\u05DE\u05D4 \u05EA\u05DF \u05EA\u05E0\u05D9 \u05EA\u05D2\u05D9\u05D3 \u05EA\u05D2\u05D9\u05D3\u05D9 \u05D1\u05D5\u05D0 \u05D1\u05D5\u05D0\u05D9".split(" ");
10032
10041
  var WANT = /^(i (want|need|would like|d like|wanna)|אני (רוצה|צריך|צריכה|מבקש|מבקשת))(\s|$)/u;
10033
10042
  var lead = (text) => {
10034
10043
  const w = text.toLowerCase().replace(/[^\p{L}\p{N}\s]+/gu, "").split(/\s+/).filter(Boolean);
@@ -13164,6 +13173,9 @@ var SecondChannelOptions = class {
13164
13173
  /** Duplex v3 (§3 first sound): a bridge skips the decider (the veto stays for backchannels/fillers) and speaks a
13165
13174
  * canned line at once; a generated one is started at dispatch and used only if it already landed. */
13166
13175
  instantBridge = false;
13176
+ /** Duplex v3 §3 late bridge: a turn that expects an answer (not a closer) and got no bridge yet gets one canned bridge
13177
+ * this long after the user's last word if channel-1 is still silent — the short turns the 0ms rule skips ("Hi"). 0 = off. */
13178
+ lateBridgeMs = 0;
13167
13179
  /** No new interim for this long → a pause (hosts may also call onPause). */
13168
13180
  fillerPauseMs = 700;
13169
13181
  fillerMinWords = 4;
@@ -13227,6 +13239,11 @@ var SecondChannel = class {
13227
13239
  pauseTimer = null;
13228
13240
  bcTimer = null;
13229
13241
  bridgeTimer = null;
13242
+ lateTimer = null;
13243
+ late = false;
13244
+ // the bridge moment being considered is the late one (no length rule)
13245
+ lastWordAgo = 0;
13246
+ // the dispatched turn: ms since the user's last word (anchors lateBridgeMs)
13230
13247
  lastAt = -Infinity;
13231
13248
  lastKind = null;
13232
13249
  live = null;
@@ -13345,9 +13362,11 @@ var SecondChannel = class {
13345
13362
  if (s === this.main) return;
13346
13363
  this.main = s;
13347
13364
  if (s === "first_audio" || s === "speaking") this.live = null;
13348
- if (s !== "queued" && s !== "thinking" && this.bridgeTimer) {
13349
- clearTimeout(this.bridgeTimer);
13350
- this.bridgeTimer = null;
13365
+ if (s !== "queued" && s !== "thinking") {
13366
+ for (const k of ["bridgeTimer", "lateTimer"]) if (this[k]) {
13367
+ clearTimeout(this[k]);
13368
+ this[k] = null;
13369
+ }
13351
13370
  }
13352
13371
  if (s === "idle") {
13353
13372
  this.heard = "";
@@ -13368,7 +13387,7 @@ var SecondChannel = class {
13368
13387
  this.userSpeaking = false;
13369
13388
  this.bridged = false;
13370
13389
  if (this.lastKind === "bridge") this.lastKind = null;
13371
- for (const k of ["pauseTimer", "bcTimer", "bridgeTimer"]) {
13390
+ for (const k of ["pauseTimer", "bcTimer", "bridgeTimer", "lateTimer"]) {
13372
13391
  if (this[k]) clearTimeout(this[k]);
13373
13392
  this[k] = null;
13374
13393
  }
@@ -13379,11 +13398,21 @@ var SecondChannel = class {
13379
13398
  void this.consider("working");
13380
13399
  }, wait);
13381
13400
  else void this.consider("working");
13401
+ const late = this.o.lateBridgeMs - this.lastWordAgo;
13402
+ if (this.o.lateBridgeMs > 0) this.lateTimer = setTimeout(() => {
13403
+ this.lateTimer = null;
13404
+ if (this.bridged || this.busy || turnIntent(this.heard) === "closer" || this.blocked("bridge")) return;
13405
+ this.late = true;
13406
+ void this.consider("working").finally(() => {
13407
+ this.late = false;
13408
+ });
13409
+ }, Math.max(late, wait + 1));
13382
13410
  }
13383
13411
  }
13384
13412
  /** The user's final line was dispatched to the main agent (its words are the bridge's micro-context). */
13385
- onUserTurn(text) {
13413
+ onUserTurn(text, lastWordAgoMs = 0) {
13386
13414
  this.heard = text.trim();
13415
+ this.lastWordAgo = Math.max(0, lastWordAgoMs);
13387
13416
  this.floorWords = this.carried + words3(this.heard).length;
13388
13417
  this.lastUser = this.heard;
13389
13418
  this.detect(this.heard);
@@ -13451,12 +13480,19 @@ var SecondChannel = class {
13451
13480
  }
13452
13481
  /** Why this turn gets no bridge by rule ('' = it may) — language, a quick/phatic turn, or blocked(). */
13453
13482
  bridgeBlock() {
13454
- const w = words3(this.heard);
13483
+ const w = words3(this.heard), intent = turnIntent(this.heard);
13455
13484
  if (!this.lang) return "language unknown";
13456
- if (w.length <= 3 || w.length <= 6 && isPhatic(this.heard, this.bank)) return w.length <= 3 ? "quick turn" : "phatic";
13457
- if (w.length < this.o.bridgeMinWords) return `under ${this.o.bridgeMinWords} words`;
13485
+ if (intent === "closer") return "no answer due";
13486
+ if (this.late) return this.blocked("bridge");
13487
+ const why = intent === "ask" ? w.length <= 6 && isPhatic(this.heard, this.bank) ? "phatic" : "" : this.shortTurn(w.length);
13488
+ if (why) return this.o.lateBridgeMs > 0 ? `${why} (late bridge armed)` : why;
13458
13489
  return this.blocked("bridge");
13459
13490
  }
13491
+ shortTurn(n) {
13492
+ if (n <= 3 || n <= 6 && isPhatic(this.heard, this.bank)) return n <= 3 ? "quick turn" : "phatic";
13493
+ if (n < this.o.bridgeMinWords) return `under ${this.o.bridgeMinWords} words`;
13494
+ return "";
13495
+ }
13460
13496
  bcTimes = [];
13461
13497
  capped() {
13462
13498
  if (!this.o.maxPerWindow) return false;
@@ -13468,8 +13504,9 @@ var SecondChannel = class {
13468
13504
  rule(trigger, pauseMs = 0, breath = false, cue = null) {
13469
13505
  const w = words3(this.heard);
13470
13506
  if (!this.lang) return "silence";
13507
+ if (trigger === "working") return this.bridgeBlock() ? "silence" : "bridge";
13471
13508
  if (w.length <= 3 || w.length <= 6 && isPhatic(this.heard, this.bank)) return "silence";
13472
- const k = trigger === "interim" ? this.backchannelMoment(w.length, breath) && (!this.o.anticipate || !this.plan || !silentTurn(this.plan) && (!!cue || !this.o.cueOnly && this.plan.density !== "low")) ? "backchannel" : null : trigger === "pause" ? pauseMs >= this.o.fillerPauseMs && w.length >= this.o.fillerMinWords ? "filler" : null : w.length >= this.o.bridgeMinWords ? "bridge" : null;
13509
+ const k = trigger === "interim" ? this.backchannelMoment(w.length, breath) && (!this.o.anticipate || !this.plan || !silentTurn(this.plan) && (!!cue || !this.o.cueOnly && this.plan.density !== "low")) ? "backchannel" : null : trigger === "pause" ? pauseMs >= this.o.fillerPauseMs && w.length >= this.o.fillerMinWords ? "filler" : null : null;
13473
13510
  return k && this.allowed(k) ? k : "silence";
13474
13511
  }
13475
13512
  /** Plan (or, with `heard`, revise) the backchannels of the user's turn — async, never on the hot path. */
@@ -13736,8 +13773,11 @@ var VoiceCall = class {
13736
13773
  aec: o.aec,
13737
13774
  // v3 §3: first sound <1.2s p50 from the last word — the endpoint takes ~0.6s and the bridge's TTS ~0.2s, so the bridge
13738
13775
  // goes 300ms after the turn if MAIN is still silent (it always is that early: warm MAIN's first token is ≥1s), no veto
13776
+ // A short question gets that bridge too (by intent, not length); any other turn expecting an answer that got none
13777
+ // gets a late one at 1.3s from the last word while MAIN is still silent (API first byte runs 1-8s); a closer, none.
13739
13778
  bridgeDelayMs: 300,
13740
13779
  instantBridge: true,
13780
+ lateBridgeMs: 1300,
13741
13781
  ...this.lang ? { lang: this.lang } : {},
13742
13782
  ...o.channel,
13743
13783
  // the ledger: a turn's bridge moment that stayed silent, and why (its wait then has no first sound until MAIN speaks)
@@ -13961,7 +14001,7 @@ var VoiceCall = class {
13961
14001
  if (this.cur) this.close(this.cur, t.replaces === this.cur.id ? "replaced" : void 0);
13962
14002
  this.follow(t.text);
13963
14003
  this.clearProgress();
13964
- this.cur = { id: t.id, text: t.text, at, finalAt, lastWord, running: true, streamed: false, sounded: false, worded: false, done: false, marks: { sent: now4 }, closed: false };
14004
+ this.cur = { id: t.id, text: t.text, at, finalAt, lastWord, running: true, streamed: false, sounded: false, worded: false, done: false, marks: { sent: now4 }, closed: false, due: turnIntent(t.text) !== "closer" };
13965
14005
  const early = this.earlyHeard;
13966
14006
  this.earlyHeard = null;
13967
14007
  if (early && early.at >= at) {
@@ -13971,7 +14011,7 @@ var VoiceCall = class {
13971
14011
  this.progress.turn();
13972
14012
  this.o.onMetric({ name: "turn", id: t.id, kind: t.kind, text: t.text });
13973
14013
  this.o.sendTurn(t.text, { id: t.id, ...t.replaces ? { replaces: t.replaces } : {} });
13974
- if (t.kind === "new") this.fill.onUserTurn(t.text);
14014
+ if (t.kind === "new") this.fill.onUserTurn(t.text, now4 - at);
13975
14015
  }
13976
14016
  onDrop(text, why) {
13977
14017
  if (!this.gate.holding) {
@@ -14010,7 +14050,7 @@ var VoiceCall = class {
14010
14050
  sounded(c, by, at = this.o.now()) {
14011
14051
  if (c.sounded) return;
14012
14052
  c.sounded = true;
14013
- this.o.onMetric({ name: "first_sound", id: c.id, ms: Math.round(at - c.at), by });
14053
+ this.o.onMetric(c.due ? { name: "first_sound", id: c.id, ms: Math.round(at - c.at), by } : { name: "first_sound", id: c.id, by, kind: "no answer due" });
14014
14054
  }
14015
14055
  /** The turn is over (answered + audible, replaced, the next turn, the call's end): its timeline, once. A turn whose MAIN
14016
14056
  * never became audible still reports first_word — with no ms and why (cut / replaced / silent) — so every turn has one. */
@@ -14030,7 +14070,8 @@ var VoiceCall = class {
14030
14070
  mainText: rel(m.mainText),
14031
14071
  mainAudio: rel(m.mainAudio),
14032
14072
  lastWord: c.lastWord,
14033
- end: c.end
14073
+ end: c.end,
14074
+ ...c.due ? {} : { due: false }
14034
14075
  };
14035
14076
  for (const k of Object.keys(tl)) if (tl[k] === void 0) delete tl[k];
14036
14077
  this.o.onMetric({ name: "timeline", id: c.id, text: c.text, timeline: tl });