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.
Files changed (263) hide show
  1. package/dist/agents/agents/context-gatherer.d.ts +5 -3
  2. package/dist/agents/agents/context-gatherer.d.ts.map +1 -1
  3. package/dist/agents/agents/context-gatherer.js +10 -9
  4. package/dist/agents/agents/context-gatherer.js.map +1 -1
  5. package/dist/agents/agents/reasoner.d.ts +2 -1
  6. package/dist/agents/agents/reasoner.d.ts.map +1 -1
  7. package/dist/agents/agents/reasoner.js +2 -1
  8. package/dist/agents/agents/reasoner.js.map +1 -1
  9. package/dist/agents/agents/reviewer-tool-calling.d.ts +3 -3
  10. package/dist/agents/agents/reviewer-tool-calling.js +3 -3
  11. package/dist/agents/agents/writer-tool-calling.d.ts +3 -3
  12. package/dist/agents/agents/writer-tool-calling.js +3 -3
  13. package/dist/agents/agents/writer.js +5 -5
  14. package/dist/agents/agents/writer.js.map +1 -1
  15. package/dist/agents/answer-quality-gate.d.ts +64 -0
  16. package/dist/agents/answer-quality-gate.d.ts.map +1 -0
  17. package/dist/agents/answer-quality-gate.js +94 -0
  18. package/dist/agents/answer-quality-gate.js.map +1 -0
  19. package/dist/agents/orchestrator.d.ts +6 -6
  20. package/dist/agents/orchestrator.d.ts.map +1 -1
  21. package/dist/agents/orchestrator.js +48 -24
  22. package/dist/agents/orchestrator.js.map +1 -1
  23. package/dist/agents/prompt-assembly.d.ts +3 -3
  24. package/dist/agents/prompt-assembly.js +3 -3
  25. package/dist/agents/tool-calling-agent.d.ts +3 -3
  26. package/dist/agents/tool-calling-agent.js +3 -3
  27. package/dist/cli/chat.d.ts +20 -9
  28. package/dist/cli/chat.d.ts.map +1 -1
  29. package/dist/cli/chat.js +167 -37
  30. package/dist/cli/chat.js.map +1 -1
  31. package/dist/cli/config.d.ts +16 -0
  32. package/dist/cli/config.d.ts.map +1 -1
  33. package/dist/cli/config.js +73 -1
  34. package/dist/cli/config.js.map +1 -1
  35. package/dist/cli/execute.d.ts.map +1 -1
  36. package/dist/cli/execute.js +59 -9
  37. package/dist/cli/execute.js.map +1 -1
  38. package/dist/cli/gateway.d.ts +7 -0
  39. package/dist/cli/gateway.d.ts.map +1 -1
  40. package/dist/cli/gateway.js +45 -0
  41. package/dist/cli/gateway.js.map +1 -1
  42. package/dist/cli/loop-executor.d.ts.map +1 -1
  43. package/dist/cli/loop-executor.js +87 -27
  44. package/dist/cli/loop-executor.js.map +1 -1
  45. package/dist/cli/models.d.ts.map +1 -1
  46. package/dist/cli/models.js +63 -0
  47. package/dist/cli/models.js.map +1 -1
  48. package/dist/cli/nlu.d.ts +8 -0
  49. package/dist/cli/nlu.d.ts.map +1 -1
  50. package/dist/cli/nlu.js +57 -0
  51. package/dist/cli/nlu.js.map +1 -1
  52. package/dist/config/manager.d.ts.map +1 -1
  53. package/dist/config/manager.js +17 -3
  54. package/dist/config/manager.js.map +1 -1
  55. package/dist/config/types.d.ts +23 -0
  56. package/dist/config/types.d.ts.map +1 -1
  57. package/dist/gateway/adapters.d.ts +24 -0
  58. package/dist/gateway/adapters.d.ts.map +1 -1
  59. package/dist/gateway/adapters.js +19 -0
  60. package/dist/gateway/adapters.js.map +1 -1
  61. package/dist/gateway/delivery.d.ts +7 -1
  62. package/dist/gateway/delivery.d.ts.map +1 -1
  63. package/dist/gateway/delivery.js +7 -1
  64. package/dist/gateway/delivery.js.map +1 -1
  65. package/dist/gateway/gateway-log.d.ts +74 -0
  66. package/dist/gateway/gateway-log.d.ts.map +1 -0
  67. package/dist/gateway/gateway-log.js +167 -0
  68. package/dist/gateway/gateway-log.js.map +1 -0
  69. package/dist/gateway/inbox.d.ts +8 -1
  70. package/dist/gateway/inbox.d.ts.map +1 -1
  71. package/dist/gateway/inbox.js.map +1 -1
  72. package/dist/gateway/registry.d.ts +212 -1
  73. package/dist/gateway/registry.d.ts.map +1 -1
  74. package/dist/gateway/registry.js +913 -46
  75. package/dist/gateway/registry.js.map +1 -1
  76. package/dist/gateway/whatsapp/baileys-bridge.d.ts +98 -1
  77. package/dist/gateway/whatsapp/baileys-bridge.d.ts.map +1 -1
  78. package/dist/gateway/whatsapp/baileys-bridge.js +264 -63
  79. package/dist/gateway/whatsapp/baileys-bridge.js.map +1 -1
  80. package/dist/gateway/whatsapp/bridge.d.ts +32 -0
  81. package/dist/gateway/whatsapp/bridge.d.ts.map +1 -1
  82. package/dist/gateway/whatsapp/bridge.js.map +1 -1
  83. package/dist/inference/tool-call-utils.d.ts +146 -0
  84. package/dist/inference/tool-call-utils.d.ts.map +1 -1
  85. package/dist/inference/tool-call-utils.js +450 -3
  86. package/dist/inference/tool-call-utils.js.map +1 -1
  87. package/dist/learning/auto-router.d.ts +65 -1
  88. package/dist/learning/auto-router.d.ts.map +1 -1
  89. package/dist/learning/auto-router.js +156 -1
  90. package/dist/learning/auto-router.js.map +1 -1
  91. package/dist/learning/deferred-task.d.ts +159 -0
  92. package/dist/learning/deferred-task.d.ts.map +1 -0
  93. package/dist/learning/deferred-task.js +451 -0
  94. package/dist/learning/deferred-task.js.map +1 -0
  95. package/dist/learning/hub-skill-catalog.d.ts.map +1 -1
  96. package/dist/learning/hub-skill-catalog.js +10 -3
  97. package/dist/learning/hub-skill-catalog.js.map +1 -1
  98. package/dist/learning/model-first-router.d.ts.map +1 -1
  99. package/dist/learning/model-first-router.js +8 -0
  100. package/dist/learning/model-first-router.js.map +1 -1
  101. package/dist/learning/model-registry.d.ts +30 -0
  102. package/dist/learning/model-registry.d.ts.map +1 -1
  103. package/dist/learning/model-registry.js +61 -0
  104. package/dist/learning/model-registry.js.map +1 -1
  105. package/dist/learning/reasoning-trace.d.ts +8 -0
  106. package/dist/learning/reasoning-trace.d.ts.map +1 -1
  107. package/dist/learning/reasoning-trace.js +1 -0
  108. package/dist/learning/reasoning-trace.js.map +1 -1
  109. package/dist/learning/resilient-call.d.ts +116 -0
  110. package/dist/learning/resilient-call.d.ts.map +1 -1
  111. package/dist/learning/resilient-call.js +435 -15
  112. package/dist/learning/resilient-call.js.map +1 -1
  113. package/dist/nlu/conversation-gate.d.ts +22 -0
  114. package/dist/nlu/conversation-gate.d.ts.map +1 -1
  115. package/dist/nlu/conversation-gate.js +52 -8
  116. package/dist/nlu/conversation-gate.js.map +1 -1
  117. package/dist/nlu/intent-confirm.d.ts +82 -0
  118. package/dist/nlu/intent-confirm.d.ts.map +1 -0
  119. package/dist/nlu/intent-confirm.js +146 -0
  120. package/dist/nlu/intent-confirm.js.map +1 -0
  121. package/dist/nlu/intent.d.ts +35 -0
  122. package/dist/nlu/intent.d.ts.map +1 -1
  123. package/dist/nlu/intent.js +192 -3
  124. package/dist/nlu/intent.js.map +1 -1
  125. package/dist/nlu/learnings.d.ts +101 -0
  126. package/dist/nlu/learnings.d.ts.map +1 -0
  127. package/dist/nlu/learnings.js +283 -0
  128. package/dist/nlu/learnings.js.map +1 -0
  129. package/dist/nlu/schema.d.ts +2 -2
  130. package/dist/tools/ask-user.d.ts.map +1 -1
  131. package/dist/tools/ask-user.js +19 -3
  132. package/dist/tools/ask-user.js.map +1 -1
  133. package/dist/tools/coding-tools.d.ts.map +1 -1
  134. package/dist/tools/coding-tools.js +5 -0
  135. package/dist/tools/coding-tools.js.map +1 -1
  136. package/dist/tools/gateway-send.d.ts.map +1 -1
  137. package/dist/tools/gateway-send.js +14 -4
  138. package/dist/tools/gateway-send.js.map +1 -1
  139. package/dist/tools/loop-project-context.d.ts +3 -3
  140. package/dist/tools/loop-project-context.js +3 -3
  141. package/dist/tools/loop-skill-hint.d.ts +44 -7
  142. package/dist/tools/loop-skill-hint.d.ts.map +1 -1
  143. package/dist/tools/loop-skill-hint.js +117 -26
  144. package/dist/tools/loop-skill-hint.js.map +1 -1
  145. package/dist/tools/registry.d.ts +14 -0
  146. package/dist/tools/registry.d.ts.map +1 -1
  147. package/dist/tools/registry.js.map +1 -1
  148. package/dist/tools/tool-loop.d.ts +55 -2
  149. package/dist/tools/tool-loop.d.ts.map +1 -1
  150. package/dist/tools/tool-loop.js +161 -7
  151. package/dist/tools/tool-loop.js.map +1 -1
  152. package/dist/tools/toolsets.js +2 -2
  153. package/dist/tools/toolsets.js.map +1 -1
  154. package/dist/tools/unified-diff.d.ts +2 -2
  155. package/dist/tools/unified-diff.js +2 -2
  156. package/dist/web-dashboard/chat-console.d.ts +41 -0
  157. package/dist/web-dashboard/chat-console.d.ts.map +1 -1
  158. package/dist/web-dashboard/chat-console.js +133 -24
  159. package/dist/web-dashboard/chat-console.js.map +1 -1
  160. package/dist/web-dashboard/chat-retry.d.ts +143 -0
  161. package/dist/web-dashboard/chat-retry.d.ts.map +1 -0
  162. package/dist/web-dashboard/chat-retry.js +219 -0
  163. package/dist/web-dashboard/chat-retry.js.map +1 -0
  164. package/dist/web-dashboard/server.d.ts +21 -2
  165. package/dist/web-dashboard/server.d.ts.map +1 -1
  166. package/dist/web-dashboard/server.js +138 -2
  167. package/dist/web-dashboard/server.js.map +1 -1
  168. package/dist/web-dashboard/src/types.d.ts +6 -3
  169. package/dist/web-dashboard/src/types.d.ts.map +1 -1
  170. package/package.json +4 -3
  171. package/src/web-dashboard/public/assets/{index-Pzkg6L8v.js → index-Co7Hk2FT.js} +60 -60
  172. package/src/web-dashboard/public/assets/index-Co7Hk2FT.js.map +1 -0
  173. package/src/web-dashboard/public/index.html +1 -1
  174. package/dist/app.js +0 -26
  175. package/dist/auth-api.d.ts +0 -2
  176. package/dist/auth-api.d.ts.map +0 -1
  177. package/dist/auth-api.js +0 -61
  178. package/dist/auth-api.js.map +0 -1
  179. package/dist/auth.test.d.ts +0 -1
  180. package/dist/auth.test.d.ts.map +0 -1
  181. package/dist/auth.test.js +0 -3
  182. package/dist/auth.test.js.map +0 -1
  183. package/dist/config/auth.js +0 -18
  184. package/dist/config/jwt.js +0 -24
  185. package/dist/config/keys.js +0 -14
  186. package/dist/file.d.ts +0 -1
  187. package/dist/file.d.ts.map +0 -1
  188. package/dist/file.js +0 -2
  189. package/dist/file.js.map +0 -1
  190. package/dist/middleware/auth.js +0 -30
  191. package/dist/passport.js +0 -34
  192. package/dist/routes/auth.d.ts +0 -3
  193. package/dist/routes/auth.d.ts.map +0 -1
  194. package/dist/routes/auth.js +0 -47
  195. package/dist/routes/auth.js.map +0 -1
  196. package/dist/routes/user.js +0 -31
  197. package/dist/server.js +0 -17
  198. package/dist/web-dashboard/src/App.js +0 -85
  199. package/dist/web-dashboard/src/admin-auth.test.js +0 -186
  200. package/dist/web-dashboard/src/ansi.js +0 -23
  201. package/dist/web-dashboard/src/ansi.test.js +0 -31
  202. package/dist/web-dashboard/src/api-admin-auth.test.js +0 -172
  203. package/dist/web-dashboard/src/api-admin.test.js +0 -65
  204. package/dist/web-dashboard/src/api-hub.test.js +0 -117
  205. package/dist/web-dashboard/src/api.js +0 -1421
  206. package/dist/web-dashboard/src/api.test.js +0 -51
  207. package/dist/web-dashboard/src/artifacts.js +0 -128
  208. package/dist/web-dashboard/src/artifacts.test.js +0 -139
  209. package/dist/web-dashboard/src/components/AdminPanel.js +0 -567
  210. package/dist/web-dashboard/src/components/AdminPanel.test.js +0 -288
  211. package/dist/web-dashboard/src/components/AgentHub.js +0 -1580
  212. package/dist/web-dashboard/src/components/AgentHub.test.js +0 -343
  213. package/dist/web-dashboard/src/components/BedrockOnboarding.js +0 -320
  214. package/dist/web-dashboard/src/components/BenchmarkCharts.js +0 -228
  215. package/dist/web-dashboard/src/components/ChatPage.js +0 -1586
  216. package/dist/web-dashboard/src/components/ChatPage.test.js +0 -899
  217. package/dist/web-dashboard/src/components/ContactsPage.js +0 -141
  218. package/dist/web-dashboard/src/components/CostDashboard.js +0 -105
  219. package/dist/web-dashboard/src/components/DAGView.js +0 -477
  220. package/dist/web-dashboard/src/components/DAGView.test.js +0 -147
  221. package/dist/web-dashboard/src/components/EnvVarEditor.js +0 -138
  222. package/dist/web-dashboard/src/components/EvalsPage.js +0 -73
  223. package/dist/web-dashboard/src/components/EvalsPage.test.js +0 -120
  224. package/dist/web-dashboard/src/components/ExecutionHistory.js +0 -201
  225. package/dist/web-dashboard/src/components/GatewayPage.js +0 -40
  226. package/dist/web-dashboard/src/components/GatewayPage.test.js +0 -74
  227. package/dist/web-dashboard/src/components/HealthPanel.js +0 -68
  228. package/dist/web-dashboard/src/components/HistoryBrowser.js +0 -34
  229. package/dist/web-dashboard/src/components/Layout.js +0 -50
  230. package/dist/web-dashboard/src/components/Markdown.js +0 -152
  231. package/dist/web-dashboard/src/components/Markdown.test.js +0 -126
  232. package/dist/web-dashboard/src/components/MarkdownZeroDep.js +0 -237
  233. package/dist/web-dashboard/src/components/MemoryPanel.js +0 -81
  234. package/dist/web-dashboard/src/components/ModelTimeline.js +0 -298
  235. package/dist/web-dashboard/src/components/ModelsPanel.js +0 -1484
  236. package/dist/web-dashboard/src/components/ModelsPanel.test.js +0 -460
  237. package/dist/web-dashboard/src/components/Overview.js +0 -123
  238. package/dist/web-dashboard/src/components/PhaseTimeline.js +0 -359
  239. package/dist/web-dashboard/src/components/PhaseTimeline.test.js +0 -234
  240. package/dist/web-dashboard/src/components/PlatformConfigSection.js +0 -384
  241. package/dist/web-dashboard/src/components/PlatformConfigSection.test.js +0 -106
  242. package/dist/web-dashboard/src/components/PlatformsPage.js +0 -250
  243. package/dist/web-dashboard/src/components/QuotaPanel.js +0 -159
  244. package/dist/web-dashboard/src/components/QuotaPanel.test.js +0 -85
  245. package/dist/web-dashboard/src/components/RequestsPanel.js +0 -229
  246. package/dist/web-dashboard/src/components/RequestsPanel.test.js +0 -103
  247. package/dist/web-dashboard/src/components/RoutingInsightsPanel.js +0 -938
  248. package/dist/web-dashboard/src/components/RoutingInsightsPanel.test.js +0 -339
  249. package/dist/web-dashboard/src/components/RoutingWalkthrough.js +0 -408
  250. package/dist/web-dashboard/src/components/RoutingWalkthrough.test.js +0 -209
  251. package/dist/web-dashboard/src/components/TaskConsole.js +0 -119
  252. package/dist/web-dashboard/src/components/TaskConsole.test.js +0 -123
  253. package/dist/web-dashboard/src/components/TasksPage.js +0 -256
  254. package/dist/web-dashboard/src/components/TasksPage.test.js +0 -103
  255. package/dist/web-dashboard/src/components/TracePanel.js +0 -306
  256. package/dist/web-dashboard/src/components/WhatsAppPanel.js +0 -195
  257. package/dist/web-dashboard/src/components/WhatsAppPanel.test.js +0 -122
  258. package/dist/web-dashboard/src/jsonOrNull.js +0 -37
  259. package/dist/web-dashboard/src/main.js +0 -15
  260. package/dist/web-dashboard/src/mask.js +0 -11
  261. package/dist/web-dashboard/vite.config.js +0 -23
  262. package/dist/web-dashboard/vitest.config.js +0 -15
  263. 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
