@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 +33 -8
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +25 -5
- package/dist/index.js +33 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -879,6 +879,10 @@ interface TaskRecord {
|
|
|
879
879
|
/** Accumulated `<spoken>` delivery text (every segment, whether or not it was actually voiced). Used to
|
|
880
880
|
* RESUME a parked delivery after a trivial barge — see isTrivialBarge / redeliverParked. */
|
|
881
881
|
spokenText?: string;
|
|
882
|
+
/** The user-turn counter (DuplexAgent.turn) at dispatch time. parkInFlightDeliveries() parks only tasks
|
|
883
|
+
* from a PRIOR turn (a superseded topic) — never the task the current turn just launched, so a barge on
|
|
884
|
+
* stale audio (or a substantive redirect) can't collateral-silence the work the user actually wants. */
|
|
885
|
+
createdTurn: number;
|
|
882
886
|
}
|
|
883
887
|
type WorkerTier = 'act' | 'think';
|
|
884
888
|
declare class DuplexAgentOptions {
|
|
@@ -953,6 +957,9 @@ declare class DuplexAgent {
|
|
|
953
957
|
readonly tasks: Map<string, TaskRecord>;
|
|
954
958
|
private queue;
|
|
955
959
|
private seq;
|
|
960
|
+
/** Monotonic user-turn counter (bumped once per SUBSTANTIVE turn). Stamped onto each task at dispatch
|
|
961
|
+
* (TaskRecord.createdTurn) so parking can tell a prior-topic task from the current turn's fresh one. */
|
|
962
|
+
private turn;
|
|
956
963
|
private pendingEvents;
|
|
957
964
|
/** Spoken text of parked deliveries that have SETTLED, awaiting a trivial-barge resume decision on the
|
|
958
965
|
* next turn (a substantive turn clears it — the result stays in the transcript, recoverable by asking). */
|
|
@@ -1073,11 +1080,17 @@ declare class DuplexAgent {
|
|
|
1073
1080
|
abortSpeculation(): void;
|
|
1074
1081
|
/** Cancel a running background task — shared by the CancelTask tool and the CLI /tasks picker. */
|
|
1075
1082
|
cancelTask(id: string): string;
|
|
1076
|
-
/** Barge-in
|
|
1077
|
-
*
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1080
|
-
|
|
1083
|
+
/** Barge-in (or a dispatch-driven supersede): suppress running tasks' remaining SPOKEN delivery so a
|
|
1084
|
+
* superseded topic never talks over the new one (the debt-after-jokes regression). Tasks keep running and
|
|
1085
|
+
* still fold their result into the transcript — recoverable, just not spoken. Returns parked ids (logging).
|
|
1086
|
+
* Does NOT cancel. Two callers, two shapes:
|
|
1087
|
+
* - BARGE (default): the user interrupted AUDIO. Park EVERY running task (including this turn's) — the
|
|
1088
|
+
* worker they cut off must stop, whatever turn launched it — and re-arm trivial-barge auto-resume so a
|
|
1089
|
+
* following "go on" can bring the parked delivery back.
|
|
1090
|
+
* - SUPERSEDE (`priorTurnsOnly`, `reenableResume:false`, from dispatch()): a new dispatch redirected to
|
|
1091
|
+
* fresh work. Park only PRIOR-turn tasks so parallel siblings dispatched in the SAME turn don't silence
|
|
1092
|
+
* each other, and keep them suppressed (the user moved on — don't resurface on a stray "go on"). */
|
|
1093
|
+
parkInFlightDeliveries(reenableResume?: boolean, priorTurnsOnly?: boolean): string[];
|
|
1081
1094
|
/** True while there is a parked delivery to potentially resume: either one already settled (queued) or
|
|
1082
1095
|
* one still running that was parked by a barge. */
|
|
1083
1096
|
private hasParkedDelivery;
|
|
@@ -1142,6 +1155,13 @@ declare class DuplexAgent {
|
|
|
1142
1155
|
private integrationPrompt;
|
|
1143
1156
|
private onWorkerSettled;
|
|
1144
1157
|
private onWorkerFailed;
|
|
1158
|
+
/** A delivery the user has SUPERSEDED — parked AND moved on (suppressParkedResume, set by a redirect) —
|
|
1159
|
+
* must not hijack the voice when it settles late (partial or failed): that would talk over the new topic
|
|
1160
|
+
* and, worse, auto-escalate work the user already abandoned. Fold a recoverable note into the transcript
|
|
1161
|
+
* instead (the reflex can surface it if asked) and report true so the caller skips queueRevoice. A merely
|
|
1162
|
+
* barge-parked task (suppressParkedResume false — a "go on" may still resume it) is NOT superseded: it
|
|
1163
|
+
* keeps the normal surfacing path so a failure the user still cares about is not swallowed. */
|
|
1164
|
+
private foldIfSuperseded;
|
|
1145
1165
|
private failTask;
|
|
1146
1166
|
/** Live-switch the think tier: `false` disables (removes the Think tool from the voice agent),
|
|
1147
1167
|
* a model id enables (adds the tool if missing). The system-prompt THINK_SLOT text is frozen at
|
package/dist/index.js
CHANGED
|
@@ -6734,6 +6734,9 @@ var DuplexAgent = class _DuplexAgent {
|
|
|
6734
6734
|
tasks = /* @__PURE__ */ new Map();
|
|
6735
6735
|
queue = Promise.resolve();
|
|
6736
6736
|
seq = 0;
|
|
6737
|
+
/** Monotonic user-turn counter (bumped once per SUBSTANTIVE turn). Stamped onto each task at dispatch
|
|
6738
|
+
* (TaskRecord.createdTurn) so parking can tell a prior-topic task from the current turn's fresh one. */
|
|
6739
|
+
turn = 0;
|
|
6737
6740
|
pendingEvents = [];
|
|
6738
6741
|
/** Spoken text of parked deliveries that have SETTLED, awaiting a trivial-barge resume decision on the
|
|
6739
6742
|
* next turn (a substantive turn clears it — the result stays in the transcript, recoverable by asking). */
|
|
@@ -7027,6 +7030,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
7027
7030
|
return this.enqueue(async () => {
|
|
7028
7031
|
await this.initMemory();
|
|
7029
7032
|
this.resetTurn();
|
|
7033
|
+
this.turn++;
|
|
7030
7034
|
this.awaitTrivialRedeliver = false;
|
|
7031
7035
|
this.parkedRedeliver = [];
|
|
7032
7036
|
this.suppressParkedResume = true;
|
|
@@ -7125,15 +7129,21 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
7125
7129
|
rec.controller.abort();
|
|
7126
7130
|
return `Task ${rec.id} (${rec.label}) cancelled.`;
|
|
7127
7131
|
}
|
|
7128
|
-
/** Barge-in
|
|
7129
|
-
*
|
|
7130
|
-
*
|
|
7131
|
-
*
|
|
7132
|
-
|
|
7133
|
-
|
|
7132
|
+
/** Barge-in (or a dispatch-driven supersede): suppress running tasks' remaining SPOKEN delivery so a
|
|
7133
|
+
* superseded topic never talks over the new one (the debt-after-jokes regression). Tasks keep running and
|
|
7134
|
+
* still fold their result into the transcript — recoverable, just not spoken. Returns parked ids (logging).
|
|
7135
|
+
* Does NOT cancel. Two callers, two shapes:
|
|
7136
|
+
* - BARGE (default): the user interrupted AUDIO. Park EVERY running task (including this turn's) — the
|
|
7137
|
+
* worker they cut off must stop, whatever turn launched it — and re-arm trivial-barge auto-resume so a
|
|
7138
|
+
* following "go on" can bring the parked delivery back.
|
|
7139
|
+
* - SUPERSEDE (`priorTurnsOnly`, `reenableResume:false`, from dispatch()): a new dispatch redirected to
|
|
7140
|
+
* fresh work. Park only PRIOR-turn tasks so parallel siblings dispatched in the SAME turn don't silence
|
|
7141
|
+
* each other, and keep them suppressed (the user moved on — don't resurface on a stray "go on"). */
|
|
7142
|
+
parkInFlightDeliveries(reenableResume = true, priorTurnsOnly = false) {
|
|
7143
|
+
if (reenableResume) this.suppressParkedResume = false;
|
|
7134
7144
|
const parked = [];
|
|
7135
7145
|
for (const rec of this.tasks.values())
|
|
7136
|
-
if (rec.status === "running" && !rec.deliveryParked) {
|
|
7146
|
+
if (rec.status === "running" && !rec.deliveryParked && (!priorTurnsOnly || rec.createdTurn < this.turn)) {
|
|
7137
7147
|
rec.deliveryParked = true;
|
|
7138
7148
|
parked.push(rec.id);
|
|
7139
7149
|
}
|
|
@@ -7319,7 +7329,7 @@ ${recent}` : brief) + verify + deliverContract;
|
|
|
7319
7329
|
flushSpoken();
|
|
7320
7330
|
return res;
|
|
7321
7331
|
}).then((res) => this.maybeVerify(id, brief, res, tier, agentOpts, askBridge)).then((res) => this.onWorkerSettled(id, res)).catch((err) => this.onWorkerFailed(id, err));
|
|
7322
|
-
this.tasks.set(id, { id, label, status: "running", controller, promise, tail, brief, followUp, splitter });
|
|
7332
|
+
this.tasks.set(id, { id, label, status: "running", controller, promise, tail, brief, followUp, splitter, createdTurn: this.turn });
|
|
7323
7333
|
if (this.tasks.size > this.options.maxTaskRecords)
|
|
7324
7334
|
for (const [tid, rec] of this.tasks) {
|
|
7325
7335
|
if (this.tasks.size <= this.options.maxTaskRecords) break;
|
|
@@ -7493,6 +7503,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7493
7503
|
toolCalls: res.messages.filter((m) => m.role === "tool").length
|
|
7494
7504
|
});
|
|
7495
7505
|
if (incomplete) {
|
|
7506
|
+
if (this.foldIfSuperseded(rec, res.text)) return;
|
|
7496
7507
|
return this.queueRevoice(this.integrationPrompt(rec, "incomplete", res.text, res.finishReason), true);
|
|
7497
7508
|
}
|
|
7498
7509
|
const tail = rec.splitter?.flush();
|
|
@@ -7514,12 +7525,25 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7514
7525
|
onWorkerFailed(id, err) {
|
|
7515
7526
|
this.failTask(this.tasks.get(id), err instanceof Error ? err.message : String(err));
|
|
7516
7527
|
}
|
|
7528
|
+
/** A delivery the user has SUPERSEDED — parked AND moved on (suppressParkedResume, set by a redirect) —
|
|
7529
|
+
* must not hijack the voice when it settles late (partial or failed): that would talk over the new topic
|
|
7530
|
+
* and, worse, auto-escalate work the user already abandoned. Fold a recoverable note into the transcript
|
|
7531
|
+
* instead (the reflex can surface it if asked) and report true so the caller skips queueRevoice. A merely
|
|
7532
|
+
* barge-parked task (suppressParkedResume false — a "go on" may still resume it) is NOT superseded: it
|
|
7533
|
+
* keeps the normal surfacing path so a failure the user still cares about is not swallowed. */
|
|
7534
|
+
foldIfSuperseded(rec, note) {
|
|
7535
|
+
if (!(rec.deliveryParked && this.suppressParkedResume)) return false;
|
|
7536
|
+
if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
|
|
7537
|
+
log13.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
|
|
7538
|
+
return true;
|
|
7539
|
+
}
|
|
7517
7540
|
failTask(rec, msg) {
|
|
7518
7541
|
this.dropAsk(rec.id);
|
|
7519
7542
|
rec.status = "error";
|
|
7520
7543
|
rec.result = msg;
|
|
7521
7544
|
log13.warn(`task ${rec.id} failed: ${msg}`);
|
|
7522
7545
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
|
|
7546
|
+
if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
|
|
7523
7547
|
this.queueRevoice(this.integrationPrompt(rec, "error", msg, "error"), true);
|
|
7524
7548
|
}
|
|
7525
7549
|
// --- voice tools (closures over this instance) ---
|
|
@@ -7540,6 +7564,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7540
7564
|
async dispatch(brief, tier = "act", label, followUp = false) {
|
|
7541
7565
|
if (tier === "think" && this.options.thinkModel === false) tier = "act";
|
|
7542
7566
|
if (followUp) this.autoEscalations++;
|
|
7567
|
+
this.parkInFlightDeliveries(false, true);
|
|
7543
7568
|
const id = `t${++this.seq}`;
|
|
7544
7569
|
const lbl = label ?? tier;
|
|
7545
7570
|
await this.options.onTaskStart?.(id, lbl);
|