@livx.cc/agentx 0.99.109 → 0.99.111

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
@@ -1,5 +1,5 @@
1
- import { a as AgentOptions, H as Hooks, p as RunResult, f as Decider, A as Agent, i as PermissionPolicy, L as LineKind, C as ChannelMode, q as SecondChannelOptions, S as SecondChannel } from './secondChannel-3IPP3Qgh.js';
2
- export { b as ChatFragment, c as Classification, d as Classifier, e as CompactResult, D as DEFAULT_MUTATING, g as Decision, M as ModelRouter, h as ModelRouterOptions, P as PermissionOptions, j as PermissionRule, k as PreToolUseDecision, R as ReasoningEffort, l as RecordingHooks, m as RecordingLifecycle, n as RouteDecision, o as RouterState, T as Tier, r as ToolUse, s as ToolUseMeta, t as claudeCodeDisallowedTools, u as composeHooks, v as estimateTokens, w as jevAvailable, x as jevClassifier, y as pickTier, z as planMode, B as reasoningToChatFragment } from './secondChannel-3IPP3Qgh.js';
1
+ import { a as AgentOptions, H as Hooks, p as RunResult, f as Decider, A as Agent, i as PermissionPolicy, L as LineKind, C as ChannelMode, q as SecondChannelOptions, S as SecondChannel } from './secondChannel-CJMY5EbL.js';
2
+ export { b as ChatFragment, c as Classification, d as Classifier, e as CompactResult, D as DEFAULT_MUTATING, g as Decision, M as ModelRouter, h as ModelRouterOptions, P as PermissionOptions, j as PermissionRule, k as PreToolUseDecision, R as ReasoningEffort, l as RecordingHooks, m as RecordingLifecycle, n as RouteDecision, o as RouterState, T as Tier, r as ToolUse, s as ToolUseMeta, t as claudeCodeDisallowedTools, u as composeHooks, v as estimateTokens, w as jevAvailable, x as jevClassifier, y as pickTier, z as planMode, B as reasoningToChatFragment } from './secondChannel-CJMY5EbL.js';
3
3
  export { MODEL_ALIASES, ModelSwitch, ResolveModelSwitchOpts, latestClaude, modelShortLabel, resolveModelAlias, resolveModelSwitch } from './models.js';
4
4
  import { IFilesystem, FileMetadata } from '@livx.cc/wcli/core';
5
5
  export { CommandExecutor, FileMetadata, IFilesystem, IndexedDbFilesystem, MemFilesystem, registerHeadlessCommands } from '@livx.cc/wcli/core';
