@livx.cc/agentx 0.99.109 → 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 CHANGED
@@ -10515,6 +10515,14 @@ ${context.trim()}
10515
10515
  </host_context>
10516
10516
  (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.)
10517
10517
 
10518
+ `;
10519
+ }
10520
+ function hostNoteBlock(note) {
10521
+ return `<host_note>
10522
+ ${note.trim()}
10523
+ </host_note>
10524
+ (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.)
10525
+
10518
10526
  `;
10519
10527
  }
10520
10528
  function nowLine() {
@@ -11010,9 +11018,9 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11010
11018
  /** One reflex model call for a turn; a turn rejected for load (rateLimited) that has not spoken or dispatched yet is rolled
11011
11019
  * back and re-run once on `reflexFallbackModel` — a re-prompt of the same model (the dead-air nudge) only hits the
11012
11020
  * same limit and ends in "could you say that again?". */
11013
- async reflexSend(content, context) {
11014
- const base = this.voice.transcript.length;
11015
- const res = await this.sendWithContext(content, context);
11021
+ async reflexSend(content, note) {
11022
+ const base = this.voice.transcript.length, context = this.lastContext;
11023
+ const res = await this.sendWithContext(content, context, note);
11016
11024
  const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
11017
11025
  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;
11018
11026
  log23.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
@@ -11020,7 +11028,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11020
11028
  this.voice.transcript.length = base;
11021
11029
  this.voice.options.model = fb;
11022
11030
  try {
11023
- return await this.sendWithContext(content, context);
11031
+ return await this.sendWithContext(content, context, note);
11024
11032
  } finally {
11025
11033
  this.voice.options.model = model;
11026
11034
  }
@@ -11029,16 +11037,18 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11029
11037
  * inside the user turn it read as the user's own words (live #938: "repeat everything I said" recited the call rules). The
11030
11038
  * stored turn keeps only what the user said, so later turns, briefs and the saved session never carry it. A block, not a
11031
11039
  * mid-transcript system message: the Anthropic/Google adapters keep only the leading system message. */
11032
- async sendWithContext(content, context) {
11033
- if (!context?.trim() || typeof content !== "string") return this.voice.send(content);
11034
- const base = this.voice.transcript.length;
11040
+ async sendWithContext(content, context, note) {
11041
+ if (typeof content !== "string" || !context?.trim() && !note?.trim()) return this.voice.send(content);
11042
+ const stored = (note?.trim() ? hostNoteBlock(note) : "") + content, base = this.voice.transcript.length;
11035
11043
  try {
11036
- return await this.voice.send(hostContextBlock(context) + content);
11044
+ return await this.voice.send((context?.trim() ? hostContextBlock(context) : "") + stored);
11037
11045
  } finally {
11038
11046
  const m = this.voice.transcript.slice(base).find((x) => x.role === "user");
11039
- if (m) m.content = content;
11047
+ if (m) m.content = stored;
11040
11048
  }
11041
11049
  }
11050
+ /** The last line's host context (call rules, browser context): every reflex call runs under it, not only the user's line. */
11051
+ lastContext;
11042
11052
  /** The turn SAID it is on it ("got it — checking…") but called no tool and nothing is running: a promise nothing
11043
11053
  * keeps — the user waits on silence (live Para #798: a follow-up while the reflex believed a finished task still ran). */
11044
11054
  get hollowPromise() {
@@ -11060,7 +11070,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11060
11070
  this.stepMarks = [];
11061
11071
  this.dropJudge();
11062
11072
  try {
11063
- await this.voice.send(reminder);
11073
+ await this.sendWithContext(reminder, this.lastContext);
11064
11074
  } catch (e) {
11065
11075
  log23.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
11066
11076
  } finally {
@@ -11203,7 +11213,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11203
11213
  if (this.dispatchAt >= 0) this.dispatchAt = 0;
11204
11214
  this.nudging = true;
11205
11215
  try {
11206
- await this.voice.send(reminder);
11216
+ await this.sendWithContext(reminder, this.lastContext);
11207
11217
  } catch (e) {
11208
11218
  log23.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
11209
11219
  } finally {
@@ -11242,7 +11252,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11242
11252
  * second Act). The host gets `turn_replace` {id, replaces, rolledBack, followed?}: only a rollback drops the replaced
11243
11253
  * line; `followed` = the kept turn whose follow-up ran (absent: the merged text ran). */
11244
11254
  send(content, opts) {
11245
- const ctx = opts?.context;
11255
+ const ctx = opts && (opts.context !== void 0 || opts.note !== void 0) ? { context: opts.context, note: opts.note } : void 0;
11246
11256
  this.clearHoldResume();
11247
11257
  this.holdResumed = false;
11248
11258
  if (opts?.replaces) this.replaceTurn(opts.replaces);
@@ -11354,12 +11364,17 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11354
11364
  return { text: "", steps: 0, finishReason: "stop", messages: [] };
11355
11365
  });
11356
11366
  }
11357
- sendTurn(content, keepParked = false, rec, ext, context) {
11367
+ sendTurn(content, keepParked = false, rec, ext, host) {
11358
11368
  const spec = this.spec;
11359
11369
  if (spec?.state === "pending") {
11360
11370
  if (typeof content === "string" && speculationConfirms(spec.text, content)) {
11361
11371
  spec.state = "confirmed";
11362
11372
  spec.finalText = content;
11373
+ if (host?.context !== void 0) this.lastContext = host.context;
11374
+ if (host?.note?.trim()) {
11375
+ const m = [...this.voice.transcript].reverse().find((x) => x.role === "user" && x.content === spec.text);
11376
+ if (m) m.content = hostNoteBlock(host.note) + spec.text;
11377
+ }
11363
11378
  if (rec) {
11364
11379
  spec.rec = rec;
11365
11380
  rec.ctl.signal.addEventListener("abort", () => spec.ctl.abort(), { once: true });
@@ -11406,8 +11421,9 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11406
11421
  }
11407
11422
  if (typeof content === "string") content += spellingNote(content);
11408
11423
  let res;
11424
+ if (host?.context !== void 0) this.lastContext = host.context;
11409
11425
  try {
11410
- res = await this.reflexSend(content, context);
11426
+ res = await this.reflexSend(content, host?.note);
11411
11427
  } finally {
11412
11428
  if (signal) {
11413
11429
  this.voice.options.signal = ext;
@@ -11456,7 +11472,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11456
11472
  this.voice.options.signal = spec.ctl.signal;
11457
11473
  let res;
11458
11474
  try {
11459
- res = await this.voice.send(spec.text);
11475
+ res = await this.sendWithContext(spec.text, this.lastContext);
11460
11476
  } catch (e) {
11461
11477
  log23.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
11462
11478
  } finally {
@@ -11735,7 +11751,7 @@ Update: ${brief}`;
11735
11751
  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");
