agent-nuvira 3.1.0 → 3.1.3
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/context-gatherer.d.ts +5 -3
- package/dist/agents/agents/context-gatherer.d.ts.map +1 -1
- package/dist/agents/agents/context-gatherer.js +10 -9
- package/dist/agents/agents/context-gatherer.js.map +1 -1
- package/dist/agents/agents/reasoner.d.ts +2 -1
- package/dist/agents/agents/reasoner.d.ts.map +1 -1
- package/dist/agents/agents/reasoner.js +2 -1
- package/dist/agents/agents/reasoner.js.map +1 -1
- package/dist/agents/agents/reviewer-tool-calling.d.ts +3 -3
- package/dist/agents/agents/reviewer-tool-calling.js +3 -3
- package/dist/agents/agents/writer-tool-calling.d.ts +3 -3
- package/dist/agents/agents/writer-tool-calling.js +3 -3
- package/dist/agents/agents/writer.js +5 -5
- package/dist/agents/agents/writer.js.map +1 -1
- package/dist/agents/answer-quality-gate.d.ts +64 -0
- package/dist/agents/answer-quality-gate.d.ts.map +1 -0
- package/dist/agents/answer-quality-gate.js +94 -0
- package/dist/agents/answer-quality-gate.js.map +1 -0
- package/dist/agents/orchestrator.d.ts +6 -6
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +48 -24
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/agents/prompt-assembly.d.ts +3 -3
- package/dist/agents/prompt-assembly.js +3 -3
- package/dist/agents/tool-calling-agent.d.ts +3 -3
- package/dist/agents/tool-calling-agent.js +3 -3
- package/dist/cli/chat.d.ts +20 -9
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +167 -37
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/config.d.ts +16 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +73 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +59 -9
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/gateway.d.ts +7 -0
- package/dist/cli/gateway.d.ts.map +1 -1
- package/dist/cli/gateway.js +45 -0
- package/dist/cli/gateway.js.map +1 -1
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +87 -27
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/models.d.ts.map +1 -1
- package/dist/cli/models.js +63 -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/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +17 -3
- package/dist/config/manager.js.map +1 -1
- package/dist/config/types.d.ts +23 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/gateway/adapters.d.ts +24 -0
- package/dist/gateway/adapters.d.ts.map +1 -1
- package/dist/gateway/adapters.js +19 -0
- package/dist/gateway/adapters.js.map +1 -1
- package/dist/gateway/delivery.d.ts +7 -1
- package/dist/gateway/delivery.d.ts.map +1 -1
- package/dist/gateway/delivery.js +7 -1
- package/dist/gateway/delivery.js.map +1 -1
- package/dist/gateway/gateway-log.d.ts +74 -0
- package/dist/gateway/gateway-log.d.ts.map +1 -0
- package/dist/gateway/gateway-log.js +167 -0
- package/dist/gateway/gateway-log.js.map +1 -0
- package/dist/gateway/inbox.d.ts +8 -1
- package/dist/gateway/inbox.d.ts.map +1 -1
- package/dist/gateway/inbox.js.map +1 -1
- package/dist/gateway/registry.d.ts +212 -1
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +913 -46
- package/dist/gateway/registry.js.map +1 -1
- package/dist/gateway/whatsapp/baileys-bridge.d.ts +98 -1
- package/dist/gateway/whatsapp/baileys-bridge.d.ts.map +1 -1
- package/dist/gateway/whatsapp/baileys-bridge.js +264 -63
- package/dist/gateway/whatsapp/baileys-bridge.js.map +1 -1
- package/dist/gateway/whatsapp/bridge.d.ts +32 -0
- package/dist/gateway/whatsapp/bridge.d.ts.map +1 -1
- package/dist/gateway/whatsapp/bridge.js.map +1 -1
- package/dist/inference/tool-call-utils.d.ts +146 -0
- package/dist/inference/tool-call-utils.d.ts.map +1 -1
- package/dist/inference/tool-call-utils.js +450 -3
- package/dist/inference/tool-call-utils.js.map +1 -1
- package/dist/learning/auto-router.d.ts +65 -1
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +156 -1
- package/dist/learning/auto-router.js.map +1 -1
- 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/hub-skill-catalog.d.ts.map +1 -1
- package/dist/learning/hub-skill-catalog.js +10 -3
- package/dist/learning/hub-skill-catalog.js.map +1 -1
- package/dist/learning/model-first-router.d.ts.map +1 -1
- package/dist/learning/model-first-router.js +8 -0
- package/dist/learning/model-first-router.js.map +1 -1
- package/dist/learning/model-registry.d.ts +30 -0
- package/dist/learning/model-registry.d.ts.map +1 -1
- package/dist/learning/model-registry.js +61 -0
- package/dist/learning/model-registry.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +8 -0
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +1 -0
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/resilient-call.d.ts +116 -0
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +435 -15
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/nlu/conversation-gate.d.ts +22 -0
- package/dist/nlu/conversation-gate.d.ts.map +1 -1
- package/dist/nlu/conversation-gate.js +52 -8
- 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/intent.d.ts +35 -0
- package/dist/nlu/intent.d.ts.map +1 -1
- package/dist/nlu/intent.js +192 -3
- package/dist/nlu/intent.js.map +1 -1
- 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/nlu/schema.d.ts +2 -2
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +19 -3
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +5 -0
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/gateway-send.d.ts.map +1 -1
- package/dist/tools/gateway-send.js +14 -4
- package/dist/tools/gateway-send.js.map +1 -1
- package/dist/tools/loop-project-context.d.ts +3 -3
- package/dist/tools/loop-project-context.js +3 -3
- package/dist/tools/loop-skill-hint.d.ts +44 -7
- package/dist/tools/loop-skill-hint.d.ts.map +1 -1
- package/dist/tools/loop-skill-hint.js +117 -26
- package/dist/tools/loop-skill-hint.js.map +1 -1
- package/dist/tools/registry.d.ts +14 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +55 -2
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +161 -7
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/toolsets.js +2 -2
- package/dist/tools/toolsets.js.map +1 -1
- package/dist/tools/unified-diff.d.ts +2 -2
- package/dist/tools/unified-diff.js +2 -2
- package/dist/web-dashboard/chat-console.d.ts +41 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +133 -24
- 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 +219 -0
- package/dist/web-dashboard/chat-retry.js.map +1 -0
- package/dist/web-dashboard/server.d.ts +21 -2
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +138 -2
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +6 -3
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/web-dashboard/public/assets/{index-Pzkg6L8v.js → index-Co7Hk2FT.js} +60 -60
- package/src/web-dashboard/public/assets/index-Co7Hk2FT.js.map +1 -0
- package/src/web-dashboard/public/index.html +1 -1
- package/dist/app.js +0 -26
- package/dist/auth-api.d.ts +0 -2
- package/dist/auth-api.d.ts.map +0 -1
- package/dist/auth-api.js +0 -61
- package/dist/auth-api.js.map +0 -1
- package/dist/auth.test.d.ts +0 -1
- package/dist/auth.test.d.ts.map +0 -1
- package/dist/auth.test.js +0 -3
- package/dist/auth.test.js.map +0 -1
- package/dist/config/auth.js +0 -18
- package/dist/config/jwt.js +0 -24
- package/dist/config/keys.js +0 -14
- package/dist/file.d.ts +0 -1
- package/dist/file.d.ts.map +0 -1
- package/dist/file.js +0 -2
- package/dist/file.js.map +0 -1
- package/dist/middleware/auth.js +0 -30
- package/dist/passport.js +0 -34
- package/dist/routes/auth.d.ts +0 -3
- package/dist/routes/auth.d.ts.map +0 -1
- package/dist/routes/auth.js +0 -47
- package/dist/routes/auth.js.map +0 -1
- package/dist/routes/user.js +0 -31
- package/dist/server.js +0 -17
- package/dist/web-dashboard/src/App.js +0 -85
- package/dist/web-dashboard/src/admin-auth.test.js +0 -186
- package/dist/web-dashboard/src/ansi.js +0 -23
- package/dist/web-dashboard/src/ansi.test.js +0 -31
- package/dist/web-dashboard/src/api-admin-auth.test.js +0 -172
- package/dist/web-dashboard/src/api-admin.test.js +0 -65
- package/dist/web-dashboard/src/api-hub.test.js +0 -117
- package/dist/web-dashboard/src/api.js +0 -1421
- package/dist/web-dashboard/src/api.test.js +0 -51
- package/dist/web-dashboard/src/artifacts.js +0 -128
- package/dist/web-dashboard/src/artifacts.test.js +0 -139
- package/dist/web-dashboard/src/components/AdminPanel.js +0 -567
- package/dist/web-dashboard/src/components/AdminPanel.test.js +0 -288
- package/dist/web-dashboard/src/components/AgentHub.js +0 -1580
- package/dist/web-dashboard/src/components/AgentHub.test.js +0 -343
- package/dist/web-dashboard/src/components/BedrockOnboarding.js +0 -320
- package/dist/web-dashboard/src/components/BenchmarkCharts.js +0 -228
- package/dist/web-dashboard/src/components/ChatPage.js +0 -1586
- package/dist/web-dashboard/src/components/ChatPage.test.js +0 -899
- package/dist/web-dashboard/src/components/ContactsPage.js +0 -141
- package/dist/web-dashboard/src/components/CostDashboard.js +0 -105
- package/dist/web-dashboard/src/components/DAGView.js +0 -477
- package/dist/web-dashboard/src/components/DAGView.test.js +0 -147
- package/dist/web-dashboard/src/components/EnvVarEditor.js +0 -138
- package/dist/web-dashboard/src/components/EvalsPage.js +0 -73
- package/dist/web-dashboard/src/components/EvalsPage.test.js +0 -120
- package/dist/web-dashboard/src/components/ExecutionHistory.js +0 -201
- package/dist/web-dashboard/src/components/GatewayPage.js +0 -40
- package/dist/web-dashboard/src/components/GatewayPage.test.js +0 -74
- package/dist/web-dashboard/src/components/HealthPanel.js +0 -68
- package/dist/web-dashboard/src/components/HistoryBrowser.js +0 -34
- package/dist/web-dashboard/src/components/Layout.js +0 -50
- package/dist/web-dashboard/src/components/Markdown.js +0 -152
- package/dist/web-dashboard/src/components/Markdown.test.js +0 -126
- package/dist/web-dashboard/src/components/MarkdownZeroDep.js +0 -237
- package/dist/web-dashboard/src/components/MemoryPanel.js +0 -81
- package/dist/web-dashboard/src/components/ModelTimeline.js +0 -298
- package/dist/web-dashboard/src/components/ModelsPanel.js +0 -1484
- package/dist/web-dashboard/src/components/ModelsPanel.test.js +0 -460
- package/dist/web-dashboard/src/components/Overview.js +0 -123
- package/dist/web-dashboard/src/components/PhaseTimeline.js +0 -359
- package/dist/web-dashboard/src/components/PhaseTimeline.test.js +0 -234
- package/dist/web-dashboard/src/components/PlatformConfigSection.js +0 -384
- package/dist/web-dashboard/src/components/PlatformConfigSection.test.js +0 -106
- package/dist/web-dashboard/src/components/PlatformsPage.js +0 -250
- package/dist/web-dashboard/src/components/QuotaPanel.js +0 -159
- package/dist/web-dashboard/src/components/QuotaPanel.test.js +0 -85
- package/dist/web-dashboard/src/components/RequestsPanel.js +0 -229
- package/dist/web-dashboard/src/components/RequestsPanel.test.js +0 -103
- package/dist/web-dashboard/src/components/RoutingInsightsPanel.js +0 -938
- package/dist/web-dashboard/src/components/RoutingInsightsPanel.test.js +0 -339
- package/dist/web-dashboard/src/components/RoutingWalkthrough.js +0 -408
- package/dist/web-dashboard/src/components/RoutingWalkthrough.test.js +0 -209
- package/dist/web-dashboard/src/components/TaskConsole.js +0 -119
- package/dist/web-dashboard/src/components/TaskConsole.test.js +0 -123
- package/dist/web-dashboard/src/components/TasksPage.js +0 -256
- package/dist/web-dashboard/src/components/TasksPage.test.js +0 -103
- package/dist/web-dashboard/src/components/TracePanel.js +0 -306
- package/dist/web-dashboard/src/components/WhatsAppPanel.js +0 -195
- package/dist/web-dashboard/src/components/WhatsAppPanel.test.js +0 -122
- package/dist/web-dashboard/src/jsonOrNull.js +0 -37
- package/dist/web-dashboard/src/main.js +0 -15
- package/dist/web-dashboard/src/mask.js +0 -11
- package/dist/web-dashboard/vite.config.js +0 -23
- package/dist/web-dashboard/vitest.config.js +0 -15
- package/src/web-dashboard/public/assets/index-Pzkg6L8v.js.map +0 -1
|
@@ -31,9 +31,9 @@ const CONTEXT_SCAN_DIRS = ['.agents', '.nuvira', '.cursor'];
|
|
|
31
31
|
// ─── Project Assessment ─────────────────────────────────────────────────────
|
|
32
32
|
/**
|
|
33
33
|
* Assess the project before executing tasks.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Detect framework, language, and project state before generating prompts.
|
|
35
|
+
* This replaces the generic context-gatherer with a fast, deterministic
|
|
36
|
+
* assessment.
|
|
37
37
|
*/
|
|
38
38
|
export function assessProject(workingDirectory) {
|
|
39
39
|
const assessment = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ToolCallingAgent — Base class for agents that use a tool-calling loop.
|
|
3
3
|
*
|
|
4
|
-
* Adopts the proven pattern
|
|
4
|
+
* Adopts the proven agentic pattern:
|
|
5
5
|
* LLM generates tool call → Agent executes tool → Result fed back → Loop
|
|
6
6
|
*
|
|
7
7
|
* KEY CONSTRAINT: This agent does NOT write to disk. Tools propose FileChange
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* agent returns. This preserves dry-run mode, rollback, and audit trail.
|
|
10
10
|
*
|
|
11
11
|
* Reference:
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
12
|
+
* - tool-calling loop: LLM step → tool dispatch → observation → next step
|
|
13
|
+
* - tool dispatch loop: run the requested tool, feed the result back
|
|
14
14
|
*
|
|
15
15
|
* The tool-calling is prompt-based (not native function calling) because
|
|
16
16
|
* the existing LLMCallFn interface doesn't support tool definitions.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ToolCallingAgent — Base class for agents that use a tool-calling loop.
|
|
3
3
|
*
|
|
4
|
-
* Adopts the proven pattern
|
|
4
|
+
* Adopts the proven agentic pattern:
|
|
5
5
|
* LLM generates tool call → Agent executes tool → Result fed back → Loop
|
|
6
6
|
*
|
|
7
7
|
* KEY CONSTRAINT: This agent does NOT write to disk. Tools propose FileChange
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* agent returns. This preserves dry-run mode, rollback, and audit trail.
|
|
10
10
|
*
|
|
11
11
|
* Reference:
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
12
|
+
* - tool-calling loop: LLM step → tool dispatch → observation → next step
|
|
13
|
+
* - tool dispatch loop: run the requested tool, feed the result back
|
|
14
14
|
*
|
|
15
15
|
* The tool-calling is prompt-based (not native function calling) because
|
|
16
16
|
* the existing LLMCallFn interface doesn't support tool definitions.
|
package/dist/cli/chat.d.ts
CHANGED
|
@@ -32,18 +32,24 @@ export interface DispatchAssessmentOptions {
|
|
|
32
32
|
text?: string;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* E3a/E3c — the rule assessment (
|
|
35
|
+
* E3a/E3c — the rule assessment (no-model fallback source).
|
|
36
36
|
*
|
|
37
37
|
* The legacy `promptDeveloperMode` menu ("1. Chat mode / 2. Developer mode")
|
|
38
38
|
* is DELETED (Session 7c re-scope, landed in E3a). E3c demotes the rules
|
|
39
|
-
* further (model-decides): EVERY request runs as a
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
39
|
+
* further (model-decides): EVERY request runs as a tool-call turn and the
|
|
40
|
+
* MODEL decides what to do. This function computes what the RULES would say,
|
|
41
|
+
* used for ONE thing only: the no-model fallback — when the tool loop fails to
|
|
42
|
+
* generate a single response AND the rules assessed a high-confidence pipeline
|
|
43
|
+
* intent, the pipeline runs directly. Rules act ONLY when the model is
|
|
44
|
+
* unavailable, never as a bypass.
|
|
45
|
+
*
|
|
46
|
+
* It is deliberately NOT injected into the model's system prompt: commit
|
|
47
|
+
* 4d30b7e removed prompt-level intent steering ("give the LLM tools and let it
|
|
48
|
+
* decide") and a test guards against its return. So on THIS surface the model
|
|
49
|
+
* decides, and `resolveAskKind` does not determine the outcome — that is only
|
|
50
|
+
* true of the GATEWAY, which routes on it BEFORE the model is called. Same
|
|
51
|
+
* rule, two different consequences: do not read a routing table here as a
|
|
52
|
+
* prediction of what `nuvira chat` will do.
|
|
47
53
|
* `dev` (the --dev flag / /dev toggle) forces the assessment to dispatch.
|
|
48
54
|
*/
|
|
49
55
|
export declare function resolvePipelineDispatch(parsed: ParsedRequest, opts?: DispatchAssessmentOptions): PipelineDispatchDecision;
|
|
@@ -241,6 +247,11 @@ export declare class ChatCommand extends BaseCommand {
|
|
|
241
247
|
* unverified claim. Every surface must treat this as "not confirmed done".
|
|
242
248
|
*/
|
|
243
249
|
unverifiedActionClaim?: boolean;
|
|
250
|
+
/**
|
|
251
|
+
* True when the answer closed on a promise to act that the turn never
|
|
252
|
+
* carried out. Surfaces must not present such a turn as "in progress".
|
|
253
|
+
*/
|
|
254
|
+
unfulfilledPromise?: boolean;
|
|
244
255
|
provider?: string;
|
|
245
256
|
model?: string;
|
|
246
257
|
}>;
|
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;AAgCxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,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
|
@@ -19,7 +19,7 @@ import { applyActiveModel } from './model.js';
|
|
|
19
19
|
import { getProviderFallback, classifyFallbackError, isRetryableError, isTransientForRetry, recordRegistrySuccess } from '../learning/provider-fallback.js';
|
|
20
20
|
import { recordActionFailure } from '../learning/failure-bookkeeping.js';
|
|
21
21
|
import { resolveThreadBudgetChars } from '../learning/context-budget.js';
|
|
22
|
-
import { getAutoRouter, isAutoModel, isAutoProvider } from '../learning/auto-router.js';
|
|
22
|
+
import { getAutoRouter, isAutoModel, isAutoProvider, governanceVerdict } from '../learning/auto-router.js';
|
|
23
23
|
import { estimateTokens } from '../learning/cost-tracker.js';
|
|
24
24
|
import { getModelRegistry } from '../learning/model-registry.js';
|
|
25
25
|
import { refreshModelRegistry } from '../inference/model-probe.js';
|
|
@@ -34,11 +34,11 @@ import { recordMetricTime, getMetrics } from '../enterprise/metrics.js';
|
|
|
34
34
|
import { resolveDispatch } from '../nlu/actions.js';
|
|
35
35
|
import { hasCodingAction, resolveAskKind } from '../nlu/conversation-gate.js';
|
|
36
36
|
import { runToolLoop, extractFallbackToolCalls } from '../tools/tool-loop.js';
|
|
37
|
-
import {
|
|
37
|
+
import { detectAnswerQualityFailure, answerQualityError, toUserFacingGenerationError, isToolCallingUnsupported, stripToolCallArtifacts, } from '../inference/tool-call-utils.js';
|
|
38
38
|
import { beginTrace, endTrace, recordStep, buildTraceOutcome } from '../learning/reasoning-trace.js';
|
|
39
39
|
import { getLoopExposureMode } from '../tools/toolsets.js';
|
|
40
40
|
import { resolveModelHarnessProfile, shouldSkipNativeTools } from '../learning/model-harness.js';
|
|
41
|
-
import { resolveAdapterDefault } from '../learning/model-selection.js';
|
|
41
|
+
import { resolveAdapterDefault, hasCredentials } from '../learning/model-selection.js';
|
|
42
42
|
import { buildLoopProjectContext } from '../tools/loop-project-context.js';
|
|
43
43
|
import { sweepTransientFailures, collectionRevivalStore } from '../learning/provider-revival.js';
|
|
44
44
|
import { analyzeComplexity } from '../learning/hybrid-router.js';
|
|
@@ -148,18 +148,24 @@ async function handleInferenceError(err, providerName, configManager) {
|
|
|
148
148
|
return { action: answer.action };
|
|
149
149
|
}
|
|
150
150
|
/**
|
|
151
|
-
* E3a/E3c — the rule assessment (
|
|
151
|
+
* E3a/E3c — the rule assessment (no-model fallback source).
|
|
152
152
|
*
|
|
153
153
|
* The legacy `promptDeveloperMode` menu ("1. Chat mode / 2. Developer mode")
|
|
154
154
|
* is DELETED (Session 7c re-scope, landed in E3a). E3c demotes the rules
|
|
155
|
-
* further (model-decides): EVERY request runs as a
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
155
|
+
* further (model-decides): EVERY request runs as a tool-call turn and the
|
|
156
|
+
* MODEL decides what to do. This function computes what the RULES would say,
|
|
157
|
+
* used for ONE thing only: the no-model fallback — when the tool loop fails to
|
|
158
|
+
* generate a single response AND the rules assessed a high-confidence pipeline
|
|
159
|
+
* intent, the pipeline runs directly. Rules act ONLY when the model is
|
|
160
|
+
* unavailable, never as a bypass.
|
|
161
|
+
*
|
|
162
|
+
* It is deliberately NOT injected into the model's system prompt: commit
|
|
163
|
+
* 4d30b7e removed prompt-level intent steering ("give the LLM tools and let it
|
|
164
|
+
* decide") and a test guards against its return. So on THIS surface the model
|
|
165
|
+
* decides, and `resolveAskKind` does not determine the outcome — that is only
|
|
166
|
+
* true of the GATEWAY, which routes on it BEFORE the model is called. Same
|
|
167
|
+
* rule, two different consequences: do not read a routing table here as a
|
|
168
|
+
* prediction of what `nuvira chat` will do.
|
|
163
169
|
* `dev` (the --dev flag / /dev toggle) forces the assessment to dispatch.
|
|
164
170
|
*/
|
|
165
171
|
export function resolvePipelineDispatch(parsed, opts) {
|
|
@@ -430,7 +436,11 @@ export class ChatCommand extends BaseCommand {
|
|
|
430
436
|
// pipeline resolves its own working provider/model).
|
|
431
437
|
if (answer.generationFailed && dispatchDecision.dispatch && !dispatchDecision.needConfirm) {
|
|
432
438
|
const r = await runPipelineTool(message, this.configManager, { provider: type, model, board: false });
|
|
433
|
-
|
|
439
|
+
// `success`, not `error`: a pipeline that RAN and failed reports its
|
|
440
|
+
// outcome in `summary` and only sometimes sets `error`, so keying off
|
|
441
|
+
// `error` alone returned a failed run's summary with NO failure flag —
|
|
442
|
+
// i.e. reported it as a successful turn on every surface.
|
|
443
|
+
if (!r.success) {
|
|
434
444
|
return { content: '', followups: [], generationFailed: true, provider: type, model };
|
|
435
445
|
}
|
|
436
446
|
return { content: r.result?.summary ?? '', followups: [], provider: type, model };
|
|
@@ -445,6 +455,7 @@ export class ChatCommand extends BaseCommand {
|
|
|
445
455
|
bounded: answer.bounded,
|
|
446
456
|
toolCalls: answer.toolCalls,
|
|
447
457
|
unverifiedActionClaim: answer.unverifiedActionClaim,
|
|
458
|
+
unfulfilledPromise: answer.unfulfilledPromise,
|
|
448
459
|
provider: type,
|
|
449
460
|
model,
|
|
450
461
|
};
|
|
@@ -548,10 +559,9 @@ export class ChatCommand extends BaseCommand {
|
|
|
548
559
|
model = routed.model;
|
|
549
560
|
}
|
|
550
561
|
// E3c: model-decides — EVERY request runs as a TOOL-CALL TURN. The
|
|
551
|
-
// rule assessment is
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
// as a bypass.
|
|
562
|
+
// model decides what to do; the rule assessment is NOT in its context
|
|
563
|
+
// (4d30b7e) and acts ONLY as the no-model fallback below (generation
|
|
564
|
+
// failed entirely), never as a bypass.
|
|
555
565
|
const parsed = parseRequestSync(prompt);
|
|
556
566
|
const dispatchDecision = resolvePipelineDispatch(parsed, { dev: options?.dev, text: prompt });
|
|
557
567
|
const answer = await this.runChatAnswer(prompt, [], { type, provider, model }, options || {}, cacheEnabled, { auto: autoMode }, parsed);
|
|
@@ -814,8 +824,11 @@ export class ChatCommand extends BaseCommand {
|
|
|
814
824
|
history.push({ role: 'user', content: message });
|
|
815
825
|
// System prompt: base identity + the tool contract — the
|
|
816
826
|
// model clarifies with ask_user and ends every response with followups.
|
|
817
|
-
//
|
|
818
|
-
//
|
|
827
|
+
//
|
|
828
|
+
// NO rule-based intent steering goes in here (commit 4d30b7e removed it on
|
|
829
|
+
// purpose: "give the LLM tools and let it decide"). `dispatchDecision` is
|
|
830
|
+
// computed for the generation-FAILED fallback only — it does NOT reach the
|
|
831
|
+
// prompt, so on this surface the MODEL decides and the rules are invisible.
|
|
819
832
|
const systemText = buildToolSystemPrompt(parsed);
|
|
820
833
|
// Phase 3.2 (assessment Addendum v4) — loop-side skill match hint: the
|
|
821
834
|
// orchestrator consults SkillStore.findMatch + the hub catalog before
|
|
@@ -1080,7 +1093,10 @@ export class ChatCommand extends BaseCommand {
|
|
|
1080
1093
|
});
|
|
1081
1094
|
}
|
|
1082
1095
|
catch (err) {
|
|
1083
|
-
// The tool loop
|
|
1096
|
+
// The tool loop does not throw on its own; this catches the errors that
|
|
1097
|
+
// ARE meant to propagate — most importantly the ANSWER-QUALITY rejection
|
|
1098
|
+
// (`answerQualityError`), which the loop rethrows once every candidate has
|
|
1099
|
+
// narrated.
|
|
1084
1100
|
logger.error(String(err));
|
|
1085
1101
|
endTrace(chatTraceId, false, { kind: 'failed' });
|
|
1086
1102
|
result = {
|
|
@@ -1091,6 +1107,14 @@ export class ChatCommand extends BaseCommand {
|
|
|
1091
1107
|
toolCalls: [],
|
|
1092
1108
|
steps: 0,
|
|
1093
1109
|
bounded: false,
|
|
1110
|
+
// AND it is a FAILURE. Without this the honest line was returned as a
|
|
1111
|
+
// SUCCESSFUL turn: the dashboard offered no retry and queued nothing,
|
|
1112
|
+
// the gateway reported the turn as fine, and the line was free to be
|
|
1113
|
+
// cached as the model's answer. Caught live on the dashboard surface —
|
|
1114
|
+
// the bubble read "The model wrote its own working notes instead of an
|
|
1115
|
+
// answer…" while `generationFailed` was false, so the one thing the
|
|
1116
|
+
// reader could have done about it (retry) was never offered.
|
|
1117
|
+
generationFailed: true,
|
|
1094
1118
|
};
|
|
1095
1119
|
}
|
|
1096
1120
|
// Record WHAT HAPPENED, not just "the model answered": a hallucinated
|
|
@@ -1099,8 +1123,12 @@ export class ChatCommand extends BaseCommand {
|
|
|
1099
1123
|
endTrace(chatTraceId, !result.generationFailed, buildTraceOutcome({
|
|
1100
1124
|
generationFailed: result.generationFailed,
|
|
1101
1125
|
cancelled: result.cancelled,
|
|
1102
|
-
|
|
1126
|
+
// What actually RAN successfully, not what was attempted: a failed
|
|
1127
|
+
// `gateway_send` must not make the trace read
|
|
1128
|
+
// "✅ action performed — message sent".
|
|
1129
|
+
tools: result.successfulToolCalls ?? result.toolCalls,
|
|
1103
1130
|
unverifiedActionClaim: result.unverifiedActionClaim,
|
|
1131
|
+
unfulfilledPromise: result.unfulfilledPromise,
|
|
1104
1132
|
}));
|
|
1105
1133
|
// Finalize the turn (cache + memory + registry telemetry).
|
|
1106
1134
|
// E3c: a generationFailed turn is NOT cached/persisted — the caller may
|
|
@@ -1146,6 +1174,7 @@ export class ChatCommand extends BaseCommand {
|
|
|
1146
1174
|
followups: result.followups,
|
|
1147
1175
|
toolCalls: result.toolCalls,
|
|
1148
1176
|
unverifiedActionClaim: result.unverifiedActionClaim,
|
|
1177
|
+
unfulfilledPromise: result.unfulfilledPromise,
|
|
1149
1178
|
};
|
|
1150
1179
|
}
|
|
1151
1180
|
/**
|
|
@@ -1212,6 +1241,33 @@ export class ChatCommand extends BaseCommand {
|
|
|
1212
1241
|
* path automatically.
|
|
1213
1242
|
*/
|
|
1214
1243
|
const nativeToolsRejected = new Set();
|
|
1244
|
+
/**
|
|
1245
|
+
* GOVERNANCE PRE-FLIGHT (pinned path).
|
|
1246
|
+
*
|
|
1247
|
+
* An explicit pin bypasses `autoRouter.resolve`, and the admin policy
|
|
1248
|
+
* (provider/model allow+deny lists, the PII privacy hard-gate) is enforced
|
|
1249
|
+
* inside resolve — so a pinned turn was the one way to serve a provider
|
|
1250
|
+
* the policy rules out, and a failed pinned turn would happily fall back
|
|
1251
|
+
* to one. A privacy policy any pin can bypass is not a policy. Checked
|
|
1252
|
+
* BEFORE the first network call so a blocked turn costs nothing, and
|
|
1253
|
+
* thrown as a typed policy error so `toUserFacingGenerationError` surfaces
|
|
1254
|
+
* the REASON instead of "the language model was unavailable".
|
|
1255
|
+
*
|
|
1256
|
+
* No policy configured → `governanceVerdict` is permissive and this is a
|
|
1257
|
+
* no-op (unchanged behaviour for every existing setup).
|
|
1258
|
+
*/
|
|
1259
|
+
if (!mode.auto) {
|
|
1260
|
+
const verdict = governanceVerdict(this.configManager, session.type, {
|
|
1261
|
+
model: session.model,
|
|
1262
|
+
taskText: message,
|
|
1263
|
+
});
|
|
1264
|
+
if (!verdict.allowed) {
|
|
1265
|
+
// Prefixed so `toUserFacingGenerationError` reports the POLICY, not a
|
|
1266
|
+
// phantom unavailable model (nothing was unreachable — a rule
|
|
1267
|
+
// refused it). The reason names the provider and the rule.
|
|
1268
|
+
throw new Error(`Governance policy: ${verdict.reason}`);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1215
1271
|
const resolveEffectiveModel = (providerType, requested) => {
|
|
1216
1272
|
if (requested && requested !== 'default')
|
|
1217
1273
|
return requested;
|
|
@@ -1236,16 +1292,25 @@ export class ChatCommand extends BaseCommand {
|
|
|
1236
1292
|
// the tool contract (e.g. apologizing that "the provided example call
|
|
1237
1293
|
// to suggest_followups is incomplete") instead of executing it — never
|
|
1238
1294
|
// throws, so failover never fired and the confusion went to the user
|
|
1239
|
-
// verbatim (live WhatsApp incident).
|
|
1240
|
-
//
|
|
1241
|
-
//
|
|
1242
|
-
//
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1295
|
+
// verbatim (live WhatsApp incident). Same for the OTHER quality failure:
|
|
1296
|
+
// the model delivering its own REASONING ("The user said \"Hi\" …
|
|
1297
|
+
// According to the instructions: …"). One shared detector
|
|
1298
|
+
// (`detectAnswerQualityFailure`) is used here and by the loop engine
|
|
1299
|
+
// (`nuvira execute` / the pipeline), so neither surface can drift.
|
|
1300
|
+
// Treat it like a generation failure: THROWS so the caller's failover
|
|
1301
|
+
// walk retries with the next candidate; the raw reply is carried on the
|
|
1302
|
+
// error for the final fallback.
|
|
1303
|
+
// `hasToolCalls` selects the strictness tier: a step that is ACTING may
|
|
1304
|
+
// legitimately open with a first-person narration ("Let me check the
|
|
1305
|
+
// config.") before its tool call, and rejecting it would throw the call
|
|
1306
|
+
// away — so a tool-carrying step is judged on the high-precision
|
|
1307
|
+
// signals only, while the step that IS the answer is judged on all.
|
|
1308
|
+
const confuseCheck = (content, hasToolCalls = false) => {
|
|
1309
|
+
const failure = detectAnswerQualityFailure(content, undefined, {
|
|
1310
|
+
highPrecisionOnly: hasToolCalls,
|
|
1311
|
+
});
|
|
1312
|
+
if (failure)
|
|
1313
|
+
throw answerQualityError(content, failure);
|
|
1249
1314
|
};
|
|
1250
1315
|
/** Mark the model that actually produced this response. */
|
|
1251
1316
|
const answered = (resp) => {
|
|
@@ -1268,11 +1333,11 @@ export class ChatCommand extends BaseCommand {
|
|
|
1268
1333
|
// still receives the answer (appears at once — today's behavior).
|
|
1269
1334
|
if (sink && typeof prov.generateToolsStream === 'function') {
|
|
1270
1335
|
const result = await prov.generateToolsStream(messages, schemas, { ...options, model: effectiveModel, signal: abort }, sink);
|
|
1271
|
-
confuseCheck(result.content);
|
|
1336
|
+
confuseCheck(result.content, result.toolCalls.length > 0);
|
|
1272
1337
|
return answered(result);
|
|
1273
1338
|
}
|
|
1274
1339
|
const result = await prov.generateTools(messages, schemas, { ...options, model: effectiveModel, signal: abort });
|
|
1275
|
-
confuseCheck(result.content);
|
|
1340
|
+
confuseCheck(result.content, result.toolCalls.length > 0);
|
|
1276
1341
|
if (sink && result.content)
|
|
1277
1342
|
sink(result.content);
|
|
1278
1343
|
return answered(result);
|
|
@@ -1286,7 +1351,7 @@ export class ChatCommand extends BaseCommand {
|
|
|
1286
1351
|
if (salvaged) {
|
|
1287
1352
|
// The salvaged essay can itself be contract-confusion — check it
|
|
1288
1353
|
// too, otherwise a confused 400 payload sails through salvage.
|
|
1289
|
-
confuseCheck(salvaged.content);
|
|
1354
|
+
confuseCheck(salvaged.content, (salvaged.followups?.length ?? 0) > 0);
|
|
1290
1355
|
logger.warn(" ⚠️ Tool call rejected (400) — salvaging the model's generated answer.");
|
|
1291
1356
|
// Re-run the recovered suggest_followups through the normal tool
|
|
1292
1357
|
// path so the followups land in the sink (and the loop's
|
|
@@ -1327,7 +1392,7 @@ export class ChatCommand extends BaseCommand {
|
|
|
1327
1392
|
raw = await prov.generate(prompt, { ...options, model: effectiveModel, signal: abort });
|
|
1328
1393
|
}
|
|
1329
1394
|
const { text, calls } = extractFallbackToolCalls(raw);
|
|
1330
|
-
confuseCheck(text);
|
|
1395
|
+
confuseCheck(text, calls.length > 0);
|
|
1331
1396
|
return answered({ content: text, toolCalls: calls });
|
|
1332
1397
|
};
|
|
1333
1398
|
try {
|
|
@@ -1394,15 +1459,68 @@ export class ChatCommand extends BaseCommand {
|
|
|
1394
1459
|
}
|
|
1395
1460
|
else if (isRetryableError(classifyFallbackError(err))) {
|
|
1396
1461
|
// Non-auto: walk the shared fallback chain (retryable errors only).
|
|
1462
|
+
// Providers the admin policy rules out are collected here so the
|
|
1463
|
+
// failure can name POLICY as the reason instead of implying the model
|
|
1464
|
+
// was unreachable.
|
|
1465
|
+
const policyBlocked = [];
|
|
1397
1466
|
try {
|
|
1398
1467
|
const fallback = getProviderFallback(this.configManager, this.configManager.getAll().fallback);
|
|
1399
1468
|
const chain = fallback.getFallbackChain(session.type);
|
|
1400
|
-
|
|
1469
|
+
/**
|
|
1470
|
+
* Order the chain the way `loop-executor`'s pinned pool does: a
|
|
1471
|
+
* registry-parked / cooling-down provider goes LAST (never dropped,
|
|
1472
|
+
* since the whole point of failover is to reach what the primary
|
|
1473
|
+
* could not). Best-effort — ordering must never cost us the chain.
|
|
1474
|
+
*/
|
|
1475
|
+
let ordered = chain;
|
|
1476
|
+
try {
|
|
1477
|
+
const isExcluded = createFailoverExclusionFilter();
|
|
1478
|
+
ordered = [
|
|
1479
|
+
...chain.filter((t) => !isExcluded(t)),
|
|
1480
|
+
...chain.filter((t) => isExcluded(t)),
|
|
1481
|
+
];
|
|
1482
|
+
}
|
|
1483
|
+
catch {
|
|
1484
|
+
// Ordering is an optimization only.
|
|
1485
|
+
}
|
|
1486
|
+
for (const fbType of ordered) {
|
|
1401
1487
|
if (fbType === session.type)
|
|
1402
1488
|
continue;
|
|
1489
|
+
// ADMIN POLICY: never fall back to a provider the policy rules
|
|
1490
|
+
// out. This was the leak — a PII task whose pinned (compliant)
|
|
1491
|
+
// provider failed would silently continue on a provider the
|
|
1492
|
+
// privacy policy forbids. Recorded so the turn can SAY why the
|
|
1493
|
+
// walk found nothing instead of blaming the model.
|
|
1494
|
+
const policy = governanceVerdict(this.configManager, fbType, {
|
|
1495
|
+
taskText: message,
|
|
1496
|
+
});
|
|
1497
|
+
if (!policy.allowed) {
|
|
1498
|
+
policyBlocked.push({ provider: fbType, reason: policy.reason ?? 'blocked by policy' });
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
// Only providers the user can actually CALL. An explicit
|
|
1502
|
+
// `fallback.providers` entry with no key is not filtered out by
|
|
1503
|
+
// the chain itself, so it used to cost a full connection timeout
|
|
1504
|
+
// (measured live at ~25s against an unauthenticated endpoint)
|
|
1505
|
+
// before the next candidate was tried — time the sender spends
|
|
1506
|
+
// waiting for a reply that is already failing. Same credential
|
|
1507
|
+
// gate `loop-executor`'s pinned pool applies.
|
|
1508
|
+
if (!hasCredentials(this.configManager, fbType))
|
|
1509
|
+
continue;
|
|
1403
1510
|
try {
|
|
1404
1511
|
const resolved = resolveProvider(this.configManager, fbType);
|
|
1405
|
-
|
|
1512
|
+
// The fallback provider gets ITS OWN model, not the primary's
|
|
1513
|
+
// id. Reusing `session.model` here sent e.g. gemini's
|
|
1514
|
+
// `gemini-3.1-flash-lite` to groq, which 404s “model not found”
|
|
1515
|
+
// — so every fallback candidate failed for a reason unrelated
|
|
1516
|
+
// to the outage and a pinned-provider turn dead-ended with
|
|
1517
|
+
// “the language model was unavailable” even though healthy
|
|
1518
|
+
// providers were available. (The auto branch below has always
|
|
1519
|
+
// passed `next.model`, which is why only the pinned path -
|
|
1520
|
+
// the dashboard console and the gateway chat engine - looked
|
|
1521
|
+
// dead.) Undefined = that provider's configured/adapter
|
|
1522
|
+
// default, which tryGenerate resolves per attempt.
|
|
1523
|
+
return await tryGenerate(resolved.provider, resolved.type, resolveEffectiveModel(resolved.type, undefined));
|
|
1406
1524
|
}
|
|
1407
1525
|
catch {
|
|
1408
1526
|
// Next fallback candidate.
|
|
@@ -1412,6 +1530,18 @@ export class ChatCommand extends BaseCommand {
|
|
|
1412
1530
|
catch {
|
|
1413
1531
|
// Fall through to rethrow.
|
|
1414
1532
|
}
|
|
1533
|
+
// Every fallback candidate was refused by ADMIN POLICY (and none
|
|
1534
|
+
// answered): the honest answer is the policy block, not "the language
|
|
1535
|
+
// model was unavailable". Surfaced through the same
|
|
1536
|
+
// `Governance policy:` prefix the pre-flight check uses.
|
|
1537
|
+
if (policyBlocked.length > 0) {
|
|
1538
|
+
logger.warn(` ⚠️ Governance policy blocked every fallback provider: ${policyBlocked
|
|
1539
|
+
.map((b) => `${b.provider} (${b.reason})`)
|
|
1540
|
+
.join('; ')}`);
|
|
1541
|
+
throw new Error(`Governance policy: no permitted fallback provider was available — ${policyBlocked
|
|
1542
|
+
.map((b) => `${b.provider}: ${b.reason}`)
|
|
1543
|
+
.join('; ')}`);
|
|
1544
|
+
}
|
|
1415
1545
|
}
|
|
1416
1546
|
// Answer-quality resilience: every candidate failed (or none was
|
|
1417
1547
|
// tried) and the error carries the model's raw confused reply —
|