@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 CHANGED
@@ -10509,6 +10509,22 @@ function nameScore(want, have) {
10509
10509
  var RESERVED_EVENT_MARKER = /\[task\b[^\]\n]*\b(?:completed|failed|progress|asks)\b/i;
10510
10510
  var RESERVED_EVENT_OPENER = /\[\s*task\b/i;
10511
10511
  var STAGE_DIRECTION_RE = /^\(\s*(?:(?:waiting|checking|searching|thinking|processing|loading|working|fetching|looking)\b[^)]*|[^)]*(?:\.\.\.|…)\s*)\)$/i;
10512
+ function hostContextBlock(context) {
10513
+ return `<host_context>
10514
+ ${context.trim()}
10515
+ </host_context>
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
+
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
+
10526
+ `;
10527
+ }
10512
10528
  function nowLine() {
10513
10529
  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" })}`;
10514
10530
  }
@@ -11002,9 +11018,9 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11002
11018
  /** One reflex model call for a turn; a turn rejected for load (rateLimited) that has not spoken or dispatched yet is rolled
11003
11019
  * back and re-run once on `reflexFallbackModel` — a re-prompt of the same model (the dead-air nudge) only hits the
11004
11020
  * same limit and ends in "could you say that again?". */
11005
- async reflexSend(content) {
11006
- const base = this.voice.transcript.length;
11007
- const res = await this.voice.send(content);
11021
+ async reflexSend(content, note) {
11022
+ const base = this.voice.transcript.length, context = this.lastContext;
11023
+ const res = await this.sendWithContext(content, context, note);
11008
11024
  const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
11009
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;
11010
11026
  log23.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
@@ -11012,11 +11028,27 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11012
11028
  this.voice.transcript.length = base;
11013
11029
  this.voice.options.model = fb;
11014
11030
  try {
11015
- return await this.voice.send(content);
11031
+ return await this.sendWithContext(content, context, note);
11016
11032
  } finally {
11017
11033
  this.voice.options.model = model;
11018
11034
  }
11019
11035
  }
11036
+ /** The host's per-line context (call rules, browser context, earlier conversation) rides THIS call only, fenced as the app's —
11037
+ * inside the user turn it read as the user's own words (live #938: "repeat everything I said" recited the call rules). The
11038
+ * stored turn keeps only what the user said, so later turns, briefs and the saved session never carry it. A block, not a
11039
+ * mid-transcript system message: the Anthropic/Google adapters keep only the leading system message. */
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;
11043
+ try {
11044
+ return await this.voice.send((context?.trim() ? hostContextBlock(context) : "") + stored);
11045
+ } finally {
11046
+ const m = this.voice.transcript.slice(base).find((x) => x.role === "user");
11047
+ if (m) m.content = stored;
11048
+ }
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;
11020
11052
  /** The turn SAID it is on it ("got it — checking…") but called no tool and nothing is running: a promise nothing
11021
11053
  * keeps — the user waits on silence (live Para #798: a follow-up while the reflex believed a finished task still ran). */
11022
11054
  get hollowPromise() {
@@ -11038,7 +11070,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11038
11070
  this.stepMarks = [];
11039
11071
  this.dropJudge();
11040
11072
  try {
11041
- await this.voice.send(reminder);
11073
+ await this.sendWithContext(reminder, this.lastContext);
11042
11074
  } catch (e) {
11043
11075
  log23.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
11044
11076
  } finally {
@@ -11181,7 +11213,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11181
11213
  if (this.dispatchAt >= 0) this.dispatchAt = 0;
11182
11214
  this.nudging = true;
11183
11215
  try {
11184
- await this.voice.send(reminder);
11216
+ await this.sendWithContext(reminder, this.lastContext);
11185
11217
  } catch (e) {
11186
11218
  log23.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
11187
11219
  } finally {
@@ -11220,6 +11252,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11220
11252
  * second Act). The host gets `turn_replace` {id, replaces, rolledBack, followed?}: only a rollback drops the replaced
11221
11253
  * line; `followed` = the kept turn whose follow-up ran (absent: the merged text ran). */
11222
11254
  send(content, opts) {
11255
+ const ctx = opts && (opts.context !== void 0 || opts.note !== void 0) ? { context: opts.context, note: opts.note } : void 0;
11223
11256
  this.clearHoldResume();
11224
11257
  this.holdResumed = false;
11225
11258
  if (opts?.replaces) this.replaceTurn(opts.replaces);
@@ -11245,7 +11278,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11245
11278
  if (intent === "resume" && !reflexCut) return this.resumeParked();
11246
11279
  if (intent === "aside" || intent === "resume") {
11247
11280
  const told = intent === "aside" ? [...this.parkedRedeliver] : [];
11248
- return this.sendTurn(intent === "aside" ? content + this.asideNote(told) : content, true, rec, ext).then(async (r) => {
11281
+ return this.sendTurn(intent === "aside" ? content + this.asideNote(told) : content, true, rec, ext, ctx).then(async (r) => {
11249
11282
  if (r.finishReason === "aborted" || this.heldThisTurn) return r;
11250
11283
  const missed = unsaidClauses(told.map((p) => p.text), this.turnSpoken, this.options.decider);
11251
11284
  const turn = this.turn;
@@ -11260,10 +11293,10 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11260
11293
  return r;
11261
11294
  });
11262
11295
  }
11263
- return this.sendTurn(content, false, rec, ext);
11296
+ return this.sendTurn(content, false, rec, ext, ctx);
11264
11297
  });
11265
11298
  }
11266
- return this.sendTurn(typeof content === "string" ? content + this.acceptNote(content) : content, false, rec, ext);
11299
+ return this.sendTurn(typeof content === "string" ? content + this.acceptNote(content) : content, false, rec, ext, ctx);
11267
11300
  }
11268
11301
  /** Host-named user turns (send opts.id), so a later send can replace one. Bounded; a replace that arrives
11269
11302
  * BEFORE its target (the host's dispatch hops can reorder) leaves a pre-superseded record behind. */
@@ -11331,12 +11364,17 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11331
11364
  return { text: "", steps: 0, finishReason: "stop", messages: [] };
11332
11365
  });