- * Adopts Codebuff's pattern: detect framework, language, and project state
35
- * before generating prompts. This replaces the generic context-gatherer
36
- * with a fast, deterministic assessment.
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 from Freebuff and Hermes:
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
- * - Freebuff: packages/agent-runtime/src/run-agent-step.ts (tool-calling loop)
13
- * - Hermes: run_agent.py AIAgent.run_conversation() (tool dispatch loop)
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 from Freebuff and Hermes:
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
- * - Freebuff: packages/agent-runtime/src/run-agent-step.ts (tool-calling loop)
13
- * - Hermes: run_agent.py AIAgent.run_conversation() (tool dispatch loop)
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.
@@ -32,18 +32,24 @@ export interface DispatchAssessmentOptions {
32
32
  text?: string;
33
33
  }
34
34
  /**
35
- * E3a/E3c — the rule assessment (hint + no-model fallback source).
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
- * tool-call turn and the MODEL decides what to do. This function computes
41
- * what the RULES would say, used for two things only:
42
- * - the rule hint injected into the model's context (buildToolSystemPrompt),
43
- * - the no-model fallback decision: when the tool loop fails to generate a
44
- * single response AND the rules assessed a high-confidence pipeline intent,
45
- * the pipeline runs directly — rules act ONLY when the model is unavailable,
46
- * never as a bypass.
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
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/cli/chat.ts"],"names":[],"mappings":"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"}
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 { looksLikeConfusedScaffoldingReply, toUserFacingGenerationError, isToolCallingUnsupported, stripToolCallArtifacts, } from '../inference/tool-call-utils.js';
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 (hint + no-model fallback source).
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
- * tool-call turn and the MODEL decides what to do. This function computes
157
- * what the RULES would say, used for two things only:
158
- * - the rule hint injected into the model's context (buildToolSystemPrompt),
159
- * - the no-model fallback decision: when the tool loop fails to generate a
160
- * single response AND the rules assessed a high-confidence pipeline intent,
161
- * the pipeline runs directly — rules act ONLY when the model is unavailable,
162
- * never as a bypass.
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
- if (r.error) {
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 a HINT in the model's context (buildToolSystemPrompt)
552
- // — the model decides what to do. Rules act
553
- // ONLY as the no-model fallback below (generation failed entirely), never
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
- // E3c: the rule assessment rides in as a hint when the rules parsed a
818
- // confident intent (model decides; hint only).
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 never throws by design; this guards future changes.
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
- tools: result.toolCalls,
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). Treat it like a generation
1240
- // failure: THROWS so the caller's failover walk retries with the next
1241
- // candidate; the raw reply is carried on the error for the final
1242
- // fallback.
1243
- const confuseCheck = (content) => {
1244
- if (looksLikeConfusedScaffoldingReply(content)) {
1245
- const err = new Error(`model answered with tool-contract confusion instead of the task (reply: ${content.slice(0, 160)})`);
1246
- err.confusedReply = content;
1247
- throw err;
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
- for (const fbType of chain) {
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
- return await tryGenerate(resolved.provider, resolved.type, session.model);
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 —