@livx.cc/agentx 0.99.17 → 0.99.19
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/{Agent-BfQS-MfB.d.ts → Agent-Cn9BxS8h.d.ts} +12 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +96 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +64 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -214,6 +214,10 @@ declare class AgentOptions {
|
|
|
214
214
|
maxTokens: number;
|
|
215
215
|
/** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
|
|
216
216
|
timeoutMs: number;
|
|
217
|
+
/** Idle-stall watchdog for a SINGLE streamed model request: abort it if no chunk (text OR reasoning)
|
|
218
|
+
* arrives for this long. The between-steps `timeoutMs` can't preempt an in-flight `chat()`, so a
|
|
219
|
+
* provider that goes silent mid-stream would otherwise park the loop forever. 0 = off. */
|
|
220
|
+
stallMs: number;
|
|
217
221
|
/** Stop if the identical tool-call batch (name+args) repeats this many times in a row. 0 = off. */
|
|
218
222
|
maxRepeats: number;
|
|
219
223
|
/** Cumulative cap on tool calls dispatched across the run. 0 = unbounded. */
|
|
@@ -436,6 +440,14 @@ declare class Agent {
|
|
|
436
440
|
* (`{kind:'text_delta'}`) and fold all chunks back into the single
|
|
437
441
|
* ChatResponse the non-stream path would have returned.
|
|
438
442
|
*/
|
|
443
|
+
/**
|
|
444
|
+
* Idle-stall watchdog for one streamed model request. Returns a linked AbortController `signal` to
|
|
445
|
+
* hand to `chat()`, a `poke()` to reset the idle timer on each chunk, a `reclassify()` that turns a
|
|
446
|
+
* watchdog-fired abort into an Error{code:'stall'} (leaving a parent/user abort untouched), and a
|
|
447
|
+
* `clear()` to disarm. Aborts the request if no `poke()` (or completion) happens within `stallMs`.
|
|
448
|
+
* `stallMs = 0` disables the timer but still links parent-abort → child so cancellation propagates.
|
|
449
|
+
*/
|
|
450
|
+
private armStallWatchdog;
|
|
439
451
|
private consumeStream;
|
|
440
452
|
private dispatch;
|
|
441
453
|
private static readonly WRITE_CLASS;
|
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-
|
|
2
|
+
import { H as Hooks, h as RunResult, R as ReasoningEffort, A as Agent } from './Agent-Cn9BxS8h.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
|
@@ -3219,6 +3219,10 @@ var AgentOptions = class {
|
|
|
3219
3219
|
maxTokens = 2e5;
|
|
3220
3220
|
/** Wall-clock ceiling in ms for the whole run. 0 = unbounded. */
|
|
3221
3221
|
timeoutMs = 12e4;
|
|
3222
|
+
/** Idle-stall watchdog for a SINGLE streamed model request: abort it if no chunk (text OR reasoning)
|
|
3223
|
+
* arrives for this long. The between-steps `timeoutMs` can't preempt an in-flight `chat()`, so a
|
|
3224
|
+
* provider that goes silent mid-stream would otherwise park the loop forever. 0 = off. */
|
|
3225
|
+
stallMs = 6e4;
|
|
3222
3226
|
/** Stop if the identical tool-call batch (name+args) repeats this many times in a row. 0 = off. */
|
|
3223
3227
|
maxRepeats = 3;
|
|
3224
3228
|
/** Cumulative cap on tool calls dispatched across the run. 0 = unbounded. */
|
|
@@ -3651,8 +3655,15 @@ var Agent = class _Agent {
|
|
|
3651
3655
|
for (let attempt = 0; ; attempt++) {
|
|
3652
3656
|
try {
|
|
3653
3657
|
if (useStream) {
|
|
3654
|
-
const
|
|
3655
|
-
|
|
3658
|
+
const stall = this.armStallWatchdog(o.signal, o.stallMs);
|
|
3659
|
+
try {
|
|
3660
|
+
const r = await o.ai.chat({ model: o.model, messages: sent, tools: sentTools, stream: true, signal: stall.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
|
|
3661
|
+
res = await this.consumeStream(r, stall.poke);
|
|
3662
|
+
} catch (err2) {
|
|
3663
|
+
throw stall.reclassify(err2);
|
|
3664
|
+
} finally {
|
|
3665
|
+
stall.clear();
|
|
3666
|
+
}
|
|
3656
3667
|
} else {
|
|
3657
3668
|
const r = await o.ai.chat({ model: o.model, messages: sent, tools: sentTools, stream: false, signal: o.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
|
|
3658
3669
|
res = r;
|
|
@@ -3662,16 +3673,21 @@ var Agent = class _Agent {
|
|
|
3662
3673
|
const sc = err2?.statusCode;
|
|
3663
3674
|
const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err2?.message ?? ""));
|
|
3664
3675
|
const network = !sc && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|socket hang up|fetch failed|network|terminated|UND_ERR/i.test(String(err2?.message ?? err2?.code ?? err2));
|
|
3665
|
-
const
|
|
3676
|
+
const stalled = err2?.code === "stall";
|
|
3677
|
+
const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < 2 && (network || serverSide || stalled);
|
|
3666
3678
|
if (!transient) throw err2;
|
|
3667
3679
|
const waitMs = 1e3 * (attempt + 1);
|
|
3668
|
-
log4.warn(
|
|
3669
|
-
o.host?.notify?.({ kind: "retry", message:
|
|
3680
|
+
log4.warn(`${stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
3681
|
+
o.host?.notify?.({ kind: "retry", message: `${stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
3670
3682
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
3671
3683
|
}
|
|
3672
3684
|
}
|
|
3673
3685
|
} catch (err2) {
|
|
3674
3686
|
if (err2?.code === "budget") return kill("budget");
|
|
3687
|
+
if (err2?.code === "stall") {
|
|
3688
|
+
log4.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
|
|
3689
|
+
return kill("timeout");
|
|
3690
|
+
}
|
|
3675
3691
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
3676
3692
|
const body = err2?.body ?? err2?.response?.data ?? err2?.error;
|
|
3677
3693
|
let bodyStr;
|
|
@@ -3742,12 +3758,54 @@ var Agent = class _Agent {
|
|
|
3742
3758
|
* (`{kind:'text_delta'}`) and fold all chunks back into the single
|
|
3743
3759
|
* ChatResponse the non-stream path would have returned.
|
|
3744
3760
|
*/
|
|
3745
|
-
|
|
3761
|
+
/**
|
|
3762
|
+
* Idle-stall watchdog for one streamed model request. Returns a linked AbortController `signal` to
|
|
3763
|
+
* hand to `chat()`, a `poke()` to reset the idle timer on each chunk, a `reclassify()` that turns a
|
|
3764
|
+
* watchdog-fired abort into an Error{code:'stall'} (leaving a parent/user abort untouched), and a
|
|
3765
|
+
* `clear()` to disarm. Aborts the request if no `poke()` (or completion) happens within `stallMs`.
|
|
3766
|
+
* `stallMs = 0` disables the timer but still links parent-abort → child so cancellation propagates.
|
|
3767
|
+
*/
|
|
3768
|
+
armStallWatchdog(parent, stallMs) {
|
|
3769
|
+
const ac = new AbortController();
|
|
3770
|
+
let fired = false;
|
|
3771
|
+
const onParentAbort = () => ac.abort();
|
|
3772
|
+
if (parent?.aborted) ac.abort();
|
|
3773
|
+
else parent?.addEventListener("abort", onParentAbort, { once: true });
|
|
3774
|
+
let timer;
|
|
3775
|
+
const poke = () => {
|
|
3776
|
+
if (!stallMs) return;
|
|
3777
|
+
if (timer) clearTimeout(timer);
|
|
3778
|
+
timer = setTimeout(() => {
|
|
3779
|
+
fired = true;
|
|
3780
|
+
ac.abort();
|
|
3781
|
+
}, stallMs);
|
|
3782
|
+
};
|
|
3783
|
+
poke();
|
|
3784
|
+
return {
|
|
3785
|
+
signal: ac.signal,
|
|
3786
|
+
poke,
|
|
3787
|
+
clear: () => {
|
|
3788
|
+
if (timer) clearTimeout(timer);
|
|
3789
|
+
parent?.removeEventListener("abort", onParentAbort);
|
|
3790
|
+
},
|
|
3791
|
+
// Distinguish OUR abort (silent provider) from a parent/user cancel, which must stay an abort.
|
|
3792
|
+
reclassify: (err2) => {
|
|
3793
|
+
if (fired && !parent?.aborted) {
|
|
3794
|
+
const e = new Error(`model stalled: no stream output for ${stallMs >= 1e3 ? `${Math.round(stallMs / 1e3)}s` : `${stallMs}ms`}`);
|
|
3795
|
+
e.code = "stall";
|
|
3796
|
+
return e;
|
|
3797
|
+
}
|
|
3798
|
+
return err2;
|
|
3799
|
+
}
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
async consumeStream(stream, poke) {
|
|
3746
3803
|
let content = "";
|
|
3747
3804
|
let finishReason;
|
|
3748
3805
|
let toolCalls;
|
|
3749
3806
|
let usage;
|
|
3750
3807
|
for await (const chunk of stream) {
|
|
3808
|
+
poke?.();
|
|
3751
3809
|
if (this.options.signal?.aborted) break;
|
|
3752
3810
|
if (chunk.reasoningContent) this.options.host.notify({ kind: "thinking_delta", message: chunk.reasoningContent });
|
|
3753
3811
|
if (chunk.toolActivity) {
|
|
@@ -9836,11 +9894,15 @@ function lastToken(line) {
|
|
|
9836
9894
|
const m = line.match(/(\S*)$/);
|
|
9837
9895
|
return m ? m[1] : "";
|
|
9838
9896
|
}
|
|
9897
|
+
function atLineStart(line, token) {
|
|
9898
|
+
return line.slice(0, line.length - token.length).trim() === "";
|
|
9899
|
+
}
|
|
9839
9900
|
function completeLine(line, ctx) {
|
|
9840
9901
|
const token = lastToken(line);
|
|
9841
|
-
if (token.startsWith("/")
|
|
9902
|
+
if (token.startsWith("/")) {
|
|
9903
|
+
const pool = atLineStart(line, token) ? ctx.commands : ctx.agentCommands ?? ctx.commands;
|
|
9842
9904
|
const want = token.slice(1);
|
|
9843
|
-
const hits = rank(
|
|
9905
|
+
const hits = rank(pool.filter((c) => fuzzy(want, c)), want).map((c) => "/" + c);
|
|
9844
9906
|
return [hits, token];
|
|
9845
9907
|
}
|
|
9846
9908
|
if (line.startsWith("!") && line.length > 1 && ctx.bangHistory?.length) {
|
|
@@ -10426,6 +10488,14 @@ var EditorState = class _EditorState {
|
|
|
10426
10488
|
for (const [ph, full] of this.pastes) if (out.includes(ph)) out = out.split(ph).join(full);
|
|
10427
10489
|
return out;
|
|
10428
10490
|
}
|
|
10491
|
+
/**
|
|
10492
|
+
* A `/cmd` the REPL will DISPATCH — i.e. the line's first word. A slash token elsewhere in the
|
|
10493
|
+
* prompt is prose the model reads, so it completes like an `@path`: no accept-and-run, no
|
|
10494
|
+
* exact-match row. Kept in sync with the REPL's whole-line `/` dispatch via `atLineStart`.
|
|
10495
|
+
*/
|
|
10496
|
+
get runnable() {
|
|
10497
|
+
return this.token.startsWith("/") && atLineStart(this.buf.slice(0, this.cursor), this.token);
|
|
10498
|
+
}
|
|
10429
10499
|
/** Recompute suggestions from the line up to the cursor. */
|
|
10430
10500
|
refresh() {
|
|
10431
10501
|
const r = this.suggest(this.buf.slice(0, this.cursor));
|
|
@@ -10433,7 +10503,7 @@ var EditorState = class _EditorState {
|
|
|
10433
10503
|
this.token = r.token;
|
|
10434
10504
|
this.describe = r.describe;
|
|
10435
10505
|
const exact = r.hits.length === 1 && r.hits[0] === r.token;
|
|
10436
|
-
this.menuOpen = r.hits.length > 0 && (!exact ||
|
|
10506
|
+
this.menuOpen = r.hits.length > 0 && (!exact || this.runnable);
|
|
10437
10507
|
if (this.sel >= r.hits.length) this.sel = 0;
|
|
10438
10508
|
}
|
|
10439
10509
|
// ── Editor undo (Ctrl-_): snapshot before every mutation, pop to restore ──
|
|
@@ -10629,13 +10699,22 @@ var EditorState = class _EditorState {
|
|
|
10629
10699
|
this.hits = [];
|
|
10630
10700
|
this.sel = 0;
|
|
10631
10701
|
}
|
|
10632
|
-
/**
|
|
10702
|
+
/**
|
|
10703
|
+
* Replace the trailing token with the highlighted hit; re-suggest (e.g. `@src/` opens its children).
|
|
10704
|
+
* A finished hit gets a trailing space so the next word is typed straight away (CC/shell parity).
|
|
10705
|
+
* Two exceptions: a DIRECTORY hit (`@src/`) is a waypoint, not a word — a space there would end the
|
|
10706
|
+
* mention mid-path; and an existing following space isn't doubled. A spaced dir rides the quoted
|
|
10707
|
+
* form (`@"my dir/"`), so the `/` test must look past a trailing quote.
|
|
10708
|
+
*/
|
|
10633
10709
|
accept() {
|
|
10634
10710
|
if (!this.menuOpen || !this.hits.length) return false;
|
|
10635
10711
|
const hit = this.hits[this.sel] ?? this.hits[0];
|
|
10636
10712
|
const start = this.cursor - this.token.length;
|
|
10637
|
-
|
|
10638
|
-
|
|
10713
|
+
const rest = this.buf.slice(this.cursor);
|
|
10714
|
+
const isDir = hit.replace(/"$/, "").endsWith("/");
|
|
10715
|
+
const pad = isDir || rest.startsWith(" ") ? "" : " ";
|
|
10716
|
+
this.buf = this.buf.slice(0, start) + hit + pad + rest;
|
|
10717
|
+
this.cursor = start + hit.length + pad.length;
|
|
10639
10718
|
this.refresh();
|
|
10640
10719
|
return true;
|
|
10641
10720
|
}
|
|
@@ -10849,9 +10928,9 @@ function applyKey(s, key, str) {
|
|
|
10849
10928
|
case "return":
|
|
10850
10929
|
case "enter":
|
|
10851
10930
|
if (s.menuOpen) {
|
|
10852
|
-
const
|
|
10931
|
+
const run = s.runnable;
|
|
10853
10932
|
s.accept();
|
|
10854
|
-
return
|
|
10933
|
+
return run ? "submit" : "none";
|
|
10855
10934
|
}
|
|
10856
10935
|
if (s.buf.slice(0, s.cursor).endsWith("\\")) {
|
|
10857
10936
|
s.backspace();
|
|
@@ -14693,10 +14772,11 @@ ${task}`;
|
|
|
14693
14772
|
return null;
|
|
14694
14773
|
}
|
|
14695
14774
|
};
|
|
14696
|
-
const
|
|
14775
|
+
const agentCommandNames = () => [...cmds.map((c) => c.name), ...skills.map((s) => s.name)];
|
|
14776
|
+
const commandNames = () => [...Object.keys(builtins).filter((k) => k !== "quit"), ...agentCommandNames()];
|
|
14697
14777
|
const describe = (hit) => hit.startsWith("/") ? builtins[hit.slice(1)]?.desc ?? cmds.find((c) => c.name === hit.slice(1))?.description ?? skills.find((s) => s.name === hit.slice(1))?.description : void 0;
|
|
14698
14778
|
const suggest = (lineToCursor) => {
|
|
14699
|
-
const [hits, token] = completeLine(lineToCursor, { commands: commandNames(), listDir, bangHistory: history });
|
|
14779
|
+
const [hits, token] = completeLine(lineToCursor, { commands: commandNames(), agentCommands: agentCommandNames(), listDir, bangHistory: history });
|
|
14700
14780
|
return { hits, token, describe };
|
|
14701
14781
|
};
|
|
14702
14782
|
const editor = createLineEditor(process.stderr);
|