@livx.cc/agentx 0.99.16 → 0.99.18
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-CvLBalJE.d.ts → Agent-Cn9BxS8h.d.ts} +17 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +82 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +82 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -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. */
|
|
@@ -381,6 +385,11 @@ declare class Agent {
|
|
|
381
385
|
/** Drop any queued-but-unconsumed injections. The REPL calls this after a LOCAL command (/cmd, !bash)
|
|
382
386
|
* — which runs no send() to drain them — so a mid-command steer can't leak into the next real turn. */
|
|
383
387
|
clearInjections(): void;
|
|
388
|
+
/** Pull queued injections that are plain TEXT, removing them, for a runtime that can accept mid-turn
|
|
389
|
+
* steers over its own channel (claude-code's warm InputQueue via `providerOptions.steer`). Non-text
|
|
390
|
+
* injections (images) stay queued for the step-boundary `drainInjections()` re-open — the text-only
|
|
391
|
+
* steer channel can't carry them. Consumed DURING chat(); no overlap with drainInjections() (post-call). */
|
|
392
|
+
drainPendingSteers(): string[];
|
|
384
393
|
/** Fold any host-injected messages into the transcript as user turns. Returns true if any landed. */
|
|
385
394
|
private drainInjections;
|
|
386
395
|
constructor(options?: Partial<AgentOptions>);
|
|
@@ -431,6 +440,14 @@ declare class Agent {
|
|
|
431
440
|
* (`{kind:'text_delta'}`) and fold all chunks back into the single
|
|
432
441
|
* ChatResponse the non-stream path would have returned.
|
|
433
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;
|
|
434
451
|
private consumeStream;
|
|
435
452
|
private dispatch;
|
|
436
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. */
|
|
@@ -3421,6 +3425,22 @@ var Agent = class _Agent {
|
|
|
3421
3425
|
clearInjections() {
|
|
3422
3426
|
this.pendingInjections.length = 0;
|
|
3423
3427
|
}
|
|
3428
|
+
/** Pull queued injections that are plain TEXT, removing them, for a runtime that can accept mid-turn
|
|
3429
|
+
* steers over its own channel (claude-code's warm InputQueue via `providerOptions.steer`). Non-text
|
|
3430
|
+
* injections (images) stay queued for the step-boundary `drainInjections()` re-open — the text-only
|
|
3431
|
+
* steer channel can't carry them. Consumed DURING chat(); no overlap with drainInjections() (post-call). */
|
|
3432
|
+
drainPendingSteers() {
|
|
3433
|
+
if (!this.pendingInjections.length) return [];
|
|
3434
|
+
const texts = [];
|
|
3435
|
+
this.pendingInjections = this.pendingInjections.filter((c) => {
|
|
3436
|
+
if (typeof c === "string") {
|
|
3437
|
+
texts.push(c);
|
|
3438
|
+
return false;
|
|
3439
|
+
}
|
|
3440
|
+
return true;
|
|
3441
|
+
});
|
|
3442
|
+
return texts;
|
|
3443
|
+
}
|
|
3424
3444
|
/** Fold any host-injected messages into the transcript as user turns. Returns true if any landed. */
|
|
3425
3445
|
drainInjections() {
|
|
3426
3446
|
if (!this.pendingInjections.length) return false;
|
|
@@ -3625,17 +3645,25 @@ var Agent = class _Agent {
|
|
|
3625
3645
|
return typeof raw === "string" ? raw : raw.text;
|
|
3626
3646
|
}
|
|
3627
3647
|
} : void 0;
|
|
3648
|
+
const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
|
|
3628
3649
|
const reasonOpts = {
|
|
3629
3650
|
...frag,
|
|
3630
3651
|
...o.promptCache ? { promptCache: true } : {},
|
|
3631
|
-
...o.providerOptions || cursorPo ? { providerOptions: { ...frag.providerOptions, ...o.providerOptions, ...cursorPo } } : {}
|
|
3652
|
+
...o.providerOptions || cursorPo || claudeSteerPo ? { providerOptions: { ...frag.providerOptions, ...o.providerOptions, ...cursorPo, ...claudeSteerPo } } : {}
|
|
3632
3653
|
};
|
|
3633
3654
|
try {
|
|
3634
3655
|
for (let attempt = 0; ; attempt++) {
|
|
3635
3656
|
try {
|
|
3636
3657
|
if (useStream) {
|
|
3637
|
-
const
|
|
3638
|
-
|
|
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
|
+
}
|
|
3639
3667
|
} else {
|
|
3640
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 });
|
|
3641
3669
|
res = r;
|
|
@@ -3645,16 +3673,21 @@ var Agent = class _Agent {
|
|
|
3645
3673
|
const sc = err2?.statusCode;
|
|
3646
3674
|
const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err2?.message ?? ""));
|
|
3647
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));
|
|
3648
|
-
const
|
|
3676
|
+
const stalled = err2?.code === "stall";
|
|
3677
|
+
const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < 2 && (network || serverSide || stalled);
|
|
3649
3678
|
if (!transient) throw err2;
|
|
3650
3679
|
const waitMs = 1e3 * (attempt + 1);
|
|
3651
|
-
log4.warn(
|
|
3652
|
-
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})` });
|
|
3653
3682
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
3654
3683
|
}
|
|
3655
3684
|
}
|
|
3656
3685
|
} catch (err2) {
|
|
3657
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
|
+
}
|
|
3658
3691
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
3659
3692
|
const body = err2?.body ?? err2?.response?.data ?? err2?.error;
|
|
3660
3693
|
let bodyStr;
|
|
@@ -3725,12 +3758,54 @@ var Agent = class _Agent {
|
|
|
3725
3758
|
* (`{kind:'text_delta'}`) and fold all chunks back into the single
|
|
3726
3759
|
* ChatResponse the non-stream path would have returned.
|
|
3727
3760
|
*/
|
|
3728
|
-
|
|
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) {
|
|
3729
3803
|
let content = "";
|
|
3730
3804
|
let finishReason;
|
|
3731
3805
|
let toolCalls;
|
|
3732
3806
|
let usage;
|
|
3733
3807
|
for await (const chunk of stream) {
|
|
3808
|
+
poke?.();
|
|
3734
3809
|
if (this.options.signal?.aborted) break;
|
|
3735
3810
|
if (chunk.reasoningContent) this.options.host.notify({ kind: "thinking_delta", message: chunk.reasoningContent });
|
|
3736
3811
|
if (chunk.toolActivity) {
|