@livx.cc/agentx 0.99.15 → 0.99.17

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.
@@ -370,6 +370,24 @@ declare class Agent {
370
370
  * `options.tools` — otherwise a constructor-passed tool resurrects on the next prepare()
371
371
  * rebuild (and re-adding it via addTools() would then DUPLICATE it in the wire schema). */
372
372
  removeTools(names: Set<string> | string[]): number;
373
+ /** Messages the host queued to steer the RUNNING turn (Claude-Code-style mid-turn input). The loop
374
+ * drains them at each step boundary — after tool results are appended, before the next model call —
375
+ * so they ride along with the latest tool results. A no-op between turns (nothing drains it). */
376
+ private pendingInjections;
377
+ /** Queue a user message into the in-flight turn. It lands as a `user` message at the next step
378
+ * boundary; if the model was about to stop, the injection re-opens the loop so it's never lost.
379
+ * Pushing onto `transcript` directly from outside would race the loop — this drains inside it. */
380
+ inject(content: MessageContent): void;
381
+ /** Drop any queued-but-unconsumed injections. The REPL calls this after a LOCAL command (/cmd, !bash)
382
+ * — which runs no send() to drain them — so a mid-command steer can't leak into the next real turn. */
383
+ clearInjections(): void;
384
+ /** Pull queued injections that are plain TEXT, removing them, for a runtime that can accept mid-turn
385
+ * steers over its own channel (claude-code's warm InputQueue via `providerOptions.steer`). Non-text
386
+ * injections (images) stay queued for the step-boundary `drainInjections()` re-open — the text-only
387
+ * steer channel can't carry them. Consumed DURING chat(); no overlap with drainInjections() (post-call). */
388
+ drainPendingSteers(): string[];
389
+ /** Fold any host-injected messages into the transcript as user turns. Returns true if any landed. */
390
+ private drainInjections;
373
391
  constructor(options?: Partial<AgentOptions>);
374
392
  /** Build the tool context from the resolved fs + options. Idempotent-safe: called once (ctor or ensureFs). */
375
393
  private buildCtx;
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { H as Hooks, h as RunResult, R as ReasoningEffort, A as Agent } from './Agent-CsVuR2O0.js';
2
+ import { H as Hooks, h as RunResult, R as ReasoningEffort, A as Agent } from './Agent-BfQS-MfB.js';
3
3
  import { IFilesystem } from '@livx.cc/wcli/core';
4
4
  import { M as Message, H as HostBridge, c as ContentPart, e as MessageContent } from './tools-DKf3hN4M.js';
5
5
 
package/dist/cli.js CHANGED
@@ -3406,6 +3406,43 @@ var Agent = class _Agent {
3406
3406
  if (this.options.tools) this.options.tools = this.options.tools.filter((t) => !s.has(t.name));
3407
3407
  return before - this.activeTools.length;
3408
3408
  }
3409
+ /** Messages the host queued to steer the RUNNING turn (Claude-Code-style mid-turn input). The loop
3410
+ * drains them at each step boundary — after tool results are appended, before the next model call —
3411
+ * so they ride along with the latest tool results. A no-op between turns (nothing drains it). */
3412
+ pendingInjections = [];
3413
+ /** Queue a user message into the in-flight turn. It lands as a `user` message at the next step
3414
+ * boundary; if the model was about to stop, the injection re-opens the loop so it's never lost.
3415
+ * Pushing onto `transcript` directly from outside would race the loop — this drains inside it. */
3416
+ inject(content) {
3417
+ this.pendingInjections.push(content);
3418
+ }
3419
+ /** Drop any queued-but-unconsumed injections. The REPL calls this after a LOCAL command (/cmd, !bash)
3420
+ * — which runs no send() to drain them — so a mid-command steer can't leak into the next real turn. */
3421
+ clearInjections() {
3422
+ this.pendingInjections.length = 0;
3423
+ }
3424
+ /** Pull queued injections that are plain TEXT, removing them, for a runtime that can accept mid-turn
3425
+ * steers over its own channel (claude-code's warm InputQueue via `providerOptions.steer`). Non-text
3426
+ * injections (images) stay queued for the step-boundary `drainInjections()` re-open — the text-only
3427
+ * steer channel can't carry them. Consumed DURING chat(); no overlap with drainInjections() (post-call). */
3428
+ drainPendingSteers() {
3429
+ if (!this.pendingInjections.length) return [];
3430
+ const texts = [];
3431
+ this.pendingInjections = this.pendingInjections.filter((c) => {
3432
+ if (typeof c === "string") {
3433
+ texts.push(c);
3434
+ return false;
3435
+ }
3436
+ return true;
3437
+ });
3438
+ return texts;
3439
+ }
3440
+ /** Fold any host-injected messages into the transcript as user turns. Returns true if any landed. */
3441
+ drainInjections() {
3442
+ if (!this.pendingInjections.length) return false;
3443
+ for (const content of this.pendingInjections.splice(0)) this.transcript.push({ role: "user", content });
3444
+ return true;
3445
+ }
3409
3446
  constructor(options) {
3410
3447
  this.options = { ...new AgentOptions(), ...options };
3411
3448
  if (this.options.fs) this.buildCtx();
@@ -3604,10 +3641,11 @@ var Agent = class _Agent {
3604
3641
  return typeof raw === "string" ? raw : raw.text;
3605
3642
  }
3606
3643
  } : void 0;
3644
+ const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
3607
3645
  const reasonOpts = {
3608
3646
  ...frag,
3609
3647
  ...o.promptCache ? { promptCache: true } : {},
3610
- ...o.providerOptions || cursorPo ? { providerOptions: { ...frag.providerOptions, ...o.providerOptions, ...cursorPo } } : {}
3648
+ ...o.providerOptions || cursorPo || claudeSteerPo ? { providerOptions: { ...frag.providerOptions, ...o.providerOptions, ...cursorPo, ...claudeSteerPo } } : {}
3611
3649
  };
3612
3650
  try {
3613
3651
  for (let attempt = 0; ; attempt++) {
@@ -3671,6 +3709,7 @@ var Agent = class _Agent {
3671
3709
  });
3672
3710
  }
3673
3711
  if (toolCalls.length === 0) {
3712
+ if (this.drainInjections()) continue;
3674
3713
  log4.verbose(`completed in ${steps} step(s)`);
3675
3714
  await this.ctx.jobs?.drain();
3676
3715
  await this.activeHooks?.onStop?.(res.content ?? "");
@@ -3695,6 +3734,7 @@ var Agent = class _Agent {
3695
3734
  }
3696
3735
  this.transcript.push({ role: "tool", tool_call_id: tc.id, name: tc.function.name, content });
3697
3736
  }
3737
+ this.drainInjections();
3698
3738
  }
