@livx.cc/agentx 0.99.25 → 0.99.26

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
@@ -6649,6 +6649,9 @@ var DuplexAgent = class _DuplexAgent {
6649
6649
  tasks = /* @__PURE__ */ new Map();
6650
6650
  queue = Promise.resolve();
6651
6651
  seq = 0;
6652
+ /** Monotonic user-turn counter (bumped once per SUBSTANTIVE turn). Stamped onto each task at dispatch
6653
+ * (TaskRecord.createdTurn) so parking can tell a prior-topic task from the current turn's fresh one. */
6654
+ turn = 0;
6652
6655
  pendingEvents = [];
6653
6656
  /** Spoken text of parked deliveries that have SETTLED, awaiting a trivial-barge resume decision on the
6654
6657
  * next turn (a substantive turn clears it — the result stays in the transcript, recoverable by asking). */
@@ -6942,6 +6945,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
6942
6945
  return this.enqueue(async () => {
6943
6946
  await this.initMemory();
6944
6947
  this.resetTurn();
6948
+ this.turn++;
6945
6949
  this.awaitTrivialRedeliver = false;
6946
6950
  this.parkedRedeliver = [];
6947
6951
  this.suppressParkedResume = true;
@@ -7040,15 +7044,21 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
7040
7044
  rec.controller.abort();
7041
7045
  return `Task ${rec.id} (${rec.label}) cancelled.`;
7042
7046
  }
7043
- /** Barge-in: the user took the floor while task(s) were running. Suppress those tasks' remaining SPOKEN
7044
- * delivery so a superseded topic never talks over the new one (the debt-after-jokes regression). The
7045
- * tasks keep running and still fold their result into the transcript — recoverable, just not spoken.
7046
- * Returns the parked ids (for logging). Does NOT cancel: that's a deliberate reflex/user action. */
7047
- parkInFlightDeliveries() {
7048
- this.suppressParkedResume = false;
7047
+ /** Barge-in (or a dispatch-driven supersede): suppress running tasks' remaining SPOKEN delivery so a
7048
+ * superseded topic never talks over the new one (the debt-after-jokes regression). Tasks keep running and
7049
+ * still fold their result into the transcript — recoverable, just not spoken. Returns parked ids (logging).
7050
+ * Does NOT cancel. Two callers, two shapes:
7051
+ * - BARGE (default): the user interrupted AUDIO. Park EVERY running task (including this turn's) — the
7052
+ * worker they cut off must stop, whatever turn launched it — and re-arm trivial-barge auto-resume so a
7053
+ * following "go on" can bring the parked delivery back.
7054
+ * - SUPERSEDE (`priorTurnsOnly`, `reenableResume:false`, from dispatch()): a new dispatch redirected to
7055
+ * fresh work. Park only PRIOR-turn tasks so parallel siblings dispatched in the SAME turn don't silence
7056
+ * each other, and keep them suppressed (the user moved on — don't resurface on a stray "go on"). */
7057
+ parkInFlightDeliveries(reenableResume = true, priorTurnsOnly = false) {
7058
+ if (reenableResume) this.suppressParkedResume = false;
7049
7059
  const parked = [];
7050
7060
  for (const rec of this.tasks.values())
7051
- if (rec.status === "running" && !rec.deliveryParked) {
7061
+ if (rec.status === "running" && !rec.deliveryParked && (!priorTurnsOnly || rec.createdTurn < this.turn)) {
7052
7062
  rec.deliveryParked = true;
7053
7063
  parked.push(rec.id);
7054
7064
  }
@@ -7234,7 +7244,7 @@ ${recent}` : brief) + verify + deliverContract;
7234
7244
  flushSpoken();
7235
7245
  return res;
7236
7246
  }).then((res) => this.maybeVerify(id, brief, res, tier, agentOpts, askBridge)).then((res) => this.onWorkerSettled(id, res)).catch((err2) => this.onWorkerFailed(id, err2));
7237
- this.tasks.set(id, { id, label, status: "running", controller, promise, tail, brief, followUp, splitter });
7247
+ this.tasks.set(id, { id, label, status: "running", controller, promise, tail, brief, followUp, splitter, createdTurn: this.turn });
7238
7248
  if (this.tasks.size > this.options.maxTaskRecords)
7239
7249
  for (const [tid, rec] of this.tasks) {
7240
7250
  if (this.tasks.size <= this.options.maxTaskRecords) break;
@@ -7408,6 +7418,7 @@ Another agent just implemented the above. Independently check the CURRENT state
7408
7418
  toolCalls: res.messages.filter((m) => m.role === "tool").length
7409
7419
  });
7410
7420
  if (incomplete) {
7421
+ if (this.foldIfSuperseded(rec, res.text)) return;
7411
7422
  return this.queueRevoice(this.integrationPrompt(rec, "incomplete", res.text, res.finishReason), true);
7412
7423
  }
7413
7424
  const tail = rec.splitter?.flush();
@@ -7429,12 +7440,25 @@ Another agent just implemented the above. Independently check the CURRENT state
7429
7440
  onWorkerFailed(id, err2) {
7430
7441
  this.failTask(this.tasks.get(id), err2 instanceof Error ? err2.message : String(err2));
7431
7442
  }
7443
+ /** A delivery the user has SUPERSEDED — parked AND moved on (suppressParkedResume, set by a redirect) —
7444
+ * must not hijack the voice when it settles late (partial or failed): that would talk over the new topic
7445
+ * and, worse, auto-escalate work the user already abandoned. Fold a recoverable note into the transcript
7446
+ * instead (the reflex can surface it if asked) and report true so the caller skips queueRevoice. A merely
7447
+ * barge-parked task (suppressParkedResume false — a "go on" may still resume it) is NOT superseded: it
7448
+ * keeps the normal surfacing path so a failure the user still cares about is not swallowed. */
7449
+ foldIfSuperseded(rec, note) {
7450
+ if (!(rec.deliveryParked && this.suppressParkedResume)) return false;
7451
+ if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
7452
+ log12.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
7453
+ return true;
7454
+ }
7432
7455
  failTask(rec, msg) {
7433
7456
  this.dropAsk(rec.id);
7434
7457
  rec.status = "error";
7435
7458
  rec.result = msg;
7436
7459
  log12.warn(`task ${rec.id} failed: ${msg}`);
7437
7460
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
7461
+ if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
7438
7462
  this.queueRevoice(this.integrationPrompt(rec, "error", msg, "error"), true);
7439
7463
  }
7440
7464
  // --- voice tools (closures over this instance) ---
@@ -7455,6 +7479,7 @@ Another agent just implemented the above. Independently check the CURRENT state
7455
7479
  async dispatch(brief, tier = "act", label, followUp = false) {
7456
7480
  if (tier === "think" && this.options.thinkModel === false) tier = "act";
7457
7481
  if (followUp) this.autoEscalations++;
7482
+ this.parkInFlightDeliveries(false, true);
7458
7483
  const id = `t${++this.seq}`;
7459
7484
  const lbl = label ?? tier;
7460
7485
  await this.options.onTaskStart?.(id, lbl);