@livx.cc/agentx 0.99.98 → 0.99.100

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
@@ -3432,7 +3432,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
3432
3432
 
3433
3433
  // cli/cli.ts
3434
3434
  import { join as join16, resolve as resolve5, basename as basename4, extname, dirname as dirname6 } from "path";
3435
- import { AIClient, DecideClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
3435
+ import { AIClient as AIClient2, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
3436
3436
 
3437
3437
  // src/ccBridge.ts
3438
3438
  var CC_BRIDGED = /* @__PURE__ */ new Set(["Recall", "Remember", "MemorySearch"]);
@@ -9251,12 +9251,20 @@ configureLogging((name) => {
9251
9251
 
9252
9252
  // src/voice/instinct.ts
9253
9253
  init_logging();
9254
+ import { AIClient, DecideClient, JevAdapter, LlmDecideAdapter } from "ai.libx.js";
9254
9255
  var log19 = forComponent("Instinct");
9255
9256
  function voiceDeciderOptions(env = process.env) {
9256
9257
  if (env.TYPESAFE_AI_KEY) return { apiKey: env.TYPESAFE_AI_KEY };
9257
9258
  if (env.GROQ_API_KEY) return { provider: "llm", model: "groq/openai/gpt-oss-120b", apiKey: env.GROQ_API_KEY };
9258
9259
  return void 0;
9259
9260
  }
9261
+ function voiceDecider(env = process.env, timeoutMs = 1500) {
9262
+ const o = voiceDeciderOptions(env);
9263
+ if (!o) return void 0;
9264
+ const ai = new AIClient();
9265
+ const llm = new LlmDecideAdapter({ client: { chat: (c) => ai.chat({ reasoningEffort: "low", ...c }) } });
9266
+ return new DecideClient({ ...o, timeoutMs, adapters: { typesafe: new JevAdapter(), llm } });
9267
+ }
9260
9268
  var InstinctOptions = class {
9261
9269
  decider;
9262
9270
  /** Partial unchanged this long (a micro-pause) → consult. */
@@ -14065,7 +14073,7 @@ Reply JSON only: {"turn":"...","density":"...","cues":[{"when":["phrase",...],"l
14065
14073
  var log31 = forComponent("SecondChannel");
14066
14074
  var SecondChannelOptions = class {
14067
14075
  /** Refiner (Jev/fast LLM): the choice over CHANNEL_ACTIONS, or null on failure. Absent → the rule alone decides.
14068
- * `state.rule` is the kind the rule proposed (fastBackchannelDecider answers only 'backchannel' moments). */
14076
+ * `state.rule` is the kind the rule proposed (fastChannelDecider answers yes/no on it). `signal` aborts a late call. */
14069
14077
  decide;
14070
14078
  /** Fast-model line writer (see fastLineGenerator). Absent or late → the canned bank. */
14071
14079
  generate;
@@ -14094,8 +14102,12 @@ var SecondChannelOptions = class {
14094
14102
  decisionBudgetMs = 300;
14095
14103
  generateBudgetMs = 500;
14096
14104
  minGapMs = 2500;
14097
- /** Backchannel after backchannel needs this gap (v1 Instinct's cadence, ≤12/min). */
14098
- backchannelGapMs = 5e3;
14105
+ /** Backchannel after backchannel needs this gap (~4-6 cues in a 30s monologue — Para phone call 2026-10-13: 5s left
14106
+ * one cue in a long statement). */
14107
+ backchannelGapMs = 3e3;
14108
+ /** The user resumes within this long of their last words after a line of theirs was dispatched: the same statement goes
14109
+ * on (an STT endpoint mid-monologue) — its words still count toward the backchannel guards, and it is no fresh request. */
14110
+ continueMs = 3e3;
14099
14111
  /** A decision whose partial grew by this many words while in flight is stale → dropped. */
14100
14112
  staleWords = 3;
14101
14113
  microWords = 30;
@@ -14137,11 +14149,12 @@ var SecondChannelOptions = class {
14137
14149
  random = Math.random;
14138
14150
  };
14139
14151
  var words2 = (s) => s.trim() ? s.trim().split(/\s+/) : [];
14140
- function withBudget(p, ms, what) {
14152
+ function withBudget(p, ms, what, ac) {
14141
14153
  let t;
14142
14154
  const late = new Promise((r) => {
14143
14155
  t = setTimeout(() => {
14144
14156
  log31.debug(`${what}: over the ${ms}ms budget \u2014 dropped`);
14157
+ ac?.abort();
14145
14158
  r(null);
14146
14159
  }, ms);
14147
14160
  });
@@ -14182,6 +14195,11 @@ var SecondChannel = class {
14182
14195
  inflight = null;
14183
14196
  startedAt;
14184
14197
  lang = null;
14198
+ carried = 0;
14199
+ // words of this statement already dispatched (a monologue split by STT endpoints)
14200
+ floorWords = 0;
14201
+ // carried + the last dispatched line's words
14202
+ lastHeardAt = -Infinity;
14185
14203
  pinned = false;
14186
14204
  // anticipate: the plan for the user's current/next turn (turnNo bumps per dispatched turn → a late plan is dropped)
14187
14205
  turnNo = 0;
@@ -14229,6 +14247,8 @@ var SecondChannel = class {
14229
14247
  onInterim(text) {
14230
14248
  const t = text.trim();
14231
14249
  if (!t || t === this.heard) return;
14250
+ if (this.main !== "idle" || !this.heard) this.carried = this.o.now() - this.lastHeardAt < this.o.continueMs ? this.floorWords : 0;
14251
+ this.lastHeardAt = this.o.now();
14232
14252
  if (this.main !== "idle") this.onAgentState("idle");
14233
14253
  this.heard = t;
14234
14254
  this.detect(t);
@@ -14310,6 +14330,7 @@ var SecondChannel = class {
14310
14330
  /** The user's final line was dispatched to the main agent (its words are the bridge's micro-context). */
14311
14331
  onUserTurn(text) {
14312
14332
  this.heard = text.trim();
14333
+ this.floorWords = this.carried + words2(this.heard).length;
14313
14334
  this.lastUser = this.heard;
14314
14335
  this.detect(this.heard);
14315
14336
  this.main = "idle";
@@ -14391,8 +14412,10 @@ var SecondChannel = class {
14391
14412
  * sentence end only deep into a story — a short request's end is the end of their turn (replay: "mm-hm" right after
14392
14413
  * "Check hacker news for recent news.", just before the final). */
14393
14414
  backchannelMoment(n, breath) {
14394
- if (n < this.o.backchannelMinWords || /\?$/.test(this.heard) || pendingQuestion(this.heard)) return false;
14395
- if (n < this.o.requestHoldWords && requestOpener(this.heard)) return false;
14415
+ if (/\?$/.test(this.heard) || pendingQuestion(this.heard)) return false;
14416
+ if (this.carried) n += this.carried;
14417
+ else if (n < this.o.requestHoldWords && requestOpener(this.heard)) return false;
14418
+ if (n < this.o.backchannelMinWords) return false;
14396
14419
  if (/[.!]$/.test(this.heard)) return n >= 2 * this.o.backchannelMinWords;
14397
14420
  return breath || /[,;:…]$/.test(this.heard);
14398
14421
  }
@@ -14411,7 +14434,8 @@ var SecondChannel = class {
14411
14434
  let kind = rule;
14412
14435
  let choice;
14413
14436
  if (this.o.decide && !cue) {
14414
- const d = await withBudget(this.o.decide(this.tail(), { ...this.state(pauseMs), rule }), this.o.decisionBudgetMs, "channel decision");
14437
+ const ac = new AbortController();
14438
+ const d = await withBudget(this.o.decide(this.tail(), { ...this.state(pauseMs), rule }, ac.signal), this.o.decisionBudgetMs, "channel decision", ac);
14415
14439
  choice = d?.choice;
14416
14440
  if (choice && choice in CHANNEL_ACTIONS && (choice === "silence" || this.allowed(choice))) kind = choice;
14417
14441
  else if (choice && choice in CHANNEL_ACTIONS) log31.debug(`decider chose ${choice} for a ${rule} moment \u2014 the rule stands`);
@@ -14491,22 +14515,28 @@ var SecondChannel = class {
14491
14515
  return true;
14492
14516
  }
14493
14517
  };
14494
- function fastBackchannelDecider(ai, model = "groq/qwen/qwen3.8-27b") {
14495
- return async (heard, state) => {
14496
- if (state.rule !== "backchannel") return null;
14497
- const prompt = `Live voice call. The user is still talking and just took a breath. So far: "${heard}"
14498
- Would an attentive human listener say a tiny "mm-hm" now? Yes ONLY if they are telling a story/explaining across several clauses. No if it is a request, command, question, or they are mid-request. Answer yes or no.`;
14518
+ var FAST_ASK = {
14519
+ backchannel: 'The user is still talking and just took a breath. Would an attentive human listener say a tiny "mm-hm" now? Yes ONLY if they are telling a story/explaining across several clauses. No if it is a request, command, question, or they are mid-request.',
14520
+ filler: 'The user went quiet mid-turn. Would a human listener make a soft "hmm\u2026" now? Yes ONLY if their sentence is unfinished and they are searching for words. No if they said a complete request, question, answer, thanks or remark (the reply comes next).',
14521
+ bridge: `The user finished and the assistant's answer is taking a moment. Would a human say a short "let me check\u2014" now? Yes if they asked for real work or a substantial/hard question. No for small talk, a greeting, thanks, an ok/yes/agreement.`
14522
+ };
14523
+ function fastChannelDecider(ai, model = "groq/qwen/qwen3.8-27b") {
14524
+ return async (heard, state, signal) => {
14525
+ const kind = state.rule;
14526
+ if (!(kind in FAST_ASK)) return null;
14527
+ const prompt = `Live voice call. ${FAST_ASK[kind]}
14528
+ So far the user said: "${heard}"
14529
+ Answer yes or no.`;
14499
14530
  const reasoning = /qwen/.test(model) ? { providerOptions: { reasoning_effort: "none" } } : /gpt-oss/.test(model) ? { providerOptions: { reasoning_effort: "low" } } : {};
14500
- const r = await ai.chat({ model, messages: [{ role: "user", content: prompt }], stream: false, maxTokens: /gpt-oss/.test(model) ? 200 : 3, ...reasoning });
14531
+ const r = await ai.chat({ model, messages: [{ role: "user", content: prompt }], stream: false, signal, temperature: 0, maxTokens: /gpt-oss/.test(model) ? 200 : 3, ...reasoning });
14501
14532
  const a = (r.content ?? "").trim().toLowerCase();
14502
- return /^yes/.test(a) ? { choice: "backchannel" } : /^no/.test(a) ? { choice: "silence" } : null;
14533
+ return /^yes/.test(a) ? { choice: kind } : /^no/.test(a) ? { choice: "silence" } : null;
14503
14534
  };
14504
14535
  }
14505
14536
  function channelDecide(ai, instinct, env = process.env) {
14506
14537
  const model = env.AGENTX_CHANNEL_DECIDER_MODEL || void 0;
14507
- const bc = model || env.GROQ_API_KEY ? fastBackchannelDecider(ai, model) : void 0;
14508
- if (!bc && !instinct) return void 0;
14509
- return async (heard, state) => bc && state.rule === "backchannel" ? bc(heard, state) : instinct ? instinct.channel(heard, state) : null;
14538
+ if (model || env.GROQ_API_KEY) return fastChannelDecider(ai, model);
14539
+ return instinct ? (heard, state) => instinct.channel(heard, state) : void 0;
14510
14540
  }
14511
14541
  var KIND_HINT2 = {
14512
14542
  backchannel: 'a tiny listener cue while they keep talking ("mm-hm", "right", "yeah") \u2014 shows you follow, never takes the turn',
@@ -21266,10 +21296,7 @@ function duplexError(e, reflexModel) {
21266
21296
  return code && !msg.includes(String(code)) ? `${msg} (${code})` : msg;
21267
21297
  }
21268
21298
  var HOLD_RESUME_MS = 4e3;
21269
- var leakDecider = () => {
21270
- const o = voiceDeciderOptions();
21271
- return o ? new DecideClient(o) : void 0;
21272
- };
21299
+ var leakDecider = () => voiceDecider();
21273
21300
  function makeSecondChannel(ai, reflexModel, opts) {
21274
21301
  const decider = leakDecider();
21275
21302
  const instinct = decider ? new Instinct({ decider }) : void 0;
@@ -21536,7 +21563,7 @@ async function main() {
21536
21563
  console.error(red("No provider key found. Set ANTHROPIC_API_KEY (or OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY) in ~/.agent/.env, or set BODIFY_API_KEY + BODIFY_APP_ID to pull from Bodify."));
21537
21564
  process.exit(1);
21538
21565
  }
21539
- const ai = new AIClient({
21566
+ const ai = new AIClient2({
21540
21567
  apiKeys,
21541
21568
  ...cfg.baseUrls ? { baseUrls: cfg.baseUrls } : {},
21542
21569
  retry: {