@@ -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`
@@ -1280,6 +1286,8 @@ declare class DuplexAgent {
1280
1286
  * stored turn keeps only what the user said, so later turns, briefs and the saved session never carry it. A block, not a
1281
1287
  * mid-transcript system message: the Anthropic/Google adapters keep only the leading system message. */
1282
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?;
1283
1291
  /** The turn SAID it is on it ("got it — checking…") but called no tool and nothing is running: a promise nothing
1284
1292
  * keeps — the user waits on silence (live Para #798: a follow-up while the reflex believed a finished task still ran). */
1285
1293
  private get hollowPromise();
@@ -1346,8 +1354,7 @@ declare class DuplexAgent {
1346
1354
  id?: string;
1347
1355
  replaces?: string;
1348
1356
  followUp?: string | Record<string, string>;
1349
- context?: string;
1350
- }): Promise<RunResult>;
1357
+ } & HostInfo): Promise<RunResult>;
1351
1358
  /** Host-named user turns (send opts.id), so a later send can replace one. Bounded; a replace that arrives
1352
1359
  * BEFORE its target (the host's dispatch hops can reorder) leaves a pre-superseded record behind. */
1353
1360
  private userTurns;
package/dist/index.js CHANGED
@@ -4913,6 +4913,39 @@ var Agent = class _Agent {
4913
4913
  * steers over its own channel (claude-code's warm InputQueue via `providerOptions.steer`). Non-text
4914
4914
  * injections (images) stay queued for the step-boundary `drainInjections()` re-open — the text-only
4915
4915
  * steer channel can't carry them. Consumed DURING chat(); no overlap with drainInjections() (post-call). */
4916
+ /** claude-code/*: the providerOptions every step adds (and prewarm, so the session it starts is the one step 1 reuses). */
4917
+ claudeCodePo() {
4918
+ const o = this.options;
4919
+ if (!o.model.startsWith("claude-code/")) return void 0;
4920
+ const po = { steer: () => this.drainPendingSteers() };
4921
+ const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name);
4922
+ if (o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
4923
+ this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
4924
+ po.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
4925
+ }
4926
+ const denyTools = claudeCodeDisallowedTools(o.permissions);
4927
+ if (denyTools.length) {
4928
+ const prior = o.providerOptions?.disallowedTools ?? [];
4929
+ po.disallowedTools = [.../* @__PURE__ */ new Set([...prior, ...denyTools])];
4930
+ }
4931
+ return po;
4932
+ }
4933
+ /** claude-code/* warm session: start it now (its process, MCP handshakes) so the first turn doesn't pay for it. Its
4934
+ * system prompt is fixed at the start, so it is prepared here (no task hint) — memoized, the turns reuse the same one.
4935
+ * No model call; the transcript gains only that system message. Other models: nothing to warm. */
4936
+ async prewarm() {
4937
+ const o = this.options;
4938
+ if (!o.model.startsWith("claude-code/") || !o.providerOptions?.claudeCodeSession) return;
4939
+ await this.ensureFs();
4940
+ const systemPrompt = await this.prepare(), startCtx = await this.fireSessionStart();
4941
+ const sys = { role: "system", content: systemPrompt + (startCtx ? "\n\n" + startCtx : "") };
4942
+ if (this.transcript[0]?.role === "system") this.transcript[0] = sys;
4943
+ else this.transcript.unshift(sys);
4944
+ const po = this.claudeCodePo(), frag = reasoningToChatFragment(o.model, o.reasoning);
4945
+ const r = await o.ai.chat({ model: o.model, messages: this.trimContext(), tools: [], stream: true, ...frag, ...o.promptCache ? { promptCache: true } : {}, providerOptions: { ...frag.providerOptions, ...o.providerOptions, ...po, prewarm: true } });
4946
+ for await (const _ of r) {
4947
+ }
4948
+ }
4916
4949
  drainPendingSteers() {
4917
4950
  if (!this.pendingInjections.length) return [];
4918
4951
  const texts = [];
@@ -5205,18 +5238,8 @@ var Agent = class _Agent {
5205
5238
  }
5206
5239
  };
5207
5240
  const cursorPo = isCursorWithTools ? { toolTimeoutMs: effectiveToolTimeoutMs, toolExecutor: hostExec } : void 0;
5208
- const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
5209
- const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name);
5210
- if (claudeSteerPo && o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
5211
- this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
5212
- claudeSteerPo.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
5213
- }
5241
+ const claudeSteerPo = this.claudeCodePo();
5214
5242
  if (this.ccServer) this.ccExec = hostExec;
5215
- const denyTools = o.model.startsWith("claude-code/") ? claudeCodeDisallowedTools(o.permissions) : [];
5216
- if (denyTools.length) {
5217
- const prior = o.providerOptions?.disallowedTools ?? [];
5218
- claudeSteerPo.disallowedTools = [.../* @__PURE__ */ new Set([...prior, ...denyTools])];
5219
- }
5220
5243
  const cursorDeny = o.model.startsWith("cursor/") ? o.permissions?.denyRules(true) ?? [] : [];
5221
5244
  if (cursorDeny.length)
5222
5245
  throw new Error(`cursor/* cannot enforce --disallowedTools (${cursorDeny.join(", ")}): its native tools bypass them. Refusing to run \u2014 use a claude-code/* or API model, or drop --disallowedTools.`);
@@ -10183,6 +10206,14 @@ ${context.trim()}
10183
10206
  </host_context>
10184
10207
  (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
10208
 
10209
+ `;
10210
+ }
10211
+ function hostNoteBlock(note) {
10212
+ return `<host_note>
10213
+ ${note.trim()}
10214
+ </host_note>
10215
+ (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.)
10216
+
10186
10217
  `;
10187
10218
  }
10188
10219
  function nowLine() {
@@ -10678,9 +10709,9 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10678
10709
  /** One reflex model call for a turn; a turn rejected for load (rateLimited) that has not spoken or dispatched yet is rolled
10679
10710
  * back and re-run once on `reflexFallbackModel` — a re-prompt of the same model (the dead-air nudge) only hits the
10680
10711
  * same limit and ends in "could you say that again?". */
