agent-nuvira 3.1.1 → 3.2.0
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/agents/agents/reasoner.d.ts +8 -0
- package/dist/agents/agents/reasoner.d.ts.map +1 -1
- package/dist/agents/agents/reasoner.js +53 -2
- package/dist/agents/agents/reasoner.js.map +1 -1
- package/dist/agents/agents/writer.d.ts +24 -0
- package/dist/agents/agents/writer.d.ts.map +1 -1
- package/dist/agents/agents/writer.js +194 -0
- package/dist/agents/agents/writer.js.map +1 -1
- package/dist/agents/composite-plan.d.ts +143 -0
- package/dist/agents/composite-plan.d.ts.map +1 -0
- package/dist/agents/composite-plan.js +399 -0
- package/dist/agents/composite-plan.js.map +1 -0
- package/dist/agents/long-form-plan.d.ts +156 -0
- package/dist/agents/long-form-plan.d.ts.map +1 -0
- package/dist/agents/long-form-plan.js +274 -0
- package/dist/agents/long-form-plan.js.map +1 -0
- package/dist/agents/orchestrator.d.ts +107 -0
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +501 -35
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/agents/prompt-assembly.d.ts +8 -0
- package/dist/agents/prompt-assembly.d.ts.map +1 -1
- package/dist/agents/prompt-assembly.js +17 -0
- package/dist/agents/prompt-assembly.js.map +1 -1
- package/dist/cli/chat.d.ts +27 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +133 -8
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/execute.d.ts +12 -0
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +105 -2
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/models.d.ts.map +1 -1
- package/dist/cli/models.js +10 -0
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/nlu.d.ts +8 -0
- package/dist/cli/nlu.d.ts.map +1 -1
- package/dist/cli/nlu.js +57 -0
- package/dist/cli/nlu.js.map +1 -1
- package/dist/cli/trace.d.ts.map +1 -1
- package/dist/cli/trace.js +27 -0
- package/dist/cli/trace.js.map +1 -1
- package/dist/gateway/gateway-log.d.ts +1 -1
- package/dist/gateway/gateway-log.d.ts.map +1 -1
- package/dist/gateway/gateway-log.js.map +1 -1
- package/dist/gateway/registry.d.ts +116 -1
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +575 -52
- package/dist/gateway/registry.js.map +1 -1
- package/dist/inference/model-entitlement.d.ts +46 -0
- package/dist/inference/model-entitlement.d.ts.map +1 -0
- package/dist/inference/model-entitlement.js +98 -0
- package/dist/inference/model-entitlement.js.map +1 -0
- package/dist/inference/tool-call-utils.d.ts.map +1 -1
- package/dist/inference/tool-call-utils.js +70 -1
- package/dist/inference/tool-call-utils.js.map +1 -1
- package/dist/learning/autonomy-policy.d.ts +334 -0
- package/dist/learning/autonomy-policy.d.ts.map +1 -0
- package/dist/learning/autonomy-policy.js +500 -0
- package/dist/learning/autonomy-policy.js.map +1 -0
- package/dist/learning/credential-fingerprint.d.ts +58 -0
- package/dist/learning/credential-fingerprint.d.ts.map +1 -0
- package/dist/learning/credential-fingerprint.js +126 -0
- package/dist/learning/credential-fingerprint.js.map +1 -0
- package/dist/learning/deferred-task.d.ts +159 -0
- package/dist/learning/deferred-task.d.ts.map +1 -0
- package/dist/learning/deferred-task.js +451 -0
- package/dist/learning/deferred-task.js.map +1 -0
- package/dist/learning/deliverable-class.d.ts +130 -0
- package/dist/learning/deliverable-class.d.ts.map +1 -0
- package/dist/learning/deliverable-class.js +432 -0
- package/dist/learning/deliverable-class.js.map +1 -0
- package/dist/learning/long-form.d.ts +252 -0
- package/dist/learning/long-form.d.ts.map +1 -0
- package/dist/learning/long-form.js +510 -0
- package/dist/learning/long-form.js.map +1 -0
- package/dist/learning/model-first-router.d.ts +17 -0
- package/dist/learning/model-first-router.d.ts.map +1 -1
- package/dist/learning/model-first-router.js +35 -0
- package/dist/learning/model-first-router.js.map +1 -1
- package/dist/learning/model-registry.d.ts +96 -4
- package/dist/learning/model-registry.d.ts.map +1 -1
- package/dist/learning/model-registry.js +128 -6
- package/dist/learning/model-registry.js.map +1 -1
- package/dist/learning/model-warmup.d.ts +97 -2
- package/dist/learning/model-warmup.d.ts.map +1 -1
- package/dist/learning/model-warmup.js +165 -60
- package/dist/learning/model-warmup.js.map +1 -1
- package/dist/learning/prompt-layers.d.ts +61 -0
- package/dist/learning/prompt-layers.d.ts.map +1 -0
- package/dist/learning/prompt-layers.js +140 -0
- package/dist/learning/prompt-layers.js.map +1 -0
- package/dist/learning/provider-limits.d.ts +66 -0
- package/dist/learning/provider-limits.d.ts.map +1 -0
- package/dist/learning/provider-limits.js +184 -0
- package/dist/learning/provider-limits.js.map +1 -0
- package/dist/learning/reasoning-trace.d.ts +36 -1
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +36 -2
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/resilient-call.d.ts +36 -1
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +118 -8
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/learning/unattended-job.d.ts +293 -0
- package/dist/learning/unattended-job.d.ts.map +1 -0
- package/dist/learning/unattended-job.js +544 -0
- package/dist/learning/unattended-job.js.map +1 -0
- package/dist/learning/unattended-progress.d.ts +95 -0
- package/dist/learning/unattended-progress.d.ts.map +1 -0
- package/dist/learning/unattended-progress.js +147 -0
- package/dist/learning/unattended-progress.js.map +1 -0
- package/dist/learning/working-state.d.ts +109 -0
- package/dist/learning/working-state.d.ts.map +1 -0
- package/dist/learning/working-state.js +244 -0
- package/dist/learning/working-state.js.map +1 -0
- package/dist/nlu/conversation-gate.d.ts +46 -0
- package/dist/nlu/conversation-gate.d.ts.map +1 -1
- package/dist/nlu/conversation-gate.js +73 -6
- package/dist/nlu/conversation-gate.js.map +1 -1
- package/dist/nlu/intent-confirm.d.ts +82 -0
- package/dist/nlu/intent-confirm.d.ts.map +1 -0
- package/dist/nlu/intent-confirm.js +146 -0
- package/dist/nlu/intent-confirm.js.map +1 -0
- package/dist/nlu/learnings.d.ts +101 -0
- package/dist/nlu/learnings.d.ts.map +1 -0
- package/dist/nlu/learnings.js +283 -0
- package/dist/nlu/learnings.js.map +1 -0
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +95 -19
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/edit-verification.d.ts +125 -0
- package/dist/tools/edit-verification.d.ts.map +1 -0
- package/dist/tools/edit-verification.js +237 -0
- package/dist/tools/edit-verification.js.map +1 -0
- package/dist/tools/git-tool.d.ts.map +1 -1
- package/dist/tools/git-tool.js +37 -4
- package/dist/tools/git-tool.js.map +1 -1
- package/dist/tools/registry.d.ts +30 -2
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +40 -11
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-cli.d.ts.map +1 -1
- package/dist/tools/run-cli.js +40 -13
- package/dist/tools/run-cli.js.map +1 -1
- package/dist/tools/run-terminal.d.ts +6 -1
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +158 -18
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +35 -0
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +157 -3
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/web-dashboard/chat-console.d.ts +35 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +97 -18
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/chat-retry.d.ts +143 -0
- package/dist/web-dashboard/chat-retry.d.ts.map +1 -0
- package/dist/web-dashboard/chat-retry.js +227 -0
- package/dist/web-dashboard/chat-retry.js.map +1 -0
- package/dist/web-dashboard/server.d.ts +17 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +200 -9
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +81 -4
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/{index-CQW0qv1r.js → index-kCUkORm7.js} +27 -27
- package/src/web-dashboard/public/assets/index-kCUkORm7.js.map +1 -0
- package/src/web-dashboard/public/index.html +1 -1
- package/src/web-dashboard/public/assets/index-CQW0qv1r.js.map +0 -1
|
@@ -25,11 +25,14 @@ import { getAutoRouter } from '../learning/auto-router.js';
|
|
|
25
25
|
import { readRecallHits } from '../context/session-recall.js';
|
|
26
26
|
import { getRouterPromotion } from '../learning/router-promotion.js';
|
|
27
27
|
import { AUTH_CLEAR_THRESHOLD } from '../learning/key-hygiene.js';
|
|
28
|
-
import { ACTION_LOG_FILENAME, aggregateActionTelemetry, readActionTelemetryFile } from '../learning/model-registry.js';
|
|
28
|
+
import { ACTION_LOG_FILENAME, aggregateActionTelemetry, readActionTelemetryFile, getModelRegistry, } from '../learning/model-registry.js';
|
|
29
|
+
import { classifyModelEntitlement } from '../inference/model-entitlement.js';
|
|
29
30
|
import { AdminSessions, countAdminRoleUsers, isAdminConfigured, listAdminUsers, removeAdminUser, roleForUser, verifyAdmin, writeAdminUser, MIN_ADMIN_PASSWORD_LENGTH, } from './src/admin-auth.js';
|
|
30
31
|
import { TaskRunner } from './task-runner.js';
|
|
31
32
|
import { WhatsAppPairingManager } from './whatsapp-pairing.js';
|
|
32
33
|
import { ChatConsole, newChatSessionId } from './chat-console.js';
|
|
34
|
+
import { ChatRetryBroker } from './chat-retry.js';
|
|
35
|
+
import { markFailoverAttempts } from '../learning/resilient-call.js';
|
|
33
36
|
import { buildProjectContext, formatProjectText } from './project-context.js';
|
|
34
37
|
import { isVaultRef } from '../enterprise/vault.js';
|
|
35
38
|
import { ROLES, roleCan } from '../enterprise/rbac.js';
|
|
@@ -322,6 +325,50 @@ export function broadcastTyping(event) {
|
|
|
322
325
|
catch { /* client disconnected */ }
|
|
323
326
|
}
|
|
324
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Push a deferred-retry update to the open dashboard.
|
|
330
|
+
*
|
|
331
|
+
* The retry runs minutes or hours after the turn that queued it, long after the
|
|
332
|
+
* per-session chat stream has been torn down (the client unsubscribes when the
|
|
333
|
+
* POST resolves), so it rides the PERSISTENT `/api/sse` channel the app opens
|
|
334
|
+
* once at load. The session id is in the payload because that channel is shared
|
|
335
|
+
* by every session and by every other panel.
|
|
336
|
+
*/
|
|
337
|
+
export function broadcastChatRetry(event) {
|
|
338
|
+
const payload = `event: chat-retry\ndata: ${JSON.stringify({ ...event, serverTime: Date.now() })}\n\n`;
|
|
339
|
+
for (const client of sseClients) {
|
|
340
|
+
try {
|
|
341
|
+
client.res.write(payload);
|
|
342
|
+
}
|
|
343
|
+
catch { /* client disconnected */ }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* The deferred-retry drain (one timer per process; idempotent).
|
|
348
|
+
*
|
|
349
|
+
* Armed with the server and cleared on shutdown. A retry is meant to outlive the
|
|
350
|
+
* user's attention — that is the whole point of the offer — so unlike the quota
|
|
351
|
+
* watcher this is NOT tied to an open SSE client: closing the tab must not
|
|
352
|
+
* cancel the work the agent promised to finish.
|
|
353
|
+
*/
|
|
354
|
+
let chatRetryTimer = null;
|
|
355
|
+
function armChatRetryDrain() {
|
|
356
|
+
if (chatRetryTimer)
|
|
357
|
+
return;
|
|
358
|
+
chatRetryTimer = setInterval(() => {
|
|
359
|
+
void chatRetryBroker.drain().catch(() => undefined);
|
|
360
|
+
}, CHAT_RETRY_DRAIN_INTERVAL_MS);
|
|
361
|
+
// Never hold the event loop open for a background convenience.
|
|
362
|
+
chatRetryTimer.unref?.();
|
|
363
|
+
}
|
|
364
|
+
function disarmChatRetryDrain() {
|
|
365
|
+
if (!chatRetryTimer)
|
|
366
|
+
return;
|
|
367
|
+
clearInterval(chatRetryTimer);
|
|
368
|
+
chatRetryTimer = null;
|
|
369
|
+
}
|
|
370
|
+
/** How often the dashboard checks for a retry whose wait is over (ms). */
|
|
371
|
+
const CHAT_RETRY_DRAIN_INTERVAL_MS = 15_000;
|
|
325
372
|
// ─── ZIP Generation (no external dependencies) ─────────────────────────────
|
|
326
373
|
/** CRC-32 lookup table (precomputed). */
|
|
327
374
|
const CRC_TABLE = new Uint32Array(256);
|
|
@@ -806,7 +853,9 @@ export function readTracesData() {
|
|
|
806
853
|
const traces = [...file.traces]
|
|
807
854
|
.sort((a, b) => b.startedAt - a.startedAt)
|
|
808
855
|
.map((t) => {
|
|
809
|
-
|
|
856
|
+
// Session 3 — the full stable layer is detail-only (the index would grow
|
|
857
|
+
// by ~16KB per trace otherwise); it is shown in the replay view.
|
|
858
|
+
const { steps, systemPrompt: _systemPrompt, ...rest } = t;
|
|
810
859
|
const stepCount = steps.length;
|
|
811
860
|
const failedSteps = steps.filter((s) => !s.success).length;
|
|
812
861
|
const totalTokens = steps.reduce((sum, s) => sum + s.inputTokens + s.outputTokens, 0);
|
|
@@ -958,12 +1007,81 @@ async function readModelsHealth() {
|
|
|
958
1007
|
checkVLLMProvider(),
|
|
959
1008
|
checkBedrockProvider(),
|
|
960
1009
|
]);
|
|
961
|
-
const
|
|
962
|
-
const totalModels =
|
|
963
|
-
const available =
|
|
964
|
-
const limited =
|
|
965
|
-
const unavailable =
|
|
966
|
-
|
|
1010
|
+
const probed = results.filter(Boolean);
|
|
1011
|
+
const totalModels = probed.reduce((sum, p) => sum + p.models.length, 0);
|
|
1012
|
+
const available = probed.reduce((sum, p) => sum + p.models.filter((m) => m.status === 'available').length, 0);
|
|
1013
|
+
const limited = probed.reduce((sum, p) => sum + p.models.filter((m) => m.status === 'limited').length, 0);
|
|
1014
|
+
const unavailable = probed.reduce((sum, p) => sum + p.models.filter((m) => m.status === 'unavailable').length, 0);
|
|
1015
|
+
// ── Reconcile the LISTING with the ROUTER (Models-page audit) ─────────────
|
|
1016
|
+
// Everything above answers "does the provider LIST this model?" — and for
|
|
1017
|
+
// OpenRouter that is 400+ ids stamped with the PROVIDER's rate-limit status,
|
|
1018
|
+
// so hundreds of models read "Available" that the router can never pick (the
|
|
1019
|
+
// registry holds 443 OpenRouter entries with 0 verified). Observed live: the
|
|
1020
|
+
// page said 509 available while the agent routed 87% of a day's calls to one
|
|
1021
|
+
// model. Two truths about the same models, on the same screen, with nothing
|
|
1022
|
+
// connecting them — which is what made the page untrustworthy.
|
|
1023
|
+
//
|
|
1024
|
+
// The fix is to ask the REGISTRY, through the SAME predicate routing uses
|
|
1025
|
+
// (`isUsable`). Duplicating the staleness/park rules here would recreate the
|
|
1026
|
+
// exact divergence this is closing.
|
|
1027
|
+
const registry = getModelRegistry();
|
|
1028
|
+
const now = Date.now();
|
|
1029
|
+
let routable = 0;
|
|
1030
|
+
let registryTotal = 0;
|
|
1031
|
+
let registryVerified = 0;
|
|
1032
|
+
for (const provider of registry.getTrackedProviders()) {
|
|
1033
|
+
for (const entry of registry.getAllModelsForProvider(provider)) {
|
|
1034
|
+
registryTotal += 1;
|
|
1035
|
+
if (entry.status === 'verified')
|
|
1036
|
+
registryVerified += 1;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
const providers = probed.map((p) => {
|
|
1040
|
+
let providerRoutable = 0;
|
|
1041
|
+
const models = p.models.map((m) => {
|
|
1042
|
+
const entry = registry.getEntry(p.provider, m.id);
|
|
1043
|
+
const usable = entry ? registry.isUsable(p.provider, m.id, now) : false;
|
|
1044
|
+
if (usable) {
|
|
1045
|
+
providerRoutable += 1;
|
|
1046
|
+
routable += 1;
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
...m,
|
|
1050
|
+
/** The registry's own verdict for this exact pair. */
|
|
1051
|
+
registryStatus: entry?.status,
|
|
1052
|
+
/** True only when the ROUTER would use it right now. */
|
|
1053
|
+
routable: usable,
|
|
1054
|
+
// WHY each unusable verdict is unusable — the raw learned reason, so the
|
|
1055
|
+
// cell can distinguish "this id does not exist on the endpoint" from
|
|
1056
|
+
// "your key cannot use it". Without it the page could only say
|
|
1057
|
+
// "unavailable", which is the ambiguity that made "requires purchase"
|
|
1058
|
+
// tempting and wrong.
|
|
1059
|
+
registryError: entry?.lastError,
|
|
1060
|
+
/** A retired provider × model pair (the id is not served there). */
|
|
1061
|
+
registryDead: entry?.deadPair === true,
|
|
1062
|
+
// Cost/entitlement, LABELLED never filtered — a user who just bought
|
|
1063
|
+
// credits must still see the models they paid for.
|
|
1064
|
+
entitlement: classifyModelEntitlement(p.provider, m.id),
|
|
1065
|
+
// Parked/quota state lived only in the registry payload, so the per-model
|
|
1066
|
+
// health cells could never render it — the type and the chips existed but
|
|
1067
|
+
// nothing populated them on this path.
|
|
1068
|
+
parked: entry ? entry.quotaParkedUntil > now : false,
|
|
1069
|
+
resetsInMs: entry?.resetsInMs ?? 0,
|
|
1070
|
+
};
|
|
1071
|
+
});
|
|
1072
|
+
return { ...p, models, routable: providerRoutable };
|
|
1073
|
+
});
|
|
1074
|
+
return {
|
|
1075
|
+
providers,
|
|
1076
|
+
lastChecked: now,
|
|
1077
|
+
totalModels,
|
|
1078
|
+
available,
|
|
1079
|
+
limited,
|
|
1080
|
+
unavailable,
|
|
1081
|
+
routable,
|
|
1082
|
+
registryTotal,
|
|
1083
|
+
registryVerified,
|
|
1084
|
+
};
|
|
967
1085
|
}
|
|
968
1086
|
/** Check local Ollama provider — no rate limits to parse */
|
|
969
1087
|
async function checkLocalProvider() {
|
|
@@ -2269,6 +2387,21 @@ let whatsappPairing = new WhatsAppPairingManager();
|
|
|
2269
2387
|
// P4 — chat sessions persist through ~/.nuvira/memory/chat-sessions.json so the
|
|
2270
2388
|
// sidebar can resume any past conversation after a dashboard restart.
|
|
2271
2389
|
let chatConsole = new ChatConsole({ persistPath: join(MEMORY_DIR, 'chat-sessions.json') });
|
|
2390
|
+
/**
|
|
2391
|
+
* The dashboard's half of the deferred retry loop (one per process).
|
|
2392
|
+
*
|
|
2393
|
+
* Reads `chatConsole` lazily through its closures, so the test hook that swaps
|
|
2394
|
+
* the console keeps working: a broker captured at module load would freeze the
|
|
2395
|
+
* original console and retry into a session nobody is looking at.
|
|
2396
|
+
*/
|
|
2397
|
+
const chatRetryBroker = new ChatRetryBroker({
|
|
2398
|
+
// `recordTurn: false` — the broker writes the retry outcome itself, once,
|
|
2399
|
+
// with the header that explains an answer arriving unprompted.
|
|
2400
|
+
answer: (sessionId, text) => chatConsole.answer(sessionId, text, { recordTurn: false }),
|
|
2401
|
+
isBusy: (sessionId) => chatConsole.isBusy(sessionId),
|
|
2402
|
+
notify: (event) => broadcastChatRetry(event),
|
|
2403
|
+
persistTurn: (sessionId, user, assistant) => chatConsole.appendTurns(sessionId, user, assistant),
|
|
2404
|
+
});
|
|
2272
2405
|
// Phase 4 (AGENTIC_CAPABILITY_ASSESSMENT Addendum v4) — the chat console's
|
|
2273
2406
|
// loop turns record their engine + per-tool telemetry into the DAG store (a
|
|
2274
2407
|
// local hook, never a cross-module import: chat-console must not know about
|
|
@@ -2334,6 +2467,14 @@ export function setChatConsoleForTest(console) {
|
|
|
2334
2467
|
};
|
|
2335
2468
|
chatConsole = console;
|
|
2336
2469
|
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Test hook: run one deferred-retry drain immediately, instead of waiting for
|
|
2472
|
+
* the 15s tick. Mirrors `setChatConsoleForTest` — the seam a real-HTTP test
|
|
2473
|
+
* needs to observe a retry that is otherwise hours of wall-clock away.
|
|
2474
|
+
*/
|
|
2475
|
+
export async function runChatRetryDrainForTest() {
|
|
2476
|
+
await chatRetryBroker.drain();
|
|
2477
|
+
}
|
|
2337
2478
|
/**
|
|
2338
2479
|
* The LIVE server handle, kept module-level so the Shutdown route can close
|
|
2339
2480
|
* the listeners (primary + IPv6 twin) before exiting — a dashboard stopped
|
|
@@ -2358,6 +2499,9 @@ let dashboardShutdownAction = () => {
|
|
|
2358
2499
|
}
|
|
2359
2500
|
catch { /* best-effort */ }
|
|
2360
2501
|
}
|
|
2502
|
+
// The queue itself is PERSISTED and deliberately left alone: a restart
|
|
2503
|
+
// resumes it (the normal case for a quota wait). Only the timer stops.
|
|
2504
|
+
disarmChatRetryDrain();
|
|
2361
2505
|
process.exit(0);
|
|
2362
2506
|
}, 150);
|
|
2363
2507
|
};
|
|
@@ -5218,11 +5362,30 @@ function handleRequest(req, res) {
|
|
|
5218
5362
|
// stream closes): for an ABORTED fetch the response never completes, so
|
|
5219
5363
|
// writableEnded stays false and the turn is cancelled; after a normal
|
|
5220
5364
|
// completion writableEnded is true and this is a no-op.
|
|
5365
|
+
// A pending retry offer is answered HERE, before any turn runs: the reply
|
|
5366
|
+
// belongs to the offer, not to the composer's next request. A message that
|
|
5367
|
+
// is neither yes nor no falls through to normal handling.
|
|
5368
|
+
const retryReply = chatRetryBroker.consumeAnswer(sessionId, message);
|
|
5369
|
+
if (retryReply) {
|
|
5370
|
+
writeJson(res, 200, {
|
|
5371
|
+
ok: true,
|
|
5372
|
+
sessionId,
|
|
5373
|
+
content: retryReply,
|
|
5374
|
+
followups: [],
|
|
5375
|
+
provider: null,
|
|
5376
|
+
model: null,
|
|
5377
|
+
generationFailed: false,
|
|
5378
|
+
});
|
|
5379
|
+
return;
|
|
5380
|
+
}
|
|
5221
5381
|
const onResClose = () => {
|
|
5222
5382
|
if (!res.writableEnded)
|
|
5223
5383
|
chatConsole.abort(sessionId);
|
|
5224
5384
|
};
|
|
5225
5385
|
res.on('close', onResClose);
|
|
5386
|
+
// Mark the failover log BEFORE the turn so a failure can report which
|
|
5387
|
+
// models were actually tried (same contract as the gateway).
|
|
5388
|
+
const attemptMark = markFailoverAttempts();
|
|
5226
5389
|
const result = await chatConsole.answer(sessionId, message, { provider, model, projectContext, projectPath, attachments });
|
|
5227
5390
|
// The response is written below — remove the disconnect listener so a
|
|
5228
5391
|
// post-completion close can never touch the console again.
|
|
@@ -5234,14 +5397,39 @@ function handleRequest(req, res) {
|
|
|
5234
5397
|
});
|
|
5235
5398
|
return;
|
|
5236
5399
|
}
|
|
5400
|
+
// A failed generation gets the SAME treatment as the gateway: report what
|
|
5401
|
+
// was tried, offer to keep checking, and queue the ask so the offer is
|
|
5402
|
+
// enforced (see ChatRetryBroker). Without this the console was a dead end
|
|
5403
|
+
// — the reader had to wait and re-send the message themselves.
|
|
5404
|
+
let content = result.content ?? '';
|
|
5405
|
+
// Did the offer we are about to send get a mechanism behind it? The client
|
|
5406
|
+
// uses this to stop offering "↻ Retry" for a turn the server is already
|
|
5407
|
+
// retrying on its own — a manual re-send on top of the queued one would
|
|
5408
|
+
// run the same ask twice.
|
|
5409
|
+
let retryQueued = false;
|
|
5410
|
+
if (result.generationFailed === true) {
|
|
5411
|
+
const report = chatRetryBroker.onFailure(sessionId, message, attemptMark);
|
|
5412
|
+
if (report) {
|
|
5413
|
+
content = content.trim() ? `${content}\n\n${report}` : report;
|
|
5414
|
+
retryQueued = true;
|
|
5415
|
+
// The console stored whatever the ENGINE returned (often nothing), so
|
|
5416
|
+
// the report the reader is about to see would vanish on reload. Record
|
|
5417
|
+
// the composed bubble — the same contract as the gateway's `record()`.
|
|
5418
|
+
chatConsole.amendLastAssistantTurn(sessionId, content);
|
|
5419
|
+
}
|
|
5420
|
+
else if (!content.trim()) {
|
|
5421
|
+
content = "🤖 I couldn't get an answer from the model just now — please try again in a moment.";
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5237
5424
|
writeJson(res, 200, {
|
|
5238
5425
|
ok: true,
|
|
5239
5426
|
sessionId,
|
|
5240
|
-
content
|
|
5427
|
+
content,
|
|
5241
5428
|
followups: result.followups ?? [],
|
|
5242
5429
|
provider: result.provider ?? null,
|
|
5243
5430
|
model: result.model ?? null,
|
|
5244
5431
|
generationFailed: result.generationFailed === true,
|
|
5432
|
+
retryQueued,
|
|
5245
5433
|
});
|
|
5246
5434
|
})();
|
|
5247
5435
|
return;
|
|
@@ -5540,6 +5728,9 @@ function loadApiKeysFromConfig() {
|
|
|
5540
5728
|
}
|
|
5541
5729
|
}
|
|
5542
5730
|
export function createDashboardServer(opts) {
|
|
5731
|
+
// Deferred retries for this dashboard's own chat sessions. Armed here (not
|
|
5732
|
+
// per SSE client): a promised retry must run whether or not the tab is open.
|
|
5733
|
+
armChatRetryDrain();
|
|
5543
5734
|
// Bind values are resolved at CALL time: explicit override → env var →
|
|
5544
5735
|
// import-time default. (PORT/HOST above are module constants, so the CLI's
|
|
5545
5736
|
// `dashboard --port X` can NOT rely on setting BUFF_DASHBOARD_PORT after
|