@livx.cc/agentx 0.99.45 → 0.99.46
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-Bzs57-iD.d.ts → Agent-OcAg2yxA.d.ts} +14 -0
- package/dist/cli.d.ts +4 -3
- package/dist/cli.js +56 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +54 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -186,6 +186,8 @@ interface RunResult {
|
|
|
186
186
|
* Answer it and resume the session to continue. */
|
|
187
187
|
finishReason: 'stop' | 'max_steps' | 'budget' | 'timeout' | 'loop' | 'max_tool_calls' | 'aborted' | 'error' | 'needs_input' | 'empty';
|
|
188
188
|
messages: Message[];
|
|
189
|
+
/** The provider's own finish reason for the final model call (e.g. 'stop', 'end_turn', 'length'), when it gave one. */
|
|
190
|
+
providerFinishReason?: string;
|
|
189
191
|
/** The parked question — present iff `finishReason === 'needs_input'`. */
|
|
190
192
|
question?: UserQuestion;
|
|
191
193
|
/** Accumulated token usage across all turns (non-stream path). With prompt caching,
|
|
@@ -352,6 +354,10 @@ declare class AgentOptions {
|
|
|
352
354
|
* not prose: a named tool with zero invocations is provable. On detection the loop nudges ONCE for a real
|
|
353
355
|
* call, and records the names in `RunResult.fabricatedToolClaims` either way. */
|
|
354
356
|
catchFabricatedToolUse: boolean;
|
|
357
|
+
/** An empty model turn (no text, no tools) that ends the loop after work is re-prompted ONCE, then finishes
|
|
358
|
+
* as `finishReason:'empty'` (a failure) instead of a phantom 'stop'. Off for layers that deliberately
|
|
359
|
+
* allow a silent turn and repair it themselves (the duplex voice reflex). */
|
|
360
|
+
failEmptyTurns: boolean;
|
|
355
361
|
/** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
|
|
356
362
|
compaction?: {
|
|
357
363
|
maxMessages: number;
|
|
@@ -415,6 +421,14 @@ declare class Agent {
|
|
|
415
421
|
/** Per-run ground truth: every tool name actually INVOKED this run (native dispatch + delegated
|
|
416
422
|
* runtime activity). The ledger that makes a fabricated tool claim provable. Reset per run. */
|
|
417
423
|
private invokedTools;
|
|
424
|
+
/** Delegated-runtime HOST tools currently executing (cursor toolExecutor). While > 0 the provider is
|
|
425
|
+
* not silent — it is waiting on US — so the idle-stall watchdog must not fire (see armStallWatchdog). */
|
|
426
|
+
private hostToolsInFlight;
|
|
427
|
+
/** The hold is bounded by the transport tool deadline: a host tool that never settles (the helper already gave
|
|
428
|
+
* up on it) must not disable stall protection for the rest of the request — or this instance. */
|
|
429
|
+
private hostToolHoldUntil;
|
|
430
|
+
/** Resets the active request's stall timer; a host tool's completion restarts the model's idle window. */
|
|
431
|
+
private stallPoke?;
|
|
418
432
|
/** Advertised tools this run's text claimed to have used while the ledger showed zero calls. */
|
|
419
433
|
private fabricatedClaims;
|
|
420
434
|
private activeHooks?;
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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-OcAg2yxA.js';
|
|
3
3
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
4
4
|
import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, g as MessageContent } from './tools-BqL8Lk4J.js';
|
|
5
5
|
|
|
@@ -277,8 +277,6 @@ declare function expandMentions(fs: IFilesystem, line: string): Promise<{
|
|
|
277
277
|
declare function jsonResult(res: RunResult, session: SessionData): {
|
|
278
278
|
error?: any;
|
|
279
279
|
question?: UserQuestion | undefined;
|
|
280
|
-
ok: boolean;
|
|
281
|
-
finishReason: "error" | "budget" | "stop" | "max_steps" | "timeout" | "loop" | "max_tool_calls" | "aborted" | "needs_input" | "empty";
|
|
282
280
|
text: string;
|
|
283
281
|
steps: number;
|
|
284
282
|
tools: number;
|
|
@@ -290,6 +288,9 @@ declare function jsonResult(res: RunResult, session: SessionData): {
|
|
|
290
288
|
cacheReadTokens?: number;
|
|
291
289
|
} | undefined;
|
|
292
290
|
sessionId: string;
|
|
291
|
+
providerFinishReason?: string | undefined;
|
|
292
|
+
ok: boolean;
|
|
293
|
+
finishReason: "error" | "budget" | "stop" | "max_steps" | "timeout" | "loop" | "max_tool_calls" | "aborted" | "needs_input" | "empty";
|
|
293
294
|
};
|
|
294
295
|
/**
|
|
295
296
|
* Read one logical input, supporting `\`-continuation for multi-line prompts: a line ending in
|
package/dist/cli.js
CHANGED
|
@@ -3961,6 +3961,10 @@ var AgentOptions = class {
|
|
|
3961
3961
|
* not prose: a named tool with zero invocations is provable. On detection the loop nudges ONCE for a real
|
|
3962
3962
|
* call, and records the names in `RunResult.fabricatedToolClaims` either way. */
|
|
3963
3963
|
catchFabricatedToolUse = true;
|
|
3964
|
+
/** An empty model turn (no text, no tools) that ends the loop after work is re-prompted ONCE, then finishes
|
|
3965
|
+
* as `finishReason:'empty'` (a failure) instead of a phantom 'stop'. Off for layers that deliberately
|
|
3966
|
+
* allow a silent turn and repair it themselves (the duplex voice reflex). */
|
|
3967
|
+
failEmptyTurns = true;
|
|
3964
3968
|
/** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
|
|
3965
3969
|
compaction;
|
|
3966
3970
|
/** Add `Checkpoint`/`Rollback` tools (requires the fs to be an OverlayFilesystem). */
|
|
@@ -4034,6 +4038,14 @@ var Agent = class _Agent {
|
|
|
4034
4038
|
/** Per-run ground truth: every tool name actually INVOKED this run (native dispatch + delegated
|
|
4035
4039
|
* runtime activity). The ledger that makes a fabricated tool claim provable. Reset per run. */
|
|
4036
4040
|
invokedTools = /* @__PURE__ */ new Set();
|
|
4041
|
+
/** Delegated-runtime HOST tools currently executing (cursor toolExecutor). While > 0 the provider is
|
|
4042
|
+
* not silent — it is waiting on US — so the idle-stall watchdog must not fire (see armStallWatchdog). */
|
|
4043
|
+
hostToolsInFlight = 0;
|
|
4044
|
+
/** The hold is bounded by the transport tool deadline: a host tool that never settles (the helper already gave
|
|
4045
|
+
* up on it) must not disable stall protection for the rest of the request — or this instance. */
|
|
4046
|
+
hostToolHoldUntil = 0;
|
|
4047
|
+
/** Resets the active request's stall timer; a host tool's completion restarts the model's idle window. */
|
|
4048
|
+
stallPoke;
|
|
4037
4049
|
/** Advertised tools this run's text claimed to have used while the ledger showed zero calls. */
|
|
4038
4050
|
fabricatedClaims = /* @__PURE__ */ new Set();
|
|
4039
4051
|
activeHooks;
|
|
@@ -4336,6 +4348,7 @@ var Agent = class _Agent {
|
|
|
4336
4348
|
let toolCallsTotal = 0;
|
|
4337
4349
|
let lastFp = "";
|
|
4338
4350
|
let repeats = 0;
|
|
4351
|
+
let emptyNudged = false;
|
|
4339
4352
|
let closureNudged = false;
|
|
4340
4353
|
let lastProgressAt = start;
|
|
4341
4354
|
const kill = (finishReason) => {
|
|
@@ -4344,12 +4357,13 @@ var Agent = class _Agent {
|
|
|
4344
4357
|
const claims = this.survivingClaims();
|
|
4345
4358
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated, ...claims.length ? { fabricatedToolClaims: claims } : {} };
|
|
4346
4359
|
};
|
|
4360
|
+
const overBudget = () => !!o.maxTokens && usage.totalTokens - 0.9 * usage.cacheReadTokens >= o.maxTokens;
|
|
4361
|
+
const canNudge = () => steps < o.maxSteps && !overBudget();
|
|
4347
4362
|
while (true) {
|
|
4348
4363
|
if (o.signal?.aborted) return kill("aborted");
|
|
4349
4364
|
if (steps >= o.maxSteps) return kill("max_steps");
|
|
4350
4365
|
if (o.timeoutMs && Date.now() - lastProgressAt - this.parkedMs >= o.timeoutMs) return kill("timeout");
|
|
4351
|
-
|
|
4352
|
-
if (o.maxTokens && budgetTokens >= o.maxTokens) return kill("budget");
|
|
4366
|
+
if (overBudget()) return kill("budget");
|
|
4353
4367
|
steps++;
|
|
4354
4368
|
this.options.host?.notify?.({ kind: "turn_start", message: `step ${steps}` });
|
|
4355
4369
|
let res;
|
|
@@ -4370,7 +4384,14 @@ var Agent = class _Agent {
|
|
|
4370
4384
|
toolTimeoutMs: effectiveToolTimeoutMs,
|
|
4371
4385
|
toolExecutor: async (name, args) => {
|
|
4372
4386
|
const tc = { id: `cursor-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
4373
|
-
|
|
4387
|
+
this.hostToolsInFlight++;
|
|
4388
|
+
this.hostToolHoldUntil = Math.max(this.hostToolHoldUntil, Date.now() + effectiveToolTimeoutMs);
|
|
4389
|
+
try {
|
|
4390
|
+
return await this.dispatch(tc);
|
|
4391
|
+
} finally {
|
|
4392
|
+
this.hostToolsInFlight--;
|
|
4393
|
+
this.stallPoke?.();
|
|
4394
|
+
}
|
|
4374
4395
|
}
|
|
4375
4396
|
} : void 0;
|
|
4376
4397
|
const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
|
|
@@ -4491,9 +4512,22 @@ var Agent = class _Agent {
|
|
|
4491
4512
|
}
|
|
4492
4513
|
if (toolCalls.length === 0) {
|
|
4493
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
|
+
}
|
|
4494
4527
|
const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
|
|
4495
4528
|
for (const n of fabricated) this.fabricatedClaims.add(n);
|
|
4496
|
-
if (fabricated.length &&
|
|
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()) {
|
|
4497
4531
|
closureNudged = true;
|
|
4498
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`);
|
|
4499
4533
|
this.transcript.push({
|
|
@@ -4502,7 +4536,7 @@ var Agent = class _Agent {
|
|
|
4502
4536
|
});
|
|
4503
4537
|
continue;
|
|
4504
4538
|
}
|
|
4505
|
-
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged &&
|
|
4539
|
+
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
|
|
4506
4540
|
closureNudged = true;
|
|
4507
4541
|
log6.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
|
|
4508
4542
|
this.transcript.push({
|
|
@@ -4516,7 +4550,7 @@ var Agent = class _Agent {
|
|
|
4516
4550
|
this.releaseStaleImages();
|
|
4517
4551
|
await this.activeHooks?.onStop?.(res.content ?? "");
|
|
4518
4552
|
const claims = this.survivingClaims();
|
|
4519
|
-
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 } : {} };
|
|
4520
4554
|
}
|
|
4521
4555
|
const fp = toolCalls.map((tc) => tc.function.name + ":" + (tc.function.arguments ?? "")).join("|");
|
|
4522
4556
|
repeats = fp === lastFp ? repeats + 1 : 1;
|
|
@@ -4580,10 +4614,12 @@ var Agent = class _Agent {
|
|
|
4580
4614
|
if (!stallMs) return;
|
|
4581
4615
|
if (timer) clearTimeout(timer);
|
|
4582
4616
|
timer = setTimeout(() => {
|
|
4617
|
+
if (this.hostToolsInFlight > 0 && Date.now() < this.hostToolHoldUntil) return poke();
|
|
4583
4618
|
fired = true;
|
|
4584
4619
|
ac.abort(stallError());
|
|
4585
4620
|
}, stallMs);
|
|
4586
4621
|
};
|
|
4622
|
+
this.stallPoke = poke;
|
|
4587
4623
|
poke();
|
|
4588
4624
|
const stalled = () => fired && !parent?.aborted;
|
|
4589
4625
|
return {
|
|
@@ -4592,6 +4628,7 @@ var Agent = class _Agent {
|
|
|
4592
4628
|
stalled,
|
|
4593
4629
|
clear: () => {
|
|
4594
4630
|
if (timer) clearTimeout(timer);
|
|
4631
|
+
if (this.stallPoke === poke) this.stallPoke = void 0;
|
|
4595
4632
|
parent?.removeEventListener("abort", onParentAbort);
|
|
4596
4633
|
},
|
|
4597
4634
|
reclassify: (err2) => stalled() ? stallError() : err2
|
|
@@ -4687,8 +4724,9 @@ var Agent = class _Agent {
|
|
|
4687
4724
|
*/
|
|
4688
4725
|
fabricatedToolNames(text) {
|
|
4689
4726
|
const out = [];
|
|
4727
|
+
const invoked = new Set([...this.invokedTools].map(toolKey));
|
|
4690
4728
|
for (const t of this.activeTools) {
|
|
4691
|
-
if (
|
|
4729
|
+
if (invoked.has(toolKey(t.name))) continue;
|
|
4692
4730
|
if (t.name.length < 4 || !/[_\-A-Z]/.test(t.name)) continue;
|
|
4693
4731
|
const re = new RegExp(`(?:^|[^\\w])${t.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^\\w]|$)`);
|
|
4694
4732
|
if (re.test(text)) out.push(t.name);
|
|
@@ -4701,7 +4739,8 @@ var Agent = class _Agent {
|
|
|
4701
4739
|
* call that demonstrably ran. See `RunResult.fabricatedToolClaims`. */
|
|
4702
4740
|
survivingClaims() {
|
|
4703
4741
|
if (!this.fabricatedClaims.size) return [];
|
|
4704
|
-
|
|
4742
|
+
const invoked = new Set([...this.invokedTools].map(toolKey));
|
|
4743
|
+
return [...this.fabricatedClaims].filter((n) => !invoked.has(toolKey(n)));
|
|
4705
4744
|
}
|
|
4706
4745
|
async dispatch(tc) {
|
|
4707
4746
|
this.invokedTools.add(tc.function.name);
|
|
@@ -5089,6 +5128,11 @@ function lastAssistantText(messages) {
|
|
|
5089
5128
|
}
|
|
5090
5129
|
return "";
|
|
5091
5130
|
}
|
|
5131
|
+
var TOOL_ALIASES = { bash: "shell" };
|
|
5132
|
+
function toolKey(name) {
|
|
5133
|
+
const k = name.toLowerCase();
|
|
5134
|
+
return TOOL_ALIASES[k] ?? k;
|
|
5135
|
+
}
|
|
5092
5136
|
|
|
5093
5137
|
// src/index.ts
|
|
5094
5138
|
init_NodeDiskFilesystem();
|
|
@@ -7772,6 +7816,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
7772
7816
|
instructionFiles: false,
|
|
7773
7817
|
maxSteps: 8,
|
|
7774
7818
|
timeoutMs: 3e4,
|
|
7819
|
+
failEmptyTurns: false,
|
|
7820
|
+
// a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
|
|
7775
7821
|
...o.reflexOptions,
|
|
7776
7822
|
tools,
|
|
7777
7823
|
// Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
|
|
@@ -14071,6 +14117,7 @@ function jsonResult(res, session) {
|
|
|
14071
14117
|
return {
|
|
14072
14118
|
ok: res.finishReason === "stop",
|
|
14073
14119
|
finishReason: res.finishReason,
|
|
14120
|
+
...res.providerFinishReason ? { providerFinishReason: res.providerFinishReason } : {},
|
|
14074
14121
|
text: res.text,
|
|
14075
14122
|
steps: res.steps,
|
|
14076
14123
|
tools: res.messages.slice(lastUser).filter((m) => m.role === "tool").length,
|
|
@@ -14078,7 +14125,7 @@ function jsonResult(res, session) {
|
|
|
14078
14125
|
sessionId: session.meta.id,
|
|
14079
14126
|
// The parked question, machine-readable — the caller answers it and resumes `sessionId`.
|
|
14080
14127
|
...res.finishReason === "needs_input" && res.question ? { question: res.question } : {},
|
|
14081
|
-
...res.finishReason === "error" && res.error ? { error: res.error?.message ?? String(res.error) } : {}
|
|
14128
|
+
...(res.finishReason === "error" || res.finishReason === "empty") && res.error ? { error: res.error?.message ?? String(res.error) } : {}
|
|
14082
14129
|
};
|
|
14083
14130
|
}
|
|
14084
14131
|
async function readMultiline(readLine) {
|