10681
- async reflexSend(content, context) {
10682
- const base = this.voice.transcript.length;
10683
- const res = await this.sendWithContext(content, context);
10712
+ async reflexSend(content, note) {
10713
+ const base = this.voice.transcript.length, context = this.lastContext;
10714
+ const res = await this.sendWithContext(content, context, note);
10684
10715
  const fb = this.options.reflexFallbackModel, model = this.voice.options.model;
10685
10716
  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;
10686
10717
  log24.warn(`reflex ${model} rate-limited \u2014 re-running the turn on ${fb}`);
@@ -10688,7 +10719,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10688
10719
  this.voice.transcript.length = base;
10689
10720
  this.voice.options.model = fb;
10690
10721
  try {
10691
- return await this.sendWithContext(content, context);
10722
+ return await this.sendWithContext(content, context, note);
10692
10723
  } finally {
10693
10724
  this.voice.options.model = model;
10694
10725
  }
@@ -10697,16 +10728,18 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10697
10728
  * inside the user turn it read as the user's own words (live #938: "repeat everything I said" recited the call rules). The
10698
10729
  * stored turn keeps only what the user said, so later turns, briefs and the saved session never carry it. A block, not a
10699
10730
  * mid-transcript system message: the Anthropic/Google adapters keep only the leading system message. */
10700
- async sendWithContext(content, context) {
10701
- if (!context?.trim() || typeof content !== "string") return this.voice.send(content);
10702
- const base = this.voice.transcript.length;
10731
+ async sendWithContext(content, context, note) {
10732
+ if (typeof content !== "string" || !context?.trim() && !note?.trim()) return this.voice.send(content);
10733
+ const stored = (note?.trim() ? hostNoteBlock(note) : "") + content, base = this.voice.transcript.length;
10703
10734
  try {
10704
- return await this.voice.send(hostContextBlock(context) + content);
10735
+ return await this.voice.send((context?.trim() ? hostContextBlock(context) : "") + stored);
10705
10736
  } finally {
10706
10737
  const m = this.voice.transcript.slice(base).find((x) => x.role === "user");
10707
- if (m) m.content = content;
10738
+ if (m) m.content = stored;
10708
10739
  }
10709
10740
  }
10741
+ /** The last line's host context (call rules, browser context): every reflex call runs under it, not only the user's line. */
10742
+ lastContext;
10710
10743
  /** The turn SAID it is on it ("got it — checking…") but called no tool and nothing is running: a promise nothing
10711
10744
  * keeps — the user waits on silence (live Para #798: a follow-up while the reflex believed a finished task still ran). */
10712
10745
  get hollowPromise() {
@@ -10728,7 +10761,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10728
10761
  this.stepMarks = [];
10729
10762
  this.dropJudge();
10730
10763
  try {
10731
- await this.voice.send(reminder);
10764
+ await this.sendWithContext(reminder, this.lastContext);
10732
10765
  } catch (e) {
10733
10766
  log24.warn(`follow-through nudge failed: ${e instanceof Error ? e.message : e}`);
10734
10767
  } finally {
@@ -10871,7 +10904,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10871
10904
  if (this.dispatchAt >= 0) this.dispatchAt = 0;
10872
10905
  this.nudging = true;
10873
10906
  try {
10874
- await this.voice.send(reminder);
10907
+ await this.sendWithContext(reminder, this.lastContext);
10875
10908
  } catch (e) {
10876
10909
  log24.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
10877
10910
  } finally {
@@ -10910,7 +10943,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
10910
10943
  * second Act). The host gets `turn_replace` {id, replaces, rolledBack, followed?}: only a rollback drops the replaced
10911
10944
  * line; `followed` = the kept turn whose follow-up ran (absent: the merged text ran). */
10912
10945
  send(content, opts) {
10913
- const ctx = opts?.context;
10946
+ const ctx = opts && (opts.context !== void 0 || opts.note !== void 0) ? { context: opts.context, note: opts.note } : void 0;
10914
10947
  this.clearHoldResume();
10915
10948
  this.holdResumed = false;
10916
10949
  if (opts?.replaces) this.replaceTurn(opts.replaces);
@@ -11022,12 +11055,17 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11022
11055
  return { text: "", steps: 0, finishReason: "stop", messages: [] };
11023
11056
  });
11024
11057
  }
11025
- sendTurn(content, keepParked = false, rec, ext, context) {
11058
+ sendTurn(content, keepParked = false, rec, ext, host) {
11026
11059
  const spec = this.spec;
11027
11060
  if (spec?.state === "pending") {
11028
11061
  if (typeof content === "string" && speculationConfirms(spec.text, content)) {
11029
11062
  spec.state = "confirmed";
11030
11063
  spec.finalText = content;
11064
+ if (host?.context !== void 0) this.lastContext = host.context;
11065
+ if (host?.note?.trim()) {
11066
+ const m = [...this.voice.transcript].reverse().find((x) => x.role === "user" && x.content === spec.text);
11067
+ if (m) m.content = hostNoteBlock(host.note) + spec.text;
11068
+ }
11031
11069
  if (rec) {
11032
11070
  spec.rec = rec;
11033
11071
  rec.ctl.signal.addEventListener("abort", () => spec.ctl.abort(), { once: true });
@@ -11074,8 +11112,9 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11074
11112
  }
11075
11113
  if (typeof content === "string") content += spellingNote(content);
11076
11114
  let res;
11115
+ if (host?.context !== void 0) this.lastContext = host.context;
11077
11116
  try {
11078
- res = await this.reflexSend(content, context);
11117
+ res = await this.reflexSend(content, host?.note);
11079
11118
  } finally {
11080
11119
  if (signal) {
11081
11120
  this.voice.options.signal = ext;
@@ -11124,7 +11163,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
11124
11163
  this.voice.options.signal = spec.ctl.signal;
11125
11164
  let res;
11126
11165
  try {
11127
- res = await this.voice.send(spec.text);
11166
+ res = await this.sendWithContext(spec.text, this.lastContext);
11128
11167
  } catch (e) {
11129
11168
  log24.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
11130
11169
  } finally {
@@ -11403,7 +11442,7 @@ Update: ${brief}`;
11403
11442
  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");
11404
11443
  }
11405
11444
  buildBrief(brief, tier = "act", deliver = true) {
11406
- const recent = this.recentExcerpt();
11445
+ const recent = this.recentExcerpt(), host = this.lastContext?.trim();
11407
11446
  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." : "";
11408
11447
  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." : "") : "";
11409
11448
  return `${nowLine()}.
@@ -11411,7 +11450,10 @@ Update: ${brief}`;
11411
11450
  ` + (recent ? `${brief}
11412
11451
 
11413
11452
  ## Recent conversation (for context)
11414
- ${recent}` : brief) + verify + deliverContract;
11453
+ ${recent}` : brief) + (host ? `
11454
+
11455
+ ## The app's context for this line (not the user's words)
11456
+ ${host}` : "") + verify + deliverContract;
11415
11457
  }
11416
11458
  /** Spawn a detached worker for task `id`; its settlement notifies + enqueues the re-voice turn. */
11417
11459
  spawnWorker(id, label, briefText, tier, brief, followUp) {