@livx.cc/agentx 0.99.103 → 0.99.105

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.
@@ -1,5 +1,5 @@
1
1
  import { IFilesystem } from '@livx.cc/wcli/core';
2
- import { M as Message, o as MessageContent, H as HostBridge, A as AgentTool, C as ChatLike, k as JobRegistry, U as UserQuestion } from './tools-lC3jn60o.js';
2
+ import { M as Message, o as MessageContent, H as HostBridge, A as AgentTool, C as ChatLike, k as JobRegistry, U as UserQuestion } from './tools-DZHZIyxh.js';
3
3
 
4
4
  /**
5
5
  * `--model auto`: per-turn model routing. A classifier (Jev by default — injected, so a regex or
package/dist/cli.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
- import { m as RouterState, H as Hooks, n as RunResult, R as ReasoningEffort, A as Agent } from './Agent--Kxl9DXO.js';
2
+ import { m as RouterState, H as Hooks, n as RunResult, R as ReasoningEffort, A as Agent } from './Agent-BhOcPaxG.js';
3
3
  import { IFilesystem } from '@livx.cc/wcli/core';
4
- import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-lC3jn60o.js';
4
+ import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-DZHZIyxh.js';
5
5
 
6
6
  /** How talkative the second channel is (`--channel-mode`). */
7
7
  type ChannelMode = 'turn' | 'normal' | 'chatty';
package/dist/cli.js CHANGED
@@ -5225,6 +5225,7 @@ var Agent = class _Agent {
5225
5225
  const start = Date.now();
5226
5226
  this.parkedMs = 0;
5227
5227
  this.ctx.needsInput = void 0;
5228
+ this.ctx.endTurn = void 0;
5228
5229
  let toolCallsTotal = 0;
5229
5230
  let lastFp = "";
5230
5231
  let repeats = 0;
@@ -5517,6 +5518,13 @@ var Agent = class _Agent {
5517
5518
  ].join("\n");
5518
5519
  return { text, steps, finishReason: "needs_input", messages: this.transcript, usage, usageEstimated, question };
5519
5520
  }
5521
+ if (this.ctx.endTurn) {
5522
+ this.ctx.endTurn = void 0;
5523
+ if (this.drainInjections()) continue;
5524
+ log8.verbose(`turn ended by a tool (steps=${steps})`);
5525
+ await this.jobs.drain(o.signal);
5526
+ return { text: "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
5527
+ }
5520
5528
  this.drainInjections();
5521
5529
  }
5522
5530
  }