11736
11752
  }
11737
11753
  buildBrief(brief, tier = "act", deliver = true) {
11738
- const recent = this.recentExcerpt();
11754
+ const recent = this.recentExcerpt(), host = this.lastContext?.trim();
11739
11755
  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." : "";
11740
11756
  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." : "") : "";
11741
11757
  return `${nowLine()}.
@@ -11743,7 +11759,10 @@ Update: ${brief}`;
11743
11759
  ` + (recent ? `${brief}
11744
11760
 
11745
11761
  ## Recent conversation (for context)
11746
- ${recent}` : brief) + verify + deliverContract;
11762
+ ${recent}` : brief) + (host ? `
11763
+
11764
+ ## The app's context for this line (not the user's words)
11765
+ ${host}` : "") + verify + deliverContract;
11747
11766
  }
11748
11767
  /** Spawn a detached worker for task `id`; its settlement notifies + enqueues the re-voice turn. */
11749
11768
  spawnWorker(id, label, briefText, tier, brief, followUp) {
@@ -18812,6 +18831,8 @@ Flags:
18812
18831
  --timeout <sec> wall-clock kill-switch (default 120)
18813
18832
  --input-format stream-json with --duplex|--voice + --output-format stream-json: headless duplex for a host app
18814
18833
  (NDJSON turns/barge-ins on stdin, reflex text + worker speech + task events on stdout)
18834
+ a user line's {context, note} = host info fenced as the app's (context: per-line rules, kept for
18835
+ results/reminders; note: persistent background) \u2014 never stored as the user's words
18815
18836
  with -p: the first {"type":"user","text"} line is the prompt; later ones STEER the running turn
18816
18837
  (unconsumed steers come back as the result's \`unsteered\`)
18817
18838
  --keep-alive with -p --input-format stream-json: stay up after a turn \u2014 each {"type":"user"} line while idle
@@ -22520,7 +22541,7 @@ async function duplexServe(args, ai, cfg, cwd) {
22520
22541
  const done2 = () => {
22521
22542
  if (seq === scTurn) sc?.onAgentState("idle");
22522
22543
  };
22523
- 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, context: typeof m.context === "string" ? m.context : void 0 })).then(({ res, error }) => {
22544
+ 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, context: typeof m.context === "string" ? m.context : void 0, note: typeof m.note === "string" ? m.note : void 0 })).then(({ res, error }) => {
22524
22545
  const why = (res?.finishReason ?? "error") === "error" ? duplexError(res?.error ?? error, dx.options.reflexModel) : "";
22525
22546
  done2();
22526
22547
  out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });