agent-nuvira 3.3.3 → 3.3.4

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 (349) hide show
  1. package/README.md +13 -5
  2. package/dist/agent-sdk/src/agent.d.ts +2 -0
  3. package/dist/agent-sdk/src/agent.d.ts.map +1 -1
  4. package/dist/agent-sdk/src/define.d.ts +64 -0
  5. package/dist/agent-sdk/src/define.d.ts.map +1 -0
  6. package/dist/agent-sdk/src/define.js +76 -0
  7. package/dist/agent-sdk/src/define.js.map +1 -0
  8. package/dist/agent-sdk/src/index.d.ts +9 -0
  9. package/dist/agent-sdk/src/index.d.ts.map +1 -1
  10. package/dist/agent-sdk/src/index.js +9 -0
  11. package/dist/agent-sdk/src/index.js.map +1 -1
  12. package/dist/agent-sdk/src/scaffold.d.ts +11 -0
  13. package/dist/agent-sdk/src/scaffold.d.ts.map +1 -1
  14. package/dist/agent-sdk/src/scaffold.js +16 -6
  15. package/dist/agent-sdk/src/scaffold.js.map +1 -1
  16. package/dist/agents/long-form-plan.d.ts.map +1 -1
  17. package/dist/agents/long-form-plan.js +2 -1
  18. package/dist/agents/long-form-plan.js.map +1 -1
  19. package/dist/agents/orchestrator.d.ts.map +1 -1
  20. package/dist/agents/orchestrator.js +5 -4
  21. package/dist/agents/orchestrator.js.map +1 -1
  22. package/dist/cli/agent.d.ts +2 -2
  23. package/dist/cli/agent.js +10 -10
  24. package/dist/cli/chat.d.ts +94 -0
  25. package/dist/cli/chat.d.ts.map +1 -1
  26. package/dist/cli/chat.js +354 -20
  27. package/dist/cli/chat.js.map +1 -1
  28. package/dist/cli/cli-program.d.ts.map +1 -1
  29. package/dist/cli/cli-program.js +5 -0
  30. package/dist/cli/cli-program.js.map +1 -1
  31. package/dist/cli/config.d.ts.map +1 -1
  32. package/dist/cli/config.js +9 -1
  33. package/dist/cli/config.js.map +1 -1
  34. package/dist/cli/doctor.d.ts.map +1 -1
  35. package/dist/cli/doctor.js +3 -2
  36. package/dist/cli/doctor.js.map +1 -1
  37. package/dist/cli/edit.js +2 -2
  38. package/dist/cli/eval.d.ts +17 -0
  39. package/dist/cli/eval.d.ts.map +1 -1
  40. package/dist/cli/eval.js +105 -2
  41. package/dist/cli/eval.js.map +1 -1
  42. package/dist/cli/execute.d.ts +16 -1
  43. package/dist/cli/execute.d.ts.map +1 -1
  44. package/dist/cli/execute.js +192 -22
  45. package/dist/cli/execute.js.map +1 -1
  46. package/dist/cli/loop-executor.d.ts +55 -0
  47. package/dist/cli/loop-executor.d.ts.map +1 -1
  48. package/dist/cli/loop-executor.js +212 -13
  49. package/dist/cli/loop-executor.js.map +1 -1
  50. package/dist/cli/model.d.ts.map +1 -1
  51. package/dist/cli/model.js +9 -8
  52. package/dist/cli/model.js.map +1 -1
  53. package/dist/cli/models.d.ts +1 -1
  54. package/dist/cli/models.js +4 -4
  55. package/dist/cli/parity.d.ts +85 -0
  56. package/dist/cli/parity.d.ts.map +1 -0
  57. package/dist/cli/parity.js +506 -0
  58. package/dist/cli/parity.js.map +1 -0
  59. package/dist/cli/plan.d.ts.map +1 -1
  60. package/dist/cli/plan.js +2 -1
  61. package/dist/cli/plan.js.map +1 -1
  62. package/dist/cli/retrieval.d.ts.map +1 -1
  63. package/dist/cli/retrieval.js +5 -4
  64. package/dist/cli/retrieval.js.map +1 -1
  65. package/dist/cli/sdk.js +4 -4
  66. package/dist/cli/sdk.js.map +1 -1
  67. package/dist/cli/trace.d.ts.map +1 -1
  68. package/dist/cli/trace.js +2 -1
  69. package/dist/cli/trace.js.map +1 -1
  70. package/dist/cli/workflow.js +2 -2
  71. package/dist/cli/workflow.js.map +1 -1
  72. package/dist/config/types.d.ts +44 -0
  73. package/dist/config/types.d.ts.map +1 -1
  74. package/dist/findings/verdicts.d.ts +241 -0
  75. package/dist/findings/verdicts.d.ts.map +1 -0
  76. package/dist/findings/verdicts.js +284 -0
  77. package/dist/findings/verdicts.js.map +1 -0
  78. package/dist/gateway/adapters.d.ts +65 -0
  79. package/dist/gateway/adapters.d.ts.map +1 -1
  80. package/dist/gateway/adapters.js +216 -10
  81. package/dist/gateway/adapters.js.map +1 -1
  82. package/dist/gateway/channel-directory.d.ts +31 -0
  83. package/dist/gateway/channel-directory.d.ts.map +1 -1
  84. package/dist/gateway/channel-directory.js +40 -0
  85. package/dist/gateway/channel-directory.js.map +1 -1
  86. package/dist/gateway/gateway-log.d.ts +1 -1
  87. package/dist/gateway/gateway-log.d.ts.map +1 -1
  88. package/dist/gateway/gateway-log.js.map +1 -1
  89. package/dist/gateway/hooks.d.ts +87 -19
  90. package/dist/gateway/hooks.d.ts.map +1 -1
  91. package/dist/gateway/hooks.js +62 -23
  92. package/dist/gateway/hooks.js.map +1 -1
  93. package/dist/gateway/inbound-media.d.ts +147 -0
  94. package/dist/gateway/inbound-media.d.ts.map +1 -0
  95. package/dist/gateway/inbound-media.js +317 -0
  96. package/dist/gateway/inbound-media.js.map +1 -0
  97. package/dist/gateway/inbox.d.ts +8 -1
  98. package/dist/gateway/inbox.d.ts.map +1 -1
  99. package/dist/gateway/inbox.js.map +1 -1
  100. package/dist/gateway/platform-config.d.ts +14 -0
  101. package/dist/gateway/platform-config.d.ts.map +1 -1
  102. package/dist/gateway/platform-config.js +26 -8
  103. package/dist/gateway/platform-config.js.map +1 -1
  104. package/dist/gateway/realtime.d.ts +114 -0
  105. package/dist/gateway/realtime.d.ts.map +1 -0
  106. package/dist/gateway/realtime.js +402 -0
  107. package/dist/gateway/realtime.js.map +1 -0
  108. package/dist/gateway/registry.d.ts +31 -0
  109. package/dist/gateway/registry.d.ts.map +1 -1
  110. package/dist/gateway/registry.js +224 -4
  111. package/dist/gateway/registry.js.map +1 -1
  112. package/dist/gateway/whatsapp/baileys-bridge.d.ts +11 -1
  113. package/dist/gateway/whatsapp/baileys-bridge.d.ts.map +1 -1
  114. package/dist/gateway/whatsapp/baileys-bridge.js +123 -4
  115. package/dist/gateway/whatsapp/baileys-bridge.js.map +1 -1
  116. package/dist/gateway/whatsapp/bridge.d.ts +6 -2
  117. package/dist/gateway/whatsapp/bridge.d.ts.map +1 -1
  118. package/dist/gateway/whatsapp/bridge.js.map +1 -1
  119. package/dist/index.js +8 -0
  120. package/dist/index.js.map +1 -1
  121. package/dist/inference/factory.d.ts +14 -0
  122. package/dist/inference/factory.d.ts.map +1 -1
  123. package/dist/inference/factory.js +17 -0
  124. package/dist/inference/factory.js.map +1 -1
  125. package/dist/inference/groq-adapter.d.ts +2 -0
  126. package/dist/inference/groq-adapter.d.ts.map +1 -1
  127. package/dist/inference/groq-adapter.js +16 -6
  128. package/dist/inference/groq-adapter.js.map +1 -1
  129. package/dist/inference/tools.d.ts.map +1 -1
  130. package/dist/inference/tools.js +29 -0
  131. package/dist/inference/tools.js.map +1 -1
  132. package/dist/learning/benchmark.d.ts.map +1 -1
  133. package/dist/learning/benchmark.js +3 -2
  134. package/dist/learning/benchmark.js.map +1 -1
  135. package/dist/learning/continuation.d.ts.map +1 -1
  136. package/dist/learning/continuation.js +2 -1
  137. package/dist/learning/continuation.js.map +1 -1
  138. package/dist/learning/cost-tracker.d.ts.map +1 -1
  139. package/dist/learning/cost-tracker.js +2 -1
  140. package/dist/learning/cost-tracker.js.map +1 -1
  141. package/dist/learning/deferred-task.d.ts.map +1 -1
  142. package/dist/learning/deferred-task.js +13 -4
  143. package/dist/learning/deferred-task.js.map +1 -1
  144. package/dist/learning/eval-framework.d.ts.map +1 -1
  145. package/dist/learning/eval-framework.js +2 -1
  146. package/dist/learning/eval-framework.js.map +1 -1
  147. package/dist/learning/long-form.d.ts.map +1 -1
  148. package/dist/learning/long-form.js +2 -1
  149. package/dist/learning/long-form.js.map +1 -1
  150. package/dist/learning/model-registry.d.ts.map +1 -1
  151. package/dist/learning/model-registry.js +2 -1
  152. package/dist/learning/model-registry.js.map +1 -1
  153. package/dist/learning/reasoning-cache.d.ts.map +1 -1
  154. package/dist/learning/reasoning-cache.js +2 -1
  155. package/dist/learning/reasoning-cache.js.map +1 -1
  156. package/dist/learning/reasoning-trace.d.ts +37 -1
  157. package/dist/learning/reasoning-trace.d.ts.map +1 -1
  158. package/dist/learning/reasoning-trace.js +66 -0
  159. package/dist/learning/reasoning-trace.js.map +1 -1
  160. package/dist/learning/resilient-call.d.ts.map +1 -1
  161. package/dist/learning/resilient-call.js +2 -1
  162. package/dist/learning/resilient-call.js.map +1 -1
  163. package/dist/learning/retrieval.d.ts.map +1 -1
  164. package/dist/learning/retrieval.js +2 -1
  165. package/dist/learning/retrieval.js.map +1 -1
  166. package/dist/learning/seeded-benchmark.d.ts +160 -0
  167. package/dist/learning/seeded-benchmark.d.ts.map +1 -0
  168. package/dist/learning/seeded-benchmark.js +321 -0
  169. package/dist/learning/seeded-benchmark.js.map +1 -0
  170. package/dist/learning/seeded-bugs.d.ts +142 -0
  171. package/dist/learning/seeded-bugs.d.ts.map +1 -0
  172. package/dist/learning/seeded-bugs.js +535 -0
  173. package/dist/learning/seeded-bugs.js.map +1 -0
  174. package/dist/learning/step-checkpoint.d.ts +127 -0
  175. package/dist/learning/step-checkpoint.d.ts.map +1 -0
  176. package/dist/learning/step-checkpoint.js +244 -0
  177. package/dist/learning/step-checkpoint.js.map +1 -0
  178. package/dist/nlu/intent-confirm.d.ts +23 -1
  179. package/dist/nlu/intent-confirm.d.ts.map +1 -1
  180. package/dist/nlu/intent-confirm.js +85 -2
  181. package/dist/nlu/intent-confirm.js.map +1 -1
  182. package/dist/nlu/learnings.d.ts +7 -0
  183. package/dist/nlu/learnings.d.ts.map +1 -1
  184. package/dist/nlu/learnings.js +7 -0
  185. package/dist/nlu/learnings.js.map +1 -1
  186. package/dist/observability/debug-log.d.ts +250 -0
  187. package/dist/observability/debug-log.d.ts.map +1 -0
  188. package/dist/observability/debug-log.js +500 -0
  189. package/dist/observability/debug-log.js.map +1 -0
  190. package/dist/observability/event-bus.d.ts.map +1 -1
  191. package/dist/observability/event-bus.js +4 -1
  192. package/dist/observability/event-bus.js.map +1 -1
  193. package/dist/observability/otel.d.ts +278 -0
  194. package/dist/observability/otel.d.ts.map +1 -0
  195. package/dist/observability/otel.js +590 -0
  196. package/dist/observability/otel.js.map +1 -0
  197. package/dist/parity/drivers.d.ts +99 -0
  198. package/dist/parity/drivers.d.ts.map +1 -0
  199. package/dist/parity/drivers.js +1362 -0
  200. package/dist/parity/drivers.js.map +1 -0
  201. package/dist/parity/graph.d.ts +73 -0
  202. package/dist/parity/graph.d.ts.map +1 -0
  203. package/dist/parity/graph.js +162 -0
  204. package/dist/parity/graph.js.map +1 -0
  205. package/dist/parity/matrix.d.ts +105 -0
  206. package/dist/parity/matrix.d.ts.map +1 -0
  207. package/dist/parity/matrix.js +352 -0
  208. package/dist/parity/matrix.js.map +1 -0
  209. package/dist/parity/observation.d.ts +444 -0
  210. package/dist/parity/observation.d.ts.map +1 -0
  211. package/dist/parity/observation.js +333 -0
  212. package/dist/parity/observation.js.map +1 -0
  213. package/dist/parity/scenarios.d.ts +229 -0
  214. package/dist/parity/scenarios.d.ts.map +1 -0
  215. package/dist/parity/scenarios.js +175 -0
  216. package/dist/parity/scenarios.js.map +1 -0
  217. package/dist/parity/surfaces.d.ts +122 -0
  218. package/dist/parity/surfaces.d.ts.map +1 -0
  219. package/dist/parity/surfaces.js +190 -0
  220. package/dist/parity/surfaces.js.map +1 -0
  221. package/dist/runtime/fault-injection.d.ts +173 -0
  222. package/dist/runtime/fault-injection.d.ts.map +1 -0
  223. package/dist/runtime/fault-injection.js +281 -0
  224. package/dist/runtime/fault-injection.js.map +1 -0
  225. package/dist/tools/child-agent-entry.d.ts +23 -0
  226. package/dist/tools/child-agent-entry.d.ts.map +1 -0
  227. package/dist/tools/child-agent-entry.js +129 -0
  228. package/dist/tools/child-agent-entry.js.map +1 -0
  229. package/dist/tools/child-agent-runtime.d.ts +124 -0
  230. package/dist/tools/child-agent-runtime.d.ts.map +1 -0
  231. package/dist/tools/child-agent-runtime.js +704 -0
  232. package/dist/tools/child-agent-runtime.js.map +1 -0
  233. package/dist/tools/coding-tools.d.ts.map +1 -1
  234. package/dist/tools/coding-tools.js +82 -10
  235. package/dist/tools/coding-tools.js.map +1 -1
  236. package/dist/tools/delegation-system.d.ts +31 -0
  237. package/dist/tools/delegation-system.d.ts.map +1 -1
  238. package/dist/tools/delegation-system.js +70 -9
  239. package/dist/tools/delegation-system.js.map +1 -1
  240. package/dist/tools/extract/docx.d.ts +27 -0
  241. package/dist/tools/extract/docx.d.ts.map +1 -0
  242. package/dist/tools/extract/docx.js +48 -0
  243. package/dist/tools/extract/docx.js.map +1 -0
  244. package/dist/tools/extract/html-text.d.ts +27 -0
  245. package/dist/tools/extract/html-text.d.ts.map +1 -0
  246. package/dist/tools/extract/html-text.js +86 -0
  247. package/dist/tools/extract/html-text.js.map +1 -0
  248. package/dist/tools/extract/pdf-ocr.d.ts +58 -0
  249. package/dist/tools/extract/pdf-ocr.d.ts.map +1 -0
  250. package/dist/tools/extract/pdf-ocr.js +116 -0
  251. package/dist/tools/extract/pdf-ocr.js.map +1 -0
  252. package/dist/tools/extract/pdf.d.ts +65 -0
  253. package/dist/tools/extract/pdf.d.ts.map +1 -0
  254. package/dist/tools/extract/pdf.js +197 -0
  255. package/dist/tools/extract/pdf.js.map +1 -0
  256. package/dist/tools/extract/pptx.d.ts +32 -0
  257. package/dist/tools/extract/pptx.d.ts.map +1 -0
  258. package/dist/tools/extract/pptx.js +77 -0
  259. package/dist/tools/extract/pptx.js.map +1 -0
  260. package/dist/tools/extract/xlsx.d.ts +47 -0
  261. package/dist/tools/extract/xlsx.d.ts.map +1 -0
  262. package/dist/tools/extract/xlsx.js +111 -0
  263. package/dist/tools/extract/xlsx.js.map +1 -0
  264. package/dist/tools/finding-tool.d.ts +76 -0
  265. package/dist/tools/finding-tool.d.ts.map +1 -0
  266. package/dist/tools/finding-tool.js +125 -0
  267. package/dist/tools/finding-tool.js.map +1 -0
  268. package/dist/tools/messaging-tools.d.ts +41 -11
  269. package/dist/tools/messaging-tools.d.ts.map +1 -1
  270. package/dist/tools/messaging-tools.js +104 -55
  271. package/dist/tools/messaging-tools.js.map +1 -1
  272. package/dist/tools/neutts-synth.d.ts +27 -3
  273. package/dist/tools/neutts-synth.d.ts.map +1 -1
  274. package/dist/tools/neutts-synth.js +57 -13
  275. package/dist/tools/neutts-synth.js.map +1 -1
  276. package/dist/tools/pipeline-tool.d.ts +43 -1
  277. package/dist/tools/pipeline-tool.d.ts.map +1 -1
  278. package/dist/tools/pipeline-tool.js +13 -2
  279. package/dist/tools/pipeline-tool.js.map +1 -1
  280. package/dist/tools/read-extract.d.ts +116 -45
  281. package/dist/tools/read-extract.d.ts.map +1 -1
  282. package/dist/tools/read-extract.js +494 -158
  283. package/dist/tools/read-extract.js.map +1 -1
  284. package/dist/tools/registry.d.ts +2 -2
  285. package/dist/tools/registry.d.ts.map +1 -1
  286. package/dist/tools/registry.js +152 -23
  287. package/dist/tools/registry.js.map +1 -1
  288. package/dist/tools/subagent-refusal.d.ts +15 -0
  289. package/dist/tools/subagent-refusal.d.ts.map +1 -0
  290. package/dist/tools/subagent-refusal.js +18 -0
  291. package/dist/tools/subagent-refusal.js.map +1 -0
  292. package/dist/tools/subagent-spawner.d.ts +122 -0
  293. package/dist/tools/subagent-spawner.d.ts.map +1 -1
  294. package/dist/tools/subagent-spawner.js +249 -28
  295. package/dist/tools/subagent-spawner.js.map +1 -1
  296. package/dist/tools/tool-hooks.d.ts +177 -0
  297. package/dist/tools/tool-hooks.d.ts.map +1 -0
  298. package/dist/tools/tool-hooks.js +427 -0
  299. package/dist/tools/tool-hooks.js.map +1 -0
  300. package/dist/tools/tool-loop.d.ts +82 -0
  301. package/dist/tools/tool-loop.d.ts.map +1 -1
  302. package/dist/tools/tool-loop.js +167 -9
  303. package/dist/tools/tool-loop.js.map +1 -1
  304. package/dist/tools/tool-refusal.d.ts +68 -0
  305. package/dist/tools/tool-refusal.d.ts.map +1 -0
  306. package/dist/tools/tool-refusal.js +78 -0
  307. package/dist/tools/tool-refusal.js.map +1 -0
  308. package/dist/tools/toolsets.d.ts +8 -0
  309. package/dist/tools/toolsets.d.ts.map +1 -1
  310. package/dist/tools/toolsets.js +12 -2
  311. package/dist/tools/toolsets.js.map +1 -1
  312. package/dist/tools/vision-tools.d.ts +88 -83
  313. package/dist/tools/vision-tools.d.ts.map +1 -1
  314. package/dist/tools/vision-tools.js +134 -103
  315. package/dist/tools/vision-tools.js.map +1 -1
  316. package/dist/tools/worktree.d.ts +210 -0
  317. package/dist/tools/worktree.d.ts.map +1 -0
  318. package/dist/tools/worktree.js +374 -0
  319. package/dist/tools/worktree.js.map +1 -0
  320. package/dist/utils/format.d.ts +3 -0
  321. package/dist/utils/format.d.ts.map +1 -0
  322. package/dist/utils/format.js +32 -0
  323. package/dist/utils/format.js.map +1 -0
  324. package/dist/web-dashboard/attachment-extract.d.ts +64 -0
  325. package/dist/web-dashboard/attachment-extract.d.ts.map +1 -0
  326. package/dist/web-dashboard/attachment-extract.js +154 -0
  327. package/dist/web-dashboard/attachment-extract.js.map +1 -0
  328. package/dist/web-dashboard/chat-console.d.ts +108 -1
  329. package/dist/web-dashboard/chat-console.d.ts.map +1 -1
  330. package/dist/web-dashboard/chat-console.js +36 -0
  331. package/dist/web-dashboard/chat-console.js.map +1 -1
  332. package/dist/web-dashboard/hub-data.d.ts +37 -0
  333. package/dist/web-dashboard/hub-data.d.ts.map +1 -1
  334. package/dist/web-dashboard/hub-data.js +61 -1
  335. package/dist/web-dashboard/hub-data.js.map +1 -1
  336. package/dist/web-dashboard/server.d.ts +14 -0
  337. package/dist/web-dashboard/server.d.ts.map +1 -1
  338. package/dist/web-dashboard/server.js +247 -13
  339. package/dist/web-dashboard/server.js.map +1 -1
  340. package/dist/web-dashboard/src/types.d.ts +196 -48
  341. package/dist/web-dashboard/src/types.d.ts.map +1 -1
  342. package/package.json +18 -3
  343. package/src/web-dashboard/public/assets/{index-Cyd6tIew.css → index-XJjj2cBX.css} +1 -1
  344. package/src/web-dashboard/public/assets/index-YWF9FpwQ.js +207 -0
  345. package/src/web-dashboard/public/assets/index-YWF9FpwQ.js.map +1 -0
  346. package/src/web-dashboard/public/index.html +2 -2
  347. package/dist/tools/child-agent-worker.js +0 -212
  348. package/src/web-dashboard/public/assets/index-CxDj7p6i.js +0 -207
  349. package/src/web-dashboard/public/assets/index-CxDj7p6i.js.map +0 -1
