@livx.cc/agentx 0.99.57 → 0.99.58

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/index.d.ts CHANGED
@@ -889,6 +889,19 @@ declare function reflectOnRun(o: ReflectOptions): Promise<string | null>;
889
889
  */
890
890
  declare function loadInstructions(fs: IFilesystem, names?: string[]): Promise<string>;
891
891
 
892
+ /** Structural subset of ai.libx.js DecideClient.decide — keeps this module provider-agnostic. */
893
+ interface Decider {
894
+ decide(o: {
895
+ state: unknown;
896
+ questions: Record<string, unknown>;
897
+ }): Promise<{
898
+ answers: Record<string, {
899
+ choice: string;
900
+ probabilities: Record<string, number>;
901
+ }>;
902
+ }>;
903
+ }
904
+
892
905
  /**
893
906
  * DuplexAgent — voice-optimized three-tier conversational engine, composed on top of `Agent`.
894
907
  *
@@ -928,15 +941,24 @@ interface TaskRecord {
928
941
  splitter?: SpokenSplitter;
929
942
  /** Set when the user barged in / took the floor while this task was in flight: its remaining SPOKEN
930
943
  * delivery is suppressed (don't talk over the new topic), but its full result still lands in the
931
- * transcript so the reflex can surface it on request. See parkInFlightDeliveries(). */
932
- deliveryParked?: boolean;
933
- /** Accumulated `<spoken>` delivery text (every segment, whether or not it was actually voiced). Used to
934
- * RESUME a parked delivery after a trivial barge — see isTrivialBarge / redeliverParked. */
944
+ * transcript so the reflex can surface it on request. See parkInFlightDeliveries().
945
+ * 'barge' = cut by a barge the user's next line has not resolved yet — a resume revives it (its unheard segments
946
+ * are parked); 'moved_on' = the user moved on (a steer, a new dispatch) or its outcome went to an integration turn
947
+ * (failed / stopped early) — it stays stale for good: nothing it says is spoken or parked, and no later barge or
948
+ * "go on" revives it. */
949
+ deliveryParked?: 'barge' | 'moved_on';
950
+ /** Accumulated `<spoken>` delivery text (every segment, whether or not it was actually voiced) — what the task
951
+ * SAID (TaskStatus). Never replayed: a resume replays only the unheard part (DuplexAgent.parkedRedeliver). */
935
952
  spokenText?: string;
936
953
  /** The user-turn counter (DuplexAgent.turn) at dispatch time. parkInFlightDeliveries() parks only tasks
937
954
  * from a PRIOR turn (a superseded topic) — never the task the current turn just launched, so a barge on
938
955
  * stale audio (or a substantive redirect) can't collateral-silence the work the user actually wants. */
939
956
  createdTurn: number;
957
+ /** Chars of this delivery voiced so far (spokenBudgetChars); `budgetSpent` once it was cut — a resume goes on unbudgeted. */
958
+ voicedChars?: number;
959
+ budgetSpent?: boolean;
960
+ /** The budget offer spoken in place of the cut rest — folded into the transcript after the written result. */
961
+ capOffer?: string;
940
962
  }
941
963
  type WorkerTier = 'act' | 'think';
