@livx.cc/agentx 0.99.45 → 0.99.47

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, i as RunResult, A as Agent } from './Agent-Bzs57-iD.js';
2
- export { C as ChatFragment, b as CompactResult, D as DEFAULT_MUTATING, c as Decision, P as PermissionOptions, d as PermissionPolicy, e as PermissionRule, f as PreToolUseDecision, R as ReasoningEffort, g as RecordingHooks, h as RecordingLifecycle, T as ToolUse, j as ToolUseMeta, k as composeHooks, l as estimateTokens, p as planMode, r as reasoningToChatFragment } from './Agent-Bzs57-iD.js';
1
+ import { a as AgentOptions, H as Hooks, i as RunResult, A as Agent } from './Agent-BWbQ4Sqp.js';
2
+ export { C as ChatFragment, b as CompactResult, D as DEFAULT_MUTATING, c as Decision, P as PermissionOptions, d as PermissionPolicy, e as PermissionRule, f as PreToolUseDecision, R as ReasoningEffort, g as RecordingHooks, h as RecordingLifecycle, T as ToolUse, j as ToolUseMeta, k as composeHooks, l as estimateTokens, p as planMode, r as reasoningToChatFragment } from './Agent-BWbQ4Sqp.js';
3
3
  export { MODEL_ALIASES, ModelSwitch, ResolveModelSwitchOpts, 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';
package/dist/index.js CHANGED
@@ -3947,6 +3947,10 @@ var AgentOptions = class {
3947
3947
  * not prose: a named tool with zero invocations is provable. On detection the loop nudges ONCE for a real
3948
3948
  * call, and records the names in `RunResult.fabricatedToolClaims` either way. */
3949
3949
  catchFabricatedToolUse = true;
3950
+ /** An empty model turn (no text, no tools) that ends the loop after work is re-prompted ONCE, then finishes
3951
+ * as `finishReason:'empty'` (a failure) instead of a phantom 'stop'. Off for layers that deliberately
3952
+ * allow a silent turn and repair it themselves (the duplex voice reflex). */
3953
+ failEmptyTurns = true;
3950
3954
  /** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
3951
3955
  compaction;
3952
3956
  /** Add `Checkpoint`/`Rollback` tools (requires the fs to be an OverlayFilesystem). */
@@ -4020,6 +4024,22 @@ var Agent = class _Agent {
4020
4024
  /** Per-run ground truth: every tool name actually INVOKED this run (native dispatch + delegated
4021
4025
  * runtime activity). The ledger that makes a fabricated tool claim provable. Reset per run. */
4022
4026
  invokedTools = /* @__PURE__ */ new Set();
4027
+ /** Delegated-runtime HOST tools currently executing (cursor toolExecutor). While > 0 the provider is
4028
+ * not silent — it is waiting on US — so the idle-stall watchdog must not fire (see armStallWatchdog). */
4029
+ hostToolsInFlight = 0;
4030
+ /** The hold is bounded by the transport tool deadline: a host tool that never settles (the helper already gave
4031
+ * up on it) must not disable stall protection for the rest of the request — or this instance. */
4032
+ hostToolHoldUntil = 0;
4033
+ /** Delegated-runtime NATIVE tools (cursor's own shell/mcp/task) seen `running` but not yet `completed`/`error`,
4034
+ * id → hold-until. The runtime streams nothing while one runs, so it holds the watchdog like a host tool —
4035
+ * bounded by the same tool deadline (`toolHoldMs`) so a lost `completed` event can't disable it. */
4036
+ nativeToolsInFlight = /* @__PURE__ */ new Map();
4037
+ toolHoldMs = 0;
4038
+ /** A tool (host or native) already executed in the current step attempt: a stall retry replays the step
4039
+ * from the pre-step transcript, which would re-run it — so such a stall is not retried. */
4040
+ toolRanThisAttempt = false;
4041
+ /** Resets the active request's stall timer; a host tool's completion restarts the model's idle window. */
4042
+ stallPoke;
4023
4043
  /** Advertised tools this run's text claimed to have used while the ledger showed zero calls. */
4024
4044
  fabricatedClaims = /* @__PURE__ */ new Set();
4025
4045
  activeHooks;
@@ -4322,6 +4342,7 @@ var Agent = class _Agent {
4322
4342
  let toolCallsTotal = 0;
4323
4343
  let lastFp = "";
4324
4344
  let repeats = 0;
4345
+ let emptyNudged = false;
4325
4346
  let closureNudged = false;
4326
4347
  let lastProgressAt = start;
4327
4348
  const kill = (finishReason) => {
@@ -4330,12 +4351,13 @@ var Agent = class _Agent {
4330
4351
  const claims = this.survivingClaims();
4331
4352
  return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated, ...claims.length ? { fabricatedToolClaims: claims } : {} };
4332
4353
  };
4354
+ const overBudget = () => !!o.maxTokens && usage.totalTokens - 0.9 * usage.cacheReadTokens >= o.maxTokens;
4355
+ const canNudge = () => steps < o.maxSteps && !overBudget();
4333
4356
  while (true) {
4334
4357
  if (o.signal?.aborted) return kill("aborted");
4335
4358
  if (steps >= o.maxSteps) return kill("max_steps");
4336
4359
  if (o.timeoutMs && Date.now() - lastProgressAt - this.parkedMs >= o.timeoutMs) return kill("timeout");
4337
- const budgetTokens = usage.totalTokens - 0.9 * usage.cacheReadTokens;
4338
- if (o.maxTokens && budgetTokens >= o.maxTokens) return kill("budget");
4360
+ if (overBudget()) return kill("budget");
4339
4361
  steps++;
4340
4362
  this.options.host?.notify?.({ kind: "turn_start", message: `step ${steps}` });
4341
4363
  let res;
@@ -4346,6 +4368,7 @@ var Agent = class _Agent {
4346
4368
  const hostToolTimeoutMs = o.providerOptions?.toolTimeoutMs;
4347
4369
  const derivedToolTimeoutMs = Math.max(TRANSPORT_TOOL_TIMEOUT_FLOOR_MS, ...this.activeTools.map(transportHeadroomMs));
4348
4370
  const effectiveToolTimeoutMs = typeof hostToolTimeoutMs === "number" ? Math.max(hostToolTimeoutMs, derivedToolTimeoutMs) : derivedToolTimeoutMs;
4371
+ this.toolHoldMs = effectiveToolTimeoutMs;
4349
4372
  if (isCursorWithTools) {
4350
4373
  if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
4351
4374
  log6.warn(`providerOptions.toolTimeoutMs=${hostToolTimeoutMs}ms is below what the registered tools need \u2014 RAISED to ${effectiveToolTimeoutMs}ms. ${toolTimeoutViolations(this.activeTools, hostToolTimeoutMs).length} tool(s) could outlive it, and the transport would have destroyed their results. To lower it, lower the tools' own maxDurationMs (e.g. the Shell tool's maxTimeoutMs) instead.`);
@@ -4356,7 +4379,15 @@ var Agent = class _Agent {
4356
4379
  toolTimeoutMs: effectiveToolTimeoutMs,
4357
4380
  toolExecutor: async (name, args) => {
4358
4381
  const tc = { id: `cursor-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
4359
- return await this.dispatch(tc);
4382
+ this.hostToolsInFlight++;
4383
+ this.hostToolHoldUntil = Math.max(this.hostToolHoldUntil, Date.now() + effectiveToolTimeoutMs);
4384
+ try {
4385
+ return await this.dispatch(tc);
4386
+ } finally {
4387
+ this.hostToolsInFlight--;
4388
+ this.toolRanThisAttempt = true;
4389
+ this.stallPoke?.();
4390
+ }
4360
4391
  }
4361
4392
  } : void 0;
4362
4393
  const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
@@ -4367,6 +4398,8 @@ var Agent = class _Agent {
4367
4398
  };
4368
4399
  try {
4369
4400
  for (let attempt = 0; ; attempt++) {
4401
+ this.nativeToolsInFlight.clear();
4402
+ this.toolRanThisAttempt = false;
4370
4403
  try {
4371
4404
  if (useStream) {
4372
4405
  const stall = this.armStallWatchdog(o.signal, o.stallMs);
@@ -4396,7 +4429,9 @@ var Agent = class _Agent {
4396
4429
  const stalled = err?.code === "stall";
4397
4430
  const empty = err?.code === "empty" || err?.code === "cursor_silent";
4398
4431
  const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
4399
- const transient = !o.signal?.aborted && !isAbortError(err) && attempt < maxAttempts && (network || serverSide || stalled || empty);
4432
+ const replaysTool = stalled && this.toolRanThisAttempt;
4433
+ if (replaysTool) log6.warn("model stall after tool(s) already ran this step \u2014 not retrying (a retry would replay them)");
4434
+ const transient = !o.signal?.aborted && !isAbortError(err) && !replaysTool && attempt < maxAttempts && (network || serverSide || stalled || empty);
4400
4435
  if (!transient) throw err;
4401
4436
  const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
4402
4437
  log6.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err?.message ?? err}) \u2014 retrying in ${waitMs}ms`);
@@ -4477,9 +4512,22 @@ var Agent = class _Agent {
4477
4512
  }
4478
4513
  if (toolCalls.length === 0) {
4479
4514
  if (this.drainInjections()) continue;
4515
+ if (emptyTurn && o.failEmptyTurns) {
4516
+ if (!emptyNudged && canNudge()) {
4517
+ emptyNudged = true;
4518
+ log6.warn(`empty model turn after tool work (step ${steps}, provider finishReason=${res.finishReason ?? "n/a"}) \u2014 nudging once`);
4519
+ this.transcript.push({ role: "user", content: "Continue \u2014 you returned nothing. Answer the user now using the tool results above, or keep working if you are not done." });
4520
+ continue;
4521
+ }
4522
+ log6.warn(`model returned empty turns after tool work \u2014 surfacing as failed`);
4523
+ const e = new Error("empty_response: model returned an empty turn (no text, no tools) after a re-prompt");
4524
+ e.code = "empty";
4525
+ return { text: "", steps, finishReason: "empty", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, error: e };
4526
+ }
4480
4527
  const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
4481
4528
  for (const n of fabricated) this.fabricatedClaims.add(n);
4482
- if (fabricated.length && steps < o.maxSteps) {
4529
+ if (fabricated.length && !canNudge()) log6.warn(`fabricated tool use: text names ${fabricated.join(", ")} but no step/budget left to demand the call \u2014 finishing with the existing answer`);
4530
+ if (fabricated.length && canNudge()) {
4483
4531
  closureNudged = true;
4484
4532
  log6.warn(`fabricated tool use: text names ${fabricated.join(", ")} but the run invoked ${this.invokedTools.size ? [...this.invokedTools].join(", ") : "nothing"} \u2014 demanding the real call`);
4485
4533
  this.transcript.push({
@@ -4488,7 +4536,7 @@ var Agent = class _Agent {
4488
4536
  });
4489
4537
  continue;
4490
4538
  }
4491
- if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && steps < o.maxSteps) {
4539
+ if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
4492
4540
  closureNudged = true;
4493
4541
  log6.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
4494
4542
  this.transcript.push({
@@ -4502,7 +4550,7 @@ var Agent = class _Agent {
4502
4550
  this.releaseStaleImages();
4503
4551
  await this.activeHooks?.onStop?.(res.content ?? "");
4504
4552
  const claims = this.survivingClaims();
4505
- return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated, ...claims.length ? { fabricatedToolClaims: claims } : {} };
4553
+ return { text: res.content ?? "", steps, finishReason: "stop", ...res.finishReason ? { providerFinishReason: res.finishReason } : {}, messages: this.transcript, usage, usageEstimated, ...claims.length ? { fabricatedToolClaims: claims } : {} };
4506
4554
  }
4507
4555
  const fp = toolCalls.map((tc) => tc.function.name + ":" + (tc.function.arguments ?? "")).join("|");
4508
4556
  repeats = fp === lastFp ? repeats + 1 : 1;
@@ -4566,10 +4614,12 @@ var Agent = class _Agent {
4566
4614
  if (!stallMs) return;
4567
4615
  if (timer) clearTimeout(timer);
4568
4616
  timer = setTimeout(() => {
4617
+ if (this.toolHoldActive()) return poke();
4569
4618
  fired = true;
4570
4619
  ac.abort(stallError());
4571
4620
  }, stallMs);
4572
4621
  };
4622
+ this.stallPoke = poke;
4573
4623
  poke();
4574
4624
  const stalled = () => fired && !parent?.aborted;
4575
4625
  return {
@@ -4578,11 +4628,19 @@ var Agent = class _Agent {
4578
4628
  stalled,
4579
4629
  clear: () => {
4580
4630
  if (timer) clearTimeout(timer);
4631
+ if (this.stallPoke === poke) this.stallPoke = void 0;
4581
4632
  parent?.removeEventListener("abort", onParentAbort);
4582
4633
  },
4583
4634
  reclassify: (err) => stalled() ? stallError() : err
4584
4635
  };
4585
4636
  }
4637
+ /** A host or native delegated tool is running within its deadline — the provider is quiet, not stalled. */
4638
+ toolHoldActive() {
4639
+ const now3 = Date.now();
4640
+ if (this.hostToolsInFlight > 0 && now3 < this.hostToolHoldUntil) return true;
4641
+ for (const until of this.nativeToolsInFlight.values()) if (now3 < until) return true;
4642
+ return false;
4643
+ }
4586
4644
  /** A clean-stop turn that carries no assistant text AND no tool calls (native or delegated) — a
4587
4645
  * non-result the model produced by going silent. Callers retry it rather than report "done". */
4588
4646
  isEmptyStop(res) {
@@ -4621,6 +4679,12 @@ var Agent = class _Agent {
4621
4679
  if (a.output !== void 0) prev.output = a.output;
4622
4680
  prev.status = a.status;
4623
4681
  }
4682
+ if (a.status === "running") {
4683
+ if (a.id) this.nativeToolsInFlight.set(a.id, Date.now() + this.toolHoldMs);
4684
+ } else {
4685
+ if (a.id) this.nativeToolsInFlight.delete(a.id);
4686
+ this.toolRanThisAttempt = true;
4687
+ }
4624
4688
  sawTool = true;
4625
4689
  this.invokedTools.add(a.name);
4626
4690
  trailing = "";
@@ -4673,8 +4737,9 @@ var Agent = class _Agent {
4673
4737
  */
4674
4738
  fabricatedToolNames(text) {
4675
4739
  const out = [];
4740
+ const invoked = new Set([...this.invokedTools].map(toolKey));
4676
4741
  for (const t of this.activeTools) {
4677
- if (this.invokedTools.has(t.name)) continue;
4742
+ if (invoked.has(toolKey(t.name))) continue;
4678
4743
  if (t.name.length < 4 || !/[_\-A-Z]/.test(t.name)) continue;
4679
4744
  const re = new RegExp(`(?:^|[^\\w])${t.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^\\w]|$)`);
4680
4745
  if (re.test(text)) out.push(t.name);
@@ -4687,7 +4752,8 @@ var Agent = class _Agent {
4687
4752
  * call that demonstrably ran. See `RunResult.fabricatedToolClaims`. */
4688
4753
  survivingClaims() {
4689
4754
  if (!this.fabricatedClaims.size) return [];
4690
- return [...this.fabricatedClaims].filter((n) => !this.invokedTools.has(n));
4755
+ const invoked = new Set([...this.invokedTools].map(toolKey));
4756
+ return [...this.fabricatedClaims].filter((n) => !invoked.has(toolKey(n)));
4691
4757
  }
4692
4758
  async dispatch(tc) {
4693
4759
  this.invokedTools.add(tc.function.name);
@@ -5075,6 +5141,11 @@ function lastAssistantText(messages) {
5075
5141
  }
5076
5142
  return "";
5077
5143
  }
5144
+ var TOOL_ALIASES = { bash: "shell" };
5145
+ function toolKey(name) {
5146
+ const k = name.toLowerCase();
5147
+ return TOOL_ALIASES[k] ?? k;
5148
+ }
5078
5149
 
5079
5150
  // src/index.ts
5080
5151
  init_NodeDiskFilesystem();
@@ -7843,6 +7914,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
7843
7914
  instructionFiles: false,
7844
7915
  maxSteps: 8,
7845
7916
  timeoutMs: 3e4,
7917
+ failEmptyTurns: false,
7918
+ // a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
7846
7919
  ...o.reflexOptions,
7847
7920
  tools,
7848
7921
  // Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.