@@ -26,6 +26,7 @@ import { ConfigManager } from '../config/manager.js';
26
26
  import { ChannelDirectory, PLATFORM_ENV_VARS, PLATFORM_LABELS, } from './channel-directory.js';
27
27
  import { DeliveryLedger } from './delivery.js';
28
28
  import { InboxLedger } from './inbox.js';
29
+ import { inboundAttachmentPolicy } from './inbound-media.js';
29
30
  import { InboundDedupLedger } from './dedup.js';
30
31
  import { GatewayHeartbeat, HEARTBEAT_INTERVAL_MS } from './heartbeat.js';
31
32
  import { logGatewayEvent, previewText } from './gateway-log.js';
@@ -36,6 +37,11 @@ import { createResilientCallLLM, markFailoverAttempts, modelBreadthReport, rende
36
37
  // The intent audit: on a REPEATED failure, ask the model what the ask really
37
38
  // needs and feed a confirmed correction back into the NLU (see learnings.ts).
38
39
  import { confirmRoutedIntent, intentConfirmedNote, intentCorrectedNote, } from '../nlu/intent-confirm.js';
40
+ // The audit's verdict goes into the gateway's own durable record, in the SAME
41
+ // wire shape every other surface reports (WS1, `findings/verdicts.ts`): a
42
+ // messaging surface has no terminal to scroll, so "what we decided, and what it
43
+ // was checked against" has to be in the log or it does not exist.
44
+ import { toWire } from '../findings/verdicts.js';
39
45
  // The retry queue behind the "Reply *yes* and I will keep trying" offer. LEAF
40
46
  // import: a plain store + matcher, so the gateway does not pull routing in for it.
41
47
  import { abandonedLine, acceptedLine, cancelTasksFor, confirmTask, deferTask, dueTasks, expiredTasks, getPendingTask, isRetryAcceptance, isRetryDecline, removeDeferredTask, retryingLine, updateDeferredTask, } from '../learning/deferred-task.js';
@@ -57,6 +63,17 @@ const DELIVERY_DRAIN_INTERVAL_MS = 30_000;
57
63
  const RETRY_DRAIN_INTERVAL_MS = 15_000;
58
64
  /** Cap the per-target send-failure map (diagnostics only — never grows). */
59
65
  const MAX_TRACKED_SEND_ERRORS = 200;
66
+ /**
67
+ * How often the inbound artifact sandbox is swept (ms). Hourly: the sweep
68
+ * removes attachments past their TTL / over the size cap, and the 15s liveness
69
+ * tick that triggers it must not do directory work it does not need to.
70
+ */
71
+ const INBOUND_PRUNE_INTERVAL_MS = 60 * 60 * 1000;
72
+ /**
73
+ * Longest answer spoken as a voice note (chars). A voice-note question expects a
74
+ * spoken reply, not a synthesized essay — past this the answer goes out as text.
75
+ */
76
+ const VOICE_REPLY_MAX_CHARS = 700;
60
77
  // ─── Response cleanup ──────────────────────────────────────────────────────
61
78
  /**
62
79
  * Strip leaked internal reasoning, planning blocks, and meta-commentary from
@@ -527,6 +544,8 @@ export class GatewayRegistry {
527
544
  adapterHealth = new Map();
528
545
  /** Earliest epoch-ms at which a not-yet-started adapter may be retried. */
529
546
  adapterRetryAt = new Map();
547
+ /** Epoch ms of the last inbound-sandbox sweep (throttles the hourly prune). */
548
+ lastInboundPruneAt = 0;
530
549
  /** The heartbeat + watchdog tick. */
531
550
  livenessTimer = null;
532
551
  chatEngine;
@@ -634,6 +653,38 @@ export class GatewayRegistry {
634
653
  }
635
654
  return this.sendToRef(ref, text, target);
636
655
  }
656
+ /**
657
+ * Deliver an ANSWER, using a VOICE NOTE when the sender's own message was a
658
+ * voice note and a TTS backend is installed.
659
+ *
660
+ * Falls back to text whenever anything is missing — no TTS backend, an answer
661
+ * too long to speak sanely, or a platform that cannot send audio — so a voice
662
+ * reply is a courtesy and never a way for the answer to get lost. Returns true
663
+ * when a voice note was delivered, false when text was sent instead.
664
+ */
665
+ async replyInKind(ref, text, opts = {}) {
666
+ if (opts.voice && text.length > 0 && text.length <= VOICE_REPLY_MAX_CHARS) {
667
+ try {
668
+ const voice = await import('../tools/modality/voice.js');
669
+ if (voice.isTtsAvailable()) {
670
+ const spoken = await voice.speak(text);
671
+ if (spoken.ok && spoken.file) {
672
+ const { readFileSync } = await import('node:fs');
673
+ const data = new Uint8Array(readFileSync(spoken.file));
674
+ const ext = spoken.file.toLowerCase().endsWith('.wav') ? 'wav' : 'mp3';
675
+ const ok = await this.sendMediaToRef(ref, { type: 'audio', data, filename: `reply.${ext}` });
676
+ if (ok)
677
+ return true;
678
+ }
679
+ }
680
+ }
681
+ catch {
682
+ /* fall back to text */
683
+ }
684
+ }
685
+ await this.sendToRef(ref, text);
686
+ return false;
687
+ }
637
688
  /**
638
689
  * P3 — send media to a channel ref. Adapters that implement the optional
639
690
  * `sendMedia` (WhatsApp, Telegram, Discord) support it; others return false
@@ -871,6 +922,53 @@ export class GatewayRegistry {
871
922
  if (!this.isAllowed(ref)) {
872
923
  return refuse('This channel is not authorized to trigger the agent. Add it to BUFF_GATEWAY_ALLOW_IDS (platform:channelId).');
873
924
  }
925
+ // ── INBOUND DOCUMENT HYDRATION ── A document/image arrives as BYTES, not
926
+ // text. Turn it into text BEFORE routing, because every downstream reader
927
+ // (the dedup ledger, the chat store, and finally the model) reads
928
+ // `msg.text`: this runs the SAME `read_extract` the agent uses for a file in
929
+ // the project folder and prepends the extracted section to the caption.
930
+ // Without it a document message had empty text and was dropped by the
931
+ // bridge — the sender's own file vanished with no reply.
932
+ //
933
+ // Placed AFTER the authorization gates on purpose: an unapproved sender must
934
+ // leave no trace, so their bytes are never written to the artifact sandbox
935
+ // or extracted. `parsed` above already ran on the CAPTION only, so a long
936
+ // document never pollutes the NLU route; the extracted text joins the turn
937
+ // for the model.
938
+ //
939
+ // Consumed ONCE (media is cleared) so a retry or an audit re-route of the
940
+ // same message never re-downloads or duplicates the section.
941
+ let mediaFailureReply = null;
942
+ // A voice note gets a voice reply when one was sent (see replyInKind).
943
+ let voiceReplyWanted = false;
944
+ if (msg.media) {
945
+ const media = msg.media;
946
+ msg.media = undefined;
947
+ const attachmentPolicy = inboundAttachmentPolicy(this.configManager);
948
+ if (!attachmentPolicy.enabled) {
949
+ // Policy lets the MESSAGE through but not the attachment. Say so to the
950
+ // model rather than silently dropping it — or extracting it.
951
+ const label = media.filename?.trim() || media.type;
952
+ msg.text = [msg.text, `[Attachment: ${label} — inbound attachment handling is disabled by configuration]`]
953
+ .filter(Boolean)
954
+ .join('\n\n');
955
+ }
956
+ else {
957
+ voiceReplyWanted = media.type === 'audio';
958
+ try {
959
+ const { hydrateInboundMedia, formatMediaFailureReply } = await import('./inbound-media.js');
960
+ const hydration = await hydrateInboundMedia(media, { maxBytes: attachmentPolicy.maxBytes });
961
+ if (hydration.section)
962
+ msg.text = [msg.text, hydration.section].filter(Boolean).join('\n\n');
963
+ if (hydration.failure)
964
+ mediaFailureReply = formatMediaFailureReply(hydration.failure);
965
+ logger.info(`gateway: hydrated inbound ${media.type} (${media.data.byteLength} bytes) for ${msg.platform}:${msg.channelId}`);
966
+ }
967
+ catch {
968
+ /* Never break handling on a media failure — the text turn still runs. */
969
+ }
970
+ }
971
+ }
874
972
  // ── IDEMPOTENCY ── A messaging transport is at-least-once: the bridge