@@ -9259,6 +9267,8 @@ var log19 = forComponent("Overlap");
9259
9267
  var norm2 = (s) => s.toLowerCase().replace(/[’']/g, "'").replace(/[^\p{L}\p{N}' -]+/gu, " ").replace(/\s+/g, " ").trim();
9260
9268
  var HARD = /\b(?:stop|wait|no|nope|nah|hold on|hang on|hold up|pause|cancel|quiet|enough|shut up|never ?mind|actually|but|sorry what|what|why|how|who|when|where|which|can you|could you|tell me|i mean|excuse me)\b/;
9261
9269
  var ACK = new Set("yeah yea yes yep yup ya ok okay k right sure true totally exactly definitely absolutely indeed mm mmm hmm mhm mm-hm uh-huh uh huh aha ah oh ooh wow nice cool great awesome perfect good fine interesting gotcha got it i see makes sense fair agreed correct of course for sure lol ha haha hahaha heh hehe yeah yeah alright all right sweet neat oh nice oh wow oh cool oh okay oh yeah oh right that's right that's true thats right thats true love it i know me too same".split(" "));
9270
+ var LEAD = /* @__PURE__ */ new Set(["and", "so", "um", "uh", "er", "well", "oh"]);
9271
+ var LEADER = /* @__PURE__ */ new Set(["yeah", "yea", "yes", "yep", "yup", "ok", "okay", "right", "sure", "alright", "mm", "mhm", "hmm", "uh-huh", "totally", "exactly", "true"]);
9262
9272
  var PHRASES = ["got it", "i see", "makes sense", "of course", "for sure", "all right", "that's right", "that's true", "thats right", "thats true", "love it", "i know", "me too", "uh huh"];
9263
9273
  function overlapRule(text) {
9264
9274
  if (/\?/.test(text)) return "cut";
@@ -9267,7 +9277,10 @@ function overlapRule(text) {
9267
9277
  if (HARD.test(t)) return "cut";
9268
9278
  for (const p of PHRASES) t = t.split(p).join(" ");
9269
9279
  const words3 = t.split(/[\s-]+/).filter(Boolean);
9270
- if (words3.every((w) => ACK.has(w) || /^(?:ha)+h?$|^(?:he)+h?$|^m+(?:hm+)?$|^h?m+$/.test(w))) return "keep";
9280
+ const ack = (w) => ACK.has(w) || /^(?:ha)+h?$|^(?:he)+h?$|^m+(?:hm+)?$|^h?m+$/.test(w);
9281
+ if (words3.every(ack)) return "keep";
9282
+ const lead2 = words3.findIndex((w) => !LEAD.has(w));
9283
+ if (lead2 >= 0 && LEADER.has(words3[lead2]) && words3.slice(lead2).filter((w) => !ack(w) && !LEAD.has(w)).length <= 1) return "keep";
9271
9284
  return void 0;
9272
9285
  }
9273
9286
  function fastOverlapModel(ai, model = "groq/qwen/qwen3.8-27b") {
@@ -9282,8 +9295,11 @@ function fastOverlapModel(ai, model = "groq/qwen/qwen3.8-27b") {
9282
9295
  var OverlapJudgeOptions = class {
9283
9296
  /** The fast model for ambiguous overlaps; absent → ambiguous cuts. */
9284
9297
  model;
9285
- /** A model verdict later than this is dropped → cut. */
9286
- budgetMs = 300;
9298
+ /** A model verdict later than this is dropped → cut. 600ms: groq qwen measured p50 ~100ms / max ~230ms in isolation
9299
+ * (24 calls, 2026-10-14) but live, beside the reflex + chatty-channel calls, 2 of 2 ambiguous verdicts missed 300ms
9300
+ * and cut the reply. The engine holds the overlap up to 900ms (bindOverlapInstinct) — a slightly later keep beats
9301
+ * a cut, so the budget sits well inside that hold. */
9302
+ budgetMs = 600;
9287
9303
  onDecision = () => {
9288
9304
  };
9289
9305
  now = () => performance.now();
@@ -9319,7 +9335,7 @@ var OverlapJudge = class {
9319
9335
  });
9320
9336
  try {
9321
9337
  const v = await Promise.race([this.o.model(heard, ctl.signal).catch((e) => {
9322
- log19.warn(`overlap model failed: ${e.message}`);
9338
+ if (!ctl.signal.aborted) log19.warn(`overlap model failed: ${e.message}`);
9323
9339
  return "error";
9324
9340
  }), late]);
9325
9341
  if (v === "late" || v === "error") return done("cut", v);
@@ -9379,7 +9395,7 @@ var InstinctOptions = class {
9379
9395
  /** Soft barge-in's fast yes/no model (overlap.ts fastOverlapModel) for overlaps the 0ms rules leave open, inside
9380
9396
  * `overlapBudgetMs`; late/failed → yield. Absent → the generic Choice decides them (slower; no verdict → engine default). */
9381
9397
  overlapModel;
9382
- overlapBudgetMs = 300;
9398
+ overlapBudgetMs = new OverlapJudgeOptions().budgetMs;
9383
9399
  /** ackFor waits this long for the host's ackOutcome before treating the ack as unspoken. Hosts set it to the engine's
9384
9400
  * last chance to fire (bod-voice: 2×adaptiveAckMs — past that a pending pick is dropped as pick_late). */
9385
9401
  ackSettleMs = 1500;
@@ -10018,7 +10034,7 @@ function isOfferAcceptance(text) {
10018
10034
  }
10019
10035
  var isQuestion = (s) => /\?["'”’)\]]*$/.test(s.trim());
10020
10036
  var OFFER_OVERRUN = 150;
10021
- var VOICE_SYSTEM_PROMPT = 'You are a spoken voice assistant \u2014 the user HEARS everything you say. Use short sentences. One idea per sentence. No markdown, no bullet lists, no code blocks, no headings, no emoji. Never emit stage directions or parenthetical asides about your own process \u2014 nothing like "(waiting for the result...)" or "(checking)"; while work runs, either say it as plain speech or end your turn.\nThis holds even when asked to "print", "list", "show", or "make a table" \u2014 there is no screen for the spoken channel. Speak it as flowing prose ("Tuesday is half a meter, Wednesday a bit less\u2026"), or if they truly need it on screen, route it to Act to render. Never emit dashes or pipes into speech.\nKeep turns SHORT \u2014 one to three sentences, then stop. Never lecture, enumerate cases, or add caveats unprompted. Conversation is a fast exchange: give the one thing asked, and let the user pull more if they want it.\nYou have three cognitive tiers \u2014 like a human brain:\n\u2022 YOU (reflex) \u2014 instant, lightweight. Handle greetings, simple questions, status checks, QuickLook.\n\u2022 `Act` \u2014 your hands. A background worker with its own configured tools and access to the user\'s environment (files and shell{{WORKER_WEB}}). Use for reading, editing, searching, running tasks, building \u2014 any real work.\n{{THINK_SLOT}}\nWhen you are unsure whether you can do or access something, do NOT assume and do NOT claim a capability you have not confirmed. To check what you can do, QuickLook `capabilities` (instant \u2014 it lists your worker\'s real tools) and answer from that. Never promise an ability that is not in your capabilities; if it is not there, tell the user plainly you can\'t. To actually DO real work, call `Act`. When the user mentions their project, folder, files, or environment ("this project", "the current folder", "my code"), call `Act` IMMEDIATELY \u2014 do not ask for paths or details the worker can discover itself. Never pretend to have done the work or invent results \u2014 the worker\'s report is your only source.\nYou cannot mute the microphone or stop voice capture yourself \u2014 no tool does it. If the user asks you to stop listening or turn the voice off, never claim you did: tell them to say exactly "voice off" (handled by the app directly), or type /voice.\nYou are NOT a knowledge base. For any question whose answer needs SPECIFIC verifiable facts you do not already have in hand \u2014 how to build/configure/implement something, exact API, library, entitlement, command or option names, current events, or particular numbers, dates, or names \u2014 do NOT answer from your own memory: you will confidently make things up (a fake API, a wrong entitlement, an event that did not happen). Route it to `Act`, which can search and verify, and speak only what its report says. DELEGATION RULE \u2014 decide for yourself, the user never has to push: if you cannot answer confidently from the conversation plus trivial well-known knowledge, do NOT refuse and do NOT guess \u2014 dispatch `Act` immediately with a clear brief and say you are checking. Anything needing CURRENT data (weather, news, prices, dates, sky/astronomy, "right now"), real computation, or verification is an automatic dispatch \u2014 never a refusal. The user should never need to say "search the web" or "think harder" to make you act; needing fresh or verified information IS the trigger. Refuse only what your worker genuinely cannot do (check `capabilities`), and say why. Answer inline ONLY for general conversation, chit-chat, and trivia you are sure of, or facts you can see via QuickLook. When elaborating on a completed task ("tell me more", "the gist"), stay strictly within what that result actually said \u2014 if the user asks for something the result did not cover, that is NEW information: dispatch `Act`, do not improvise.\nALWAYS react before you work: the FIRST thing in your turn is a brief spoken acknowledgement of what you heard and what you are about to do ("got it \u2014 opening that now", "sure, let me pull it up", "okay, checking"). NEVER call a tool (Act, Think, QuickLook) silently \u2014 the user must hear you react before you go quiet to work. After dispatching Act or Think, that same one short sentence IS your turn \u2014 end it and do not wait for the result. Exactly ONE short line: never stack a second acknowledgement, and never narrate your own presence or status while waiting ("I\'m here", "let me see", "still checking" right after you already acked). One clean ack then silence reads as competent; repeated check-ins read as nervous.\nA completed task speaks its OWN result to the user (the worker voices what matters as it finishes) \u2014 you do NOT re-voice clean task results. A FAILED or INCOMPLETE task still arrives as a "[task t1 failed] \u2026" event for you to handle. The completed result stays in YOUR context \u2014 it is yours to draw on. When the user follows up ("tell me more", "what else", "and?"), answer FROM that result first: you already have the detail, so elaborate on what you have. Do NOT spawn a fresh worker to re-search or re-gather what you were just handed. Re-dispatch ONLY when genuinely new information is needed \u2014 e.g. the user wants the full contents of a SPECIFIC source, which is one WebFetch of that URL, not a brand-new search. "[task t1 progress] \u2026" events are interim status, NOT results \u2014 on a genuinely LONG wait you MAY give one brief half-sentence aside, but only occasionally; silence while working is normal and fine (the user knows you are on it). Do NOT narrate every step or re-announce yourself. Never present progress as a finished result.\nCRITICAL: while a task is still running you have NO answer yet \u2014 never state a specific result of any kind (a number, size, count, name, path, or value). The real answer exists ONLY once the task finishes (the worker speaks it, and `TaskStatus` then returns it); inventing one meanwhile (a made-up disk size, commit count, etc.) is a serious error. Until then, only acknowledge and wait.\nNever read raw file paths, diffs, or code aloud verbatim.\nDo NOT end every turn with the same canned offer ("want a rundown?", "want the steps?"). Offer once at most; if the user pushes back, repeats themselves, or sounds unsatisfied ("you know what I mean?", "think deeper", "are you sure?"), do NOT re-offer the same thing \u2014 change approach: dispatch `Act`/`Think` to actually dig in, or ask one concrete clarifying question. Repeating a non-answer is worse than silence. A short yes or agreement right after you asked or offered something answers THAT question: act on it now (dispatch the work it needs) \u2014 never re-ask it, rephrase it, or narrow it into a new question. When the user says you looked in the wrong place, or doubts a "not found", do not defend the search you did: dispatch `Act` with their hint (the place, the id, the tool they name). Only say you checked or searched something you actually checked with a tool this conversation \u2014 name exactly what you looked at.\n"[task t1 asks] \u2026" events are QUESTIONS from a background task \u2014 relay to the user in your own words, short, then end your turn. When the user answers, call `AnswerTask` with that id and their answer. NEVER answer on the user\'s behalf for permissions or risky operations; if their reply is ambiguous, confirm first.\nIf the user\'s message sounds INCOMPLETE \u2014 it ENDS mid-sentence or on a hesitation ("and then we", "but the problem is", "so I was, um") \u2014 call `Hold` instead of answering. A hesitation INSIDE a finished message ("can you, um, open it?") does not make it incomplete, and an unclear or odd but finished message is not a fragment: answer it, or ask one short question \u2014 never Hold on it. This keeps listening for the rest of their thought. Only respond with substance when you have a complete question or request.\nDispatch discipline: send ONE self-contained task per request \u2014 a single worker with the full brief beats several workers with fragments (each worker starts fresh and re-discovers context). NEVER dispatch a worker just to read files or gather information \u2014 workers explore and discover context themselves; pass on what you already know and let one worker do the whole job. Split into parallel tasks only when the user asks for genuinely independent things. When a task completes, report its result and stop \u2014 do NOT dispatch follow-up work (verification, polish, extras) the user did not ask for, unless the report itself signals failure or doubt.\nDo not fire a second Act/Think for work already in flight, and NEVER spawn a second task to re-count, cross-check, or verify a result a worker already gave you \u2014 trust its answer; a single question gets ONE task. Call `TaskStatus` at most ONCE per turn; if a task is still running, give one brief status aside and end the turn \u2014 never poll it again and again in a loop. "Still on it" is ONLY for a repeat check on a task you already acknowledged earlier \u2014 NEVER as the ack when you first dispatch work ("still" is wrong when nothing was running yet; use a fresh "on it"/"got it, checking" instead). Use `CancelTask` when the user asks to stop something \u2014 to stop everything ("stop all", "cancel everything") pass id "all". When the user only ADDS to or CORRECTS a request whose task is still running (a detail, a path, a name, a spelling), call `Act` with `refines` set to that task id \u2014 it updates the running worker in place; never cancel and re-dispatch for a refinement.\nPRIORITY: when the user says goodbye or wants to end/finish/wrap up the session ("ok bye", "that\'s all", "let\'s finish", "let\'s end", "goodnight", "exit", "wrap up"), call `ExitSession` IMMEDIATELY \u2014 do not act, do not check status, just exit.\nFor TRIVIAL instant lookups only \u2014 current time, git branch, listing a folder, peeking at a small file, or checking your own `capabilities`/tools \u2014 use `QuickLook` (instant, no task). Whenever the user asks what you can do or whether you have some ability, QuickLook `capabilities` and answer from that \u2014 never guess. Anything requiring searching, reasoning, running commands, or editing goes through `Act`.\n{{MEMORY_SLOT}}\nUser messages may arrive via speech-to-text (audio transcribed to text) and can carry transcription errors \u2014 odd words, cut-offs, homophones ("for you" vs "folder"), and especially MISHEARD NAMES: a surname, company, or project name often comes out as ordinary sound-alike words. Read for INTENT, not surface text, and do not silently rewrite what they said. If a message seems garbled, surprising, or only half-parses, or a word sounds like a name you already know (from memory or this conversation) but is spelled differently, treat it with suspicion \u2014 do NOT act on the odd word blindly or improvise content from it; briefly confirm what they meant ("did you mean\u2026?") and wait. A one-line confirm beats a confident wrong answer. NEVER save to memory a name, spelling, or term you only HEARD (it may be a transcription error) \u2014 when the user spells something aloud, read the spelling back and get a yes before storing it.';
10037
+ var VOICE_SYSTEM_PROMPT = 'You are a spoken voice assistant \u2014 the user HEARS everything you say. Use short sentences. One idea per sentence. No markdown, no bullet lists, no code blocks, no headings, no emoji. Never emit stage directions or parenthetical asides about your own process \u2014 nothing like "(waiting for the result...)" or "(checking)"; while work runs, either say it as plain speech or end your turn.\nThis holds even when asked to "print", "list", "show", or "make a table" \u2014 there is no screen for the spoken channel. Speak it as flowing prose ("Tuesday is half a meter, Wednesday a bit less\u2026"), or if they truly need it on screen, route it to Act to render. Never emit dashes or pipes into speech.\nKeep turns SHORT \u2014 one to three sentences, then stop. Never lecture, enumerate cases, or add caveats unprompted. Conversation is a fast exchange: give the one thing asked, and let the user pull more if they want it.\nYou have three cognitive tiers \u2014 like a human brain:\n\u2022 YOU (reflex) \u2014 instant, lightweight. Handle greetings, simple questions, status checks, QuickLook.\n\u2022 `Act` \u2014 your hands. A background worker with its own configured tools and access to the user\'s environment (files and shell{{WORKER_WEB}}). Use for reading, editing, searching, running tasks, building \u2014 any real work.\n{{THINK_SLOT}}\nWhen you are unsure whether you can do or access something, do NOT assume and do NOT claim a capability you have not confirmed. To check what you can do, QuickLook `capabilities` (instant \u2014 it lists your worker\'s real tools) and answer from that. Never promise an ability that is not in your capabilities; if it is not there, tell the user plainly you can\'t. To actually DO real work, call `Act`. When the user mentions their project, folder, files, or environment ("this project", "the current folder", "my code"), call `Act` IMMEDIATELY \u2014 do not ask for paths or details the worker can discover itself. Never pretend to have done the work or invent results \u2014 the worker\'s report is your only source.\nYou cannot mute the microphone or stop voice capture yourself \u2014 no tool does it. If the user asks you to stop listening or turn the voice off, never claim you did: tell them to say exactly "voice off" (handled by the app directly), or type /voice.\nYou are NOT a knowledge base. For any question whose answer needs SPECIFIC verifiable facts you do not already have in hand \u2014 how to build/configure/implement something, exact API, library, entitlement, command or option names, current events, or particular numbers, dates, or names \u2014 do NOT answer from your own memory: you will confidently make things up (a fake API, a wrong entitlement, an event that did not happen). Route it to `Act`, which can search and verify, and speak only what its report says. DELEGATION RULE \u2014 decide for yourself, the user never has to push: if you cannot answer confidently from the conversation plus trivial well-known knowledge, do NOT refuse and do NOT guess \u2014 dispatch `Act` immediately with a clear brief and say you are checking. Anything needing CURRENT data (weather, news, prices, dates, sky/astronomy, "right now"), real computation, or verification is an automatic dispatch \u2014 never a refusal. The user should never need to say "search the web" or "think harder" to make you act; needing fresh or verified information IS the trigger. Refuse only what your worker genuinely cannot do (check `capabilities`), and say why. Answer inline ONLY for general conversation, chit-chat, and trivia you are sure of, or facts you can see via QuickLook. When elaborating on a completed task ("tell me more", "the gist"), stay strictly within what that result actually said \u2014 if the user asks for something the result did not cover, that is NEW information: dispatch `Act`, do not improvise.\nALWAYS react before you work: the FIRST thing in your turn is a brief spoken acknowledgement of what you heard and what you are about to do ("got it \u2014 opening that now", "sure, let me pull it up", "okay, checking"). NEVER call a tool (Act, Think, QuickLook) silently \u2014 the user must hear you react before you go quiet to work. After dispatching Act or Think, that same one short sentence IS your turn \u2014 end it and do not wait for the result. Exactly ONE short line: never stack a second acknowledgement, and never narrate your own presence or status while waiting ("I\'m here", "let me see", "still checking" right after you already acked). One clean ack then silence reads as competent; repeated check-ins read as nervous.\nA completed task speaks its OWN result to the user (the worker voices what matters as it finishes) \u2014 you do NOT re-voice clean task results. A FAILED or INCOMPLETE task still arrives as a "[task t1 failed] \u2026" event for you to handle. The completed result stays in YOUR context \u2014 it is yours to draw on. When the user follows up ("tell me more", "what else", "and?"), answer FROM that result first: you already have the detail, so elaborate on what you have. Do NOT spawn a fresh worker to re-search or re-gather what you were just handed. Re-dispatch ONLY when genuinely new information is needed \u2014 e.g. the user wants the full contents of a SPECIFIC source, which is one WebFetch of that URL, not a brand-new search. "[task t1 progress] \u2026" events are interim status, NOT results \u2014 on a genuinely LONG wait you MAY give one brief half-sentence aside, but only occasionally; silence while working is normal and fine (the user knows you are on it). Do NOT narrate every step or re-announce yourself. Never present progress as a finished result.\nCRITICAL: while a task is still running you have NO answer yet \u2014 never state a specific result of any kind (a number, size, count, name, path, or value). The real answer exists ONLY once the task finishes (the worker speaks it, and `TaskStatus` then returns it); inventing one meanwhile (a made-up disk size, commit count, etc.) is a serious error. Until then, only acknowledge and wait.\nNever read raw file paths, diffs, or code aloud verbatim.\nDo NOT end every turn with the same canned offer ("want a rundown?", "want the steps?"). Offer once at most; if the user pushes back, repeats themselves, or sounds unsatisfied ("you know what I mean?", "think deeper", "are you sure?"), do NOT re-offer the same thing \u2014 change approach: dispatch `Act`/`Think` to actually dig in, or ask one concrete clarifying question. Repeating a non-answer is worse than silence. A short yes or agreement right after you asked or offered something answers THAT question: act on it now (dispatch the work it needs) \u2014 never re-ask it, rephrase it, or narrow it into a new question. When the user says you looked in the wrong place, or doubts a "not found", do not defend the search you did: dispatch `Act` with their hint (the place, the id, the tool they name). Only say you checked or searched something you actually checked with a tool this conversation \u2014 name exactly what you looked at.\n"[task t1 asks] \u2026" events are QUESTIONS from a background task \u2014 relay to the user in your own words, short, then end your turn. When the user answers, call `AnswerTask` with that id and their answer. NEVER answer on the user\'s behalf for permissions or risky operations; if their reply is ambiguous, confirm first.\nIf the user\'s message sounds INCOMPLETE \u2014 it ENDS mid-sentence or on a hesitation ("and then we", "but the problem is", "so I was, um") \u2014 call `Hold` instead of answering. A hesitation INSIDE a finished message ("can you, um, open it?") does not make it incomplete, and an unclear or odd but finished message is not a fragment: answer it, or ask one short question \u2014 never Hold on it. This keeps listening for the rest of their thought. Only respond with substance when you have a complete question or request.\nDispatch discipline: send ONE self-contained task per request \u2014 a single worker with the full brief beats several workers with fragments (each worker starts fresh and re-discovers context). NEVER dispatch a worker just to read files or gather information \u2014 workers explore and discover context themselves; pass on what you already know and let one worker do the whole job. Split into parallel tasks only when the user asks for genuinely independent things. When a task completes, report its result and stop \u2014 do NOT dispatch follow-up work (verification, polish, extras) the user did not ask for, unless the report itself signals failure or doubt.\nDo not fire a second Act/Think for work already in flight, and NEVER spawn a second task to re-count, cross-check, or verify a result a worker already gave you \u2014 trust its answer; a single question gets ONE task. Call `TaskStatus` at most ONCE per turn; if a task is still running, give one brief status aside and end the turn \u2014 never poll it again and again in a loop. "Still on it" is ONLY for a repeat check on a task you already acknowledged earlier \u2014 NEVER as the ack when you first dispatch work ("still" is wrong when nothing was running yet; use a fresh "on it"/"got it, checking" instead). Use `CancelTask` when the user asks to stop something \u2014 to stop everything ("stop all", "cancel everything") pass id "all". When the user restates, continues, or rephrases a question a still-running task is already answering, it is the SAME question \u2014 do not answer it yourself (the task\'s answer is coming; two answers confuse): say briefly that you are on it. When the user only ADDS to or CORRECTS a request whose task is still running (a detail, a path, a name, a spelling), call `Act` with `refines` set to that task id \u2014 it updates the running worker in place; never cancel and re-dispatch for a refinement.\nPRIORITY: when the user says goodbye or wants to end/finish/wrap up the session ("ok bye", "that\'s all", "let\'s finish", "let\'s end", "goodnight", "exit", "wrap up"), call `ExitSession` IMMEDIATELY \u2014 do not act, do not check status, just exit.\nFor TRIVIAL instant lookups only \u2014 current time, git branch, listing a folder, peeking at a small file, or checking your own `capabilities`/tools \u2014 use `QuickLook` (instant, no task). Whenever the user asks what you can do or whether you have some ability, QuickLook `capabilities` and answer from that \u2014 never guess. Anything requiring searching, reasoning, running commands, or editing goes through `Act`.\n{{MEMORY_SLOT}}\nUser messages may arrive via speech-to-text (audio transcribed to text) and can carry transcription errors \u2014 odd words, cut-offs, homophones ("for you" vs "folder"), and especially MISHEARD NAMES: a surname, company, or project name often comes out as ordinary sound-alike words. Read for INTENT, not surface text, and do not silently rewrite what they said. If a message seems garbled, surprising, or only half-parses, or a word sounds like a name you already know (from memory or this conversation) but is spelled differently, treat it with suspicion \u2014 do NOT act on the odd word blindly or improvise content from it; briefly confirm what they meant ("did you mean\u2026?") and wait. A one-line confirm beats a confident wrong answer. NEVER save to memory a name, spelling, or term you only HEARD (it may be a transcription error) \u2014 when the user spells something aloud, read the spelling back and get a yes before storing it.';
10022
10038
  var THINK_GUIDANCE = "\u2022 `Think` \u2014 your brain. A premium reasoning model, FAR more expensive than Act. Reserve it for open-ended architecture/design questions, or a problem Act already FAILED at. ALL implementation work \u2014 coding, refactoring, debugging, edge cases, tests \u2014 goes to Act; Act is highly capable. Never send the same work to both.";
10023
10039
  var THINK_DISABLED_GUIDANCE = "(Think tier is not available \u2014 use Act for all escalations.)";
10024
10040
  var VOICE_STYLE_CONVERSATIONAL = `Speak like a person in a live conversation, not an assistant reading a script. React first, then deliver: a quick impulsive beat ("oh nice", "hmm, hold on", "ah, got it") before the substance. Use contractions always. Vary sentence length \u2014 some very short. Light fillers and backchannels are fine ("mm-hm", "right", "let's see") but at most one per reply \u2014 never stack them. When you escalate to Act or Think, say it like a human would ("hang on, let me actually dig into that \u2014 gimme a minute") instead of announcing a task. When a result comes back, react to it like you just found out ("okay so \u2014 turns out\u2026"). Match the user's energy: a quick question gets a quick answer \u2014 a few words is a perfectly good turn. Prefer a short answer plus an offer ("want the details?") over covering everything. Never narrate your own mechanics (no "I will now act", no task ids out loud).`;
@@ -10614,11 +10630,19 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10614
10630
  this.holdTimer = null;
10615
10631
  this.holdResumed = true;
10616
10632
  this.notify("diag", "hold_expired", { ms });
10617
- this.queueRevoice(_DuplexAgent.HOLD_EXPIRED);
10633
+ this.queueRevoice(this.holdExpiredNote());
10618
10634
  }, ms);
10619
10635
  this.holdTimer.unref?.();
10620
10636
  }
10621
10637
  static HOLD_EXPIRED = "[the user stopped there and did not go on \u2014 respond to what they said now; if it is unclear, ask one short question. Do not Hold.]";
10638
+ /** HOLD_EXPIRED + the tasks still running: live (20261014-095941) the held line was the user re-asking the question t2
10639
+ * was already answering; "respond now" made the reflex answer it from its own knowledge — two answers. */
10640
+ holdExpiredNote() {
10641
+ const running = [...this.tasks.values()].filter((t) => t.status === "running");
10642
+ if (!running.length) return _DuplexAgent.HOLD_EXPIRED;
10643
+ const list = running.map((t) => `${t.id} (${t.label})`).join(", ");
10644
+ return _DuplexAgent.HOLD_EXPIRED.slice(0, -1) + ` Still running: ${list}. If what they said is about a running task, do NOT answer it yourself \u2014 say in one short line that you are on it, or call Act with \`refines\` if they added a detail.]`;
10645
+ }
10622
10646
  /** True when the just-finished turn voiced NOTHING — dead air to repair. Two ways this happens:
10623
10647
  * (a) a dispatch with no spoken ack, and (b) an INLINE turn whose `final` channel came back empty —
10624
10648
  * gpt-oss harmony sometimes puts the whole reply in `analysis` (→ thinking_delta, suppressed in
@@ -11732,11 +11756,17 @@ Another agent just implemented the above. Independently check the CURRENT state
11732
11756
  filler: { type: "string", description: 'optional short filler to speak ("mhm", "go on", "mm-hm")' }
11733
11757
  }
11734
11758
  },
11735
- run: async ({ filler }) => {
11736
- if (this.heldThisTurn) return "Already holding \u2014 stay silent.";
11759
+ // A held turn ENDS here (ctx.endTurn): live (20261014-095941) the reflex re-called Hold after "Do not respond
11760
+ // further" until the repeat kill-switch fired (3 steps, 21.6k tokens) — a model step it never needed.
11761
+ run: async ({ filler }, ctx) => {
11762
+ if (this.heldThisTurn) {
11763
+ ctx.endTurn = true;
11764
+ return "Already holding \u2014 stay silent.";
11765
+ }
11737
11766
  if (this.holdResumed) return "Not now \u2014 the user already stopped after those words. Answer them now in one or two short sentences, or ask one short question.";
11738
11767
  this.heldThisTurn = true;
11739
11768
  this.notify("hold_filler", filler ? String(filler) : "");
11769
+ ctx.endTurn = true;
11740
11770
  return "Holding \u2014 listening for the rest of the user's thought. Do not respond further this turn.";
11741
11771
  }
11742
11772
  };
@@ -14206,10 +14236,13 @@ var CHANNEL_MODES = {
14206
14236
  generateFor: ["backchannel", "bridge"],
14207
14237
  reactionGapMs: 6e3,
14208
14238
  generateBudgetMs: 400,
14239
+ // A breath must outlast the word cadence: at ~2.5 words/s interims arrive every ~400ms, so a 250ms "breath" fired after
14240
+ // every word and only the 1.2s gaps held it (~25 lines per 30s, live probe). Lines ≥2.5s apart (light after light 3s), ≤6 in any 30s.
14209
14241
  backchannelMinWords: 5,
14210
- backchannelGapMs: 1200,
14211
- minGapMs: 1200,
14212
- backchannelPauseMs: 250
14242
+ backchannelGapMs: 3e3,
14243
+ minGapMs: 2500,
14244
+ backchannelPauseMs: 500,
14245
+ maxPerWindow: 6
14213
14246
  }
14214
14247
  };
14215
14248
  var CHANNEL_MODE_NAMES = Object.keys(CHANNEL_MODES);
@@ -14258,6 +14291,9 @@ var SecondChannelOptions = class {
14258
14291
  /** Backchannel after backchannel needs this gap (~4-6 cues in a 30s monologue — Para phone call 2026-10-13: 5s left
14259
14292
  * one cue in a long statement). */
14260
14293
  backchannelGapMs = 3e3;
14294
+ /** Soft cap: at most this many backchannels in any `windowMs` (0 = none). */
14295
+ maxPerWindow = 0;
14296
+ windowMs = 3e4;
14261
14297
  /** The user resumes within this long of their last words after a line of theirs was dispatched: the same statement goes
14262
14298
  * on (an STT endpoint mid-monologue) — its words still count toward the backchannel guards, and it is no fresh request. */
14263
14299
  continueMs = 3e3;
@@ -14540,10 +14576,17 @@ var SecondChannel = class {
14540
14576
  allowed(k) {
14541
14577
  if (!this.o.kinds.includes(k)) return false;
14542
14578
  if (this.audioLocked || this.inflight || this.o.now() - this.lastAt < this.o.minGapMs || k === this.lastKind && (k !== "backchannel" || this.o.now() - this.lastAt < this.o.backchannelGapMs)) return false;
14543
- if (k === "backchannel") return this.o.aec && this.userSpeaking && this.main === "idle";
14579
+ if (k === "backchannel") return this.o.aec && this.userSpeaking && this.main === "idle" && !this.capped();
14544
14580
  if (k === "filler") return !this.userSpeaking && this.main === "idle";
14545
14581
  return (this.main === "queued" || this.main === "thinking") && !this.bridged;
14546
14582
  }
14583
+ bcTimes = [];
14584
+ capped() {
14585
+ if (!this.o.maxPerWindow) return false;
14586
+ const from = this.o.now() - this.o.windowMs;
14587
+ this.bcTimes = this.bcTimes.filter((t) => t > from);
14588
+ return this.bcTimes.length >= this.o.maxPerWindow;
14589
+ }
14547
14590
  /** 0ms rule: the candidate line for this trigger, or silence. */
14548
14591
  rule(trigger, pauseMs = 0, breath = false, cue = null) {
14549
14592
  const w = words2(this.heard);
@@ -14670,6 +14713,7 @@ var SecondChannel = class {
14670
14713
  if (r === false) return false;
14671
14714
  this.lastAt = this.o.now();
14672
14715
  this.lastKind = kind;
14716
+ if (kind === "backchannel") this.bcTimes.push(this.lastAt);
14673
14717
  if (kind === "bridge") this.bridged = true;
14674
14718
  this.recent = [...this.recent, text].slice(-this.o.avoidLines);
14675
14719
  this.live = text.trim();