@livx.cc/agentx 0.99.44 → 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-f4um2q8E.d.ts → Agent-OcAg2yxA.d.ts} +15 -1
- package/dist/cli.d.ts +5 -4
- package/dist/cli.js +68 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +66 -12
- package/dist/index.js.map +1 -1
- package/dist/{mcp-D5eN0JyQ.d.ts → mcp-kgG6rtsd.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/{tools-S-ArIR3o.d.ts → tools-BqL8Lk4J.d.ts} +4 -0
- package/dist/tools.shell.d.ts +1 -1
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
|
@@ -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, g as MessageContent, U as UserQuestion } from './tools-
|
|
2
|
+
import { M as Message, H as HostBridge, A as AgentTool, C as ChatLike, g as MessageContent, U as UserQuestion } from './tools-BqL8Lk4J.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Hooks — deterministic interception points around tool execution, run by the
|
|
@@ -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,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-OcAg2yxA.js';
|
|
3
3
|
import { IFilesystem } from '@livx.cc/wcli/core';
|
|
4
|
-
import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, g as MessageContent } from './tools-
|
|
4
|
+
import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, g as MessageContent } from './tools-BqL8Lk4J.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* On-disk session store for the CLI: each conversation is one JSON file at
|
|
@@ -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
|
@@ -23,6 +23,9 @@ var init_logging = __esm({
|
|
|
23
23
|
import { readFileSync, statSync as statSync2, realpathSync } from "fs";
|
|
24
24
|
import { resolve, sep } from "path";
|
|
25
25
|
import { randomBytes } from "crypto";
|
|
26
|
+
function passesBackReasoning(model) {
|
|
27
|
+
return !!model && /^(deepseek|moonshot)\//.test(model);
|
|
28
|
+
}
|
|
26
29
|
function contentText(content) {
|
|
27
30
|
if (content == null) return "";
|
|
28
31
|
if (typeof content === "string") return content;
|
|
@@ -203,7 +206,7 @@ function sendBytes(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
|
203
206
|
}
|
|
204
207
|
function sendBytesPer(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
205
208
|
const keep = messages.some(bearsImage) ? imageWindows(messages, keepRecent) : null;
|
|
206
|
-
return messages.map((m, i) => wireBytes(m.content, keep == null || keep.has(i), stub) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0));
|
|
209
|
+
return messages.map((m, i) => wireBytes(m.content, keep == null || keep.has(i), stub) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0) + (m.reasoning_content?.length ?? 0));
|
|
207
210
|
}
|
|
208
211
|
function wireBytes(content, kept, stub) {
|
|
209
212
|
const tool = toolImageRef(content);
|
|
@@ -3958,6 +3961,10 @@ var AgentOptions = class {
|
|
|
3958
3961
|
* not prose: a named tool with zero invocations is provable. On detection the loop nudges ONCE for a real
|
|
3959
3962
|
* call, and records the names in `RunResult.fabricatedToolClaims` either way. */
|
|
3960
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;
|
|
3961
3968
|
/** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
|
|
3962
3969
|
compaction;
|
|
3963
3970
|
/** Add `Checkpoint`/`Rollback` tools (requires the fs to be an OverlayFilesystem). */
|
|
@@ -4031,6 +4038,14 @@ var Agent = class _Agent {
|
|
|
4031
4038
|
/** Per-run ground truth: every tool name actually INVOKED this run (native dispatch + delegated
|
|
4032
4039
|
* runtime activity). The ledger that makes a fabricated tool claim provable. Reset per run. */
|
|
4033
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;
|
|
4034
4049
|
/** Advertised tools this run's text claimed to have used while the ledger showed zero calls. */
|
|
4035
4050
|
fabricatedClaims = /* @__PURE__ */ new Set();
|
|
4036
4051
|
activeHooks;
|
|
@@ -4333,6 +4348,7 @@ var Agent = class _Agent {
|
|
|
4333
4348
|
let toolCallsTotal = 0;
|
|
4334
4349
|
let lastFp = "";
|
|
4335
4350
|
let repeats = 0;
|
|
4351
|
+
let emptyNudged = false;
|
|
4336
4352
|
let closureNudged = false;
|
|
4337
4353
|
let lastProgressAt = start;
|
|
4338
4354
|
const kill = (finishReason) => {
|
|
@@ -4341,12 +4357,13 @@ var Agent = class _Agent {
|
|
|
4341
4357
|
const claims = this.survivingClaims();
|
|
4342
4358
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated, ...claims.length ? { fabricatedToolClaims: claims } : {} };
|
|
4343
4359
|
};
|
|
4360
|
+
const overBudget = () => !!o.maxTokens && usage.totalTokens - 0.9 * usage.cacheReadTokens >= o.maxTokens;
|
|
4361
|
+
const canNudge = () => steps < o.maxSteps && !overBudget();
|
|
4344
4362
|
while (true) {
|
|
4345
4363
|
if (o.signal?.aborted) return kill("aborted");
|
|
4346
4364
|
if (steps >= o.maxSteps) return kill("max_steps");
|
|
4347
4365
|
if (o.timeoutMs && Date.now() - lastProgressAt - this.parkedMs >= o.timeoutMs) return kill("timeout");
|
|
4348
|
-
|
|
4349
|
-
if (o.maxTokens && budgetTokens >= o.maxTokens) return kill("budget");
|
|
4366
|
+
if (overBudget()) return kill("budget");
|
|
4350
4367
|
steps++;
|
|
4351
4368
|
this.options.host?.notify?.({ kind: "turn_start", message: `step ${steps}` });
|
|
4352
4369
|
let res;
|
|
@@ -4367,7 +4384,14 @@ var Agent = class _Agent {
|
|
|
4367
4384
|
toolTimeoutMs: effectiveToolTimeoutMs,
|
|
4368
4385
|
toolExecutor: async (name, args) => {
|
|
4369
4386
|
const tc = { id: `cursor-${Date.now()}`, type: "function", function: { name, arguments: JSON.stringify(args) } };
|
|
4370
|
-
|
|
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
|
+
}
|
|
4371
4395
|
}
|
|
4372
4396
|
} : void 0;
|
|
4373
4397
|
const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
|
|
@@ -4482,14 +4506,28 @@ var Agent = class _Agent {
|
|
|
4482
4506
|
this.transcript.push({
|
|
4483
4507
|
role: "assistant",
|
|
4484
4508
|
content: res.content ?? "",
|
|
4485
|
-
...toolCalls.length ? { tool_calls: toolCalls } : {}
|
|
4509
|
+
...toolCalls.length ? { tool_calls: toolCalls } : {},
|
|
4510
|
+
...res.reasoningContent && passesBackReasoning(o.model) ? { reasoning_content: res.reasoningContent } : {}
|
|
4486
4511
|
});
|
|
4487
4512
|
}
|
|
4488
4513
|
if (toolCalls.length === 0) {
|
|
4489
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
|
+
}
|
|
4490
4527
|
const fabricated = o.catchFabricatedToolUse && !closureNudged ? this.fabricatedToolNames(contentText(res.content ?? "")) : [];
|
|
4491
4528
|
for (const n of fabricated) this.fabricatedClaims.add(n);
|
|
4492
|
-
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()) {
|
|
4493
4531
|
closureNudged = true;
|
|
4494
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`);
|
|
4495
4533
|
this.transcript.push({
|
|
@@ -4498,7 +4536,7 @@ var Agent = class _Agent {
|
|
|
4498
4536
|
});
|
|
4499
4537
|
continue;
|
|
4500
4538
|
}
|
|
4501
|
-
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged &&
|
|
4539
|
+
if (o.closeDelegatedTurns && res.endedWithoutClosing && !closureNudged && canNudge()) {
|
|
4502
4540
|
closureNudged = true;
|
|
4503
4541
|
log6.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
|
|
4504
4542
|
this.transcript.push({
|
|
@@ -4512,7 +4550,7 @@ var Agent = class _Agent {
|
|
|
4512
4550
|
this.releaseStaleImages();
|
|
4513
4551
|
await this.activeHooks?.onStop?.(res.content ?? "");
|
|
4514
4552
|
const claims = this.survivingClaims();
|
|
4515
|
-
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 } : {} };
|
|
4516
4554
|
}
|
|
4517
4555
|
const fp = toolCalls.map((tc) => tc.function.name + ":" + (tc.function.arguments ?? "")).join("|");
|
|
4518
4556
|
repeats = fp === lastFp ? repeats + 1 : 1;
|
|
@@ -4576,10 +4614,12 @@ var Agent = class _Agent {
|
|
|
4576
4614
|
if (!stallMs) return;
|
|
4577
4615
|
if (timer) clearTimeout(timer);
|
|
4578
4616
|
timer = setTimeout(() => {
|
|
4617
|
+
if (this.hostToolsInFlight > 0 && Date.now() < this.hostToolHoldUntil) return poke();
|
|
4579
4618
|
fired = true;
|
|
4580
4619
|
ac.abort(stallError());
|
|
4581
4620
|
}, stallMs);
|
|
4582
4621
|
};
|
|
4622
|
+
this.stallPoke = poke;
|
|
4583
4623
|
poke();
|
|
4584
4624
|
const stalled = () => fired && !parent?.aborted;
|
|
4585
4625
|
return {
|
|
@@ -4588,6 +4628,7 @@ var Agent = class _Agent {
|
|
|
4588
4628
|
stalled,
|
|
4589
4629
|
clear: () => {
|
|
4590
4630
|
if (timer) clearTimeout(timer);
|
|
4631
|
+
if (this.stallPoke === poke) this.stallPoke = void 0;
|
|
4591
4632
|
parent?.removeEventListener("abort", onParentAbort);
|
|
4592
4633
|
},
|
|
4593
4634
|
reclassify: (err2) => stalled() ? stallError() : err2
|
|
@@ -4608,10 +4649,14 @@ var Agent = class _Agent {
|
|
|
4608
4649
|
const delegatedOrder = [];
|
|
4609
4650
|
let sawTool = false;
|
|
4610
4651
|
let trailing = "";
|
|
4652
|
+
let reasoning = "";
|
|
4611
4653
|
for await (const chunk of stream) {
|
|
4612
4654
|
poke?.();
|
|
4613
4655
|
if (this.options.signal?.aborted) break;
|
|
4614
|
-
if (chunk.reasoningContent)
|
|
4656
|
+
if (chunk.reasoningContent) {
|
|
4657
|
+
reasoning += chunk.reasoningContent;
|
|
4658
|
+
this.options.host.notify({ kind: "thinking_delta", message: chunk.reasoningContent });
|
|
4659
|
+
}
|
|
4615
4660
|
if (chunk.toolActivity) {
|
|
4616
4661
|
const a = chunk.toolActivity;
|
|
4617
4662
|
const verb = a.status === "error" ? "\u2717" : a.status === "completed" ? "\u2713" : "\u2192";
|
|
@@ -4642,7 +4687,7 @@ var Agent = class _Agent {
|
|
|
4642
4687
|
}
|
|
4643
4688
|
const delegatedTools = delegatedOrder.map((k) => delegated.get(k));
|
|
4644
4689
|
const endedWithoutClosing = sawTool && (!trailing.trim() || endsOnAnnouncedAction(trailing));
|
|
4645
|
-
return { content, ...finishReason ? { finishReason } : {}, ...toolCalls ? { toolCalls } : {}, ...usage ? { usage } : {}, ...delegatedTools.length ? { delegatedTools } : {}, ...endedWithoutClosing ? { endedWithoutClosing: true } : {} };
|
|
4690
|
+
return { content, ...reasoning ? { reasoningContent: reasoning } : {}, ...finishReason ? { finishReason } : {}, ...toolCalls ? { toolCalls } : {}, ...usage ? { usage } : {}, ...delegatedTools.length ? { delegatedTools } : {}, ...endedWithoutClosing ? { endedWithoutClosing: true } : {} };
|
|
4646
4691
|
}
|
|
4647
4692
|
/**
|
|
4648
4693
|
* FABRICATED TOOL USE — the failure no call-reactive layer can see, because there IS no call.
|
|
@@ -4679,8 +4724,9 @@ var Agent = class _Agent {
|
|
|
4679
4724
|
*/
|
|
4680
4725
|
fabricatedToolNames(text) {
|
|
4681
4726
|
const out = [];
|
|
4727
|
+
const invoked = new Set([...this.invokedTools].map(toolKey));
|
|
4682
4728
|
for (const t of this.activeTools) {
|
|
4683
|
-
if (
|
|
4729
|
+
if (invoked.has(toolKey(t.name))) continue;
|
|
4684
4730
|
if (t.name.length < 4 || !/[_\-A-Z]/.test(t.name)) continue;
|
|
4685
4731
|
const re = new RegExp(`(?:^|[^\\w])${t.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^\\w]|$)`);
|
|
4686
4732
|
if (re.test(text)) out.push(t.name);
|
|
@@ -4693,7 +4739,8 @@ var Agent = class _Agent {
|
|
|
4693
4739
|
* call that demonstrably ran. See `RunResult.fabricatedToolClaims`. */
|
|
4694
4740
|
survivingClaims() {
|
|
4695
4741
|
if (!this.fabricatedClaims.size) return [];
|
|
4696
|
-
|
|
4742
|
+
const invoked = new Set([...this.invokedTools].map(toolKey));
|
|
4743
|
+
return [...this.fabricatedClaims].filter((n) => !invoked.has(toolKey(n)));
|
|
4697
4744
|
}
|
|
4698
4745
|
async dispatch(tc) {
|
|
4699
4746
|
this.invokedTools.add(tc.function.name);
|
|
@@ -5081,6 +5128,11 @@ function lastAssistantText(messages) {
|
|
|
5081
5128
|
}
|
|
5082
5129
|
return "";
|
|
5083
5130
|
}
|
|
5131
|
+
var TOOL_ALIASES = { bash: "shell" };
|
|
5132
|
+
function toolKey(name) {
|
|
5133
|
+
const k = name.toLowerCase();
|
|
5134
|
+
return TOOL_ALIASES[k] ?? k;
|
|
5135
|
+
}
|
|
5084
5136
|
|
|
5085
5137
|
// src/index.ts
|
|
5086
5138
|
init_NodeDiskFilesystem();
|
|
@@ -7764,6 +7816,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
7764
7816
|
instructionFiles: false,
|
|
7765
7817
|
maxSteps: 8,
|
|
7766
7818
|
timeoutMs: 3e4,
|
|
7819
|
+
failEmptyTurns: false,
|
|
7820
|
+
// a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
|
|
7767
7821
|
...o.reflexOptions,
|
|
7768
7822
|
tools,
|
|
7769
7823
|
// Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
|
|
@@ -14063,6 +14117,7 @@ function jsonResult(res, session) {
|
|
|
14063
14117
|
return {
|
|
14064
14118
|
ok: res.finishReason === "stop",
|
|
14065
14119
|
finishReason: res.finishReason,
|
|
14120
|
+
...res.providerFinishReason ? { providerFinishReason: res.providerFinishReason } : {},
|
|
14066
14121
|
text: res.text,
|
|
14067
14122
|
steps: res.steps,
|
|
14068
14123
|
tools: res.messages.slice(lastUser).filter((m) => m.role === "tool").length,
|
|
@@ -14070,7 +14125,7 @@ function jsonResult(res, session) {
|
|
|
14070
14125
|
sessionId: session.meta.id,
|
|
14071
14126
|
// The parked question, machine-readable — the caller answers it and resumes `sessionId`.
|
|
14072
14127
|
...res.finishReason === "needs_input" && res.question ? { question: res.question } : {},
|
|
14073
|
-
...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) } : {}
|
|
14074
14129
|
};
|
|
14075
14130
|
}
|
|
14076
14131
|
async function readMultiline(readLine) {
|