@livx.cc/agentx 0.97.5 → 0.97.9

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
@@ -4585,6 +4585,8 @@ init_logging();
4585
4585
  // src/voice/spokenSplitter.ts
4586
4586
  var OPEN = "<spoken>";
4587
4587
  var CLOSE = "</spoken>";
4588
+ var CLOSERS = `"')]}\xBB\u201D\u2019`;
4589
+ var hasSpeech = (s) => /[\p{L}\p{N}]/u.test(s);
4588
4590
  var SentenceCoalescer = class _SentenceCoalescer {
4589
4591
  buf = "";
4590
4592
  static isEnd(c) {
@@ -4595,14 +4597,15 @@ var SentenceCoalescer = class _SentenceCoalescer {
4595
4597
  let cut = -1;
4596
4598
  for (let i = 0; i < this.buf.length; i++) if (_SentenceCoalescer.isEnd(this.buf[i])) cut = i;
4597
4599
  if (cut < 0) return "";
4600
+ if (this.buf[cut] !== "\n") while (cut + 1 < this.buf.length && CLOSERS.includes(this.buf[cut + 1])) cut++;
4598
4601
  const ready = this.buf.slice(0, cut + 1).trim();
4599
4602
  this.buf = this.buf.slice(cut + 1);
4600
- return ready;
4603
+ return hasSpeech(ready) ? ready : "";
4601
4604
  }
4602
4605
  flush() {
4603
4606
  const s = this.buf.trim();
4604
4607
  this.buf = "";
4605
- return s;
4608
+ return hasSpeech(s) ? s : "";
4606
4609
  }
4607
4610
  };
4608
4611
  var SpokenSplitter = class {
@@ -4945,6 +4948,19 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4945
4948
  rec.controller.abort();
4946
4949
  return `Task ${rec.id} (${rec.label}) cancelled.`;
4947
4950
  }
4951
+ /** Barge-in: the user took the floor while task(s) were running. Suppress those tasks' remaining SPOKEN
4952
+ * delivery so a superseded topic never talks over the new one (the debt-after-jokes regression). The
4953
+ * tasks keep running and still fold their result into the transcript — recoverable, just not spoken.
4954
+ * Returns the parked ids (for logging). Does NOT cancel: that's a deliberate reflex/user action. */
4955
+ parkInFlightDeliveries() {
4956
+ const parked = [];
4957
+ for (const rec of this.tasks.values())
4958
+ if (rec.status === "running" && !rec.deliveryParked) {
4959
+ rec.deliveryParked = true;
4960
+ parked.push(rec.id);
4961
+ }
4962
+ return parked;
4963
+ }
4948
4964
  /** Resolve when all queued voice turns AND all in-flight worker tasks have settled (tests, graceful shutdown). */
4949
4965
  async idle() {
4950
4966
  while (true) {
@@ -5042,7 +5058,7 @@ ${recent}` : brief) + verify + deliverContract;
5042
5058
  };
5043
5059
  const splitter = new SpokenSplitter();
5044
5060
  const speak = (seg) => {
5045
- if (seg) o.host?.notify?.({ kind: "speak_utterance", message: seg });
5061
+ if (seg && !this.tasks.get(id)?.deliveryParked) o.host?.notify?.({ kind: "speak_utterance", message: seg });
5046
5062
  };
5047
5063
  const coalescer = new SentenceCoalescer();
5048
5064
  const feedSpoken = (s) => {
@@ -5263,9 +5279,9 @@ Another agent just implemented the above. Independently check the CURRENT state
5263
5279
  return this.queueRevoice(this.integrationPrompt(rec, "incomplete", res.text, res.finishReason), true);
5264
5280
  }
5265
5281
  const tail = rec.splitter?.flush();
5266
- if (tail?.spoken) this.options.host?.notify?.({ kind: "speak_utterance", message: tail.spoken });
5282
+ if (tail?.spoken && !rec.deliveryParked) this.options.host?.notify?.({ kind: "speak_utterance", message: tail.spoken });
5267
5283
  if (res.text.trim()) this.voice.transcript.push({ role: "assistant", content: res.text });
5268
- if (!rec.splitter?.spokeAny && res.text.trim())
5284
+ if (!rec.splitter?.spokeAny && res.text.trim() && !rec.deliveryParked)
5269
5285
  this.options.host?.notify?.({ kind: "speak_utterance", message: res.text });
5270
5286
  }
5271
5287
  onWorkerFailed(id, err2) {
@@ -5822,7 +5838,7 @@ var VoiceEngine = class _VoiceEngine {
5822
5838
  * If nothing is currently speaking it plays immediately; otherwise it queues and plays after the
5823
5839
  * current utterance fully ends (settle → pumpQueue) — never spliced into an open reflex utterance. */
5824
5840
  enqueueUtterance(text) {
5825
- if (!text || !text.trim()) return;
5841
+ if (!text || !/[\p{L}\p{N}]/u.test(text)) return;
5826
5842
  this.uttQueue.push(text);
5827
5843
  if (!this.speaking) this.pumpQueue();
5828
5844
  }
@@ -13349,8 +13365,11 @@ ${out}
13349
13365
  // voiceEchoEnd closes the open echo line; '\r\x1b[0J' wipes the stale prompt/footer before the
13350
13366
  // notice — every other async-chrome writer does this, and without it "✋ interrupted" overprints
13351
13367
  // the footer's leading chars (the "interrupted% ctx" glue).
13368
+ // Barge-in also parks any in-flight task's spoken delivery so a superseded topic can't keep talking
13369
+ // over the user's new one (debt-after-jokes regression) — the result still lands in the transcript.
13352
13370
  onBargeIn: (phase) => {
13353
13371
  activeTurn?.abort();
13372
+ dx?.parkInFlightDeliveries();
13354
13373
  voiceEchoEnd();
13355
13374
  if (phase === "speaking") err("\r\x1B[0J" + yellow(" \u270B interrupted\n"));
13356
13375
  },