@livx.cc/agentx 0.99.41 → 0.99.43

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-DydgLoT-.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-DydgLoT-.js';
1
+ import { a as AgentOptions, H as Hooks, i as RunResult, A as Agent } from './Agent-f4um2q8E.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-f4um2q8E.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
@@ -2015,7 +2015,7 @@ function makeRealShellTool(options) {
2015
2015
  return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
2016
2016
  },
2017
2017
  get description() {
2018
- return `Run a shell command via /bin/sh in the working directory. Executes any installed binary \u2014 ls, cat, grep, git, bun, node, curl, scripts, etc. Returns combined stdout+stderr; non-zero exits are prefixed \`[exit N]\`. Runs non-interactively with no terminal (stdin is /dev/null): commands that prompt for input fail fast rather than hang \u2014 for privileged actions use a non-interactive flag (e.g. \`sudo -n\`), or ask the user to run the command themselves. Each command is killed after ${defaultTimeoutMs}ms (result \`[exit 124]\` with whatever output it produced) \u2014 pass \`timeoutMs\` (max ${maxTimeoutMs}) for a legitimately slower command, and always bound network commands yourself (e.g. \`curl -m 10\`). ` + backgroundDoc();
2018
+ return "Run a shell command via /bin/sh in the working directory. Executes any installed binary \u2014 ls, cat, grep, git, bun, node, curl, scripts, etc. Returns combined stdout+stderr; non-zero exits are prefixed `[exit N]`. Runs non-interactively with no terminal (stdin is /dev/null): commands that prompt for input fail fast rather than hang \u2014 for privileged actions use a non-interactive flag (e.g. `sudo -n`), or ask the user to run the command themselves. " + (options.registry?.adopt ? `A command still running after ${defaultTimeoutMs}ms is NOT killed \u2014 it is handed to a background job and you get its id, so you can keep checking on it (\`[still running]\`). Pass \`timeoutMs\` (max ${maxTimeoutMs}) to wait longer in the foreground, and always bound network commands yourself (e.g. \`curl -m 10\`). ` : `Each command is killed after ${defaultTimeoutMs}ms (result \`[exit 124]\` with whatever output it produced) \u2014 pass \`timeoutMs\` (max ${maxTimeoutMs}) for a legitimately slower command, and always bound network commands yourself (e.g. \`curl -m 10\`). `) + backgroundDoc();
2019
2019
  },
2020
2020
  // Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
2021
2021
  maxDurationMs: maxTimeoutMs,
@@ -2054,9 +2054,12 @@ function makeRealShellTool(options) {
2054
2054
  else ctx.signal.addEventListener("abort", onAbort, { once: true });
2055
2055
  }
2056
2056
  let timedOut = false;
