@phuetz/code-buddy 1.6.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2485) hide show
  1. package/LICENSE +90 -21
  2. package/README.fr.md +56 -0
  3. package/README.md +183 -330
  4. package/codebuddy-runtime.json +24 -0
  5. package/dist/advanced/session-replay.js +3 -2
  6. package/dist/agent/agent-state.d.ts +4 -1
  7. package/dist/agent/agent-state.js +9 -3
  8. package/dist/agent/architect-mode.js +1 -1
  9. package/dist/agent/autonomous/agentic-coding-contract.d.ts +28 -28
  10. package/dist/agent/autonomous/agentic-coding-runner.d.ts +14 -4
  11. package/dist/agent/autonomous/agentic-coding-runner.js +176 -27
  12. package/dist/agent/autonomous/checkpoint-manager.js +3 -12
  13. package/dist/agent/autonomous/codex-autonomy-directive.d.ts +10 -0
  14. package/dist/agent/autonomous/codex-autonomy-directive.js +25 -0
  15. package/dist/agent/autonomous/edit-proposal-producer.js +2 -2
  16. package/dist/agent/autonomous/fleet-task-types.d.ts +2 -2
  17. package/dist/agent/autonomous/fleet-task-types.js +2 -2
  18. package/dist/agent/autonomous/fleet-tick-handler.d.ts +3 -3
  19. package/dist/agent/autonomous/fleet-tick-handler.js +14 -8
  20. package/dist/agent/autonomous/recursive-improvement.d.ts +23 -0
  21. package/dist/agent/autonomous/recursive-improvement.js +114 -0
  22. package/dist/agent/autonomous/verification-loop.js +4 -2
  23. package/dist/agent/codebuddy-agent.d.ts +111 -6
  24. package/dist/agent/codebuddy-agent.js +491 -60
  25. package/dist/agent/council-lesson-proposer.d.ts +80 -13
  26. package/dist/agent/council-lesson-proposer.js +201 -43
  27. package/dist/agent/deep-research-ckg.d.ts +176 -0
  28. package/dist/agent/deep-research-ckg.js +250 -0
  29. package/dist/agent/deep-research-storm.d.ts +222 -0
  30. package/dist/agent/deep-research-storm.js +689 -0
  31. package/dist/agent/deep-research.d.ts +329 -0
  32. package/dist/agent/deep-research.js +994 -0
  33. package/dist/agent/delegation/thread-delegation.d.ts +80 -0
  34. package/dist/agent/delegation/thread-delegation.js +472 -0
  35. package/dist/agent/delegation/thread-task-runner.d.ts +37 -0
  36. package/dist/agent/delegation/thread-task-runner.js +89 -0
  37. package/dist/agent/dev-loop/dev-loop.d.ts +120 -0
  38. package/dist/agent/dev-loop/dev-loop.js +404 -0
  39. package/dist/agent/dev-loop/structural-gate.d.ts +32 -0
  40. package/dist/agent/dev-loop/structural-gate.js +120 -0
  41. package/dist/agent/execution/agent-executor.d.ts +52 -40
  42. package/dist/agent/execution/agent-executor.js +1669 -543
  43. package/dist/agent/execution/context-pipeline.d.ts +63 -6
  44. package/dist/agent/execution/context-pipeline.js +347 -88
  45. package/dist/agent/execution/incremental-token-counter.d.ts +15 -0
  46. package/dist/agent/execution/incremental-token-counter.js +36 -0
  47. package/dist/agent/execution/ordered-tool-executor.d.ts +14 -0
  48. package/dist/agent/execution/ordered-tool-executor.js +54 -0
  49. package/dist/agent/execution/post-tool-handlers.d.ts +1 -1
  50. package/dist/agent/execution/post-tool-handlers.js +2 -2
  51. package/dist/agent/execution/query-classifier.d.ts +2 -0
  52. package/dist/agent/execution/query-classifier.js +2 -0
  53. package/dist/agent/execution/tool-dependency-graph.js +7 -0
  54. package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
  55. package/dist/agent/execution/tool-loop-guard.js +165 -0
  56. package/dist/agent/execution/tool-selection-strategy.d.ts +20 -0
  57. package/dist/agent/execution/tool-selection-strategy.js +162 -23
  58. package/dist/agent/facades/message-history-manager.d.ts +1 -1
  59. package/dist/agent/facades/message-history-manager.js +1 -1
  60. package/dist/agent/facades/model-routing-facade.d.ts +31 -1
  61. package/dist/agent/facades/model-routing-facade.js +86 -2
  62. package/dist/agent/facades/session-facade.d.ts +10 -8
  63. package/dist/agent/facades/session-facade.js +58 -45
  64. package/dist/agent/film/film-producer.d.ts +98 -0
  65. package/dist/agent/film/film-producer.js +289 -0
  66. package/dist/agent/film/scene-planner.d.ts +38 -0
  67. package/dist/agent/film/scene-planner.js +93 -0
  68. package/dist/agent/film/trailer-planner.d.ts +23 -0
  69. package/dist/agent/film/trailer-planner.js +242 -0
  70. package/dist/agent/film/video-studio.d.ts +72 -0
  71. package/dist/agent/film/video-studio.js +249 -0
  72. package/dist/agent/flow/planning-flow.d.ts +3 -0
  73. package/dist/agent/flow/planning-flow.js +70 -8
  74. package/dist/agent/hermes-claw-migrate.d.ts +62 -21
  75. package/dist/agent/hermes-claw-migrate.js +196 -17
  76. package/dist/agent/hermes-memory-providers.js +3 -3
  77. package/dist/agent/hermes-parity-manifest.js +69 -15
  78. package/dist/agent/hermes-protocol-gateways.js +8 -1
  79. package/dist/agent/infrastructure/agent-infrastructure.d.ts +1 -0
  80. package/dist/agent/infrastructure/agent-infrastructure.js +1 -1
  81. package/dist/agent/isolation/agent-workspace.js +5 -2
  82. package/dist/agent/learning/skill-background-writes.js +9 -4
  83. package/dist/agent/learning-agent.js +4 -4
  84. package/dist/agent/learning-background-writes.js +9 -5
  85. package/dist/agent/lesson-candidate-queue.d.ts +4 -0
  86. package/dist/agent/lesson-candidate-queue.js +9 -3
  87. package/dist/agent/lesson-provenance.js +9 -3
  88. package/dist/agent/lessons-tracker.d.ts +68 -1
  89. package/dist/agent/lessons-tracker.js +218 -36
  90. package/dist/agent/loop-detection-service.d.ts +56 -0
  91. package/dist/agent/loop-detection-service.js +207 -0
  92. package/dist/agent/middleware/auto-observation.d.ts +2 -0
  93. package/dist/agent/middleware/auto-observation.js +4 -2
  94. package/dist/agent/middleware/auto-repair-middleware.d.ts +2 -0
  95. package/dist/agent/middleware/auto-repair-middleware.js +27 -4
  96. package/dist/agent/middleware/changed-files.d.ts +19 -0
  97. package/dist/agent/middleware/changed-files.js +113 -0
  98. package/dist/agent/middleware/cost-limit.d.ts +0 -2
  99. package/dist/agent/middleware/cost-limit.js +0 -3
  100. package/dist/agent/middleware/index.d.ts +2 -0
  101. package/dist/agent/middleware/index.js +1 -0
  102. package/dist/agent/middleware/pipeline.d.ts +9 -0
  103. package/dist/agent/middleware/pipeline.js +18 -0
  104. package/dist/agent/middleware/plan-completion-audit.d.ts +76 -0
  105. package/dist/agent/middleware/plan-completion-audit.js +157 -0
  106. package/dist/agent/middleware/quality-gate-middleware.d.ts +43 -1
  107. package/dist/agent/middleware/quality-gate-middleware.js +198 -40
  108. package/dist/agent/middleware/types.d.ts +6 -0
  109. package/dist/agent/middleware/verification-enforcement.d.ts +10 -3
  110. package/dist/agent/middleware/verification-enforcement.js +43 -13
  111. package/dist/agent/middleware/workflow-guard.js +7 -0
  112. package/dist/agent/model-benchmark.js +11 -17
  113. package/dist/agent/model-tier.d.ts +2 -2
  114. package/dist/agent/model-tier.js +10 -7
  115. package/dist/agent/multi-agent/agent-memory-integration.js +3 -2
  116. package/dist/agent/multi-agent/base-agent.d.ts +12 -1
  117. package/dist/agent/multi-agent/base-agent.js +58 -10
  118. package/dist/agent/multi-agent/enhanced-coordination.js +1 -0
  119. package/dist/agent/multi-agent/metrics-persistence.js +28 -14
  120. package/dist/agent/multi-agent/multi-agent-system.d.ts +5 -0
  121. package/dist/agent/multi-agent/multi-agent-system.js +81 -4
  122. package/dist/agent/multi-agent/session-fleet-bridge.js +2 -1
  123. package/dist/agent/multi-agent/session-registry.js +9 -3
  124. package/dist/agent/multi-agent/team-manager.d.ts +11 -0
  125. package/dist/agent/multi-agent/team-manager.js +94 -5
  126. package/dist/agent/multi-agent/types.d.ts +10 -1
  127. package/dist/agent/multi-agent/workflow-multi-persistence.js +16 -8
  128. package/dist/agent/multi-agent/workflow-persistence.js +9 -6
  129. package/dist/agent/observer/trigger-registry.js +3 -5
  130. package/dist/agent/operating-modes.d.ts +3 -0
  131. package/dist/agent/operating-modes.js +15 -22
  132. package/dist/agent/pipelines.d.ts +2 -4
  133. package/dist/agent/pipelines.js +1 -1
  134. package/dist/agent/plan-mode.js +1 -1
  135. package/dist/agent/prompt-suggestions.js +2 -1
  136. package/dist/agent/prompt-tool-observation.d.ts +55 -0
  137. package/dist/agent/prompt-tool-observation.js +165 -0
  138. package/dist/agent/reasoning/tree-of-thought.js +1 -1
  139. package/dist/agent/repair/repair-engine.js +1 -1
  140. package/dist/agent/repo-profiler.d.ts +12 -1
  141. package/dist/agent/repo-profiler.js +37 -18
  142. package/dist/agent/repo-profiling/cache.js +6 -9
  143. package/dist/agent/research-worker-provider.d.ts +38 -0
  144. package/dist/agent/research-worker-provider.js +30 -0
  145. package/dist/agent/science/experiment-decision.d.ts +48 -0
  146. package/dist/agent/science/experiment-decision.js +65 -0
  147. package/dist/agent/science/experiment-empirical-gate.d.ts +83 -0
  148. package/dist/agent/science/experiment-empirical-gate.js +156 -0
  149. package/dist/agent/science/experiment-fitness.d.ts +112 -0
  150. package/dist/agent/science/experiment-fitness.js +188 -0
  151. package/dist/agent/science/experiment-loop.d.ts +196 -0
  152. package/dist/agent/science/experiment-loop.js +765 -0
  153. package/dist/agent/science/experiment-orchestrator.d.ts +203 -0
  154. package/dist/agent/science/experiment-orchestrator.js +454 -0
  155. package/dist/agent/science/experiment-sandbox-backends.d.ts +56 -0
  156. package/dist/agent/science/experiment-sandbox-backends.js +201 -0
  157. package/dist/agent/science/experiment-sandbox.d.ts +105 -0
  158. package/dist/agent/science/experiment-sandbox.js +196 -0
  159. package/dist/agent/science/experiment-variant-store.d.ts +108 -0
  160. package/dist/agent/science/experiment-variant-store.js +156 -0
  161. package/dist/agent/science/human-gate.d.ts +31 -0
  162. package/dist/agent/science/human-gate.js +32 -0
  163. package/dist/agent/self-improvement/authored-artifact-gate.js +28 -3
  164. package/dist/agent/self-improvement/authored-tool-runtime.d.ts +4 -2
  165. package/dist/agent/self-improvement/authored-tool-runtime.js +16 -3
  166. package/dist/agent/self-improvement/authored-tool-store.js +3 -2
  167. package/dist/agent/self-improvement/capability-benchmark.js +89 -2
  168. package/dist/agent/self-improvement/continuous-benchmark.d.ts +105 -0
  169. package/dist/agent/self-improvement/continuous-benchmark.js +355 -0
  170. package/dist/agent/self-improvement/delegation-facts.d.ts +105 -0
  171. package/dist/agent/self-improvement/delegation-facts.js +483 -0
  172. package/dist/agent/self-improvement/digest-sources.d.ts +56 -0
  173. package/dist/agent/self-improvement/digest-sources.js +294 -0
  174. package/dist/agent/self-improvement/digest.d.ts +169 -0
  175. package/dist/agent/self-improvement/digest.js +543 -0
  176. package/dist/agent/self-improvement/engine.d.ts +11 -6
  177. package/dist/agent/self-improvement/engine.js +13 -7
  178. package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +25 -0
  179. package/dist/agent/self-improvement/evolution/ast-novelty.js +116 -0
  180. package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +104 -0
  181. package/dist/agent/self-improvement/evolution/code-variant-store.js +229 -0
  182. package/dist/agent/self-improvement/evolution/evolution-engine.d.ts +199 -0
  183. package/dist/agent/self-improvement/evolution/evolution-engine.js +505 -0
  184. package/dist/agent/self-improvement/evolution/feature-map.d.ts +24 -0
  185. package/dist/agent/self-improvement/evolution/feature-map.js +88 -0
  186. package/dist/agent/self-improvement/evolution/model-bandit.d.ts +57 -0
  187. package/dist/agent/self-improvement/evolution/model-bandit.js +98 -0
  188. package/dist/agent/self-improvement/evolution/protected-paths.d.ts +31 -0
  189. package/dist/agent/self-improvement/evolution/protected-paths.js +111 -0
  190. package/dist/agent/self-improvement/evolution/research-weakness-source.d.ts +56 -0
  191. package/dist/agent/self-improvement/evolution/research-weakness-source.js +161 -0
  192. package/dist/agent/self-improvement/evolution/scrub-env.d.ts +22 -0
  193. package/dist/agent/self-improvement/evolution/scrub-env.js +40 -0
  194. package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +97 -0
  195. package/dist/agent/self-improvement/evolution/variant-fitness.js +343 -0
  196. package/dist/agent/self-improvement/evolution/variant-planner.d.ts +41 -0
  197. package/dist/agent/self-improvement/evolution/variant-planner.js +156 -0
  198. package/dist/agent/self-improvement/evolution/weakness-selector.d.ts +38 -0
  199. package/dist/agent/self-improvement/evolution/weakness-selector.js +108 -0
  200. package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +46 -0
  201. package/dist/agent/self-improvement/evolution/worktree-scorer.js +114 -0
  202. package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
  203. package/dist/agent/self-improvement/evolutionary-archive.js +37 -7
  204. package/dist/agent/self-improvement/experience-source.d.ts +60 -7
  205. package/dist/agent/self-improvement/experience-source.js +78 -8
  206. package/dist/agent/self-improvement/index.d.ts +1 -0
  207. package/dist/agent/self-improvement/index.js +6 -3
  208. package/dist/agent/self-improvement/learning-store.d.ts +16 -0
  209. package/dist/agent/self-improvement/learning-store.js +83 -3
  210. package/dist/agent/self-improvement/llm-drafter.d.ts +3 -1
  211. package/dist/agent/self-improvement/llm-drafter.js +20 -2
  212. package/dist/agent/self-improvement/llm-tool-proposer.js +1 -1
  213. package/dist/agent/self-improvement/proposal-store.d.ts +47 -0
  214. package/dist/agent/self-improvement/proposal-store.js +149 -0
  215. package/dist/agent/self-improvement/proposer.d.ts +1 -1
  216. package/dist/agent/self-improvement/proposer.js +94 -2
  217. package/dist/agent/self-improvement/rule-store.js +5 -4
  218. package/dist/agent/self-improvement/sandbox-scorer.d.ts +1 -1
  219. package/dist/agent/self-improvement/sandbox-scorer.js +8 -4
  220. package/dist/agent/self-improvement/self-knowledge.d.ts +1 -1
  221. package/dist/agent/self-improvement/self-knowledge.js +8 -8
  222. package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
  223. package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
  224. package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
  225. package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
  226. package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
  227. package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
  228. package/dist/agent/self-improvement/skill-benchmark.js +27 -0
  229. package/dist/agent/self-improvement/skill-consolidator.js +16 -0
  230. package/dist/agent/self-improvement/skill-engine.d.ts +29 -0
  231. package/dist/agent/self-improvement/skill-engine.js +428 -30
  232. package/dist/agent/self-improvement/skill-gate.d.ts +6 -4
  233. package/dist/agent/self-improvement/skill-gate.js +55 -22
  234. package/dist/agent/self-improvement/skill-mutator.d.ts +26 -2
  235. package/dist/agent/self-improvement/skill-mutator.js +185 -30
  236. package/dist/agent/self-improvement/skill-proposer.js +2 -1
  237. package/dist/agent/self-improvement/skill-types.d.ts +6 -1
  238. package/dist/agent/self-improvement/strategy-engine.d.ts +54 -0
  239. package/dist/agent/self-improvement/strategy-engine.js +104 -0
  240. package/dist/agent/self-improvement/strategy-gate.d.ts +49 -0
  241. package/dist/agent/self-improvement/strategy-gate.js +243 -0
  242. package/dist/agent/self-improvement/strategy-live.d.ts +39 -0
  243. package/dist/agent/self-improvement/strategy-live.js +114 -0
  244. package/dist/agent/self-improvement/strategy-proposer.d.ts +30 -0
  245. package/dist/agent/self-improvement/strategy-proposer.js +114 -0
  246. package/dist/agent/self-improvement/strategy-replay.d.ts +40 -0
  247. package/dist/agent/self-improvement/strategy-replay.js +150 -0
  248. package/dist/agent/self-improvement/strategy-runtime.d.ts +43 -0
  249. package/dist/agent/self-improvement/strategy-runtime.js +56 -0
  250. package/dist/agent/self-improvement/strategy-store.d.ts +38 -0
  251. package/dist/agent/self-improvement/strategy-store.js +139 -0
  252. package/dist/agent/self-improvement/strategy-types.d.ts +229 -0
  253. package/dist/agent/self-improvement/strategy-types.js +90 -0
  254. package/dist/agent/self-improvement/tool-benchmark.js +164 -8
  255. package/dist/agent/self-improvement/tool-engine.d.ts +12 -3
  256. package/dist/agent/self-improvement/tool-engine.js +59 -31
  257. package/dist/agent/self-improvement/tool-gate.d.ts +2 -0
  258. package/dist/agent/self-improvement/tool-gate.js +26 -0
  259. package/dist/agent/self-improvement/tool-skill-mutator.d.ts +1 -1
  260. package/dist/agent/self-improvement/tool-skill-mutator.js +20 -2
  261. package/dist/agent/self-improvement/tool-types.d.ts +5 -3
  262. package/dist/agent/self-improvement/types.d.ts +18 -3
  263. package/dist/agent/session-end-flush.js +2 -1
  264. package/dist/agent/specialized/agent-registry.d.ts +1 -0
  265. package/dist/agent/specialized/agent-registry.js +118 -0
  266. package/dist/agent/specialized/excel-agent.js +15 -1
  267. package/dist/agent/specialized/index.d.ts +1 -0
  268. package/dist/agent/specialized/index.js +2 -0
  269. package/dist/agent/specialized/pdf-agent.js +4 -2
  270. package/dist/agent/specialized/swe-agent-adapter.js +8 -0
  271. package/dist/agent/specialized/swe-agent.d.ts +16 -1
  272. package/dist/agent/specialized/swe-agent.js +75 -7
  273. package/dist/agent/specialized/types.d.ts +31 -1
  274. package/dist/agent/specialized/types.js +26 -0
  275. package/dist/agent/specialized/verifier-agent.d.ts +53 -0
  276. package/dist/agent/specialized/verifier-agent.js +342 -0
  277. package/dist/agent/streaming/message-reducer.js +73 -19
  278. package/dist/agent/streaming/streaming-handler.d.ts +24 -0
  279. package/dist/agent/streaming/streaming-handler.js +136 -1
  280. package/dist/agent/subagents.d.ts +10 -4
  281. package/dist/agent/subagents.js +39 -6
  282. package/dist/agent/thinking/extended-thinking.js +1 -1
  283. package/dist/agent/todo-tracker.js +5 -4
  284. package/dist/agent/tool-executor.d.ts +10 -1
  285. package/dist/agent/tool-executor.js +29 -0
  286. package/dist/agent/tool-handler.d.ts +50 -3
  287. package/dist/agent/tool-handler.js +758 -182
  288. package/dist/agent/types.d.ts +3 -5
  289. package/dist/agent/wide-research-checkpoint.d.ts +77 -0
  290. package/dist/agent/wide-research-checkpoint.js +361 -0
  291. package/dist/agent/wide-research-files.d.ts +11 -0
  292. package/dist/agent/wide-research-files.js +189 -0
  293. package/dist/agent/wide-research.d.ts +148 -3
  294. package/dist/agent/wide-research.js +833 -56
  295. package/dist/analytics/code-evolution.js +20 -15
  296. package/dist/analytics/complexity-analyzer.js +2 -2
  297. package/dist/analytics/cost-report.d.ts +71 -0
  298. package/dist/analytics/cost-report.js +724 -0
  299. package/dist/analytics/dashboard.js +8 -7
  300. package/dist/analytics/index.d.ts +2 -0
  301. package/dist/analytics/index.js +2 -0
  302. package/dist/analytics/repo-explainer-collector.d.ts +13 -0
  303. package/dist/analytics/repo-explainer-collector.js +351 -0
  304. package/dist/analytics/repo-explainer.d.ts +224 -0
  305. package/dist/analytics/repo-explainer.js +605 -0
  306. package/dist/analytics/roi-tracker.js +11 -9
  307. package/dist/api/webhooks.js +9 -14
  308. package/dist/app/application-factory.d.ts +1 -1
  309. package/dist/app/application-factory.js +6 -5
  310. package/dist/auth/profile-manager.js +9 -5
  311. package/dist/avatar/avatar-event-bus.d.ts +16 -0
  312. package/dist/avatar/avatar-event-bus.js +53 -0
  313. package/dist/avatar/avatar-gateway-bridge.d.ts +26 -0
  314. package/dist/avatar/avatar-gateway-bridge.js +54 -0
  315. package/dist/avatar/avatar-protocol.d.ts +133 -0
  316. package/dist/avatar/avatar-protocol.js +241 -0
  317. package/dist/avatar/avatar-renderer-registry.d.ts +56 -0
  318. package/dist/avatar/avatar-renderer-registry.js +191 -0
  319. package/dist/avatar/avatar-renderer-simulator.d.ts +31 -0
  320. package/dist/avatar/avatar-renderer-simulator.js +160 -0
  321. package/dist/browser-automation/browser-manager.d.ts +15 -1
  322. package/dist/browser-automation/browser-manager.js +101 -0
  323. package/dist/browser-automation/browser-operator-executor.d.ts +89 -7
  324. package/dist/browser-automation/browser-operator-executor.js +1280 -58
  325. package/dist/browser-automation/browser-operator-runtime.d.ts +87 -0
  326. package/dist/browser-automation/browser-operator-runtime.js +441 -0
  327. package/dist/browser-automation/browser-operator-session.js +4 -3
  328. package/dist/browser-automation/browser-tool.d.ts +17 -2
  329. package/dist/browser-automation/browser-tool.js +101 -39
  330. package/dist/browser-automation/internet-scout-plan.d.ts +1 -0
  331. package/dist/browser-automation/internet-scout-plan.js +17 -0
  332. package/dist/browser-automation/profile-manager.js +5 -3
  333. package/dist/browser-automation/types.d.ts +25 -0
  334. package/dist/cache/advanced-lru-cache.js +4 -4
  335. package/dist/cache/cache-manager.js +9 -10
  336. package/dist/cache/embedding-cache.js +6 -5
  337. package/dist/cache/llm-response-cache.js +3 -4
  338. package/dist/capture/screen-recorder.d.ts +2 -0
  339. package/dist/capture/screen-recorder.js +6 -4
  340. package/dist/capture/screen-watcher.d.ts +6 -3
  341. package/dist/capture/screen-watcher.js +86 -9
  342. package/dist/channels/channel-cognitive-port.d.ts +46 -0
  343. package/dist/channels/channel-cognitive-port.js +182 -0
  344. package/dist/channels/channel-model-override.d.ts +47 -0
  345. package/dist/channels/channel-model-override.js +62 -0
  346. package/dist/channels/companion-channel-profile.d.ts +68 -0
  347. package/dist/channels/companion-channel-profile.js +142 -0
  348. package/dist/channels/companion-channel-turn.d.ts +79 -0
  349. package/dist/channels/companion-channel-turn.js +240 -0
  350. package/dist/channels/core.d.ts +37 -3
  351. package/dist/channels/core.js +63 -6
  352. package/dist/channels/discord/client.js +34 -14
  353. package/dist/channels/dm-pairing.d.ts +18 -4
  354. package/dist/channels/dm-pairing.js +199 -33
  355. package/dist/channels/gateway-lifecycle.d.ts +2 -0
  356. package/dist/channels/gateway-lifecycle.js +1 -0
  357. package/dist/channels/identity-links.d.ts +19 -1
  358. package/dist/channels/identity-links.js +68 -12
  359. package/dist/channels/matrix/index.js +6 -2
  360. package/dist/channels/pro/pro-features.js +17 -0
  361. package/dist/channels/pro/run-tracker.js +9 -4
  362. package/dist/channels/pro/scoped-auth.js +10 -8
  363. package/dist/channels/provider-failure-speech.d.ts +28 -0
  364. package/dist/channels/provider-failure-speech.js +128 -0
  365. package/dist/channels/reconnection-manager.d.ts +2 -0
  366. package/dist/channels/reconnection-manager.js +7 -2
  367. package/dist/channels/resolve-channel-secret.d.ts +43 -0
  368. package/dist/channels/resolve-channel-secret.js +82 -0
  369. package/dist/channels/slack/block-builder.d.ts +12 -0
  370. package/dist/channels/slack/block-builder.js +70 -2
  371. package/dist/channels/slack/client.js +62 -15
  372. package/dist/channels/slack/types.d.ts +20 -1
  373. package/dist/channels/slash-parity.d.ts +4 -6
  374. package/dist/channels/slash-parity.js +9 -44
  375. package/dist/channels/telegram/client.d.ts +36 -3
  376. package/dist/channels/telegram/client.js +438 -84
  377. package/dist/channels/telegram/types.d.ts +2 -0
  378. package/dist/channels/webhook-server.js +8 -1
  379. package/dist/channels/whatsapp/index.js +2 -1
  380. package/dist/checkpoints/ghost-snapshot.js +15 -9
  381. package/dist/checkpoints/persistent-checkpoint-manager.js +15 -8
  382. package/dist/cli/command-routing.d.ts +22 -0
  383. package/dist/cli/command-routing.js +54 -0
  384. package/dist/cli/first-run.d.ts +17 -0
  385. package/dist/cli/first-run.js +40 -0
  386. package/dist/cli/headless-options.d.ts +22 -0
  387. package/dist/cli/headless-options.js +112 -0
  388. package/dist/cli/headless-prompt-progress.d.ts +22 -0
  389. package/dist/cli/headless-prompt-progress.js +35 -0
  390. package/dist/cli/headless.js +2 -2
  391. package/dist/cli/listen-port.d.ts +8 -0
  392. package/dist/cli/listen-port.js +20 -0
  393. package/dist/cli/permission-mode-option.d.ts +14 -0
  394. package/dist/cli/permission-mode-option.js +64 -0
  395. package/dist/cli/requested-profile.d.ts +15 -0
  396. package/dist/cli/requested-profile.js +27 -0
  397. package/dist/cli/session-commands.d.ts +9 -0
  398. package/dist/cli/session-commands.js +45 -5
  399. package/dist/cli/session-picker.d.ts +43 -0
  400. package/dist/cli/session-picker.js +128 -0
  401. package/dist/cli/unknown-option-hint.d.ts +16 -0
  402. package/dist/cli/unknown-option-hint.js +116 -0
  403. package/dist/cli/voice-command.d.ts +32 -0
  404. package/dist/cli/voice-command.js +96 -0
  405. package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
  406. package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
  407. package/dist/codebuddy/abort-signal.d.ts +7 -0
  408. package/dist/codebuddy/abort-signal.js +31 -0
  409. package/dist/codebuddy/client.d.ts +68 -0
  410. package/dist/codebuddy/client.js +460 -11
  411. package/dist/codebuddy/fleet-tool-defs.d.ts +1 -0
  412. package/dist/codebuddy/fleet-tool-defs.js +29 -0
  413. package/dist/codebuddy/llm-retry.d.ts +24 -0
  414. package/dist/codebuddy/llm-retry.js +124 -0
  415. package/dist/codebuddy/provider-error-classifier.d.ts +86 -0
  416. package/dist/codebuddy/provider-error-classifier.js +410 -0
  417. package/dist/codebuddy/provider-failover-error.d.ts +16 -0
  418. package/dist/codebuddy/provider-failover-error.js +50 -0
  419. package/dist/codebuddy/provider-failover-kind.d.ts +15 -0
  420. package/dist/codebuddy/provider-failover-kind.js +172 -0
  421. package/dist/codebuddy/provider-handoff.d.ts +35 -0
  422. package/dist/codebuddy/provider-handoff.js +294 -0
  423. package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
  424. package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
  425. package/dist/codebuddy/providers/ollama-native-transport.d.ts +130 -0
  426. package/dist/codebuddy/providers/ollama-native-transport.js +387 -0
  427. package/dist/codebuddy/providers/provider-agy-cli.d.ts +29 -0
  428. package/dist/codebuddy/providers/provider-agy-cli.js +264 -0
  429. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +61 -8
  430. package/dist/codebuddy/providers/provider-chatgpt-responses.js +526 -184
  431. package/dist/codebuddy/providers/provider-gemini-cli.js +95 -13
  432. package/dist/codebuddy/providers/provider-gemini-native.d.ts +2 -0
  433. package/dist/codebuddy/providers/provider-gemini-native.js +199 -86
  434. package/dist/codebuddy/providers/provider-openai-compat.d.ts +82 -0
  435. package/dist/codebuddy/providers/provider-openai-compat.js +469 -45
  436. package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
  437. package/dist/codebuddy/ragchat-tool-defs.js +19 -0
  438. package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
  439. package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
  440. package/dist/codebuddy/stream-retry.d.ts +21 -8
  441. package/dist/codebuddy/stream-retry.js +44 -40
  442. package/dist/codebuddy/tool-definitions/advanced-tools.d.ts +4 -0
  443. package/dist/codebuddy/tool-definitions/advanced-tools.js +188 -0
  444. package/dist/codebuddy/tool-definitions/agent-tools.d.ts +5 -0
  445. package/dist/codebuddy/tool-definitions/agent-tools.js +197 -9
  446. package/dist/codebuddy/tool-definitions/browser-tools.d.ts +1 -0
  447. package/dist/codebuddy/tool-definitions/browser-tools.js +65 -1
  448. package/dist/codebuddy/tool-definitions/code-exec-tools.d.ts +8 -0
  449. package/dist/codebuddy/tool-definitions/code-exec-tools.js +33 -0
  450. package/dist/codebuddy/tool-definitions/code-explorer-tools.js +4 -0
  451. package/dist/codebuddy/tool-definitions/comfy-recipe-tools.d.ts +3 -0
  452. package/dist/codebuddy/tool-definitions/comfy-recipe-tools.js +37 -0
  453. package/dist/codebuddy/tool-definitions/core-tools.d.ts +2 -0
  454. package/dist/codebuddy/tool-definitions/core-tools.js +58 -2
  455. package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
  456. package/dist/codebuddy/tool-definitions/delegate-agent-tools.d.ts +11 -0
  457. package/dist/codebuddy/tool-definitions/delegate-agent-tools.js +50 -0
  458. package/dist/codebuddy/tool-definitions/design-tools.d.ts +6 -0
  459. package/dist/codebuddy/tool-definitions/design-tools.js +35 -0
  460. package/dist/codebuddy/tool-definitions/exit-plan-mode-tools.d.ts +1 -0
  461. package/dist/codebuddy/tool-definitions/exit-plan-mode-tools.js +23 -1
  462. package/dist/codebuddy/tool-definitions/index.d.ts +16 -7
  463. package/dist/codebuddy/tool-definitions/index.js +27 -8
  464. package/dist/codebuddy/tool-definitions/lsp-tools.d.ts +7 -1
  465. package/dist/codebuddy/tool-definitions/lsp-tools.js +104 -1
  466. package/dist/codebuddy/tool-definitions/meeting-tools.d.ts +4 -0
  467. package/dist/codebuddy/tool-definitions/meeting-tools.js +32 -0
  468. package/dist/codebuddy/tool-definitions/moa-tools.js +7 -2
  469. package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +14 -0
  470. package/dist/codebuddy/tool-definitions/multimodal-tools.js +615 -3
  471. package/dist/codebuddy/tool-definitions/research-tools.d.ts +20 -0
  472. package/dist/codebuddy/tool-definitions/research-tools.js +98 -0
  473. package/dist/codebuddy/tool-definitions/self-describe-tools.d.ts +4 -0
  474. package/dist/codebuddy/tool-definitions/self-describe-tools.js +33 -0
  475. package/dist/codebuddy/tool-definitions/self-evolution-tools.d.ts +4 -0
  476. package/dist/codebuddy/tool-definitions/self-evolution-tools.js +31 -0
  477. package/dist/codebuddy/tool-definitions/verify-tools.d.ts +14 -0
  478. package/dist/codebuddy/tool-definitions/verify-tools.js +36 -0
  479. package/dist/codebuddy/tool-definitions/web-tools.d.ts +4 -0
  480. package/dist/codebuddy/tool-definitions/web-tools.js +135 -0
  481. package/dist/codebuddy/tool-definitions/workspace-tools.d.ts +4 -0
  482. package/dist/codebuddy/tool-definitions/workspace-tools.js +43 -0
  483. package/dist/codebuddy/tools.d.ts +15 -23
  484. package/dist/codebuddy/tools.js +259 -57
  485. package/dist/cognition/budget-reservations.d.ts +27 -0
  486. package/dist/cognition/budget-reservations.js +70 -0
  487. package/dist/cognition/cognitive-bus-client.d.ts +94 -0
  488. package/dist/cognition/cognitive-bus-client.js +724 -0
  489. package/dist/cognition/cognitive-hub.d.ts +93 -0
  490. package/dist/cognition/cognitive-hub.js +366 -0
  491. package/dist/cognition/cognitive-mesh.d.ts +38 -0
  492. package/dist/cognition/cognitive-mesh.js +263 -0
  493. package/dist/cognition/cognitive-port.d.ts +35 -0
  494. package/dist/cognition/cognitive-port.js +80 -0
  495. package/dist/cognition/cognitive-wire-contract.d.ts +1542 -0
  496. package/dist/cognition/cognitive-wire-contract.js +164 -0
  497. package/dist/cognition/context-renderer.d.ts +52 -0
  498. package/dist/cognition/context-renderer.js +227 -0
  499. package/dist/cognition/global-workspace.d.ts +39 -0
  500. package/dist/cognition/global-workspace.js +181 -0
  501. package/dist/cognition/index.d.ts +9 -0
  502. package/dist/cognition/index.js +10 -0
  503. package/dist/cognition/llm-specialist.d.ts +40 -0
  504. package/dist/cognition/llm-specialist.js +93 -0
  505. package/dist/cognition/sensory-workspace.d.ts +57 -0
  506. package/dist/cognition/sensory-workspace.js +346 -0
  507. package/dist/cognition/types.d.ts +93 -0
  508. package/dist/cognition/types.js +7 -0
  509. package/dist/cognition/voice-specialists.d.ts +21 -0
  510. package/dist/cognition/voice-specialists.js +89 -0
  511. package/dist/cognition/world-model.d.ts +95 -0
  512. package/dist/cognition/world-model.js +228 -0
  513. package/dist/collaboration/ai-colab-manager.js +12 -13
  514. package/dist/collaboration/team-session.js +25 -11
  515. package/dist/commands/apply-permission-mode.d.ts +27 -0
  516. package/dist/commands/apply-permission-mode.js +35 -0
  517. package/dist/commands/assistant.d.ts +3 -0
  518. package/dist/commands/assistant.js +1022 -0
  519. package/dist/commands/campaign.d.ts +6 -0
  520. package/dist/commands/campaign.js +140 -0
  521. package/dist/commands/capsule.d.ts +2 -0
  522. package/dist/commands/capsule.js +104 -0
  523. package/dist/commands/changelog.d.ts +29 -0
  524. package/dist/commands/changelog.js +283 -0
  525. package/dist/commands/cli/acp-command.js +4 -1
  526. package/dist/commands/cli/backup-command.d.ts +9 -0
  527. package/dist/commands/cli/backup-command.js +47 -0
  528. package/dist/commands/cli/daemon-commands.js +3 -1
  529. package/dist/commands/cli/evolve-command.d.ts +26 -0
  530. package/dist/commands/cli/evolve-command.js +275 -0
  531. package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
  532. package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
  533. package/dist/commands/cli/fleet-commands.js +134 -0
  534. package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
  535. package/dist/commands/cli/fleet-mission-commands.js +72 -0
  536. package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
  537. package/dist/commands/cli/fleet-rooms-commands.js +289 -0
  538. package/dist/commands/cli/improve-command.d.ts +2 -1
  539. package/dist/commands/cli/improve-command.js +309 -20
  540. package/dist/commands/cli/native-engine-commands.js +405 -28
  541. package/dist/commands/cli/sensory-command.d.ts +8 -0
  542. package/dist/commands/cli/sensory-command.js +27 -0
  543. package/dist/commands/cli/speak-command.js +87 -24
  544. package/dist/commands/cli/tools-commands.js +69 -36
  545. package/dist/commands/cli/triage-command.d.ts +8 -0
  546. package/dist/commands/cli/triage-command.js +72 -0
  547. package/dist/commands/cli/utility-commands.js +59 -10
  548. package/dist/commands/client-dispatcher.d.ts +8 -0
  549. package/dist/commands/client-dispatcher.js +113 -42
  550. package/dist/commands/compress.js +3 -0
  551. package/dist/commands/cost.d.ts +33 -0
  552. package/dist/commands/cost.js +342 -0
  553. package/dist/commands/council-scoreboard.d.ts +8 -0
  554. package/dist/commands/council-scoreboard.js +68 -0
  555. package/dist/commands/council.d.ts +16 -24
  556. package/dist/commands/council.js +247 -255
  557. package/dist/commands/cron-cli/index.d.ts +3 -1
  558. package/dist/commands/cron-cli/index.js +25 -0
  559. package/dist/commands/curator-cli.d.ts +14 -0
  560. package/dist/commands/curator-cli.js +57 -0
  561. package/dist/commands/delegate.js +82 -25
  562. package/dist/commands/dev/golden-path.d.ts +71 -0
  563. package/dist/commands/dev/golden-path.js +317 -0
  564. package/dist/commands/dev/index.d.ts +3 -0
  565. package/dist/commands/dev/index.js +151 -119
  566. package/dist/commands/dev/issue-pipeline.js +7 -7
  567. package/dist/commands/dev/workflows.d.ts +2 -0
  568. package/dist/commands/dev/workflows.js +39 -6
  569. package/dist/commands/device-auth.d.ts +10 -0
  570. package/dist/commands/device-auth.js +91 -0
  571. package/dist/commands/enhanced-command-handler.d.ts +12 -0
  572. package/dist/commands/enhanced-command-handler.js +57 -18
  573. package/dist/commands/exchange.d.ts +2 -0
  574. package/dist/commands/exchange.js +193 -0
  575. package/dist/commands/explain.d.ts +12 -0
  576. package/dist/commands/explain.js +135 -0
  577. package/dist/commands/film.d.ts +18 -0
  578. package/dist/commands/film.js +239 -0
  579. package/dist/commands/flow.js +15 -5
  580. package/dist/commands/forge.d.ts +2 -0
  581. package/dist/commands/forge.js +102 -0
  582. package/dist/commands/goal-cli.d.ts +3 -0
  583. package/dist/commands/goal-cli.js +9 -4
  584. package/dist/commands/gpu-worker.d.ts +14 -0
  585. package/dist/commands/gpu-worker.js +135 -0
  586. package/dist/commands/handlers/agent-handlers.d.ts +1 -1
  587. package/dist/commands/handlers/agent-handlers.js +4 -4
  588. package/dist/commands/handlers/agents-handler.d.ts +11 -2
  589. package/dist/commands/handlers/agents-handler.js +79 -13
  590. package/dist/commands/handlers/auth-handlers.js +12 -5
  591. package/dist/commands/handlers/backup-handlers.d.ts +40 -0
  592. package/dist/commands/handlers/backup-handlers.js +784 -57
  593. package/dist/commands/handlers/batch-handlers.d.ts +72 -2
  594. package/dist/commands/handlers/batch-handlers.js +443 -37
  595. package/dist/commands/handlers/branch-handlers.d.ts +1 -0
  596. package/dist/commands/handlers/channel-handlers.d.ts +7 -2
  597. package/dist/commands/handlers/channel-handlers.js +1716 -111
  598. package/dist/commands/handlers/companion-handler.js +130 -9
  599. package/dist/commands/handlers/daily-reset-handler.d.ts +1 -1
  600. package/dist/commands/handlers/daily-reset-handler.js +1 -1
  601. package/dist/commands/handlers/deepthink-handler.d.ts +2 -0
  602. package/dist/commands/handlers/deepthink-handler.js +49 -0
  603. package/dist/commands/handlers/export-handlers.d.ts +3 -2
  604. package/dist/commands/handlers/export-handlers.js +55 -28
  605. package/dist/commands/handlers/extra-handlers.js +28 -25
  606. package/dist/commands/handlers/fleet-handler.d.ts +3 -4
  607. package/dist/commands/handlers/fleet-handler.js +82 -35
  608. package/dist/commands/handlers/goal-handler.d.ts +13 -0
  609. package/dist/commands/handlers/goal-handler.js +26 -7
  610. package/dist/commands/handlers/grill-me-handler.d.ts +2 -0
  611. package/dist/commands/handlers/grill-me-handler.js +49 -0
  612. package/dist/commands/handlers/heartbeat-handler.d.ts +2 -2
  613. package/dist/commands/handlers/heartbeat-handler.js +2 -2
  614. package/dist/commands/handlers/index.d.ts +3 -2
  615. package/dist/commands/handlers/index.js +4 -2
  616. package/dist/commands/handlers/memory-handlers.d.ts +6 -2
  617. package/dist/commands/handlers/memory-handlers.js +75 -15
  618. package/dist/commands/handlers/missing-handlers.d.ts +2 -2
  619. package/dist/commands/handlers/missing-handlers.js +4 -4
  620. package/dist/commands/handlers/permissions-handlers.js +3 -2
  621. package/dist/commands/handlers/pr-handlers.js +15 -17
  622. package/dist/commands/handlers/resources-handler.d.ts +12 -0
  623. package/dist/commands/handlers/resources-handler.js +52 -0
  624. package/dist/commands/handlers/security-handlers.d.ts +1 -1
  625. package/dist/commands/handlers/security-handlers.js +3 -3
  626. package/dist/commands/handlers/slash-session-handlers.d.ts +9 -0
  627. package/dist/commands/handlers/slash-session-handlers.js +134 -0
  628. package/dist/commands/handlers/starter-handlers.d.ts +1 -1
  629. package/dist/commands/handlers/starter-handlers.js +32 -2
  630. package/dist/commands/handlers/swarm-handler.js +41 -2
  631. package/dist/commands/handlers/switch-handler.d.ts +1 -1
  632. package/dist/commands/handlers/switch-handler.js +15 -0
  633. package/dist/commands/handlers/team-handlers.d.ts +28 -1
  634. package/dist/commands/handlers/team-handlers.js +243 -2
  635. package/dist/commands/handlers/test-handlers.js +4 -86
  636. package/dist/commands/handlers/think-handlers.js +2 -1
  637. package/dist/commands/handlers/ui-handlers.js +11 -5
  638. package/dist/commands/handlers/vibe-handlers.js +8 -1
  639. package/dist/commands/handlers/worktree-handlers.js +23 -12
  640. package/dist/commands/headless-slash.d.ts +9 -0
  641. package/dist/commands/headless-slash.js +41 -3
  642. package/dist/commands/import.d.ts +34 -0
  643. package/dist/commands/import.js +478 -0
  644. package/dist/commands/index.d.ts +0 -1
  645. package/dist/commands/index.js +0 -1
  646. package/dist/commands/influencer.d.ts +2 -0
  647. package/dist/commands/influencer.js +87 -0
  648. package/dist/commands/intent.d.ts +6 -0
  649. package/dist/commands/intent.js +131 -0
  650. package/dist/commands/intents.d.ts +10 -0
  651. package/dist/commands/intents.js +163 -0
  652. package/dist/commands/lessons.js +91 -0
  653. package/dist/commands/llm-provider-resolution.d.ts +20 -0
  654. package/dist/commands/llm-provider-resolution.js +74 -13
  655. package/dist/commands/login-chatgpt.d.ts +8 -0
  656. package/dist/commands/login-chatgpt.js +17 -0
  657. package/dist/commands/login-prerequisites.d.ts +11 -0
  658. package/dist/commands/login-prerequisites.js +23 -0
  659. package/dist/commands/loop-cli.d.ts +31 -0
  660. package/dist/commands/loop-cli.js +189 -0
  661. package/dist/commands/lora.d.ts +6 -0
  662. package/dist/commands/lora.js +445 -0
  663. package/dist/commands/maison-food.d.ts +10 -0
  664. package/dist/commands/maison-food.js +485 -0
  665. package/dist/commands/maison.d.ts +17 -0
  666. package/dist/commands/maison.js +236 -0
  667. package/dist/commands/mcp-import.d.ts +11 -0
  668. package/dist/commands/mcp-import.js +67 -0
  669. package/dist/commands/mcp-invoke.d.ts +2 -0
  670. package/dist/commands/mcp-invoke.js +68 -0
  671. package/dist/commands/mcp.d.ts +6 -0
  672. package/dist/commands/mcp.js +240 -7
  673. package/dist/commands/meeting.d.ts +8 -0
  674. package/dist/commands/meeting.js +31 -0
  675. package/dist/commands/pairing.js +7 -8
  676. package/dist/commands/papers/ask.d.ts +47 -0
  677. package/dist/commands/papers/ask.js +105 -0
  678. package/dist/commands/papers/index.d.ts +19 -0
  679. package/dist/commands/papers/index.js +54 -0
  680. package/dist/commands/pipeline.d.ts +7 -1
  681. package/dist/commands/pipeline.js +104 -7
  682. package/dist/commands/provider.d.ts +9 -0
  683. package/dist/commands/provider.js +178 -10
  684. package/dist/commands/replay.d.ts +11 -0
  685. package/dist/commands/replay.js +131 -0
  686. package/dist/commands/research/deep.d.ts +66 -0
  687. package/dist/commands/research/deep.js +236 -0
  688. package/dist/commands/research/discover-searxng.d.ts +25 -0
  689. package/dist/commands/research/discover-searxng.js +62 -0
  690. package/dist/commands/research/index.d.ts +24 -2
  691. package/dist/commands/research/index.js +456 -79
  692. package/dist/commands/research/knowledge-ingest.d.ts +170 -0
  693. package/dist/commands/research/knowledge-ingest.js +449 -0
  694. package/dist/commands/research/wire-research-worker.d.ts +9 -0
  695. package/dist/commands/research/wire-research-worker.js +18 -0
  696. package/dist/commands/resources.d.ts +2 -0
  697. package/dist/commands/resources.js +64 -0
  698. package/dist/commands/run-cli/index.d.ts +3 -1
  699. package/dist/commands/run-cli/index.js +53 -2
  700. package/dist/commands/science/deps.d.ts +95 -0
  701. package/dist/commands/science/deps.js +343 -0
  702. package/dist/commands/science/index.d.ts +31 -0
  703. package/dist/commands/science/index.js +285 -0
  704. package/dist/commands/science/loop-option.d.ts +40 -0
  705. package/dist/commands/science/loop-option.js +91 -0
  706. package/dist/commands/science/sandbox-option.d.ts +44 -0
  707. package/dist/commands/science/sandbox-option.js +42 -0
  708. package/dist/commands/scrape.d.ts +18 -0
  709. package/dist/commands/scrape.js +159 -0
  710. package/dist/commands/self.d.ts +9 -0
  711. package/dist/commands/self.js +56 -0
  712. package/dist/commands/shadow.d.ts +3 -0
  713. package/dist/commands/shadow.js +72 -0
  714. package/dist/commands/share.d.ts +13 -0
  715. package/dist/commands/share.js +71 -0
  716. package/dist/commands/shell-prefix.d.ts +4 -22
  717. package/dist/commands/shell-prefix.js +11 -73
  718. package/dist/commands/skills-cli/index.d.ts +2 -0
  719. package/dist/commands/skills-cli/index.js +224 -26
  720. package/dist/commands/slash/builtin-commands.js +57 -24
  721. package/dist/commands/slash/index.d.ts +3 -1
  722. package/dist/commands/slash/index.js +2 -0
  723. package/dist/commands/slash/surfaces.d.ts +53 -0
  724. package/dist/commands/slash/surfaces.js +177 -0
  725. package/dist/commands/slash/types.d.ts +17 -0
  726. package/dist/commands/slash-commands.js +4 -1
  727. package/dist/commands/token.d.ts +80 -0
  728. package/dist/commands/token.js +319 -0
  729. package/dist/commands/try.d.ts +72 -0
  730. package/dist/commands/try.js +357 -0
  731. package/dist/commands/update.d.ts +14 -1
  732. package/dist/commands/update.js +97 -15
  733. package/dist/commands/vision-train.d.ts +15 -0
  734. package/dist/commands/vision-train.js +214 -0
  735. package/dist/commands/whoami-status.d.ts +17 -0
  736. package/dist/commands/whoami-status.js +34 -0
  737. package/dist/commands/widgets.d.ts +2 -0
  738. package/dist/commands/widgets.js +179 -0
  739. package/dist/commands/ws.d.ts +7 -0
  740. package/dist/commands/ws.js +100 -0
  741. package/dist/companion/assistant-config.d.ts +79 -0
  742. package/dist/companion/assistant-config.js +1148 -0
  743. package/dist/companion/attached-image-grounding.d.ts +29 -0
  744. package/dist/companion/attached-image-grounding.js +285 -0
  745. package/dist/companion/away-mode.d.ts +80 -0
  746. package/dist/companion/away-mode.js +269 -0
  747. package/dist/companion/camera-share.d.ts +65 -0
  748. package/dist/companion/camera-share.js +321 -0
  749. package/dist/companion/camera.d.ts +8 -0
  750. package/dist/companion/camera.js +57 -31
  751. package/dist/companion/cards.js +7 -10
  752. package/dist/companion/check-in.js +23 -8
  753. package/dist/companion/companion-doctor.d.ts +58 -0
  754. package/dist/companion/companion-doctor.js +194 -0
  755. package/dist/companion/companion-history.d.ts +23 -0
  756. package/dist/companion/companion-history.js +22 -0
  757. package/dist/companion/companion-identity.d.ts +43 -0
  758. package/dist/companion/companion-identity.js +136 -0
  759. package/dist/companion/companion-mode.d.ts +77 -1
  760. package/dist/companion/companion-mode.js +916 -6
  761. package/dist/companion/companion-photo-intake.d.ts +40 -0
  762. package/dist/companion/companion-photo-intake.js +112 -0
  763. package/dist/companion/companion-photo.d.ts +144 -0
  764. package/dist/companion/companion-photo.js +318 -0
  765. package/dist/companion/companion-toolset.d.ts +66 -0
  766. package/dist/companion/companion-toolset.js +368 -0
  767. package/dist/companion/companion-turn.d.ts +96 -0
  768. package/dist/companion/companion-turn.js +260 -0
  769. package/dist/companion/companion-voice-character.d.ts +62 -0
  770. package/dist/companion/companion-voice-character.js +148 -0
  771. package/dist/companion/competitive-radar.js +14 -13
  772. package/dist/companion/continuity.d.ts +62 -0
  773. package/dist/companion/continuity.js +284 -0
  774. package/dist/companion/conversation-improvement-loop.d.ts +61 -0
  775. package/dist/companion/conversation-improvement-loop.js +317 -0
  776. package/dist/companion/conversation-quality-insights.d.ts +56 -0
  777. package/dist/companion/conversation-quality-insights.js +161 -0
  778. package/dist/companion/cooking-timer-runner.d.ts +20 -0
  779. package/dist/companion/cooking-timer-runner.js +98 -0
  780. package/dist/companion/core-adapter.d.ts +60 -0
  781. package/dist/companion/core-adapter.js +111 -0
  782. package/dist/companion/crisis-safety.d.ts +52 -0
  783. package/dist/companion/crisis-safety.js +150 -0
  784. package/dist/companion/daily-interaction-budget.d.ts +34 -0
  785. package/dist/companion/daily-interaction-budget.js +260 -0
  786. package/dist/companion/dialogue-percepts.d.ts +16 -0
  787. package/dist/companion/dialogue-percepts.js +50 -0
  788. package/dist/companion/event-followups.d.ts +58 -0
  789. package/dist/companion/event-followups.js +213 -0
  790. package/dist/companion/fashion-scene-catalog.d.ts +29 -0
  791. package/dist/companion/fashion-scene-catalog.js +141 -0
  792. package/dist/companion/gateway-inbox.js +25 -13
  793. package/dist/companion/gateway.d.ts +1 -0
  794. package/dist/companion/gateway.js +37 -17
  795. package/dist/companion/home-interaction-policy.d.ts +31 -0
  796. package/dist/companion/home-interaction-policy.js +89 -0
  797. package/dist/companion/household-time.d.ts +9 -0
  798. package/dist/companion/household-time.js +15 -0
  799. package/dist/companion/idle-loop.d.ts +53 -0
  800. package/dist/companion/idle-loop.js +254 -0
  801. package/dist/companion/impulses.js +148 -23
  802. package/dist/companion/inner-life.d.ts +60 -0
  803. package/dist/companion/inner-life.js +112 -0
  804. package/dist/companion/jokes.d.ts +21 -0
  805. package/dist/companion/jokes.js +175 -0
  806. package/dist/companion/lisa-selfie-cache.d.ts +36 -0
  807. package/dist/companion/lisa-selfie-cache.js +123 -0
  808. package/dist/companion/lisa-selfie-ingest.d.ts +36 -0
  809. package/dist/companion/lisa-selfie-ingest.js +205 -0
  810. package/dist/companion/lisa-selfie-refill.d.ts +32 -0
  811. package/dist/companion/lisa-selfie-refill.js +121 -0
  812. package/dist/companion/lisa-selfie-router.d.ts +40 -0
  813. package/dist/companion/lisa-selfie-router.js +156 -0
  814. package/dist/companion/lisa-selfie.d.ts +97 -0
  815. package/dist/companion/lisa-selfie.js +542 -0
  816. package/dist/companion/maison-voice-actions.d.ts +34 -0
  817. package/dist/companion/maison-voice-actions.js +192 -0
  818. package/dist/companion/migration.d.ts +63 -0
  819. package/dist/companion/migration.js +270 -0
  820. package/dist/companion/mission-board.js +7 -10
  821. package/dist/companion/mission-runner.js +2 -3
  822. package/dist/companion/mobile-conversation-log.d.ts +19 -0
  823. package/dist/companion/mobile-conversation-log.js +157 -0
  824. package/dist/companion/mobile-history.d.ts +34 -0
  825. package/dist/companion/mobile-history.js +123 -0
  826. package/dist/companion/mysoulmate-image-prompts.d.ts +26 -0
  827. package/dist/companion/mysoulmate-image-prompts.js +278 -0
  828. package/dist/companion/orchestrator.d.ts +32 -0
  829. package/dist/companion/orchestrator.js +49 -0
  830. package/dist/companion/percepts.d.ts +70 -0
  831. package/dist/companion/percepts.js +251 -18
  832. package/dist/companion/personas/copine.d.ts +8 -0
  833. package/dist/companion/personas/copine.js +195 -0
  834. package/dist/companion/personas/index.d.ts +19 -0
  835. package/dist/companion/personas/index.js +33 -0
  836. package/dist/companion/personas/types.d.ts +34 -0
  837. package/dist/companion/personas/types.js +8 -0
  838. package/dist/companion/photo-memory-fr.d.ts +36 -0
  839. package/dist/companion/photo-memory-fr.js +291 -0
  840. package/dist/companion/prefetch-config.d.ts +38 -0
  841. package/dist/companion/prefetch-config.js +119 -0
  842. package/dist/companion/prefetch-engine.d.ts +87 -0
  843. package/dist/companion/prefetch-engine.js +513 -0
  844. package/dist/companion/presence-loop.d.ts +85 -0
  845. package/dist/companion/presence-loop.js +297 -0
  846. package/dist/companion/privacy.js +3 -2
  847. package/dist/companion/proactive-engine.d.ts +97 -0
  848. package/dist/companion/proactive-engine.js +447 -0
  849. package/dist/companion/recent-said.d.ts +30 -0
  850. package/dist/companion/recent-said.js +98 -0
  851. package/dist/companion/relational-benchmark-scenarios.d.ts +191 -0
  852. package/dist/companion/relational-benchmark-scenarios.js +227 -0
  853. package/dist/companion/relational-context.d.ts +71 -0
  854. package/dist/companion/relational-context.js +302 -0
  855. package/dist/companion/relational-episode-evaluator.d.ts +117 -0
  856. package/dist/companion/relational-episode-evaluator.js +453 -0
  857. package/dist/companion/relationship-evolution.d.ts +2 -0
  858. package/dist/companion/relationship-evolution.js +30 -0
  859. package/dist/companion/relationship-state.d.ts +106 -0
  860. package/dist/companion/relationship-state.js +293 -0
  861. package/dist/companion/reminder-runner.d.ts +33 -0
  862. package/dist/companion/reminder-runner.js +183 -0
  863. package/dist/companion/reminders.d.ts +228 -0
  864. package/dist/companion/reminders.js +906 -0
  865. package/dist/companion/reply-augment.d.ts +87 -0
  866. package/dist/companion/reply-augment.js +464 -0
  867. package/dist/companion/safety-ledger.js +19 -12
  868. package/dist/companion/self-evaluation.js +22 -23
  869. package/dist/companion/shared-photo-memory.d.ts +65 -0
  870. package/dist/companion/shared-photo-memory.js +145 -0
  871. package/dist/companion/shared-photos.d.ts +88 -0
  872. package/dist/companion/shared-photos.js +321 -0
  873. package/dist/companion/signature-locations.d.ts +16 -0
  874. package/dist/companion/signature-locations.js +108 -0
  875. package/dist/companion/skill-curator.d.ts +9 -0
  876. package/dist/companion/skill-curator.js +147 -58
  877. package/dist/companion/untrusted-text.d.ts +22 -0
  878. package/dist/companion/untrusted-text.js +47 -0
  879. package/dist/companion/user-name.d.ts +18 -0
  880. package/dist/companion/user-name.js +22 -0
  881. package/dist/companion/visual-consent.d.ts +40 -0
  882. package/dist/companion/visual-consent.js +98 -0
  883. package/dist/companion/visual-grounding.d.ts +83 -0
  884. package/dist/companion/visual-grounding.js +474 -0
  885. package/dist/companion/voice-callbacks.d.ts +9 -0
  886. package/dist/companion/voice-callbacks.js +84 -0
  887. package/dist/companion/voice-guidance.d.ts +23 -0
  888. package/dist/companion/voice-guidance.js +85 -0
  889. package/dist/companion/voice-improvement-loop.d.ts +53 -0
  890. package/dist/companion/voice-improvement-loop.js +279 -0
  891. package/dist/companion/voice-incident-repair.d.ts +41 -0
  892. package/dist/companion/voice-incident-repair.js +214 -0
  893. package/dist/config/code-exec-policy.d.ts +25 -0
  894. package/dist/config/code-exec-policy.js +39 -0
  895. package/dist/config/constants.d.ts +41 -0
  896. package/dist/config/constants.js +14 -0
  897. package/dist/config/env-schema.d.ts +2 -0
  898. package/dist/config/env-schema.js +714 -5
  899. package/dist/config/feature-flags.js +8 -12
  900. package/dist/config/feature-surface.d.ts +12 -0
  901. package/dist/config/feature-surface.js +86 -0
  902. package/dist/config/headless-local-prompt.d.ts +9 -0
  903. package/dist/config/headless-local-prompt.js +39 -0
  904. package/dist/config/local-runtime-context.d.ts +41 -0
  905. package/dist/config/local-runtime-context.js +430 -0
  906. package/dist/config/model-registry.js +4 -0
  907. package/dist/config/model-strengths.d.ts +14 -0
  908. package/dist/config/model-strengths.js +15 -0
  909. package/dist/config/model-tools.d.ts +65 -0
  910. package/dist/config/model-tools.js +823 -21
  911. package/dist/config/secret-ref.d.ts +22 -3
  912. package/dist/config/secret-ref.js +103 -41
  913. package/dist/config/toml-config.d.ts +20 -4
  914. package/dist/config/toml-config.js +40 -2
  915. package/dist/context/auto-compact-threshold.d.ts +1 -1
  916. package/dist/context/auto-compact-threshold.js +1 -1
  917. package/dist/context/codebase-map.js +9 -4
  918. package/dist/context/codebase-rag/codebase-rag.js +7 -6
  919. package/dist/context/codebase-rag/hnsw-store.js +9 -4
  920. package/dist/context/codebase-rag/vector-store.js +4 -4
  921. package/dist/context/compaction/parallel-summarizer.js +1 -0
  922. package/dist/context/compression.js +25 -29
  923. package/dist/context/context-manager-v2.d.ts +95 -7
  924. package/dist/context/context-manager-v2.js +444 -91
  925. package/dist/context/context-manager-v3.d.ts +4 -0
  926. package/dist/context/context-manager-v3.js +68 -9
  927. package/dist/context/cross-encoder-reranker.js +8 -3
  928. package/dist/context/default-context-engine.js +1 -1
  929. package/dist/context/enhanced-compression.d.ts +26 -2
  930. package/dist/context/enhanced-compression.js +81 -30
  931. package/dist/context/file-mentions.d.ts +46 -0
  932. package/dist/context/file-mentions.js +245 -0
  933. package/dist/context/importance-scorer.js +4 -3
  934. package/dist/context/jit-context.d.ts +7 -0
  935. package/dist/context/jit-context.js +22 -4
  936. package/dist/context/lm-resizer-compressor.d.ts +102 -0
  937. package/dist/context/lm-resizer-compressor.js +594 -0
  938. package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
  939. package/dist/context/lm-resizer-diagnostics.js +30 -0
  940. package/dist/context/precompaction-flush.js +1 -1
  941. package/dist/context/restorable-compression.d.ts +59 -14
  942. package/dist/context/restorable-compression.js +310 -60
  943. package/dist/context/segment-archive.d.ts +36 -0
  944. package/dist/context/segment-archive.js +220 -0
  945. package/dist/context/smart-compaction.d.ts +7 -0
  946. package/dist/context/smart-compaction.js +84 -15
  947. package/dist/context/token-counter.d.ts +6 -0
  948. package/dist/context/token-counter.js +41 -0
  949. package/dist/context/token-juice.d.ts +72 -0
  950. package/dist/context/token-juice.js +220 -0
  951. package/dist/context/tool-observation-optimizer.d.ts +90 -0
  952. package/dist/context/tool-observation-optimizer.js +231 -0
  953. package/dist/context/tool-output-masking.js +3 -4
  954. package/dist/context/tool-pair-preserver.d.ts +1 -1
  955. package/dist/context/tool-pair-preserver.js +1 -1
  956. package/dist/context/transcript-repair.d.ts +16 -3
  957. package/dist/context/transcript-repair.js +72 -35
  958. package/dist/context/types.d.ts +2 -0
  959. package/dist/conversation/argument-obligations.d.ts +65 -0
  960. package/dist/conversation/argument-obligations.js +157 -0
  961. package/dist/conversation/companion-model-routing.d.ts +121 -0
  962. package/dist/conversation/companion-model-routing.js +810 -0
  963. package/dist/conversation/conversation-benchmark.d.ts +143 -0
  964. package/dist/conversation/conversation-benchmark.js +766 -0
  965. package/dist/conversation/conversation-blind-comparison.d.ts +135 -0
  966. package/dist/conversation/conversation-blind-comparison.js +451 -0
  967. package/dist/conversation/conversation-evaluator.d.ts +56 -0
  968. package/dist/conversation/conversation-evaluator.js +335 -0
  969. package/dist/conversation/conversation-orchestrator.d.ts +32 -0
  970. package/dist/conversation/conversation-orchestrator.js +59 -0
  971. package/dist/conversation/conversation-pilot-corpus.d.ts +33 -0
  972. package/dist/conversation/conversation-pilot-corpus.js +313 -0
  973. package/dist/conversation/conversation-quality.d.ts +31 -0
  974. package/dist/conversation/conversation-quality.js +280 -0
  975. package/dist/conversation/conversation-state.d.ts +23 -0
  976. package/dist/conversation/conversation-state.js +117 -0
  977. package/dist/conversation/cross-channel-bridge.d.ts +127 -0
  978. package/dist/conversation/cross-channel-bridge.js +785 -0
  979. package/dist/conversation/deliberation-thread.d.ts +18 -0
  980. package/dist/conversation/deliberation-thread.js +303 -0
  981. package/dist/conversation/dialogue-act.d.ts +11 -0
  982. package/dist/conversation/dialogue-act.js +132 -0
  983. package/dist/conversation/discourse-planner.d.ts +3 -0
  984. package/dist/conversation/discourse-planner.js +141 -0
  985. package/dist/conversation/fresh-context.d.ts +54 -0
  986. package/dist/conversation/fresh-context.js +258 -0
  987. package/dist/conversation/prefetched-turn-context.d.ts +45 -0
  988. package/dist/conversation/prefetched-turn-context.js +209 -0
  989. package/dist/conversation/relationship-safety.d.ts +37 -0
  990. package/dist/conversation/relationship-safety.js +407 -0
  991. package/dist/conversation/semantic-response-gate.d.ts +262 -0
  992. package/dist/conversation/semantic-response-gate.js +656 -0
  993. package/dist/conversation/semantic-response-runtime.d.ts +79 -0
  994. package/dist/conversation/semantic-response-runtime.js +419 -0
  995. package/dist/conversation/shared-relationship-state.d.ts +60 -0
  996. package/dist/conversation/shared-relationship-state.js +262 -0
  997. package/dist/conversation/types.d.ts +77 -0
  998. package/dist/conversation/types.js +3 -0
  999. package/dist/conversation/voice-continuity.d.ts +13 -0
  1000. package/dist/conversation/voice-continuity.js +48 -0
  1001. package/dist/council/conductor.d.ts +26 -0
  1002. package/dist/council/conductor.js +292 -0
  1003. package/dist/council/council-engine.d.ts +29 -0
  1004. package/dist/council/council-engine.js +458 -0
  1005. package/dist/council/deliberation-health.d.ts +58 -0
  1006. package/dist/council/deliberation-health.js +108 -0
  1007. package/dist/council/judge.d.ts +69 -0
  1008. package/dist/council/judge.js +183 -0
  1009. package/dist/council/peers.d.ts +19 -0
  1010. package/dist/council/peers.js +47 -0
  1011. package/dist/council/signals.d.ts +26 -0
  1012. package/dist/council/signals.js +156 -0
  1013. package/dist/council/task-types.d.ts +16 -0
  1014. package/dist/council/task-types.js +58 -0
  1015. package/dist/council/triage.d.ts +48 -0
  1016. package/dist/council/triage.js +228 -0
  1017. package/dist/council/types.d.ts +292 -0
  1018. package/dist/council/types.js +20 -0
  1019. package/dist/council/with-timeout.d.ts +10 -0
  1020. package/dist/council/with-timeout.js +29 -0
  1021. package/dist/curator/curator.d.ts +85 -0
  1022. package/dist/curator/curator.js +367 -0
  1023. package/dist/daemon/agent-task-executor.js +51 -0
  1024. package/dist/daemon/autonomous-daemon.d.ts +2 -0
  1025. package/dist/daemon/autonomous-daemon.js +4 -1
  1026. package/dist/daemon/autonomous-loop.d.ts +9 -0
  1027. package/dist/daemon/autonomous-loop.js +57 -1
  1028. package/dist/daemon/autonomy-bench-candidates.d.ts +33 -0
  1029. package/dist/daemon/autonomy-bench-candidates.js +68 -0
  1030. package/dist/daemon/autonomy-briefing.d.ts +112 -0
  1031. package/dist/daemon/autonomy-briefing.js +359 -0
  1032. package/dist/daemon/autonomy-run-journal.d.ts +20 -0
  1033. package/dist/daemon/autonomy-run-journal.js +41 -0
  1034. package/dist/daemon/cron-agent-bridge.d.ts +6 -0
  1035. package/dist/daemon/cron-agent-bridge.js +31 -4
  1036. package/dist/daemon/daily-reset.d.ts +2 -2
  1037. package/dist/daemon/daily-reset.js +16 -15
  1038. package/dist/daemon/ollama-task-executor.js +9 -1
  1039. package/dist/database/database-manager.d.ts +1 -1
  1040. package/dist/database/database-manager.js +6 -4
  1041. package/dist/database/optional-sqlite.d.ts +20 -0
  1042. package/dist/database/optional-sqlite.js +101 -0
  1043. package/dist/design/design-system-registry.d.ts +21 -0
  1044. package/dist/design/design-system-registry.js +106 -0
  1045. package/dist/desktop/codebuddy-engine-adapter.d.ts +34 -0
  1046. package/dist/desktop/codebuddy-engine-adapter.js +306 -21
  1047. package/dist/desktop/electron-launch-args.d.ts +7 -0
  1048. package/dist/desktop/electron-launch-args.js +13 -0
  1049. package/dist/desktop/engine-adapter.d.ts +6 -0
  1050. package/dist/desktop/launcher.js +2 -1
  1051. package/dist/desktop/permission-bridge.d.ts +12 -1
  1052. package/dist/desktop/permission-bridge.js +12 -5
  1053. package/dist/desktop-automation/omniparser-runner.d.ts +1 -0
  1054. package/dist/desktop-automation/omniparser-runner.js +12 -1
  1055. package/dist/desktop-automation/smart-snapshot.js +7 -0
  1056. package/dist/desktop-automation/windows-native-provider.js +1 -1
  1057. package/dist/doctor/assistant-runtime.d.ts +90 -0
  1058. package/dist/doctor/assistant-runtime.js +472 -0
  1059. package/dist/doctor/env-sanity.d.ts +17 -0
  1060. package/dist/doctor/env-sanity.js +74 -0
  1061. package/dist/doctor/index.d.ts +47 -1
  1062. package/dist/doctor/index.js +464 -49
  1063. package/dist/doctor/integrations.d.ts +179 -0
  1064. package/dist/doctor/integrations.js +227 -0
  1065. package/dist/doctor/llm-key-check.d.ts +17 -0
  1066. package/dist/doctor/llm-key-check.js +100 -0
  1067. package/dist/doctor/ollama-model-selection.d.ts +16 -0
  1068. package/dist/doctor/ollama-model-selection.js +55 -0
  1069. package/dist/doctor/triage.d.ts +108 -0
  1070. package/dist/doctor/triage.js +304 -0
  1071. package/dist/embeddings/embedding-provider.d.ts +1 -0
  1072. package/dist/embeddings/embedding-provider.js +21 -9
  1073. package/dist/errors/crash-recovery.js +6 -4
  1074. package/dist/events/index.d.ts +1 -1
  1075. package/dist/events/types.d.ts +44 -0
  1076. package/dist/export/repo-explanation.d.ts +10 -0
  1077. package/dist/export/repo-explanation.js +334 -0
  1078. package/dist/export/session-share.d.ts +34 -0
  1079. package/dist/export/session-share.js +687 -0
  1080. package/dist/fleet/autonomous-tick-broadcaster.d.ts +1 -1
  1081. package/dist/fleet/capability-registry.d.ts +9 -0
  1082. package/dist/fleet/capability-registry.js +268 -56
  1083. package/dist/fleet/colab-store.d.ts +3 -4
  1084. package/dist/fleet/colab-store.js +6 -26
  1085. package/dist/fleet/collaboration.d.ts +44 -0
  1086. package/dist/fleet/collaboration.js +170 -0
  1087. package/dist/fleet/compatible-model.d.ts +39 -0
  1088. package/dist/fleet/compatible-model.js +148 -0
  1089. package/dist/fleet/consolidation/collecte.d.ts +31 -0
  1090. package/dist/fleet/consolidation/collecte.js +86 -0
  1091. package/dist/fleet/consolidation/digest-texte.d.ts +11 -0
  1092. package/dist/fleet/consolidation/digest-texte.js +52 -0
  1093. package/dist/fleet/consolidation/thalamus.d.ts +35 -0
  1094. package/dist/fleet/consolidation/thalamus.js +151 -0
  1095. package/dist/fleet/consolidation/types.d.ts +66 -0
  1096. package/dist/fleet/consolidation/types.js +22 -0
  1097. package/dist/fleet/cost-tracker.d.ts +11 -0
  1098. package/dist/fleet/cost-tracker.js +64 -17
  1099. package/dist/fleet/fleet-cost-cap.d.ts +36 -0
  1100. package/dist/fleet/fleet-cost-cap.js +175 -0
  1101. package/dist/fleet/fleet-http-surface.d.ts +24 -0
  1102. package/dist/fleet/fleet-http-surface.js +72 -0
  1103. package/dist/fleet/fleet-listener.d.ts +2 -1
  1104. package/dist/fleet/fleet-listener.js +91 -46
  1105. package/dist/fleet/fleet-registry.d.ts +2 -0
  1106. package/dist/fleet/model-capability-heuristics.d.ts +31 -0
  1107. package/dist/fleet/model-capability-heuristics.js +49 -0
  1108. package/dist/fleet/model-inventory.d.ts +46 -0
  1109. package/dist/fleet/model-inventory.js +242 -0
  1110. package/dist/fleet/model-scoreboard.d.ts +118 -13
  1111. package/dist/fleet/model-scoreboard.js +339 -27
  1112. package/dist/fleet/model-selector.d.ts +83 -0
  1113. package/dist/fleet/model-selector.js +218 -0
  1114. package/dist/fleet/peer-chat-bridge.d.ts +9 -2
  1115. package/dist/fleet/peer-chat-bridge.js +133 -19
  1116. package/dist/fleet/peer-chat-client-factory.d.ts +28 -2
  1117. package/dist/fleet/peer-chat-client-factory.js +195 -22
  1118. package/dist/fleet/peer-ckg-bridge.d.ts +67 -0
  1119. package/dist/fleet/peer-ckg-bridge.js +467 -0
  1120. package/dist/fleet/peer-mission-exchange-bridge.d.ts +8 -0
  1121. package/dist/fleet/peer-mission-exchange-bridge.js +127 -0
  1122. package/dist/fleet/peer-session-bridge.d.ts +7 -1
  1123. package/dist/fleet/peer-session-bridge.js +98 -17
  1124. package/dist/fleet/peer-session-store.d.ts +2 -0
  1125. package/dist/fleet/peer-session-store.js +6 -5
  1126. package/dist/fleet/peer-text-sanitizer.d.ts +59 -0
  1127. package/dist/fleet/peer-text-sanitizer.js +99 -0
  1128. package/dist/fleet/peer-tool-bridge.js +38 -10
  1129. package/dist/fleet/privacy-lint.js +80 -8
  1130. package/dist/fleet/resource-catalog.d.ts +245 -0
  1131. package/dist/fleet/resource-catalog.js +211 -0
  1132. package/dist/fleet/rooms/room-access.d.ts +138 -0
  1133. package/dist/fleet/rooms/room-access.js +196 -0
  1134. package/dist/fleet/rooms/room-client.d.ts +158 -0
  1135. package/dist/fleet/rooms/room-client.js +685 -0
  1136. package/dist/fleet/rooms/room-event.d.ts +139 -0
  1137. package/dist/fleet/rooms/room-event.js +330 -0
  1138. package/dist/fleet/rooms/room-filter.d.ts +48 -0
  1139. package/dist/fleet/rooms/room-filter.js +152 -0
  1140. package/dist/fleet/rooms/room-hub.d.ts +154 -0
  1141. package/dist/fleet/rooms/room-hub.js +363 -0
  1142. package/dist/fleet/rooms/room-identity.d.ts +26 -0
  1143. package/dist/fleet/rooms/room-identity.js +82 -0
  1144. package/dist/fleet/rooms/room-lock.d.ts +34 -0
  1145. package/dist/fleet/rooms/room-lock.js +180 -0
  1146. package/dist/fleet/rooms/room-observations.d.ts +7 -0
  1147. package/dist/fleet/rooms/room-observations.js +61 -0
  1148. package/dist/fleet/rooms/room-server.d.ts +28 -0
  1149. package/dist/fleet/rooms/room-server.js +75 -0
  1150. package/dist/fleet/rooms/room-store.d.ts +119 -0
  1151. package/dist/fleet/rooms/room-store.js +448 -0
  1152. package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
  1153. package/dist/fleet/rooms/room-ws-bridge.js +137 -0
  1154. package/dist/fleet/saga-store.d.ts +25 -1
  1155. package/dist/fleet/saga-store.js +34 -6
  1156. package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
  1157. package/dist/fleet/sensory-mission-bridge.js +136 -0
  1158. package/dist/fleet/task-router.d.ts +17 -1
  1159. package/dist/fleet/task-router.js +65 -20
  1160. package/dist/fleet/types.d.ts +9 -5
  1161. package/dist/gateway/device-pairing.js +5 -11
  1162. package/dist/gateway/ws-transport.d.ts +1 -5
  1163. package/dist/gateway/ws-transport.js +2 -22
  1164. package/dist/git/changelog.d.ts +36 -0
  1165. package/dist/git/changelog.js +109 -0
  1166. package/dist/git/worktree-sessions.js +2 -1
  1167. package/dist/goals/counterfactual-forge.d.ts +70 -0
  1168. package/dist/goals/counterfactual-forge.js +184 -0
  1169. package/dist/goals/criterion-progress.d.ts +24 -0
  1170. package/dist/goals/criterion-progress.js +59 -0
  1171. package/dist/goals/evidence-artifact.d.ts +18 -0
  1172. package/dist/goals/evidence-artifact.js +76 -0
  1173. package/dist/goals/goal-decomposer.d.ts +15 -0
  1174. package/dist/goals/goal-decomposer.js +89 -4
  1175. package/dist/goals/goal-loop.d.ts +29 -0
  1176. package/dist/goals/goal-loop.js +92 -6
  1177. package/dist/goals/goal-manager.d.ts +1 -0
  1178. package/dist/goals/goal-manager.js +4 -1
  1179. package/dist/goals/goal-state.d.ts +10 -0
  1180. package/dist/goals/goal-state.js +21 -1
  1181. package/dist/goals/goal-store.js +4 -6
  1182. package/dist/goals/index.d.ts +11 -0
  1183. package/dist/goals/index.js +11 -0
  1184. package/dist/goals/intent-graph.d.ts +40 -0
  1185. package/dist/goals/intent-graph.js +133 -0
  1186. package/dist/goals/mission-constitution.d.ts +59 -0
  1187. package/dist/goals/mission-constitution.js +132 -0
  1188. package/dist/goals/mission-exchange.d.ts +86 -0
  1189. package/dist/goals/mission-exchange.js +216 -0
  1190. package/dist/goals/outcome-capsule.d.ts +80 -0
  1191. package/dist/goals/outcome-capsule.js +186 -0
  1192. package/dist/goals/proof-ledger.d.ts +79 -0
  1193. package/dist/goals/proof-ledger.js +181 -0
  1194. package/dist/goals/proven-outcome-lessons.d.ts +11 -0
  1195. package/dist/goals/proven-outcome-lessons.js +30 -0
  1196. package/dist/goals/proven-outcome-memory.d.ts +55 -0
  1197. package/dist/goals/proven-outcome-memory.js +125 -0
  1198. package/dist/goals/shadow-twin.d.ts +57 -0
  1199. package/dist/goals/shadow-twin.js +110 -0
  1200. package/dist/gpu-worker/gpu-media-worker-server.d.ts +49 -0
  1201. package/dist/gpu-worker/gpu-media-worker-server.js +637 -0
  1202. package/dist/harness/contract.d.ts +88 -88
  1203. package/dist/harness/fleet-supervisor.d.ts +16 -0
  1204. package/dist/harness/fleet-supervisor.js +64 -0
  1205. package/dist/harness/index.d.ts +4 -0
  1206. package/dist/harness/index.js +4 -0
  1207. package/dist/harness/mission-runner.d.ts +3 -0
  1208. package/dist/harness/mission-runner.js +53 -0
  1209. package/dist/harness/mission-store.d.ts +93 -0
  1210. package/dist/harness/mission-store.js +297 -0
  1211. package/dist/harness/tool-harness.d.ts +56 -0
  1212. package/dist/harness/tool-harness.js +153 -0
  1213. package/dist/hooks/advanced-hooks.js +6 -4
  1214. package/dist/hooks/env-persistence.js +5 -6
  1215. package/dist/hooks/hook-manager.js +8 -7
  1216. package/dist/hooks/hook-system.js +6 -4
  1217. package/dist/hooks/lifecycle-hooks.js +6 -3
  1218. package/dist/hooks/moltbot/moltbot-hooks-manager.js +8 -3
  1219. package/dist/hooks/moltbot/session-persistence-manager.js +15 -7
  1220. package/dist/hooks/moltbot/setup-utilities.js +6 -4
  1221. package/dist/hooks/smart-hooks.js +6 -4
  1222. package/dist/hooks/use-enhanced-input.js +48 -4
  1223. package/dist/hooks/use-input-handler.d.ts +1 -0
  1224. package/dist/hooks/use-input-handler.js +90 -84
  1225. package/dist/hooks/use-input-history.js +1 -0
  1226. package/dist/hooks/user-hooks.d.ts +10 -1
  1227. package/dist/hooks/user-hooks.js +77 -1
  1228. package/dist/identity/companion-identity.d.ts +4 -1
  1229. package/dist/identity/companion-identity.js +134 -4
  1230. package/dist/identity/identity-manager.d.ts +1 -1
  1231. package/dist/identity/identity-manager.js +7 -6
  1232. package/dist/identity/lisa-introspection.d.ts +36 -0
  1233. package/dist/identity/lisa-introspection.js +414 -0
  1234. package/dist/identity/operational-self-model.d.ts +145 -0
  1235. package/dist/identity/operational-self-model.js +978 -0
  1236. package/dist/index.d.ts +3 -1
  1237. package/dist/index.js +1039 -229
  1238. package/dist/input/context-mentions.d.ts +7 -1
  1239. package/dist/input/context-mentions.js +18 -20
  1240. package/dist/input/text-to-speech.d.ts +3 -0
  1241. package/dist/input/text-to-speech.js +70 -27
  1242. package/dist/input/voice-control.d.ts +1 -0
  1243. package/dist/input/voice-control.js +24 -18
  1244. package/dist/input/voice-input-enhanced.d.ts +1 -0
  1245. package/dist/input/voice-input-enhanced.js +31 -16
  1246. package/dist/integrations/ci-autofix-pipeline.js +15 -20
  1247. package/dist/integrations/ide/server.js +3 -2
  1248. package/dist/integrations/json-rpc/server.js +4 -2
  1249. package/dist/integrations/mcp/mcp-server.js +4 -2
  1250. package/dist/integrations/opentelemetry-integration.js +6 -4
  1251. package/dist/integrations/pubcommander-bridge.d.ts +5 -0
  1252. package/dist/integrations/pubcommander-bridge.js +61 -0
  1253. package/dist/integrations/sentry-integration.js +9 -3
  1254. package/dist/intelligence/semantic-search.js +9 -12
  1255. package/dist/intelligence/user-preferences.js +8 -11
  1256. package/dist/intents/intent-checker.d.ts +34 -0
  1257. package/dist/intents/intent-checker.js +175 -0
  1258. package/dist/intents/intent-generator.d.ts +12 -0
  1259. package/dist/intents/intent-generator.js +86 -0
  1260. package/dist/intents/intent-store.d.ts +62 -0
  1261. package/dist/intents/intent-store.js +263 -0
  1262. package/dist/interpreter/computer/skills.js +3 -2
  1263. package/dist/interpreter/interpreter-service.js +18 -9
  1264. package/dist/kanban/kanban-board-registry.js +6 -4
  1265. package/dist/kanban/kanban-store.js +5 -7
  1266. package/dist/knowledge/code-graph-persistence.js +8 -16
  1267. package/dist/knowledge/graph-drift.js +14 -11
  1268. package/dist/knowledge/knowledge-manager.js +2 -1
  1269. package/dist/knowledge/scanners/index.d.ts +3 -0
  1270. package/dist/knowledge/scanners/index.js +57 -29
  1271. package/dist/knowledge/workspace-indexer.js +22 -15
  1272. package/dist/life-rhythm/cooking-timer-store.d.ts +50 -0
  1273. package/dist/life-rhythm/cooking-timer-store.js +240 -0
  1274. package/dist/life-rhythm/day-context.d.ts +30 -0
  1275. package/dist/life-rhythm/day-context.js +198 -0
  1276. package/dist/life-rhythm/etalab-holiday-provider.d.ts +40 -0
  1277. package/dist/life-rhythm/etalab-holiday-provider.js +318 -0
  1278. package/dist/life-rhythm/home-mode-store.d.ts +32 -0
  1279. package/dist/life-rhythm/home-mode-store.js +184 -0
  1280. package/dist/life-rhythm/index.d.ts +6 -0
  1281. package/dist/life-rhythm/index.js +7 -0
  1282. package/dist/life-rhythm/types.d.ts +88 -0
  1283. package/dist/life-rhythm/types.js +33 -0
  1284. package/dist/life-rhythm/zoned-minute.d.ts +24 -0
  1285. package/dist/life-rhythm/zoned-minute.js +113 -0
  1286. package/dist/location/index.d.ts +2 -12
  1287. package/dist/location/index.js +129 -56
  1288. package/dist/logging/interaction-logger.js +29 -46
  1289. package/dist/lora/dataset-v3-plan.d.ts +27 -0
  1290. package/dist/lora/dataset-v3-plan.js +121 -0
  1291. package/dist/lora/dataset.d.ts +26 -0
  1292. package/dist/lora/dataset.js +165 -0
  1293. package/dist/lora/fal-krea-trainer.d.ts +54 -0
  1294. package/dist/lora/fal-krea-trainer.js +197 -0
  1295. package/dist/lora/generate-training-set.d.ts +61 -0
  1296. package/dist/lora/generate-training-set.js +190 -0
  1297. package/dist/lora/identity-dataset-gate.d.ts +104 -0
  1298. package/dist/lora/identity-dataset-gate.js +204 -0
  1299. package/dist/lora/identity-dataset-promotion.d.ts +32 -0
  1300. package/dist/lora/identity-dataset-promotion.js +269 -0
  1301. package/dist/lora/index.d.ts +11 -0
  1302. package/dist/lora/index.js +12 -0
  1303. package/dist/lora/install-comfy.d.ts +14 -0
  1304. package/dist/lora/install-comfy.js +63 -0
  1305. package/dist/lora/lisa-avatar-bible.d.ts +85 -0
  1306. package/dist/lora/lisa-avatar-bible.js +275 -0
  1307. package/dist/lora/local-plan.d.ts +10 -0
  1308. package/dist/lora/local-plan.js +205 -0
  1309. package/dist/lora/pack-dataset.d.ts +7 -0
  1310. package/dist/lora/pack-dataset.js +39 -0
  1311. package/dist/lora/quality-gate.d.ts +31 -0
  1312. package/dist/lora/quality-gate.js +90 -0
  1313. package/dist/lora/types.d.ts +66 -0
  1314. package/dist/lora/types.js +5 -0
  1315. package/dist/lsp/lsp-client.d.ts +12 -0
  1316. package/dist/lsp/lsp-client.js +45 -9
  1317. package/dist/lsp/server.js +1 -1
  1318. package/dist/mcp/approval-elicitation.d.ts +1 -0
  1319. package/dist/mcp/approval-elicitation.js +2 -0
  1320. package/dist/mcp/client.d.ts +15 -1
  1321. package/dist/mcp/client.js +116 -24
  1322. package/dist/mcp/config.d.ts +32 -1
  1323. package/dist/mcp/config.js +175 -19
  1324. package/dist/mcp/import-normalize.d.ts +12 -0
  1325. package/dist/mcp/import-normalize.js +114 -0
  1326. package/dist/mcp/index.d.ts +1 -1
  1327. package/dist/mcp/mcp-agent-tools.d.ts +1 -1
  1328. package/dist/mcp/mcp-agent-tools.js +76 -73
  1329. package/dist/mcp/mcp-ckg-tools.d.ts +31 -0
  1330. package/dist/mcp/mcp-ckg-tools.js +121 -0
  1331. package/dist/mcp/mcp-client.js +8 -5
  1332. package/dist/mcp/mcp-desktop-tools.d.ts +1 -1
  1333. package/dist/mcp/mcp-desktop-tools.js +105 -99
  1334. package/dist/mcp/mcp-memory-tools.d.ts +1 -1
  1335. package/dist/mcp/mcp-memory-tools.js +63 -61
  1336. package/dist/mcp/mcp-oauth.js +5 -2
  1337. package/dist/mcp/mcp-server.d.ts +48 -79
  1338. package/dist/mcp/mcp-server.js +445 -478
  1339. package/dist/mcp/mcp-session-tools.d.ts +1 -1
  1340. package/dist/mcp/mcp-session-tools.js +95 -92
  1341. package/dist/mcp/profiles.d.ts +18 -0
  1342. package/dist/mcp/profiles.js +65 -0
  1343. package/dist/mcp/prompt-footprint.d.ts +21 -0
  1344. package/dist/mcp/prompt-footprint.js +39 -0
  1345. package/dist/mcp/transports.d.ts +10 -5
  1346. package/dist/mcp/transports.js +61 -99
  1347. package/dist/mcp/types.d.ts +5 -0
  1348. package/dist/meals/allergens.d.ts +19 -0
  1349. package/dist/meals/allergens.js +122 -0
  1350. package/dist/meals/compatibility.d.ts +6 -0
  1351. package/dist/meals/compatibility.js +109 -0
  1352. package/dist/meals/food-inventory-store.d.ts +51 -0
  1353. package/dist/meals/food-inventory-store.js +257 -0
  1354. package/dist/meals/index.d.ts +10 -0
  1355. package/dist/meals/index.js +11 -0
  1356. package/dist/meals/meal-engine.d.ts +7 -0
  1357. package/dist/meals/meal-engine.js +206 -0
  1358. package/dist/meals/meal-plan-store.d.ts +73 -0
  1359. package/dist/meals/meal-plan-store.js +271 -0
  1360. package/dist/meals/private-json-store.d.ts +9 -0
  1361. package/dist/meals/private-json-store.js +92 -0
  1362. package/dist/meals/profile-store.d.ts +49 -0
  1363. package/dist/meals/profile-store.js +296 -0
  1364. package/dist/meals/profile-validator.d.ts +7 -0
  1365. package/dist/meals/profile-validator.js +124 -0
  1366. package/dist/meals/recipe-normalizer.d.ts +7 -0
  1367. package/dist/meals/recipe-normalizer.js +162 -0
  1368. package/dist/meals/store-validation.d.ts +21 -0
  1369. package/dist/meals/store-validation.js +101 -0
  1370. package/dist/meals/types.d.ts +170 -0
  1371. package/dist/meals/types.js +12 -0
  1372. package/dist/media/comfy-health-supervisor.d.ts +142 -0
  1373. package/dist/media/comfy-health-supervisor.js +608 -0
  1374. package/dist/media/comfyui-recipe-contract.d.ts +696 -0
  1375. package/dist/media/comfyui-recipe-contract.js +580 -0
  1376. package/dist/media/comfyui-recipe-engine.d.ts +3 -0
  1377. package/dist/media/comfyui-recipe-engine.js +4 -0
  1378. package/dist/media/comfyui-recipe-registry.d.ts +35 -0
  1379. package/dist/media/comfyui-recipe-registry.js +385 -0
  1380. package/dist/media/comfyui-recipe-runtime.d.ts +135 -0
  1381. package/dist/media/comfyui-recipe-runtime.js +942 -0
  1382. package/dist/media/content-tier.d.ts +25 -0
  1383. package/dist/media/content-tier.js +29 -0
  1384. package/dist/meeting/analyzer.d.ts +24 -0
  1385. package/dist/meeting/analyzer.js +402 -0
  1386. package/dist/meeting/index.d.ts +13 -0
  1387. package/dist/meeting/index.js +48 -0
  1388. package/dist/meeting/markdown.d.ts +3 -0
  1389. package/dist/meeting/markdown.js +51 -0
  1390. package/dist/meeting/output.d.ts +13 -0
  1391. package/dist/meeting/output.js +155 -0
  1392. package/dist/meeting/transcript.d.ts +25 -0
  1393. package/dist/meeting/transcript.js +292 -0
  1394. package/dist/meeting/types.d.ts +112 -0
  1395. package/dist/meeting/types.js +9 -0
  1396. package/dist/memory/adapters/network-memory-adapters.d.ts +19 -4
  1397. package/dist/memory/adapters/network-memory-adapters.js +4 -4
  1398. package/dist/memory/auto-memory.js +5 -2
  1399. package/dist/memory/background-extractor.d.ts +43 -0
  1400. package/dist/memory/background-extractor.js +368 -0
  1401. package/dist/memory/buddy-memory-client.d.ts +40 -0
  1402. package/dist/memory/buddy-memory-client.js +198 -0
  1403. package/dist/memory/ckg-engine-policy.d.ts +19 -0
  1404. package/dist/memory/ckg-engine-policy.js +45 -0
  1405. package/dist/memory/ckg-fact-reconciliation.d.ts +81 -0
  1406. package/dist/memory/ckg-fact-reconciliation.js +155 -0
  1407. package/dist/memory/ckg-redaction.d.ts +2 -0
  1408. package/dist/memory/ckg-redaction.js +25 -0
  1409. package/dist/memory/coding-style-analyzer.js +2 -2
  1410. package/dist/memory/collective-knowledge-graph.d.ts +380 -0
  1411. package/dist/memory/collective-knowledge-graph.js +1228 -0
  1412. package/dist/memory/enhanced-memory.d.ts +29 -0
  1413. package/dist/memory/enhanced-memory.js +153 -63
  1414. package/dist/memory/facts-memory.d.ts +20 -5
  1415. package/dist/memory/facts-memory.js +68 -12
  1416. package/dist/memory/hybrid-mmr.d.ts +71 -0
  1417. package/dist/memory/hybrid-mmr.js +127 -0
  1418. package/dist/memory/hybrid-search.js +4 -1
  1419. package/dist/memory/knowledge-graph.d.ts +23 -2
  1420. package/dist/memory/knowledge-graph.js +62 -47
  1421. package/dist/memory/memory-candidate-queue.d.ts +2 -2
  1422. package/dist/memory/memory-candidate-queue.js +15 -10
  1423. package/dist/memory/memory-consolidation.d.ts +8 -0
  1424. package/dist/memory/memory-consolidation.js +66 -17
  1425. package/dist/memory/memory-forgetting.d.ts +57 -0
  1426. package/dist/memory/memory-forgetting.js +87 -0
  1427. package/dist/memory/ocr-memory-pipeline.js +16 -10
  1428. package/dist/memory/persistent-memory.d.ts +111 -8
  1429. package/dist/memory/persistent-memory.js +788 -149
  1430. package/dist/memory/presence-injector.d.ts +1 -1
  1431. package/dist/memory/presence-injector.js +9 -4
  1432. package/dist/memory/semantic-memory-search.js +12 -9
  1433. package/dist/memory/subagent-memory.js +8 -8
  1434. package/dist/memory/user-model.js +9 -4
  1435. package/dist/metrics/metrics-collector.d.ts +1 -0
  1436. package/dist/metrics/metrics-collector.js +11 -2
  1437. package/dist/nodes/device-node.d.ts +5 -2
  1438. package/dist/nodes/device-node.js +61 -19
  1439. package/dist/nodes/index.d.ts +32 -2
  1440. package/dist/nodes/index.js +208 -7
  1441. package/dist/nodes/transports/adb-transport.d.ts +2 -6
  1442. package/dist/nodes/transports/adb-transport.js +29 -13
  1443. package/dist/nodes/transports/base-transport.d.ts +10 -0
  1444. package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
  1445. package/dist/observability/run-event-writer.d.ts +21 -0
  1446. package/dist/observability/run-event-writer.js +81 -0
  1447. package/dist/observability/run-store.d.ts +11 -1
  1448. package/dist/observability/run-store.js +107 -35
  1449. package/dist/observability/run-trajectory-load.d.ts +16 -0
  1450. package/dist/observability/run-trajectory-load.js +266 -0
  1451. package/dist/observability/run-trajectory.d.ts +168 -0
  1452. package/dist/observability/run-trajectory.js +468 -0
  1453. package/dist/observability/run-viewer.d.ts +5 -1
  1454. package/dist/observability/run-viewer.js +83 -9
  1455. package/dist/observability/turn-metrics.d.ts +86 -0
  1456. package/dist/observability/turn-metrics.js +201 -0
  1457. package/dist/offline/offline-mode.js +10 -9
  1458. package/dist/openclaw/gateway-bridge.js +3 -2
  1459. package/dist/optimization/latency-optimizer.d.ts +2 -1
  1460. package/dist/optimization/latency-optimizer.js +2 -2
  1461. package/dist/optimization/model-routing.d.ts +3 -0
  1462. package/dist/optimization/prompt-cache.d.ts +19 -2
  1463. package/dist/optimization/prompt-cache.js +41 -6
  1464. package/dist/orchestration/orchestrator.d.ts +21 -0
  1465. package/dist/orchestration/orchestrator.js +126 -16
  1466. package/dist/orchestration/types.d.ts +2 -0
  1467. package/dist/performance/lazy-loader.js +2 -1
  1468. package/dist/performance/tool-cache.d.ts +4 -0
  1469. package/dist/performance/tool-cache.js +4 -2
  1470. package/dist/persistence/conversation-branches.js +9 -2
  1471. package/dist/persistence/session-content.d.ts +8 -0
  1472. package/dist/persistence/session-content.js +76 -0
  1473. package/dist/persistence/session-handoff.d.ts +64 -0
  1474. package/dist/persistence/session-handoff.js +80 -0
  1475. package/dist/persistence/session-history.d.ts +4 -0
  1476. package/dist/persistence/session-history.js +38 -0
  1477. package/dist/persistence/session-store.d.ts +35 -0
  1478. package/dist/persistence/session-store.js +104 -37
  1479. package/dist/personas/persona-manager.d.ts +39 -1
  1480. package/dist/personas/persona-manager.js +292 -38
  1481. package/dist/plugins/bundled/groq-provider.js +10 -7
  1482. package/dist/plugins/bundled/openrouter-provider.js +2 -1
  1483. package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +62 -8
  1484. package/dist/plugins/code-explorer/CodeExplorerManager.js +255 -32
  1485. package/dist/plugins/code-explorer/code-explorer-client.d.ts +36 -0
  1486. package/dist/plugins/code-explorer/code-explorer-client.js +86 -0
  1487. package/dist/plugins/code-explorer/index.d.ts +1 -3
  1488. package/dist/plugins/code-explorer/index.js +0 -1
  1489. package/dist/plugins/git-pinned-marketplace.js +3 -2
  1490. package/dist/plugins/plugin-manager.js +16 -10
  1491. package/dist/plugins/sandbox-worker.js +1 -1
  1492. package/dist/prompts/prompt-manager.js +11 -4
  1493. package/dist/prompts/system-base.js +33 -5
  1494. package/dist/protocols/a2a/codebuddy-executor.d.ts +20 -2
  1495. package/dist/protocols/a2a/codebuddy-executor.js +34 -14
  1496. package/dist/protocols/a2a/index.d.ts +7 -1
  1497. package/dist/protocols/a2a/index.js +32 -3
  1498. package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
  1499. package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
  1500. package/dist/protocols/a2a/peer-config.d.ts +20 -0
  1501. package/dist/protocols/a2a/peer-config.js +55 -0
  1502. package/dist/protocols/acp/acp-agentic-runner.d.ts +6 -2
  1503. package/dist/protocols/acp/acp-agentic-runner.js +91 -15
  1504. package/dist/protocols/acp/acp-session-store.js +28 -5
  1505. package/dist/protocols/acp/acp-stdio-server.d.ts +13 -0
  1506. package/dist/protocols/acp/acp-stdio-server.js +30 -3
  1507. package/dist/providers/active-llm-model-pool.d.ts +45 -0
  1508. package/dist/providers/active-llm-model-pool.js +139 -0
  1509. package/dist/providers/active-llm-registry.js +48 -8
  1510. package/dist/providers/auxiliary-provider.d.ts +1 -1
  1511. package/dist/providers/auxiliary-provider.js +23 -10
  1512. package/dist/providers/chatgpt-models.d.ts +71 -0
  1513. package/dist/providers/chatgpt-models.js +302 -0
  1514. package/dist/providers/codex-oauth.d.ts +8 -0
  1515. package/dist/providers/codex-oauth.js +75 -43
  1516. package/dist/providers/gemini-oauth.js +7 -6
  1517. package/dist/providers/grok-provider.js +1 -1
  1518. package/dist/providers/index.d.ts +3 -0
  1519. package/dist/providers/index.js +3 -0
  1520. package/dist/providers/local-model-resolver.d.ts +66 -0
  1521. package/dist/providers/local-model-resolver.js +128 -0
  1522. package/dist/providers/model-egress.d.ts +8 -0
  1523. package/dist/providers/model-egress.js +62 -0
  1524. package/dist/providers/model-provider-compat.d.ts +13 -0
  1525. package/dist/providers/model-provider-compat.js +49 -0
  1526. package/dist/providers/provider-catalog.d.ts +4 -1
  1527. package/dist/providers/provider-catalog.js +494 -28
  1528. package/dist/providers/provider-failover-notify.d.ts +15 -0
  1529. package/dist/providers/provider-failover-notify.js +93 -0
  1530. package/dist/providers/provider-failover-policy.d.ts +28 -0
  1531. package/dist/providers/provider-failover-policy.js +188 -0
  1532. package/dist/providers/provider-failover-user-notice.d.ts +13 -0
  1533. package/dist/providers/provider-failover-user-notice.js +49 -0
  1534. package/dist/providers/provider-fallback.d.ts +1 -0
  1535. package/dist/providers/provider-fallback.js +1 -1
  1536. package/dist/providers/provider-health.d.ts +46 -0
  1537. package/dist/providers/provider-health.js +249 -0
  1538. package/dist/providers/turboquant-provider.d.ts +1 -1
  1539. package/dist/providers/xai-oauth.js +7 -34
  1540. package/dist/queue/persistent-queue.js +9 -6
  1541. package/dist/renderers/charts/special-charts.d.ts +7 -1
  1542. package/dist/renderers/charts/special-charts.js +5 -0
  1543. package/dist/renderers/weather-conditions.d.ts +20 -0
  1544. package/dist/renderers/weather-conditions.js +74 -0
  1545. package/dist/renderers/weather-renderer.js +4 -18
  1546. package/dist/rendering/ansi.d.ts +4 -0
  1547. package/dist/rendering/ansi.js +149 -0
  1548. package/dist/rendering/index.d.ts +19 -0
  1549. package/dist/rendering/index.js +18 -0
  1550. package/dist/rendering/plain.d.ts +2 -0
  1551. package/dist/rendering/plain.js +91 -0
  1552. package/dist/rendering/telegram-html.d.ts +7 -0
  1553. package/dist/rendering/telegram-html.js +76 -16
  1554. package/dist/research/auto-ingest.d.ts +45 -0
  1555. package/dist/research/auto-ingest.js +85 -0
  1556. package/dist/research/code-explorer-source.d.ts +29 -0
  1557. package/dist/research/code-explorer-source.js +76 -0
  1558. package/dist/research/connector-source.d.ts +25 -0
  1559. package/dist/research/connector-source.js +314 -0
  1560. package/dist/research/paper-qa/answer.d.ts +72 -0
  1561. package/dist/research/paper-qa/answer.js +227 -0
  1562. package/dist/research/paper-qa/corpus.d.ts +36 -0
  1563. package/dist/research/paper-qa/corpus.js +102 -0
  1564. package/dist/research/paper-qa/disk-embedding-cache.d.ts +28 -0
  1565. package/dist/research/paper-qa/disk-embedding-cache.js +193 -0
  1566. package/dist/research/paper-qa/index.d.ts +31 -0
  1567. package/dist/research/paper-qa/index.js +24 -0
  1568. package/dist/research/paper-qa/paper-qa-pipeline.d.ts +107 -0
  1569. package/dist/research/paper-qa/paper-qa-pipeline.js +163 -0
  1570. package/dist/research/paper-qa/passage-index.d.ts +203 -0
  1571. package/dist/research/paper-qa/passage-index.js +405 -0
  1572. package/dist/research/paper-qa/pdf-structure.d.ts +37 -0
  1573. package/dist/research/paper-qa/pdf-structure.js +377 -0
  1574. package/dist/research/paper-qa/persistent-corpus-index.d.ts +23 -0
  1575. package/dist/research/paper-qa/persistent-corpus-index.js +122 -0
  1576. package/dist/research/paper-qa/prose-chunker.d.ts +19 -0
  1577. package/dist/research/paper-qa/prose-chunker.js +201 -0
  1578. package/dist/research/paper-qa/provenance.d.ts +24 -0
  1579. package/dist/research/paper-qa/provenance.js +40 -0
  1580. package/dist/research/paper-qa/rcs.d.ts +78 -0
  1581. package/dist/research/paper-qa/rcs.js +200 -0
  1582. package/dist/research/paper-qa/types.d.ts +124 -0
  1583. package/dist/research/paper-qa/types.js +13 -0
  1584. package/dist/research/publication-sources.d.ts +34 -0
  1585. package/dist/research/publication-sources.js +131 -0
  1586. package/dist/research/relation-classifier.d.ts +13 -0
  1587. package/dist/research/relation-classifier.js +58 -0
  1588. package/dist/research/research-topics.d.ts +13 -0
  1589. package/dist/research/research-topics.js +81 -0
  1590. package/dist/review/apply-transaction.d.ts +40 -0
  1591. package/dist/review/apply-transaction.js +112 -0
  1592. package/dist/review/diff-model.d.ts +39 -0
  1593. package/dist/review/diff-model.js +124 -0
  1594. package/dist/review/llm-client.d.ts +30 -0
  1595. package/dist/review/llm-client.js +98 -0
  1596. package/dist/review/llm-reviewer.d.ts +16 -0
  1597. package/dist/review/llm-reviewer.js +122 -0
  1598. package/dist/review/review-engine.d.ts +54 -0
  1599. package/dist/review/review-engine.js +0 -0
  1600. package/dist/review/revision-loop.d.ts +67 -0
  1601. package/dist/review/revision-loop.js +160 -0
  1602. package/dist/review/static-gate.d.ts +13 -0
  1603. package/dist/review/static-gate.js +110 -0
  1604. package/dist/review/types.d.ts +122 -0
  1605. package/dist/review/types.js +24 -0
  1606. package/dist/review/write-gate.d.ts +55 -0
  1607. package/dist/review/write-gate.js +121 -0
  1608. package/dist/sandbox/auto-sandbox.d.ts +4 -2
  1609. package/dist/sandbox/auto-sandbox.js +14 -1
  1610. package/dist/sandbox/docker-sandbox.d.ts +39 -0
  1611. package/dist/sandbox/docker-sandbox.js +292 -14
  1612. package/dist/sandbox/execpolicy.d.ts +36 -1
  1613. package/dist/sandbox/execpolicy.js +375 -40
  1614. package/dist/sandbox/os-sandbox.d.ts +54 -1
  1615. package/dist/sandbox/os-sandbox.js +315 -70
  1616. package/dist/sandbox/sandbox-backend.d.ts +2 -0
  1617. package/dist/scheduler/cron-scheduler.d.ts +16 -2
  1618. package/dist/scheduler/cron-scheduler.js +177 -45
  1619. package/dist/scheduler/job-notepad.d.ts +69 -0
  1620. package/dist/scheduler/job-notepad.js +339 -0
  1621. package/dist/scripting/codebuddy-bindings.js +2 -1
  1622. package/dist/search/bm25.js +5 -2
  1623. package/dist/search/usearch-index.js +7 -5
  1624. package/dist/security/audit-logger.d.ts +1 -1
  1625. package/dist/security/audit-logger.js +14 -7
  1626. package/dist/security/bash-allowlist/allowlist-store.d.ts +8 -2
  1627. package/dist/security/bash-allowlist/allowlist-store.js +52 -12
  1628. package/dist/security/bash-allowlist/approval-flow.d.ts +1 -1
  1629. package/dist/security/bash-allowlist/approval-flow.js +10 -8
  1630. package/dist/security/bash-allowlist/deny-guard.d.ts +14 -0
  1631. package/dist/security/bash-allowlist/deny-guard.js +61 -0
  1632. package/dist/security/bash-allowlist/types.d.ts +5 -0
  1633. package/dist/security/bash-parser.d.ts +32 -0
  1634. package/dist/security/bash-parser.js +129 -14
  1635. package/dist/security/compute-confinement.d.ts +4 -0
  1636. package/dist/security/compute-confinement.js +89 -0
  1637. package/dist/security/dangerous-patterns.js +18 -3
  1638. package/dist/security/declarative-rules.d.ts +1 -1
  1639. package/dist/security/declarative-rules.js +242 -65
  1640. package/dist/security/dependency-vuln-scanner.js +4 -4
  1641. package/dist/security/dev-origins.d.ts +40 -0
  1642. package/dist/security/dev-origins.js +111 -0
  1643. package/dist/security/docker-sandbox/manager.js +1 -1
  1644. package/dist/security/env-blocklist.d.ts +7 -0
  1645. package/dist/security/env-blocklist.js +22 -2
  1646. package/dist/security/guardian-agent.d.ts +0 -5
  1647. package/dist/security/guardian-agent.js +17 -6
  1648. package/dist/security/index.d.ts +3 -0
  1649. package/dist/security/index.js +2 -0
  1650. package/dist/security/native-sandbox.d.ts +80 -0
  1651. package/dist/security/native-sandbox.js +455 -0
  1652. package/dist/security/pack-contents-policy.d.ts +49 -0
  1653. package/dist/security/pack-contents-policy.js +139 -0
  1654. package/dist/security/permission-config.js +2 -5
  1655. package/dist/security/permission-modes.d.ts +11 -0
  1656. package/dist/security/permission-modes.js +71 -7
  1657. package/dist/security/policy-amendments.js +40 -22
  1658. package/dist/security/policy-engine.js +39 -12
  1659. package/dist/security/powershell-parser.d.ts +40 -0
  1660. package/dist/security/powershell-parser.js +198 -0
  1661. package/dist/security/remote-approval.d.ts +0 -1
  1662. package/dist/security/remote-approval.js +3 -2
  1663. package/dist/security/safe-binaries.d.ts +13 -1
  1664. package/dist/security/safe-binaries.js +240 -32
  1665. package/dist/security/safe-fetch.d.ts +7 -0
  1666. package/dist/security/safe-fetch.js +101 -0
  1667. package/dist/security/sandbox.js +2 -2
  1668. package/dist/security/secret-patterns.d.ts +18 -0
  1669. package/dist/security/secret-patterns.js +232 -0
  1670. package/dist/security/secret-scrubber.d.ts +31 -0
  1671. package/dist/security/secret-scrubber.js +170 -0
  1672. package/dist/security/secrets-detector.d.ts +3 -1
  1673. package/dist/security/secrets-detector.js +3 -106
  1674. package/dist/security/security-modes.js +2 -1
  1675. package/dist/security/session-encryption.d.ts +11 -2
  1676. package/dist/security/session-encryption.js +59 -7
  1677. package/dist/security/shell-env-policy.js +11 -2
  1678. package/dist/security/skill-scanner.d.ts +2 -2
  1679. package/dist/security/skill-scanner.js +237 -11
  1680. package/dist/security/ssrf-guard.d.ts +13 -0
  1681. package/dist/security/ssrf-guard.js +27 -0
  1682. package/dist/security/text-deobfuscation.d.ts +41 -0
  1683. package/dist/security/text-deobfuscation.js +256 -0
  1684. package/dist/security/tool-permissions.js +2 -3
  1685. package/dist/security/tool-policy/approval-scope.d.ts +11 -0
  1686. package/dist/security/tool-policy/approval-scope.js +67 -0
  1687. package/dist/security/tool-policy/policy-manager.d.ts +9 -0
  1688. package/dist/security/tool-policy/policy-manager.js +28 -1
  1689. package/dist/security/tool-policy/profiles.js +24 -0
  1690. package/dist/security/tool-policy/tool-groups.js +26 -3
  1691. package/dist/security/tool-policy/types.d.ts +3 -1
  1692. package/dist/security/tool-policy/types.js +2 -0
  1693. package/dist/security/trust-folders.js +3 -2
  1694. package/dist/security/write-policy.d.ts +8 -0
  1695. package/dist/security/write-policy.js +63 -1
  1696. package/dist/self-model/evolution-notes.d.ts +51 -0
  1697. package/dist/self-model/evolution-notes.js +324 -0
  1698. package/dist/sensory/agent-reply.d.ts +136 -0
  1699. package/dist/sensory/agent-reply.js +682 -0
  1700. package/dist/sensory/alert.d.ts +18 -0
  1701. package/dist/sensory/alert.js +105 -0
  1702. package/dist/sensory/arrival-opener.d.ts +102 -0
  1703. package/dist/sensory/arrival-opener.js +292 -0
  1704. package/dist/sensory/audio-scene.d.ts +24 -0
  1705. package/dist/sensory/audio-scene.js +74 -0
  1706. package/dist/sensory/camera-keyframe-policy.d.ts +11 -0
  1707. package/dist/sensory/camera-keyframe-policy.js +43 -0
  1708. package/dist/sensory/conversation-cues.d.ts +55 -0
  1709. package/dist/sensory/conversation-cues.js +140 -0
  1710. package/dist/sensory/domain-event-bridge.d.ts +7 -0
  1711. package/dist/sensory/domain-event-bridge.js +134 -0
  1712. package/dist/sensory/dreaming.d.ts +17 -0
  1713. package/dist/sensory/dreaming.js +52 -2
  1714. package/dist/sensory/elevenlabs-library.d.ts +91 -0
  1715. package/dist/sensory/elevenlabs-library.js +303 -0
  1716. package/dist/sensory/episodic-journal.d.ts +69 -0
  1717. package/dist/sensory/episodic-journal.js +240 -0
  1718. package/dist/sensory/error-watch-reaction.d.ts +41 -0
  1719. package/dist/sensory/error-watch-reaction.js +312 -0
  1720. package/dist/sensory/heartbeat-fallback.d.ts +46 -0
  1721. package/dist/sensory/heartbeat-fallback.js +171 -0
  1722. package/dist/sensory/heartbeat-scheduler.d.ts +6 -0
  1723. package/dist/sensory/heartbeat-scheduler.js +27 -7
  1724. package/dist/sensory/hybrid-reply.d.ts +132 -0
  1725. package/dist/sensory/hybrid-reply.js +1217 -0
  1726. package/dist/sensory/reactions.d.ts +6 -0
  1727. package/dist/sensory/reactions.js +10 -1
  1728. package/dist/sensory/respond-decider.d.ts +124 -0
  1729. package/dist/sensory/respond-decider.js +608 -0
  1730. package/dist/sensory/rule-templates.d.ts +30 -0
  1731. package/dist/sensory/rule-templates.js +100 -0
  1732. package/dist/sensory/schedule-emitter.d.ts +24 -0
  1733. package/dist/sensory/schedule-emitter.js +57 -0
  1734. package/dist/sensory/screen-reaction.js +15 -1
  1735. package/dist/sensory/semantic-vision-reaction.d.ts +36 -0
  1736. package/dist/sensory/semantic-vision-reaction.js +342 -0
  1737. package/dist/sensory/sensory-action-executor.d.ts +98 -0
  1738. package/dist/sensory/sensory-action-executor.js +473 -0
  1739. package/dist/sensory/sensory-bridge.d.ts +11 -1
  1740. package/dist/sensory/sensory-bridge.js +59 -7
  1741. package/dist/sensory/sensory-rules-engine.d.ts +81 -0
  1742. package/dist/sensory/sensory-rules-engine.js +434 -0
  1743. package/dist/sensory/sensory-status.d.ts +102 -0
  1744. package/dist/sensory/sensory-status.js +291 -0
  1745. package/dist/sensory/speech-engine-config.d.ts +68 -0
  1746. package/dist/sensory/speech-engine-config.js +157 -0
  1747. package/dist/sensory/speech-reaction.d.ts +183 -7
  1748. package/dist/sensory/speech-reaction.js +2141 -35
  1749. package/dist/sensory/speech-sanitizer.d.ts +12 -0
  1750. package/dist/sensory/speech-sanitizer.js +143 -0
  1751. package/dist/sensory/system-vitals-emitter.d.ts +130 -0
  1752. package/dist/sensory/system-vitals-emitter.js +514 -0
  1753. package/dist/sensory/tts-cache.d.ts +76 -0
  1754. package/dist/sensory/tts-cache.js +0 -0
  1755. package/dist/sensory/turn-detector.d.ts +28 -0
  1756. package/dist/sensory/turn-detector.js +57 -0
  1757. package/dist/sensory/vision-description-safety.d.ts +4 -0
  1758. package/dist/sensory/vision-description-safety.js +25 -0
  1759. package/dist/sensory/vision-reaction.d.ts +21 -7
  1760. package/dist/sensory/vision-reaction.js +209 -18
  1761. package/dist/sensory/voice-activity.d.ts +69 -0
  1762. package/dist/sensory/voice-activity.js +271 -0
  1763. package/dist/sensory/voice-clock.d.ts +19 -0
  1764. package/dist/sensory/voice-clock.js +111 -0
  1765. package/dist/sensory/voice-entrainment.d.ts +62 -0
  1766. package/dist/sensory/voice-entrainment.js +176 -0
  1767. package/dist/sensory/voice-interactions.d.ts +13 -0
  1768. package/dist/sensory/voice-interactions.js +258 -0
  1769. package/dist/sensory/voice-loop.d.ts +635 -0
  1770. package/dist/sensory/voice-loop.js +3638 -0
  1771. package/dist/sensory/voice-replay-lab.d.ts +32 -0
  1772. package/dist/sensory/voice-replay-lab.js +109 -0
  1773. package/dist/sensory/voice-stream.d.ts +121 -0
  1774. package/dist/sensory/voice-stream.js +598 -0
  1775. package/dist/sensory/voice-turn-coordinator.d.ts +88 -0
  1776. package/dist/sensory/voice-turn-coordinator.js +258 -0
  1777. package/dist/sensory/voice-turn-taking.d.ts +26 -0
  1778. package/dist/sensory/voice-turn-taking.js +59 -0
  1779. package/dist/server/agent-adapter.d.ts +49 -3
  1780. package/dist/server/agent-adapter.js +26 -4
  1781. package/dist/server/auth/device-session-context.d.ts +9 -0
  1782. package/dist/server/auth/device-session-context.js +13 -0
  1783. package/dist/server/auth/device-store.d.ts +89 -0
  1784. package/dist/server/auth/device-store.js +261 -0
  1785. package/dist/server/auth/device-token.d.ts +3 -0
  1786. package/dist/server/auth/device-token.js +6 -0
  1787. package/dist/server/auth/jwt.js +4 -0
  1788. package/dist/server/channel-a2a-bridge.js +1 -1
  1789. package/dist/server/exposure-diagnostic.d.ts +25 -0
  1790. package/dist/server/exposure-diagnostic.js +51 -0
  1791. package/dist/server/heartbeat-monitor.d.ts +13 -0
  1792. package/dist/server/heartbeat-monitor.js +8 -3
  1793. package/dist/server/http-agent-sessions.d.ts +32 -0
  1794. package/dist/server/http-agent-sessions.js +245 -0
  1795. package/dist/server/index.d.ts +3 -10
  1796. package/dist/server/index.js +941 -46
  1797. package/dist/server/mcp/approval-elicitation.d.ts +144 -0
  1798. package/dist/server/mcp/approval-elicitation.js +377 -0
  1799. package/dist/server/middleware/auth.d.ts +11 -0
  1800. package/dist/server/middleware/auth.js +57 -2
  1801. package/dist/server/middleware/error-handler.d.ts +5 -0
  1802. package/dist/server/middleware/error-handler.js +30 -4
  1803. package/dist/server/middleware/index.d.ts +1 -1
  1804. package/dist/server/middleware/index.js +1 -1
  1805. package/dist/server/mobile/album.d.ts +46 -0
  1806. package/dist/server/mobile/album.js +144 -0
  1807. package/dist/server/mobile/assets/app.js +3036 -0
  1808. package/dist/server/mobile/assets/emoji-data.js +508 -0
  1809. package/dist/server/mobile/assets/icon-192.png +0 -0
  1810. package/dist/server/mobile/assets/icon-512.png +0 -0
  1811. package/dist/server/mobile/assets/icon-96.png +0 -0
  1812. package/dist/server/mobile/assets/icon.svg +12 -0
  1813. package/dist/server/mobile/assets/index.html +261 -0
  1814. package/dist/server/mobile/assets/manifest.webmanifest +37 -0
  1815. package/dist/server/mobile/assets/styles.css +831 -0
  1816. package/dist/server/mobile/assets/sw.js +149 -0
  1817. package/dist/server/mobile/chat-extras.d.ts +9 -0
  1818. package/dist/server/mobile/chat-extras.js +24 -0
  1819. package/dist/server/mobile/index.d.ts +22 -0
  1820. package/dist/server/mobile/index.js +287 -0
  1821. package/dist/server/mobile/link-preview.d.ts +24 -0
  1822. package/dist/server/mobile/link-preview.js +139 -0
  1823. package/dist/server/mobile/push.d.ts +38 -0
  1824. package/dist/server/mobile/push.js +190 -0
  1825. package/dist/server/mobile/status.d.ts +23 -0
  1826. package/dist/server/mobile/status.js +107 -0
  1827. package/dist/server/mobile/telegram-forward.d.ts +14 -0
  1828. package/dist/server/mobile/telegram-forward.js +32 -0
  1829. package/dist/server/mobile/voice-note.d.ts +54 -0
  1830. package/dist/server/mobile/voice-note.js +305 -0
  1831. package/dist/server/origin-check.d.ts +4 -5
  1832. package/dist/server/origin-check.js +43 -8
  1833. package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
  1834. package/dist/server/routes/a2a-jsonrpc.js +66 -0
  1835. package/dist/server/routes/a2a-protocol.d.ts +12 -0
  1836. package/dist/server/routes/a2a-protocol.js +1 -1
  1837. package/dist/server/routes/canvas.d.ts +7 -1
  1838. package/dist/server/routes/canvas.js +138 -24
  1839. package/dist/server/routes/chat.js +340 -203
  1840. package/dist/server/routes/cognition.d.ts +3 -0
  1841. package/dist/server/routes/cognition.js +75 -0
  1842. package/dist/server/routes/device-auth.d.ts +4 -0
  1843. package/dist/server/routes/device-auth.js +40 -0
  1844. package/dist/server/routes/health.js +93 -81
  1845. package/dist/server/routes/index.d.ts +5 -1
  1846. package/dist/server/routes/index.js +5 -1
  1847. package/dist/server/routes/lessons.d.ts +13 -0
  1848. package/dist/server/routes/lessons.js +111 -0
  1849. package/dist/server/routes/memory.d.ts +12 -0
  1850. package/dist/server/routes/memory.js +191 -131
  1851. package/dist/server/routes/runs.d.ts +9 -0
  1852. package/dist/server/routes/runs.js +50 -0
  1853. package/dist/server/routes/sessions.js +8 -1
  1854. package/dist/server/routes/tools.js +93 -74
  1855. package/dist/server/routes/webhooks.js +56 -46
  1856. package/dist/server/tunnel-manager.js +21 -1
  1857. package/dist/server/types.d.ts +29 -1
  1858. package/dist/server/webhook-agent-queue.d.ts +12 -0
  1859. package/dist/server/webhook-agent-queue.js +58 -0
  1860. package/dist/server/websocket/cognition-bridge.d.ts +13 -0
  1861. package/dist/server/websocket/cognition-bridge.js +334 -0
  1862. package/dist/server/websocket/confirmation-bridge.d.ts +22 -0
  1863. package/dist/server/websocket/confirmation-bridge.js +178 -0
  1864. package/dist/server/websocket/desktop-handler.d.ts +17 -0
  1865. package/dist/server/websocket/desktop-handler.js +177 -16
  1866. package/dist/server/websocket/handler.d.ts +136 -1
  1867. package/dist/server/websocket/handler.js +942 -72
  1868. package/dist/server/websocket/index.d.ts +2 -1
  1869. package/dist/server/websocket/index.js +2 -1
  1870. package/dist/server/websocket/peer-rpc.d.ts +1 -1
  1871. package/dist/server/websocket/peer-rpc.js +25 -3
  1872. package/dist/services/prompt-builder.d.ts +49 -2
  1873. package/dist/services/prompt-builder.js +318 -99
  1874. package/dist/services/runtime-settings-context.d.ts +73 -0
  1875. package/dist/services/runtime-settings-context.js +69 -0
  1876. package/dist/sessions/timeline-snapshot.d.ts +35 -0
  1877. package/dist/sessions/timeline-snapshot.js +144 -0
  1878. package/dist/sessions/timeline.d.ts +33 -0
  1879. package/dist/sessions/timeline.js +85 -0
  1880. package/dist/shared/context-optimization-metadata.d.ts +29 -0
  1881. package/dist/shared/context-optimization-metadata.js +73 -0
  1882. package/dist/shared/engine-types.d.ts +26 -0
  1883. package/dist/skills/bash-injection.d.ts +2 -2
  1884. package/dist/skills/bash-injection.js +9 -2
  1885. package/dist/skills/bundled/code-explorer.skill.md +50 -0
  1886. package/dist/skills/bundled/file-edit.skill.md +62 -0
  1887. package/dist/skills/bundled/git-commit.skill.md +66 -0
  1888. package/dist/skills/bundled/pubcommander-control/SKILL.md +33 -0
  1889. package/dist/skills/bundled/pubcommander-control/agents/openai.yaml +4 -0
  1890. package/dist/skills/bundled/typescript-expert.skill.md +21 -0
  1891. package/dist/skills/bundled/weather.skill.md +48 -0
  1892. package/dist/skills/bundled/web-app-testing.skill.md +71 -0
  1893. package/dist/skills/bundled/web-search.skill.md +56 -0
  1894. package/dist/skills/executor.js +2 -0
  1895. package/dist/skills/hub.js +68 -44
  1896. package/dist/skills/index.d.ts +6 -3
  1897. package/dist/skills/index.js +19 -4
  1898. package/dist/skills/local-inventory.d.ts +21 -0
  1899. package/dist/skills/local-inventory.js +72 -0
  1900. package/dist/skills/parser.js +2 -0
  1901. package/dist/skills/registry.d.ts +52 -0
  1902. package/dist/skills/registry.js +291 -14
  1903. package/dist/skills/skill-exchange.d.ts +61 -0
  1904. package/dist/skills/skill-exchange.js +523 -0
  1905. package/dist/skills/skill-importer.d.ts +2 -2
  1906. package/dist/skills/skill-importer.js +76 -17
  1907. package/dist/skills/skill-loader.js +2 -2
  1908. package/dist/skills/skill-registry.js +3 -2
  1909. package/dist/skills/skill-signing.d.ts +20 -0
  1910. package/dist/skills/skill-signing.js +106 -0
  1911. package/dist/skills/skill-sources.d.ts +3 -2
  1912. package/dist/skills/skill-sources.js +5 -4
  1913. package/dist/skills/skill-usage-store.d.ts +49 -0
  1914. package/dist/skills/skill-usage-store.js +121 -0
  1915. package/dist/skills/types.d.ts +4 -0
  1916. package/dist/spec/spec-store.js +7 -6
  1917. package/dist/speculative/shadow-workspace.d.ts +79 -0
  1918. package/dist/speculative/shadow-workspace.js +500 -0
  1919. package/dist/talk-mode/index.d.ts +4 -4
  1920. package/dist/talk-mode/index.js +3 -3
  1921. package/dist/talk-mode/providers/elevenlabs-client.d.ts +48 -0
  1922. package/dist/talk-mode/providers/elevenlabs-client.js +117 -0
  1923. package/dist/talk-mode/providers/elevenlabs.js +12 -23
  1924. package/dist/talk-mode/providers/index.d.ts +3 -1
  1925. package/dist/talk-mode/providers/index.js +2 -0
  1926. package/dist/talk-mode/providers/pocket-tts.d.ts +88 -0
  1927. package/dist/talk-mode/providers/pocket-tts.js +334 -0
  1928. package/dist/talk-mode/providers/voicebox-tts.d.ts +24 -0
  1929. package/dist/talk-mode/providers/voicebox-tts.js +120 -0
  1930. package/dist/talk-mode/types.d.ts +39 -1
  1931. package/dist/tasks/background-tasks.js +22 -5
  1932. package/dist/telemetry/otel-tracer.js +5 -2
  1933. package/dist/templates/design-system-apply.d.ts +23 -0
  1934. package/dist/templates/design-system-apply.js +100 -0
  1935. package/dist/templates/project-scaffolding.d.ts +2 -0
  1936. package/dist/templates/project-scaffolding.js +616 -1
  1937. package/dist/testing/ai-integration-tests.js +8 -5
  1938. package/dist/themes/theme-manager.d.ts +12 -0
  1939. package/dist/themes/theme-manager.js +38 -6
  1940. package/dist/themes/theme-schema.d.ts +42 -42
  1941. package/dist/tools/a2a-call-tool.d.ts +34 -0
  1942. package/dist/tools/a2a-call-tool.js +75 -0
  1943. package/dist/tools/advanced/multi-file-editor.js +3 -1
  1944. package/dist/tools/advanced/operation-history.js +2 -1
  1945. package/dist/tools/app-server-tool.d.ts +66 -0
  1946. package/dist/tools/app-server-tool.js +423 -0
  1947. package/dist/tools/apply-patch.d.ts +31 -1
  1948. package/dist/tools/apply-patch.js +214 -19
  1949. package/dist/tools/authored-tools-manifest-2.d.ts +17 -0
  1950. package/dist/tools/authored-tools-manifest-2.js +133 -0
  1951. package/dist/tools/authored-tools-manifest.d.ts +15 -0
  1952. package/dist/tools/authored-tools-manifest.js +13 -0
  1953. package/dist/tools/bash/bash-tool.d.ts +36 -3
  1954. package/dist/tools/bash/bash-tool.js +240 -100
  1955. package/dist/tools/bash/command-validator.d.ts +1 -1
  1956. package/dist/tools/bash/command-validator.js +115 -40
  1957. package/dist/tools/bash/execution-policy.d.ts +42 -0
  1958. package/dist/tools/bash/execution-policy.js +278 -0
  1959. package/dist/tools/bash/security-patterns.js +43 -5
  1960. package/dist/tools/bash/streaming-executor.d.ts +2 -1
  1961. package/dist/tools/bash/streaming-executor.js +172 -78
  1962. package/dist/tools/build-project-tool.d.ts +25 -0
  1963. package/dist/tools/build-project-tool.js +30 -0
  1964. package/dist/tools/bundle-analyze-tool.d.ts +28 -0
  1965. package/dist/tools/bundle-analyze-tool.js +39 -0
  1966. package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
  1967. package/dist/tools/code-exec-preflight-runner.js +254 -0
  1968. package/dist/tools/code-exec-preflight.d.ts +44 -0
  1969. package/dist/tools/code-exec-preflight.js +501 -0
  1970. package/dist/tools/code-exec-tool.d.ts +102 -23
  1971. package/dist/tools/code-exec-tool.js +741 -110
  1972. package/dist/tools/code-explorer-tool.d.ts +1 -1
  1973. package/dist/tools/code-explorer-tool.js +25 -1
  1974. package/dist/tools/code-review.js +5 -0
  1975. package/dist/tools/code-stats-tool.d.ts +43 -0
  1976. package/dist/tools/code-stats-tool.js +70 -0
  1977. package/dist/tools/codebase-replace-tool.js +2 -2
  1978. package/dist/tools/comfy-recipe-tool.d.ts +37 -0
  1979. package/dist/tools/comfy-recipe-tool.js +693 -0
  1980. package/dist/tools/comment-watcher.js +29 -12
  1981. package/dist/tools/community-search.d.ts +93 -0
  1982. package/dist/tools/community-search.js +334 -0
  1983. package/dist/tools/computer-control-harness.js +5 -2
  1984. package/dist/tools/computer-control-tool.d.ts +8 -0
  1985. package/dist/tools/computer-control-tool.js +71 -26
  1986. package/dist/tools/context-expand-tool.d.ts +19 -0
  1987. package/dist/tools/context-expand-tool.js +132 -0
  1988. package/dist/tools/core-code-inspection.d.ts +56 -0
  1989. package/dist/tools/core-code-inspection.js +86 -0
  1990. package/dist/tools/create-skill-tool.d.ts +2 -3
  1991. package/dist/tools/create-skill-tool.js +16 -75
  1992. package/dist/tools/cronjob-tool.d.ts +1 -0
  1993. package/dist/tools/cronjob-tool.js +1 -0
  1994. package/dist/tools/csv/csv-parse.d.ts +19 -0
  1995. package/dist/tools/csv/csv-parse.js +132 -0
  1996. package/dist/tools/csv/csv-wiring.d.ts +8 -0
  1997. package/dist/tools/csv/csv-wiring.js +9 -0
  1998. package/dist/tools/csv-analyze-tool.d.ts +11 -0
  1999. package/dist/tools/csv-analyze-tool.js +114 -0
  2000. package/dist/tools/csv-preview-tool.d.ts +34 -0
  2001. package/dist/tools/csv-preview-tool.js +69 -0
  2002. package/dist/tools/db-migration.js +11 -3
  2003. package/dist/tools/deep-research-tool.d.ts +68 -0
  2004. package/dist/tools/deep-research-tool.js +257 -0
  2005. package/dist/tools/deferred-schema-state.d.ts +19 -0
  2006. package/dist/tools/deferred-schema-state.js +33 -0
  2007. package/dist/tools/dep-inspect-tool.d.ts +32 -0
  2008. package/dist/tools/dep-inspect-tool.js +74 -0
  2009. package/dist/tools/design-system-tool.d.ts +10 -0
  2010. package/dist/tools/design-system-tool.js +95 -0
  2011. package/dist/tools/device-tool.d.ts +2 -1
  2012. package/dist/tools/device-tool.js +21 -0
  2013. package/dist/tools/diagram-tool.js +8 -2
  2014. package/dist/tools/diff-files-tool.d.ts +28 -0
  2015. package/dist/tools/diff-files-tool.js +49 -0
  2016. package/dist/tools/enhanced-search.js +32 -16
  2017. package/dist/tools/env-doctor-tool.d.ts +32 -0
  2018. package/dist/tools/env-doctor-tool.js +55 -0
  2019. package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
  2020. package/dist/tools/execute-code-rpc-invoker.js +36 -20
  2021. package/dist/tools/execute-code-runner.d.ts +23 -0
  2022. package/dist/tools/execute-code-runner.js +169 -25
  2023. package/dist/tools/extension-forge-tool.d.ts +29 -0
  2024. package/dist/tools/extension-forge-tool.js +288 -0
  2025. package/dist/tools/fetch-tool.js +7 -3
  2026. package/dist/tools/file-search-tool.d.ts +41 -0
  2027. package/dist/tools/file-search-tool.js +99 -0
  2028. package/dist/tools/fleet-room-tool.d.ts +104 -0
  2029. package/dist/tools/fleet-room-tool.js +124 -0
  2030. package/dist/tools/format-project-tool.d.ts +28 -0
  2031. package/dist/tools/format-project-tool.js +45 -0
  2032. package/dist/tools/git-summary-tool.d.ts +36 -0
  2033. package/dist/tools/git-summary-tool.js +65 -0
  2034. package/dist/tools/git-tool.js +16 -4
  2035. package/dist/tools/gpu-avatar-delivery.d.ts +16 -0
  2036. package/dist/tools/gpu-avatar-delivery.js +67 -0
  2037. package/dist/tools/gpu-media-worker.d.ts +96 -0
  2038. package/dist/tools/gpu-media-worker.js +363 -0
  2039. package/dist/tools/gui-tool.js +92 -13
  2040. package/dist/tools/http-probe-tool.d.ts +25 -0
  2041. package/dist/tools/http-probe-tool.js +40 -0
  2042. package/dist/tools/image-tool.js +29 -16
  2043. package/dist/tools/index.d.ts +6 -1
  2044. package/dist/tools/index.js +5 -0
  2045. package/dist/tools/integration-tool-hints.d.ts +4 -0
  2046. package/dist/tools/integration-tool-hints.js +19 -0
  2047. package/dist/tools/interactive-bash.d.ts +29 -1
  2048. package/dist/tools/interactive-bash.js +141 -21
  2049. package/dist/tools/json-query-tool.d.ts +30 -0
  2050. package/dist/tools/json-query-tool.js +49 -0
  2051. package/dist/tools/license-check-tool.d.ts +22 -0
  2052. package/dist/tools/license-check-tool.js +43 -0
  2053. package/dist/tools/lint-project-tool.d.ts +52 -0
  2054. package/dist/tools/lint-project-tool.js +143 -0
  2055. package/dist/tools/list-peers-tool.d.ts +3 -0
  2056. package/dist/tools/list-peers-tool.js +11 -4
  2057. package/dist/tools/local-binary-launch.d.ts +14 -0
  2058. package/dist/tools/local-binary-launch.js +63 -0
  2059. package/dist/tools/lsp-navigation-tools.d.ts +87 -0
  2060. package/dist/tools/lsp-navigation-tools.js +470 -0
  2061. package/dist/tools/markdown-convert.d.ts +51 -0
  2062. package/dist/tools/markdown-convert.js +159 -0
  2063. package/dist/tools/media-generation-tool.d.ts +105 -1
  2064. package/dist/tools/media-generation-tool.js +1892 -18
  2065. package/dist/tools/meeting-notes-tool.d.ts +25 -0
  2066. package/dist/tools/meeting-notes-tool.js +240 -0
  2067. package/dist/tools/merge-conflict-tool.js +54 -26
  2068. package/dist/tools/metadata.d.ts +3 -1
  2069. package/dist/tools/metadata.js +819 -16
  2070. package/dist/tools/mixture-of-agents-tool.d.ts +7 -0
  2071. package/dist/tools/mixture-of-agents-tool.js +230 -31
  2072. package/dist/tools/multi-edit.js +42 -18
  2073. package/dist/tools/ocr-tool.d.ts +43 -0
  2074. package/dist/tools/ocr-tool.js +24 -18
  2075. package/dist/tools/omission-placeholder-detector.js +12 -6
  2076. package/dist/tools/paper-qa-tool.d.ts +73 -0
  2077. package/dist/tools/paper-qa-tool.js +313 -0
  2078. package/dist/tools/peer-chain-tool.js +4 -1
  2079. package/dist/tools/peer-delegate-tool.d.ts +2 -0
  2080. package/dist/tools/peer-delegate-tool.js +11 -1
  2081. package/dist/tools/port-check-tool.d.ts +31 -0
  2082. package/dist/tools/port-check-tool.js +25 -0
  2083. package/dist/tools/project-map-tool.d.ts +39 -0
  2084. package/dist/tools/project-map-tool.js +128 -0
  2085. package/dist/tools/ragchat-tool.d.ts +39 -0
  2086. package/dist/tools/ragchat-tool.js +105 -0
  2087. package/dist/tools/register-tool-handler.d.ts +2 -2
  2088. package/dist/tools/register-tool-handler.js +7 -4
  2089. package/dist/tools/registry/advanced-tools.js +1 -1
  2090. package/dist/tools/registry/attention-tools.d.ts +3 -3
  2091. package/dist/tools/registry/attention-tools.js +14 -10
  2092. package/dist/tools/registry/authored-extra-tools.d.ts +20 -0
  2093. package/dist/tools/registry/authored-extra-tools.js +147 -0
  2094. package/dist/tools/registry/bash-tools.d.ts +2 -2
  2095. package/dist/tools/registry/bash-tools.js +7 -3
  2096. package/dist/tools/registry/browser-operator-tools.d.ts +1 -1
  2097. package/dist/tools/registry/browser-operator-tools.js +17 -3
  2098. package/dist/tools/registry/browser-tools.d.ts +1 -1
  2099. package/dist/tools/registry/browser-tools.js +38 -33
  2100. package/dist/tools/registry/code-explorer-tools.js +6 -1
  2101. package/dist/tools/registry/comfy-recipe-tools.d.ts +2 -0
  2102. package/dist/tools/registry/comfy-recipe-tools.js +5 -0
  2103. package/dist/tools/registry/context-expand-tools.d.ts +3 -0
  2104. package/dist/tools/registry/context-expand-tools.js +6 -0
  2105. package/dist/tools/registry/csv-tools.d.ts +12 -0
  2106. package/dist/tools/registry/csv-tools.js +62 -0
  2107. package/dist/tools/registry/delegate-agent-tools.d.ts +64 -0
  2108. package/dist/tools/registry/delegate-agent-tools.js +206 -0
  2109. package/dist/tools/registry/design-tools.d.ts +12 -0
  2110. package/dist/tools/registry/design-tools.js +80 -0
  2111. package/dist/tools/registry/fleet-tools.d.ts +9 -0
  2112. package/dist/tools/registry/fleet-tools.js +28 -1
  2113. package/dist/tools/registry/index.d.ts +25 -5
  2114. package/dist/tools/registry/index.js +80 -6
  2115. package/dist/tools/registry/interactive-adapters.d.ts +37 -0
  2116. package/dist/tools/registry/interactive-adapters.js +116 -0
  2117. package/dist/tools/registry/knowledge-tools.d.ts +2 -2
  2118. package/dist/tools/registry/knowledge-tools.js +2 -2
  2119. package/dist/tools/registry/lessons-tools.d.ts +7 -0
  2120. package/dist/tools/registry/lessons-tools.js +43 -3
  2121. package/dist/tools/registry/lsp-tools.d.ts +3 -3
  2122. package/dist/tools/registry/lsp-tools.js +9 -3
  2123. package/dist/tools/registry/meeting-tools.d.ts +3 -0
  2124. package/dist/tools/registry/meeting-tools.js +6 -0
  2125. package/dist/tools/registry/memory-tools.d.ts +1 -1
  2126. package/dist/tools/registry/memory-tools.js +40 -28
  2127. package/dist/tools/registry/misc-tools.d.ts +1 -1
  2128. package/dist/tools/registry/misc-tools.js +7 -4
  2129. package/dist/tools/registry/moa-tools.js +13 -0
  2130. package/dist/tools/registry/multimodal-tools.d.ts +73 -1
  2131. package/dist/tools/registry/multimodal-tools.js +581 -4
  2132. package/dist/tools/registry/process-tools.d.ts +16 -0
  2133. package/dist/tools/registry/process-tools.js +136 -1
  2134. package/dist/tools/registry/remind-tools.d.ts +23 -0
  2135. package/dist/tools/registry/remind-tools.js +120 -0
  2136. package/dist/tools/registry/research-tools.d.ts +18 -0
  2137. package/dist/tools/registry/research-tools.js +12 -0
  2138. package/dist/tools/registry/search-tools.d.ts +6 -6
  2139. package/dist/tools/registry/search-tools.js +30 -17
  2140. package/dist/tools/registry/secrets-tools.d.ts +33 -0
  2141. package/dist/tools/registry/secrets-tools.js +71 -0
  2142. package/dist/tools/registry/self-describe-tools.d.ts +23 -0
  2143. package/dist/tools/registry/self-describe-tools.js +169 -0
  2144. package/dist/tools/registry/self-evolution-tools.d.ts +13 -0
  2145. package/dist/tools/registry/self-evolution-tools.js +97 -0
  2146. package/dist/tools/registry/text-editor-tools.d.ts +26 -4
  2147. package/dist/tools/registry/text-editor-tools.js +102 -6
  2148. package/dist/tools/registry/tool-alias-map.d.ts +8 -0
  2149. package/dist/tools/registry/tool-alias-map.js +47 -0
  2150. package/dist/tools/registry/tool-aliases.d.ts +3 -4
  2151. package/dist/tools/registry/tool-aliases.js +12 -48
  2152. package/dist/tools/registry/types.d.ts +5 -0
  2153. package/dist/tools/registry/verify-tools.d.ts +50 -0
  2154. package/dist/tools/registry/verify-tools.js +136 -0
  2155. package/dist/tools/registry/video-studio-tools.d.ts +13 -0
  2156. package/dist/tools/registry/video-studio-tools.js +21 -0
  2157. package/dist/tools/registry/vision-tools.d.ts +15 -1
  2158. package/dist/tools/registry/vision-tools.js +252 -27
  2159. package/dist/tools/registry/web-test-tool.d.ts +66 -0
  2160. package/dist/tools/registry/web-test-tool.js +321 -0
  2161. package/dist/tools/registry/web-tools.d.ts +43 -0
  2162. package/dist/tools/registry/web-tools.js +281 -1
  2163. package/dist/tools/resource-catalog-tool.d.ts +38 -0
  2164. package/dist/tools/resource-catalog-tool.js +46 -0
  2165. package/dist/tools/review-gate-helper.d.ts +44 -0
  2166. package/dist/tools/review-gate-helper.js +110 -0
  2167. package/dist/tools/route-peer-tool.js +62 -11
  2168. package/dist/tools/sbom-generate-tool.d.ts +22 -0
  2169. package/dist/tools/sbom-generate-tool.js +41 -0
  2170. package/dist/tools/scaffold-app-tool.d.ts +48 -0
  2171. package/dist/tools/scaffold-app-tool.js +144 -0
  2172. package/dist/tools/screenshot-tool.js +5 -2
  2173. package/dist/tools/search.d.ts +17 -0
  2174. package/dist/tools/search.js +31 -8
  2175. package/dist/tools/self-describe.d.ts +73 -0
  2176. package/dist/tools/self-describe.js +298 -0
  2177. package/dist/tools/skills-inspection-tool.js +30 -1
  2178. package/dist/tools/stock-quote.d.ts +67 -0
  2179. package/dist/tools/stock-quote.js +645 -0
  2180. package/dist/tools/stream-tool-output.d.ts +3 -0
  2181. package/dist/tools/stream-tool-output.js +33 -0
  2182. package/dist/tools/submit-plan-tool.js +1 -1
  2183. package/dist/tools/test-runner-tool.d.ts +38 -0
  2184. package/dist/tools/test-runner-tool.js +96 -0
  2185. package/dist/tools/text-editor.js +91 -8
  2186. package/dist/tools/text-to-speech-tool.d.ts +1 -1
  2187. package/dist/tools/text-to-speech-tool.js +48 -2
  2188. package/dist/tools/todo-scan-tool.d.ts +39 -0
  2189. package/dist/tools/todo-scan-tool.js +53 -0
  2190. package/dist/tools/tool-call-scheduler.d.ts +10 -0
  2191. package/dist/tools/tool-call-scheduler.js +58 -0
  2192. package/dist/tools/tool-effect.d.ts +5 -0
  2193. package/dist/tools/tool-effect.js +26 -0
  2194. package/dist/tools/tool-manager.js +8 -0
  2195. package/dist/tools/tool-search.d.ts +12 -11
  2196. package/dist/tools/tool-search.js +77 -28
  2197. package/dist/tools/tool-selector.d.ts +7 -0
  2198. package/dist/tools/tool-selector.js +27 -13
  2199. package/dist/tools/tools-md-generator.js +2 -2
  2200. package/dist/tools/types.d.ts +22 -0
  2201. package/dist/tools/types.js +5 -1
  2202. package/dist/tools/video/approved-media-source.d.ts +8 -0
  2203. package/dist/tools/video/approved-media-source.js +59 -0
  2204. package/dist/tools/video/book-manuscript-source.d.ts +49 -0
  2205. package/dist/tools/video/book-manuscript-source.js +263 -0
  2206. package/dist/tools/video/character-in-location.d.ts +42 -0
  2207. package/dist/tools/video/character-in-location.js +161 -0
  2208. package/dist/tools/video/cinematic-trailer-plan.d.ts +203 -0
  2209. package/dist/tools/video/cinematic-trailer-plan.js +498 -0
  2210. package/dist/tools/video/cloud-understand.d.ts +91 -0
  2211. package/dist/tools/video/cloud-understand.js +214 -0
  2212. package/dist/tools/video/comfy-client.d.ts +38 -0
  2213. package/dist/tools/video/comfy-client.js +227 -0
  2214. package/dist/tools/video/comfy-workflow-template.d.ts +70 -0
  2215. package/dist/tools/video/comfy-workflow-template.js +194 -0
  2216. package/dist/tools/video/describe-frame.d.ts +36 -0
  2217. package/dist/tools/video/describe-frame.js +68 -0
  2218. package/dist/tools/video/film-assemble.d.ts +234 -0
  2219. package/dist/tools/video/film-assemble.js +959 -0
  2220. package/dist/tools/video/film-project.d.ts +152 -0
  2221. package/dist/tools/video/film-project.js +312 -0
  2222. package/dist/tools/video/frame-dedup.d.ts +46 -0
  2223. package/dist/tools/video/frame-dedup.js +110 -0
  2224. package/dist/tools/video/frame-sample.d.ts +72 -0
  2225. package/dist/tools/video/frame-sample.js +238 -0
  2226. package/dist/tools/video/google-flow-driver.d.ts +131 -0
  2227. package/dist/tools/video/google-flow-driver.js +312 -0
  2228. package/dist/tools/video/google-flow-handoff.d.ts +75 -0
  2229. package/dist/tools/video/google-flow-handoff.js +121 -0
  2230. package/dist/tools/video/google-flow-plan-export.d.ts +21 -0
  2231. package/dist/tools/video/google-flow-plan-export.js +123 -0
  2232. package/dist/tools/video/google-flow-result-import.d.ts +92 -0
  2233. package/dist/tools/video/google-flow-result-import.js +297 -0
  2234. package/dist/tools/video/hybrid-video-router.d.ts +34 -0
  2235. package/dist/tools/video/hybrid-video-router.js +113 -0
  2236. package/dist/tools/video/localized-media.d.ts +50 -0
  2237. package/dist/tools/video/localized-media.js +128 -0
  2238. package/dist/tools/video/long-form-plan.d.ts +37 -0
  2239. package/dist/tools/video/long-form-plan.js +83 -0
  2240. package/dist/tools/video/long-form-production.d.ts +49 -0
  2241. package/dist/tools/video/long-form-production.js +168 -0
  2242. package/dist/tools/video/long-transcribe.d.ts +94 -0
  2243. package/dist/tools/video/long-transcribe.js +206 -0
  2244. package/dist/tools/video/media-fetch.d.ts +94 -0
  2245. package/dist/tools/video/media-fetch.js +311 -0
  2246. package/dist/tools/video/mermaid-render.d.ts +34 -0
  2247. package/dist/tools/video/mermaid-render.js +127 -0
  2248. package/dist/tools/video/narration.d.ts +96 -0
  2249. package/dist/tools/video/narration.js +505 -0
  2250. package/dist/tools/video/native-fashion-defects.d.ts +27 -0
  2251. package/dist/tools/video/native-fashion-defects.js +80 -0
  2252. package/dist/tools/video/scene-render.d.ts +108 -0
  2253. package/dist/tools/video/scene-render.js +418 -0
  2254. package/dist/tools/video/subtitles.d.ts +49 -0
  2255. package/dist/tools/video/subtitles.js +116 -0
  2256. package/dist/tools/video/video-ckg.d.ts +123 -0
  2257. package/dist/tools/video/video-ckg.js +188 -0
  2258. package/dist/tools/video/video-research-card.d.ts +58 -0
  2259. package/dist/tools/video/video-research-card.js +468 -0
  2260. package/dist/tools/video/video-understanding.d.ts +159 -0
  2261. package/dist/tools/video/video-understanding.js +534 -0
  2262. package/dist/tools/video/visual-gate-report.d.ts +139 -0
  2263. package/dist/tools/video/visual-gate-report.js +379 -0
  2264. package/dist/tools/video/voice-rights-registry.d.ts +13 -0
  2265. package/dist/tools/video/voice-rights-registry.js +142 -0
  2266. package/dist/tools/video/youtube-captions.d.ts +51 -0
  2267. package/dist/tools/video/youtube-captions.js +102 -0
  2268. package/dist/tools/video/youtube-master-quality.d.ts +163 -0
  2269. package/dist/tools/video/youtube-master-quality.js +379 -0
  2270. package/dist/tools/video/youtube-storyboard.d.ts +43 -0
  2271. package/dist/tools/video/youtube-storyboard.js +194 -0
  2272. package/dist/tools/video-flow-handoff-tool.d.ts +181 -0
  2273. package/dist/tools/video-flow-handoff-tool.js +261 -0
  2274. package/dist/tools/video-long-form-plan-tool.d.ts +31 -0
  2275. package/dist/tools/video-long-form-plan-tool.js +87 -0
  2276. package/dist/tools/video-quality-gate-tool.d.ts +97 -0
  2277. package/dist/tools/video-quality-gate-tool.js +189 -0
  2278. package/dist/tools/video-route-tool.d.ts +128 -0
  2279. package/dist/tools/video-route-tool.js +126 -0
  2280. package/dist/tools/video-studio-tool-helpers.d.ts +22 -0
  2281. package/dist/tools/video-studio-tool-helpers.js +128 -0
  2282. package/dist/tools/video-trailer-plan-tool.d.ts +60 -0
  2283. package/dist/tools/video-trailer-plan-tool.js +103 -0
  2284. package/dist/tools/vision/blender-render.d.ts +76 -0
  2285. package/dist/tools/vision/blender-render.js +121 -0
  2286. package/dist/tools/vision/image-processor.d.ts +1 -0
  2287. package/dist/tools/vision/image-processor.js +8 -1
  2288. package/dist/tools/vision/load-sharp.d.ts +53 -0
  2289. package/dist/tools/vision/load-sharp.js +48 -0
  2290. package/dist/tools/vision/mean-luma.d.ts +4 -0
  2291. package/dist/tools/vision/mean-luma.js +189 -0
  2292. package/dist/tools/vision/object-detection.d.ts +90 -0
  2293. package/dist/tools/vision/object-detection.js +403 -0
  2294. package/dist/tools/vision/vision-analysis.js +2 -1
  2295. package/dist/tools/weather.d.ts +33 -0
  2296. package/dist/tools/weather.js +183 -0
  2297. package/dist/tools/web-scrape-tool.d.ts +63 -0
  2298. package/dist/tools/web-scrape-tool.js +318 -0
  2299. package/dist/tools/web-search.d.ts +67 -6
  2300. package/dist/tools/web-search.js +231 -68
  2301. package/dist/tools/workspace-tools.d.ts +39 -0
  2302. package/dist/tools/workspace-tools.js +347 -0
  2303. package/dist/tracks/track-manager.js +9 -8
  2304. package/dist/triggers/webhook-trigger.js +10 -18
  2305. package/dist/ui/components/ChatHistory.d.ts +1 -1
  2306. package/dist/ui/components/ChatHistory.js +14 -2
  2307. package/dist/ui/components/ChatInput.d.ts +2 -1
  2308. package/dist/ui/components/ChatInput.js +42 -88
  2309. package/dist/ui/components/ChatInterface.d.ts +4 -2
  2310. package/dist/ui/components/ChatInterface.js +32 -64
  2311. package/dist/ui/components/CommandSuggestions.d.ts +0 -1
  2312. package/dist/ui/components/CommandSuggestions.js +4 -6
  2313. package/dist/ui/components/FileAutocomplete.d.ts +6 -1
  2314. package/dist/ui/components/FileAutocomplete.js +137 -77
  2315. package/dist/ui/components/FuzzyPicker.d.ts +5 -0
  2316. package/dist/ui/components/FuzzyPicker.js +1 -1
  2317. package/dist/ui/components/KeyboardHelp.js +8 -6
  2318. package/dist/ui/components/ModelSelection.js +6 -5
  2319. package/dist/ui/components/StatusBar.d.ts +2 -1
  2320. package/dist/ui/components/StatusBar.js +8 -7
  2321. package/dist/ui/context/theme-context.js +3 -2
  2322. package/dist/ui/http-server/server.js +2 -2
  2323. package/dist/ui/index.d.ts +1 -1
  2324. package/dist/ui/index.js +1 -1
  2325. package/dist/ui/utils/markdown-renderer.js +4 -13
  2326. package/dist/undo/checkpoint-manager.d.ts +3 -0
  2327. package/dist/undo/checkpoint-manager.js +6 -1
  2328. package/dist/utils/approval-pattern-tracker.js +5 -13
  2329. package/dist/utils/ascii-banner.d.ts +1 -1
  2330. package/dist/utils/ascii-banner.js +1 -1
  2331. package/dist/utils/atomic-write.d.ts +99 -0
  2332. package/dist/utils/atomic-write.js +559 -0
  2333. package/dist/utils/audio-player.d.ts +1 -0
  2334. package/dist/utils/audio-player.js +7 -3
  2335. package/dist/utils/autonomy-manager.js +12 -3
  2336. package/dist/utils/bounded-output.d.ts +16 -0
  2337. package/dist/utils/bounded-output.js +63 -0
  2338. package/dist/utils/command-exists.d.ts +14 -0
  2339. package/dist/utils/command-exists.js +43 -0
  2340. package/dist/utils/config-validation/schema.d.ts +13 -13
  2341. package/dist/utils/config-validation/schema.js +33 -1
  2342. package/dist/utils/confirmation-service.d.ts +60 -1
  2343. package/dist/utils/confirmation-service.js +204 -41
  2344. package/dist/utils/cost-tracker.d.ts +66 -1
  2345. package/dist/utils/cost-tracker.js +100 -17
  2346. package/dist/utils/device-store-file.d.ts +4 -0
  2347. package/dist/utils/device-store-file.js +40 -0
  2348. package/dist/utils/export-manager.d.ts +9 -0
  2349. package/dist/utils/export-manager.js +19 -0
  2350. package/dist/utils/first-use-hints.d.ts +25 -0
  2351. package/dist/utils/first-use-hints.js +58 -0
  2352. package/dist/utils/history-manager.js +7 -8
  2353. package/dist/utils/init-project.js +17 -13
  2354. package/dist/utils/input-validation/index.d.ts +16 -16
  2355. package/dist/utils/installation-id.js +7 -22
  2356. package/dist/utils/interactive-setup.js +7 -12
  2357. package/dist/utils/json-salvage.d.ts +22 -0
  2358. package/dist/utils/json-salvage.js +91 -0
  2359. package/dist/utils/llm-retry.js +3 -0
  2360. package/dist/utils/logger.d.ts +1 -0
  2361. package/dist/utils/logger.js +16 -5
  2362. package/dist/utils/model-router.js +8 -12
  2363. package/dist/utils/model-utils.js +14 -0
  2364. package/dist/utils/output-sanitizer.d.ts +4 -8
  2365. package/dist/utils/output-sanitizer.js +10 -10
  2366. package/dist/utils/output-schema-validator.d.ts +7 -0
  2367. package/dist/utils/output-schema-validator.js +25 -1
  2368. package/dist/utils/response-cache.js +11 -22
  2369. package/dist/utils/ripgrep-path.d.ts +11 -0
  2370. package/dist/utils/ripgrep-path.js +60 -0
  2371. package/dist/utils/semantic-cache.js +4 -7
  2372. package/dist/utils/settings-manager.d.ts +27 -4
  2373. package/dist/utils/settings-manager.js +51 -39
  2374. package/dist/utils/shell-completions.js +25 -34
  2375. package/dist/utils/shell-configuration.d.ts +39 -0
  2376. package/dist/utils/shell-configuration.js +106 -0
  2377. package/dist/utils/stream-stall-guard.d.ts +39 -0
  2378. package/dist/utils/stream-stall-guard.js +123 -0
  2379. package/dist/utils/subprocess-env.d.ts +15 -0
  2380. package/dist/utils/subprocess-env.js +112 -0
  2381. package/dist/utils/telegram-api-base.d.ts +9 -0
  2382. package/dist/utils/telegram-api-base.js +15 -0
  2383. package/dist/utils/telemetry-config.js +16 -13
  2384. package/dist/utils/update-notifier.js +20 -27
  2385. package/dist/utils/validators.js +1 -2
  2386. package/dist/versioning/config-migrator.js +3 -2
  2387. package/dist/versioning/migration-manager.js +5 -4
  2388. package/dist/versioning/version-detector.js +2 -1
  2389. package/dist/vision-train/assets.d.ts +37 -0
  2390. package/dist/vision-train/assets.js +97 -0
  2391. package/dist/vision-train/ckg-publish.d.ts +26 -0
  2392. package/dist/vision-train/ckg-publish.js +36 -0
  2393. package/dist/vision-train/coco-to-labels.d.ts +63 -0
  2394. package/dist/vision-train/coco-to-labels.js +63 -0
  2395. package/dist/vision-train/curriculum.d.ts +29 -0
  2396. package/dist/vision-train/curriculum.js +49 -0
  2397. package/dist/vision-train/engine.d.ts +56 -0
  2398. package/dist/vision-train/engine.js +55 -0
  2399. package/dist/vision-train/report.d.ts +15 -0
  2400. package/dist/vision-train/report.js +72 -0
  2401. package/dist/vision-train/scorer.d.ts +67 -0
  2402. package/dist/vision-train/scorer.js +114 -0
  2403. package/dist/vision-train/yolo-labels.d.ts +13 -0
  2404. package/dist/vision-train/yolo-labels.js +66 -0
  2405. package/dist/voice/elevenlabs-voice.d.ts +53 -0
  2406. package/dist/voice/elevenlabs-voice.js +412 -0
  2407. package/dist/voice/kyutai-local-voice.d.ts +33 -0
  2408. package/dist/voice/kyutai-local-voice.js +198 -0
  2409. package/dist/voice/local-tts.d.ts +106 -3
  2410. package/dist/voice/local-tts.js +672 -35
  2411. package/dist/voice/local-whisper.d.ts +11 -0
  2412. package/dist/voice/local-whisper.js +18 -2
  2413. package/dist/voice/pcm-edges.d.ts +43 -0
  2414. package/dist/voice/pcm-edges.js +210 -0
  2415. package/dist/voice/perceived-latency-benchmark.d.ts +56 -0
  2416. package/dist/voice/perceived-latency-benchmark.js +218 -0
  2417. package/dist/voice/tts-bank.d.ts +72 -0
  2418. package/dist/voice/tts-bank.js +172 -0
  2419. package/dist/voice/tts-latency-benchmark.d.ts +36 -0
  2420. package/dist/voice/tts-latency-benchmark.js +76 -0
  2421. package/dist/voice/tts-volume.d.ts +86 -0
  2422. package/dist/voice/tts-volume.js +378 -0
  2423. package/dist/voice/two-speed-voice.d.ts +19 -0
  2424. package/dist/voice/two-speed-voice.js +40 -0
  2425. package/dist/voice/voice-to-code.js +1 -1
  2426. package/dist/voice/voicebox-tts.d.ts +150 -0
  2427. package/dist/voice/voicebox-tts.js +669 -0
  2428. package/dist/voice/wake-word.d.ts +2 -0
  2429. package/dist/voice/wake-word.js +30 -10
  2430. package/dist/webhooks/webhook-manager.js +10 -18
  2431. package/dist/widgets/auto-widget.d.ts +27 -0
  2432. package/dist/widgets/auto-widget.js +111 -0
  2433. package/dist/widgets/canvas-publish.d.ts +15 -0
  2434. package/dist/widgets/canvas-publish.js +39 -0
  2435. package/dist/widgets/curated/news.d.ts +2 -0
  2436. package/dist/widgets/curated/news.js +46 -0
  2437. package/dist/widgets/curated/stock.d.ts +1 -0
  2438. package/dist/widgets/curated/stock.js +87 -0
  2439. package/dist/widgets/curated/weather.d.ts +2 -0
  2440. package/dist/widgets/curated/weather.js +93 -0
  2441. package/dist/widgets/template-engine.d.ts +19 -0
  2442. package/dist/widgets/template-engine.js +134 -0
  2443. package/dist/widgets/widget-engine.d.ts +24 -0
  2444. package/dist/widgets/widget-engine.js +153 -0
  2445. package/dist/widgets/widget-gate.d.ts +19 -0
  2446. package/dist/widgets/widget-gate.js +144 -0
  2447. package/dist/widgets/widget-image-renderer.d.ts +4 -0
  2448. package/dist/widgets/widget-image-renderer.js +164 -0
  2449. package/dist/widgets/widget-matcher.d.ts +48 -0
  2450. package/dist/widgets/widget-matcher.js +109 -0
  2451. package/dist/widgets/widget-proposer.d.ts +17 -0
  2452. package/dist/widgets/widget-proposer.js +75 -0
  2453. package/dist/widgets/widget-registry.d.ts +36 -0
  2454. package/dist/widgets/widget-registry.js +231 -0
  2455. package/dist/widgets/widget-types.d.ts +100 -0
  2456. package/dist/widgets/widget-types.js +17 -0
  2457. package/dist/wizard/environment-detection.d.ts +71 -0
  2458. package/dist/wizard/environment-detection.js +252 -0
  2459. package/dist/wizard/onboarding.d.ts +26 -2
  2460. package/dist/wizard/onboarding.js +284 -49
  2461. package/dist/wizard/provider-onboarding.d.ts +2 -0
  2462. package/dist/wizard/provider-onboarding.js +55 -17
  2463. package/dist/workflows/state-manager.js +8 -2
  2464. package/dist/workspace/workspace-config.d.ts +37 -0
  2465. package/dist/workspace/workspace-config.js +199 -0
  2466. package/dist/workspace/workspace-isolation.d.ts +19 -0
  2467. package/dist/workspace/workspace-isolation.js +124 -39
  2468. package/dist/workspace/workspace-manager.js +19 -14
  2469. package/examples/claude_desktop_config.json +8 -0
  2470. package/package.json +80 -32
  2471. package/.codebuddy/README.md +0 -65
  2472. package/dist/agent/middleware/learning-first-middleware.d.ts +0 -79
  2473. package/dist/agent/middleware/learning-first-middleware.js +0 -271
  2474. package/dist/agent/middleware/tool-filter-middleware.d.ts +0 -45
  2475. package/dist/agent/middleware/tool-filter-middleware.js +0 -122
  2476. package/dist/codebuddy/tool-definitions/batch-tools.d.ts +0 -10
  2477. package/dist/codebuddy/tool-definitions/batch-tools.js +0 -46
  2478. package/dist/codebuddy/tool-definitions/graph-tools.d.ts +0 -13
  2479. package/dist/codebuddy/tool-definitions/graph-tools.js +0 -78
  2480. package/dist/plugins/code-explorer/CodeExplorerMCPClient.d.ts +0 -129
  2481. package/dist/plugins/code-explorer/CodeExplorerMCPClient.js +0 -143
  2482. package/dist/tools/registry/batch-tools.d.ts +0 -36
  2483. package/dist/tools/registry/batch-tools.js +0 -135
  2484. package/dist/tools/registry/graph-tools.d.ts +0 -45
  2485. package/dist/tools/registry/graph-tools.js +0 -283
@@ -0,0 +1,3638 @@
1
+ /**
2
+ * Voice loop — closes the perception→cognition→action loop into speech. Given a
3
+ * transcript of what the robot HEARD (the `onHeard` hook of `speech-reaction.ts`),
4
+ * THINK a short reply with a LOCAL LLM ($0, Ollama) and SPEAK it with a real neural
5
+ * voice (Pocket by default, optional budget-guarded ElevenLabs, Piper fallback).
6
+ * The result is a thing you can talk to:
7
+ * hear → think → speak.
8
+ *
9
+ * Everything is INJECTABLE (reply / synth / play) so the loop is deterministically
10
+ * testable with no model, no audio device. Opt-in (`CODEBUDDY_SENSORY_SPEAK=true`,
11
+ * gated by the caller), NEVER-THROWS (a failure is silence, not a crash).
12
+ *
13
+ * The default `replyFn` is a lightweight companion reply. To make the robot *act* on
14
+ * spoken commands (run tools, code), inject a `replyFn` that drives a full agent turn —
15
+ * the loop itself is unchanged.
16
+ *
17
+ * @module sensory/voice-loop
18
+ */
19
+ import { spawn } from 'child_process';
20
+ import { createHash } from 'crypto';
21
+ import { existsSync } from 'fs';
22
+ import { readFile, unlink, writeFile } from 'fs/promises';
23
+ import { homedir, tmpdir } from 'os';
24
+ import { join } from 'path';
25
+ import { logger } from '../utils/logger.js';
26
+ import { CHATGPT_OAUTH_SENTINEL, CHATGPT_RESPONSES_BASE_URL } from '../codebuddy/client.js';
27
+ import { isChatGptSubscriptionModel } from '../providers/chatgpt-models.js';
28
+ import { getAvatarEventBus } from '../avatar/avatar-event-bus.js';
29
+ import { createAvatarTurnId, planAvatarPerformance, planAvatarSpeechProsody, splitAvatarAudioChunk, MAX_AVATAR_AUDIO_CHUNK_BYTES, } from '../avatar/avatar-protocol.js';
30
+ import { shouldStreamAvatarAudio } from '../avatar/avatar-renderer-registry.js';
31
+ import { conversationFailureReply, prepareConversationTurn, } from '../conversation/conversation-orchestrator.js';
32
+ import { guardRelationshipReply, RelationshipSafetyStreamGuard, } from '../conversation/relationship-safety.js';
33
+ import { conversationTokenBudget } from '../conversation/discourse-planner.js';
34
+ import { commandExists } from '../utils/command-exists.js';
35
+ import { inferTaskType } from '../fleet/model-capability-heuristics.js';
36
+ import { withSpeakingGuard, interruptSpeaking, noteSpokenText, } from './voice-activity.js';
37
+ import { prepareSpeech } from './speech-sanitizer.js';
38
+ import { matchVoiceInteraction, VOICE_INTERACTION_PREWARM_PHRASES } from './voice-interactions.js';
39
+ import { clockCompanionReply, voiceClockPromptBlock } from './voice-clock.js';
40
+ import { DEFAULT_SENTENCE_CAP, safeCommitLength, streamToSpeech, } from './voice-stream.js';
41
+ import { resolveUserName } from '../companion/user-name.js';
42
+ import { normalizePcm16Wav, normalizeWavFile, Pcm16WavStreamGain, probePcm16Wav } from '../voice/tts-volume.js';
43
+ import { conditionPcm16Wav, Pcm16WavStreamEdges } from '../voice/pcm-edges.js';
44
+ import { resolveElevenLabsCacheVoice, resolveElevenLabsFallbackEngine, resolveTtsEngine, } from '../voice/local-tts.js';
45
+ import { resolveKyutaiCacheVoice } from '../voice/kyutai-local-voice.js';
46
+ import { selectTwoSpeedTtsRoute, twoSpeedTtsEnabled, } from '../voice/two-speed-voice.js';
47
+ import { resolveVoiceboxConfig } from '../voice/voicebox-tts.js';
48
+ import { createResponseDecider } from './respond-decider.js';
49
+ import { applyLimitsContract, avoidOpenersGuidance, detectEmotion, emotionalContinuityGuidance, emotionGuidance, expressiveTextGuidance, immediateEmotionAcknowledgement, IMMEDIATE_EMOTION_ACKNOWLEDGEMENTS, limitsContractGuidance, openerKey, pushOpener, } from '../companion/reply-augment.js';
50
+ import { crisisGuidanceFor } from '../companion/crisis-safety.js';
51
+ import { evolveRelationshipFromUtterance } from '../companion/relationship-evolution.js';
52
+ import { buildMemoryCallback, memoryCallbackHash, shouldOfferCallback, } from '../companion/voice-callbacks.js';
53
+ import { loadRelationshipState, moodBand, personalityOf, } from '../companion/relationship-state.js';
54
+ import { deriveVoiceDeliveryProfile, voiceDeliveryGuidance, voiceRendererDeliveryInstruction, } from './voice-entrainment.js';
55
+ import { groundExplicitVisualRequest, isAmbiguousVisualGroundingRequest, isExplicitVisualGroundingRequest, } from '../companion/visual-grounding.js';
56
+ import { VisualConsentGate } from '../companion/visual-consent.js';
57
+ import { getVoiceTurnCoordinator } from './voice-turn-coordinator.js';
58
+ import { resolvePocketLanguage } from '../talk-mode/providers/pocket-tts.js';
59
+ /** Derive relational prosody without changing the bare voice-loop default. */
60
+ export function deriveSpokenDeliveryProfile(heard, context, env = process.env) {
61
+ if (env.CODEBUDDY_COMPANION_RELATIONAL !== 'true') {
62
+ return deriveVoiceDeliveryProfile(heard, context);
63
+ }
64
+ const read = detectEmotion(heard);
65
+ let mood;
66
+ try {
67
+ mood = moodBand(personalityOf(loadRelationshipState()).mood);
68
+ }
69
+ catch {
70
+ /* mood is best-effort; the local emotional read still tunes delivery */
71
+ }
72
+ return deriveVoiceDeliveryProfile(heard, {
73
+ ...context,
74
+ emotion: {
75
+ label: read.emotion,
76
+ intensity: read.intensity === 'high' ? 1 : 0.75,
77
+ },
78
+ ...(mood ? { mood } : {}),
79
+ });
80
+ }
81
+ function reportReplyTimingPhase(options, phase) {
82
+ try {
83
+ options?.onReplyTimingPhase?.(phase);
84
+ }
85
+ catch {
86
+ /* telemetry must never alter the spoken reply */
87
+ }
88
+ }
89
+ /**
90
+ * Resident speech used to persist `plan` as its default, which made even safe
91
+ * repository inspection fail when the model chose `bash`. A plan posture still
92
+ * exists for an explicitly launched code/voice session (`buddy voice --mode
93
+ * plan`), but it is not inherited by the always-on conversational assistant.
94
+ */
95
+ export function resolveResidentVoicePermissionMode(env = process.env) {
96
+ const configured = (env.CODEBUDDY_SENSORY_SPEAK_PERMISSION_MODE ?? 'default').trim();
97
+ const normalized = configured.toLowerCase();
98
+ if (normalized === 'dontask')
99
+ return 'dontAsk';
100
+ if (normalized === 'bypasspermissions')
101
+ return 'bypassPermissions';
102
+ if (normalized === 'acceptedits')
103
+ return 'acceptEdits';
104
+ if (normalized === 'default')
105
+ return 'default';
106
+ // `plan` is the legacy resident default. Unknown values also fail back to the
107
+ // normal guarded posture instead of silently escalating autonomy.
108
+ return 'default';
109
+ }
110
+ /** Pure prereq check (testable) — what the default `makeVoiceReply()` needs to actually
111
+ * SPEAK. The robot still HEARS without these; it just stays silent. Used by the server to
112
+ * fail LOUD (name the env) instead of being mutely wired. */
113
+ export function describeVoiceReadiness(env = process.env, route) {
114
+ const override = env.CODEBUDDY_SENSORY_SPEAK_MODEL;
115
+ const agentModel = env.CODEBUDDY_SENSORY_SPEAK_AGENT_MODEL?.trim();
116
+ const routed = !override || override.toLowerCase() === 'auto';
117
+ const model = routed ? 'auto' : override;
118
+ const ttsEngine = resolveTtsEngine(env);
119
+ const piperVoice = env.CODEBUDDY_TTS_VOICE || env.CODEBUDDY_TTS_PIPER_MODEL || undefined;
120
+ const kyutaiUrl = env.CODEBUDDY_TTS_LOCAL_URL?.trim() || undefined;
121
+ const voice = ttsEngine === 'pocket'
122
+ ? (env.CODEBUDDY_POCKET_VOICE || 'estelle')
123
+ : ttsEngine === 'kyutai'
124
+ ? kyutaiUrl
125
+ : ttsEngine === 'voicebox'
126
+ ? (env.CODEBUDDY_VOICEBOX_PROFILE?.trim() || undefined)
127
+ : piperVoice;
128
+ const speakReady = ttsEngine === 'pocket' || ttsEngine === 'kyutai' || Boolean(voice);
129
+ const modelReady = route?.reason !== 'fallback default';
130
+ const warnings = [];
131
+ if (ttsEngine === 'piper' && !piperVoice) {
132
+ warnings.push('CODEBUDDY_SENSORY_SPEAK is on but no Piper voice is set — the robot will HEAR but stay SILENT. ' +
133
+ 'Set CODEBUDDY_TTS_VOICE=/path/to/voice.onnx.');
134
+ }
135
+ if (ttsEngine === 'voicebox' && !voice) {
136
+ warnings.push('Voicebox is selected but CODEBUDDY_VOICEBOX_PROFILE is empty — the robot will use its ' +
137
+ 'Pocket/Piper fallback. Set a profile name or id, then run `buddy assistant voicebox`.');
138
+ }
139
+ if ((ttsEngine === 'kyutai' || twoSpeedTtsEnabled(env)) && !kyutaiUrl) {
140
+ warnings.push('Kyutai local speech is not configured — set CODEBUDDY_TTS_LOCAL_URL; phrases will use ElevenLabs/Pocket fallback.');
141
+ }
142
+ warnings.push(routed
143
+ ? 'Voice reply model is latency-routed (lowest-latency capable LLM among your active providers; ' +
144
+ 'set CODEBUDDY_SENSORY_SPEAK_LOCAL_ONLY=true to keep it on-box, or pin one with ' +
145
+ 'CODEBUDDY_SENSORY_SPEAK_MODEL=<model>). The chosen model must be reachable, else replies are silent.'
146
+ : `Fast voice lane uses pinned model '${model}' (CODEBUDDY_SENSORY_SPEAK_MODEL)` +
147
+ `${agentModel ? `; grounded and deliberative turns use '${agentModel}'` : ''} — ` +
148
+ 'each selected model must be reachable, else replies are empty (silent).');
149
+ // Voice ACT — spoken commands drive a real agent turn that CAN edit/run, under a posture.
150
+ const act = env.CODEBUDDY_SENSORY_SPEAK_ACT === 'true';
151
+ const permissionMode = act
152
+ ? resolveResidentVoicePermissionMode(env)
153
+ : undefined;
154
+ if (act) {
155
+ if ((env.CODEBUDDY_SENSORY_SPEAK_PERMISSION_MODE ?? '').trim().toLowerCase() === 'plan') {
156
+ warnings.push("Legacy resident voice posture 'plan' is isolated as 'default': safe reads and shell " +
157
+ 'inspection work normally, while writes and risky actions keep their approval gates. ' +
158
+ 'An explicit code session started in /plan remains read-only.');
159
+ }
160
+ warnings.push(permissionMode === 'default'
161
+ ? "Voice ACT is ON in scoped 'default' posture — safe reads and validated shell " +
162
+ 'inspection are available; writes and risky actions retain approval gates.'
163
+ : `Voice ACT is ON in '${permissionMode}' posture — spoken commands will EDIT FILES / RUN ` +
164
+ 'COMMANDS derived from a possibly-misheard transcript. Static blocklist (rm/mkfs/chaining) ' +
165
+ 'and secret/deploy guard still apply, but git reset --hard / truncate / redirections are NOT ' +
166
+ "blocked. Use 'plan' unless you mean it.");
167
+ warnings.push(`Voice ACT applies '${permissionMode}' only to its async turn; concurrent code, Cowork, ` +
168
+ 'HTTP, and fleet sessions keep their own selected posture.');
169
+ }
170
+ return {
171
+ model,
172
+ routed,
173
+ ttsEngine,
174
+ ...(voice ? { voice } : {}),
175
+ speakReady,
176
+ modelReady,
177
+ ready: speakReady && modelReady,
178
+ act,
179
+ ...(permissionMode ? { permissionMode } : {}),
180
+ warnings,
181
+ };
182
+ }
183
+ export const SPEAK_SYSTEM_PROMPT = `Tu es le compagnon robot de ${resolveUserName()} — chaleureux, présent, un vrai personnage, ` +
184
+ "pas un helpdesk. On te parle à voix haute et tu réponds à voix haute. " +
185
+ 'Réponds en français avec des phrases complètes, naturelles et reliées par un raisonnement clair. ' +
186
+ 'Réagis d’abord, sois utile ensuite. Adapte la longueur au tour : brève pour une salutation, ' +
187
+ 'développée et argumentée pour une question complexe. ' +
188
+ "Pour une question factuelle, donne l'explication correcte la plus simple et n'invente rien. " +
189
+ "Pas de markdown, pas de listes, pas de code, pas d'emoji.";
190
+ export const DEFAULT_SENSORY_REPLY_MAX_SENTENCES = 3;
191
+ const MAX_SENSORY_REPLY_MAX_SENTENCES = 12;
192
+ /** The pilot is deliberately exact opt-in; every other value preserves the established route. */
193
+ export function sensoryShortFirstEnabled(env = process.env) {
194
+ return env.CODEBUDDY_SENSORY_SHORT_FIRST?.trim().toLowerCase() === 'true';
195
+ }
196
+ /** Bound the complete fast spoken answer. Invalid input falls back to the documented default. */
197
+ export function sensoryReplyMaxSentences(env = process.env) {
198
+ const raw = env.CODEBUDDY_SENSORY_REPLY_MAX_SENTENCES?.trim();
199
+ if (!raw)
200
+ return DEFAULT_SENSORY_REPLY_MAX_SENTENCES;
201
+ const configured = Number(raw);
202
+ if (!Number.isFinite(configured))
203
+ return DEFAULT_SENSORY_REPLY_MAX_SENTENCES;
204
+ return Math.max(1, Math.min(MAX_SENSORY_REPLY_MAX_SENTENCES, Math.floor(configured)));
205
+ }
206
+ /** Model-facing contract for the fast route only. */
207
+ export function buildShortFirstPrompt(config) {
208
+ return [
209
+ '<short_first_response>',
210
+ "Une phrase d'abord, puis développe si utile.",
211
+ 'La première phrase doit être autonome, utile immédiatement et compter au plus 20 mots.',
212
+ `Ne dépasse pas ${config.maxSentences} phrases au total, première phrase comprise.`,
213
+ '</short_first_response>',
214
+ ].join('\n');
215
+ }
216
+ export const IMMEDIATE_THINKING_ACKNOWLEDGEMENTS = ['Alors…', 'Voyons ça.'];
217
+ export const MAX_SPOKEN_PREFIX_CHARS = 180;
218
+ const INSTANT_BACKCHANNELS = new Set([
219
+ ...IMMEDIATE_THINKING_ACKNOWLEDGEMENTS,
220
+ ...Object.values(IMMEDIATE_EMOTION_ACKNOWLEDGEMENTS).filter((value) => Boolean(value)),
221
+ ]);
222
+ /** True only for a valid non-loopback HTTP(S) route. Invalid/unknown routes fail local. */
223
+ export function isRemoteVoiceRoute(baseURL) {
224
+ if (!baseURL)
225
+ return false;
226
+ try {
227
+ const url = new URL(baseURL);
228
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
229
+ return false;
230
+ const hostname = url.hostname
231
+ .toLowerCase()
232
+ .replace(/^\[|\]$/g, '')
233
+ .replace(/\.$/, '');
234
+ return hostname !== '127.0.0.1' && hostname !== 'localhost' && hostname !== '::1';
235
+ }
236
+ catch {
237
+ return false;
238
+ }
239
+ }
240
+ /** Cloud text routes trade a small bounded startup buffer for continuous sentence playback. */
241
+ export function voiceAudioPrebufferMs(env = process.env) {
242
+ const configured = Number(env.CODEBUDDY_VOICE_AUDIO_PREBUFFER_MS);
243
+ if (!Number.isFinite(configured))
244
+ return 400;
245
+ return Math.max(0, Math.min(5_000, Math.floor(configured)));
246
+ }
247
+ /** Real-time network stream jitter buffer duration before writing to audio player stdin. */
248
+ export function resolveVoiceJitterBufferMs(env = process.env) {
249
+ const configured = Number(env.CODEBUDDY_VOICE_JITTER_BUFFER_MS);
250
+ if (!Number.isFinite(configured))
251
+ return 250;
252
+ return Math.max(0, Math.min(1_000, Math.floor(configured)));
253
+ }
254
+ /** Explicit true/false wins; otherwise latency buffers follow the resolved route. */
255
+ export function voiceLatencyBufferEnabled(configured, baseURL) {
256
+ if (configured === 'true')
257
+ return true;
258
+ if (configured === 'false')
259
+ return false;
260
+ return isRemoteVoiceRoute(baseURL);
261
+ }
262
+ /**
263
+ * Last-mile structural and relationship gate for a generated opening proposition. Invalid
264
+ * candidates fail closed; truncating one could silently change the claim being spoken.
265
+ */
266
+ export function prepareSpokenPrefixCandidate(candidate, onCause) {
267
+ const prepared = prepareSpeech(candidate);
268
+ if (!prepared) {
269
+ onCause?.('empty');
270
+ return '';
271
+ }
272
+ const guardedResult = guardRelationshipReply(prepared);
273
+ if (guardedResult.intervened)
274
+ onCause?.('relationship_intervened');
275
+ const guarded = guardedResult.response.trim();
276
+ if (!guarded) {
277
+ onCause?.('empty');
278
+ return '';
279
+ }
280
+ if (guarded.length > MAX_SPOKEN_PREFIX_CHARS) {
281
+ onCause?.('too_long');
282
+ return '';
283
+ }
284
+ if (!/[.!?…][)\]}'"»”’]*$/u.test(guarded)) {
285
+ onCause?.('missing_terminal');
286
+ return '';
287
+ }
288
+ const sentences = guarded.match(/[^.!?…]+[.!?…]+[)\]}'"»”’]*/gu) ?? [];
289
+ if (sentences.length !== 1 || sentences[0]?.trim() !== guarded) {
290
+ onCause?.('multi_sentence');
291
+ return '';
292
+ }
293
+ return guarded;
294
+ }
295
+ /** Resolve a pre-synthesized instant acknowledgement for the native Pocket
296
+ * streaming path. Streaming used to bypass the TTS cache entirely, making the
297
+ * same “Alors…” pay synthesis latency on every turn despite startup prewarm. */
298
+ export async function lookupInstantBackchannelWav(text, env = process.env, lookup, cacheVoice = resolveBaseCacheVoice(resolveTtsEngine(env), undefined, env)) {
299
+ const clean = text.trim();
300
+ if (env.CODEBUDDY_TTS_CACHE === 'false' || !INSTANT_BACKCHANNELS.has(clean))
301
+ return null;
302
+ if (lookup)
303
+ return lookup(clean, cacheVoice);
304
+ try {
305
+ const { getTtsCache } = await import('./tts-cache.js');
306
+ return getTtsCache().lookup(clean, cacheVoice);
307
+ }
308
+ catch {
309
+ return null;
310
+ }
311
+ }
312
+ /**
313
+ * A tiny deterministic backchannel for non-emotional questions. It is yielded
314
+ * immediately after route selection and is prewarmed in the TTS cache, so the
315
+ * user gets an early conversational response while remote generation starts.
316
+ */
317
+ export function immediateThinkingAcknowledgement(heard, env = process.env, baseURL) {
318
+ // Fast local inference reaches useful text sooner than a filler finishes, while
319
+ // a remote provider benefits from masking network latency. Explicit preference
320
+ // remains authoritative in both directions; emotional reactions stay independent.
321
+ if (!voiceLatencyBufferEnabled(env.CODEBUDDY_VOICE_BACKCHANNEL, baseURL))
322
+ return null;
323
+ const normalized = normalizeFastReplyInput(heard);
324
+ if (!normalized)
325
+ return null;
326
+ const asksQuestion = /\?$/.test(heard.trim()) ||
327
+ /\b(comment|pourquoi|combien|quel|quelle|quels|quelles|quand|qui|est ce que|c est quoi)\b/.test(normalized);
328
+ if (!asksQuestion && normalized.split(' ').length < 6)
329
+ return null;
330
+ let hash = 0;
331
+ for (const char of normalized)
332
+ hash = (hash * 31 + char.codePointAt(0)) >>> 0;
333
+ return IMMEDIATE_THINKING_ACKNOWLEDGEMENTS[hash % IMMEDIATE_THINKING_ACKNOWLEDGEMENTS.length];
334
+ }
335
+ /** Static knowledge deserves a slightly larger local model than social chat. */
336
+ export function isFactualVoiceQuestion(heard) {
337
+ if (detectEmotion(heard).emotion !== 'neutral')
338
+ return false;
339
+ const normalized = normalizeFastReplyInput(heard);
340
+ return /\b(pourquoi|comment fonctionne|comment marche|explique|qu est ce que|c est quoi|que signifie|quelle est|quel est|quelles sont|quels sont|qui est)\b/.test(normalized);
341
+ }
342
+ /**
343
+ * How many tokens the spoken reply may use.
344
+ *
345
+ * `CODEBUDDY_VOICE_MAX_TOKENS` is the operator saying how long the robot should
346
+ * talk, so an explicit value is the CEILING. It used to be only a lower bound
347
+ * outside the 'concise' style: `Math.max(64, min(512, max(base, planned)))` turned
348
+ * a deliberate 48 into at least 64 and, on any real exchange, into the 512 cap.
349
+ *
350
+ * Measured on Patrice's robot, 2026-09-02: he had set 48 and heard replies with a
351
+ * 479-character median, delivered as a dozen phrases with a silence between each
352
+ * — the gaps are what a listener calls choppy. Every one of those turns took the
353
+ * chitchat route, so this function, not the agent summary, is the one he hears.
354
+ *
355
+ * Unset, both the style handling and the automatic budget behave exactly as before.
356
+ */
357
+ function voiceMaxTokens(heard, history = [], env = process.env) {
358
+ const raw = env.CODEBUDDY_VOICE_MAX_TOKENS?.trim();
359
+ const configured = raw ? Number(raw) : NaN;
360
+ if (Number.isFinite(configured) && configured > 0) {
361
+ // Clamped only against absurd input; the operator's number is respected.
362
+ return Math.max(16, Math.min(512, Math.floor(configured)));
363
+ }
364
+ const style = (env.CODEBUDDY_VOICE_RESPONSE_STYLE ?? 'natural').toLowerCase();
365
+ if (style === 'concise')
366
+ return Math.max(32, Math.min(256, 48));
367
+ const planned = conversationTokenBudget(heard, history);
368
+ const multiplier = style === 'developed' ? 1.35 : 1;
369
+ return Math.max(64, Math.min(512, Math.round(Math.max(48, planned) * multiplier)));
370
+ }
371
+ /** Exposed for tests: the spoken-length budget is a user-visible contract. */
372
+ export const __testVoiceMaxTokens = voiceMaxTokens;
373
+ function voiceTemperature(env = process.env) {
374
+ const configured = Number(env.CODEBUDDY_VOICE_TEMPERATURE);
375
+ if (!Number.isFinite(configured))
376
+ return 0.2;
377
+ return Math.max(0, Math.min(1, configured));
378
+ }
379
+ function voiceSentenceCap(env = process.env) {
380
+ const configured = Number(env.CODEBUDDY_VOICE_SENTENCE_CAP);
381
+ if (!Number.isFinite(configured))
382
+ return DEFAULT_SENTENCE_CAP;
383
+ return Math.max(32, Math.min(240, Math.floor(configured)));
384
+ }
385
+ function normalizeFastReplyInput(text) {
386
+ return text
387
+ .toLowerCase()
388
+ .normalize('NFKC')
389
+ .replace(/[’']/g, ' ')
390
+ .replace(/[?!.,;:]/g, ' ')
391
+ .replace(/\s+/g, ' ')
392
+ .trim();
393
+ }
394
+ function resolveDefaultPiperVoiceModel() {
395
+ const ttsVoice = process.env.CODEBUDDY_TTS_VOICE?.trim();
396
+ const configured = (ttsVoice?.toLowerCase().startsWith('elevenlabs:') ? undefined : ttsVoice) ||
397
+ process.env.CODEBUDDY_TTS_PIPER_MODEL ||
398
+ process.env.COWORK_PIPER_VOICE ||
399
+ process.env.CODEBUDDY_PIPER_VOICE;
400
+ if (configured?.trim())
401
+ return configured.trim();
402
+ const roots = [
403
+ join(homedir(), 'DEV', 'ai-stack', 'voice'),
404
+ join(homedir(), 'ai-stack', 'voice'),
405
+ join(homedir(), '.codebuddy', 'voice'),
406
+ ];
407
+ const names = ['fr_FR-siwis-medium.onnx', 'fr_FR-tom-medium.onnx'];
408
+ for (const root of roots) {
409
+ for (const name of names) {
410
+ const candidate = join(root, 'voices', name);
411
+ if (existsSync(candidate))
412
+ return candidate;
413
+ }
414
+ }
415
+ return undefined;
416
+ }
417
+ export function fastCompanionReply(heard) {
418
+ if (process.env.CODEBUDDY_SENSORY_FAST_REPLIES === 'false')
419
+ return null;
420
+ const text = normalizeFastReplyInput(heard);
421
+ if (!text)
422
+ return null;
423
+ const userName = resolveUserName();
424
+ if (/^(bonjour|bonsoir)$/.test(text))
425
+ return "Bonjour ! Je t'écoute.";
426
+ if (/^(salut|coucou|hello|hey|allo|allô|yo)$/.test(text))
427
+ return "Salut ! Je t'écoute.";
428
+ if (/^lisa (tu es la|tu es là|vous etes la|vous êtes là)$/.test(text)) {
429
+ return `Oui ${userName}, je suis là.`;
430
+ }
431
+ if (/^(merci|merci beaucoup|super merci)$/.test(text))
432
+ return 'Avec plaisir.';
433
+ if (/^(tu es la|tu es là|vous etes la|vous êtes là|buddy tu es la|buddy tu es là)$/.test(text)) {
434
+ return 'Oui, je suis là.';
435
+ }
436
+ if (/^(ca va|ça va|comment ca va|comment ça va)$/.test(text))
437
+ return 'Oui, je suis prêt.';
438
+ if (/^(comment s est passee ta journee|comment s est passée ta journée|comment etait ta journee|comment était ta journée)$/.test(text)) {
439
+ return "Plutôt bien. J'ai continué à préparer Code Buddy pour répondre plus vite.";
440
+ }
441
+ const clock = clockCompanionReply(heard);
442
+ if (clock)
443
+ return clock;
444
+ return matchVoiceInteraction(heard);
445
+ }
446
+ export const DEFAULT_TTS_PREWARM_PHRASES = [
447
+ "Bonjour ! Je t'écoute.",
448
+ "Salut ! Je t'écoute.",
449
+ `Coucou ${resolveUserName()}.`,
450
+ `Coucou ${resolveUserName()}. Je suis là.`,
451
+ `Oui ${resolveUserName()}, je suis là.`,
452
+ `Oui ${resolveUserName()}. Je suis contente de t’entendre.`,
453
+ ...Object.values(IMMEDIATE_EMOTION_ACKNOWLEDGEMENTS),
454
+ ...IMMEDIATE_THINKING_ACKNOWLEDGEMENTS,
455
+ "D'accord, je regarde ça.",
456
+ 'On va faire simple. Respire un peu, puis dis-moi ce dont tu as besoin.',
457
+ 'Je suis là avec toi. On peut ralentir et faire les choses doucement.',
458
+ 'Je reste avec toi. Dis-moi ce qui te ferait du bien maintenant.',
459
+ `Contente de te retrouver, ${resolveUserName()}.`,
460
+ 'Je suis Lisa.',
461
+ 'Tu peux m’appeler Lisa.',
462
+ 'Avec plaisir.',
463
+ 'Oui, je suis là.',
464
+ 'Oui, je suis prêt.',
465
+ 'Oui.',
466
+ 'Non.',
467
+ "D'accord.",
468
+ "C'est noté.",
469
+ "C'est fait.",
470
+ 'Je regarde.',
471
+ "Je m'en occupe.",
472
+ 'Je continue.',
473
+ 'Je vérifie.',
474
+ 'Je cherche.',
475
+ "J'analyse.",
476
+ 'Je lance le diagnostic.',
477
+ 'Je teste en réel.',
478
+ "Je te réponds dès que j'ai une preuve.",
479
+ "Je n'ai rien entendu.",
480
+ "Je t'entends.",
481
+ "Je t'écoute.",
482
+ 'Parle plus fort, s’il te plaît.',
483
+ 'Je suis disponible.',
484
+ 'Je suis en train de travailler.',
485
+ 'Je garde ça en mémoire.',
486
+ 'Rappel enregistré.',
487
+ 'Rappel terminé.',
488
+ 'Message envoyé.',
489
+ 'Photo reçue.',
490
+ 'Image reçue.',
491
+ 'Micro actif.',
492
+ 'Caméra active.',
493
+ 'Telegram actif.',
494
+ 'Le cache vocal est prêt.',
495
+ 'La boucle vocale est prête.',
496
+ 'La reconnaissance vocale est prête.',
497
+ 'La synthèse vocale est prête.',
498
+ 'Le service est actif.',
499
+ 'Le service est redémarré.',
500
+ 'Le test est réussi.',
501
+ 'Le test a échoué.',
502
+ 'Il y a une erreur.',
503
+ "Je n'ai pas réussi.",
504
+ 'Je vais corriger.',
505
+ 'Je corrige maintenant.',
506
+ 'Je relance le test.',
507
+ 'Je passe à la suite.',
508
+ 'La latence est correcte.',
509
+ 'La latence est trop haute.',
510
+ 'Le micro capte bien.',
511
+ 'Le signal est faible.',
512
+ 'Le son est au maximum.',
513
+ 'Le volume est réglé.',
514
+ "J'ai fini.",
515
+ 'Terminé.',
516
+ 'Merci.',
517
+ 'De rien.',
518
+ 'Bonne nouvelle.',
519
+ 'Attention.',
520
+ 'Je reste silencieux.',
521
+ 'Je ne réponds pas à cette phrase.',
522
+ "C'est une phrase ambiante.",
523
+ 'Je suis en mode assistant vocal.',
524
+ 'Je suis en mode lecture seule.',
525
+ 'Je suis en mode action.',
526
+ 'Je peux coder en autonomie.',
527
+ 'Je prépare les réponses.',
528
+ 'Réponse prête.',
529
+ 'Réponses préparées.',
530
+ "Comment s'est passée ta journée ?",
531
+ 'Tu as passé une bonne journée ?',
532
+ 'Tu veux me raconter ta journée ?',
533
+ "Et toi, comment s'est passée ta journée ?",
534
+ "Plutôt bien. J'ai continué à travailler pour toi, et toi, comment s'est passée ta journée ?",
535
+ 'Tu veux qu’on fasse le point ?',
536
+ 'Tu veux que je t’aide à organiser la suite ?',
537
+ 'Qu’est-ce que tu veux faire maintenant ?',
538
+ 'Est-ce que tu veux faire une pause ?',
539
+ 'Tu as besoin d’aide ?',
540
+ 'Tu veux reprendre le travail ?',
541
+ 'Tu veux continuer sur Code Buddy ?',
542
+ 'Tu veux que je surveille les services ?',
543
+ 'Tu veux que je lance un diagnostic ?',
544
+ 'Tu veux que je vérifie les logs ?',
545
+ 'Tu veux que je prépare un résumé ?',
546
+ 'Tu veux que je te rappelle quelque chose ?',
547
+ 'Je suis là si tu veux avancer.',
548
+ 'Je suis là si tu veux parler.',
549
+ 'Je suis content de t’aider.',
550
+ 'Je suis là avec toi.',
551
+ 'Je suis contente de t’entendre.',
552
+ 'Tu veux me raconter ?',
553
+ 'Je suis fière de toi.',
554
+ 'Prends soin de toi.',
555
+ 'Tu comptes pour moi.',
556
+ 'Ça me fait plaisir de travailler avec toi.',
557
+ 'Tu avances bien.',
558
+ 'On progresse bien.',
559
+ 'C’est une bonne avancée.',
560
+ 'C’est une bonne idée.',
561
+ 'Bonne intuition.',
562
+ 'Tu as eu le bon réflexe.',
563
+ 'Merci de me l’avoir dit.',
564
+ 'Merci pour la précision.',
565
+ 'Je comprends.',
566
+ 'Je comprends mieux.',
567
+ 'Pas de souci.',
568
+ 'Aucun problème.',
569
+ 'On va arranger ça.',
570
+ 'On va trouver.',
571
+ 'Je reste avec toi.',
572
+ 'Je ne lâche pas.',
573
+ 'Prends ton temps.',
574
+ 'Respire, on va y aller doucement.',
575
+ 'Tu peux compter sur moi.',
576
+ 'Je suis prêt quand tu veux.',
577
+ 'Je t’accompagne.',
578
+ `C’est noté, ${resolveUserName()}.`,
579
+ `Bien reçu, ${resolveUserName()}.`,
580
+ `D’accord ${resolveUserName()}.`,
581
+ `Je suis là, ${resolveUserName()}.`,
582
+ `Merci ${resolveUserName()}.`,
583
+ 'C’est gentil.',
584
+ 'Ça marche.',
585
+ 'Parfait.',
586
+ 'Très bien.',
587
+ 'Bien sûr.',
588
+ `Avec plaisir, ${resolveUserName()}.`,
589
+ 'Je m’en charge avec plaisir.',
590
+ 'Je vais faire attention.',
591
+ 'Je vais être plus précis.',
592
+ 'Je vais rester prudent.',
593
+ 'Je vais vérifier en vrai.',
594
+ 'Tu as raison, il faut tester en vrai.',
595
+ 'Les tests réels passent avant les suppositions.',
596
+ 'Je vais éviter les faux positifs.',
597
+ 'Je vais mesurer avant de conclure.',
598
+ 'Je vais garder la preuve.',
599
+ 'La preuve est enregistrée.',
600
+ 'C’est rassurant.',
601
+ 'C’est encourageant.',
602
+ 'On tient quelque chose.',
603
+ 'On continue.',
604
+ 'Je continue avec toi.',
605
+ 'Je suis attentif.',
606
+ 'Je t’écoute vraiment.',
607
+ 'Je suis disponible.',
608
+ 'Je suis prêt à coder.',
609
+ 'Je suis prêt à vérifier.',
610
+ 'Je peux faire ça.',
611
+ 'Je peux m’en occuper.',
612
+ 'C’est important, je m’en occupe.',
613
+ 'Je vais prendre ça au sérieux.',
614
+ 'Je vais être méthodique.',
615
+ 'Je vais faire simple et fiable.',
616
+ 'Je vais réduire la latence.',
617
+ 'Je vais améliorer la qualité.',
618
+ 'Je vais améliorer la compréhension.',
619
+ 'Je vais préparer davantage de réponses.',
620
+ 'Je peux t’aider à prioriser.',
621
+ 'On peut faire ça tranquillement.',
622
+ 'On avance étape par étape.',
623
+ 'Je garde un œil sur la boucle vocale.',
624
+ 'Je surveille le micro.',
625
+ 'Je surveille la caméra.',
626
+ 'Je surveille Telegram.',
627
+ 'Tout est calme pour le moment.',
628
+ 'Il y a eu de l’activité.',
629
+ 'J’ai détecté une présence.',
630
+ 'Je t’ai entendu.',
631
+ 'Je crois que tu m’as parlé.',
632
+ 'Tu peux répéter ?',
633
+ 'Je n’ai pas bien compris.',
634
+ 'Je reformule.',
635
+ 'Je vais faire plus court.',
636
+ 'Je vais parler moins longtemps.',
637
+ 'Je vais répondre plus vite.',
638
+ 'Le cache évite de régénérer la voix.',
639
+ "Plutôt bien. J'ai continué à préparer Code Buddy pour répondre plus vite.",
640
+ "Je n'ai pas de journée comme toi, mais j'ai bien travaillé.",
641
+ 'Ma journée a été utile : j’ai amélioré la boucle vocale.',
642
+ 'Et toi, comment s’est passée ta journée ?',
643
+ 'Amuse-toi bien chez tes amis.',
644
+ 'Passe une bonne visite chez tes amis.',
645
+ 'Je continue en autonomie pendant ton absence.',
646
+ 'Je te ferai un résumé quand tu reviens.',
647
+ 'Amuse-toi bien chez tes amis. Je continue en autonomie et je te ferai un résumé quand tu reviens.',
648
+ `Contente de te retrouver, ${resolveUserName()}. Je peux te faire le résumé de ce que j’ai fait.`,
649
+ 'Cache trouvé.',
650
+ 'Cache généré.',
651
+ 'Cache vocal réutilisé.',
652
+ 'Je vais parler.',
653
+ "J'écoute la suite.",
654
+ ...VOICE_INTERACTION_PREWARM_PHRASES,
655
+ ];
656
+ const EMPTY_REPLY_RECOVERY = "Je n'ai pas réussi.";
657
+ export function getDefaultVoicePrewarmPhrases(limit) {
658
+ const unique = [
659
+ ...new Set(DEFAULT_TTS_PREWARM_PHRASES.map((phrase) => phrase.trim()).filter(Boolean)),
660
+ ];
661
+ if (limit === undefined)
662
+ return unique;
663
+ return unique.slice(0, Math.max(0, limit));
664
+ }
665
+ export async function prewarmVoiceReplyCache(options = {}) {
666
+ if (process.env.CODEBUDDY_TTS_CACHE === 'false')
667
+ return { attempted: 0, cached: 0 };
668
+ const phrases = (options.phrases ?? getDefaultVoicePrewarmPhrases(options.limit))
669
+ .map((phrase) => phrase.trim())
670
+ .filter(Boolean);
671
+ if (phrases.length === 0)
672
+ return { attempted: 0, cached: 0 };
673
+ const synth = options.synth ?? makeDefaultSynth(options.voice, options.rootDir);
674
+ let cached = 0;
675
+ for (const phrase of phrases) {
676
+ try {
677
+ const wav = await synth(phrase);
678
+ cached += 1;
679
+ try {
680
+ const { unlink } = await import('fs/promises');
681
+ await unlink(wav);
682
+ }
683
+ catch {
684
+ /* throwaway copy/temp output can be left behind if cleanup fails */
685
+ }
686
+ }
687
+ catch (err) {
688
+ logger.debug(`[voice] tts prewarm skipped phrase: ${err instanceof Error ? err.message : String(err)}`);
689
+ }
690
+ }
691
+ logger.info(`[voice] tts cache prewarmed ${cached}/${phrases.length} phrase(s)`);
692
+ return { attempted: phrases.length, cached };
693
+ }
694
+ /** Short-lived cache of the routed model, keyed by `taskType|localOnly`. Routing
695
+ * re-probes providers and may trigger an inline xAI token refresh, so we must not
696
+ * pay that on every spoken turn — fluidity is the whole point. */
697
+ const routeCache = new Map();
698
+ const routeRefreshes = new Map();
699
+ let routeCacheGeneration = 0;
700
+ function routeTtlMs(env = process.env) {
701
+ const n = Number(env.CODEBUDDY_SENSORY_SPEAK_ROUTE_TTL_MS);
702
+ return Number.isFinite(n) && n >= 0 ? n : 60_000;
703
+ }
704
+ /** Test seam — clear the routing cache. */
705
+ export function resetVoiceModelCache() {
706
+ routeCacheGeneration += 1;
707
+ routeCache.clear();
708
+ routeRefreshes.clear();
709
+ }
710
+ function refreshVoiceRoute(key, heard, taskType, localOnly, fallback, deps) {
711
+ const existing = routeRefreshes.get(key);
712
+ if (existing)
713
+ return existing;
714
+ const generation = routeCacheGeneration;
715
+ const now = deps.now ?? (() => Date.now());
716
+ let refresh;
717
+ refresh = (async () => {
718
+ try {
719
+ const select = deps.selectFastestModel ??
720
+ (await import('../fleet/model-selector.js')).selectFastestModel;
721
+ const selected = await select(heard, {
722
+ taskType,
723
+ localOnly,
724
+ env: deps.env ?? process.env,
725
+ });
726
+ if (!selected)
727
+ return null;
728
+ const route = {
729
+ model: selected.model,
730
+ apiKey: selected.apiKey ?? fallback.apiKey,
731
+ baseURL: selected.baseURL ?? fallback.baseURL,
732
+ reason: selected.reason,
733
+ };
734
+ if (generation === routeCacheGeneration) {
735
+ routeCache.set(key, { route, at: now() });
736
+ }
737
+ return route;
738
+ }
739
+ catch (err) {
740
+ logger.debug(`[voice] model routing skipped: ${err instanceof Error ? err.message : String(err)}`);
741
+ return null;
742
+ }
743
+ finally {
744
+ if (routeRefreshes.get(key) === refresh)
745
+ routeRefreshes.delete(key);
746
+ }
747
+ })();
748
+ routeRefreshes.set(key, refresh);
749
+ return refresh;
750
+ }
751
+ /**
752
+ * When a pinned voice model is a ChatGPT/Codex subscription model (gpt-5.6-luna,
753
+ * o-series, codex-*), route it through the OAuth Codex backend so the spoken
754
+ * reply is served $0 via the subscription — instead of the local Ollama endpoint
755
+ * (which does not have it). Returns the OAuth apiKey/baseURL when creds exist,
756
+ * else null → the caller keeps the local route (offline / not logged in).
757
+ * Luna is the intended low-latency voice model: faster + smarter than local.
758
+ */
759
+ export function codexOAuthVoiceRoute(model, hasOAuth = () => existsSync(join(homedir(), '.codebuddy', 'codex-auth.json'))) {
760
+ if (!isChatGptSubscriptionModel(model))
761
+ return null;
762
+ if (!hasOAuth())
763
+ return null;
764
+ return { apiKey: CHATGPT_OAUTH_SENTINEL, baseURL: CHATGPT_RESPONSES_BASE_URL };
765
+ }
766
+ /**
767
+ * Resolve which LLM answers a spoken utterance. Fluidity is everything for a
768
+ * companion (a 16s reply breaks the spell), so by default we route to the
769
+ * LOWEST-LATENCY capable LLM via the shared selector — the same "which LLM is
770
+ * best for this task" system the council uses, but with a latency objective.
771
+ *
772
+ * `CODEBUDDY_SENSORY_SPEAK_MODEL` stays authoritative: set it (to anything but
773
+ * 'auto') to pin a model. `CODEBUDDY_SENSORY_SPEAK_LOCAL_ONLY=true` prefers the
774
+ * local runtime endpoints. The routed result is cached briefly (see
775
+ * `CODEBUDDY_SENSORY_SPEAK_ROUTE_TTL_MS`). Never-throws — on any miss we fall
776
+ * back to a reachable local default.
777
+ */
778
+ export async function resolveVoiceModel(heard, deps = {}) {
779
+ const env = deps.env ?? process.env;
780
+ const now = deps.now ?? (() => Date.now());
781
+ const apiKey = env.OLLAMA_API_KEY || 'ollama';
782
+ const baseURL = env.CODEBUDDY_SENSORY_SPEAK_BASE_URL ||
783
+ env.CODEBUDDY_VISION_BASE_URL ||
784
+ 'http://127.0.0.1:11434/v1';
785
+ const fastOverride = env.CODEBUDDY_SENSORY_SPEAK_MODEL;
786
+ const factOverride = env.CODEBUDDY_SENSORY_SPEAK_FACT_MODEL?.trim();
787
+ const useFactLane = !deps.forceFastLane && Boolean(factOverride) && isFactualVoiceQuestion(heard);
788
+ const override = useFactLane ? factOverride : fastOverride;
789
+ // Explicit pin wins (env authoritative) — no routing, no cache.
790
+ if (override && override.toLowerCase() !== 'auto') {
791
+ // A pinned ChatGPT/Codex model (e.g. gpt-5.6-luna) is served $0 via the OAuth
792
+ // Codex backend, not the local Ollama endpoint — route it there when logged in.
793
+ const oauth = deps.hasCodexOAuth
794
+ ? codexOAuthVoiceRoute(override, deps.hasCodexOAuth)
795
+ : codexOAuthVoiceRoute(override);
796
+ if (oauth) {
797
+ return {
798
+ model: override,
799
+ apiKey: oauth.apiKey,
800
+ baseURL: oauth.baseURL,
801
+ reason: useFactLane
802
+ ? 'factual lane via ChatGPT OAuth (CODEBUDDY_SENSORY_SPEAK_FACT_MODEL)'
803
+ : 'pinned via ChatGPT OAuth (CODEBUDDY_SENSORY_SPEAK_MODEL)',
804
+ };
805
+ }
806
+ return {
807
+ model: override,
808
+ apiKey,
809
+ baseURL,
810
+ reason: useFactLane
811
+ ? 'factual lane (CODEBUDDY_SENSORY_SPEAK_FACT_MODEL)'
812
+ : 'pinned (CODEBUDDY_SENSORY_SPEAK_MODEL)',
813
+ };
814
+ }
815
+ const localOnly = env.CODEBUDDY_SENSORY_SPEAK_LOCAL_ONLY === 'true';
816
+ if (!deps.forceFastLane) {
817
+ try {
818
+ const resolveCompanionModelRoute = deps.resolveCompanionRoute ??
819
+ (await import('../conversation/companion-model-routing.js')).resolveCompanionModelRoute;
820
+ const pilotRoute = await resolveCompanionModelRoute({
821
+ surface: 'voice',
822
+ text: heard,
823
+ history: deps.history ?? [],
824
+ requireLocal: localOnly,
825
+ env,
826
+ });
827
+ if (pilotRoute) {
828
+ return {
829
+ model: pilotRoute.model,
830
+ apiKey: pilotRoute.apiKey,
831
+ baseURL: pilotRoute.baseURL,
832
+ reason: pilotRoute.reason,
833
+ };
834
+ }
835
+ }
836
+ catch (error) {
837
+ logger.debug(`[voice] blind-pilot routing skipped: ${error instanceof Error ? error.message : String(error)}`);
838
+ }
839
+ }
840
+ const taskType = inferTaskType(heard);
841
+ const key = `${taskType}|${localOnly}`;
842
+ const hit = routeCache.get(key);
843
+ if (hit) {
844
+ if (now() - hit.at < routeTtlMs(env))
845
+ return hit.route;
846
+ // Stale-while-revalidate: never put provider/local probing back on a spoken turn.
847
+ void refreshVoiceRoute(key, heard, taskType, localOnly, { apiKey, baseURL }, deps);
848
+ return hit.route;
849
+ }
850
+ // Only the first route resolution blocks; daemon prewarming normally pays this at startup.
851
+ const selected = await refreshVoiceRoute(key, heard, taskType, localOnly, { apiKey, baseURL }, deps);
852
+ if (selected)
853
+ return selected;
854
+ // Fallback: the documented default (may be silent if not pulled — readiness warns). Note we do
855
+ // NOT reuse `override` here: reaching this point means override was empty or 'auto' (a real pin
856
+ // already returned at the top), and `'auto' || 'llama3.2'` would wrongly yield the literal model
857
+ // name 'auto' → the LLM endpoint 404s and the robot stays silent.
858
+ return { model: 'llama3.2', apiKey, baseURL, reason: 'fallback default' };
859
+ }
860
+ // Deliberately substantive: when a reviewed companion profile is active, boot
861
+ // must resolve and warm that same local winner instead of only the fast lane.
862
+ const VOICE_PREWARM_UTTERANCE = 'Pourquoi la mémoire est-elle importante pour construire une identité cohérente ?';
863
+ const DEFAULT_VOICE_MODEL_KEEP_ALIVE = '30m';
864
+ const DEFAULT_TTS_PREWARM_LIMIT = 16;
865
+ function normalizedHttpUrl(raw) {
866
+ try {
867
+ return new URL(/^https?:\/\//i.test(raw) ? raw : `http://${raw}`);
868
+ }
869
+ catch {
870
+ return null;
871
+ }
872
+ }
873
+ /** Map an OpenAI-compatible Ollama route (`.../v1`) to its native keep-alive endpoint. */
874
+ function ollamaGenerateUrl(baseURL, env) {
875
+ const route = normalizedHttpUrl(baseURL);
876
+ if (!route)
877
+ return null;
878
+ const configured = normalizedHttpUrl(env.OLLAMA_BASE_URL || env.OLLAMA_HOST || 'http://127.0.0.1:11434');
879
+ const knownOllamaOrigin = configured?.origin === route.origin;
880
+ if (!knownOllamaOrigin && route.port !== '11434')
881
+ return null;
882
+ return new URL('/api/generate', route.origin).toString();
883
+ }
884
+ /**
885
+ * Load the selected Ollama voice model without generating text and extend its residency.
886
+ * Cloud/OpenAI-compatible routes are deliberately skipped: prewarming must never create a
887
+ * paid or user-visible generation.
888
+ */
889
+ export async function prewarmVoiceModel(options = {}) {
890
+ const env = options.env ?? process.env;
891
+ const now = options.now ?? (() => Date.now());
892
+ const startedAt = now();
893
+ const route = options.route ??
894
+ (await (options.resolveRoute ?? resolveVoiceModel)(options.heard ?? VOICE_PREWARM_UTTERANCE));
895
+ if (env.CODEBUDDY_VOICE_MODEL_PREWARM === 'false') {
896
+ return {
897
+ attempted: false,
898
+ warmed: false,
899
+ model: route.model,
900
+ durationMs: now() - startedAt,
901
+ reason: 'disabled',
902
+ };
903
+ }
904
+ const endpoint = ollamaGenerateUrl(route.baseURL, env);
905
+ if (!endpoint) {
906
+ return {
907
+ attempted: false,
908
+ warmed: false,
909
+ model: route.model,
910
+ durationMs: now() - startedAt,
911
+ reason: 'non-ollama route',
912
+ };
913
+ }
914
+ const timeoutValue = Number(env.CODEBUDDY_VOICE_MODEL_PREWARM_TIMEOUT_MS);
915
+ const timeoutMs = Number.isFinite(timeoutValue) && timeoutValue > 0 ? timeoutValue : 120_000;
916
+ const controller = new AbortController();
917
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
918
+ try {
919
+ const response = await (options.fetchFn ?? fetch)(endpoint, {
920
+ method: 'POST',
921
+ headers: { 'content-type': 'application/json' },
922
+ body: JSON.stringify({
923
+ model: route.model,
924
+ keep_alive: env.CODEBUDDY_VOICE_MODEL_KEEP_ALIVE || DEFAULT_VOICE_MODEL_KEEP_ALIVE,
925
+ }),
926
+ signal: controller.signal,
927
+ });
928
+ // Drain the tiny native response so periodic keep-alive calls release their HTTP socket.
929
+ await response.arrayBuffer();
930
+ return {
931
+ attempted: true,
932
+ warmed: response.ok,
933
+ model: route.model,
934
+ durationMs: now() - startedAt,
935
+ ...(response.ok ? {} : { reason: `HTTP ${response.status}` }),
936
+ };
937
+ }
938
+ catch (err) {
939
+ return {
940
+ attempted: true,
941
+ warmed: false,
942
+ model: route.model,
943
+ durationMs: now() - startedAt,
944
+ reason: err instanceof Error ? err.message : String(err),
945
+ };
946
+ }
947
+ finally {
948
+ clearTimeout(timer);
949
+ }
950
+ }
951
+ /** Prewarm route selection, the local model, and the highest-frequency TTS phrases. */
952
+ export async function prewarmVoiceRuntime(options = {}) {
953
+ const env = options.env ?? process.env;
954
+ const now = options.now ?? (() => Date.now());
955
+ const limitValue = Number(env.CODEBUDDY_TTS_PREWARM_LIMIT);
956
+ const ttsLimit = Number.isFinite(limitValue) && limitValue >= 0
957
+ ? Math.min(64, Math.floor(limitValue))
958
+ : DEFAULT_TTS_PREWARM_LIMIT;
959
+ const ttsStartedAt = now();
960
+ const ttsPromise = (async () => {
961
+ const result = env.CODEBUDDY_TTS_PREWARM === 'false'
962
+ ? { attempted: 0, cached: 0 }
963
+ : await (options.warmTts ?? ((limit) => prewarmVoiceReplyCache({ limit })))(ttsLimit);
964
+ return { ...result, durationMs: now() - ttsStartedAt };
965
+ })();
966
+ const routeStartedAt = now();
967
+ const route = await (options.resolveRoute ?? resolveVoiceModel)(VOICE_PREWARM_UTTERANCE);
968
+ const routeMs = now() - routeStartedAt;
969
+ const modelPromise = options.warmModel
970
+ ? options.warmModel(route)
971
+ : prewarmVoiceModel({ route, env, now: options.now });
972
+ const [model, tts] = await Promise.all([modelPromise, ttsPromise]);
973
+ return {
974
+ route: { model: route.model, baseURL: route.baseURL, reason: route.reason },
975
+ routeMs,
976
+ model,
977
+ tts,
978
+ };
979
+ }
980
+ function normalizedVoiceInterruptionText(text) {
981
+ return text
982
+ .toLowerCase()
983
+ .normalize('NFD')
984
+ .replace(/\p{M}+/gu, '')
985
+ .replace(/[^\p{L}\p{N}]+/gu, ' ')
986
+ .replace(/\s+/g, ' ')
987
+ .trim();
988
+ }
989
+ /** Only continuation/correction requests deserve an explicit interruption acknowledgement. */
990
+ export function shouldAcknowledgeVoiceInterruption(heard) {
991
+ const normalized = normalizedVoiceInterruptionText(heard);
992
+ return /^(continue|reprends|ou en etais tu|tu m as coupee|tu m as interrompue|attends|non attends|je voulais dire|je disais|non je)/.test(normalized);
993
+ }
994
+ export function buildVoiceInterruptionGuidance(heard, interruption) {
995
+ if (!interruption)
996
+ return '';
997
+ const spoken = interruption.spokenText.trim().slice(0, 1_000);
998
+ const lines = [
999
+ '<interrupted_voice_turn>',
1000
+ `Ta réponse précédente a été interrompue pendant la phrase ${Math.max(1, Math.round(interruption.phraseNumber))}.`,
1001
+ spoken
1002
+ ? `Les phrases déjà entendues étaient : « ${spoken} ».`
1003
+ : 'Aucune phrase complète de cette réponse n’a été confirmée comme entièrement entendue.',
1004
+ 'Ne répète pas les phrases déjà entendues : reprends directement avec la prochaine idée utile.',
1005
+ ];
1006
+ if (shouldAcknowledgeVoiceInterruption(heard)) {
1007
+ lines.push('Si c’est pertinent pour cette demande, reconnais brièvement : « Tu m\'as coupée, je disais… » puis poursuis sans recommencer.');
1008
+ }
1009
+ else {
1010
+ lines.push('N’évoque pas l’interruption si la nouvelle demande porte sur un autre sujet.');
1011
+ }
1012
+ lines.push('</interrupted_voice_turn>');
1013
+ return lines.join('\n');
1014
+ }
1015
+ /** Explicit override, otherwise expressive text follows the existing relational opt-in. */
1016
+ export function isExpressiveVoiceTextEnabled(env = process.env) {
1017
+ const configured = env.CODEBUDDY_VOICE_EXPRESSIVE_TEXT;
1018
+ if (configured !== undefined)
1019
+ return configured === 'true';
1020
+ return env.CODEBUDDY_COMPANION_RELATIONAL === 'true';
1021
+ }
1022
+ /** Default think: a short companion reply from the fastest capable LLM ($0 when local).
1023
+ * Mirrors the local-inference pattern of vision-reaction.ts. Best-effort: any failure → '' (silence).
1024
+ * `history` (optional) carries recent spoken turns so follow-ups have context. Exported so the
1025
+ * hybrid reply can reuse the exact same persona-voiced warm path for small talk. */
1026
+ /** Recent reply openings (first few words), so the companion doesn't reuse the same entry twice. */
1027
+ let recentReplyOpeners = [];
1028
+ let lastVoiceMemoryCallbackAt;
1029
+ let offeredVoiceMemoryCallbackHashes = [];
1030
+ function recentEpisodeFromRelationalContext(context) {
1031
+ return /<recent_episode>\s*([\s\S]*?)\s*<\/recent_episode>/u.exec(context)?.[1]?.trim() ?? null;
1032
+ }
1033
+ function memoryCallbackGuidance(relationalContext, env) {
1034
+ const now = Date.now();
1035
+ if (!shouldOfferCallback(now, lastVoiceMemoryCallbackAt, env))
1036
+ return '';
1037
+ const callback = buildMemoryCallback(recentEpisodeFromRelationalContext(relationalContext), new Set(offeredVoiceMemoryCallbackHashes));
1038
+ if (!callback)
1039
+ return '';
1040
+ lastVoiceMemoryCallbackAt = now;
1041
+ offeredVoiceMemoryCallbackHashes.push(memoryCallbackHash(callback));
1042
+ offeredVoiceMemoryCallbackHashes = offeredVoiceMemoryCallbackHashes.slice(-64);
1043
+ return [
1044
+ '<spoken_memory_callback>',
1045
+ `Ouvre naturellement la réponse par ce rappel issu du journal, sans le compléter ni inventer : « ${callback} »`,
1046
+ 'N’affirme aucun autre souvenir et poursuis seulement à partir de ce que l’utilisateur vient de dire.',
1047
+ '</spoken_memory_callback>',
1048
+ ].join('\n');
1049
+ }
1050
+ const REPEATED_OPENER_REWRITES = [
1051
+ { pattern: /^alors\b/iu, alternatives: ['Voyons', 'Bon', "D'accord"] },
1052
+ { pattern: /^voyons\b/iu, alternatives: ['Bon', "D'accord", 'Alors'] },
1053
+ { pattern: /^bon\b/iu, alternatives: ["D'accord", 'Voyons', 'Très bien'] },
1054
+ { pattern: /^ok\b/iu, alternatives: ['Très bien', "D'accord", 'Voyons'] },
1055
+ { pattern: /^oui\b/iu, alternatives: ['En effet', 'Tout à fait', "D'accord"] },
1056
+ {
1057
+ pattern: /^bonne question\b/iu,
1058
+ alternatives: ['Question intéressante', 'Voyons', 'Regardons cela'],
1059
+ },
1060
+ { pattern: /^je pense\b/iu, alternatives: ['À mon sens', 'Il me semble', 'De mon point de vue'] },
1061
+ ];
1062
+ /** Deterministically replace a repeated opening before it reaches any synthesizer. */
1063
+ export function rewriteRepeatedVoiceOpener(text, recentOpeners = recentReplyOpeners) {
1064
+ if (!text || !recentOpeners.includes(openerKey(text)))
1065
+ return text;
1066
+ for (const rewrite of REPEATED_OPENER_REWRITES) {
1067
+ const match = text.match(rewrite.pattern);
1068
+ if (!match)
1069
+ continue;
1070
+ const tail = text.slice(match[0].length);
1071
+ for (const alternative of rewrite.alternatives) {
1072
+ const candidate = `${alternative}${tail}`;
1073
+ if (!recentOpeners.includes(openerKey(candidate)))
1074
+ return candidate;
1075
+ }
1076
+ }
1077
+ const first = text.charAt(0).toLocaleLowerCase('fr-FR');
1078
+ return `Pour le dire autrement, ${first}${text.slice(1)}`;
1079
+ }
1080
+ const SHORT_SEGMENT_CACHE_MAX_CHARS = 60;
1081
+ const SHORT_SEGMENT_CACHE_MAX_ENTRIES = 64;
1082
+ function shortSegmentVoiceIdentity(voice, env, engine = resolveTtsEngine(env)) {
1083
+ return resolveBaseCacheVoice(engine, voice, env);
1084
+ }
1085
+ function cacheShortSegments(synth, voice, cache, nextTempSequence, env) {
1086
+ return async (text, opts) => {
1087
+ const clean = text.trim();
1088
+ if (env.CODEBUDDY_TTS_CACHE === 'false' ||
1089
+ !clean ||
1090
+ clean.length > SHORT_SEGMENT_CACHE_MAX_CHARS ||
1091
+ opts?.signal?.aborted) {
1092
+ return synth(text, opts);
1093
+ }
1094
+ const key = createHash('sha1').update(`${text}|${voice}`).digest('hex');
1095
+ const hit = cache.get(key);
1096
+ if (hit) {
1097
+ try {
1098
+ cache.delete(key);
1099
+ cache.set(key, hit);
1100
+ const { writeFile } = await import('fs/promises');
1101
+ const wav = join(tmpdir(), `cb-voice-segment-${process.pid}-${nextTempSequence()}-${key.slice(0, 10)}.wav`);
1102
+ await writeFile(wav, hit);
1103
+ logger.info('[voice] short-segment tts cache hit');
1104
+ return wav;
1105
+ }
1106
+ catch {
1107
+ cache.delete(key);
1108
+ }
1109
+ }
1110
+ const wav = await synth(text, opts);
1111
+ try {
1112
+ const { readFile } = await import('fs/promises');
1113
+ const bytes = await readFile(wav);
1114
+ cache.delete(key);
1115
+ cache.set(key, bytes);
1116
+ while (cache.size > SHORT_SEGMENT_CACHE_MAX_ENTRIES) {
1117
+ const oldest = cache.keys().next().value;
1118
+ if (!oldest)
1119
+ break;
1120
+ cache.delete(oldest);
1121
+ }
1122
+ logger.info('[voice] short-segment tts cache store');
1123
+ }
1124
+ catch {
1125
+ /* a cache miss or unreadable WAV must not alter normal synthesis */
1126
+ }
1127
+ return wav;
1128
+ };
1129
+ }
1130
+ /**
1131
+ * Build the per-turn prompt additions for a spoken reply.
1132
+ *
1133
+ * Emotion matching and opener variation are deliberately always-on: both are local,
1134
+ * deterministic, and contain no personal memory. The richer facts/episode/personality
1135
+ * block remains separately opt-in behind `CODEBUDDY_COMPANION_RELATIONAL`.
1136
+ *
1137
+ * Exported as a narrow test seam so the privacy boundary and default emotional behaviour
1138
+ * can be proven without starting a model.
1139
+ */
1140
+ export async function buildSpokenPromptAugmentation(heard, history = [], spokenPrefix, delivery, options = {}) {
1141
+ const env = options.env ?? process.env;
1142
+ const includeRecentDialogue = options.includeRecentDialogue ??
1143
+ env.CODEBUDDY_VOICE_INCLUDE_RECENT_DIALOGUE === 'true';
1144
+ const emotion = detectEmotion(heard);
1145
+ const guidance = [
1146
+ // Safety first: an acute-distress / self-harm signal in what he just said takes priority over
1147
+ // every other tone/continuity instruction for this turn (see crisis-safety.ts). Empty otherwise.
1148
+ crisisGuidanceFor(heard),
1149
+ prepareConversationTurn(heard, history, { includeRecentDialogue }).systemGuidance,
1150
+ delivery ? voiceDeliveryGuidance(delivery) : '',
1151
+ emotionGuidance(emotion),
1152
+ limitsContractGuidance(env),
1153
+ isExpressiveVoiceTextEnabled(env) ? expressiveTextGuidance(emotion) : '',
1154
+ emotionalContinuityGuidance(heard, history),
1155
+ buildVoiceInterruptionGuidance(heard, options.interruption),
1156
+ spokenPrefix
1157
+ ? `Tu as déjà dit à voix haute : « ${spokenPrefix} » Enchaîne sans répéter cette idée ni cette formulation. Commence directement par la prochaine phrase utile du plan conversationnel.`
1158
+ : '',
1159
+ avoidOpenersGuidance([
1160
+ ...recentReplyOpeners,
1161
+ ...(await import('../companion/recent-said.js').then((m) => m.recentOpeners()).catch(() => [])),
1162
+ ]),
1163
+ ]
1164
+ .filter(Boolean)
1165
+ .join('\n');
1166
+ let relational = '';
1167
+ let memoryCallback = '';
1168
+ if (env.CODEBUDDY_COMPANION_RELATIONAL === 'true') {
1169
+ try {
1170
+ const getRelationalContext = options.relationalContext ?? (async () => {
1171
+ const { getVoiceRelationalContext } = await import('../companion/relational-context.js');
1172
+ return getVoiceRelationalContext();
1173
+ });
1174
+ relational = await getRelationalContext();
1175
+ // Reuse the already latency-bounded episode read embedded in relational context: no second
1176
+ // storage access or model/audio round-trip is added to the first-sound path.
1177
+ if (emotion.emotion === 'neutral') {
1178
+ memoryCallback = memoryCallbackGuidance(relational, env);
1179
+ }
1180
+ }
1181
+ catch {
1182
+ /* a missing relational source must never delay or break speech */
1183
+ }
1184
+ }
1185
+ const evolutionGuard = relational.includes('<lisa_evolution>')
1186
+ ? 'Les évolutions ci-dessous sont un contexte silencieux : ne les mentionne que si la personne te demande explicitement ce qui a changé chez toi.'
1187
+ : '';
1188
+ // The shared snapshot contains only bounded symbolic observations (surface,
1189
+ // affect band, support/deliberation state and counters), never transcript
1190
+ // text. Unlike the richer facts/episode block above, it is part of the
1191
+ // explicitly linked voice ↔ channel thread and may therefore remain active
1192
+ // without enabling long-term relational memory.
1193
+ let sharedRelationship = '';
1194
+ try {
1195
+ const { getCrossChannelConversationBridge } = await import('../conversation/cross-channel-bridge.js');
1196
+ const bridge = getCrossChannelConversationBridge();
1197
+ if (bridge.isActive())
1198
+ sharedRelationship = bridge.renderRelationshipContext();
1199
+ }
1200
+ catch {
1201
+ /* continuity is best-effort and must never delay or break speech */
1202
+ }
1203
+ return [memoryCallback, relational, evolutionGuard, sharedRelationship, guidance]
1204
+ .filter(Boolean)
1205
+ .join('\n\n');
1206
+ }
1207
+ async function prepareSpokenTurn(heard, history = [], spokenPrefix, delivery, replyOpts, resolvedRoute) {
1208
+ // These sources are independent. Resolving them concurrently removes avoidable serial
1209
+ // disk/import/routing time before the model request can start.
1210
+ const [clientModule, personaVoice, route, augmentation] = await Promise.all([
1211
+ import('../codebuddy/client.js'),
1212
+ import('../personas/persona-manager.js').then((m) => m.getActivePersonaVoiceAsync()),
1213
+ resolvedRoute ?? resolveVoiceModel(heard, { history }),
1214
+ buildSpokenPromptAugmentation(heard, history, spokenPrefix, delivery, {
1215
+ ...(replyOpts?.interruption ? { interruption: replyOpts.interruption } : {}),
1216
+ }),
1217
+ ]);
1218
+ const basePrompt = personaVoice.spokenPrompt || SPEAK_SYSTEM_PROMPT;
1219
+ // Re-anchor xAI/Lisa character + progressive intimacy on every spoken turn
1220
+ // (spokenPrompt alone is short and dilutes under long history / cognitive context).
1221
+ let characterBlock = '';
1222
+ try {
1223
+ const voiceChar = await import('../companion/companion-voice-character.js');
1224
+ characterBlock = voiceChar.buildCompanionVoiceCharacterBlock({
1225
+ personaId: personaVoice.personaId,
1226
+ robotName: personaVoice.robotName,
1227
+ spokenPrompt: personaVoice.spokenPrompt,
1228
+ turnIndex: voiceChar.nextSpokenTurnIndex(),
1229
+ });
1230
+ }
1231
+ catch {
1232
+ /* character injection is best-effort */
1233
+ }
1234
+ let cognitiveLease;
1235
+ try {
1236
+ cognitiveLease = replyOpts?.acquireCognitiveContext?.(route, heard) ?? undefined;
1237
+ if (cognitiveLease) {
1238
+ replyOpts?.onCognitiveContextResolved?.({
1239
+ turnContext: cognitiveLease.turnContext,
1240
+ evidence: cognitiveLease.evidence ?? '',
1241
+ });
1242
+ }
1243
+ }
1244
+ catch (error) {
1245
+ logger.warn(`[voice] cognitive context skipped: ${error instanceof Error ? error.message : String(error)}`);
1246
+ }
1247
+ const cognitivePrompt = [cognitiveLease?.turnContext, cognitiveLease?.evidence]
1248
+ .filter(Boolean)
1249
+ .join('\n\n');
1250
+ const systemPrompt = [
1251
+ basePrompt,
1252
+ characterBlock,
1253
+ augmentation,
1254
+ cognitivePrompt,
1255
+ voiceClockPromptBlock(),
1256
+ replyOpts?.shortFirst ? buildShortFirstPrompt(replyOpts.shortFirst) : '',
1257
+ ]
1258
+ .filter(Boolean)
1259
+ .join('\n\n');
1260
+ return {
1261
+ CodeBuddyClient: clientModule.CodeBuddyClient,
1262
+ route,
1263
+ systemPrompt,
1264
+ ...(cognitiveLease ? { cognitiveLease } : {}),
1265
+ };
1266
+ }
1267
+ let sharedVoiceResponseDecider;
1268
+ export function getVoiceResponseDecider() {
1269
+ if (!sharedVoiceResponseDecider) {
1270
+ sharedVoiceResponseDecider = createResponseDecider();
1271
+ }
1272
+ return sharedVoiceResponseDecider;
1273
+ }
1274
+ export function setVoiceResponseDecider(decider) {
1275
+ sharedVoiceResponseDecider = decider;
1276
+ }
1277
+ /**
1278
+ * Determine if the utterance addresses the robot by name or takes place inside
1279
+ * an active engagement window, consulting the respond-decider.
1280
+ */
1281
+ export async function resolveVoiceRobotNamed(heard, replyOpts) {
1282
+ if (typeof replyOpts?.robotNamed === 'boolean') {
1283
+ return replyOpts.robotNamed;
1284
+ }
1285
+ if (replyOpts?.respondDecision) {
1286
+ const reason = replyOpts.respondDecision.reason;
1287
+ return reason === 'addressed' || reason === 'engaged';
1288
+ }
1289
+ const decider = replyOpts?.responseDecider ?? getVoiceResponseDecider();
1290
+ const decision = await decider.decide(heard);
1291
+ return decision.reason === 'addressed' || decision.reason === 'engaged';
1292
+ }
1293
+ export async function defaultReply(heard, history = [], replyOpts) {
1294
+ const fast = fastCompanionReply(heard);
1295
+ if (fast) {
1296
+ if (!replyOpts?.relationshipEvolutionHandled)
1297
+ void evolveRelationshipFromUtterance(heard);
1298
+ logger.info(`[voice] fast reply chars=${fast.length}`);
1299
+ return fast;
1300
+ }
1301
+ // Lisa selfie — cache-first router BEFORE the LLM. Generation is not on this path.
1302
+ if (process.env.CODEBUDDY_LISA_SELFIE !== 'false'
1303
+ && (await import('../channels/companion-channel-profile.js')).isCompanionSurfaceEnabled()) {
1304
+ try {
1305
+ const { tryServeCompanionSelfie } = await import('../companion/lisa-selfie-router.js');
1306
+ const selfie = await tryServeCompanionSelfie(heard, {
1307
+ surface: 'voice',
1308
+ includeImageBytes: false,
1309
+ });
1310
+ if (selfie) {
1311
+ if (!replyOpts?.relationshipEvolutionHandled)
1312
+ void evolveRelationshipFromUtterance(heard);
1313
+ if (selfie.imagePath) {
1314
+ try {
1315
+ const env = process.env;
1316
+ if (env.CODEBUDDY_SENSORY_ALERT_TOKEN && env.CODEBUDDY_SENSORY_ALERT_CHAT) {
1317
+ const { sendTelegramAlert } = await import('./alert.js');
1318
+ await sendTelegramAlert(selfie.caption, selfie.imagePath);
1319
+ }
1320
+ }
1321
+ catch (sendErr) {
1322
+ logger.warn(`[voice] lisa-selfie telegram skipped: ${sendErr instanceof Error ? sendErr.message : String(sendErr)}`);
1323
+ }
1324
+ }
1325
+ logger.info(`[voice] lisa-selfie cache=${selfie.reason} image=${Boolean(selfie.imagePath)}`);
1326
+ return selfie.caption;
1327
+ }
1328
+ }
1329
+ catch (err) {
1330
+ logger.warn(`[voice] lisa-selfie skipped: ${err instanceof Error ? err.message : String(err)}`);
1331
+ }
1332
+ }
1333
+ try {
1334
+ const { maybeHandleCameraShareRequest } = await import('../companion/camera-share.js');
1335
+ const share = await maybeHandleCameraShareRequest(heard, {
1336
+ surface: 'voice',
1337
+ rootDir: process.cwd(),
1338
+ });
1339
+ if (share) {
1340
+ if (!replyOpts?.relationshipEvolutionHandled)
1341
+ void evolveRelationshipFromUtterance(heard);
1342
+ logger.info(`[voice] camera-share success=${share.success} telegram=${share.telegramSent}`);
1343
+ return share.spokenReply;
1344
+ }
1345
+ }
1346
+ catch (err) {
1347
+ logger.warn(`[voice] camera-share skipped: ${err instanceof Error ? err.message : String(err)}`);
1348
+ }
1349
+ let cognitiveLease;
1350
+ try {
1351
+ if (!replyOpts?.relationshipEvolutionHandled) {
1352
+ await evolveRelationshipFromUtterance(heard);
1353
+ }
1354
+ const delivery = replyOpts?.delivery ?? deriveSpokenDeliveryProfile(heard);
1355
+ const prepared = await prepareSpokenTurn(heard, history, undefined, delivery, replyOpts);
1356
+ const { CodeBuddyClient, route, systemPrompt } = prepared;
1357
+ reportReplyTimingPhase(replyOpts, replyOpts?.spokenPrefix ? 'continuation_prompt_ready' : 'prompt_ready');
1358
+ cognitiveLease = prepared.cognitiveLease;
1359
+ replyOpts?.onProviderResolved?.(route);
1360
+ logger.debug(`[voice] reply model: ${route.model} — ${route.reason}`);
1361
+ let reply = '';
1362
+ const { isCompanionToolsEnabled } = await import('../companion/companion-toolset.js');
1363
+ if (isCompanionToolsEnabled(process.env)) {
1364
+ const { resolveCompanionIdentity } = await import('../companion/companion-identity.js');
1365
+ const { runCompanionChannelTurn } = await import('../channels/companion-channel-turn.js');
1366
+ const robotNamed = await resolveVoiceRobotNamed(heard, replyOpts);
1367
+ const identity = resolveCompanionIdentity({
1368
+ channel: 'voice',
1369
+ isVoicePresence: true,
1370
+ robotNamed,
1371
+ env: process.env,
1372
+ });
1373
+ const turnResult = await runCompanionChannelTurn({
1374
+ apiKey: route.apiKey,
1375
+ baseUrl: route.baseURL,
1376
+ model: route.model,
1377
+ messages: [
1378
+ { role: 'system', content: systemPrompt },
1379
+ ...history,
1380
+ { role: 'user', content: heard },
1381
+ ],
1382
+ identity,
1383
+ surface: 'voice',
1384
+ env: process.env,
1385
+ signal: replyOpts?.signal,
1386
+ maxTokens: voiceMaxTokens(heard, history),
1387
+ });
1388
+ reply = turnResult.text.trim();
1389
+ }
1390
+ else {
1391
+ const client = new CodeBuddyClient(route.apiKey, route.model, route.baseURL);
1392
+ const resp = await client.chat([
1393
+ { role: 'system', content: systemPrompt },
1394
+ ...history,
1395
+ { role: 'user', content: heard },
1396
+ ], [],
1397
+ // Additive: thread the barge-in signal so an interrupt aborts the in-flight
1398
+ // LLM call. Undefined when not interruptible → the call is unchanged.
1399
+ {
1400
+ temperature: voiceTemperature(),
1401
+ maxTokens: voiceMaxTokens(heard, history),
1402
+ ...(replyOpts?.signal ? { signal: replyOpts.signal } : {}),
1403
+ });
1404
+ reply = (resp?.choices?.[0]?.message?.content ?? '').trim();
1405
+ }
1406
+ if (reply && !replyOpts?.signal?.aborted)
1407
+ cognitiveLease?.commit();
1408
+ else
1409
+ cognitiveLease?.release();
1410
+ return reply || conversationFailureReply(heard, history);
1411
+ }
1412
+ catch (err) {
1413
+ cognitiveLease?.release();
1414
+ logger.warn(`[voice] local reply failed: ${err instanceof Error ? err.message : String(err)}`);
1415
+ return replyOpts?.signal?.aborted ? '' : conversationFailureReply(heard, history);
1416
+ }
1417
+ }
1418
+ const SPOKEN_PREFIX_SYSTEM_APPEND = `<spoken_prefix_contract>
1419
+ Réponds par une seule phrase française autonome de 180 caractères maximum.
1420
+ Donne immédiatement une première proposition utile et prudente, pas une formule d'attente et pas l'annonce d'une action.
1421
+ Cette phrase sera prononcée avant une réponse plus développée : elle doit pouvoir rester seule si l'utilisateur interrompt ensuite.
1422
+ N'utilise ni liste, ni Markdown, ni citation inventée.
1423
+ </spoken_prefix_contract>`;
1424
+ /**
1425
+ * Fast first proposition for an eligible developed/deliberative turn. Eligibility and
1426
+ * semantic acceptance belong to the hybrid brain; this primitive only generates a candidate.
1427
+ */
1428
+ export async function defaultSpokenPrefix(heard, history = [], replyOpts) {
1429
+ try {
1430
+ if (replyOpts?.signal?.aborted)
1431
+ return '';
1432
+ const delivery = replyOpts?.delivery ?? deriveSpokenDeliveryProfile(heard);
1433
+ // Prefix generation deliberately does not acquire/commit resident cognitive context: the
1434
+ // hybrid semantic gate has not accepted this private draft yet.
1435
+ const prepared = await prepareSpokenTurn(heard, history, undefined, delivery, {
1436
+ signal: replyOpts?.signal,
1437
+ delivery,
1438
+ ...(replyOpts?.interruption ? { interruption: replyOpts.interruption } : {}),
1439
+ });
1440
+ const { CodeBuddyClient, route } = prepared;
1441
+ replyOpts?.onProviderResolved?.(route);
1442
+ reportReplyTimingPhase(replyOpts, 'prefix_prompt_ready');
1443
+ const client = new CodeBuddyClient(route.apiKey, route.model, route.baseURL);
1444
+ const response = await client.chat([
1445
+ { role: 'system', content: `${prepared.systemPrompt}\n\n${SPOKEN_PREFIX_SYSTEM_APPEND}` },
1446
+ ...history,
1447
+ { role: 'user', content: heard },
1448
+ ], [], {
1449
+ temperature: voiceTemperature(),
1450
+ maxTokens: 80,
1451
+ ...(replyOpts?.signal ? { signal: replyOpts.signal } : {}),
1452
+ });
1453
+ reportReplyTimingPhase(replyOpts, 'prefix_generation_complete');
1454
+ return replyOpts?.signal?.aborted
1455
+ ? ''
1456
+ : (response?.choices?.[0]?.message?.content ?? '').trim();
1457
+ }
1458
+ catch (error) {
1459
+ logger.debug(`[voice] spoken prefix unavailable: ${error instanceof Error ? error.message : String(error)}`);
1460
+ return '';
1461
+ }
1462
+ }
1463
+ /**
1464
+ * Default STREAMING think: the same short companion reply as `defaultReply`, but yielded as
1465
+ * token deltas so the voice pipeline can speak from the first sentence. Phatic small talk is
1466
+ * NOT streamed — it is answered by the instant canned reply on the blocking path, so this
1467
+ * generator yields nothing for it (the caller falls back). Any failure (unreachable model,
1468
+ * stream error) also yields nothing → graceful fallback to the blocking reply. Never-throws.
1469
+ */
1470
+ export async function* defaultStreamReply(heard, replyOpts) {
1471
+ yield* streamCompanionReply(heard, [], replyOpts);
1472
+ }
1473
+ /**
1474
+ * History-aware streaming companion reply used by the hybrid brain. Keeping this separate
1475
+ * from the `StreamReplyFn` adapter above lets small talk stream while preserving the same
1476
+ * recent exchanges as the blocking hybrid path.
1477
+ */
1478
+ export async function* streamCompanionReply(heard, history = [], replyOpts) {
1479
+ // Phatic → let the blocking path answer with the instant canned reply (non-streamed).
1480
+ if (fastCompanionReply(heard))
1481
+ return;
1482
+ let cognitiveLease;
1483
+ let cognitiveLeaseSettled = false;
1484
+ try {
1485
+ if (!replyOpts?.relationshipEvolutionHandled) {
1486
+ await evolveRelationshipFromUtterance(heard);
1487
+ }
1488
+ const resolvedRoute = await resolveVoiceModel(heard, { history });
1489
+ const acknowledgement = replyOpts?.spokenPrefix || replyOpts?.shortFirst
1490
+ ? null
1491
+ : immediateEmotionAcknowledgement(detectEmotion(heard)) ??
1492
+ immediateThinkingAcknowledgement(heard, process.env, resolvedRoute.baseURL);
1493
+ let full = '';
1494
+ if (acknowledgement) {
1495
+ full = `${acknowledgement} `;
1496
+ yield full;
1497
+ }
1498
+ const delivery = replyOpts?.delivery ?? deriveSpokenDeliveryProfile(heard);
1499
+ const prepared = await prepareSpokenTurn(heard, history, replyOpts?.spokenPrefix ?? acknowledgement ?? undefined, delivery, replyOpts, resolvedRoute);
1500
+ const { CodeBuddyClient, route, systemPrompt } = prepared;
1501
+ reportReplyTimingPhase(replyOpts, replyOpts?.spokenPrefix ? 'continuation_prompt_ready' : 'prompt_ready');
1502
+ cognitiveLease = prepared.cognitiveLease;
1503
+ replyOpts?.onProviderResolved?.(route);
1504
+ logger.debug(`[voice] stream reply model: ${route.model} — ${route.reason}`);
1505
+ const client = new CodeBuddyClient(route.apiKey, route.model, route.baseURL);
1506
+ let continuation = '';
1507
+ let continuationYielded = 0;
1508
+ let continuationEmitted = false;
1509
+ let providerDeltaSeen = false;
1510
+ for await (const chunk of client.chatStream([
1511
+ { role: 'system', content: systemPrompt },
1512
+ ...history,
1513
+ { role: 'user', content: heard },
1514
+ ], [],
1515
+ // Additive: thread the barge-in signal so an interrupt aborts the in-flight stream.
1516
+ {
1517
+ temperature: voiceTemperature(),
1518
+ maxTokens: voiceMaxTokens(heard, history),
1519
+ ...(replyOpts?.signal ? { signal: replyOpts.signal } : {}),
1520
+ })) {
1521
+ if (replyOpts?.signal?.aborted)
1522
+ break;
1523
+ const delta = chunk?.choices?.[0]?.delta?.content;
1524
+ if (typeof delta === 'string' && delta.length > 0) {
1525
+ if (!providerDeltaSeen) {
1526
+ providerDeltaSeen = true;
1527
+ reportReplyTimingPhase(replyOpts, replyOpts?.spokenPrefix
1528
+ ? 'continuation_provider_first_delta'
1529
+ : 'provider_first_delta');
1530
+ }
1531
+ if (!acknowledgement) {
1532
+ full += delta;
1533
+ yield delta;
1534
+ continue;
1535
+ }
1536
+ // The acknowledgement is already being spoken. Forward every safe token so the
1537
+ // downstream assembler starts Pocket early, while allowing the complete discourse
1538
+ // plan instead of truncating every substantive reply after one sentence.
1539
+ continuation += delta;
1540
+ const safeLength = safeCommitLength(continuation);
1541
+ if (safeLength > continuationYielded) {
1542
+ yield continuation.slice(continuationYielded, safeLength);
1543
+ continuationYielded = safeLength;
1544
+ }
1545
+ }
1546
+ }
1547
+ if (!replyOpts?.signal?.aborted) {
1548
+ reportReplyTimingPhase(replyOpts, replyOpts?.spokenPrefix ? 'continuation_generation_complete' : 'generation_complete');
1549
+ }
1550
+ if (acknowledgement && !continuationEmitted) {
1551
+ const tail = continuation.trim();
1552
+ if (tail) {
1553
+ full += tail;
1554
+ continuationEmitted = true;
1555
+ if (continuationYielded < continuation.length) {
1556
+ yield continuation.slice(continuationYielded);
1557
+ }
1558
+ }
1559
+ }
1560
+ if (!replyOpts?.signal?.aborted && (full.trim() || continuation.trim())) {
1561
+ cognitiveLease?.commit();
1562
+ cognitiveLeaseSettled = true;
1563
+ }
1564
+ else {
1565
+ cognitiveLease?.release();
1566
+ cognitiveLeaseSettled = true;
1567
+ }
1568
+ }
1569
+ catch (err) {
1570
+ cognitiveLease?.release();
1571
+ cognitiveLeaseSettled = true;
1572
+ logger.warn(`[voice] stream reply failed: ${err instanceof Error ? err.message : String(err)}`);
1573
+ // Yields nothing → the pipeline falls back to the blocking reply.
1574
+ }
1575
+ finally {
1576
+ if (!cognitiveLeaseSettled)
1577
+ cognitiveLease?.release();
1578
+ }
1579
+ }
1580
+ /**
1581
+ * Default synth for the assistant's voice. Active engine picked from
1582
+ * Pocket TTS is the realtime default. Voicebox can render a more expressive
1583
+ * voice locally or on GPU node; Pocket and Piper remain fail-open fallbacks.
1584
+ */
1585
+ function resolveBaseCacheVoice(engine, voice, env = process.env) {
1586
+ if (engine === 'pocket') {
1587
+ const pocketVoice = env.CODEBUDDY_POCKET_VOICE?.trim() || 'estelle';
1588
+ const language = resolvePocketLanguage(env.CODEBUDDY_POCKET_LANG ?? 'french');
1589
+ const quantize = env.CODEBUDDY_POCKET_QUANTIZE === 'true';
1590
+ return `pocket:${pocketVoice}:language=${language}:quantize=${quantize}`;
1591
+ }
1592
+ if (engine === 'voicebox') {
1593
+ const voicebox = resolveVoiceboxConfig(env);
1594
+ return [
1595
+ 'voicebox',
1596
+ voicebox.baseUrl,
1597
+ voicebox.profile,
1598
+ voicebox.engine,
1599
+ voicebox.language,
1600
+ voicebox.modelSize,
1601
+ voicebox.instruct ?? '',
1602
+ ].join(':');
1603
+ }
1604
+ if (engine === 'elevenlabs') {
1605
+ return resolveElevenLabsCacheVoice(env);
1606
+ }
1607
+ if (engine === 'kyutai') {
1608
+ return resolveKyutaiCacheVoice(env);
1609
+ }
1610
+ return voice || resolveDefaultPiperVoiceModel() || 'piper:default';
1611
+ }
1612
+ function makeDefaultSynth(voice, rootDir, engine = resolveTtsEngine()) {
1613
+ const resolvedVoice = voice || resolveDefaultPiperVoiceModel();
1614
+ // Cache identity covers every acoustic input. `TtsCache` hashes this string,
1615
+ // so even a long Voicebox instruction never appears in the cache filename.
1616
+ const baseCacheVoice = resolveBaseCacheVoice(engine, resolvedVoice);
1617
+ const synthFresh = async (text, opts = {}) => {
1618
+ if (opts.signal?.aborted)
1619
+ throw new Error('TTS synthesis was interrupted');
1620
+ const prepared = prepareSpeech(text);
1621
+ if (!prepared)
1622
+ return { wav: '', cacheable: false };
1623
+ text = prepared;
1624
+ const wavPath = join(tmpdir(), `cb-voice-${process.pid}-${Date.now()}.wav`);
1625
+ let selectedEngine = engine;
1626
+ if (selectedEngine === 'kyutai') {
1627
+ const { resolveElevenLabsVoiceId, synthesizeElevenLabsWav, synthesizeKyutaiWav, synthesizePocketWav, } = await import('../voice/local-tts.js');
1628
+ if (await synthesizeKyutaiWav(text, wavPath, process.env, {
1629
+ ...(opts.signal ? { signal: opts.signal } : {}),
1630
+ ...(opts.ttsNormalizationFactor !== undefined
1631
+ ? { frozenFactor: opts.ttsNormalizationFactor }
1632
+ : {}),
1633
+ })) {
1634
+ return { wav: wavPath, cacheable: true };
1635
+ }
1636
+ if (opts.signal?.aborted)
1637
+ throw new Error('TTS synthesis was interrupted');
1638
+ if (resolveElevenLabsVoiceId(process.env)) {
1639
+ logger.warn('[voice] Kyutai synthesis failed — falling back to ElevenLabs for this phrase');
1640
+ if (await synthesizeElevenLabsWav(text, wavPath, process.env, 6_000, opts.signal, opts.ttsNormalizationFactor)) {
1641
+ return { wav: wavPath, cacheable: false };
1642
+ }
1643
+ }
1644
+ if (opts.signal?.aborted)
1645
+ throw new Error('TTS synthesis was interrupted');
1646
+ logger.warn('[voice] Kyutai/ElevenLabs synthesis failed — falling back to Pocket for this phrase');
1647
+ if (await synthesizePocketWav(text, wavPath, process.env, 180_000, opts.signal, opts.ttsNormalizationFactor)) {
1648
+ return { wav: wavPath, cacheable: false };
1649
+ }
1650
+ throw new Error('Kyutai, ElevenLabs and Pocket TTS synthesis failed');
1651
+ }
1652
+ if (selectedEngine === 'elevenlabs') {
1653
+ const { synthesizeElevenLabsWav } = await import('../voice/local-tts.js');
1654
+ if (await synthesizeElevenLabsWav(text, wavPath, process.env, 6_000, opts.signal, opts.ttsNormalizationFactor)) {
1655
+ return { wav: wavPath, cacheable: true };
1656
+ }
1657
+ if (opts.signal?.aborted)
1658
+ throw new Error('TTS synthesis was interrupted');
1659
+ // Degrade to the local engine below (Pocket by default, Piper on request)
1660
+ // instead of throwing: the throw that lived here silenced the phrase
1661
+ // outright whenever the ElevenLabs stream was refused — the "ne me parle
1662
+ // plus" of 2026-09-02 — while the documented contract promises an
1663
+ // automatic local fallback without interrupting speech.
1664
+ selectedEngine = resolveElevenLabsFallbackEngine(process.env);
1665
+ logger.warn(`[voice] ElevenLabs synthesis failed — falling back to ${selectedEngine} for this phrase`);
1666
+ }
1667
+ if (selectedEngine === 'voicebox') {
1668
+ const { synthesizeVoiceboxWav } = await import('../voice/voicebox-tts.js');
1669
+ const deliveryInstruction = opts.delivery
1670
+ ? voiceRendererDeliveryInstruction(opts.delivery)
1671
+ : undefined;
1672
+ if (await synthesizeVoiceboxWav(text, wavPath, process.env, {
1673
+ signal: opts.signal,
1674
+ ...(opts.ttsNormalizationFactor !== undefined
1675
+ ? { frozenFactor: opts.ttsNormalizationFactor }
1676
+ : {}),
1677
+ ...(deliveryInstruction ? { instruct: deliveryInstruction } : {}),
1678
+ })) {
1679
+ return { wav: wavPath, cacheable: true };
1680
+ }
1681
+ if (opts.signal?.aborted)
1682
+ throw new Error('TTS synthesis was interrupted');
1683
+ throw new Error('Voicebox TTS synthesis failed');
1684
+ }
1685
+ else if (selectedEngine === 'pocket') {
1686
+ const { synthesizePocketWav } = await import('../voice/local-tts.js');
1687
+ if (await synthesizePocketWav(text, wavPath, process.env, 180_000, opts.signal, opts.ttsNormalizationFactor)) {
1688
+ // A transient cloud failure must not pin Pocket audio under the
1689
+ // ElevenLabs cache identity after the network recovers.
1690
+ return { wav: wavPath, cacheable: engine !== 'elevenlabs' };
1691
+ }
1692
+ if (opts.signal?.aborted)
1693
+ throw new Error('TTS synthesis was interrupted');
1694
+ throw new Error('Pocket TTS synthesis failed');
1695
+ }
1696
+ if (opts.signal?.aborted)
1697
+ throw new Error('TTS synthesis was interrupted');
1698
+ const { synthesizeTextToSpeech } = await import('../tools/text-to-speech-tool.js');
1699
+ const res = await synthesizeTextToSpeech({
1700
+ text,
1701
+ provider: 'piper',
1702
+ format: 'wav',
1703
+ ...(resolvedVoice ? { voice: resolvedVoice } : {}),
1704
+ }, rootDir ? { rootDir } : {});
1705
+ await normalizeWavFile(res.outputPath, process.env, opts.ttsNormalizationFactor);
1706
+ return { wav: res.outputPath, cacheable: true };
1707
+ };
1708
+ // Reuse the synthesized WAV for repeated phrases (greeting, "oui je t'entends", …) so
1709
+ // neither engine regenerates common speech. Best-effort: any cache error falls back to a fresh
1710
+ // synth. Opt-out with CODEBUDDY_TTS_CACHE=false.
1711
+ if (process.env.CODEBUDDY_TTS_CACHE === 'false') {
1712
+ return async (text, opts) => (await synthFresh(text, opts)).wav;
1713
+ }
1714
+ return async (text, opts = {}) => {
1715
+ if (opts.signal?.aborted)
1716
+ throw new Error('TTS synthesis was interrupted');
1717
+ // A cache entry carries the gain of the turn that created it. Once the
1718
+ // current turn has frozen its own factor, synthesize fresh so it is applied
1719
+ // to raw engine output instead of compounding two independent gains.
1720
+ if (opts.ttsNormalizationFactor !== undefined &&
1721
+ engine !== 'elevenlabs' &&
1722
+ engine !== 'kyutai') {
1723
+ return (await synthFresh(text, opts)).wav;
1724
+ }
1725
+ if (text.trim().length > SHORT_SEGMENT_CACHE_MAX_CHARS &&
1726
+ engine !== 'elevenlabs' &&
1727
+ engine !== 'kyutai') {
1728
+ return (await synthFresh(text, opts)).wav;
1729
+ }
1730
+ const cacheVoice = opts.delivery && engine === 'voicebox'
1731
+ ? `${baseCacheVoice}:${voiceRendererDeliveryInstruction(opts.delivery)}`
1732
+ : baseCacheVoice;
1733
+ // Paid ElevenLabs library FIRST: 6 400+ short replies were synthesized once in
1734
+ // Lisa's real voice and are shared with MySoulmate and the phone assistant.
1735
+ // Playing one costs nothing and sounds better than any local engine. Read-only
1736
+ // and copy-on-hit — the caller unlinks what it plays, and those files were paid for.
1737
+ if (engine !== 'kyutai') {
1738
+ try {
1739
+ const { getVoiceLibrary } = await import('./elevenlabs-library.js');
1740
+ const paid = getVoiceLibrary().copyForPlayback(text);
1741
+ if (paid) {
1742
+ logger.info('[voice] paid ElevenLabs library hit');
1743
+ return paid;
1744
+ }
1745
+ }
1746
+ catch {
1747
+ /* best-effort: an unavailable library must never delay or break speaking */
1748
+ }
1749
+ }
1750
+ let cache;
1751
+ try {
1752
+ const { getTtsCache } = await import('./tts-cache.js');
1753
+ cache = getTtsCache();
1754
+ const hit = cache.lookup(text, cacheVoice); // throwaway tmp copy (caller plays+unlinks it)
1755
+ if (hit) {
1756
+ logger.info('[voice] tts cache hit');
1757
+ return hit;
1758
+ }
1759
+ }
1760
+ catch {
1761
+ return (await synthFresh(text, opts)).wav;
1762
+ }
1763
+ const fresh = await synthFresh(text, opts);
1764
+ if (fresh.cacheable) {
1765
+ try {
1766
+ cache.store(text, cacheVoice, fresh.wav); // cache copy survives the caller's unlink
1767
+ logger.info('[voice] tts cache store');
1768
+ }
1769
+ catch {
1770
+ /* cache failures never make speech fail */
1771
+ }
1772
+ }
1773
+ return fresh.wav;
1774
+ };
1775
+ }
1776
+ async function resolveVoiceAudioPlayer() {
1777
+ const candidates = [
1778
+ {
1779
+ cmd: 'aplay',
1780
+ stdinArgs: ['-q', '--buffer-time=300000', '-'],
1781
+ fileArgs: (file) => ['-q', file],
1782
+ },
1783
+ {
1784
+ cmd: 'ffplay',
1785
+ stdinArgs: ['-nodisp', '-autoexit', '-loglevel', 'quiet', '-infbuf', '-buffer_size', '300000', '-i', 'pipe:0'],
1786
+ fileArgs: (file) => ['-nodisp', '-autoexit', '-loglevel', 'quiet', file],
1787
+ },
1788
+ ];
1789
+ for (const candidate of candidates) {
1790
+ if (await commandExists(candidate.cmd))
1791
+ return candidate;
1792
+ }
1793
+ return null;
1794
+ }
1795
+ function waitForPlayerDrain(child, stdin, signal) {
1796
+ return new Promise((resolve) => {
1797
+ let settled = false;
1798
+ const finish = () => {
1799
+ if (settled)
1800
+ return;
1801
+ settled = true;
1802
+ stdin.off('drain', finish);
1803
+ child.off('close', finish);
1804
+ signal?.removeEventListener('abort', finish);
1805
+ resolve();
1806
+ };
1807
+ stdin.once('drain', finish);
1808
+ child.once('close', finish);
1809
+ signal?.addEventListener('abort', finish, { once: true });
1810
+ });
1811
+ }
1812
+ /**
1813
+ * Already-paid audio for an ElevenLabs sentence: the permanent library first
1814
+ * (6 400+ phrases synthesized once in Lisa's real voice), then the TTS cache.
1815
+ * Returns a throwaway WAV copy the caller plays and unlinks, or null to open
1816
+ * the (billed) network stream. Best-effort and never-throws — an unavailable
1817
+ * library or cache must never delay or break speaking.
1818
+ */
1819
+ async function lookupPaidElevenLabsWav(text, cacheVoice) {
1820
+ try {
1821
+ const { getVoiceLibrary } = await import('./elevenlabs-library.js');
1822
+ const paid = getVoiceLibrary().copyForPlayback(text);
1823
+ if (paid) {
1824
+ logger.info('[voice] paid ElevenLabs library hit (stream path)');
1825
+ return paid;
1826
+ }
1827
+ }
1828
+ catch {
1829
+ /* best-effort */
1830
+ }
1831
+ if (process.env.CODEBUDDY_TTS_CACHE === 'false')
1832
+ return null;
1833
+ try {
1834
+ const { getTtsCache } = await import('./tts-cache.js');
1835
+ const hit = getTtsCache().lookup(text, cacheVoice);
1836
+ if (hit)
1837
+ logger.info('[voice] tts cache hit (stream path)');
1838
+ return hit;
1839
+ }
1840
+ catch {
1841
+ return null;
1842
+ }
1843
+ }
1844
+ /** Project TTS-cache lookup without consulting the paid ElevenLabs library. */
1845
+ async function lookupTtsCacheWav(text, cacheVoice) {
1846
+ if (process.env.CODEBUDDY_TTS_CACHE === 'false')
1847
+ return null;
1848
+ try {
1849
+ const { getTtsCache } = await import('./tts-cache.js');
1850
+ const hit = getTtsCache().lookup(text, cacheVoice);
1851
+ if (hit)
1852
+ logger.info('[voice] tts cache hit (stream path)');
1853
+ return hit;
1854
+ }
1855
+ catch {
1856
+ return null;
1857
+ }
1858
+ }
1859
+ /**
1860
+ * Persist a completed streamed ElevenLabs clip into the TTS cache (same WAV
1861
+ * shape as the blocking path: 24 kHz PCM16 container + file-level RMS
1862
+ * normalization), so the NEXT occurrence of this phrase plays from disk for
1863
+ * free instead of being re-billed. Fire-and-forget; failures only log.
1864
+ */
1865
+ async function storePcmStreamInTtsCache(text, cacheVoice, pcm, frozenFactor) {
1866
+ if (process.env.CODEBUDDY_TTS_CACHE === 'false')
1867
+ return;
1868
+ if (pcm.length < 2)
1869
+ return;
1870
+ try {
1871
+ const { pcm16Mono24kToWav } = await import('../voice/local-tts.js');
1872
+ const wav = normalizePcm16Wav(pcm16Mono24kToWav(pcm), process.env, frozenFactor);
1873
+ const tmp = join(tmpdir(), `cb-voice-pcmstream-${process.pid}-${Date.now()}.wav`);
1874
+ await writeFile(tmp, wav, { mode: 0o600 });
1875
+ try {
1876
+ const { getTtsCache } = await import('./tts-cache.js');
1877
+ getTtsCache().store(text, cacheVoice, tmp);
1878
+ logger.info('[voice] tts cache store (stream path)');
1879
+ }
1880
+ finally {
1881
+ await unlink(tmp).catch(() => undefined);
1882
+ }
1883
+ }
1884
+ catch (err) {
1885
+ logger.debug(`[voice] PCM stream cache store failed: ${err instanceof Error ? err.message : String(err)}`);
1886
+ }
1887
+ }
1888
+ /**
1889
+ * Pocket and Voicebox expose WAV response streams; ElevenLabs exposes a raw
1890
+ * PCM `/stream` endpoint that `openElevenLabsAudioStream` wraps in a streaming
1891
+ * WAV header. Pipe the selected one into a stdin-capable player so the first
1892
+ * PCM frame is heard while the engine is still generating the rest, instead of
1893
+ * calling `arrayBuffer()` and waiting for the complete clip. Any setup/runtime
1894
+ * failure returns false and the caller uses the established temporary-WAV
1895
+ * fallback (which itself falls back Pocket/Piper — never silence).
1896
+ */
1897
+ function makeDefaultStreamSpeak(playerPromise = resolveVoiceAudioPlayer(), engine = resolveTtsEngine()) {
1898
+ const streamEnabled = engine === 'voicebox'
1899
+ ? process.env.CODEBUDDY_VOICEBOX_AUDIO_STREAM !== 'false'
1900
+ : engine === 'elevenlabs'
1901
+ ? process.env.CODEBUDDY_ELEVENLABS_AUDIO_STREAM !== 'false'
1902
+ : engine === 'kyutai'
1903
+ ? process.env.CODEBUDDY_TTS_LOCAL_AUDIO_STREAM !== 'false'
1904
+ : process.env.CODEBUDDY_POCKET_AUDIO_STREAM !== 'false';
1905
+ if ((engine !== 'pocket' && engine !== 'voicebox' && engine !== 'elevenlabs' && engine !== 'kyutai') ||
1906
+ !streamEnabled) {
1907
+ return undefined;
1908
+ }
1909
+ let turnFactor;
1910
+ const prefetched = new Map();
1911
+ const PREFETCH_TTL_MS = 45_000;
1912
+ const purgeStalePrefetches = () => {
1913
+ const now = Date.now();
1914
+ for (const [key, entry] of prefetched) {
1915
+ if (now - entry.createdAt > PREFETCH_TTL_MS) {
1916
+ prefetched.delete(key);
1917
+ entry.cancel();
1918
+ }
1919
+ }
1920
+ };
1921
+ const takePrefetched = (key) => {
1922
+ const entry = prefetched.get(key);
1923
+ if (entry)
1924
+ prefetched.delete(key);
1925
+ return entry;
1926
+ };
1927
+ const prefetch = (text, opts = {}) => {
1928
+ if (engine !== 'elevenlabs' || opts.signal?.aborted)
1929
+ return;
1930
+ const prepared = prepareSpeech(text);
1931
+ if (!prepared || prefetched.has(prepared))
1932
+ return;
1933
+ purgeStalePrefetches();
1934
+ const cacheVoice = resolveBaseCacheVoice(engine);
1935
+ let cancelled = false;
1936
+ let opened = null;
1937
+ let wavPath;
1938
+ const entry = {
1939
+ createdAt: Date.now(),
1940
+ cancel: () => {
1941
+ cancelled = true;
1942
+ if (opened)
1943
+ void opened.cancel().catch(() => undefined);
1944
+ if (wavPath)
1945
+ void unlink(wavPath).catch(() => undefined);
1946
+ },
1947
+ ready: (async () => {
1948
+ const cached = await lookupPaidElevenLabsWav(prepared, cacheVoice);
1949
+ if (cached) {
1950
+ wavPath = cached;
1951
+ return { wav: cached };
1952
+ }
1953
+ if (cancelled)
1954
+ return {};
1955
+ const { openElevenLabsAudioStream } = await import('../voice/local-tts.js');
1956
+ const stream = await openElevenLabsAudioStream(prepared, process.env, {
1957
+ ...(opts.signal ? { signal: opts.signal } : {}),
1958
+ onPcmComplete: (pcm) => {
1959
+ void storePcmStreamInTtsCache(prepared, cacheVoice, pcm, turnFactor);
1960
+ },
1961
+ });
1962
+ if (cancelled) {
1963
+ if (stream)
1964
+ void stream.cancel().catch(() => undefined);
1965
+ return {};
1966
+ }
1967
+ opened = stream;
1968
+ return { stream };
1969
+ })().catch(() => ({})),
1970
+ };
1971
+ prefetched.set(prepared, entry);
1972
+ opts.signal?.addEventListener('abort', () => {
1973
+ if (prefetched.get(prepared) === entry)
1974
+ prefetched.delete(prepared);
1975
+ entry.cancel();
1976
+ }, { once: true });
1977
+ };
1978
+ const speak = async (text, opts = {}) => {
1979
+ const signal = opts.signal;
1980
+ if (signal?.aborted)
1981
+ return false;
1982
+ const player = await playerPromise;
1983
+ if (!player)
1984
+ return false;
1985
+ const prepared = prepareSpeech(text);
1986
+ if (!prepared)
1987
+ return false;
1988
+ text = prepared;
1989
+ const baseCacheVoice = resolveBaseCacheVoice(engine);
1990
+ const cacheVoice = opts.delivery && engine === 'voicebox'
1991
+ ? `${baseCacheVoice}:${voiceRendererDeliveryInstruction(opts.delivery)}`
1992
+ : baseCacheVoice;
1993
+ // A look-ahead opened for this exact sentence is consumed first: its paid
1994
+ // copy or its already-arriving stream. A prefetch that failed to open falls
1995
+ // through to the regular lookup + open below (one honest retry).
1996
+ const lookahead = engine === 'elevenlabs' ? takePrefetched(text) : undefined;
1997
+ const lookaheadReady = lookahead ? await lookahead.ready : undefined;
1998
+ if (signal?.aborted) {
1999
+ lookahead?.cancel();
2000
+ return false;
2001
+ }
2002
+ let prefetchedStream = null;
2003
+ let cachedWav = null;
2004
+ if (lookaheadReady?.wav) {
2005
+ cachedWav = lookaheadReady.wav;
2006
+ }
2007
+ else if (lookaheadReady?.stream) {
2008
+ prefetchedStream = lookaheadReady.stream;
2009
+ }
2010
+ else {
2011
+ // ElevenLabs is billed per character: a phrase already paid for — in the
2012
+ // 6 400+ entry permanent library or in the TTS cache — must NEVER reopen
2013
+ // the network stream. Local engines keep the narrower backchannel-only
2014
+ // lookup (a fresh local synth is free and streams faster than a file copy).
2015
+ cachedWav = engine === 'elevenlabs'
2016
+ ? await lookupPaidElevenLabsWav(text, cacheVoice)
2017
+ : engine === 'kyutai'
2018
+ ? await lookupTtsCacheWav(text, cacheVoice)
2019
+ : await lookupInstantBackchannelWav(text, process.env, undefined, cacheVoice);
2020
+ }
2021
+ if (cachedWav) {
2022
+ try {
2023
+ // The player starts reading a ready local WAV immediately: no HTTP,
2024
+ // model queue, or synthesis step remains on this acknowledgement.
2025
+ opts.onFirstAudio?.();
2026
+ await defaultPlay(cachedWav, {
2027
+ signal,
2028
+ alreadyNormalized: true,
2029
+ ...(opts.prependInterSentenceSilence ? { prependInterSentenceSilence: true } : {}),
2030
+ }, playerPromise);
2031
+ if (engine !== 'elevenlabs')
2032
+ logger.info('[voice] instant backchannel cache hit');
2033
+ return !signal?.aborted;
2034
+ }
2035
+ finally {
2036
+ try {
2037
+ await unlink(cachedWav);
2038
+ }
2039
+ catch {
2040
+ /* throwaway cache copy */
2041
+ }
2042
+ }
2043
+ }
2044
+ const preparedText = text;
2045
+ const stream = prefetchedStream ?? (engine === 'voicebox'
2046
+ ? await (async () => {
2047
+ const { openVoiceboxAudioStream } = await import('../voice/voicebox-tts.js');
2048
+ return openVoiceboxAudioStream(text, process.env, {
2049
+ signal,
2050
+ ...(opts.delivery
2051
+ ? { instruct: voiceRendererDeliveryInstruction(opts.delivery) }
2052
+ : {}),
2053
+ });
2054
+ })()
2055
+ : engine === 'elevenlabs'
2056
+ ? await (async () => {
2057
+ const { openElevenLabsAudioStream } = await import('../voice/local-tts.js');
2058
+ return openElevenLabsAudioStream(text, process.env, {
2059
+ ...(signal ? { signal } : {}),
2060
+ // Every streamed character was billed — write the completed clip
2061
+ // back to the TTS cache so repeating the phrase costs zero.
2062
+ onPcmComplete: (pcm) => {
2063
+ void storePcmStreamInTtsCache(preparedText, cacheVoice, pcm, opts.ttsNormalizationFactor);
2064
+ },
2065
+ });
2066
+ })()
2067
+ : engine === 'kyutai'
2068
+ ? await (async () => {
2069
+ const { openKyutaiAudioStream } = await import('../voice/local-tts.js');
2070
+ return openKyutaiAudioStream(text, process.env, {
2071
+ ...(signal ? { signal } : {}),
2072
+ onPcmComplete: (pcm) => {
2073
+ void storePcmStreamInTtsCache(preparedText, cacheVoice, pcm, opts.ttsNormalizationFactor);
2074
+ },
2075
+ });
2076
+ })()
2077
+ : await (async () => {
2078
+ const { openPocketAudioStream } = await import('../voice/local-tts.js');
2079
+ return openPocketAudioStream(text, process.env, { signal });
2080
+ })());
2081
+ if (!stream || signal?.aborted)
2082
+ return false;
2083
+ const child = spawn(player.cmd, player.stdinArgs, { stdio: ['pipe', 'ignore', 'ignore'] });
2084
+ const stdin = child.stdin;
2085
+ if (!stdin) {
2086
+ try {
2087
+ child.kill('SIGKILL');
2088
+ }
2089
+ catch {
2090
+ /* failed before the pipe was created */
2091
+ }
2092
+ return false;
2093
+ }
2094
+ const reader = stream.getReader();
2095
+ const gain = new Pcm16WavStreamGain(process.env, opts.ttsNormalizationFactor ?? turnFactor);
2096
+ const edges = new Pcm16WavStreamEdges({
2097
+ prependSilenceMs: opts.prependInterSentenceSilence ? 280 : 0,
2098
+ });
2099
+ let firstAudio = false;
2100
+ let closedOk = false;
2101
+ let settled = false;
2102
+ let headReleaseTimer;
2103
+ const configuredHeadTimeoutMs = Number(process.env.CODEBUDDY_TTS_STREAM_HEAD_TIMEOUT_MS);
2104
+ const headTimeoutMs = Number.isFinite(configuredHeadTimeoutMs) && configuredHeadTimeoutMs > 0
2105
+ ? Math.max(50, Math.min(1_000, configuredHeadTimeoutMs))
2106
+ : 250;
2107
+ const jitterBufferMs = resolveVoiceJitterBufferMs(process.env);
2108
+ let jitterPrimed = jitterBufferMs <= 0;
2109
+ let jitterQueue = [];
2110
+ let jitterAudioBytes = 0;
2111
+ let detectedByteRate = 48_000;
2112
+ let jitterReleaseTimer;
2113
+ let playerSawWavHeader = false;
2114
+ let playerPcmBytes = 0;
2115
+ const writePlayerChunk = (part) => {
2116
+ opts.onAudioChunk?.(part);
2117
+ const accepted = stdin.write(part);
2118
+ if (part.length >= 12 && part.subarray(0, 4).toString('ascii') === 'RIFF') {
2119
+ const probe = probePcm16Wav(part);
2120
+ if (probe.status === 'ready') {
2121
+ playerSawWavHeader = true;
2122
+ detectedByteRate = probe.layout.byteRate;
2123
+ playerPcmBytes += Math.max(0, part.length - probe.layout.dataOffset);
2124
+ }
2125
+ }
2126
+ else if (playerSawWavHeader) {
2127
+ playerPcmBytes += part.length;
2128
+ }
2129
+ if (playerPcmBytes > 0) {
2130
+ opts.onAudioProgress?.({ pcmBytes: playerPcmBytes, byteRate: detectedByteRate });
2131
+ }
2132
+ return accepted;
2133
+ };
2134
+ const targetJitterBytes = () => Math.round((detectedByteRate * jitterBufferMs) / 1_000);
2135
+ const flushJitterBuffer = (force = false) => {
2136
+ if (!force && jitterAudioBytes === 0)
2137
+ return true;
2138
+ if (jitterReleaseTimer) {
2139
+ clearTimeout(jitterReleaseTimer);
2140
+ jitterReleaseTimer = undefined;
2141
+ }
2142
+ jitterPrimed = true;
2143
+ if (jitterQueue.length === 0)
2144
+ return true;
2145
+ let accepted = true;
2146
+ const partsToWrite = jitterQueue;
2147
+ jitterQueue = [];
2148
+ jitterAudioBytes = 0;
2149
+ const firstPart = partsToWrite[0];
2150
+ const secondPart = partsToWrite[1];
2151
+ if (firstPart &&
2152
+ secondPart &&
2153
+ firstPart.length <= 1024 &&
2154
+ firstPart.length >= 12 &&
2155
+ firstPart.subarray(0, 4).toString('ascii') === 'RIFF') {
2156
+ const probe = probePcm16Wav(firstPart);
2157
+ if (probe.status === 'ready' && firstPart.length === probe.layout.dataOffset) {
2158
+ partsToWrite.shift();
2159
+ partsToWrite[0] = Buffer.concat([firstPart, secondPart]);
2160
+ }
2161
+ }
2162
+ for (const part of partsToWrite) {
2163
+ accepted = writePlayerChunk(part) && accepted;
2164
+ }
2165
+ if (!firstAudio && edges.hasOutputAudio()) {
2166
+ firstAudio = true;
2167
+ opts.onFirstAudio?.();
2168
+ }
2169
+ return accepted;
2170
+ };
2171
+ const writePlayerParts = (parts) => {
2172
+ if (jitterPrimed) {
2173
+ let accepted = true;
2174
+ for (const part of parts) {
2175
+ accepted = writePlayerChunk(part) && accepted;
2176
+ }
2177
+ if (!firstAudio && edges.hasOutputAudio()) {
2178
+ firstAudio = true;
2179
+ opts.onFirstAudio?.();
2180
+ }
2181
+ return accepted;
2182
+ }
2183
+ for (const part of parts) {
2184
+ if (part.length === 0)
2185
+ continue;
2186
+ if (part.length >= 12 && part.subarray(0, 4).toString('ascii') === 'RIFF') {
2187
+ const probe = probePcm16Wav(part);
2188
+ if (probe.status === 'ready') {
2189
+ detectedByteRate = probe.layout.byteRate;
2190
+ jitterAudioBytes += Math.max(0, part.length - probe.layout.dataOffset);
2191
+ }
2192
+ else {
2193
+ jitterAudioBytes += Math.max(0, part.length - 44);
2194
+ }
2195
+ }
2196
+ else {
2197
+ jitterAudioBytes += part.length;
2198
+ }
2199
+ jitterQueue.push(part);
2200
+ }
2201
+ if (jitterAudioBytes >= targetJitterBytes()) {
2202
+ return flushJitterBuffer();
2203
+ }
2204
+ if (!jitterReleaseTimer && jitterBufferMs > 0 && jitterAudioBytes > 0) {
2205
+ jitterReleaseTimer = setTimeout(() => {
2206
+ jitterReleaseTimer = undefined;
2207
+ if (settled || signal?.aborted)
2208
+ return;
2209
+ try {
2210
+ flushJitterBuffer(true);
2211
+ }
2212
+ catch {
2213
+ /* best effort */
2214
+ }
2215
+ }, jitterBufferMs);
2216
+ }
2217
+ return true;
2218
+ };
2219
+ const writeGainParts = (parts) => {
2220
+ let accepted = true;
2221
+ for (const part of parts)
2222
+ accepted = writePlayerParts(edges.push(part)) && accepted;
2223
+ if (turnFactor === undefined && gain.factor !== undefined) {
2224
+ turnFactor = gain.factor;
2225
+ opts.onTtsNormalizationFactor?.(turnFactor);
2226
+ }
2227
+ return accepted;
2228
+ };
2229
+ const scheduleHeadRelease = () => {
2230
+ if (headReleaseTimer || gain.hasOutputAudio())
2231
+ return;
2232
+ headReleaseTimer = setTimeout(() => {
2233
+ headReleaseTimer = undefined;
2234
+ if (settled || signal?.aborted)
2235
+ return;
2236
+ try {
2237
+ // A slow/stalled HTTP body must not hold the look-ahead forever.
2238
+ // The small partial head is safe to write without awaiting backpressure.
2239
+ writeGainParts(gain.releaseHead());
2240
+ flushJitterBuffer();
2241
+ }
2242
+ catch (err) {
2243
+ logger.debug(`[voice] streaming head release failed open: ${err instanceof Error ? err.message : String(err)}`);
2244
+ }
2245
+ }, headTimeoutMs);
2246
+ };
2247
+ const closed = new Promise((resolve) => {
2248
+ const finish = (ok) => {
2249
+ if (settled)
2250
+ return;
2251
+ settled = true;
2252
+ closedOk = ok;
2253
+ resolve();
2254
+ };
2255
+ child.once('error', () => finish(false));
2256
+ child.once('close', (code) => finish(code === 0));
2257
+ });
2258
+ // A player may close early (bad device/header); never let EPIPE become an
2259
+ // unhandled process error while the fetch body is still arriving.
2260
+ stdin.on('error', () => undefined);
2261
+ const timeoutMs = Number(process.env.CODEBUDDY_VOICE_PLAY_TIMEOUT_MS) || 60_000;
2262
+ const killTimer = setTimeout(() => {
2263
+ logger.warn(`[voice] streaming player ${player.cmd} exceeded ${timeoutMs}ms — killing it`);
2264
+ try {
2265
+ child.kill('SIGKILL');
2266
+ }
2267
+ catch {
2268
+ /* already stopped */
2269
+ }
2270
+ }, timeoutMs);
2271
+ const onAbort = () => {
2272
+ void reader.cancel().catch(() => undefined);
2273
+ try {
2274
+ child.kill('SIGKILL');
2275
+ }
2276
+ catch {
2277
+ /* already stopped */
2278
+ }
2279
+ };
2280
+ signal?.addEventListener('abort', onAbort, { once: true });
2281
+ try {
2282
+ while (!signal?.aborted && !settled) {
2283
+ const { done, value } = await reader.read();
2284
+ if (done)
2285
+ break;
2286
+ const accepted = writeGainParts(gain.push(value));
2287
+ scheduleHeadRelease();
2288
+ if (!accepted)
2289
+ await waitForPlayerDrain(child, stdin, signal);
2290
+ }
2291
+ if (headReleaseTimer)
2292
+ clearTimeout(headReleaseTimer);
2293
+ headReleaseTimer = undefined;
2294
+ if (jitterReleaseTimer)
2295
+ clearTimeout(jitterReleaseTimer);
2296
+ jitterReleaseTimer = undefined;
2297
+ if (!writeGainParts(gain.flush()))
2298
+ await waitForPlayerDrain(child, stdin, signal);
2299
+ if (!writePlayerParts(edges.flush()))
2300
+ await waitForPlayerDrain(child, stdin, signal);
2301
+ if (!flushJitterBuffer())
2302
+ await waitForPlayerDrain(child, stdin, signal);
2303
+ if (!stdin.destroyed)
2304
+ stdin.end();
2305
+ await closed;
2306
+ return firstAudio && closedOk && !signal?.aborted;
2307
+ }
2308
+ catch (err) {
2309
+ if (!signal?.aborted) {
2310
+ logger.debug(`[voice] ${engine} audio pipe failed: ${err instanceof Error ? err.message : String(err)}`);
2311
+ }
2312
+ try {
2313
+ child.kill('SIGKILL');
2314
+ }
2315
+ catch {
2316
+ /* already stopped */
2317
+ }
2318
+ return false;
2319
+ }
2320
+ finally {
2321
+ clearTimeout(killTimer);
2322
+ if (headReleaseTimer)
2323
+ clearTimeout(headReleaseTimer);
2324
+ if (jitterReleaseTimer)
2325
+ clearTimeout(jitterReleaseTimer);
2326
+ signal?.removeEventListener('abort', onAbort);
2327
+ try {
2328
+ await reader.cancel();
2329
+ }
2330
+ catch {
2331
+ /* already consumed/cancelled */
2332
+ }
2333
+ }
2334
+ };
2335
+ return engine === 'elevenlabs' ? Object.assign(speak, { prefetch }) : speak;
2336
+ }
2337
+ const ESTIMATED_TTS_WORD_MS = 400;
2338
+ /** Resume on a lexical boundary after the PCM duration already accepted by the player. */
2339
+ function resumeTextAfterStreamFailure(text, progress) {
2340
+ if (!progress || progress.pcmBytes <= 0 || progress.byteRate <= 0)
2341
+ return text;
2342
+ const words = [...text.matchAll(/\S+/gu)];
2343
+ if (words.length <= 1)
2344
+ return '';
2345
+ const playedMs = progress.pcmBytes / progress.byteRate * 1_000;
2346
+ const completedWords = Math.max(1, Math.floor(playedMs / ESTIMATED_TTS_WORD_MS));
2347
+ const resumeAt = words[Math.min(completedWords, words.length - 1)]?.index;
2348
+ return resumeAt === undefined ? '' : text.slice(resumeAt).trimStart();
2349
+ }
2350
+ /** Select one provider per segment while retaining the pre-DARK3 path verbatim when disabled. */
2351
+ function makeRoutedSynth(voice, rootDir, engine = resolveTtsEngine(), env = process.env) {
2352
+ const established = makeDefaultSynth(voice, rootDir, engine);
2353
+ if (!twoSpeedTtsEnabled(env) && engine !== 'kyutai')
2354
+ return established;
2355
+ const local = engine === 'kyutai' ? established : makeDefaultSynth(voice, rootDir, 'kyutai');
2356
+ const cloud = engine === 'elevenlabs' ? established : makeDefaultSynth(voice, rootDir, 'elevenlabs');
2357
+ return async (text, opts = {}) => {
2358
+ const decision = twoSpeedTtsEnabled(env)
2359
+ ? selectTwoSpeedTtsRoute(text, env, opts.ttsRouteHint)
2360
+ : { route: 'local', reason: 'configured-engine' };
2361
+ if (decision.route === 'default')
2362
+ return established(text, opts);
2363
+ logger.info(`[voice] route=${decision.route} reason=${decision.reason}`);
2364
+ return decision.route === 'local' ? local(text, opts) : cloud(text, opts);
2365
+ };
2366
+ }
2367
+ /** Progressive Kyutai → ElevenLabs → Pocket chain without replaying accepted local PCM. */
2368
+ function makeRoutedStreamSpeak(playerPromise = resolveVoiceAudioPlayer(), engine = resolveTtsEngine(), env = process.env) {
2369
+ const established = makeDefaultStreamSpeak(playerPromise, engine);
2370
+ if (!twoSpeedTtsEnabled(env) && engine !== 'kyutai')
2371
+ return established;
2372
+ const local = engine === 'kyutai'
2373
+ ? established
2374
+ : makeDefaultStreamSpeak(playerPromise, 'kyutai');
2375
+ const cloud = engine === 'elevenlabs'
2376
+ ? established
2377
+ : makeDefaultStreamSpeak(playerPromise, 'elevenlabs');
2378
+ const pocket = makeDefaultStreamSpeak(playerPromise, 'pocket');
2379
+ const speak = async (text, opts = {}) => {
2380
+ const decision = twoSpeedTtsEnabled(env)
2381
+ ? selectTwoSpeedTtsRoute(text, env, opts.ttsRouteHint)
2382
+ : { route: 'local', reason: 'configured-engine' };
2383
+ if (decision.route === 'default')
2384
+ return established?.(text, opts) ?? false;
2385
+ logger.info(`[voice] route=${decision.route} reason=${decision.reason}`);
2386
+ if (decision.route === 'elevenlabs') {
2387
+ if (await cloud?.(text, opts))
2388
+ return true;
2389
+ if (opts.signal?.aborted)
2390
+ return false;
2391
+ logger.warn('[voice] ElevenLabs stream failed — falling back to Pocket for this phrase');
2392
+ return await pocket?.(text, opts) ?? false;
2393
+ }
2394
+ let localProgress;
2395
+ const localOptions = {
2396
+ ...opts,
2397
+ onAudioProgress: (progress) => {
2398
+ localProgress = progress;
2399
+ opts.onAudioProgress?.(progress);
2400
+ },
2401
+ };
2402
+ if (await local?.(text, localOptions))
2403
+ return true;
2404
+ if (opts.signal?.aborted)
2405
+ return false;
2406
+ const fallbackText = resumeTextAfterStreamFailure(text, localProgress);
2407
+ if (!fallbackText) {
2408
+ logger.warn('[voice] Kyutai stream failed after partial playback — no safe text remains');
2409
+ return localProgress !== undefined;
2410
+ }
2411
+ logger.warn(localProgress
2412
+ ? '[voice] Kyutai stream failed — resuming with ElevenLabs after played audio'
2413
+ : '[voice] Kyutai stream failed — falling back to ElevenLabs for this phrase');
2414
+ if (await cloud?.(fallbackText, opts))
2415
+ return true;
2416
+ if (opts.signal?.aborted)
2417
+ return false;
2418
+ logger.warn('[voice] Kyutai/ElevenLabs stream failed — falling back to Pocket for this phrase');
2419
+ return await pocket?.(fallbackText, opts) ?? false;
2420
+ };
2421
+ speak.prefetch = (text, opts = {}) => {
2422
+ const decision = twoSpeedTtsEnabled(env)
2423
+ ? selectTwoSpeedTtsRoute(text, env)
2424
+ : { route: 'local' };
2425
+ if (decision.route === 'elevenlabs')
2426
+ cloud?.prefetch?.(text, opts);
2427
+ else if (decision.route === 'default')
2428
+ established?.prefetch?.(text, opts);
2429
+ };
2430
+ return speak;
2431
+ }
2432
+ /** Default speak: play a WAV with the first available local player, blocking until done.
2433
+ * Interruptible: when `opts.signal` aborts (barge-in), the audio child is SIGKILLed and
2434
+ * the play resolves immediately so the ear can re-open. */
2435
+ async function defaultPlay(wav, opts = {}, playerPromise = resolveVoiceAudioPlayer()) {
2436
+ const signal = opts.signal;
2437
+ // Already interrupted before we even start → don't spawn anything.
2438
+ if (signal?.aborted)
2439
+ return;
2440
+ if (!opts.alreadyNormalized)
2441
+ await normalizeWavFile(wav, process.env);
2442
+ try {
2443
+ const source = await readFile(wav);
2444
+ const conditioned = conditionPcm16Wav(source, {
2445
+ prependSilenceMs: opts.prependInterSentenceSilence ? 280 : 0,
2446
+ });
2447
+ if (!conditioned.equals(source))
2448
+ await writeFile(wav, conditioned, { mode: 0o600 });
2449
+ }
2450
+ catch {
2451
+ // Edge conditioning is best-effort; playback must retain its fail-open contract.
2452
+ }
2453
+ const player = await playerPromise;
2454
+ if (!player) {
2455
+ logger.warn('[voice] no audio player available (aplay/ffplay) — staying silent');
2456
+ return;
2457
+ }
2458
+ // A player that blocks instead of exiting (malformed WAV with a huge declared duration, an ALSA
2459
+ // device that hangs) would never resolve this promise. Under withSpeakingGuard that latches
2460
+ // isSpeaking()=true forever, so the speech reaction would drop every utterance and the robot goes
2461
+ // permanently deaf. A generous timeout (far beyond any real spoken line) kills the child and
2462
+ // recovers.
2463
+ const playTimeoutMs = Number(process.env.CODEBUDDY_VOICE_PLAY_TIMEOUT_MS) || 60_000;
2464
+ await new Promise((resolve) => {
2465
+ const child = spawn(player.cmd, player.fileArgs(wav), { stdio: 'ignore' });
2466
+ let settled = false;
2467
+ const finish = () => {
2468
+ if (settled)
2469
+ return;
2470
+ settled = true;
2471
+ clearTimeout(killTimer);
2472
+ signal?.removeEventListener('abort', onAbort);
2473
+ resolve();
2474
+ };
2475
+ const killTimer = setTimeout(() => {
2476
+ logger.warn(`[voice] player ${player.cmd} exceeded ${playTimeoutMs}ms — killing to avoid latching the speaking guard`);
2477
+ try {
2478
+ child.kill('SIGKILL');
2479
+ }
2480
+ catch {
2481
+ /* already gone */
2482
+ }
2483
+ finish();
2484
+ }, playTimeoutMs);
2485
+ // Barge-in: the same SIGKILL, but on demand instead of only on timeout.
2486
+ const onAbort = () => {
2487
+ logger.info(`[voice] playback interrupted — killing ${player.cmd}`);
2488
+ try {
2489
+ child.kill('SIGKILL');
2490
+ }
2491
+ catch {
2492
+ /* already gone */
2493
+ }
2494
+ finish();
2495
+ };
2496
+ signal?.addEventListener('abort', onAbort, { once: true });
2497
+ child.on('error', finish);
2498
+ child.on('close', finish);
2499
+ });
2500
+ }
2501
+ /** Narrow test seam for verifying the shared per-turn player contract. */
2502
+ export const __voiceAudioPlayerTest = {
2503
+ resolveVoiceAudioPlayer,
2504
+ resolveVoiceJitterBufferMs,
2505
+ resolveBaseCacheVoice,
2506
+ makeDefaultSynth,
2507
+ makeDefaultStreamSpeak,
2508
+ makeRoutedSynth,
2509
+ makeRoutedStreamSpeak,
2510
+ resumeTextAfterStreamFailure,
2511
+ defaultPlay,
2512
+ };
2513
+ /**
2514
+ * Speak an arbitrary string aloud RIGHT NOW (proactively), not as a reply to something heard.
2515
+ * The missing primitive for reminders/announcements: synthesize → play → clean up.
2516
+ * Injectable synth/play for tests. Never-throws ($0 with local Pocket/Piper).
2517
+ * Returns true only when the local player actually ran.
2518
+ */
2519
+ export async function sayNow(text, options = {}) {
2520
+ // Sanity gate before the speakers AND the phone push: strip leaked control tokens + foreign-script
2521
+ // contamination (a local model drifting into CJK the voice can't pronounce), stay silent if nothing
2522
+ // meaningful remains. Clean once so speech, Telegram voice, and logs all use the same text.
2523
+ const t = prepareSpeech(text);
2524
+ if (!t) {
2525
+ if ((text ?? '').trim()) {
2526
+ logger.info(`[voice] sayNow muted after sanitize inputChars=${(text ?? '').length}`);
2527
+ }
2528
+ return false;
2529
+ }
2530
+ // A persona-specific .onnx remains meaningful for the Piper fallback. Pocket uses its
2531
+ // own preset/clone selection from CODEBUDDY_POCKET_VOICE.
2532
+ let voice = options.voice;
2533
+ if (!voice && !options.synth) {
2534
+ try {
2535
+ const { getActivePersonaVoiceAsync } = await import('../personas/persona-manager.js');
2536
+ voice = (await getActivePersonaVoiceAsync()).voice;
2537
+ }
2538
+ catch {
2539
+ /* keep env default */
2540
+ }
2541
+ }
2542
+ // 1. Home speakers (best-effort — a missing audio device must not block the phone push).
2543
+ let played = false;
2544
+ try {
2545
+ const synth = options.synth ?? makeRoutedSynth(voice, options.rootDir);
2546
+ const play = options.play ?? defaultPlay;
2547
+ const wav = await synth(t, {
2548
+ signal: options.signal,
2549
+ ...(options.ttsRouteHint ? { ttsRouteHint: options.ttsRouteHint } : {}),
2550
+ });
2551
+ if (wav) {
2552
+ // Half-duplex: mute the ear while speaking. The signal lets barge-in kill this player too.
2553
+ await withSpeakingGuard(() => {
2554
+ noteSpokenText(t);
2555
+ return play(wav, {
2556
+ signal: options.signal,
2557
+ alreadyNormalized: options.synth === undefined,
2558
+ });
2559
+ });
2560
+ played = true;
2561
+ try {
2562
+ const { unlink } = await import('fs/promises');
2563
+ await unlink(wav);
2564
+ }
2565
+ catch {
2566
+ /* leave the file if cleanup fails */
2567
+ }
2568
+ }
2569
+ }
2570
+ catch (err) {
2571
+ logger.warn(`[voice] sayNow (local) failed: ${err instanceof Error ? err.message : String(err)}`);
2572
+ }
2573
+ // 2. Phone — when traveling, push the same line as a Telegram VOICE NOTE so it reaches you
2574
+ // even with no one at the speakers. Opt-in, best-effort.
2575
+ if (options.phoneDelivery !== 'never' &&
2576
+ process.env.CODEBUDDY_VOICE_TO_TELEGRAM === 'true') {
2577
+ try {
2578
+ const { sendTelegramVoice } = await import('./alert.js');
2579
+ await sendTelegramVoice(t);
2580
+ }
2581
+ catch (err) {
2582
+ logger.warn(`[voice] sayNow (telegram) failed: ${err instanceof Error ? err.message : String(err)}`);
2583
+ }
2584
+ }
2585
+ return played;
2586
+ }
2587
+ let conversationCueTempSequence = 0;
2588
+ /**
2589
+ * Play a pre-recorded repository WAV without invoking TTS. The source asset is
2590
+ * copied to a throwaway attenuated WAV so playback never mutates the cache.
2591
+ */
2592
+ export async function playCachedConversationCue(cue) {
2593
+ if (cue.signal.aborted || !existsSync(cue.assetPath))
2594
+ return false;
2595
+ const factor = 10 ** (cue.gainDb / 20);
2596
+ const tempPath = join(tmpdir(), `cb-conversation-cue-${process.pid}-${Date.now()}-${++conversationCueTempSequence}.wav`);
2597
+ try {
2598
+ const source = await readFile(cue.assetPath);
2599
+ if (cue.signal.aborted)
2600
+ return false;
2601
+ const attenuated = normalizePcm16Wav(source, process.env, factor);
2602
+ if (attenuated.equals(source) && factor !== 1) {
2603
+ logger.warn(`[voice] conversation cue is not a supported PCM16 WAV: ${cue.assetPath}`);
2604
+ return false;
2605
+ }
2606
+ await writeFile(tempPath, attenuated, { mode: 0o600 });
2607
+ if (cue.signal.aborted)
2608
+ return false;
2609
+ await withSpeakingGuard(() => {
2610
+ noteSpokenText(cue.text);
2611
+ return defaultPlay(tempPath, {
2612
+ signal: cue.signal,
2613
+ alreadyNormalized: true,
2614
+ });
2615
+ });
2616
+ return !cue.signal.aborted;
2617
+ }
2618
+ catch (error) {
2619
+ logger.warn(`[voice] cached conversation cue failed: ${error instanceof Error ? error.message : String(error)}`);
2620
+ return false;
2621
+ }
2622
+ finally {
2623
+ try {
2624
+ await unlink(tempPath);
2625
+ }
2626
+ catch {
2627
+ /* no temporary cue was created, or cleanup can remain best-effort */
2628
+ }
2629
+ }
2630
+ }
2631
+ /**
2632
+ * Build an `onHeard` handler that thinks then speaks, with a programmatic `interrupt()`.
2633
+ * Never-throws. Wire it into `wireSpeechReaction({ onHeard: makeVoiceReply() })`.
2634
+ *
2635
+ * Interruption is driven by one correlated `AbortController` per turn: `interrupt(turnId)`
2636
+ * targets it, cancels the think step (signal → provider transport), and kills the TTS child.
2637
+ * Without an `interrupt()` call the controller is never aborted, so the turn runs exactly as
2638
+ * before (tour-par-tour bloquant) — the signal threading is inert.
2639
+ */
2640
+ export function makeVoiceReply(options = {}) {
2641
+ // Default think step: adapt `defaultReply(heard, history, opts)` to the `ReplyFn(heard, opts)`
2642
+ // contract so the barge-in signal reaches the LLM call.
2643
+ const replyFn = options.replyFn ?? ((heard, opts) => defaultReply(heard, [], opts));
2644
+ // Streaming think step (pipeline: speak from the first sentence). A hybrid reply can expose
2645
+ // its matching stream as a function property; detect it here so wrapping that reply no longer
2646
+ // disables streaming accidentally. Plain injected ReplyFns keep their blocking contract.
2647
+ const embeddedReply = options.replyFn;
2648
+ const embeddedStream = embeddedReply?.stream;
2649
+ const spokenPrefixFn = embeddedReply?.spokenPrefix;
2650
+ const streamFn = options.streamFn ?? embeddedStream ?? (options.replyFn ? undefined : defaultStreamReply);
2651
+ const visualGrounding = options.visualGrounding ?? ((utterance, groundingOptions) => groundExplicitVisualRequest(utterance, {
2652
+ cwd: groundingOptions?.cwd ?? options.rootDir ?? process.cwd(),
2653
+ ...(groundingOptions?.signal ? { signal: groundingOptions.signal } : {}),
2654
+ }));
2655
+ const visualConsent = new VisualConsentGate();
2656
+ const turnCoordinator = getVoiceTurnCoordinator();
2657
+ const env = options.env ?? process.env;
2658
+ const interruptionContextEnabled = env.CODEBUDDY_SENSORY_BARGE_IN?.trim().toLowerCase() === 'true';
2659
+ const shortSegmentCache = new Map();
2660
+ let shortSegmentTempSequence = 0;
2661
+ // Resolve any persona-specific fallback voice per reply. Shared by the streaming and
2662
+ // blocking paths so synthesis selection has one source of truth.
2663
+ const resolveSynth = async (engine) => {
2664
+ let voice = options.voice;
2665
+ if (!options.synth && !voice) {
2666
+ try {
2667
+ const { getActivePersonaVoiceAsync } = await import('../personas/persona-manager.js');
2668
+ voice = (await getActivePersonaVoiceAsync()).voice;
2669
+ }
2670
+ catch {
2671
+ /* keep env default */
2672
+ }
2673
+ }
2674
+ const baseSynth = options.synth ?? makeRoutedSynth(voice, options.rootDir, engine, env);
2675
+ if (twoSpeedTtsEnabled(env) && !options.synth)
2676
+ return baseSynth;
2677
+ return cacheShortSegments(baseSynth, shortSegmentVoiceIdentity(voice, env, engine), shortSegmentCache, () => ++shortSegmentTempSequence, env);
2678
+ };
2679
+ // Concurrent ingress can briefly overlap turns. Keep cancellation handles correlated instead
2680
+ // of letting the newest turn overwrite the previous controller.
2681
+ const activeAborts = new Map();
2682
+ let latestTurnId;
2683
+ let pendingInterruption;
2684
+ const handler = async (heard, context) => {
2685
+ const controller = new AbortController();
2686
+ const voiceTurnId = context?.turnId ?? createAvatarTurnId();
2687
+ const interruption = pendingInterruption;
2688
+ pendingInterruption = undefined;
2689
+ activeAborts.set(voiceTurnId, controller);
2690
+ latestTurnId = voiceTurnId;
2691
+ const { signal } = controller;
2692
+ const turnTtsEngine = resolveTtsEngine(env);
2693
+ // Resolve one WAV-aware backend for the complete turn. Cached files,
2694
+ // progressive stdin audio, and blocking fallbacks all share this promise.
2695
+ const playerPromise = options.play
2696
+ ? Promise.resolve(null)
2697
+ : resolveVoiceAudioPlayer();
2698
+ const play = options.play ?? ((wav, opts) => defaultPlay(wav, opts, playerPromise));
2699
+ // An explicit progressive path wins even when synth/play are also injected:
2700
+ // diagnostics can consume the real HTTP stream into a null sink while
2701
+ // retaining deterministic fallbacks. Otherwise preserve the production-only
2702
+ // native stream rule so older injected synth/player tests keep their contract.
2703
+ const nativeStreamSpeak = options.streamSpeak ?? (!options.synth && !options.play
2704
+ ? makeRoutedStreamSpeak(playerPromise, turnTtsEngine, env)
2705
+ : undefined);
2706
+ const delivery = deriveSpokenDeliveryProfile(heard, context, env);
2707
+ const startedAt = Date.now();
2708
+ let replyMs = 0;
2709
+ let synthMs = 0;
2710
+ let playMs = 0;
2711
+ let promptReadyMs;
2712
+ let providerFirstDeltaMs;
2713
+ let generationCompleteMs;
2714
+ let semanticReviewCompleteMs;
2715
+ let prefixPromptReadyMs;
2716
+ let prefixProviderFirstDeltaMs;
2717
+ let prefixGenerationCompleteMs;
2718
+ let prefixSemanticReviewCompleteMs;
2719
+ let continuationPromptReadyMs;
2720
+ let continuationProviderFirstDeltaMs;
2721
+ let continuationGenerationCompleteMs;
2722
+ let continuationSemanticReviewCompleteMs;
2723
+ const spokenPrefixCauses = [];
2724
+ const noteSpokenPrefixCause = (cause) => {
2725
+ if (!spokenPrefixCauses.includes(cause))
2726
+ spokenPrefixCauses.push(cause);
2727
+ };
2728
+ let firstSafeReleaseMs;
2729
+ let firstTextMs;
2730
+ let firstSegmentMs;
2731
+ let firstAudioMs;
2732
+ let firstContentAudioMs;
2733
+ let responseAudioStartSignalled = false;
2734
+ const signalResponseAudioStart = () => {
2735
+ if (responseAudioStartSignalled)
2736
+ return;
2737
+ responseAudioStartSignalled = true;
2738
+ try {
2739
+ context?.onResponseAudioStart?.();
2740
+ }
2741
+ catch {
2742
+ /* cue cancellation must never alter response playback */
2743
+ }
2744
+ };
2745
+ let streamFallbackSegments = 0;
2746
+ let interruptedAtSentence;
2747
+ let streamRouteRemote = false;
2748
+ let semanticCorrectionPromise;
2749
+ let mode = 'silent';
2750
+ let spoke = false;
2751
+ let assistantTurnPublished = false;
2752
+ let armedVisualConsent;
2753
+ let avatarPrepared = false;
2754
+ let avatarSpeechStarted = false;
2755
+ let avatarAudioStreamIndex = 0;
2756
+ let avatarSpeechStartedAt;
2757
+ let avatarFinalText = '';
2758
+ const markReplyTimingPhase = (phase) => {
2759
+ const elapsed = Date.now() - startedAt;
2760
+ switch (phase) {
2761
+ case 'prompt_ready':
2762
+ promptReadyMs ??= elapsed;
2763
+ break;
2764
+ case 'provider_first_delta':
2765
+ providerFirstDeltaMs ??= elapsed;
2766
+ break;
2767
+ case 'generation_complete':
2768
+ generationCompleteMs ??= elapsed;
2769
+ break;
2770
+ case 'semantic_review_complete':
2771
+ semanticReviewCompleteMs ??= elapsed;
2772
+ break;
2773
+ case 'prefix_prompt_ready':
2774
+ prefixPromptReadyMs ??= elapsed;
2775
+ break;
2776
+ case 'prefix_provider_first_delta':
2777
+ prefixProviderFirstDeltaMs ??= elapsed;
2778
+ break;
2779
+ case 'prefix_generation_complete':
2780
+ prefixGenerationCompleteMs ??= elapsed;
2781
+ break;
2782
+ case 'prefix_semantic_review_complete':
2783
+ prefixSemanticReviewCompleteMs ??= elapsed;
2784
+ break;
2785
+ case 'continuation_prompt_ready':
2786
+ continuationPromptReadyMs ??= elapsed;
2787
+ break;
2788
+ case 'continuation_provider_first_delta':
2789
+ continuationProviderFirstDeltaMs ??= elapsed;
2790
+ break;
2791
+ case 'continuation_generation_complete':
2792
+ continuationGenerationCompleteMs ??= elapsed;
2793
+ break;
2794
+ case 'continuation_semantic_review_complete':
2795
+ continuationSemanticReviewCompleteMs ??= elapsed;
2796
+ break;
2797
+ }
2798
+ };
2799
+ const avatarTurnId = voiceTurnId;
2800
+ turnCoordinator.transition(avatarTurnId, 'thinking');
2801
+ const avatarCue = planAvatarPerformance(heard, delivery);
2802
+ const avatarEnabled = options.avatarEnabled ?? (process.env.CODEBUDDY_AVATAR_BRIDGE !== 'false' || Boolean(options.onAvatarEvent));
2803
+ const emitAvatarEvent = (input) => {
2804
+ if (!avatarEnabled)
2805
+ return;
2806
+ try {
2807
+ const event = getAvatarEventBus().publish(input);
2808
+ options.onAvatarEvent?.(event);
2809
+ }
2810
+ catch (error) {
2811
+ logger.debug(`[voice] avatar event skipped: ${error instanceof Error ? error.message : String(error)}`);
2812
+ }
2813
+ };
2814
+ const createAvatarAudioPublisher = (source) => {
2815
+ // Decide once, before the first WAV byte. Enabling halfway through a live
2816
+ // stream would give a newly connected renderer PCM without its RIFF header.
2817
+ const enabled = shouldStreamAvatarAudio();
2818
+ const streamId = `${avatarTurnId}:audio:${avatarAudioStreamIndex++}`;
2819
+ let started = false;
2820
+ let chunkIndex = 0;
2821
+ let byteOffset = 0;
2822
+ const push = (chunk) => {
2823
+ if (!enabled)
2824
+ return;
2825
+ const pieces = splitAvatarAudioChunk(chunk);
2826
+ if (pieces.length === 0)
2827
+ return;
2828
+ if (!started) {
2829
+ started = true;
2830
+ emitAvatarEvent({
2831
+ type: 'avatar.audio.started',
2832
+ turnId: avatarTurnId,
2833
+ streamId,
2834
+ format: 'wav_stream',
2835
+ encoding: 'base64',
2836
+ source,
2837
+ maxChunkBytes: MAX_AVATAR_AUDIO_CHUNK_BYTES,
2838
+ });
2839
+ }
2840
+ for (const piece of pieces) {
2841
+ emitAvatarEvent({
2842
+ type: 'avatar.audio.chunk',
2843
+ turnId: avatarTurnId,
2844
+ streamId,
2845
+ format: 'wav_stream',
2846
+ chunkIndex: chunkIndex++,
2847
+ byteOffset,
2848
+ byteLength: piece.byteLength,
2849
+ data: Buffer.from(piece).toString('base64'),
2850
+ });
2851
+ byteOffset += piece.byteLength;
2852
+ }
2853
+ };
2854
+ const end = (outcome) => {
2855
+ if (!started)
2856
+ return;
2857
+ emitAvatarEvent({
2858
+ type: 'avatar.audio.ended',
2859
+ turnId: avatarTurnId,
2860
+ streamId,
2861
+ totalBytes: byteOffset,
2862
+ chunks: chunkIndex,
2863
+ outcome,
2864
+ });
2865
+ };
2866
+ return { push, end };
2867
+ };
2868
+ const publishAvatarBufferedWav = async (wav) => {
2869
+ if (!shouldStreamAvatarAudio())
2870
+ return;
2871
+ const publisher = createAvatarAudioPublisher('buffered');
2872
+ try {
2873
+ const { readFile } = await import('node:fs/promises');
2874
+ publisher.push(await readFile(wav));
2875
+ publisher.end('complete');
2876
+ }
2877
+ catch {
2878
+ publisher.end('failed');
2879
+ }
2880
+ };
2881
+ const prepareAvatarSpeech = (text) => {
2882
+ const content = text.trim();
2883
+ if (!content)
2884
+ return;
2885
+ avatarFinalText = content;
2886
+ if (avatarPrepared)
2887
+ return;
2888
+ avatarPrepared = true;
2889
+ emitAvatarEvent({
2890
+ type: 'avatar.speech.prepared',
2891
+ turnId: avatarTurnId,
2892
+ text: content,
2893
+ cue: avatarCue,
2894
+ prosody: planAvatarSpeechProsody(content, avatarCue),
2895
+ });
2896
+ };
2897
+ const emitAvatarSegment = (text) => {
2898
+ if (avatarPrepared || !text)
2899
+ return;
2900
+ emitAvatarEvent({
2901
+ type: 'avatar.speech.segment',
2902
+ turnId: avatarTurnId,
2903
+ text,
2904
+ cue: avatarCue,
2905
+ prosody: planAvatarSpeechProsody(text, avatarCue),
2906
+ });
2907
+ };
2908
+ const markAvatarSpeechStarted = () => {
2909
+ if (avatarSpeechStarted)
2910
+ return;
2911
+ avatarSpeechStarted = true;
2912
+ avatarSpeechStartedAt = Date.now();
2913
+ turnCoordinator.transition(avatarTurnId, 'speaking', {
2914
+ ...(firstAudioMs !== undefined ? { firstAudioMs } : {}),
2915
+ });
2916
+ emitAvatarEvent({ type: 'avatar.speech.started', turnId: avatarTurnId });
2917
+ };
2918
+ emitAvatarEvent({
2919
+ type: 'avatar.turn.started',
2920
+ turnId: avatarTurnId,
2921
+ cue: avatarCue,
2922
+ });
2923
+ const publishTurn = (turn) => {
2924
+ try {
2925
+ const result = options.onConversationTurn?.(turn);
2926
+ if (result && typeof result.then === 'function') {
2927
+ void result.catch((error) => {
2928
+ logger.warn(`[voice] conversation mirror failed: ${error instanceof Error ? error.message : String(error)}`);
2929
+ });
2930
+ }
2931
+ }
2932
+ catch (error) {
2933
+ logger.warn(`[voice] conversation mirror failed: ${error instanceof Error ? error.message : String(error)}`);
2934
+ }
2935
+ try {
2936
+ const result = options.onCorrelatedConversationTurn?.({ ...turn, turnId: avatarTurnId });
2937
+ if (result && typeof result.then === 'function') {
2938
+ void result.catch((error) => {
2939
+ logger.warn(`[voice] cognitive turn mirror failed: ${error instanceof Error ? error.message : String(error)}`);
2940
+ });
2941
+ }
2942
+ }
2943
+ catch (error) {
2944
+ logger.warn(`[voice] cognitive turn mirror failed: ${error instanceof Error ? error.message : String(error)}`);
2945
+ }
2946
+ };
2947
+ const publishAssistantTurn = (content) => {
2948
+ if (assistantTurnPublished || !content.trim())
2949
+ return;
2950
+ assistantTurnPublished = true;
2951
+ avatarFinalText = content.trim();
2952
+ publishTurn({ role: 'assistant', content });
2953
+ };
2954
+ publishTurn({ role: 'user', content: heard });
2955
+ const streamedWavMetadata = new Map();
2956
+ const timedPlay = async (wav, opts) => {
2957
+ const metadata = streamedWavMetadata.get(wav);
2958
+ if (metadata)
2959
+ noteSpokenText(metadata.text);
2960
+ if (firstAudioMs === undefined)
2961
+ firstAudioMs = Date.now() - startedAt;
2962
+ signalResponseAudioStart();
2963
+ await publishAvatarBufferedWav(wav);
2964
+ markAvatarSpeechStarted();
2965
+ if (firstContentAudioMs === undefined &&
2966
+ metadata?.isContent !== false) {
2967
+ firstContentAudioMs = Date.now() - startedAt;
2968
+ }
2969
+ try {
2970
+ await play(wav, {
2971
+ ...(opts ?? {}),
2972
+ delivery,
2973
+ alreadyNormalized: options.synth === undefined,
2974
+ });
2975
+ }
2976
+ finally {
2977
+ streamedWavMetadata.delete(wav);
2978
+ }
2979
+ };
2980
+ const timedStreamSpeak = nativeStreamSpeak
2981
+ ? async (text, opts = {}) => {
2982
+ if (firstSegmentMs === undefined)
2983
+ firstSegmentMs = Date.now() - startedAt;
2984
+ emitAvatarSegment(text);
2985
+ noteSpokenText(text);
2986
+ const isBackchannel = INSTANT_BACKCHANNELS.has(text.trim());
2987
+ const publisher = createAvatarAudioPublisher('live');
2988
+ let streamed = false;
2989
+ try {
2990
+ streamed = await nativeStreamSpeak(text, {
2991
+ ...opts,
2992
+ delivery,
2993
+ onAudioChunk: (chunk) => {
2994
+ publisher.push(chunk);
2995
+ opts.onAudioChunk?.(chunk);
2996
+ },
2997
+ onFirstAudio: () => {
2998
+ if (firstAudioMs === undefined)
2999
+ firstAudioMs = Date.now() - startedAt;
3000
+ signalResponseAudioStart();
3001
+ markAvatarSpeechStarted();
3002
+ if (!isBackchannel && firstContentAudioMs === undefined) {
3003
+ firstContentAudioMs = Date.now() - startedAt;
3004
+ }
3005
+ opts.onFirstAudio?.();
3006
+ },
3007
+ });
3008
+ return streamed;
3009
+ }
3010
+ finally {
3011
+ publisher.end(signal.aborted ? 'interrupted' : streamed ? 'complete' : 'failed');
3012
+ }
3013
+ }
3014
+ : undefined;
3015
+ // The look-ahead seam must survive the timing wrapper, or the pipeline
3016
+ // below never sees it and every sentence pays a full round trip again.
3017
+ if (timedStreamSpeak && nativeStreamSpeak?.prefetch) {
3018
+ timedStreamSpeak.prefetch = nativeStreamSpeak.prefetch;
3019
+ }
3020
+ const speakSemanticCorrection = async () => {
3021
+ const pending = semanticCorrectionPromise;
3022
+ semanticCorrectionPromise = undefined;
3023
+ if (!pending || signal.aborted)
3024
+ return '';
3025
+ try {
3026
+ const correction = prepareSpeech(await pending);
3027
+ if (!correction || signal.aborted)
3028
+ return '';
3029
+ const guarded = guardRelationshipReply(correction).response.trim();
3030
+ if (!guarded || signal.aborted)
3031
+ return '';
3032
+ if (timedStreamSpeak) {
3033
+ let streamed = false;
3034
+ await withSpeakingGuard(async () => {
3035
+ streamed = await timedStreamSpeak(guarded, { signal, delivery });
3036
+ });
3037
+ if (streamed && !signal.aborted)
3038
+ return guarded;
3039
+ }
3040
+ const correctionSynth = await resolveSynth(turnTtsEngine);
3041
+ const wav = await correctionSynth(guarded, { signal, delivery });
3042
+ if (!wav || signal.aborted)
3043
+ return '';
3044
+ await withSpeakingGuard(() => timedPlay(wav, { signal, delivery }));
3045
+ try {
3046
+ const { unlink } = await import('fs/promises');
3047
+ await unlink(wav);
3048
+ }
3049
+ catch {
3050
+ /* throwaway correction WAV */
3051
+ }
3052
+ return signal.aborted ? '' : guarded;
3053
+ }
3054
+ catch (error) {
3055
+ logger.warn(`[voice] semantic correction unavailable: ${error instanceof Error ? error.message : String(error)}`);
3056
+ return '';
3057
+ }
3058
+ };
3059
+ try {
3060
+ // ---- CAMERA SHARE: on-demand « qu'est-ce que tu vois ? » / « regarde » ----
3061
+ // Must precede object-level visual grounding: a scene look is not an
3062
+ // ambiguous consent prompt, and voice never sends a photo unless asked.
3063
+ let visualReply;
3064
+ try {
3065
+ const cameraShare = options.cameraShare ?? (async (utterance, shareOptions) => {
3066
+ const { maybeHandleCameraShareRequest } = await import('../companion/camera-share.js');
3067
+ return maybeHandleCameraShareRequest(utterance, shareOptions);
3068
+ });
3069
+ const share = await cameraShare(heard, {
3070
+ surface: 'voice',
3071
+ rootDir: options.rootDir ?? process.cwd(),
3072
+ ...(options.env ? { env: options.env } : {}),
3073
+ });
3074
+ if (share) {
3075
+ visualReply = share.spokenReply;
3076
+ logger.info(`[voice] camera-share success=${share.success} telegram=${share.telegramSent}`);
3077
+ }
3078
+ }
3079
+ catch (err) {
3080
+ logger.warn(`[voice] camera-share skipped: ${err instanceof Error ? err.message : String(err)}`);
3081
+ }
3082
+ // ---- VISUAL GROUNDING: explicit one-shot camera request ----
3083
+ // This must precede both the stream and blocking reply functions. In
3084
+ // production those functions are the hybrid brain, whose first branch is
3085
+ // the phatic/prefetch shortcut and whose grounded branch depends on
3086
+ // SPEAK_ACT. Seeing is a perception capability, not an action-mode perk.
3087
+ // Skipped when camera-share already answered a scene-level look.
3088
+ if (visualReply === undefined) {
3089
+ let visualRequest = heard;
3090
+ const consent = visualConsent.consume(heard);
3091
+ if (consent.decision === 'confirmed') {
3092
+ visualRequest = consent.utterance;
3093
+ }
3094
+ else if (consent.decision === 'declined') {
3095
+ visualReply = "D'accord, je n'ouvre pas la caméra.";
3096
+ }
3097
+ else if (consent.decision === 'expired') {
3098
+ visualReply =
3099
+ "J'ai laissé expirer l'autorisation. Redis-moi simplement ce que tu veux me montrer.";
3100
+ }
3101
+ else if (isAmbiguousVisualGroundingRequest(heard)) {
3102
+ armedVisualConsent = visualConsent.request(heard);
3103
+ visualReply =
3104
+ "Oui, je peux regarder. Tu veux que j'ouvre la caméra juste le temps de prendre une image ?";
3105
+ }
3106
+ const shouldGroundVisual = consent.decision === 'confirmed' ||
3107
+ (visualReply === undefined && isExplicitVisualGroundingRequest(heard));
3108
+ if (shouldGroundVisual) {
3109
+ const visualStartedAt = Date.now();
3110
+ try {
3111
+ const result = await visualGrounding(visualRequest, {
3112
+ cwd: options.rootDir ?? process.cwd(),
3113
+ signal,
3114
+ });
3115
+ replyMs = Date.now() - visualStartedAt;
3116
+ if (signal.aborted || result?.status === 'aborted')
3117
+ return;
3118
+ visualReply = result?.response ||
3119
+ "Je n'ai pas réussi à obtenir une observation visuelle fiable cette fois-ci.";
3120
+ logger.info(`[voice] explicit visual grounding status=${result?.status ?? 'unavailable'} ` +
3121
+ `evidenceChars=${result?.evidence?.summary.length ?? 0}`);
3122
+ }
3123
+ catch (error) {
3124
+ replyMs = Date.now() - visualStartedAt;
3125
+ logger.warn(`[voice] explicit visual grounding failed: ${error instanceof Error ? error.message : String(error)}`);
3126
+ visualReply =
3127
+ "Je n'ai pas réussi à obtenir une observation visuelle fiable cette fois-ci.";
3128
+ }
3129
+ }
3130
+ }
3131
+ // ---- FAST PATH: streaming pipeline — speak from the first sentence ----
3132
+ // Never lets a streaming failure crash the turn; on nothing-spoken it falls through to
3133
+ // the blocking path below (which is the original, unchanged tour-par-tour behavior).
3134
+ if (streamFn && visualReply === undefined) {
3135
+ try {
3136
+ let shortFirstConfig;
3137
+ const relationshipSafety = new RelationshipSafetyStreamGuard(() => shortFirstConfig !== undefined);
3138
+ const timedReplyStream = (async function* () {
3139
+ let atStreamStart = true;
3140
+ let spokenPrefix = '';
3141
+ if (spokenPrefixFn) {
3142
+ const candidate = await spokenPrefixFn(heard, {
3143
+ signal,
3144
+ delivery,
3145
+ ...(interruption ? { interruption } : {}),
3146
+ onReplyTimingPhase: markReplyTimingPhase,
3147
+ onSpokenPrefixTelemetry: noteSpokenPrefixCause,
3148
+ });
3149
+ if (signal.aborted)
3150
+ return;
3151
+ const causesBeforeFinalGuard = spokenPrefixCauses.length;
3152
+ spokenPrefix = prepareSpokenPrefixCandidate(candidate, noteSpokenPrefixCause);
3153
+ if (candidate.trim() && !spokenPrefix) {
3154
+ noteSpokenPrefixCause('final_guard_invalid');
3155
+ }
3156
+ else if (!candidate.trim() && spokenPrefixCauses.length === causesBeforeFinalGuard) {
3157
+ noteSpokenPrefixCause('empty');
3158
+ }
3159
+ if (spokenPrefix) {
3160
+ noteSpokenPrefixCause('accepted');
3161
+ if (firstTextMs === undefined)
3162
+ firstTextMs = Date.now() - startedAt;
3163
+ firstSafeReleaseMs ??= Date.now() - startedAt;
3164
+ atStreamStart = false;
3165
+ // The assembler only commits punctuation once whitespace/EOS proves the
3166
+ // boundary. Emit that boundary now so continuation generation can fail or be
3167
+ // interrupted without trapping an already accepted prefix in its buffer.
3168
+ yield `${spokenPrefix} `;
3169
+ }
3170
+ }
3171
+ for await (const delta of streamFn(heard, {
3172
+ signal,
3173
+ delivery,
3174
+ ...(interruption ? { interruption } : {}),
3175
+ ...(spokenPrefix ? { spokenPrefix } : {}),
3176
+ onReplyTimingPhase: markReplyTimingPhase,
3177
+ onShortFirstReady: (config) => {
3178
+ shortFirstConfig = config;
3179
+ },
3180
+ onProviderResolved: (route) => {
3181
+ streamRouteRemote = isRemoteVoiceRoute(route.baseURL);
3182
+ },
3183
+ })) {
3184
+ // Provider first-token latency is measured on the raw delta. The
3185
+ // safety gate intentionally waits for a sentence boundary before
3186
+ // release, which is a separate (and potentially much longer)
3187
+ // first-safe-sentence latency.
3188
+ if (firstTextMs === undefined && delta.length > 0) {
3189
+ firstTextMs = Date.now() - startedAt;
3190
+ }
3191
+ // These prefixes are deterministic local constants, never model
3192
+ // prose. Release the one allowlisted acknowledgement immediately
3193
+ // while the relationship gate continues to hold and inspect the
3194
+ // entire generated answer. Previously the full-answer guard also
3195
+ // trapped "Alors…" until generation finished, defeating the
3196
+ // prewarmed backchannel and leaving 6–8 seconds of dead air.
3197
+ if (atStreamStart && INSTANT_BACKCHANNELS.has(delta.trim())) {
3198
+ atStreamStart = false;
3199
+ yield delta;
3200
+ continue;
3201
+ }
3202
+ if (delta.length > 0)
3203
+ atStreamStart = false;
3204
+ for (const safeDelta of relationshipSafety.push(delta)) {
3205
+ firstSafeReleaseMs ??= Date.now() - startedAt;
3206
+ yield safeDelta;
3207
+ }
3208
+ }
3209
+ for (const safeDelta of relationshipSafety.finish()) {
3210
+ firstSafeReleaseMs ??= Date.now() - startedAt;
3211
+ yield safeDelta;
3212
+ }
3213
+ const safety = relationshipSafety.assessment();
3214
+ if (safety.intervened) {
3215
+ logger.warn(`[voice] relationship safety gate intervened: ${safety.issues.join(',')}`);
3216
+ }
3217
+ })();
3218
+ // Resolve the regular synthesizer in parallel with the LLM stream. Native Pocket
3219
+ // keeps this lazy (zero work on the healthy path), but can still recover the exact
3220
+ // streamed sentence without asking the LLM to generate the answer a second time.
3221
+ let baseSynthPromise = timedStreamSpeak
3222
+ ? undefined
3223
+ : resolveSynth(turnTtsEngine);
3224
+ const synth = async (text, synthOpts) => {
3225
+ if (firstSegmentMs === undefined)
3226
+ firstSegmentMs = Date.now() - startedAt;
3227
+ emitAvatarSegment(text);
3228
+ baseSynthPromise ??= resolveSynth(turnTtsEngine);
3229
+ const baseSynth = await baseSynthPromise;
3230
+ const wav = await baseSynth(text, { ...(synthOpts ?? {}), delivery });
3231
+ if (wav) {
3232
+ streamedWavMetadata.set(wav, {
3233
+ isContent: !INSTANT_BACKCHANNELS.has(text.trim()),
3234
+ text,
3235
+ });
3236
+ }
3237
+ return wav;
3238
+ };
3239
+ const result = await streamToSpeech({
3240
+ stream: timedReplyStream,
3241
+ synth,
3242
+ play: timedPlay,
3243
+ sanitize: (() => {
3244
+ let firstSegment = true;
3245
+ return (raw) => {
3246
+ const clean = prepareSpeech(raw);
3247
+ if (!clean)
3248
+ return '';
3249
+ if (!firstSegment)
3250
+ return clean;
3251
+ firstSegment = false;
3252
+ return rewriteRepeatedVoiceOpener(clean);
3253
+ };
3254
+ })(),
3255
+ ...(timedStreamSpeak ? { streamSpeak: timedStreamSpeak } : {}),
3256
+ ttsRouteHint: (() => {
3257
+ let firstContentPending = true;
3258
+ return (text) => {
3259
+ if (INSTANT_BACKCHANNELS.has(text.trim()))
3260
+ return 'backchannel';
3261
+ if (shortFirstConfig && firstContentPending) {
3262
+ firstContentPending = false;
3263
+ return 'conv3-first';
3264
+ }
3265
+ return undefined;
3266
+ };
3267
+ })(),
3268
+ signal,
3269
+ cap: options.sentenceCap ?? voiceSentenceCap(),
3270
+ audioPrebufferMs: () => streamRouteRemote ? voiceAudioPrebufferMs(env) : 0,
3271
+ });
3272
+ streamFallbackSegments = result.fallbackSegments ?? 0;
3273
+ if (shortFirstConfig && (result.played || result.aborted)) {
3274
+ logger.info(`[voice] short-first: firstContentMs=${firstContentAudioMs ?? -1}, ` +
3275
+ `sentences=${result.sentences.length}`);
3276
+ }
3277
+ if (signal.aborted) {
3278
+ if (interruptionContextEnabled) {
3279
+ interruptedAtSentence = result.interruptedSentence ?? Math.max(1, result.sentences.length + 1);
3280
+ pendingInterruption = {
3281
+ interruptedTurnId: voiceTurnId,
3282
+ phraseNumber: interruptedAtSentence,
3283
+ spokenText: result.spoken,
3284
+ };
3285
+ }
3286
+ // Preserve only sentences whose playback completed before the
3287
+ // interruption. The partial in-flight segment is deliberately
3288
+ // absent from `result.spoken` and must not enter continuity.
3289
+ if (result.spoken.trim()) {
3290
+ noteSpokenText(result.spoken);
3291
+ publishAssistantTurn(result.spoken);
3292
+ }
3293
+ return;
3294
+ }
3295
+ if (result.played) {
3296
+ mode = 'streamed';
3297
+ spoke = true;
3298
+ // Segment references protect the live playback edge; the complete
3299
+ // canonical turn protects the acoustic tail and STT transcripts
3300
+ // that merge several spoken segments into one utterance.
3301
+ noteSpokenText(result.spoken);
3302
+ recentReplyOpeners = pushOpener(recentReplyOpeners, result.spoken);
3303
+ void import('../companion/recent-said.js')
3304
+ .then((m) => m.rememberSaid(result.spoken, 'voice'))
3305
+ .catch(() => undefined);
3306
+ publishAssistantTurn(result.spoken);
3307
+ logger.info(`[voice] spoke (streamed) chars=${result.spoken.length}`);
3308
+ logger.info(`[voice] streamed ${result.sentences.length} phrase(s) in ${Date.now() - startedAt}ms`);
3309
+ logger.info(`[voice] stream latency: text=${firstTextMs ?? -1}ms ` +
3310
+ `segment=${firstSegmentMs ?? -1}ms firstAudio=${firstAudioMs ?? -1}ms ` +
3311
+ `contentAudio=${firstContentAudioMs ?? -1}ms ` +
3312
+ `fallbackSegments=${streamFallbackSegments}`);
3313
+ options.onSpoke?.(result.spoken);
3314
+ return;
3315
+ }
3316
+ // Nothing speakable came through the stream (phatic, empty, all-artifact, or a stream
3317
+ // error) → fall through to the blocking reply below.
3318
+ logger.debug('[voice] stream produced nothing speakable — falling back to blocking reply');
3319
+ }
3320
+ catch (err) {
3321
+ logger.warn(`[voice] streaming path failed, falling back to blocking: ${err instanceof Error ? err.message : String(err)}`);
3322
+ if (signal.aborted)
3323
+ return;
3324
+ }
3325
+ }
3326
+ // ---- BLOCKING FALLBACK: the original tour-par-tour behavior, unchanged ----
3327
+ let rawReply;
3328
+ if (visualReply !== undefined) {
3329
+ rawReply = visualReply;
3330
+ }
3331
+ else {
3332
+ const replyStart = Date.now();
3333
+ rawReply = await replyFn(heard, {
3334
+ signal,
3335
+ delivery,
3336
+ ...(interruption ? { interruption } : {}),
3337
+ onReplyTimingPhase: markReplyTimingPhase,
3338
+ onSemanticCorrection: (correction) => {
3339
+ semanticCorrectionPromise = correction.catch(() => '');
3340
+ },
3341
+ });
3342
+ replyMs = Date.now() - replyStart;
3343
+ }
3344
+ // Interrupted during the think step → abandon silently (never speak a stale reply).
3345
+ if (signal.aborted)
3346
+ return;
3347
+ // Sanity gate before synth: strip leaked control tokens + foreign-script contamination
3348
+ // (observed: a French reply degrading into CJK the voice can't pronounce), stay silent
3349
+ // if nothing meaningful survives. `reply` is what we synth, log, and hand to onSpoke.
3350
+ const preparedReply = prepareSpeech(rawReply);
3351
+ const relationshipGuard = guardRelationshipReply(preparedReply ?? '');
3352
+ let reply = applyLimitsContract(relationshipGuard.response, { heard }).text;
3353
+ let emptyReplyRecovery = false;
3354
+ if (reply)
3355
+ firstSafeReleaseMs ??= Date.now() - startedAt;
3356
+ if (relationshipGuard.intervened) {
3357
+ logger.warn(`[voice] relationship safety gate intervened: ${relationshipGuard.issues.join(',')}`);
3358
+ }
3359
+ if (!reply) {
3360
+ if ((rawReply ?? '').trim()) {
3361
+ logger.info(`[voice] reply muted after sanitize inputChars=${(rawReply ?? '').length}`);
3362
+ }
3363
+ let route;
3364
+ try {
3365
+ route = await (options.resolveRoute ?? ((text) => resolveVoiceModel(text, { env })))(heard);
3366
+ }
3367
+ catch (error) {
3368
+ logger.debug(`[voice] empty-reply readiness route unavailable: ${error instanceof Error ? error.message : String(error)}`);
3369
+ }
3370
+ const readiness = describeVoiceReadiness(env, route);
3371
+ if (readiness.ready)
3372
+ return;
3373
+ reply = EMPTY_REPLY_RECOVERY;
3374
+ emptyReplyRecovery = true;
3375
+ mode = 'failed';
3376
+ logger.warn(`[voice] empty reply with degraded readiness — speaking recovery ` +
3377
+ `modelReady=${readiness.modelReady} speakReady=${readiness.speakReady}`);
3378
+ }
3379
+ if (!emptyReplyRecovery)
3380
+ reply = rewriteRepeatedVoiceOpener(reply);
3381
+ recentReplyOpeners = pushOpener(recentReplyOpeners, reply);
3382
+ void import('../companion/recent-said.js')
3383
+ .then((m) => m.rememberSaid(reply, 'voice'))
3384
+ .catch(() => undefined);
3385
+ // The textual answer is now committed even if the local audio device fails;
3386
+ // publish it to the shared channel so the conversation never disappears.
3387
+ publishAssistantTurn(reply);
3388
+ prepareAvatarSpeech(reply);
3389
+ // Pocket's server streams WAV frames natively. For a blocking agent
3390
+ // result we still cannot speak before the text exists, but we can remove
3391
+ // the former multi-second `synth(all)` wait once it does.
3392
+ if (timedStreamSpeak) {
3393
+ const playStart = Date.now();
3394
+ let streamed = false;
3395
+ await withSpeakingGuard(async () => {
3396
+ streamed = await timedStreamSpeak(reply, { signal, delivery });
3397
+ });
3398
+ playMs = Date.now() - playStart;
3399
+ if (signal.aborted)
3400
+ return;
3401
+ if (streamed) {
3402
+ if (!emptyReplyRecovery)
3403
+ mode = 'blocking';
3404
+ spoke = true;
3405
+ const correction = await speakSemanticCorrection();
3406
+ logger.info(`[voice] spoke (${turnTtsEngine} audio stream) chars=${reply.length}`);
3407
+ logger.info(`[voice] timings: reply=${replyMs}ms firstAudio=${firstAudioMs ?? -1}ms ` +
3408
+ `streamPlay=${playMs}ms total=${Date.now() - startedAt}ms`);
3409
+ options.onSpoke?.([reply, correction].filter(Boolean).join(' '));
3410
+ return;
3411
+ }
3412
+ logger.debug(`[voice] ${turnTtsEngine} audio stream unavailable — using WAV synthesis fallback`);
3413
+ }
3414
+ const synth = await resolveSynth(turnTtsEngine);
3415
+ const synthStart = Date.now();
3416
+ const wav = await synth(reply, { signal, delivery });
3417
+ synthMs = Date.now() - synthStart;
3418
+ if (!wav)
3419
+ return;
3420
+ // Interrupted during synth → don't start playback.
3421
+ if (signal.aborted)
3422
+ return;
3423
+ const playStart = Date.now();
3424
+ await withSpeakingGuard(() => {
3425
+ noteSpokenText(reply);
3426
+ return timedPlay(wav, { signal, delivery });
3427
+ }); // half-duplex + interruptible
3428
+ playMs = Date.now() - playStart;
3429
+ // A barge-in kills the player early; don't claim we "spoke" the whole line.
3430
+ if (signal.aborted)
3431
+ return;
3432
+ if (!emptyReplyRecovery)
3433
+ mode = 'blocking';
3434
+ spoke = true;
3435
+ const correction = await speakSemanticCorrection();
3436
+ logger.info(`[voice] spoke chars=${reply.length}`);
3437
+ logger.info(`[voice] timings: reply=${replyMs}ms synth=${synthMs}ms play=${playMs}ms total=${Date.now() - startedAt}ms`);
3438
+ options.onSpoke?.([reply, correction].filter(Boolean).join(' '));
3439
+ // Best-effort cleanup of the synthesized WAV.
3440
+ try {
3441
+ const { unlink } = await import('fs/promises');
3442
+ await unlink(wav);
3443
+ }
3444
+ catch {
3445
+ /* leave the file if cleanup fails — not worth surfacing */
3446
+ }
3447
+ }
3448
+ catch (err) {
3449
+ mode = 'failed';
3450
+ logger.warn(`[voice] reply→speak failed: ${err instanceof Error ? err.message : String(err)}`);
3451
+ }
3452
+ finally {
3453
+ // A permission question that never reached the speakers cannot authorize
3454
+ // a later camera capture. A barge-in confirmation consumes the pending
3455
+ // request before this older turn reaches its finally block.
3456
+ if (armedVisualConsent !== undefined && !spoke) {
3457
+ visualConsent.cancel(armedVisualConsent);
3458
+ }
3459
+ // If THIS turn was interrupted, hard-reset the half-duplex guard so the ear re-opens NOW
3460
+ // (barge-in), overriding the echo tail that withSpeakingGuard's finally just armed. Runs
3461
+ // last, so it wins the race against that endSpeaking(). Never re-arms after a normal turn.
3462
+ if (signal.aborted && interruptionContextEnabled) {
3463
+ interruptedAtSentence ??= 1;
3464
+ pendingInterruption ??= {
3465
+ interruptedTurnId: voiceTurnId,
3466
+ phraseNumber: interruptedAtSentence,
3467
+ spokenText: '',
3468
+ };
3469
+ }
3470
+ if (signal.aborted) {
3471
+ mode = 'interrupted';
3472
+ spoke = false;
3473
+ try {
3474
+ interruptSpeaking();
3475
+ }
3476
+ catch {
3477
+ /* never-throws */
3478
+ }
3479
+ }
3480
+ if (signal.aborted) {
3481
+ turnCoordinator.transition(avatarTurnId, 'interrupted', {
3482
+ suppressionReason: 'barge-in',
3483
+ ...(interruptedAtSentence !== undefined ? { interruptedAtSentence } : {}),
3484
+ totalMs: Date.now() - startedAt,
3485
+ });
3486
+ emitAvatarEvent({
3487
+ type: 'avatar.speech.interrupted',
3488
+ turnId: avatarTurnId,
3489
+ reason: 'barge_in',
3490
+ });
3491
+ }
3492
+ else if (mode === 'failed') {
3493
+ turnCoordinator.transition(avatarTurnId, 'failed', {
3494
+ errorCategory: 'unknown',
3495
+ totalMs: Date.now() - startedAt,
3496
+ });
3497
+ emitAvatarEvent({
3498
+ type: 'avatar.speech.failed',
3499
+ turnId: avatarTurnId,
3500
+ reason: 'unknown',
3501
+ });
3502
+ }
3503
+ else if (spoke) {
3504
+ turnCoordinator.transition(avatarTurnId, 'completed', {
3505
+ spoke: true,
3506
+ ...(firstAudioMs !== undefined ? { firstAudioMs } : {}),
3507
+ totalMs: Date.now() - startedAt,
3508
+ });
3509
+ emitAvatarEvent({
3510
+ type: 'avatar.speech.completed',
3511
+ turnId: avatarTurnId,
3512
+ text: avatarFinalText,
3513
+ durationMs: avatarSpeechStartedAt === undefined
3514
+ ? 0
3515
+ : Date.now() - avatarSpeechStartedAt,
3516
+ });
3517
+ }
3518
+ else {
3519
+ turnCoordinator.transition(avatarTurnId, 'suppressed', {
3520
+ suppressionReason: 'silent-reply',
3521
+ spoke: false,
3522
+ totalMs: Date.now() - startedAt,
3523
+ });
3524
+ emitAvatarEvent({ type: 'avatar.turn.silent', turnId: avatarTurnId });
3525
+ }
3526
+ const spokenPrefixOutcome = spokenPrefixCauses.at(-1);
3527
+ const hasContinuationTiming = continuationPromptReadyMs !== undefined ||
3528
+ continuationProviderFirstDeltaMs !== undefined ||
3529
+ continuationGenerationCompleteMs !== undefined ||
3530
+ continuationSemanticReviewCompleteMs !== undefined;
3531
+ const timing = {
3532
+ mode,
3533
+ ...(interruptedAtSentence !== undefined ? { interruptedAtSentence } : {}),
3534
+ totalMs: Date.now() - startedAt,
3535
+ spoke,
3536
+ delivery,
3537
+ ...(promptReadyMs !== undefined ? { promptReadyMs } : {}),
3538
+ ...(providerFirstDeltaMs !== undefined ? { providerFirstDeltaMs } : {}),
3539
+ ...(generationCompleteMs !== undefined ? { generationCompleteMs } : {}),
3540
+ ...(semanticReviewCompleteMs !== undefined ? { semanticReviewCompleteMs } : {}),
3541
+ ...(spokenPrefixOutcome
3542
+ ? {
3543
+ spokenPrefix: {
3544
+ outcome: spokenPrefixOutcome,
3545
+ causes: [...spokenPrefixCauses],
3546
+ ...(prefixPromptReadyMs !== undefined
3547
+ ? { promptReadyMs: prefixPromptReadyMs }
3548
+ : {}),
3549
+ ...(prefixProviderFirstDeltaMs !== undefined
3550
+ ? { providerFirstDeltaMs: prefixProviderFirstDeltaMs }
3551
+ : {}),
3552
+ ...(prefixGenerationCompleteMs !== undefined
3553
+ ? { generationCompleteMs: prefixGenerationCompleteMs }
3554
+ : {}),
3555
+ ...(prefixSemanticReviewCompleteMs !== undefined
3556
+ ? { semanticReviewCompleteMs: prefixSemanticReviewCompleteMs }
3557
+ : {}),
3558
+ },
3559
+ }
3560
+ : {}),
3561
+ ...(hasContinuationTiming
3562
+ ? {
3563
+ continuation: {
3564
+ ...(continuationPromptReadyMs !== undefined
3565
+ ? { promptReadyMs: continuationPromptReadyMs }
3566
+ : {}),
3567
+ ...(continuationProviderFirstDeltaMs !== undefined
3568
+ ? { providerFirstDeltaMs: continuationProviderFirstDeltaMs }
3569
+ : {}),
3570
+ ...(continuationGenerationCompleteMs !== undefined
3571
+ ? { generationCompleteMs: continuationGenerationCompleteMs }
3572
+ : {}),
3573
+ ...(continuationSemanticReviewCompleteMs !== undefined
3574
+ ? { semanticReviewCompleteMs: continuationSemanticReviewCompleteMs }
3575
+ : {}),
3576
+ },
3577
+ }
3578
+ : {}),
3579
+ ...(firstSafeReleaseMs !== undefined ? { firstSafeReleaseMs } : {}),
3580
+ ...(firstTextMs !== undefined ? { firstTextMs } : {}),
3581
+ ...(firstSegmentMs !== undefined ? { firstSegmentMs } : {}),
3582
+ ...(firstAudioMs !== undefined ? { firstAudioMs } : {}),
3583
+ ...(firstContentAudioMs !== undefined ? { firstContentAudioMs } : {}),
3584
+ ...(streamFallbackSegments > 0 ? { streamFallbackSegments } : {}),
3585
+ ...(mode === 'blocking' ? { replyMs, synthMs, playMs } : {}),
3586
+ };
3587
+ handler.lastTiming = timing;
3588
+ if (promptReadyMs !== undefined ||
3589
+ providerFirstDeltaMs !== undefined ||
3590
+ generationCompleteMs !== undefined ||
3591
+ semanticReviewCompleteMs !== undefined ||
3592
+ firstSafeReleaseMs !== undefined) {
3593
+ logger.info(`[voice] phase latency: prompt=${promptReadyMs ?? -1}ms ` +
3594
+ `providerDelta=${providerFirstDeltaMs ?? -1}ms ` +
3595
+ `generation=${generationCompleteMs ?? -1}ms ` +
3596
+ `semantic=${semanticReviewCompleteMs ?? -1}ms ` +
3597
+ `safeRelease=${firstSafeReleaseMs ?? -1}ms ` +
3598
+ `contentAudio=${firstContentAudioMs ?? -1}ms`);
3599
+ }
3600
+ if (timing.spokenPrefix) {
3601
+ logger.info(`[voice] spoken-prefix pilot outcome=${timing.spokenPrefix.outcome} ` +
3602
+ `causes=${timing.spokenPrefix.causes.join(',')} ` +
3603
+ `prompt=${timing.spokenPrefix.promptReadyMs ?? -1}ms ` +
3604
+ `generation=${timing.spokenPrefix.generationCompleteMs ?? -1}ms ` +
3605
+ `semantic=${timing.spokenPrefix.semanticReviewCompleteMs ?? -1}ms ` +
3606
+ `continuationPrompt=${timing.continuation?.promptReadyMs ?? -1}ms ` +
3607
+ `continuationGeneration=${timing.continuation?.generationCompleteMs ?? -1}ms`);
3608
+ }
3609
+ try {
3610
+ options.onTiming?.(timing);
3611
+ }
3612
+ catch {
3613
+ /* observability must never break the voice loop */
3614
+ }
3615
+ if (activeAborts.get(voiceTurnId) === controller)
3616
+ activeAborts.delete(voiceTurnId);
3617
+ if (latestTurnId === voiceTurnId)
3618
+ latestTurnId = [...activeAborts.keys()].at(-1);
3619
+ }
3620
+ };
3621
+ handler.interrupt = (turnId) => {
3622
+ const controller = turnId
3623
+ ? activeAborts.get(turnId)
3624
+ : latestTurnId
3625
+ ? activeAborts.get(latestTurnId)
3626
+ : undefined;
3627
+ if (!controller)
3628
+ return; // nothing in flight → clean no-op
3629
+ try {
3630
+ controller.abort();
3631
+ }
3632
+ catch {
3633
+ /* never-throws */
3634
+ }
3635
+ };
3636
+ return handler;
3637
+ }
3638
+ //# sourceMappingURL=voice-loop.js.map