11333
11366
  }
11334
- sendTurn(content, keepParked = false, rec, ext) {
11367
+ sendTurn(content, keepParked = false, rec, ext, host) {
11335
11368
  const spec = this.spec;
11336
11369
  if (spec?.state === "pending") {
11337
11370
  if (typeof content === "string" && speculationConfirms(spec.text, content)) {
11338
11371
  spec.state = "confirmed";
11339
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
+ }
11340
11378
  if (rec) {
11341
11379
  spec.rec = rec;
11342
11380
  rec.ctl.signal.addEventListener("abort", () => spec.ctl.abort(), { once: true });
@@ -11383,8 +11421,9 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11383
11421
  }
11384
11422
  if (typeof content === "string") content += spellingNote(content);
11385
11423
  let res;
11424
+ if (host?.context !== void 0) this.lastContext = host.context;
11386
11425
  try {
11387
- res = await this.reflexSend(content);
11426
+ res = await this.reflexSend(content, host?.note);
11388
11427
  } finally {
11389
11428
  if (signal) {
11390
11429
  this.voice.options.signal = ext;
@@ -11433,7 +11472,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11433
11472
  this.voice.options.signal = spec.ctl.signal;
11434
11473
  let res;
11435
11474
  try {
11436
- res = await this.voice.send(spec.text);
11475
+ res = await this.sendWithContext(spec.text, this.lastContext);
11437
11476
  } catch (e) {
11438
11477
  log23.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
11439
11478
  } finally {
@@ -11712,7 +11751,7 @@ Update: ${brief}`;
11712
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");
11713
11752
  }
11714
11753
  buildBrief(brief, tier = "act", deliver = true) {
11715
- const recent = this.recentExcerpt();
11754
+ const recent = this.recentExcerpt(), host = this.lastContext?.trim();
11716
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." : "";
11717
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." : "") : "";
11718
11757
  return `${nowLine()}.
@@ -11720,7 +11759,10 @@ Update: ${brief}`;
11720
11759
  ` + (recent ? `${brief}
11721
11760
 
11722
11761
  ## Recent conversation (for context)
11723
- ${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;
11724
11766
  }
11725
11767
  /** Spawn a detached worker for task `id`; its settlement notifies + enqueues the re-voice turn. */
11726
11768
  spawnWorker(id, label, briefText, tier, brief, followUp) {
@@ -18789,6 +18831,8 @@ Flags:
18789
18831
  --timeout <sec> wall-clock kill-switch (default 120)
18790
18832
  --input-format stream-json with --duplex|--voice + --output-format stream-json: headless duplex for a host app
18791
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
18792
18836
  with -p: the first {"type":"user","text"} line is the prompt; later ones STEER the running turn
18793
18837
  (unconsumed steers come back as the result's \`unsteered\`)
18794
18838
  --keep-alive with -p --input-format stream-json: stay up after a turn \u2014 each {"type":"user"} line while idle
@@ -22497,7 +22541,7 @@ async function duplexServe(args, ai, cfg, cwd) {
22497
22541
  const done2 = () => {
22498
22542
  if (seq === scTurn) sc?.onAgentState("idle");
22499
22543
  };
22500
- 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 })).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 }) => {
22501
22545
  const why = (res?.finishReason ?? "error") === "error" ? duplexError(res?.error ?? error, dx.options.reflexModel) : "";
22502
22546
  done2();
22503
22547
  out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });