@livx.cc/agentx 0.99.71 → 0.99.72

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
@@ -9167,15 +9167,16 @@ var STRICT = [
9167
9167
  ];
9168
9168
  var SENTENCE_END = new RegExp(`[.!?\u2026]+["')\\]]*(?:\\s|(?=\\p{Lu}|\\[))`, "gu");
9169
9169
  var SENTENCE_SPLIT = new RegExp(`(?<=[.!?\u2026]+["')\\]]*)(?:\\s+|(?=\\p{Lu}|\\[))`, "u");
9170
+ var BRACKET_NOTE = /^\s*\[\s*(?:[^\]\s]+\s+){3,}[^\]]*(?:\]\s*)?$/;
9170
9171
  var clean = (s) => s.replace(/\[[a-z_ ]+\]\s*/gi, "").trim();
9171
9172
  function isLeakSuspect(sentence) {
9172
9173
  const s = clean(sentence);
9173
- return SUSPECT.some((re) => re.test(s));
9174
+ return BRACKET_NOTE.test(sentence) || SUSPECT.some((re) => re.test(s));
9174
9175
  }
9175
9176
  function isStrictLeak(sentence) {
9176
9177
  return sentence.split(SENTENCE_SPLIT).some((x) => {
9177
9178
  const s = clean(x);
9178
- return STRICT.some((re) => re.test(s));
9179
+ return BRACKET_NOTE.test(x) || STRICT.some((re) => re.test(s));
9179
9180
  });
9180
9181
  }
9181
9182
  var JUDGE_INSTRUCTIONS = `This sentence was produced by a voice assistant's reply stream and is about to be read aloud to the user. Is it the assistant's private scratchpad reasoning that leaked out \u2014 talking ABOUT the conversation instead of TO the listener? Leaked reasoning: refers to the listener in the third person ("the user wants\u2026", "user said\u2026"), plans its own reply ("we need to respond\u2026", "we should output\u2026", "I should answer briefly"), cites its own guidelines/instructions, or narrates its internal tool calls and task events (QuickLook, Act, Think, Hold, TaskStatus, "t2 completed") as steps it is taking. Speech: anything said TO the listener \u2014 answers, questions, offers, news, advice, explaining what it can do when asked, even if it mentions a user, an assistant, a policy, instructions, replying, or a tool.`;
@@ -9778,12 +9779,17 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9778
9779
  * `opts.id` names the turn; `opts.replaces` names an earlier one this content REPLACES — the voice engine's