3699
3739
  }
3700
3740
  /**
@@ -11740,6 +11780,13 @@ function installUpdate() {
11740
11780
  const r = spawnSync6("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
11741
11781
  return (r.status ?? 1) === 0;
11742
11782
  }
11783
+ var typeaheadOnLine = false;
11784
+ var clearTypeaheadLine = () => {
11785
+ if (typeaheadOnLine) {
11786
+ err("\r\x1B[2K");
11787
+ typeaheadOnLine = false;
11788
+ }
11789
+ };
11743
11790
  var spinner = /* @__PURE__ */ (() => {
11744
11791
  const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
11745
11792
  let timer;
@@ -11768,8 +11815,10 @@ var spinner = /* @__PURE__ */ (() => {
11768
11815
  clearInterval(timer);
11769
11816
  timer = void 0;
11770
11817
  err("\r\x1B[2K");
11818
+ typeaheadOnLine = false;
11771
11819
  }
11772
11820
  }
11821
+ // the tail (type-ahead) went with it
11773
11822
  };
11774
11823
  })();
11775
11824
  var setTermTitle = (t) => {
@@ -11947,7 +11996,7 @@ REPL slash commands: /help /version /tools /permissions /status /cost /context /
11947
11996
  REPL completion: type / (commands+skills) or @ (files) for a LIVE menu \u2014 \u2191/\u2193 select, \u23CE/Tab accept, Esc dismiss.
11948
11997
  REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to continue. Esc cancels a running turn / clears the input line; double-Esc jumps back to edit a previous message.
11949
11998
  REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2192 plan) \xB7 Alt+T toggles reasoning \xB7 Alt+P switches model \xB7 Ctrl+O toggles verbose tool output \xB7 Ctrl+X Ctrl+E edits the buffer in $EDITOR \xB7 \u2192 or Tab accepts the dim history ghost-suggestion \xB7 Alt+S/Ctrl+S stash/unstash.
11950
- REPL stash: type while a turn is running \u2192 Enter queues it (auto-submits when the turn finishes). Alt+S (or Ctrl+S) with text stashes it; on an empty prompt pops the next entry for editing.
11999
+ REPL steer/stash: type while a turn is running \u2192 Enter STEERS it (the text reaches the model at its next step, alongside the next tool result). Alt+S (or Ctrl+S) with text stashes it for AFTER the turn instead; on an empty prompt pops the next stashed entry for editing.
11951
12000
  REPL editing (emacs/readline): Ctrl-A/E line start/end \xB7 Ctrl-B/F char \xB7 Alt-B/F or Alt/Ctrl-\u2190/\u2192 word \xB7 Ctrl-W kill word \xB7 Ctrl-U/K kill to start/end \xB7 Ctrl-Y yank \xB7 Ctrl-_ undo \xB7 Alt-D kill word fwd \xB7 Ctrl-L clear screen. Set editorMode:'vim' (or /config) for modal vim editing.