875
973
  // reconnects and replays its offline backfill, a webhook retries, a device
876
974
  // re-syncs. Observed live: ONE WhatsApp ask arrived 20+ times and was
@@ -898,6 +996,15 @@ export class GatewayRegistry {
898
996
  record('duplicate', undefined, { key: dedupVerdict.key, count: dedupVerdict.count });
899
997
  return 'duplicate';
900
998
  }
999
+ // A document that produced NO text cannot be answered by routing it to the
1000
+ // model — the model would only invent an answer about a file it never read.
1001
+ // Answer the sender directly, naming why, and stop. Placed AFTER dedup so a
1002
+ // re-delivered message (bridge backfill / webhook retry) never re-sends it.
1003
+ if (mediaFailureReply) {
1004
+ await replyTo(mediaFailureReply);
1005
+ record('attachment_failed', mediaFailureReply);
1006
+ return mediaFailureReply;
1007
+ }
901
1008
  // 7-DAY PER-CONTACT CONVERSATION MEMORY — record every AUTHORIZED inbound
902
1009
  // message the moment it arrives, BEFORE the routing decision (chat,
903
1010
  // pipeline, help). Placed AFTER the sender/channel gates: an unapproved
@@ -1000,7 +1107,9 @@ export class GatewayRegistry {
1000
1107
  const attemptMark = markFailoverAttempts();
1001
1108
  const answer = await this.runInboundChat(msg);
1002
1109
  if (answer && answer.content.trim() && !answer.generationFailed) {
1003
- await replyTo(answer.content);
1110
+ // Reply IN KIND: a spoken question gets a spoken answer when TTS is
1111
+ // available (falls back to text — see replyInKind).
1112
+ await this.replyInKind(ref, answer.content, { voice: voiceReplyWanted });
1004
1113
  record('chat', answer.content);
1005
1114
  // A retry that produced a real answer is DONE — the queue entry must go,
1006
1115
  // or the drain would keep re-running a fulfilled ask.
@@ -1068,7 +1177,10 @@ export class GatewayRegistry {
1068
1177
  const answer = await this.runInboundChat(msg);
1069
1178
  const answered = !!answer && answer.content.trim().length > 0 && !answer.generationFailed;
1070
1179
  const line = answered ? answer.content : this.generationFailureLine();
1071
- await replyTo(line);
1180
+ if (answered)
1181
+ await this.replyInKind(ref, line, { voice: voiceReplyWanted });
1182
+ else
1183
+ await replyTo(line);
1072
1184
  record('pipeline', line);
1073
1185
  if (answered && opts.retryOf)
1074
1186
  removeDeferredTask(opts.retryOf);
@@ -1241,6 +1353,10 @@ export class GatewayRegistry {
1241
1353
  routed,
1242
1354
  probed: !verdict.failed,
1243
1355
  reason: verdict.reason,
1356
+ // The verdict for THIS decision, with its evidence. A confirmed reading
1357
+ // now means the model justified it, so the log says which of the two it
1358
+ // was instead of leaving "probed: true" to imply it was checked.
1359
+ finding: toWire(verdict.finding),
1244
1360
  }, 'info');
1245
1361
  // The audit ran and confirmed the reading: say so, because "it failed" and
1246
1362
  // "it failed and I checked that I understood you" are different promises.
@@ -1254,6 +1370,11 @@ export class GatewayRegistry {
1254
1370
  to: verdict.kind,
1255
1371
  reason: verdict.reason,
1256
1372
  learningId: verdict.learning?.id,
1373
+ // Logged even when `learningId` is absent, and that is the case worth
1374
+ // recording: a correction the model could not justify is acted on but NOT
1375
+ // taught, so the log states the verdict (PLAUSIBLE) and its own outcome
1376
+ // rather than looking like a correction that simply failed to save.
1377
+ finding: toWire(verdict.finding),
1257
1378
  }, 'warn');
1258
1379
  const corrected = verdict.kind;
1259
1380
  const line = intentCorrectedNote(corrected, verdict.reason);
@@ -1364,6 +1485,23 @@ export class GatewayRegistry {
1364
1485
  return true;
1365
1486
  }
1366
1487
  }
1488
+ /**
1489
+ * One inbound chat turn, and the backend it was served by.
1490
+ *
1491
+ * The result carries `provider`/`model`/`transport` because this method is the
1492
+ * surface's boundary: the shared engine resolves all three, and keeping only
1493
+ * `content` + `generationFailed` is what made `run-attribution@gateway-chat`
1494
+ * the one capability cell the gateway could not prove. A messaging run is
1495
+ * also the hardest to inspect after the fact — there is no terminal to scroll
1496
+ * — so the same triple goes to the gateway log.
1497
+ *
1498
+ * It also carries the turn's per-call tool lifecycles (`toolCalls`), read from
1499
+ * the engine's `onToolCall` seam. Those are recorded, never sent to the
1500
+ * sender: a chat turn's `started`/`called` events are internal, exactly like
1501
+ * `onProgress`, but the OUTCOME of each call is a fact the surface should be
1502
+ * able to report — otherwise a gateway turn's tool work is unattributable
1503
+ * (`tool-call-lifecycle@gateway-chat`).
1504
+ */
1367
1505
  async runInboundChat(msg) {
1368
1506
  try {
1369
1507
  // Broadcast typing indicator start.
@@ -1437,10 +1575,44 @@ export class GatewayRegistry {
1437
1575
  const routingDefault = this.configManager.getAll().defaultProvider;
1438
1576
  const useAuto = !routingDefault || routingDefault === 'auto';
1439
1577
  const { type: providerType, config: providerConfig } = this.configManager.getProviderConfig();
1578
+ // The turn's tool-call lifecycle. Collected from the SHARED engine seam
1579
+ // (the same `called` phase the CLI and dashboard read) so the gateway
1580
+ // reports what the turn DID, not what the answer text claims. Outcome is
1581
+ // optional: a call whose outcome never arrived is recorded as absent, the
1582
+ // same honest reading the other surfaces use, never up-graded to success.
1583
+ const toolCalls = [];
1584
+ /**
1585
+ * WS1 — the findings this turn recorded, in call order.
1586
+ *
1587
+ * Read from the shared engine seam like `toolCalls` above, and written to
1588
+ * the `inbound.chat` log record for the same reason: a messaging surface
1589
+ * has no terminal to scroll, so the log is where a turn's verdicts have to
1590
+ * live (and it is what the parity driver reads).
1591
+ */
1592
+ const findings = [];
1440
1593
  const answer = await engine.answerOnce(prompt, {
1441
1594
  provider: useAuto ? 'auto' : providerType,
1442
1595
  model: useAuto ? 'auto' : providerConfig.model,
1443
1596
  history,
1597
+ // WS5 (#27) — isolation and resume are asked through the environment
1598
+ // (`NUVIRA_ISOLATE` / `NUVIRA_RESUME`), which is how a messaging gateway
1599
+ // is configured: an inbound WhatsApp message has no flags to carry, and
1600
+ // the operator decides once for the deployment. This is the code's own
1601
+ // record of that decision, on the log line that reports the turn.
1602
+ onToolCall: (phase, info) => {
1603
+ if (phase !== 'called')
1604
+ return;
1605
+ toolCalls.push({ tool: info.tool, ...(typeof info.ok === 'boolean' ? { ok: info.ok } : {}) });
1606
+ },
1607
+ // WS1 — every finding the turn recorded, with the gate's verdict. Kept
1608
+ // internal exactly like onToolCall: it belongs in the log and this
1609
+ // method's return, never in the channel sender's message.
1610
+ onFinding: (finding) => {
1611
+ findings.push(finding);
1612
+ },
1613
+ // WS2 (#24) — a messaging turn is the GATEWAY's; the session debug log's
1614
+ // header must name it that way rather than the shared engine's default.
1615
+ debugSurface: 'gateway-chat',
1444
1616
  // Session 3 — the channel rules go in the STABLE layer, not the ask.
1445
1617
  systemPolicy: CHANNEL_POLICY,
1446
1618
  // P5 — a replied followup carries the continuation marker into the
@@ -1493,7 +1665,9 @@ export class GatewayRegistry {
1493
1665
  // P3 note: the engine's onProgress is intentionally NOT wired here —
1494
1666
  // internal progress lines (routed-to, raw tool calls) must never leak
1495
1667
  // to the channel. Progress stays in the audit logs; the single
1496
- // "working" line + final answer is the whole conversation.
1668
+ // "working" line + final answer is the whole conversation. The
1669
+ // structured onToolCall lifecycle above is wired, but only for the log
1670
+ // and this method's return — it is never rendered to the sender.
1497
1671
  });
1498
1672
  // Only the FINAL answer reaches the sender, in natural language — plus
1499
1673
  // the model's suggested followups rendered as a readable numbered list
@@ -1584,7 +1758,36 @@ export class GatewayRegistry {
1584
1758
  this.chatStore.append(historyKey, null, content);
1585
1759
  this.onTypingCallback?.({ platform: msg.platform, channelId: msg.channelId, typing: false });
1586
1760
  this.writeTypingFile({ platform: msg.platform, channelId: msg.channelId, typing: false });
1587
- return { content, generationFailed: answer.generationFailed };
1761
+ // The run's own record, with the backend that served it. Best-effort by
1762
+ // the logger's own contract — an unwritable log must never lose a reply.
1763
+ logGatewayEvent('inbound.chat', {
1764
+ platform: msg.platform,
1765
+ channelId: msg.channelId,
1766
+ ...(answer.provider ? { provider: answer.provider } : {}),
1767
+ ...(answer.model ? { model: answer.model } : {}),
1768
+ ...(answer.transport ? { transport: answer.transport } : {}),
1769
+ ...(toolCalls.length > 0 ? { toolCalls } : {}),
1770
+ ...(findings.length > 0 ? { findings } : {}),
1771
+ // WS5 (#27) — the isolation this turn had, and what it changed against
1772
+ // its base commit. Durable, for the same reason the tool lifecycle and
1773
+ // the findings are: a messaging surface has no terminal, so the log IS
1774
+ // where an operator can see that an isolated turn ran and what it
1775
+ // touched.
1776
+ ...(answer.worktree ? { worktree: answer.worktree } : {}),
1777
+ ...(answer.resume ? { resume: answer.resume } : {}),
1778
+ generationFailed: answer.generationFailed === true,
1779
+ });
1780
+ return {
1781
+ content,
1782
+ generationFailed: answer.generationFailed,
1783
+ ...(answer.provider ? { provider: answer.provider } : {}),
1784
+ ...(answer.model ? { model: answer.model } : {}),
1785
+ ...(answer.transport ? { transport: answer.transport } : {}),
1786
+ ...(toolCalls.length > 0 ? { toolCalls } : {}),
1787
+ ...(findings.length > 0 ? { findings } : {}),
1788
+ ...(answer.worktree ? { worktree: answer.worktree } : {}),
1789
+ ...(answer.resume ? { resume: answer.resume } : {}),
1790
+ };
1588
1791
  }
1589
1792
  catch (err) {
1590
1793
  const message = err instanceof Error ? err.message : String(err);
@@ -1774,6 +1977,23 @@ export class GatewayRegistry {
1774
1977
  supervisorPid: process.ppid || undefined,
1775
1978
  adapters: [...this.adapterHealth.values()],
1776
1979
  });
1980
+ // Scheduled sandbox cleanup: extracted inbound attachments are kept for
1981
+ // their TTL, then swept — throttled to hourly so the 15s liveness tick
1982
+ // never walks the directory unnecessarily.
1983
+ const now = Date.now();
1984
+ if (now - this.lastInboundPruneAt >= INBOUND_PRUNE_INTERVAL_MS) {
1985
+ this.lastInboundPruneAt = now;
1986
+ try {
1987
+ const { pruneInboundMedia } = await import('./inbound-media.js');
1988
+ const pruned = pruneInboundMedia({ now });
1989
+ if (pruned.removed > 0) {
1990
+ logger.info(`gateway: pruned ${pruned.removed} inbound attachment(s) — ${Math.round(pruned.bytesFreed / 1024)} KB freed`);
1991
+ }
1992
+ }
1993
+ catch {
1994
+ /* best-effort sweep — never take the gateway down over cleanup */
1995
+ }
1996
+ }
1777
1997
  };
1778
1998
  // First beat immediately, so `gateway status` is honest within a second of
1779
1999
  // startup instead of reporting the previous run's stale beat.