2057
- const timer = setTimeout(() => {
2057
+ let onDeadline = () => {
2058
2058
  timedOut = true;
2059
2059
  ctl.abort();
2060
+ };
2061
+ const timer = setTimeout(() => {
2062
+ onDeadline();
2060
2063
  }, timeoutMs);
2061
2064
  let pend = "";
2062
2065
  let flushTimer = null;
@@ -2087,6 +2090,18 @@ function makeRealShellTool(options) {
2087
2090
  }
2088
2091
  if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
2089
2092
  else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
2093
+ const registry = options.registry;
2094
+ if (registry?.adopt) {
2095
+ onDeadline = () => {
2096
+ timedOut = true;
2097
+ if (settled) return;
2098
+ flushEmit(ctx);
2099
+ proc.stdout?.off?.("data", collect);
2100
+ proc.stderr?.off?.("data", collect);
2101
+ const id = registry.adopt(cmd, proc, out);
2102
+ finish(handoffFor(timeoutMs, id, clean(out), !!registry.notifiesOnExit));
2103
+ };
2104
+ }
2090
2105
  const collect = (chunk) => {
2091
2106
  const s = typeof chunk === "string" ? chunk : chunk?.toString?.("utf8") ?? "";
2092
2107
  out += s;
@@ -2119,6 +2134,12 @@ function makeRealShellTool(options) {
2119
2134
  }
2120
2135
  };
2121
2136
  }
2137
+ function handoffFor(timeoutMs, id, body, notifies) {
2138
+ const head = `[still running] exceeded ${timeoutMs}ms, so it was handed to background job ${id} \u2014 NOT killed, it is still going. Check on it with ShellOutput({id:"${id}"}) / ShellStatus({id:"${id}"}), stop it with ShellKill({id:"${id}"}). ` + (notifies ? "Its completion will be reported to you, so you may continue with other work meanwhile." : "Nothing will tell you when it finishes \u2014 poll it yourself before you rely on its result.");
2139
+ return body ? `${head}
2140
+ Output so far:
2141
+ ${body}` : head;
2142
+ }
2122
2143
  function reasonFor(timedOut, timeoutMs, body) {
2123
2144
  const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed). Re-running this command unchanged will time out again. Either re-run it with background:true (returns a job id immediately; poll with ShellOutput/ShellStatus, stop with ShellKill) or narrow it so it can finish \u2014 bound it, scope it, or ask for less.` : "[exit 130] cancelled (killed)";
2124
2145
  return body ? `${head}
@@ -2258,6 +2279,41 @@ var init_tools_shell = __esm({
2258
2279
  list() {
2259
2280
  return [...this.jobs].map(([id, j]) => ({ id, command: j.command, status: j.status }));
2260
2281
  }
2282
+ /**
2283
+ * Take over an ALREADY-RUNNING child as a background job. A foreground command that outruns its
2284
+ * timeout is not necessarily a hung command — killing it throws away work that was nearly done and,
2285
+ * worse, a launcher that spawned its own detached worker leaves that worker running with nothing
2286
+ * tracking it. Adopting hands the model a handle instead: the command keeps going, its completion
2287
+ * is reported like any other job, and `seed` carries the output produced before the handoff.
2288
+ */
2289
+ adopt(command, proc, seed = "") {
2290
+ const id = `job-${++this.seq}`;
2291
+ const max = this.cfg.maxBuffer ?? 256 * 1024;
2292
+ const job = { command, buf: seed.slice(-max), status: "running", proc };
2293
+ const append = (chunk) => {
2294
+ const s = typeof chunk === "string" ? chunk : chunk?.toString?.("utf8") ?? "";
2295
+ job.buf = (job.buf + s).slice(-max);
2296
+ };
2297
+ proc.stdout?.on("data", append);
2298
+ proc.stderr?.on("data", append);
2299
+ proc.on("error", (err) => {
2300
+ if (job.status === "running") {
2301
+ job.status = "error";
2302
+ append(`
2303
+ [error] ${err?.message ?? err}`);
2304
+ this.notifyExit(id, job);
2305
+ }
2306
+ });
2307
+ proc.on("close", (code) => {
2308
+ if (job.status === "running") {
2309
+ job.status = "exited";
2310
+ job.exitCode = code ?? void 0;
2311
+ this.notifyExit(id, job);
2312
+ }
2313
+ });
2314
+ this.jobs.set(id, job);
2315
+ return id;
2316
+ }
2261
2317
  kill(id) {
2262
2318
  const j = this.jobs.get(id);
2263
2319
  if (!j) return false;
@@ -3793,7 +3849,9 @@ var AgentOptions = class {
3793
3849
  /** Retry budget for an EMPTY/silent-turn (a delegated-runtime wedge: cursor's warm helper spins then
3794
3850
  * returns a content-less end, surfaced as `code:'empty'`/`code:'cursor_silent'`). Tuned WIDER than the
3795
3851
  * network-drop budget (fixed 2) because clearing a wedge needs several reap+respawn cycles — each retry
3796
- * hits a FRESH cursor session (the adapter evicts the wedged warm helper on the silent-end). Still bounded:
3852
+ * hits a FRESH cursor session (the adapter evicts the wedged warm helper on the silent-end; a stall-watchdog
3853
+ * cancel is NOT this class — it is classified `stall`, and the watchdog's coded abort reason is what tells
3854
+ * the adapter to evict on that path too). Still bounded:
3797
3855
  * after exhaustion the turn surfaces `finishReason:'empty'` (non-zero exit), never infinite, never silent.
3798
3856
  * Env: AGENTX_EMPTY_RETRIES. */
3799
3857
  emptyRetries = envInt("AGENTX_EMPTY_RETRIES", 4);
@@ -4312,6 +4370,7 @@ var Agent = class _Agent {
4312
4370
  try {
4313
4371
  const r = await o.ai.chat({ model: o.model, messages: sent, tools: sentTools, stream: true, signal: stall.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
4314
4372
  res = await this.consumeStream(r, stall.poke);
4373
+ if (stall.stalled()) throw stall.reclassify(void 0);
4315
4374
  } catch (err) {
4316
4375
  throw stall.reclassify(err);
4317
4376
  } finally {
@@ -4346,7 +4405,7 @@ var Agent = class _Agent {
4346
4405
  if (err?.code === "budget") return kill("budget");
4347
4406
  if (err?.code === "stall") {
4348
4407
  log6.warn(`model stalled out after retries: ${err?.message ?? err}`);
4349
- return kill("timeout");
4408
+ return { ...kill("timeout"), error: err };
4350
4409
  }
4351
4410
  if (o.signal?.aborted || isAbortError(err)) return kill("aborted");
4352
4411
  if (err?.code === "empty" || err?.code === "cursor_silent") {
@@ -4494,31 +4553,30 @@ var Agent = class _Agent {
4494
4553
  if (parent?.aborted) ac.abort();
4495
4554
  else parent?.addEventListener("abort", onParentAbort, { once: true });
4496
4555
  let timer;
4556
+ const stallError = () => {
4557
+ const e = new Error(`model stalled: no stream output for ${stallMs >= 1e3 ? `${Math.round(stallMs / 1e3)}s` : `${stallMs}ms`}`);
4558
+ e.code = "stall";
4559
+ return e;
4560
+ };
4497
4561
  const poke = () => {
4498
4562
  if (!stallMs) return;
4499
4563
  if (timer) clearTimeout(timer);
4500
4564
  timer = setTimeout(() => {
4501
4565
  fired = true;
4502
- ac.abort();
4566
+ ac.abort(stallError());
4503
4567
  }, stallMs);
4504
4568
  };
4505
4569
  poke();
4570
+ const stalled = () => fired && !parent?.aborted;
4506
4571
  return {
4507
4572
  signal: ac.signal,
4508
4573
  poke,
4574
+ stalled,
4509
4575
  clear: () => {
4510
4576
  if (timer) clearTimeout(timer);
4511
4577
  parent?.removeEventListener("abort", onParentAbort);
4512
4578
  },
4513
- // Distinguish OUR abort (silent provider) from a parent/user cancel, which must stay an abort.
4514
- reclassify: (err) => {
4515
- if (fired && !parent?.aborted) {
4516
- const e = new Error(`model stalled: no stream output for ${stallMs >= 1e3 ? `${Math.round(stallMs / 1e3)}s` : `${stallMs}ms`}`);
4517
- e.code = "stall";
4518
- return e;
4519
- }
4520
- return err;
4521
- }
4579
+ reclassify: (err) => stalled() ? stallError() : err
4522
4580
  };
4523
4581
  }
4524
4582
  /** A clean-stop turn that carries no assistant text AND no tool calls (native or delegated) — a