@livx.cc/agentx 0.99.48 → 0.99.49
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/README.md +1 -1
- package/dist/{Agent-BeoEFl2T.d.ts → Agent-CXkPfYX2.d.ts} +14 -2
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +28 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +28 -4
- package/dist/index.js.map +1 -1
- package/dist/{mcp-DjYZzGGW.d.ts → mcp-CSLRyvEp.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/{tools-MNdqlJIa.d.ts → tools-HsbxgqjF.d.ts} +6 -1
- package/dist/tools.shell.d.ts +1 -1
- package/dist/tools.shell.js +6 -1
- package/dist/tools.shell.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ Beyond file tools, the runtime ships the higher-altitude pieces too — each an
|
|
|
92
92
|
- **DuplexAgent** (`src/duplex.ts`) — voice-optimized three-tier engine (reflex/act/think): a fast reflex agent streams instant replies and self-selects escalation — `Act` for standard tool work (Sonnet-class), `Think` for deep reasoning (Opus-class, configurable, default on). Results are pushed back and re-voiced by the reflex (turn mutex, coalesced completions, `TaskStatus`/`CancelTask`). See [`mind/10`](./mind/10-duplex.md).
|
|
93
93
|
- **Scheduler** (`src/scheduler.ts` + `cli/osScheduler.ts`) — one-off (`{at}`), interval (`{everyMs}`), cron (`{cron}`) via `ScheduleTask`/`ScheduleList`/`ScheduleCancel`/`Wakeup`. In-session jobs fire while the session is alive (persisted, re-armed on `--resume`); far one-offs (or `backend:'os'`) register with the OS scheduler (launchd / crontab / at) and **survive quitting** — the fired job headless-resumes the session (`agentx -p … --resume <id> --yes`). The `PushNotification` tool (osascript / notify-send) alerts the user out-of-band; `Read` on a `.pdf` returns extracted text (poppler's pdftotext, disk mode). **`RemoteTrigger`** invokes another agentx session on this machine: a session open in a live terminal receives the prompt as an injected turn (per-session unix socket, same-user only); otherwise it's resumed headless and the final answer comes back. See [`mind/12`](./mind/12-scheduler.md).
|
|
94
94
|
- **Budget kill-switches** — always-on per-run guards (`maxTokens`/`timeoutMs`/`maxRepeats`/`maxToolCalls`/`signal` → `finishReason` `budget`/`timeout`/`loop`/`max_tool_calls`/`aborted`) protect the API spend against runaway loops. The *enforceable* billing cap is server-side in the web key-proxy: a VFS-backed budget config (`/.agent/budget.json`, USD-metered, hot-reloaded, $100/wk default) a browser client can't bypass. See [`web/`](./web) and [`mind/06`](./mind/06-agent-features.md).
|
|
95
|
-
- **Jobs & liveness (never kill a working tool)** — every long-lived tool call is a job in `agent.jobs` (`JobRegistry`: id, kind `shell|mcp|host|native|bash|task`, status, `startedAt`, `lastActivityAt`, output tail, result). The model follows jobs with `JobStatus` / `JobOutput` (tail or `offset`) / `JobWait` (bounded — use instead of sleeping) / `JobKill`, and a job that finishes after its caller stopped waiting is **reported back** (re-opening a stopped turn). Liveness comes from output chunks, MCP `notifications/progress`, delegated tool events, and (for adopted shell jobs) process-tree CPU time via `ps` (group + descendants that left it). A job is killed only when it is **stuck** — silent past its kind's idle window with no CPU progress (`shell`: 10 min; unknown CPU never counts as idle) — for `mcp` only after the server has sent progress and then stopped, since most servers never report progress — or past its hard cap (`mcp`: 30 min; delegated host/native tools: the transport tool deadline) — and the model sees `killed: stuck: idle 600s`; the run continues, and every kill is logged with its evidence. Explicit `Shell({background:true})` jobs are never idle-reaped (a quiet server is healthy). The model **stall watchdog** (`stallMs`, 60s of stream silence) only fires when no job is holding the stream; a detached job does not mask a silent model, and a step whose tools already ran is never replayed. Knobs: `new Agent({ jobs: new JobRegistry({ idleMs, hardCapMs, foregroundMs, sweepMs, killGraceMs }) })`; share the same registry with `new ShellJobRegistry({ jobs })` (the CLI and `fullAgentOptions` do).
|
|
95
|
+
- **Jobs & liveness (never kill a working tool)** — every long-lived tool call is a job in `agent.jobs` (`JobRegistry`: id, kind `shell|mcp|host|native|bash|task`, status, `startedAt`, `lastActivityAt`, output tail, result). The model follows jobs with `JobStatus` / `JobOutput` (tail or `offset`) / `JobWait` (bounded — use instead of sleeping) / `JobKill`, and a job that finishes after its caller stopped waiting is **reported back** (re-opening a stopped turn). Liveness comes from output chunks, MCP `notifications/progress`, delegated tool events, and (for adopted shell jobs) process-tree CPU time via `ps` (group + descendants that left it). A job is killed only when it is **stuck** — silent past its kind's idle window with no CPU progress (`shell`: 10 min; unknown CPU never counts as idle) — for `mcp` only after the server has sent progress and then stopped, since most servers never report progress — or past its hard cap (`mcp`: 30 min; delegated host/native tools: the transport tool deadline). A delegated **`native`** tool (cursor's own shell/mcp/task) additionally forfeits its hold after `stallMs`-scale silence (`AgentOptions.nativeHoldMs`, 5 min): unlike a `host` job — whose pending dispatch promise is independent proof it is alive — a native job is only the provider's own claim, and it suppresses the very stall detection that would catch that provider going quiet. Further `running` events `touch()` it, so a native tool that keeps reporting progress holds indefinitely — and the model sees `killed: stuck: idle 600s`; the run continues, and every kill is logged with its evidence. Explicit `Shell({background:true})` jobs are never idle-reaped (a quiet server is healthy). The model **stall watchdog** (`stallMs`, 60s of stream silence) only fires when no job is holding the stream; a detached job does not mask a silent model, and a step whose tools already ran is never replayed. A hold is never silent — every suppressed window logs and notifies `waiting on <kind> "<label>" (<age>, quiet <n>s)`, so a wedged tool is visible in seconds instead of stalling the run unseen. Knobs: `new Agent({ jobs: new JobRegistry({ idleMs, hardCapMs, foregroundMs, sweepMs, killGraceMs }) })`; share the same registry with `new ShellJobRegistry({ jobs })` (the CLI and `fullAgentOptions` do).
|
|
96
96
|
|
|
97
97
|
## The `agentx` CLI
|
|
98
98
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
2
|
-
import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, k as JobRegistry, o as MessageContent, U as UserQuestion } from './tools-
|
|
2
|
+
import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, k as JobRegistry, o as MessageContent, U as UserQuestion } from './tools-HsbxgqjF.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Hooks — deterministic interception points around tool execution, run by the
|
|
@@ -244,6 +244,15 @@ declare class AgentOptions {
|
|
|
244
244
|
* arrives for this long. The between-steps `timeoutMs` can't preempt an in-flight `chat()`, so a
|
|
245
245
|
* provider that goes silent mid-stream would otherwise park the loop forever. 0 = off. */
|
|
246
246
|
stallMs: number;
|
|
247
|
+
/** IDLE bound on a NATIVE delegated tool's hold over the stall watchdog (cursor's own shell/mcp/task:
|
|
248
|
+
* `running` → silence → `completed`). Such a hold is the provider's own unverified CLAIM that it is
|
|
249
|
+
* busy, and it SUSPENDS the very stall detection that would catch that provider going silent — so its
|
|
250
|
+
* only backstop was `hardCapMs = toolHoldMs`, the TRANSPORT deadline (≥30m, sized for host-tool
|
|
251
|
+
* round-trips a native call never makes). A wedged native call therefore bought 30m of total silence,
|
|
252
|
+
* ×2 attempts = the 61-minute zero-output run (blank 2026-09-18). Idle, not absolute: every further
|
|
253
|
+
* `running` event `touch()`es the job, so a native tool that keeps reporting progress holds
|
|
254
|
+
* indefinitely — what expires is silence, not work. 0 = off (hard cap only). */
|
|
255
|
+
nativeHoldMs: number;
|
|
247
256
|
/** Stop if the identical tool-call batch (name+args) repeats this many times in a row. 0 = off. */
|
|
248
257
|
maxRepeats: number;
|
|
249
258
|
/** Cumulative cap on tool calls dispatched across the run. 0 = unbounded. */
|
|
@@ -431,11 +440,14 @@ declare class Agent {
|
|
|
431
440
|
* toolExecutor) and NATIVE tools (cursor's own shell/mcp/task, `running` → `completed`) are tracked as
|
|
432
441
|
* `holds` jobs: while one runs the provider is not silent — it is waiting on a tool — so the idle-stall
|
|
433
442
|
* watchdog must not fire. Each is bounded by the transport tool deadline (`hardCapMs = toolHoldMs`), so a
|
|
434
|
-
* tool that never settles (or a lost `completed` event) is reaped and cannot disable stall protection.
|
|
443
|
+
* tool that never settles (or a lost `completed` event) is reaped and cannot disable stall protection.
|
|
444
|
+
* A NATIVE job carries a second, much tighter bound (`idleMs = nativeHoldMs`): the transport deadline is
|
|
445
|
+
* sized for a host-tool round-trip it never makes, and silence is the only thing it can actually prove. */
|
|
435
446
|
readonly jobs: JobRegistry;
|
|
436
447
|
/** Native delegated tool activity id → its job (per step attempt). */
|
|
437
448
|
private nativeJobs;
|
|
438
449
|
private toolHoldMs;
|
|
450
|
+
private nativeHoldMs;
|
|
439
451
|
/** A tool (host or native) already executed in the current step attempt: a stall retry replays the step
|
|
440
452
|
* from the pre-step transcript, which would re-run it — so such a stall is not retried. */
|
|
441
453
|
private toolRanThisAttempt;
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { H as Hooks, i as RunResult, R as ReasoningEffort, A as Agent } from './Agent-
|
|
2
|
+
import { H as Hooks, i as RunResult, R as ReasoningEffort, A as Agent } from './Agent-CXkPfYX2.js';
|
|
3
3
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
4
|
-
import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-
|
|
4
|
+
import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-HsbxgqjF.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* On-disk session store for the CLI: each conversation is one JSON file at
|
package/dist/cli.js
CHANGED
|
@@ -1577,7 +1577,12 @@ var init_tools_jobs = __esm({
|
|
|
1577
1577
|
maxBuffer = 256 * 1024;
|
|
1578
1578
|
/** Idle window per kind: no output/progress/CPU for this long ⇒ stuck. Kinds without an activity channel
|
|
1579
1579
|
* (host, native) are absent on purpose — silence is all they can ever show, so it proves nothing; their
|
|
1580
|
-
* bound is the hard cap. Explicit `background:true` shell jobs opt out per job (a quiet server is healthy).
|
|
1580
|
+
* bound is the hard cap. Explicit `background:true` shell jobs opt out per job (a quiet server is healthy).
|
|
1581
|
+
* NOTE the asymmetry that default hides: for a HOST job silence really is uninformative (the caller holds
|
|
1582
|
+
* the pending dispatch promise — independent proof it is alive), but a NATIVE job has no such proof AND it
|
|
1583
|
+
* suppresses the stall watchdog, i.e. it turns the provider's own silence into permission to keep ignoring
|
|
1584
|
+
* that provider's silence. So Agent passes an explicit per-job `idleMs` (nativeHoldMs) for `native`: the
|
|
1585
|
+
* provider must keep re-proving liveness with `running` events or forfeit the hold. See Agent.nativeHoldMs. */
|
|
1581
1586
|
idleMs = { shell: 10 * 6e4, mcp: 10 * 6e4 };
|
|
1582
1587
|
/** Absolute ceiling per kind (0/absent = none). */
|
|
1583
1588
|
hardCapMs = { mcp: 30 * 6e4 };
|
|
@@ -4180,6 +4185,15 @@ var AgentOptions = class {
|
|
|
4180
4185
|
* arrives for this long. The between-steps `timeoutMs` can't preempt an in-flight `chat()`, so a
|
|
4181
4186
|
* provider that goes silent mid-stream would otherwise park the loop forever. 0 = off. */
|
|
4182
4187
|
stallMs = 6e4;
|
|
4188
|
+
/** IDLE bound on a NATIVE delegated tool's hold over the stall watchdog (cursor's own shell/mcp/task:
|
|
4189
|
+
* `running` → silence → `completed`). Such a hold is the provider's own unverified CLAIM that it is
|
|
4190
|
+
* busy, and it SUSPENDS the very stall detection that would catch that provider going silent — so its
|
|
4191
|
+
* only backstop was `hardCapMs = toolHoldMs`, the TRANSPORT deadline (≥30m, sized for host-tool
|
|
4192
|
+
* round-trips a native call never makes). A wedged native call therefore bought 30m of total silence,
|
|
4193
|
+
* ×2 attempts = the 61-minute zero-output run (blank 2026-09-18). Idle, not absolute: every further
|
|
4194
|
+
* `running` event `touch()`es the job, so a native tool that keeps reporting progress holds
|
|
4195
|
+
* indefinitely — what expires is silence, not work. 0 = off (hard cap only). */
|
|
4196
|
+
nativeHoldMs = 3e5;
|
|
4183
4197
|
/** Stop if the identical tool-call batch (name+args) repeats this many times in a row. 0 = off. */
|
|
4184
4198
|
maxRepeats = 3;
|
|
4185
4199
|
/** Cumulative cap on tool calls dispatched across the run. 0 = unbounded. */
|
|
@@ -4371,11 +4385,14 @@ var Agent = class _Agent {
|
|
|
4371
4385
|
* toolExecutor) and NATIVE tools (cursor's own shell/mcp/task, `running` → `completed`) are tracked as
|
|
4372
4386
|
* `holds` jobs: while one runs the provider is not silent — it is waiting on a tool — so the idle-stall
|
|
4373
4387
|
* watchdog must not fire. Each is bounded by the transport tool deadline (`hardCapMs = toolHoldMs`), so a
|
|
4374
|
-
* tool that never settles (or a lost `completed` event) is reaped and cannot disable stall protection.
|
|
4388
|
+
* tool that never settles (or a lost `completed` event) is reaped and cannot disable stall protection.
|
|
4389
|
+
* A NATIVE job carries a second, much tighter bound (`idleMs = nativeHoldMs`): the transport deadline is
|
|
4390
|
+
* sized for a host-tool round-trip it never makes, and silence is the only thing it can actually prove. */
|
|
4375
4391
|
jobs;
|
|
4376
4392
|
/** Native delegated tool activity id → its job (per step attempt). */
|
|
4377
4393
|
nativeJobs = /* @__PURE__ */ new Map();
|
|
4378
4394
|
toolHoldMs = 0;
|
|
4395
|
+
nativeHoldMs = 0;
|
|
4379
4396
|
/** A tool (host or native) already executed in the current step attempt: a stall retry replays the step
|
|
4380
4397
|
* from the pre-step transcript, which would re-run it — so such a stall is not retried. */
|
|
4381
4398
|
toolRanThisAttempt = false;
|
|
@@ -4717,6 +4734,7 @@ var Agent = class _Agent {
|
|
|
4717
4734
|
const derivedToolTimeoutMs = Math.max(TRANSPORT_TOOL_TIMEOUT_FLOOR_MS, ...this.activeTools.map(transportHeadroomMs));
|
|
4718
4735
|
const effectiveToolTimeoutMs = typeof hostToolTimeoutMs === "number" ? Math.max(hostToolTimeoutMs, derivedToolTimeoutMs) : derivedToolTimeoutMs;
|
|
4719
4736
|
this.toolHoldMs = effectiveToolTimeoutMs;
|
|
4737
|
+
this.nativeHoldMs = o.nativeHoldMs;
|
|
4720
4738
|
if (isCursorWithTools) {
|
|
4721
4739
|
if (typeof hostToolTimeoutMs === "number" && effectiveToolTimeoutMs !== hostToolTimeoutMs) {
|
|
4722
4740
|
log7.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.`);
|
|
@@ -4970,7 +4988,13 @@ var Agent = class _Agent {
|
|
|
4970
4988
|
if (!stallMs) return;
|
|
4971
4989
|
if (timer) clearTimeout(timer);
|
|
4972
4990
|
timer = setTimeout(() => {
|
|
4973
|
-
if (this.toolHoldActive())
|
|
4991
|
+
if (this.toolHoldActive()) {
|
|
4992
|
+
const now4 = Date.now();
|
|
4993
|
+
const who = this.jobs.holding().map((j) => `${j.kind} "${j.label}" (${Math.round((now4 - j.startedAt) / 1e3)}s, quiet ${Math.round((now4 - j.lastActivityAt) / 1e3)}s)`).join(", ");
|
|
4994
|
+
log7.warn(`stall watchdog: no stream output for ${stallMs}ms \u2014 held by ${who}`);
|
|
4995
|
+
this.options.host?.notify?.({ kind: "tool_hold", message: `waiting on ${who}` });
|
|
4996
|
+
return poke();
|
|
4997
|
+
}
|
|
4974
4998
|
const inFlight = this.jobs.list().filter((j) => j.status === "running");
|
|
4975
4999
|
log7.warn(`stall watchdog: no stream output for ${stallMs}ms and no tool holding the stream \u2014 aborting the request` + (inFlight.length ? ` (${inFlight.length} detached job(s) running, not awaited by the stream: ${inFlight.map((j) => `${j.id}/${j.kind}`).join(", ")})` : ""));
|
|
4976
5000
|
fired = true;
|
|
@@ -5039,7 +5063,7 @@ var Agent = class _Agent {
|
|
|
5039
5063
|
const nj = a.id ? this.nativeJobs.get(a.id) : void 0;
|
|
5040
5064
|
if (a.status === "running") {
|
|
5041
5065
|
if (nj) nj.touch();
|
|
5042
|
-
else if (a.id) this.nativeJobs.set(a.id, this.jobs.track({ kind: "native", label: a.name, holds: true, hardCapMs: this.toolHoldMs }));
|
|
5066
|
+
else if (a.id) this.nativeJobs.set(a.id, this.jobs.track({ kind: "native", label: a.name, holds: true, idleMs: this.nativeHoldMs, hardCapMs: this.toolHoldMs }));
|
|
5043
5067
|
} else {
|
|
5044
5068
|
if (nj) {
|
|
5045
5069
|
if (typeof a.output === "string") nj.chunk(a.output.slice(-4e3));
|