9779
9780
  * fuller re-finalization of an utterance whose reply had not started ("…previous" → "…previous
9780
9781
  * conversation."). The replaced turn is skipped if it has not run yet, else aborted and rolled back out of
9781
- * the transcript (kept only if it already dispatched work) — one reply, to the full text. */
9782
+ * the transcript (kept only if it already dispatched work) — one reply, to the full text. `opts.followUp` = the new words
9783
+ * beyond the replaced turn (a string), or beyond each earlier turn of a re-final chain ({turnId: words}): when the nearest
9784
+ * turn still in the transcript is KEPT, its words run instead of the merged text (which would repeat its request — a
9785
+ * second Act). The host gets `turn_replace` {id, replaces, rolledBack, followed?}: only a rollback drops the replaced
9786
+ * line; `followed` = the kept turn whose follow-up ran (absent: the merged text ran). */
9782
9787
  send(content, opts) {
9783
9788
  if (opts?.replaces) this.replaceTurn(opts.replaces);
9784
9789
  const cur = this.voice.options.signal;
9785
9790
  const ext = cur && cur === this.turnSignal?.own ? this.turnSignal.ext : cur;
9786
9791
  const rec = opts?.id ? this.trackTurn(opts.id) : void 0;
9792
+ if (rec && opts?.replaces && typeof content === "string") rec.replaces = { id: opts.id, of: opts.replaces, content, followUp: typeof opts.followUp === "string" ? { [opts.replaces]: opts.followUp } : opts.followUp ?? {} };
9787
9793
  if (rec?.superseded) {
9788
9794
  this.notify("diag", "turn_replaced", { id: opts.id, ran: false });
9789
9795
  return Promise.resolve(_DuplexAgent.replacedResult(this.voice.transcript));
@@ -9841,6 +9847,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9841
9847
  if (rec.done) {
9842
9848
  const drop = rec.dropped;
9843
9849
  rec.dropped = void 0;
9850
+ rec.kept = !drop?.length;
9844
9851
  if (drop?.length) void this.enqueue(async () => {
9845
9852
  const t = this.voice.transcript;
9846
9853
  for (const m of drop) {
@@ -9862,12 +9869,13 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9862
9869
  * carries the user's full words) — unless it already dispatched work or SPOKE: the task is real, and the user
9863
9870
  * heard those words, so both stay visible to the replacing turn. An aborted stream never reaches the transcript,
9864
9871
  * so what was spoken is recorded as the replaced turn's reply. */
9865
- dropReplacedTurn() {
9872
+ dropReplacedTurn(rec) {
9866
9873
  this.fabricationCut = true;
9867
9874
  this.reflexBuf = this.reflexBuf.slice(0, this.reflexForwarded);
9868
9875
  this.dropJudge();
9869
9876
  const spoke = this.turnSpoken.trim();
9870
9877
  const rolledBack = !this.turnDispatched && !spoke;
9878
+ rec.kept = !rolledBack;
9871
9879
  if (rolledBack) this.voice.transcript.length = this.turnBase;
9872
9880
  else if (spoke) {
9873
9881
  const t = this.voice.transcript;
@@ -9911,6 +9919,22 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9911
9919
  this.parkedRedeliver = [];
9912
9920
  for (const t of this.tasks.values()) if (t.deliveryParked === "barge") t.deliveryParked = "moved_on";
9913
9921
  }
9922
+ if (rec?.replaces) {
9923
+ const { id, of, content: said, followUp } = rec.replaces;
9924
+ let base;
9925
+ for (let t = of, n = 0; t && n < 32; n++) {
9926
+ const r = this.userTurns.get(t);
9927
+ if (r?.kept) {
9928
+ base = t;
9929
+ break;
9930
+ }
9931
+ t = r?.replaces?.of;
9932
+ }
9933
+ const fu = base !== void 0 ? followUp[base] : void 0;
9934
+ if (fu !== void 0 && typeof content === "string" && content.startsWith(said)) content = fu + content.slice(said.length);
9935
+ else base = void 0;
9936
+ this.notify("turn_replace", of, { id, replaces: of, rolledBack: !this.userTurns.get(of)?.kept, ...base !== void 0 ? { followed: base } : {} });
9937
+ }
9914
9938
  if (rec?.superseded) {
9915
9939
  this.notify("diag", "turn_replaced", { ran: false });
9916
9940
  return _DuplexAgent.replacedResult(this.voice.transcript);
@@ -9930,7 +9954,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9930
9954
  }
9931
9955
  if (rec) rec.done = true;
9932
9956
  }
9933
- if (rec?.superseded) return this.dropReplacedTurn();
9957
+ if (rec?.superseded) return this.dropReplacedTurn(rec);
9934
9958
  if (rec && res.finishReason === "aborted" && !this.turnDispatched && !this.turnSpoken.trim()) rec.dropped = this.voice.transcript.slice(this.turnBase);
9935
9959
  await this.endTurn(res);
9936
9960
  return res;
@@ -10000,7 +10024,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10000
10024
  }
10001
10025
  this.spec = void 0;
10002
10026
  if (spec.rec) spec.rec.done = true;
10003
- if (spec.rec?.superseded) return this.dropReplacedTurn();
10027
+ if (spec.rec?.superseded) return this.dropReplacedTurn(spec.rec);
10004
10028
  await this.endTurn(res);
10005
10029
  return res ?? empty;
10006
10030
  });
@@ -19319,6 +19343,7 @@ async function duplexServe(args, ai, cfg, cwd) {
19319
19343
  else if (k === "thinking_delta") out({ type: "thinking", text: e.message });
19320
19344
  else if (k === "speak_utterance") out({ type: "speak", text: e.message });
19321
19345
  else if (k === "hold_filler") out({ type: "filler", text: e.message });
19346
+ else if (k === "turn_replace") out({ type: "replaced", ...e.data });
19322
19347
  else if (k === "revoice_done") {
19323
19348
  out({ type: "revoice_done" });
19324
19349
  persist();
@@ -19374,7 +19399,7 @@ async function duplexServe(args, ai, cfg, cwd) {
19374
19399
  if (delivery || reply) dx.parkCutDelivery(delivery, !!reply, heard);
19375
19400
  const note = reply && reply.full.length - reply.heard.length > 40 ? interruptionNote(reply) : "";
19376
19401
  const id = m.id ? String(m.id) : void 0;
19377
- void runTurn(dx.voice, store, session, m.text + note, void 0, cwd, (c) => dx.send(c, { id, replaces: m.replaces ? String(m.replaces) : void 0 })).then(({ res }) => out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error" })).catch((e) => {
19402
+ void runTurn(dx.voice, store, session, m.text + note, void 0, cwd, (c) => dx.send(c, { id, replaces: m.replaces ? String(m.replaces) : void 0, followUp: typeof m.followUp === "string" || m.followUp && typeof m.followUp === "object" ? m.followUp : void 0 })).then(({ res }) => out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error" })).catch((e) => {
19378
19403
  log33.error("duplex turn failed", e);
19379
19404
  out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
19380
19405
  });