agent-nuvira 3.5.1 → 3.5.2
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/cli/chat.d.ts +42 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +240 -16
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/edit.d.ts.map +1 -1
- package/dist/cli/edit.js +3 -2
- package/dist/cli/edit.js.map +1 -1
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +10 -3
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/loop-executor.d.ts +16 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +9 -0
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/plan.d.ts.map +1 -1
- package/dist/cli/plan.js +18 -6
- package/dist/cli/plan.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +23 -8
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/ui/answer-view.d.ts +38 -0
- package/dist/cli/ui/answer-view.d.ts.map +1 -0
- package/dist/cli/ui/answer-view.js +72 -0
- package/dist/cli/ui/answer-view.js.map +1 -0
- package/dist/cli/ui/cards.d.ts +61 -0
- package/dist/cli/ui/cards.d.ts.map +1 -0
- package/dist/cli/ui/cards.js +141 -0
- package/dist/cli/ui/cards.js.map +1 -0
- package/dist/cli/ui/highlight.d.ts +32 -0
- package/dist/cli/ui/highlight.d.ts.map +1 -0
- package/dist/cli/ui/highlight.js +261 -0
- package/dist/cli/ui/highlight.js.map +1 -0
- package/dist/cli/ui/index.d.ts +21 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/index.js +14 -0
- package/dist/cli/ui/index.js.map +1 -0
- package/dist/cli/ui/markdown.d.ts +90 -0
- package/dist/cli/ui/markdown.d.ts.map +1 -0
- package/dist/cli/ui/markdown.js +292 -0
- package/dist/cli/ui/markdown.js.map +1 -0
- package/dist/cli/ui/presenter.d.ts +147 -0
- package/dist/cli/ui/presenter.d.ts.map +1 -0
- package/dist/cli/ui/presenter.js +313 -0
- package/dist/cli/ui/presenter.js.map +1 -0
- package/dist/cli/ui/render.d.ts +52 -0
- package/dist/cli/ui/render.d.ts.map +1 -0
- package/dist/cli/ui/render.js +384 -0
- package/dist/cli/ui/render.js.map +1 -0
- package/dist/cli/ui/theme.d.ts +36 -0
- package/dist/cli/ui/theme.d.ts.map +1 -0
- package/dist/cli/ui/theme.js +92 -0
- package/dist/cli/ui/theme.js.map +1 -0
- package/dist/inference/model-entitlement.d.ts +10 -0
- package/dist/inference/model-entitlement.d.ts.map +1 -1
- package/dist/inference/model-entitlement.js +1 -1
- package/dist/inference/model-entitlement.js.map +1 -1
- package/dist/learning/auto-router.d.ts +10 -1
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +63 -11
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +21 -1
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +70 -4
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/tools/process-liveness.d.ts +227 -0
- package/dist/tools/process-liveness.d.ts.map +1 -0
- package/dist/tools/process-liveness.js +486 -0
- package/dist/tools/process-liveness.js.map +1 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +4 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-registry.d.ts +282 -0
- package/dist/tools/run-registry.d.ts.map +1 -0
- package/dist/tools/run-registry.js +544 -0
- package/dist/tools/run-registry.js.map +1 -0
- package/dist/tools/run-terminal.d.ts +100 -2
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +841 -67
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +46 -0
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +115 -2
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/web-dashboard/chat-console.d.ts +25 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +16 -1
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +7 -1
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +32 -1
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/task-registry.d.ts +98 -0
- package/dist/web-dashboard/task-registry.d.ts.map +1 -0
- package/dist/web-dashboard/task-registry.js +159 -0
- package/dist/web-dashboard/task-registry.js.map +1 -0
- package/dist/web-dashboard/task-runner.d.ts +155 -5
- package/dist/web-dashboard/task-runner.d.ts.map +1 -1
- package/dist/web-dashboard/task-runner.js +505 -14
- package/dist/web-dashboard/task-runner.js.map +1 -1
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/index-DycGBWwy.js +236 -0
- package/src/web-dashboard/public/assets/index-DycGBWwy.js.map +1 -0
- package/src/web-dashboard/public/index.html +1 -1
- package/src/web-dashboard/public/assets/index-Bik91KJh.js +0 -236
- package/src/web-dashboard/public/assets/index-Bik91KJh.js.map +0 -1
package/dist/cli/chat.d.ts
CHANGED
|
@@ -674,6 +674,27 @@ export declare class ChatCommand extends BaseCommand {
|
|
|
674
674
|
* isolation on a surface the CLI did not.
|
|
675
675
|
*/
|
|
676
676
|
private ws5Overrides;
|
|
677
|
+
/**
|
|
678
|
+
* Live answer streaming for the interactive REPL.
|
|
679
|
+
*
|
|
680
|
+
* Returns the per-turn context overrides that paint the answer as it is
|
|
681
|
+
* generated, plus a `stop()` the caller MUST invoke once the turn returns.
|
|
682
|
+
* `stop()` unmounts the live view WITHOUT archiving, because the turn's own
|
|
683
|
+
* answer-printing path renders the authoritative final `answer.content` —
|
|
684
|
+
* archiving here as well would print the answer twice. The streamed tokens
|
|
685
|
+
* are a PREVIEW; the final frame is what stays in the transcript.
|
|
686
|
+
*
|
|
687
|
+
* ON A REAL TERMINAL ONLY. A pipe, CI run or `--json-events` gets no Ink view
|
|
688
|
+
* and no `onToken`, so a later step never streams and the bytes on stdout are
|
|
689
|
+
* exactly what they were before this existed.
|
|
690
|
+
*
|
|
691
|
+
* WHY THE RESET ON `tool:started`. `onToken` is invoked for EVERY model call
|
|
692
|
+
* in the tool loop, not only for the answer, so a turn that calls a tool
|
|
693
|
+
* would otherwise append the next step's answer onto the previous step's
|
|
694
|
+
* preamble. A tool starting is the boundary between steps, so the preview is
|
|
695
|
+
* cleared there.
|
|
696
|
+
*/
|
|
697
|
+
private liveAnswer;
|
|
677
698
|
private execute;
|
|
678
699
|
/**
|
|
679
700
|
* E3b — run one chat answer as a TOOL-CALL TURN.
|
|
@@ -820,6 +841,27 @@ export declare class ChatCommand extends BaseCommand {
|
|
|
820
841
|
* the ranked candidates and picks the first one whose isAvailable() passes —
|
|
821
842
|
* so Auto routing never sends a request to a provider that would 401.
|
|
822
843
|
*/
|
|
844
|
+
/**
|
|
845
|
+
* Point the routing snapshot at the pair this step will ACTUALLY use.
|
|
846
|
+
*
|
|
847
|
+
* THE DEFECT THIS CLOSES (measured 2026-10-13). The snapshot was assigned once
|
|
848
|
+
* per `routeMessageAuto` call, from the router's DECISION — before the
|
|
849
|
+
* candidate walk had a chance to substitute a pair — so a step could record
|
|
850
|
+
* `routing: gemini/gemini-3.1-flash-lite, agenticCapable: true` while the
|
|
851
|
+
* request went to `local/deepseek-coder:latest`: the Trace tab, the console
|
|
852
|
+
* line and the capability verdict all named a model the turn never ran, and the
|
|
853
|
+
* disagreement was invisible (trace-1791907393457-7seewg). The pair that really
|
|
854
|
+
* ran was in `lastAttempt` only.
|
|
855
|
+
*
|
|
856
|
+
* So the snapshot now FOLLOWS the attempt: provider/model are the pair being
|
|
857
|
+
* asked, the capability verdict is recomputed for THAT pair (the field asks
|
|
858
|
+
* "may the final routed model hold an agentic software task", so it must
|
|
859
|
+
* describe the model that ran), and the router's intended pair is kept once as
|
|
860
|
+
* `substitutedFor` so the substitution stays auditable instead of being erased.
|
|
861
|
+
* The first substitution wins, because that is the DECISION the walk moved away
|
|
862
|
+
* from — a later hop does not overwrite it with an intermediate pair.
|
|
863
|
+
*/
|
|
864
|
+
private noteServedRoute;
|
|
823
865
|
private routeMessageAuto;
|
|
824
866
|
/**
|
|
825
867
|
* Read multi-line input from stdin using readline.
|
package/dist/cli/chat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/cli/chat.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,WAAW,EAAc,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/cli/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/chat.js
CHANGED
|
@@ -16,6 +16,7 @@ import { decisionsRecordingEnabled, recallDecisionBlock } from '../learning/deci
|
|
|
16
16
|
import { getMemoryManager } from '../memory/manager.js';
|
|
17
17
|
import { logger } from '../utils/logger.js';
|
|
18
18
|
import { printOrchestrationResult } from './execute.js';
|
|
19
|
+
import { AnswerStream, attachShellCards, gitDiffCard, presentAnswer, shouldUseRichUi, toolCallCard, } from './ui/index.js';
|
|
19
20
|
// WS5 (#27) — isolation (a git worktree around the turn) and resume (replaying
|
|
20
21
|
// recorded steps instead of re-paying for them). See the module headers for why
|
|
21
22
|
// the worktree is created HERE, around the whole turn, and why a replay is
|
|
@@ -29,7 +30,7 @@ import { recordActionFailure, } from '../learning/failure-bookkeeping.js';
|
|
|
29
30
|
import { getRouterBandit } from '../learning/router-bandit.js';
|
|
30
31
|
import { UnusableModelResponseError } from '../learning/response-usability.js';
|
|
31
32
|
import { resolveThreadBudgetChars } from '../learning/context-budget.js';
|
|
32
|
-
import { getAutoRouter, isAgentAutoRoute, isAutoModel, isAutoProvider, governanceVerdict, adminBudgetVerdict } from '../learning/auto-router.js';
|
|
33
|
+
import { getAutoRouter, isAgentAutoRoute, isAutoModel, isAutoProvider, governanceVerdict, adminBudgetVerdict, isAgenticTask } from '../learning/auto-router.js';
|
|
33
34
|
import { continuationSoftwareText } from '../learning/continuation-intent.js';
|
|
34
35
|
import { TaskModelContinuity, taskSignature } from '../learning/task-model-continuity.js';
|
|
35
36
|
import { describeFailureKind, renderExhaustionReport, } from '../learning/exhaustion-report.js';
|
|
@@ -1292,6 +1293,63 @@ export class ChatCommand extends BaseCommand {
|
|
|
1292
1293
|
resume: options?.resume,
|
|
1293
1294
|
};
|
|
1294
1295
|
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Live answer streaming for the interactive REPL.
|
|
1298
|
+
*
|
|
1299
|
+
* Returns the per-turn context overrides that paint the answer as it is
|
|
1300
|
+
* generated, plus a `stop()` the caller MUST invoke once the turn returns.
|
|
1301
|
+
* `stop()` unmounts the live view WITHOUT archiving, because the turn's own
|
|
1302
|
+
* answer-printing path renders the authoritative final `answer.content` —
|
|
1303
|
+
* archiving here as well would print the answer twice. The streamed tokens
|
|
1304
|
+
* are a PREVIEW; the final frame is what stays in the transcript.
|
|
1305
|
+
*
|
|
1306
|
+
* ON A REAL TERMINAL ONLY. A pipe, CI run or `--json-events` gets no Ink view
|
|
1307
|
+
* and no `onToken`, so a later step never streams and the bytes on stdout are
|
|
1308
|
+
* exactly what they were before this existed.
|
|
1309
|
+
*
|
|
1310
|
+
* WHY THE RESET ON `tool:started`. `onToken` is invoked for EVERY model call
|
|
1311
|
+
* in the tool loop, not only for the answer, so a turn that calls a tool
|
|
1312
|
+
* would otherwise append the next step's answer onto the previous step's
|
|
1313
|
+
* preamble. A tool starting is the boundary between steps, so the preview is
|
|
1314
|
+
* cleared there.
|
|
1315
|
+
*/
|
|
1316
|
+
liveAnswer(header, opts = {}) {
|
|
1317
|
+
if (!shouldUseRichUi(opts))
|
|
1318
|
+
return { overrides: {}, stop: () => { }, stream: null };
|
|
1319
|
+
const stream = new AnswerStream({ header, headerColor: 'assistant' }, opts);
|
|
1320
|
+
let begun = false;
|
|
1321
|
+
// Shell commands are emitted on the process-wide bus (utils/shell.ts owns
|
|
1322
|
+
// them), so the turn subscribes for its own duration and detaches on stop().
|
|
1323
|
+
const detachShell = attachShellCards((card) => stream.card(card));
|
|
1324
|
+
return {
|
|
1325
|
+
stream,
|
|
1326
|
+
overrides: {
|
|
1327
|
+
onToken: (token) => {
|
|
1328
|
+
if (!begun) {
|
|
1329
|
+
begun = true;
|
|
1330
|
+
stream.begin();
|
|
1331
|
+
}
|
|
1332
|
+
stream.append(token);
|
|
1333
|
+
},
|
|
1334
|
+
onToolCall: (phase, info) => {
|
|
1335
|
+
if (phase === 'started') {
|
|
1336
|
+
// The step's preamble is not the answer: clear it so the preview
|
|
1337
|
+
// shows the step that is actually producing the reply.
|
|
1338
|
+
if (begun)
|
|
1339
|
+
stream.reset();
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
// Completed call → an archived card (this is what the turn DID).
|
|
1343
|
+
stream.card(toolCallCard({ tool: info.tool, ok: info.ok, error: info.error, result: info.result, durationMs: info.durationMs, args: info.args }));
|
|
1344
|
+
},
|
|
1345
|
+
onGitDiff: (payload) => stream.card(gitDiffCard(payload)),
|
|
1346
|
+
},
|
|
1347
|
+
stop: () => {
|
|
1348
|
+
detachShell();
|
|
1349
|
+
stream.finish({ archive: false });
|
|
1350
|
+
},
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1295
1353
|
async execute(prompt, options) {
|
|
1296
1354
|
// Apply the active model state from `nuvira model switch` as defaults
|
|
1297
1355
|
const activeOpts = applyActiveModel({ provider: options?.provider, model: options?.model });
|
|
@@ -1418,10 +1476,14 @@ export class ChatCommand extends BaseCommand {
|
|
|
1418
1476
|
// failed entirely), never as a bypass.
|
|
1419
1477
|
const parsed = parseRequestSync(prompt);
|
|
1420
1478
|
const dispatchDecision = resolvePipelineDispatch(parsed, { dev: options?.dev, text: prompt });
|
|
1479
|
+
// P7 — paint the answer live while it generates (TTY only; a no-op
|
|
1480
|
+
// elsewhere). `stop()` unmounts the preview once the turn returns; the
|
|
1481
|
+
// final frame is printed by the answer path below.
|
|
1482
|
+
const live = this.liveAnswer(`nuvira · ${provider.name}${model ? ` / ${model}` : ''}`);
|
|
1421
1483
|
const answer = await this.runChatAnswer(prompt, [], { type, provider, model }, options || {}, cacheEnabled, { auto: autoMode }, parsed,
|
|
1422
1484
|
// WS5 (#27) — isolation and resume ride on the CLI's own flags. Passed
|
|
1423
1485
|
// per turn: in the REPL each message is its own turn (see the manual).
|
|
1424
|
-
this.ws5Overrides(options));
|
|
1486
|
+
{ ...this.ws5Overrides(options), ...live.overrides }).finally(() => live.stop());
|
|
1425
1487
|
// No-model fallback: the tool loop could not generate a single response
|
|
1426
1488
|
// AND the rules assessed a high-confidence pipeline intent — run the
|
|
1427
1489
|
// pipeline directly (rules decide only when the model is unavailable;
|
|
@@ -1456,7 +1518,8 @@ export class ChatCommand extends BaseCommand {
|
|
|
1456
1518
|
// JSON blob, or the empty ```json fence the fallback transport leaves
|
|
1457
1519
|
// behind) — see stripToolCallArtifacts.
|
|
1458
1520
|
else if (answer.content.trim()) {
|
|
1459
|
-
console.log('
|
|
1521
|
+
console.log('');
|
|
1522
|
+
presentAnswer(stripToolCallArtifacts(answer.content));
|
|
1460
1523
|
}
|
|
1461
1524
|
if (!process.stdin.isTTY) {
|
|
1462
1525
|
await this.renderFollowups(answer.followups ?? [], false);
|
|
@@ -1470,12 +1533,14 @@ export class ChatCommand extends BaseCommand {
|
|
|
1470
1533
|
const picked = await this.renderFollowups(singleAnswer.followups ?? [], true);
|
|
1471
1534
|
if (!picked)
|
|
1472
1535
|
break;
|
|
1536
|
+
const liveNext = this.liveAnswer(`nuvira · ${provider.name}${model ? ` / ${model}` : ''}`);
|
|
1473
1537
|
const next = await this.runChatAnswer(picked, history, { type, provider, model }, options || {}, cacheEnabled, { auto: autoMode }, parseRequestSync(picked),
|
|
1474
1538
|
// P5 — a picked followup continues the previous execution.
|
|
1475
|
-
{ continuation: true, ...this.ws5Overrides(options) });
|
|
1539
|
+
{ continuation: true, ...this.ws5Overrides(options), ...liveNext.overrides }).finally(() => liveNext.stop());
|
|
1476
1540
|
const nextText = stripToolCallArtifacts(next.content);
|
|
1477
1541
|
if (nextText) {
|
|
1478
|
-
console.log('
|
|
1542
|
+
console.log('');
|
|
1543
|
+
presentAnswer(nextText);
|
|
1479
1544
|
// NOTE: no history.push here — runChatAnswer already recorded the
|
|
1480
1545
|
// assistant turn. The old duplicate push gave every subsequent turn
|
|
1481
1546
|
// TWO copies of the previous answer (relevance noise).
|
|
@@ -1565,13 +1630,16 @@ export class ChatCommand extends BaseCommand {
|
|
|
1565
1630
|
const parsed = recordMetricTime('rule.parse.ms', () => parseRequestSync(message));
|
|
1566
1631
|
const dispatchDecision = recordMetricTime('rule.dispatch.ms', () => resolvePipelineDispatch(parsed, { dev: this.devModeAuto, text: message }));
|
|
1567
1632
|
const session = { type, provider, model: effectiveModel };
|
|
1633
|
+
// P7 — live preview for this turn (see `liveAnswer`).
|
|
1634
|
+
const live = this.liveAnswer(`nuvira · ${provider.name}${effectiveModel ? ` / ${effectiveModel}` : ''}`);
|
|
1568
1635
|
const answer = await withLogCorrelation({ sessionId: chatSessionId }, () => recordMetricTime('llm.answer.ms', () => this.runChatAnswer(message, history, session, options || {}, cacheEnabled, { auto: autoMode }, parsed,
|
|
1569
1636
|
// P5 — a picked followup (or a typed one that matches the last
|
|
1570
1637
|
// suggestions) is a continuation, not a fresh independent request.
|
|
1571
1638
|
{
|
|
1572
1639
|
continuation: pickedFollowup || isSuggestedFollowup(message, lastFollowups),
|
|
1573
1640
|
...this.ws5Overrides(options),
|
|
1574
|
-
|
|
1641
|
+
...live.overrides,
|
|
1642
|
+
}))).finally(() => live.stop());
|
|
1575
1643
|
// No-model fallback: the tool loop could not generate a single response
|
|
1576
1644
|
// AND the rules assessed a high-confidence pipeline intent — run the
|
|
1577
1645
|
// pipeline directly (rules decide only when the model is unavailable).
|
|
@@ -1591,7 +1659,8 @@ export class ChatCommand extends BaseCommand {
|
|
|
1591
1659
|
// followup pick becomes the next message). The user asked for the
|
|
1592
1660
|
// content — the menu must never print before it.
|
|
1593
1661
|
if (answer.content.trim()) {
|
|
1594
|
-
console.log('
|
|
1662
|
+
console.log('');
|
|
1663
|
+
presentAnswer(answer.content);
|
|
1595
1664
|
}
|
|
1596
1665
|
lastFollowups = answer.followups ?? [];
|
|
1597
1666
|
const followupPrompt = await this.renderFollowups(lastFollowups, true);
|
|
@@ -2557,6 +2626,37 @@ export class ChatCommand extends BaseCommand {
|
|
|
2557
2626
|
callModel: callModelWithTrace,
|
|
2558
2627
|
// P2 — the empty-response handoff (see `requestModelSwitch` above).
|
|
2559
2628
|
requestModelSwitch,
|
|
2629
|
+
// P5 — the SAME blip policy the thrown-empty path uses, reachable from
|
|
2630
|
+
// the loop's empty-response path too. A provider that answers 200 with
|
|
2631
|
+
// no content never throws, so it never reached
|
|
2632
|
+
// `generateWithTransientRetry` and its empty schedule was computed but
|
|
2633
|
+
// never spent: both live failures (trace-1791906998502-txddq4,
|
|
2634
|
+
// trace-1791907393457-7seewg) handed off after the loop's single retry
|
|
2635
|
+
// and landed on the local last resort. The plan knows whether waiting
|
|
2636
|
+
// is worth it (a proven pair, or alternatives that would de-escalate),
|
|
2637
|
+
// so the loop asks the CALLER rather than inventing a backoff.
|
|
2638
|
+
emptyResponsePatienceMs: ({ emptyStreak, deliveredWorkThisTurn }) => {
|
|
2639
|
+
try {
|
|
2640
|
+
const pair = {
|
|
2641
|
+
provider: session.type,
|
|
2642
|
+
model: this.lastAttempt?.model ?? session.model ?? 'default',
|
|
2643
|
+
};
|
|
2644
|
+
return planBlipPatience({
|
|
2645
|
+
current: pair,
|
|
2646
|
+
alternatives: failoverAlternatives(this.configManager, pair.provider),
|
|
2647
|
+
evidence: registryPatienceEvidence(pair.provider, pair.model),
|
|
2648
|
+
// An entry here means an EARLIER step of this turn already failed
|
|
2649
|
+
// on this pair — the same pair failing twice is a pattern, not a
|
|
2650
|
+
// blip, and patience collapses there.
|
|
2651
|
+
failedEarlierStep: this.turnAttempts.some((a) => a.provider === pair.provider && a.model === pair.model),
|
|
2652
|
+
deliveredWorkThisTurn,
|
|
2653
|
+
}).emptyResponseDelaysMs;
|
|
2654
|
+
}
|
|
2655
|
+
catch {
|
|
2656
|
+
// No evidence → no extra patience (the pre-P5 behaviour).
|
|
2657
|
+
return [];
|
|
2658
|
+
}
|
|
2659
|
+
},
|
|
2560
2660
|
// C5 — `requestModelSwitch` installs the replacement on `session`, so
|
|
2561
2661
|
// re-resolve the thread budget from the session AFTER a handoff: a
|
|
2562
2662
|
// handoff to a smaller window is the case that overflows, and the
|
|
@@ -3514,6 +3614,10 @@ export class ChatCommand extends BaseCommand {
|
|
|
3514
3614
|
// (`answered()`, and the walks that install what they served) are the
|
|
3515
3615
|
// ones that own `session.model`.
|
|
3516
3616
|
this.lastAttempt = { provider: typ, model: effectiveModel };
|
|
3617
|
+
// G5/P6 — and the trace's routing snapshot follows the ATTEMPT, so a step
|
|
3618
|
+
// can never record a pair the request did not go to (see
|
|
3619
|
+
// `noteServedRoute`).
|
|
3620
|
+
this.noteServedRoute(typ, effectiveModel ?? 'default');
|
|
3517
3621
|
const nativeKey = `${typ}|${effectiveModel ?? ''}`;
|
|
3518
3622
|
// Answer-quality resilience: a CONFUSED reply — the model talking about
|
|
3519
3623
|
// the tool contract (e.g. apologizing that "the provided example call
|
|
@@ -3710,7 +3814,44 @@ export class ChatCommand extends BaseCommand {
|
|
|
3710
3814
|
// success log per landed candidate — and now the failure KIND in the
|
|
3711
3815
|
// line, so the console says what went wrong instead of only that it did.
|
|
3712
3816
|
logger.warn(` ⚠️ ${session.provider.name} failed (${describeFailureKind(primaryKind)}) — trying the next auto candidate...`);
|
|
3713
|
-
|
|
3817
|
+
/**
|
|
3818
|
+
* ── PAIR-SCOPED WALK: follow what is ROUTABLE, not one model per
|
|
3819
|
+
* provider ────────────────────────────────────────────────────────
|
|
3820
|
+
*
|
|
3821
|
+
* The walk used to track PROVIDERS: the failing provider was put in
|
|
3822
|
+
* `failed` and every re-route passed it as `excludeProviders`, so each
|
|
3823
|
+
* iteration could only ever land on a DIFFERENT provider — one model
|
|
3824
|
+
* tried per provider no matter how many that provider serves. Measured
|
|
3825
|
+
* live (trace-1791906998502-txddq4): a per-model 429 on
|
|
3826
|
+
* `gemini/gemma-4-31b-it` ended gemini's part of the turn, a `model not
|
|
3827
|
+
* found` on one openrouter id ended openrouter's, and the walk reached
|
|
3828
|
+
* the local last resort while ~370 routable pairs were never tried.
|
|
3829
|
+
*
|
|
3830
|
+
* The registry, the quota ledger and the exclusion filter all treat
|
|
3831
|
+
* these failures as PAIR-scoped (`recordActionFailure` parks the pair,
|
|
3832
|
+
* `ledger.parkModel` parks the model, and only
|
|
3833
|
+
* `PROVIDER_RATE_LIMIT_ESCALATION_MODELS` distinct models 429ing
|
|
3834
|
+
* escalates to a provider park). The walk was the one place
|
|
3835
|
+
* contradicting that, so it is the one place fixed here: it follows the
|
|
3836
|
+
* pair-scoped exclusions the bookkeeping just wrote, and only leaves a
|
|
3837
|
+
* provider when the EVIDENCE says the provider itself is out — a
|
|
3838
|
+
* transient/server failure, a provider-wide rate limit, a refused
|
|
3839
|
+
* account or a provider the registry has blocked.
|
|
3840
|
+
*/
|
|
3841
|
+
const firstPair = `${this.lastAttempt?.provider ?? session.type}|${this.lastAttempt?.model ?? session.model ?? 'default'}`;
|
|
3842
|
+
const failedPairs = new Set([firstPair]);
|
|
3843
|
+
const failedProviders = new Set();
|
|
3844
|
+
try {
|
|
3845
|
+
// The bookkeeping already ran for the failure above: a provider still
|
|
3846
|
+
// carrying an ACTIVE provider-scoped exclusion is out for this walk,
|
|
3847
|
+
// whatever kind the failure was classified as.
|
|
3848
|
+
if ((this.sessionFailedProviders.get(session.type) ?? 0) > Date.now()) {
|
|
3849
|
+
failedProviders.add(session.type);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
catch {
|
|
3853
|
+
// Best-effort — evidence must never break the walk.
|
|
3854
|
+
}
|
|
3714
3855
|
// Intent-aware escalation for the FAILOVER walk (the live junk path):
|
|
3715
3856
|
// a mid-turn failure used to re-route on the bare message alone, so a
|
|
3716
3857
|
// software turn whose first cloud provider died fell through to a tiny
|
|
@@ -3720,25 +3861,34 @@ export class ChatCommand extends BaseCommand {
|
|
|
3720
3861
|
// applies here too — but NEVER drop a candidate the floor would keep,
|
|
3721
3862
|
// so auto still cannot dead-end (local stays the last resort).
|
|
3722
3863
|
const failoverRoutingText = routingText;
|
|
3723
|
-
// Try ALL
|
|
3724
|
-
//
|
|
3864
|
+
// Try ALL routable candidates (no 3-candidate cap) — bounded by a
|
|
3865
|
+
// fixed attempt count so a churning pool cannot spin the turn.
|
|
3725
3866
|
const maxAttempts = 10;
|
|
3726
3867
|
for (let i = 0; i < maxAttempts; i++) {
|
|
3727
3868
|
let next = null;
|
|
3728
3869
|
try {
|
|
3729
3870
|
next = failoverRoutingText
|
|
3730
|
-
? await this.routeMessageAuto(message, [...
|
|
3871
|
+
? await this.routeMessageAuto(message, [...failedProviders], {
|
|
3731
3872
|
routingText: failoverRoutingText,
|
|
3732
3873
|
fallbackFrom: firstType,
|
|
3874
|
+
excludePairs: [...failedPairs],
|
|
3733
3875
|
})
|
|
3734
|
-
: await this.routeMessageAuto(message, [...
|
|
3876
|
+
: await this.routeMessageAuto(message, [...failedProviders], {
|
|
3877
|
+
fallbackFrom: firstType,
|
|
3878
|
+
excludePairs: [...failedPairs],
|
|
3879
|
+
});
|
|
3735
3880
|
}
|
|
3736
3881
|
catch {
|
|
3737
3882
|
break;
|
|
3738
3883
|
}
|
|
3739
|
-
if (!next
|
|
3884
|
+
if (!next)
|
|
3740
3885
|
break;
|
|
3741
|
-
|
|
3886
|
+
const nextPair = `${next.type}|${next.model ?? 'default'}`;
|
|
3887
|
+
// A pair this walk already tried means nothing NEW is routable — stop
|
|
3888
|
+
// rather than re-paying for a known-bad call.
|
|
3889
|
+
if (failedPairs.has(nextPair))
|
|
3890
|
+
break;
|
|
3891
|
+
failedPairs.add(nextPair);
|
|
3742
3892
|
// Last-resort truthfulness: if even the agentic floor could not
|
|
3743
3893
|
// avoid a weak model, say so ONCE so a degraded answer is never
|
|
3744
3894
|
// mistaken for a real one (the tiny local model fabricated tool
|
|
@@ -3801,7 +3951,23 @@ export class ChatCommand extends BaseCommand {
|
|
|
3801
3951
|
catch {
|
|
3802
3952
|
// Best-effort — bookkeeping must never break the turn.
|
|
3803
3953
|
}
|
|
3804
|
-
|
|
3954
|
+
// Is the PROVIDER out, or only this PAIR? The bookkeeping just
|
|
3955
|
+
// wrote the answer; read it rather than re-deriving the rule, so
|
|
3956
|
+
// the walk and the exclusion filter can never disagree.
|
|
3957
|
+
let providerOut = false;
|
|
3958
|
+
try {
|
|
3959
|
+
providerOut = (this.sessionFailedProviders.get(next.type) ?? 0) > Date.now();
|
|
3960
|
+
}
|
|
3961
|
+
catch {
|
|
3962
|
+
// Unknown scope → treat it as pair-scoped (the conservative
|
|
3963
|
+
// direction for staying routable).
|
|
3964
|
+
}
|
|
3965
|
+
if (providerOut)
|
|
3966
|
+
failedProviders.add(next.type);
|
|
3967
|
+
logger.warn(` ⚠️ ${next.type}/${next.model ?? 'default'} failed (${describeFailureKind(nextKind)}) — ` +
|
|
3968
|
+
(providerOut
|
|
3969
|
+
? 'that provider is out; trying the next auto candidate...'
|
|
3970
|
+
: `trying ${next.type}'s next model, then the next provider...`));
|
|
3805
3971
|
// Next candidate.
|
|
3806
3972
|
}
|
|
3807
3973
|
}
|
|
@@ -4165,6 +4331,44 @@ export class ChatCommand extends BaseCommand {
|
|
|
4165
4331
|
* the ranked candidates and picks the first one whose isAvailable() passes —
|
|
4166
4332
|
* so Auto routing never sends a request to a provider that would 401.
|
|
4167
4333
|
*/
|
|
4334
|
+
/**
|
|
4335
|
+
* Point the routing snapshot at the pair this step will ACTUALLY use.
|
|
4336
|
+
*
|
|
4337
|
+
* THE DEFECT THIS CLOSES (measured 2026-10-13). The snapshot was assigned once
|
|
4338
|
+
* per `routeMessageAuto` call, from the router's DECISION — before the
|
|
4339
|
+
* candidate walk had a chance to substitute a pair — so a step could record
|
|
4340
|
+
* `routing: gemini/gemini-3.1-flash-lite, agenticCapable: true` while the
|
|
4341
|
+
* request went to `local/deepseek-coder:latest`: the Trace tab, the console
|
|
4342
|
+
* line and the capability verdict all named a model the turn never ran, and the
|
|
4343
|
+
* disagreement was invisible (trace-1791907393457-7seewg). The pair that really
|
|
4344
|
+
* ran was in `lastAttempt` only.
|
|
4345
|
+
*
|
|
4346
|
+
* So the snapshot now FOLLOWS the attempt: provider/model are the pair being
|
|
4347
|
+
* asked, the capability verdict is recomputed for THAT pair (the field asks
|
|
4348
|
+
* "may the final routed model hold an agentic software task", so it must
|
|
4349
|
+
* describe the model that ran), and the router's intended pair is kept once as
|
|
4350
|
+
* `substitutedFor` so the substitution stays auditable instead of being erased.
|
|
4351
|
+
* The first substitution wins, because that is the DECISION the walk moved away
|
|
4352
|
+
* from — a later hop does not overwrite it with an intermediate pair.
|
|
4353
|
+
*/
|
|
4354
|
+
noteServedRoute(provider, model) {
|
|
4355
|
+
const snap = this.lastRouteSnapshot;
|
|
4356
|
+
if (!snap)
|
|
4357
|
+
return;
|
|
4358
|
+
const served = `${provider}/${model}`;
|
|
4359
|
+
const intended = `${snap.provider}/${snap.model}`;
|
|
4360
|
+
const next = {
|
|
4361
|
+
...snap,
|
|
4362
|
+
provider,
|
|
4363
|
+
model,
|
|
4364
|
+
agenticCapable: isAgenticCapableModel(model, provider),
|
|
4365
|
+
};
|
|
4366
|
+
if (snap.substitutedFor)
|
|
4367
|
+
next.substitutedFor = snap.substitutedFor;
|
|
4368
|
+
else if (served !== intended)
|
|
4369
|
+
next.substitutedFor = intended;
|
|
4370
|
+
this.lastRouteSnapshot = next;
|
|
4371
|
+
}
|
|
4168
4372
|
async routeMessageAuto(message, excludeProviders = [], opts) {
|
|
4169
4373
|
// The text ROUTING is decided from — the prior software ask for a bare
|
|
4170
4374
|
// continuation, otherwise the message itself. `message` is used everywhere
|
|
@@ -4235,6 +4439,13 @@ export class ChatCommand extends BaseCommand {
|
|
|
4235
4439
|
const decision = opts?.noCache === true
|
|
4236
4440
|
? resolveFresh()
|
|
4237
4441
|
: withRoutingCache(cacheSignature, 30_000, resolveFresh);
|
|
4442
|
+
/**
|
|
4443
|
+
* Does this ask need CAPACITY (not just a healthy model)? One predicate for
|
|
4444
|
+
* the whole route, shared by the capability floor, the model-first override
|
|
4445
|
+
* and this walk's per-provider model resolution — so a `default` model is
|
|
4446
|
+
* resolved capability-first exactly when the ask is agentic.
|
|
4447
|
+
*/
|
|
4448
|
+
const agenticAsk = isAgenticTask(decision.complexity, decision.taskProfile);
|
|
4238
4449
|
// G5 — record the routing snapshot now (the winner + why), so the reasoning
|
|
4239
4450
|
// trace for this turn can show the decision instead of a blank column.
|
|
4240
4451
|
// P3 — the TASK this turn belongs to, as far as model continuity is
|
|
@@ -4362,6 +4573,10 @@ export class ChatCommand extends BaseCommand {
|
|
|
4362
4573
|
if (isModelExcluded(prov, model))
|
|
4363
4574
|
return;
|
|
4364
4575
|
const key = `${prov}|${model}`;
|
|
4576
|
+
// Pairs this WALK already tried (see `opts.excludePairs`) — a pair-scoped
|
|
4577
|
+
// exclusion, so a provider's remaining models stay reachable.
|
|
4578
|
+
if (opts?.excludePairs?.includes(key))
|
|
4579
|
+
return;
|
|
4365
4580
|
if (seenPairs.has(key))
|
|
4366
4581
|
return;
|
|
4367
4582
|
// NOTE: deliberately NO registry-usability skip here. `resolveWorkingModel`
|
|
@@ -4412,9 +4627,18 @@ export class ChatCommand extends BaseCommand {
|
|
|
4412
4627
|
try {
|
|
4413
4628
|
const resolved = resolveProvider(this.configManager, candidate.provider);
|
|
4414
4629
|
if (await resolved.provider.isAvailable()) {
|
|
4630
|
+
// A `default` model is resolved by the router, and for a task that
|
|
4631
|
+
// NEEDS capacity that resolution is told so: the health ranking it
|
|
4632
|
+
// otherwise follows picks the fastest model, which is how a keyless
|
|
4633
|
+
// local rung ended up on `local/deepseek-coder:latest` (a
|
|
4634
|
+
// confabulation) with a verified `gpt-oss:120b-cloud` unused
|
|
4635
|
+
// (trace-1791907393457-7seewg). `agenticAsk` is the same predicate the
|
|
4636
|
+
// router's own capability floor uses, so the two cannot disagree.
|
|
4415
4637
|
const desired = candidate.model !== 'default'
|
|
4416
4638
|
? candidate.model
|
|
4417
|
-
: getAutoRouter().resolveModel(candidate.provider, 'chat', this.configManager
|
|
4639
|
+
: getAutoRouter().resolveModel(candidate.provider, 'chat', this.configManager, undefined, {
|
|
4640
|
+
agenticAsk,
|
|
4641
|
+
});
|
|
4418
4642
|
// Model health: only use models that actually exist on the provider.
|
|
4419
4643
|
// A provider's pinned config.model can be deprecated or a placeholder
|
|
4420
4644
|
// (e.g. gemini-2.0-flash-exp → 404) — repair to a live model. The pair
|