11952
12001
  REPL paste: large/multi-line pastes collapse to a [Pasted text +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; Ctrl-V or /paste grabs a clipboard image (macOS).`;
11953
12002
  function newestModel() {
@@ -12035,9 +12084,11 @@ function makeHost(format = "text", opts) {
12035
12084
  notify(e) {
12036
12085
  if (e.kind === "turn_start") {
12037
12086
  activitySeen.clear();
12087
+ typeaheadOnLine = false;
12038
12088
  return;
12039
12089
  }
12040
12090
  spinner.stop();
12091
+ clearTypeaheadLine();
12041
12092
  if (e.kind === "text_delta") {
12042
12093
  flushLookupGroup();
12043
12094
  if (streamJson) process.stdout.write(JSON.stringify({ type: "text", text: e.message }) + "\n");
@@ -12184,7 +12235,10 @@ function displayHooks(fs, opts) {
12184
12235
  if (call.name === "TodoWrite" && Array.isArray(call.args?.todos)) latestTodos = call.args.todos;
12185
12236
  if (!on()) return;
12186
12237
  if (bg) err("\r\x1B[0J");
12187
- else spinner.stop();
12238
+ else {
12239
+ spinner.stop();
12240
+ clearTypeaheadLine();
12241
+ }
12188
12242
  flush();
12189
12243
  if (!bg && !verboseOutput && LOOKUP.has(call.name)) {
12190
12244
  pendingHeader = header(call);
@@ -12205,7 +12259,10 @@ function displayHooks(fs, opts) {
12205
12259
  async postToolUse(call, result) {
12206
12260
  if (!on()) return;
12207
12261
  if (bg) err("\r\x1B[0J");
12208
- else spinner.stop();
12262
+ else {
12263
+ spinner.stop();
12264
+ clearTypeaheadLine();
12265
+ }
12209
12266
  try {
12210
12267
  if (pendingHeader) {
12211
12268
  const t = String(result).replace(/\s+$/, "");
@@ -14662,6 +14719,7 @@ ${task}`;
14662
14719
  const renderStashBuf = () => {
14663
14720
  if (spinner.active) return;
14664
14721
  err(`\r\x1B[K${sEd.buf ? " " + stashView() : ""}`);
14722
+ typeaheadOnLine = !!sEd.buf;
14665
14723
  };
14666
14724
  repaintStash = renderStashBuf;
14667
14725
  keyInput.on("keypress", (_s, key) => {
@@ -14694,11 +14752,18 @@ ${task}`;
14694
14752
  if (action === "submit") {
14695
14753
  const text = sEd.expand().trim();
14696
14754
  sEd.reset();
14755
+ typeaheadOnLine = false;
14697
14756
  if (text) {
14698
- inputStash.push(text);
14699
14757
  const view = text.replace(/\n+/g, " \u23CE ");
14700
- err(`\r\x1B[K${green(" \u2713 stashed")} ${dim(`#${inputStash.length}: ${view.slice(0, 50)}${view.length > 50 ? "\u2026" : ""}`)}
14758
+ if ((activeTurn || dispatchPending) && !duplex) {
14759
+ agent.inject(text);
14760
+ err(`\r\x1B[K${bold(cyan(" \u21B3 steering \u203A"))} ${cyan(`${view.slice(0, 60)}${view.length > 60 ? "\u2026" : ""}`)}
14701
14761
  `);
14762
+ } else {
14763
+ inputStash.push(text);
14764
+ err(`\r\x1B[K${green(" \u2713 stashed")} ${dim(`#${inputStash.length}: ${view.slice(0, 50)}${view.length > 50 ? "\u2026" : ""}`)}
14765
+ `);
14766
+ }
14702
14767
  }
14703
14768
  return;
14704
14769
  }
@@ -15079,6 +15144,7 @@ ${out}
15079
15144
  }
15080
15145
  let quit = await dispatchLine(line) === "quit";
15081
15146
  const wasLocal = /^[!#/]/.test(line);
15147
+ if (wasLocal) agent.clearInjections();
15082
15148
  while (!quit && !wasLocal && inputStash.length) {
15083
15149
  const next = inputStash.shift();
15084
15150
  const nview = next.replace(/\n+/g, " \u23CE ");