@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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as AgentOptions, H as Hooks, p as RunResult, f as Decider, A as Agent, i as PermissionPolicy, G as Gender, L as LineKind, C as ChannelMode, q as SecondChannelOptions, S as SecondChannel } from './secondChannel-DepBV0Zv.js';
2
- export { b as ChatFragment, c as Classification, d as Classifier, e as CompactResult, D as DEFAULT_MUTATING, g as Decision, M as ModelRouter, h as ModelRouterOptions, P as PermissionOptions, j as PermissionRule, k as PreToolUseDecision, R as ReasoningEffort, l as RecordingHooks, m as RecordingLifecycle, n as RouteDecision, o as RouterState, T as Tier, r as ToolUse, s as ToolUseMeta, t as claudeCodeDisallowedTools, u as composeHooks, v as estimateTokens, w as jevAvailable, x as jevClassifier, y as pickTier, z as planMode, B as reasoningToChatFragment } from './secondChannel-DepBV0Zv.js';
1
+ import { a as AgentOptions, H as Hooks, p as RunResult, f as Decider, A as Agent, i as PermissionPolicy, G as Gender, L as LineKind, C as ChannelMode, q as SecondChannelOptions, S as SecondChannel } from './secondChannel-D33Wvruo.js';
2
+ export { b as ChatFragment, c as Classification, d as Classifier, e as CompactResult, D as DEFAULT_MUTATING, g as Decision, M as ModelRouter, h as ModelRouterOptions, P as PermissionOptions, j as PermissionRule, k as PreToolUseDecision, R as ReasoningEffort, l as RecordingHooks, m as RecordingLifecycle, n as RouteDecision, o as RouterState, T as Tier, r as ToolUse, s as ToolUseMeta, t as claudeCodeDisallowedTools, u as composeHooks, v as estimateTokens, w as jevAvailable, x as jevClassifier, y as pickTier, z as planMode, B as reasoningToChatFragment } from './secondChannel-D33Wvruo.js';
3
3
  export { MODEL_ALIASES, ModelSwitch, ResolveModelSwitchOpts, latestClaude, modelShortLabel, resolveModelAlias, resolveModelSwitch } from './models.js';
4
4
  import { IFilesystem, FileMetadata } from '@livx.cc/wcli/core';
5
5
  export { CommandExecutor, FileMetadata, IFilesystem, IndexedDbFilesystem, MemFilesystem, registerHeadlessCommands } from '@livx.cc/wcli/core';
@@ -1684,6 +1684,8 @@ interface TurnTimeline {
1684
1684
  mainText?: number;
1685
1685
  /** MAIN's first audio started (the host's player). */
1686
1686
  mainAudio?: number;
1687
+ /** false: a closer/acknowledgement ("Okay.", "תודה") — no answer due, no fill; its first sound is not a latency sample. */
1688
+ due?: false;
1687
1689
  /** false: the host gave no last-word time — the anchor is the final's arrival. */
1688
1690
  lastWord: boolean;
1689
1691
  /** How the turn ended: answered = MAIN audio played · cut = barged · replaced = a fuller re-final took it · silent = no MAIN audio. */
package/dist/index.js CHANGED
@@ -9695,8 +9695,17 @@ function isPhatic(text, bank) {
9695
9695
  return !!q && (t === q || t.startsWith(q + " "));
9696
9696
  });
9697
9697
  }
