@livx.cc/agentx 0.99.106 → 0.99.108

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-BYW5qPAJ.js';
2
+ import { o as RouterState, H as Hooks, p as RunResult, R as ReasoningEffort, C as ChannelMode, A as Agent } from './secondChannel-3IPP3Qgh.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
 
@@ -139,6 +139,7 @@ interface Args {
139
139
  reasoning?: ReasoningEffort;
140
140
  help: boolean;
141
141
  version: boolean;
142
+ keepAlive?: boolean;
142
143
  duplex: boolean;
143
144
  duplexV2?: boolean;
144
145
  call?: boolean;
package/dist/cli.js CHANGED
@@ -13022,6 +13022,9 @@ var SecondChannelOptions = class {
13022
13022
  /** A bridge waits this long after the user's turn is dispatched, and plays only if channel-1 has said nothing yet
13023
13023
  * (a fast reply/ack needs no bridge — one before it reads as the agent starting a different sentence). 0 = at once. */
13024
13024
  bridgeDelayMs = 1200;
13025
+ /** Duplex v3 (§3 first sound): a bridge skips the decider (the veto stays for backchannels/fillers) and speaks a
13026
+ * canned line at once; a generated one is started at dispatch and used only if it already landed. */
13027
+ instantBridge = false;
13025
13028
  /** No new interim for this long → a pause (hosts may also call onPause). */
13026
13029
  fillerPauseMs = 700;
13027
13030
  fillerMinWords = 4;
@@ -13093,6 +13096,8 @@ var SecondChannel = class {
13093
13096
  // the line the last barge actually cut (spoke())
13094
13097
  bridged = false;
13095
13098
  // one bridge per main turn
13099
+ bridgeDraft = null;
13100
+ // a bridge generated ahead, used only if ready in time
13096
13101
  recent = [];
13097
13102
  picks = /* @__PURE__ */ new Map();
13098
13103
  inflight = null;
@@ -13228,6 +13233,7 @@ var SecondChannel = class {
13228
13233
  if (this[k]) clearTimeout(this[k]);
13229
13234
  this[k] = null;
13230
13235
  }
13236
+ if (this.o.instantBridge) this.prefetchBridge();
13231
13237
  const wait = Math.max(this.o.bridgeDelayMs, this.o.minGapMs - (this.o.now() - this.lastAt));
13232
13238
  if (wait > 0) this.bridgeTimer = setTimeout(() => {
13233
13239
  this.bridgeTimer = null;
@@ -13360,7 +13366,7 @@ var SecondChannel = class {
13360
13366
  try {
13361
13367
  let kind = rule;
13362
13368
  let choice;
13363
- if (this.o.decide && !cue) {
13369
+ if (this.o.decide && !cue && !(rule === "bridge" && this.o.instantBridge)) {
13364
13370
  const ac = new AbortController();
13365
13371
  const d = await withBudget(this.o.decide(this.tail(), { ...this.state(pauseMs), rule }, ac.signal), this.o.decisionBudgetMs, "channel decision", ac);
13366
13372
  choice = d?.choice;
@@ -13394,7 +13400,24 @@ var SecondChannel = class {
13394
13400
  call_ms: Math.round(now4 - this.startedAt)
13395
13401
  };
13396
13402
  }
13403
+ /** Start writing this turn's bridge now, so it may be ready by bridgeDelayMs. The bridge never waits for it. */
13404
+ prefetchBridge() {
13405
+ this.bridgeDraft = null;
13406
+ if (!this.o.generate || !this.o.generateFor.includes("bridge") || !this.lang || !this.heard) return;
13407
+ const gen = this.gen, ac = new AbortController();
13408
+ const ctx = { kind: "bridge", heard: this.tail(), state: this.main, avoid: [...this.recent], lang: this.lang };
13409
+ void withBudget(this.o.generate(ctx, ac.signal), Math.max(this.o.generateBudgetMs, this.o.bridgeDelayMs), "bridge generate", ac).then((raw) => {
13410
+ const text = cleanLine(raw ?? "", ctx.lang);
13411
+ if (text && gen === this.gen) this.bridgeDraft = { gen, text };
13412
+ }).catch((e) => log27.debug(`bridge prefetch failed: ${e?.message ?? e}`));
13413
+ }
13397
13414
  async line(kind, react = true) {
13415
+ if (kind === "bridge" && this.o.instantBridge) {
13416
+ const d = this.bridgeDraft?.gen === this.gen ? this.bridgeDraft.text : "";
13417
+ this.bridgeDraft = null;
13418
+ if (d && !this.recent.some((r) => norm3(r) === norm3(d))) return d;
13419
+ return this.pick(kind);
13420
+ }
13398
13421
  if (this.o.generate && this.o.generateFor.includes(kind) && (kind !== "backchannel" || react)) {
13399
13422
  const ac = new AbortController();
13400
13423
  const ctx = { kind, heard: this.tail(), state: this.main, avoid: [...this.recent], lang: this.lang, ...kind === "backchannel" && this.o.reactionGapMs ? { react: true } : {} };
@@ -13544,6 +13567,9 @@ var VoiceCall = class {
13544
13567
  } : {},
13545
13568
  mode: o.mode,
13546
13569
  aec: o.aec,
13570
+ bridgeDelayMs: 700,
13571
+ instantBridge: true,
13572
+ // v3 §3: first sound <1.2s p50 — a bridge at 700ms if MAIN is still silent, no veto, no wait
13547
13573
  ...this.lang ? { lang: this.lang } : {},
13548
13574
  ...o.channel,
13549
13575
  speak: (text, kind) => this.say(text, kind),
@@ -18634,7 +18660,8 @@ function parseArgs(argv) {
18634
18660
  } else if (x === "--call") {
18635
18661
  a.call = true;
18636
18662
  a.duplex = true;
18637
- } else if (x === "--channel-only") a.channelOnly = true;
18663
+ } else if (x === "--keep-alive") a.keepAlive = true;
18664
+ else if (x === "--channel-only") a.channelOnly = true;
18638
18665
  else if (x === "--channel-mode") a.channelMode = parseChannelMode(val(++i, x));
18639
18666
  else if (x === "--conversational" || x === "--convo" || x === "--voice") {
18640
18667
  a.voice = true;
@@ -18676,6 +18703,7 @@ function parseArgs(argv) {
18676
18703
  if (a.worktree && (a.vfs || a.boddb)) throw new Error("--worktree is a real-disk concept \u2014 incompatible with --vfs/--boddb");
18677
18704
  if (a.duplex && (a.task || a.print)) throw new Error("--duplex is interactive-only (a conversational mode) \u2014 drop the task/-p");
18678
18705
  if (a.inputFormat && (!a.duplex && !a.print && !a.channelOnly || a.outputFormat !== "stream-json")) throw new Error("--input-format stream-json needs --output-format stream-json and --duplex (or --voice), --channel-only or -p");
18706
+ if (a.keepAlive && (!a.print || !a.inputFormat)) throw new Error("--keep-alive needs -p --input-format stream-json --output-format stream-json");
18679
18707
  if (a.channelOnly && !a.inputFormat) throw new Error("--channel-only needs --input-format stream-json --output-format stream-json");
18680
18708
  if (a.duplex && a.plan) throw new Error("--plan is not supported in --duplex (workers are non-interactive; a plan could never be approved)");
18681
18709
  if (a.voiceSystemPrompt && !a.duplex) throw new Error("--voice-system-prompt only applies with --duplex");
@@ -18763,6 +18791,9 @@ Flags:
18763
18791
  (NDJSON turns/barge-ins on stdin, reflex text + worker speech + task events on stdout)
18764
18792
  with -p: the first {"type":"user","text"} line is the prompt; later ones STEER the running turn
18765
18793
  (unconsumed steers come back as the result's \`unsteered\`)
18794
+ --keep-alive with -p --input-format stream-json: stay up after a turn \u2014 each {"type":"user"} line while idle
18795
+ is the NEXT turn (warm: same session, model connection and MCP servers), one {type:result}
18796
+ per turn; {"type":"abort"} stops the running turn; stdin EOF exits
18766
18797
  --output-format <f> headless output: text (default) | json (one result object) | stream-json (NDJSON events: {type:text|thinking}\u2026 then {type:result})
18767
18798
  -v, --version print version and exit
18768
18799
  -h, --help
@@ -22527,9 +22558,9 @@ async function channelServe(args, ai, cfg) {
22527
22558
  await new Promise((r) => rl.on("close", () => r()));
22528
22559
  call.end();
22529
22560
  }
22530
- function readStdinSteers() {
22561
+ function readStdinSteers(keepAlive = false) {
22531
22562
  const early = [];
22532
- let sink, got;
22563
+ let sink, got, closed = false;
22533
22564
  const first = new Promise((res) => {
22534
22565
  got = res;
22535
22566
  });
@@ -22541,6 +22572,10 @@ function readStdinSteers() {
22541
22572
  } catch {
22542
22573
  return;
22543
22574
  }
22575
+ if (keepAlive && m?.type === "abort") {
22576
+ activeTurn?.abort();
22577
+ return;
22578
+ }
22544
22579
  if (m?.type !== "user" || typeof m.text !== "string" || !m.text.trim()) return;
22545
22580
  if (got) {
22546
22581
  got(m.text);
@@ -22548,11 +22583,21 @@ function readStdinSteers() {
22548
22583
  } else if (sink) sink(m.text);
22549
22584
  else early.push(m.text);
22550
22585
  });
22551
- rl.on("close", () => got?.(""));
22586
+ rl.on("close", () => {
22587
+ closed = true;
22588
+ got?.("");
22589
+ got = void 0;
22590
+ });
22591
+ const next = () => {
22592
+ sink = void 0;
22593
+ return early.length ? Promise.resolve(early.shift()) : closed ? Promise.resolve("") : new Promise((res) => {
22594
+ got = res;
22595
+ });
22596
+ };
22552
22597
  return { first, steer: (to) => {
22553
22598
  sink = to;
22554
22599
  early.splice(0).forEach(to);
22555
- } };
22600
+ }, next };
22556
22601
  }
22557
22602
  function readAllStdin() {
22558
22603
  return new Promise((res) => {
@@ -22587,7 +22632,7 @@ async function main() {
22587
22632
  process.exit(installUpdate() ? 0 : 1);
22588
22633
  }
22589
22634
  if (args.debug) process.env.DEBUG ||= "*";
22590
- const steers = args.print && args.inputFormat && !args.task ? readStdinSteers() : void 0;
22635
+ const steers = args.print && args.inputFormat && !args.task ? readStdinSteers(args.keepAlive) : void 0;
22591
22636
  if (steers) args.task = (await steers.first).trim();
22592
22637
  else if (args.print && !args.task && !process.stdin.isTTY) args.task = (await readAllStdin()).trim();
22593
22638
  if (args.print && !args.task) {
@@ -22680,7 +22725,19 @@ async function main() {
22680
22725
  const session = startSession(args, store, agent, cwd);
22681
22726
  process.once("SIGINT", () => activeTurn?.abort());
22682
22727
  steers?.steer((text) => agent.inject(text));
22683
- const { ok, res } = await runTurn(agent, store, session, args.task, void 0, cwd, autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name))));
22728
+ const turn = (task) => runTurn(agent, store, session, task, void 0, cwd, autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name))));
22729
+ if (args.keepAlive && steers) {
22730
+ for (let task = args.task; task.trim(); task = (await steers.next()).trim()) {
22731
+ steers.steer((text) => agent.inject(text));
22732
+ const { res: res2 } = await turn(task);
22733
+ const left = agent.drainPendingSteers();
22734
+ process.stdout.write(JSON.stringify({ type: "result", ...res2 ? jsonResult(res2, session) : { ok: false, finishReason: "error", sessionId: session.meta.id }, ...left.length ? { unsteered: left } : {} }) + "\n");
22735
+ }
22736
+ await closeMcp(mounted);
22737
+ worktreeCleanup?.();
22738
+ process.exit(0);
22739
+ }
22740
+ const { ok, res } = await turn(args.task);
22684
22741
  if (cfg.reflectOnFailure && !ok && res?.finishReason !== "needs_input" && res && agent.options.memoryDir) {
22685
22742
  const _fsBase = agent.options.fs.getCwd() === "/" ? "" : agent.options.fs.getCwd();
22686
22743
  const slug2 = await reflectOnRun({ ai, model: agent.options.model, fs: agent.options.fs, dir: primaryMemDir(agent.options.memoryDir, `${_fsBase}/.agent/memory`), result: res });