@livx.cc/agentx 0.99.108 → 0.99.110
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 +59 -15
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +56 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1119,6 +1119,12 @@ declare class DuplexAgentOptions {
|
|
|
1119
1119
|
/** User-scope memory dir for global facts (type=user/feedback). Forwarded to Remember's routing. */
|
|
1120
1120
|
memoryUserDir?: string;
|
|
1121
1121
|
}
|
|
1122
|
+
/** The host side of a user line: `context` = volatile per-line rules (they stay in force for later reflex calls — results,
|
|
1123
|
+
* reminders — until the next line replaces them); `note` = persistent background, stored fenced in that turn. */
|
|
1124
|
+
type HostInfo = {
|
|
1125
|
+
context?: string;
|
|
1126
|
+
note?: string;
|
|
1127
|
+
};
|
|
1122
1128
|
declare const VOICE_SYSTEM_PROMPT: string;
|
|
1123
1129
|
/**
|
|
1124
1130
|
* The duplex orchestrator. `send()` enqueues a user turn on the reflex agent; `Act`/`Think`
|
|
@@ -1275,6 +1281,13 @@ declare class DuplexAgent {
|
|
|
1275
1281
|
* back and re-run once on `reflexFallbackModel` — a re-prompt of the same model (the dead-air nudge) only hits the
|
|
1276
1282
|
* same limit and ends in "could you say that again?". */
|
|
1277
1283
|
private reflexSend;
|
|
1284
|
+
/** The host's per-line context (call rules, browser context, earlier conversation) rides THIS call only, fenced as the app's —
|
|
1285
|
+
* inside the user turn it read as the user's own words (live #938: "repeat everything I said" recited the call rules). The
|
|
1286
|
+
* stored turn keeps only what the user said, so later turns, briefs and the saved session never carry it. A block, not a
|
|
1287
|
+
* mid-transcript system message: the Anthropic/Google adapters keep only the leading system message. */
|
|
1288
|
+
private sendWithContext;
|
|
1289
|
+
/** The last line's host context (call rules, browser context): every reflex call runs under it, not only the user's line. */
|
|
1290
|
+
private lastContext?;
|
|
1278
1291
|
/** The turn SAID it is on it ("got it — checking…") but called no tool and nothing is running: a promise nothing
|
|
1279
1292
|
* keeps — the user waits on silence (live Para #798: a follow-up while the reflex believed a finished task still ran). */
|
|
1280
1293
|
private get hollowPromise();
|
|
@@ -1341,7 +1354,7 @@ declare class DuplexAgent {
|
|
|
1341
1354
|
id?: string;
|
|
1342
1355
|
replaces?: string;
|
|
1343
1356
|
followUp?: string | Record<string, string>;
|
|
1344
|
-
}): Promise<RunResult>;
|
|
1357
|
+
} & HostInfo): Promise<RunResult>;
|
|
1345
1358
|
/** Host-named user turns (send opts.id), so a later send can replace one. Bounded; a replace that arrives
|
|
1346
1359
|
* BEFORE its target (the host's dispatch hops can reorder) leaves a pre-superseded record behind. */
|
|
1347
1360
|
private userTurns;
|
package/dist/index.js
CHANGED
|
@@ -10177,6 +10177,22 @@ function nameScore(want, have) {
|
|
|
10177
10177
|
var RESERVED_EVENT_MARKER = /\[task\b[^\]\n]*\b(?:completed|failed|progress|asks)\b/i;
|
|
10178
10178
|
var RESERVED_EVENT_OPENER = /\[\s*task\b/i;
|
|
10179
10179
|
var STAGE_DIRECTION_RE = /^\(\s*(?:(?:waiting|checking|searching|thinking|processing|loading|working|fetching|looking)\b[^)]*|[^)]*(?:\.\.\.|…)\s*)\)$/i;
|
|
10180
|
+
function hostContextBlock(context) {
|
|
10181
|
+
return `<host_context>
|
|
10182
|
+
${context.trim()}
|
|
10183
|
+
</host_context>
|
|
10184
|
+
(Above: context and instructions from the app \u2014 NOT the user's words. Never read it aloud, quote it, or attribute it to the user. The user's line follows.)
|
|
10185
|
+
|
|
10186
|
+
`;
|
|
10187
|
+
}
|
|
10188
|
+
function hostNoteBlock(note) {
|
|
10189
|
+
return `<host_note>
|
|
10190
|
+
${note.trim()}
|
|
10191
|
+
</host_note>
|
|
10192
|
+
(Above: background from the app \u2014 NOT the user's words on this line. Never read it aloud as theirs. The user's line follows.)
|
|
10193
|
+
|
|
10194
|
+
`;
|
|
10195
|
+
}
|
|
10180
10196
|
function nowLine() {
|
|
10181
10197
|
return `Current date and time: ${(/* @__PURE__ */ new Date()).toLocaleString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "short" })}`;
|
|
10182
10198
|
}
|
|
@@ -10670,9 +10686,9 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10670
10686
|
/** One reflex model call for a turn; a turn rejected for load (rateLimited) that has not spoken or dispatched yet is rolled
|
|
10671
10687
|
* back and re-run once on `reflexFallbackModel` — a re-prompt of the same model (the dead-air nudge) only hits the
|
|
10672
10688
|
* same limit and ends in "could you say that again?". */
|
|
10673
|
-
async reflexSend(content) {
|
|
10674
|
-
const base = this.voice.transcript.length;
|
|
10675
|
-
const res = await this.
|
|
10689
|
+
async reflexSend(content, note) {
|
|
10690
|
+
const base = this.voice.transcript.length, context = this.lastContext;
|
|
10691
|
+
const res = await this.sendWithContext(content, context, note);
|
|
10676
10692
|
const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
|
|
10677
10693
|
if (res.finishReason !== "error" || !rateLimited(res.error) || !fb || fb === model || fb.includes("/") && model.includes("/") && fb.split("/")[0] !== model.split("/")[0] || this.turnDispatched || this.turnSpoken.trim()) return res;
|
|
10678
10694
|
log24.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
|
|
@@ -10680,11 +10696,27 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10680
10696
|
this.voice.transcript.length = base;
|
|
10681
10697
|
this.voice.options.model = fb;
|
|
10682
10698
|
try {
|
|
10683
|
-
return await this.
|
|
10699
|
+
return await this.sendWithContext(content, context, note);
|
|
10684
10700
|
} finally {
|
|
10685
10701
|
this.voice.options.model = model;
|
|
10686
10702
|
}
|
|
10687
10703
|
}
|
|
10704
|
+
/** The host's per-line context (call rules, browser context, earlier conversation) rides THIS call only, fenced as the app's —
|
|
10705
|
+
* inside the user turn it read as the user's own words (live #938: "repeat everything I said" recited the call rules). The
|
|
10706
|
+
* stored turn keeps only what the user said, so later turns, briefs and the saved session never carry it. A block, not a
|
|
10707
|
+
* mid-transcript system message: the Anthropic/Google adapters keep only the leading system message. */
|
|
10708
|
+
async sendWithContext(content, context, note) {
|
|
10709
|
+
if (typeof content !== "string" || !context?.trim() && !note?.trim()) return this.voice.send(content);
|
|
10710
|
+
const stored = (note?.trim() ? hostNoteBlock(note) : "") + content, base = this.voice.transcript.length;
|
|
10711
|
+
try {
|
|
10712
|
+
return await this.voice.send((context?.trim() ? hostContextBlock(context) : "") + stored);
|
|
10713
|
+
} finally {
|
|
10714
|
+
const m = this.voice.transcript.slice(base).find((x) => x.role === "user");
|
|
10715
|
+
if (m) m.content = stored;
|
|
10716
|
+
}
|
|
10717
|
+
}
|
|
10718
|
+
/** The last line's host context (call rules, browser context): every reflex call runs under it, not only the user's line. */
|
|
10719
|
+
lastContext;
|
|
10688
10720
|
/** The turn SAID it is on it ("got it — checking…") but called no tool and nothing is running: a promise nothing
|
|
10689
10721
|
* keeps — the user waits on silence (live Para #798: a follow-up while the reflex believed a finished task still ran). */
|
|
10690
10722
|
get hollowPromise() {
|
|
@@ -10706,7 +10738,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10706
10738
|
this.stepMarks = [];
|
|
10707
10739
|
this.dropJudge();
|
|
10708
10740
|
try {
|
|
10709
|
-
await this.
|
|
10741
|
+
await this.sendWithContext(reminder, this.lastContext);
|
|
10710
10742
|
} catch (e) {
|
|
10711
10743
|
log24.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
10712
10744
|
} finally {
|
|
@@ -10849,7 +10881,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10849
10881
|
if (this.dispatchAt >= 0) this.dispatchAt = 0;
|
|
10850
10882
|
this.nudging = true;
|
|
10851
10883
|
try {
|
|
10852
|
-
await this.
|
|
10884
|
+
await this.sendWithContext(reminder, this.lastContext);
|
|
10853
10885
|
} catch (e) {
|
|
10854
10886
|
log24.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
10855
10887
|
} finally {
|
|
@@ -10888,6 +10920,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10888
10920
|
* second Act). The host gets `turn_replace` {id, replaces, rolledBack, followed?}: only a rollback drops the replaced
|
|
10889
10921
|
* line; `followed` = the kept turn whose follow-up ran (absent: the merged text ran). */
|
|
10890
10922
|
send(content, opts) {
|
|
10923
|
+
const ctx = opts && (opts.context !== void 0 || opts.note !== void 0) ? { context: opts.context, note: opts.note } : void 0;
|
|
10891
10924
|
this.clearHoldResume();
|
|
10892
10925
|
this.holdResumed = false;
|
|
10893
10926
|
if (opts?.replaces) this.replaceTurn(opts.replaces);
|
|
@@ -10913,7 +10946,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10913
10946
|
if (intent === "resume" && !reflexCut) return this.resumeParked();
|
|
10914
10947
|
if (intent === "aside" || intent === "resume") {
|
|
10915
10948
|
const told = intent === "aside" ? [...this.parkedRedeliver] : [];
|
|
10916
|
-
return this.sendTurn(intent === "aside" ? content + this.asideNote(told) : content, true, rec, ext).then(async (r) => {
|
|
10949
|
+
return this.sendTurn(intent === "aside" ? content + this.asideNote(told) : content, true, rec, ext, ctx).then(async (r) => {
|
|
10917
10950
|
if (r.finishReason === "aborted" || this.heldThisTurn) return r;
|
|
10918
10951
|
const missed = unsaidClauses(told.map((p) => p.text), this.turnSpoken, this.options.decider);
|
|
10919
10952
|
const turn = this.turn;
|
|
@@ -10928,10 +10961,10 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10928
10961
|
return r;
|
|
10929
10962
|
});
|
|
10930
10963
|
}
|
|
10931
|
-
return this.sendTurn(content, false, rec, ext);
|
|
10964
|
+
return this.sendTurn(content, false, rec, ext, ctx);
|
|
10932
10965
|
});
|
|
10933
10966
|
}
|
|
10934
|
-
return this.sendTurn(typeof content === "string" ? content + this.acceptNote(content) : content, false, rec, ext);
|
|
10967
|
+
return this.sendTurn(typeof content === "string" ? content + this.acceptNote(content) : content, false, rec, ext, ctx);
|
|
10935
10968
|
}
|
|
10936
10969
|
/** Host-named user turns (send opts.id), so a later send can replace one. Bounded; a replace that arrives
|
|
10937
10970
|
* BEFORE its target (the host's dispatch hops can reorder) leaves a pre-superseded record behind. */
|
|
@@ -10999,12 +11032,17 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
10999
11032
|
return { text: "", steps: 0, finishReason: "stop", messages: [] };
|
|
11000
11033
|
});
|
|
11001
11034
|
}
|
|
11002
|
-
sendTurn(content, keepParked = false, rec, ext) {
|
|
11035
|
+
sendTurn(content, keepParked = false, rec, ext, host) {
|
|
11003
11036
|
const spec = this.spec;
|
|
11004
11037
|
if (spec?.state === "pending") {
|
|
11005
11038
|
if (typeof content === "string" && speculationConfirms(spec.text, content)) {
|
|
11006
11039
|
spec.state = "confirmed";
|
|
11007
11040
|
spec.finalText = content;
|
|
11041
|
+
if (host?.context !== void 0) this.lastContext = host.context;
|
|
11042
|
+
if (host?.note?.trim()) {
|
|
11043
|
+
const m = [...this.voice.transcript].reverse().find((x) => x.role === "user" && x.content === spec.text);
|
|
11044
|
+
if (m) m.content = hostNoteBlock(host.note) + spec.text;
|
|
11045
|
+
}
|
|
11008
11046
|
if (rec) {
|
|
11009
11047
|
spec.rec = rec;
|
|
11010
11048
|
rec.ctl.signal.addEventListener("abort", () => spec.ctl.abort(), { once: true });
|
|
@@ -11051,8 +11089,9 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11051
11089
|
}
|
|
11052
11090
|
if (typeof content === "string") content += spellingNote(content);
|
|
11053
11091
|
let res;
|
|
11092
|
+
if (host?.context !== void 0) this.lastContext = host.context;
|
|
11054
11093
|
try {
|
|
11055
|
-
res = await this.reflexSend(content);
|
|
11094
|
+
res = await this.reflexSend(content, host?.note);
|
|
11056
11095
|
} finally {
|
|
11057
11096
|
if (signal) {
|
|
11058
11097
|
this.voice.options.signal = ext;
|
|
@@ -11101,7 +11140,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
11101
11140
|
this.voice.options.signal = spec.ctl.signal;
|
|
11102
11141
|
let res;
|
|
11103
11142
|
try {
|
|
11104
|
-
res = await this.
|
|
11143
|
+
res = await this.sendWithContext(spec.text, this.lastContext);
|
|
11105
11144
|
} catch (e) {
|
|
11106
11145
|
log24.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
|
|
11107
11146
|
} finally {
|
|
@@ -11380,7 +11419,7 @@ Update: ${brief}`;
|
|
|
11380
11419
|
return this.voice.transcript.filter((m) => (m.role === "user" || m.role === "assistant") && contentText(m.content).trim()).slice(-turns).map((m) => `${m.role}: ${contentText(m.content)}`).join("\n");
|
|
11381
11420
|
}
|
|
11382
11421
|
buildBrief(brief, tier = "act", deliver = true) {
|
|
11383
|
-
const recent = this.recentExcerpt();
|
|
11422
|
+
const recent = this.recentExcerpt(), host = this.lastContext?.trim();
|
|
11384
11423
|
const verify = tier === "act" ? "\n\nBefore reporting done: re-read what you changed and check it against EVERY requirement above \u2014 fix any gap first. Your report is trusted without review. A command exiting 0 or echoing its input means it was ACCEPTED, not that it worked: before you say a web address was opened or reached, confirm it exists (its host resolves in DNS and answers an HTTP request); if it does not, report that it does not exist instead of success, and name the address you tried." : "";
|
|
11385
11424
|
const deliverContract = deliver ? '\n\n## DELIVER (spoken delivery)\nYou are reporting back to a user who is LISTENING. Stream your work normally \u2014 your prose is the written work record and detail, and is NOT spoken. Wrap anything the user should HEAR in <spoken>\u2026</spoken> tags. LEAD WITH the actual content they asked for: if they asked for a specific piece of content \u2014 a value, a name, the actual lines, the writing itself \u2014 that content goes INSIDE the <spoken> tags, not a remark about it. Your FIRST <spoken> segment is substantive \u2014 never a greeting or an acknowledgement (the front-end has already acked; do not double-ack). Keep spoken text concise and natural for the ear: short sentences, no markdown. Never read a file path, URL, command or code aloud, nor spell one out ("slash \u2026 slash \u2026") \u2014 say it the way a person would, by the name of the folder, project, file or site; the exact text stays in your written prose. ' + (this.options.spokenBudgetChars ? `Speak a SHORT summary only: at most three short sentences with the one thing that matters most, then ONE short offer that names what else you have, as a question they can answer in a word \u2014 under ${this.options.spokenBudgetChars} characters in all, the offer included. Everything else stays in your written prose (it is kept and they can ask for it); spoken text past that budget is cut and held until they ask. ` : "") + `NEVER enumerate in speech \u2014 no numbered or bulleted lists ("One. \u2026 Two. \u2026" is robotic). Deliver multiple items as flowing conversation with brief connective phrasing ("here's one\u2026", "and another\u2026", "oh, and\u2026"), pausing between items with sentence breaks, not numbers.` + (this.options.emotionTags ? " Inside <spoken>, you may prefix a sentence with an inline [emotion] tag (e.g. [excited], [curious]) to color how it is voiced \u2014 only when it genuinely fits, and vary it; [laughter] gives a natural laugh." : "") : "";
|
|
11386
11425
|
return `${nowLine()}.
|
|
@@ -11388,7 +11427,10 @@ Update: ${brief}`;
|
|
|
11388
11427
|
` + (recent ? `${brief}
|
|
11389
11428
|
|
|
11390
11429
|
## Recent conversation (for context)
|
|
11391
|
-
${recent}` : brief) +
|
|
11430
|
+
${recent}` : brief) + (host ? `
|
|
11431
|
+
|
|
11432
|
+
## The app's context for this line (not the user's words)
|
|
11433
|
+
${host}` : "") + verify + deliverContract;
|
|
11392
11434
|
}
|
|
11393
11435
|
/** Spawn a detached worker for task `id`; its settlement notifies + enqueues the re-voice turn. */
|
|
11394
11436
|
spawnWorker(id, label, briefText, tier, brief, followUp) {
|