9698
+ 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(" "));
9699
+ function turnIntent(text) {
9700
+ const t = text.trim();
9701
+ if (/\?[\s"')\]]*$/.test(t)) return "ask";
9702
+ const w = norm3(t).split(" ").filter(Boolean);
9703
+ const asks = requestOpener(t) && !/^ת/.test(lead(t)[0] ?? "") || pendingQuestion(t);
9704
+ if (w.length && w.length <= 8 && w.every((x) => CLOSER.has(x)) && !asks) return "closer";
9705
+ return asks || requestOpener(t) ? "ask" : "say";
9706
+ }
9698
9707
  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(" "));
9699
- 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(" ");
9708
+ 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(" ");
9700
9709
  var WANT = /^(i (want|need|would like|d like|wanna)|אני (רוצה|צריך|צריכה|מבקש|מבקשת))(\s|$)/u;
9701
9710
  var lead = (text) => {
9702
9711
  const w = text.toLowerCase().replace(/[^\p{L}\p{N}\s]+/gu, "").split(/\s+/).filter(Boolean);
@@ -12832,6 +12841,9 @@ var SecondChannelOptions = class {
12832
12841
  /** Duplex v3 (§3 first sound): a bridge skips the decider (the veto stays for backchannels/fillers) and speaks a
12833
12842
  * canned line at once; a generated one is started at dispatch and used only if it already landed. */
12834
12843
  instantBridge = false;
12844
+ /** Duplex v3 §3 late bridge: a turn that expects an answer (not a closer) and got no bridge yet gets one canned bridge
12845
+ * this long after the user's last word if channel-1 is still silent — the short turns the 0ms rule skips ("Hi"). 0 = off. */
12846
+ lateBridgeMs = 0;
12835
12847
  /** No new interim for this long → a pause (hosts may also call onPause). */
12836
12848
  fillerPauseMs = 700;
12837
12849
  fillerMinWords = 4;
@@ -12895,6 +12907,11 @@ var SecondChannel = class {
12895
12907
  pauseTimer = null;
12896
12908
  bcTimer = null;
12897
12909
  bridgeTimer = null;
12910
+ lateTimer = null;
12911
+ late = false;
12912
+ // the bridge moment being considered is the late one (no length rule)
12913
+ lastWordAgo = 0;
12914
+ // the dispatched turn: ms since the user's last word (anchors lateBridgeMs)
12898
12915
  lastAt = -Infinity;
12899
12916
  lastKind = null;
12900
12917
  live = null;
@@ -13013,9 +13030,11 @@ var SecondChannel = class {
13013
13030
  if (s === this.main) return;
13014
13031
  this.main = s;
13015
13032
  if (s === "first_audio" || s === "speaking") this.live = null;
13016
- if (s !== "queued" && s !== "thinking" && this.bridgeTimer) {
13017
- clearTimeout(this.bridgeTimer);
13018
- this.bridgeTimer = null;
13033
+ if (s !== "queued" && s !== "thinking") {
13034
+ for (const k of ["bridgeTimer", "lateTimer"]) if (this[k]) {
13035
+ clearTimeout(this[k]);
13036
+ this[k] = null;
13037
+ }
13019
13038
  }
13020
13039
  if (s === "idle") {
13021
13040
  this.heard = "";
@@ -13036,7 +13055,7 @@ var SecondChannel = class {
13036
13055
  this.userSpeaking = false;
13037
13056
  this.bridged = false;
13038
13057
  if (this.lastKind === "bridge") this.lastKind = null;
13039
- for (const k of ["pauseTimer", "bcTimer", "bridgeTimer"]) {
13058
+ for (const k of ["pauseTimer", "bcTimer", "bridgeTimer", "lateTimer"]) {
13040
13059
  if (this[k]) clearTimeout(this[k]);
13041
13060
  this[k] = null;
13042
13061
  }
@@ -13047,11 +13066,21 @@ var SecondChannel = class {
13047
13066
  void this.consider("working");
13048
13067
  }, wait);
13049
13068
  else void this.consider("working");
13069
+ const late = this.o.lateBridgeMs - this.lastWordAgo;
13070
+ if (this.o.lateBridgeMs > 0) this.lateTimer = setTimeout(() => {
13071
+ this.lateTimer = null;
13072
+ if (this.bridged || this.busy || turnIntent(this.heard) === "closer" || this.blocked("bridge")) return;
13073
+ this.late = true;
13074
+ void this.consider("working").finally(() => {
13075
+ this.late = false;
13076
+ });
13077
+ }, Math.max(late, wait + 1));
13050
13078
  }
13051
13079
  }
13052
13080
  /** The user's final line was dispatched to the main agent (its words are the bridge's micro-context). */
13053
- onUserTurn(text) {
13081
+ onUserTurn(text, lastWordAgoMs = 0) {
13054
13082
  this.heard = text.trim();
13083
+ this.lastWordAgo = Math.max(0, lastWordAgoMs);
13055
13084
  this.floorWords = this.carried + words2(this.heard).length;
13056
13085
  this.lastUser = this.heard;
13057
13086
  this.detect(this.heard);
@@ -13119,12 +13148,19 @@ var SecondChannel = class {
13119
13148
  }
13120
13149
  /** Why this turn gets no bridge by rule ('' = it may) — language, a quick/phatic turn, or blocked(). */
13121
13150
  bridgeBlock() {
13122
- const w = words2(this.heard);
13151
+ const w = words2(this.heard), intent = turnIntent(this.heard);
13123
13152
  if (!this.lang) return "language unknown";
13124
- if (w.length <= 3 || w.length <= 6 && isPhatic(this.heard, this.bank)) return w.length <= 3 ? "quick turn" : "phatic";
13125
- if (w.length < this.o.bridgeMinWords) return `under ${this.o.bridgeMinWords} words`;
13153
+ if (intent === "closer") return "no answer due";
13154
+ if (this.late) return this.blocked("bridge");
13155
+ const why = intent === "ask" ? w.length <= 6 && isPhatic(this.heard, this.bank) ? "phatic" : "" : this.shortTurn(w.length);
13156
+ if (why) return this.o.lateBridgeMs > 0 ? `${why} (late bridge armed)` : why;
13126
13157
  return this.blocked("bridge");
13127
13158
  }
13159
+ shortTurn(n) {
13160
+ if (n <= 3 || n <= 6 && isPhatic(this.heard, this.bank)) return n <= 3 ? "quick turn" : "phatic";
13161
+ if (n < this.o.bridgeMinWords) return `under ${this.o.bridgeMinWords} words`;
13162
+ return "";
13163
+ }
13128
13164
  bcTimes = [];
13129
13165
  capped() {
13130
13166
  if (!this.o.maxPerWindow) return false;
@@ -13136,8 +13172,9 @@ var SecondChannel = class {
13136
13172
  rule(trigger, pauseMs = 0, breath = false, cue = null) {
13137
13173
  const w = words2(this.heard);
13138
13174
  if (!this.lang) return "silence";
13175
+ if (trigger === "working") return this.bridgeBlock() ? "silence" : "bridge";
13139
13176
  if (w.length <= 3 || w.length <= 6 && isPhatic(this.heard, this.bank)) return "silence";
13140
- 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;
13177
+ 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;
13141
13178
  return k && this.allowed(k) ? k : "silence";
13142
13179
  }
13143
13180
  /** Plan (or, with `heard`, revise) the backchannels of the user's turn — async, never on the hot path. */
@@ -13404,8 +13441,11 @@ var VoiceCall = class {
13404
13441
  aec: o.aec,
13405
13442
  // 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
13406
13443
  // goes 300ms after the turn if MAIN is still silent (it always is that early: warm MAIN's first token is ≥1s), no veto
13444
+ // A short question gets that bridge too (by intent, not length); any other turn expecting an answer that got none
13445
+ // 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.
13407
13446
  bridgeDelayMs: 300,
13408
13447
  instantBridge: true,
13448
+ lateBridgeMs: 1300,
13409
13449
  ...this.lang ? { lang: this.lang } : {},
13410
13450
  ...o.channel,
13411
13451
  // the ledger: a turn's bridge moment that stayed silent, and why (its wait then has no first sound until MAIN speaks)
@@ -13629,7 +13669,7 @@ var VoiceCall = class {
13629
13669
  if (this.cur) this.close(this.cur, t.replaces === this.cur.id ? "replaced" : void 0);
13630
13670
  this.follow(t.text);
13631
13671
  this.clearProgress();
13632
- this.cur = { id: t.id, text: t.text, at, finalAt, lastWord, running: true, streamed: false, sounded: false, worded: false, done: false, marks: { sent: now3 }, closed: false };
13672
+ this.cur = { id: t.id, text: t.text, at, finalAt, lastWord, running: true, streamed: false, sounded: false, worded: false, done: false, marks: { sent: now3 }, closed: false, due: turnIntent(t.text) !== "closer" };
13633
13673
  const early = this.earlyHeard;
13634
13674
  this.earlyHeard = null;
13635
13675
  if (early && early.at >= at) {
@@ -13639,7 +13679,7 @@ var VoiceCall = class {
13639
13679
  this.progress.turn();
13640
13680
  this.o.onMetric({ name: "turn", id: t.id, kind: t.kind, text: t.text });
13641
13681
  this.o.sendTurn(t.text, { id: t.id, ...t.replaces ? { replaces: t.replaces } : {} });
13642
- if (t.kind === "new") this.fill.onUserTurn(t.text);
13682
+ if (t.kind === "new") this.fill.onUserTurn(t.text, now3 - at);
13643
13683
  }
13644
13684
  onDrop(text, why) {
13645
13685
  if (!this.gate.holding) {
@@ -13678,7 +13718,7 @@ var VoiceCall = class {
13678
13718
  sounded(c, by, at = this.o.now()) {
13679
13719
  if (c.sounded) return;
13680
13720
  c.sounded = true;
13681
- this.o.onMetric({ name: "first_sound", id: c.id, ms: Math.round(at - c.at), by });
13721
+ 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" });
13682
13722
  }
13683
13723
  /** The turn is over (answered + audible, replaced, the next turn, the call's end): its timeline, once. A turn whose MAIN
13684
13724
  * never became audible still reports first_word — with no ms and why (cut / replaced / silent) — so every turn has one. */
@@ -13698,7 +13738,8 @@ var VoiceCall = class {
13698
13738
  mainText: rel(m.mainText),
13699
13739
  mainAudio: rel(m.mainAudio),
13700
13740
  lastWord: c.lastWord,
13701
- end: c.end
13741
+ end: c.end,
13742
+ ...c.due ? {} : { due: false }
13702
13743
  };
13703
13744
  for (const k of Object.keys(tl)) if (tl[k] === void 0) delete tl[k];
13704
13745
  this.o.onMetric({ name: "timeline", id: c.id, text: c.text, timeline: tl });