942
964
  declare class DuplexAgentOptions {
@@ -972,6 +994,13 @@ declare class DuplexAgentOptions {
972
994
  /** Teach the model to emit inline `[emotion]` tags for Cartesia emotion control. Only set when the
973
995
  * TTS actually speaks them — text-duplex (no TTS) would otherwise print literal tags. */
974
996
  emotionTags: boolean;
997
+ /** Judge a post-barge line while a delivery is parked: resume it now, answer an aside and carry it on (the reflex,
998
+ * told what was heard — see asideNote), or drop it (steer/stop). Unset → the conservative `isTrivialBarge` allowlist (resume | steer); a throwing judge
999
+ * falls back to it too. */
1000
+ bargeIntent?: (text: string) => Promise<'resume' | 'aside' | 'steer' | 'stop'>;
1001
+ /** Typed-decision model (e.g. ai.libx.js DecideClient → Jev). Judges reflex sentences the leak screen flags as
1002
+ * SUSPECT (src/voice/leak.ts) — only suspects pay the ~300ms call. Unset → only the strict leak shapes are cut. */
1003
+ decider?: Decider;
975
1004
  /** Awaited BEFORE a worker spawns — open a per-task checkpoint frame, audit, etc.
976
1005
  * (post-spawn would race the worker's first edits). */
977
1006
  onTaskStart?: (id: string, label: string) => void | Promise<void>;
@@ -980,6 +1009,13 @@ declare class DuplexAgentOptions {
980
1009
  progressUpdates: boolean;
981
1010
  /** Min ms between progress re-voices per task. */
982
1011
  progressIntervalMs: number;
1012
+ /** Spoken budget of ONE task delivery (chars, ~3 short sentences). Past it the rest is parked — cut at a sentence
1013
+ * boundary, `spokenOffer` spoken instead — so "go on" continues it once (the parked/resume path) and an aside or
1014
+ * steer handles it like any parked delivery. The worker is also asked for a short spoken summary + one offer (the
1015
+ * source); this is the backstop. Live 20260924-093945: ~20s and ~70s monologues whose closing choice went unspoken.
1016
+ * The full result always folds into the transcript. 0 = unbudgeted. */
1017
+ spokenBudgetChars: number;
1018
+ spokenOffer: string;
983
1019
  /** Relay worker questions (AskUserQuestion + permission asks via parkQuestion) through the VOICE:
984
1020
  * the question re-voices as '[task <id> asks] …', the user answers conversationally, and the
985
1021
  * voice model resolves it with the AnswerTask tool. Off → host.ask passthrough (text menus). */
@@ -1015,14 +1051,22 @@ declare class DuplexAgent {
1015
1051
  * (TaskRecord.createdTurn) so parking can tell a prior-topic task from the current turn's fresh one. */
1016
1052
  private turn;
1017
1053
  private pendingEvents;
1018
- /** Spoken text of parked deliveries that have SETTLED, awaiting a trivial-barge resume decision on the
1019
- * next turn (a substantive turn clears it — the result stays in the transcript, recoverable by asking). */
1054
+ /** THE parked delivery: every piece the user has NOT heard, in speaking order — the unheard rest a barge cut out of
1055
+ * the voice queue (parkCutDelivery) followed by each segment a barge-parked task produced since (deliverSegment).
1056
+ * Nothing already voiced is ever added, so a resume never repeats what was heard. The next line's barge intent
1057
+ * decides: resume → spoken verbatim, aside → handed to the reflex to carry on after its answer (asideNote) — both
1058
+ * send the barge-parked tasks live again; steer/stop → cleared, and those tasks
1059
+ * are moved on from (the full result stays in the transcript, recoverable by asking). `task` = the owner, so a
1060
+ * task whose outcome goes to an integration turn takes its pieces back (dropParked). */
1020
1061
  private parkedRedeliver;
1021
- /** A trivial barge arrived while a parked delivery was STILL RUNNING → resume it the moment it settles. */
1022
- private awaitTrivialRedeliver;
1023
- /** Set by a SUBSTANTIVE turn after a barge: the user moved on, so a parked delivery that settles LATER
1024
- * must NOT arm a resume (else a much-later "go on" replays a stale result). Reset by a fresh barge. */
1025
- private suppressParkedResume;
1062
+ /** parkedRedeliver length when the last barge parked (parkInFlightDeliveries): the cut it hands over later
1063
+ * (parkCutDelivery) was voiced BEFORE anything parked since, so it is inserted here. */
1064
+ private bargeMark;
1065
+ /** The last barge cut a REFLEX reply (parkCutDelivery): a "go on" is then the reflex's to answer — it runs a normal
1066
+ * turn (the host's interruption note continues the reply), and only then does the parked delivery resume. */
1067
+ private reflexCut;
1068
+ /** Recent live task segments → their task id: attributes a cut delivery (parkCutDelivery) to the task that spoke it. */
1069
+ private voicedBy;
1026
1070
  /** Out-of-band follow-up attribution for the events coalescing into the next flush turn: TRUE iff ≥1 of
1027
1071
  * the tasks being integrated was NON-CLEAN (early-stop/failure). Carried out-of-band on the enqueue call
1028
1072
  * by the caller that KNOWS the outcome — a plain boolean the MODEL CANNOT PERTURB. It is NOT scanned from
@@ -1045,6 +1089,15 @@ declare class DuplexAgent {
1045
1089
  private reflexBuf;
1046
1090
  private reflexForwarded;
1047
1091
  private fabricationCut;
1092
+ private leakCuts;
1093
+ private leakStep;
1094
+ private leakHit;
1095
+ private stepMarks;
1096
+ private leakVerdicts;
1097
+ private judging?;
1098
+ private turnGen;
1099
+ private turnSpoken;
1100
+ private turnBase;
1048
1101
  /** TRUE for the duration of a re-voice turn that is integrating ≥1 NON-CLEAN task (turn-eligibility,
1049
1102
  * carried out-of-band — NOT derived from any worker/brief string). ANY Act/Think dispatched in such a
1050
1103
  * turn is stamped followUp:true. This GUARANTEES the dangerous direction is impossible: a genuine
@@ -1082,9 +1135,38 @@ declare class DuplexAgent {
1082
1135
  constructor(options?: Partial<DuplexAgentOptions>);
1083
1136
  /** Resolve memory tools + inject index into voice system prompt (once). */
1084
1137
  private initMemory;
1085
- /** Flush any held-back trailing fragment (a possible `[task` opener that never completed) once the
1086
- * turn's stream is done — so a legit message ending in "[t" isn't silently dropped. */
1087
- private flushHeldReflexTail;
1138
+ /** Forward what the reflex's spoken-stream gates allow (text_delta → host/TTS; forwarded audio can't be unsent):
1139
+ * - hold a trailing fragment that could be a `[task` opener split across deltas ("[ta" + "sk"), and an UNCLOSED
1140
+ * "(" (a stage direction is judged — and dropped — only once its ")" arrives; bounded at 80 chars);
1141
+ * - the leak gate: whole sentences only, each screened by isLeakSuspect the moment it completes — a strict match
1142
+ * is cut, any other suspect waits for the decider's verdict (judgeLeak) while the rest queues behind it.
1143
+ * `final` = the stream is done: release the holds and judge whatever is left as a sentence. Text before a step
1144
+ * boundary (stepMarks) is complete — no holds, its tail is a sentence. Idempotent; re-run as verdicts land. */
1145
+ private pumpReflex;
1146
+ private forwardReflex;
1147
+ /** Leak verdict for one complete sentence: false = speak, true = cut, undefined = the judge is still out.
1148
+ * Non-suspects pass free; strict shapes are cut without a call; other suspects go to the decider (if any) —
1149
+ * one call at a time, cached per turn. A failed/slow judge fails OPEN (speech is never muted on a guess). */
1150
+ private leakVerdict;
1151
+ private static readonly LEAK_JUDGE_TIMEOUT_MS;
1152
+ /** The stream is done: settle every pending verdict and release everything the gates still hold. */
1153
+ private drainReflex;
1154
+ /** Drop any in-flight judge of the current stream (its verdict must not pump a stream that moved on). */
1155
+ private dropJudge;
1156
+ /** Remove a cut analysis leak from this turn's assistant message(s) — left in, it primes the next turn to
1157
+ * leak again and reads as something we said. Keeps any legit prefix; drops a message left empty. */
1158
+ private scrubLeakFromTranscript;
1159
+ /** A Hold turn is silent BY DESIGN: every reflex text after the Hold is muted (voiceHost drops it), so none of it may
1160
+ * persist either — the transcript is what the agent SAID. Live 20260923-125143 msg 10: the model's reasoning ("We
1161
+ * need to respond according to guidelines… So we should hold") was saved as an assistant reply and fed every later
1162
+ * turn. Strips the text of assistant messages after the Hold call (tool calls stay — they happened); a message left
1163
+ * empty is removed. */
1164
+ private scrubHeldText;
1165
+ /** Shared turn epilogue (user turn, confirmed speculation, re-voice): release held text, scrub a cut leak,
1166
+ * then repair dead air — unless the turn was ABORTED: the user took the floor (barge-in / Esc), which is
1167
+ * not dead air, and a nudge would run on the same aborted signal (live: the "[reminder]" landed in the
1168
+ * transcript as a user turn with no reply, session 20260923-101910 msg 27). */
1169
+ private endTurn;
1088
1170
  /** Remove complete stage-direction parentheticals from the UNFORWARDED reflex text (STAGE_DIRECTION_RE).
1089
1171
  * Only the unforwarded region is touched — already-spoken audio can't be unsent, and splicing before
1090
1172
  * reflexForwarded would corrupt the forward offset. */
@@ -1115,13 +1197,35 @@ declare class DuplexAgent {
1115
1197
  * hosts/tests key on it. */
1116
1198
  private static readonly FALLBACK_ACKS;
1117
1199
  private static readonly FALLBACK_RETRY;
1200
+ private static readonly FALLBACK_OWN;
1118
1201
  private lastFallback;
1119
1202
  /** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns.
1120
1203
  * If a speculative reflex call is pending and this content CONFIRMS it (the final matches the
1121
1204
  * speculated partial), the in-flight call is adopted as THE turn: its buffered output flushes to the
1122
1205
  * host NOW (this is the latency win) and streaming continues live. Any other content aborts the
1123
- * speculation first (rolled back silently) and runs a normal turn behind it. */
1124
- send(content: MessageContent): Promise<RunResult>;
1206
+ * speculation first (rolled back silently) and runs a normal turn behind it.
1207
+ * `opts.id` names the turn; `opts.replaces` names an earlier one this content REPLACES — the voice engine's
1208
+ * fuller re-finalization of an utterance whose reply had not started ("…previous" → "…previous
1209
+ * conversation."). The replaced turn is skipped if it has not run yet, else aborted and rolled back out of
1210
+ * the transcript (kept only if it already dispatched work) — one reply, to the full text. */
1211
+ send(content: MessageContent, opts?: {
1212
+ id?: string;
1213
+ replaces?: string;
1214
+ }): Promise<RunResult>;
1215
+ /** Host-named user turns (send opts.id), so a later send can replace one. Bounded; a replace that arrives
1216
+ * BEFORE its target (the host's dispatch hops can reorder) leaves a pre-superseded record behind. */
1217
+ private userTurns;
1218
+ private turnSignal?;
1219
+ private trackTurn;
1220
+ private replaceTurn;
1221
+ private static replacedResult;
1222
+ /** A turn replaced mid-run: mute whatever it still holds, and erase it from the transcript (the replacing turn
1223
+ * carries the user's full words) — unless it already dispatched work or SPOKE: the task is real, and the user
1224
+ * heard those words, so both stay visible to the replacing turn. An aborted stream never reaches the transcript,
1225
+ * so what was spoken is recorded as the replaced turn's reply. */
1226
+ private dropReplacedTurn;
1227
+ private resumeParked;
1228
+ private sendTurn;
1125
1229
  /** Start a HELD speculative reflex turn on a stable partial (VoiceEngine.onSpeculate). Its spoken
1126
1230
  * output buffers in emitHost; send() later confirms (flush + adopt) or aborts it. The turn holds the
1127
1231
  * voice mutex until that decision (bounded by a safety timeout), so no other turn can interleave
@@ -1138,18 +1242,45 @@ declare class DuplexAgent {
1138
1242
  * superseded topic never talks over the new one (the debt-after-jokes regression). Tasks keep running and
1139
1243
  * still fold their result into the transcript — recoverable, just not spoken. Returns parked ids (logging).
1140
1244
  * Does NOT cancel. Two callers, two shapes:
1141
- * - BARGE (default): the user interrupted AUDIO. Park EVERY running task (including this turn's) — the
1142
- * worker they cut off must stop, whatever turn launched it — and re-arm trivial-barge auto-resume so a
1143
- * following "go on" can bring the parked delivery back.
1144
- * - SUPERSEDE (`priorTurnsOnly`, `reenableResume:false`, from dispatch()): a new dispatch redirected to
1145
- * fresh work. Park only PRIOR-turn tasks so parallel siblings dispatched in the SAME turn don't silence
1146
- * each other, and keep them suppressed (the user moved on — don't resurface on a stray "go on"). */
1147
- parkInFlightDeliveries(reenableResume?: boolean, priorTurnsOnly?: boolean): string[];
1148
- /** True while there is a parked delivery to potentially resume: either one already settled (queued) or
1149
- * one still running that was parked by a barge. */
1245
+ * - BARGE (default): the user interrupted AUDIO. Park EVERY live running task (including this turn's) — the
1246
+ * worker they cut off must stop, whatever turn launched it — as 'barge', so a following "go on" can bring
1247
+ * it back. A task the user already moved on from stays moved on: a barge never revives it.
1248
+ * - SUPERSEDE (`priorTurnsOnly`, `barge:false`, from dispatch()): a new dispatch redirected to fresh work.
1249
+ * Park only PRIOR-turn live tasks so parallel siblings dispatched in the SAME turn don't silence each other,
1250
+ * as 'moved_on' (don't resurface on a stray "go on"). */
1251
+ parkInFlightDeliveries(barge?: boolean, priorTurnsOnly?: boolean): string[];
1252
+ /** A barge-in cut a worker delivery the VOICE ENGINE was still speaking — the queued utterances it dropped plus the
1253
+ * unheard rest of the one playing (cli splitCut). It joins the parked delivery AHEAD of whatever parked tasks
1254
+ * produced since the barge (it was voiced first); the user's next line runs the barge-intent judge — resume it,
1255
+ * answer an aside and carry it on, or drop it on a steer/stop. `heard` = the cut utterance's part the user DID hear
1256
+ * (the aside note tells the reflex what was heard vs missed).
1257
+ * Live 20260923-125143: "Oh, that— that's new." cut an iOS-news delivery and its 6 remaining utterances vanished.
1258
+ * `reflexCut` = the barge cut a REFLEX reply (the host's cut had a reply): a "go on" then runs the reflex (see send).
1259
+ * A cut spoken by a task the user moved on from (or whose outcome went to an integration turn) is not parked. */
1260
+ parkCutDelivery(text: string, reflexCut?: boolean, heard?: string): void;
1261
+ /** One spoken segment of a task's delivery — the ONLY path a worker's speech takes (streamed segments, the settle
1262
+ * tail, the no-spoken fallback). Live → the voice queue; barge-parked → the parked delivery, in order; superseded
1263
+ * (the user moved on) → nowhere (the result still folds into the transcript at settle). */
1264
+ private deliverSegment;
1265
+ /** Split a delivery segment at the spoken budget (whole sentences; the first sentence always speaks): what to voice
1266
+ * now, and the rest to park. Marks the budget spent on a cut, so a resumed delivery runs on unbudgeted. */
1267
+ private budgetSplit;
1268
+ /** The cap offer last spoken and the task whose rest it offers — a barge over it is not a delivery to park
1269
+ * (parkCutDelivery). */
1270
+ private offer;
1271
+ /** The cap offer was the last thing voiced (no delivery or turn has spoken since) — a plain yes accepts it (send). */
1272
+ private offerOpen;
1273
+ /** The task's outcome now belongs to its integration turn (failed / stopped early): take back its parked pieces and
1274
+ * keep it from being revived — a "go on" must not replay raw segments of a result the reflex is about to explain. */
1275
+ private dropParked;
1276
+ /** True while there is a parked delivery the next line may resume: unheard text already parked, or a barge-parked
1277
+ * task still running (one the user moved on from does not count). */
1150
1278
  private hasParkedDelivery;
1151
- /** Speak any settled-and-queued parked delivery now (trivial-barge resume). Returns false if nothing was
1152
- * queued yet (the caller then arms awaitTrivialRedeliver so the task resumes the instant it settles). */
1279
+ /** The note for an aside over a parked delivery: the same reply-interruption note a cut reflex reply gets
1280
+ * (interruptionNote), over the delivery as it was being said — the cut utterance's heard part, then every unheard
1281
+ * piece. Nothing parked yet (the task is still working) → its result simply follows the answer. */
1282
+ private asideNote;
1283
+ /** Speak the parked delivery now (resume), in order, and clear it. */
1153
1284
  private redeliverParked;
1154
1285
  /** Resolve when all queued voice turns AND all in-flight worker tasks have settled (tests, graceful shutdown). */
1155
1286
  idle(): Promise<void>;
@@ -1170,6 +1301,8 @@ declare class DuplexAgent {
1170
1301
  * Act briefs get a self-verify footer — the worker's report is trusted without review, so it
1171
1302
  * must check its own work before reporting (nearly free under prompt caching; measured honest:
1172
1303
  * it does NOT fix one-shot logic bugs — see mind/10). Think tasks are pure reasoning — no footer. */
1304
+ /** Static text snapshot of the last `turns` user/assistant messages (`role: text` lines). */
1305
+ recentExcerpt(turns?: number): string;
1173
1306
  private buildBrief;
1174
1307
  /** Spawn a detached worker for task `id`; its settlement notifies + enqueues the re-voice turn. */
1175
1308
  private spawnWorker;
@@ -1209,11 +1342,11 @@ declare class DuplexAgent {
1209
1342
  private integrationPrompt;
1210
1343
  private onWorkerSettled;
1211
1344
  private onWorkerFailed;
1212
- /** A delivery the user has SUPERSEDED — parked AND moved on (suppressParkedResume, set by a redirect) —
1345
+ /** A delivery the user has SUPERSEDED — moved on from (deliveryParked 'moved_on', set by a redirect) —
1213
1346
  * must not hijack the voice when it settles late (partial or failed): that would talk over the new topic
1214
1347
  * and, worse, auto-escalate work the user already abandoned. Fold a recoverable note into the transcript
1215
1348
  * instead (the reflex can surface it if asked) and report true so the caller skips queueRevoice. A merely
1216
- * barge-parked task (suppressParkedResume false — a "go on" may still resume it) is NOT superseded: it
1349
+ * barge-parked task ('barge' — a "go on" may still resume it) is NOT superseded: it
1217
1350
  * keeps the normal surfacing path so a failure the user still cares about is not swallowed. */
1218
1351
  private foldIfSuperseded;
1219
1352
  private failTask;
@@ -1226,6 +1359,11 @@ declare class DuplexAgent {
1226
1359
  * `followUp` marks an automatic escalation/re-delegation (set by the integration turn) so the new
1227
1360
  * task's own integration turn won't escalate again — capping auto-follow-ups to one hop. */
1228
1361
  dispatch(brief: string, tier?: WorkerTier, label?: string, followUp?: boolean): Promise<string>;
1362
+ /** Tool result for a dispatch. The task is ALREADY RUNNING — live, the terse "Acknowledge briefly" drew
1363
+ * "Did you want me to look up today's news?" right after dispatching exactly that (session 20260923-101910
1364
+ * msg 18). Also truthful about delivery: a clean result is spoken by the worker itself and lands in
1365
+ * context (TaskStatus returns it); only failures come back as a [task … failed] event. */
1366
+ private dispatchResult;
1229
1367
  private actTool;
1230
1368
  private thinkTool;
1231
1369
  private taskStatusTool;