@getpaseo/server 0.1.15 → 0.1.17

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 (364) hide show
  1. package/dist/scripts/daemon-runner.js +53 -14
  2. package/dist/scripts/daemon-runner.js.map +1 -1
  3. package/dist/scripts/dev-runner.js +9 -16
  4. package/dist/scripts/dev-runner.js.map +1 -1
  5. package/dist/scripts/supervisor.js +40 -13
  6. package/dist/scripts/supervisor.js.map +1 -1
  7. package/dist/server/client/daemon-client.d.ts +63 -6
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +436 -92
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/server/agent/agent-manager.d.ts +13 -1
  12. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-manager.js +404 -39
  14. package/dist/server/server/agent/agent-manager.js.map +1 -1
  15. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  16. package/dist/server/server/agent/agent-metadata-generator.js +13 -4
  17. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  18. package/dist/server/server/agent/agent-projections.d.ts +5 -0
  19. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  20. package/dist/server/server/agent/agent-projections.js +24 -0
  21. package/dist/server/server/agent/agent-projections.js.map +1 -1
  22. package/dist/server/server/agent/agent-response-loop.js +1 -1
  23. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  24. package/dist/server/server/agent/agent-sdk-types.d.ts +20 -0
  25. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  26. package/dist/server/server/agent/agent-sdk-types.js +11 -1
  27. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  28. package/dist/server/server/agent/agent-storage.d.ts +20 -6
  29. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  30. package/dist/server/server/agent/agent-storage.js +43 -72
  31. package/dist/server/server/agent/agent-storage.js.map +1 -1
  32. package/dist/server/server/agent/agent-title-limits.d.ts +3 -0
  33. package/dist/server/server/agent/agent-title-limits.d.ts.map +1 -0
  34. package/dist/server/server/agent/agent-title-limits.js +3 -0
  35. package/dist/server/server/agent/agent-title-limits.js.map +1 -0
  36. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +29 -0
  37. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -0
  38. package/dist/server/server/agent/providers/claude/model-catalog.js +70 -0
  39. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -0
  40. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +44 -0
  41. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -0
  42. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  43. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  44. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  45. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +17 -0
  46. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  47. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  48. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +2 -0
  49. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  50. package/dist/server/server/agent/providers/claude-agent.d.ts +10 -3
  51. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  52. package/dist/server/server/agent/providers/claude-agent.js +1702 -330
  53. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  54. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  55. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +81 -28
  56. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  57. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  58. package/dist/server/server/agent/providers/codex-app-server-agent.js +50 -9
  59. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  60. package/dist/server/server/agent/providers/opencode-agent.d.ts +10 -1
  61. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  62. package/dist/server/server/agent/providers/opencode-agent.js +207 -176
  63. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  64. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +55 -0
  65. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  66. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +1 -0
  67. package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
  68. package/dist/server/server/agent/timeline-projection.d.ts +20 -0
  69. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  70. package/dist/server/server/agent/timeline-projection.js +73 -0
  71. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  72. package/dist/server/server/bootstrap.d.ts +15 -0
  73. package/dist/server/server/bootstrap.d.ts.map +1 -1
  74. package/dist/server/server/bootstrap.js +27 -4
  75. package/dist/server/server/bootstrap.js.map +1 -1
  76. package/dist/server/server/client-message-id.d.ts +3 -0
  77. package/dist/server/server/client-message-id.d.ts.map +1 -0
  78. package/dist/server/server/client-message-id.js +12 -0
  79. package/dist/server/server/client-message-id.js.map +1 -0
  80. package/dist/server/server/file-download/token-store.d.ts +0 -1
  81. package/dist/server/server/file-download/token-store.d.ts.map +1 -1
  82. package/dist/server/server/file-download/token-store.js.map +1 -1
  83. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  84. package/dist/server/server/file-explorer/service.js +56 -36
  85. package/dist/server/server/file-explorer/service.js.map +1 -1
  86. package/dist/server/server/index.js +85 -29
  87. package/dist/server/server/index.js.map +1 -1
  88. package/dist/server/server/logger.d.ts +24 -3
  89. package/dist/server/server/logger.d.ts.map +1 -1
  90. package/dist/server/server/logger.js +157 -21
  91. package/dist/server/server/logger.js.map +1 -1
  92. package/dist/server/server/persisted-config.d.ts +94 -8
  93. package/dist/server/server/persisted-config.d.ts.map +1 -1
  94. package/dist/server/server/persisted-config.js +25 -3
  95. package/dist/server/server/persisted-config.js.map +1 -1
  96. package/dist/server/server/persistence-hooks.js +1 -1
  97. package/dist/server/server/persistence-hooks.js.map +1 -1
  98. package/dist/server/server/pid-lock.d.ts +6 -2
  99. package/dist/server/server/pid-lock.d.ts.map +1 -1
  100. package/dist/server/server/pid-lock.js +7 -10
  101. package/dist/server/server/pid-lock.js.map +1 -1
  102. package/dist/server/server/relay-transport.js +28 -28
  103. package/dist/server/server/relay-transport.js.map +1 -1
  104. package/dist/server/server/session.d.ts +60 -4
  105. package/dist/server/server/session.d.ts.map +1 -1
  106. package/dist/server/server/session.js +854 -190
  107. package/dist/server/server/session.js.map +1 -1
  108. package/dist/server/server/websocket-server.d.ts +24 -5
  109. package/dist/server/server/websocket-server.d.ts.map +1 -1
  110. package/dist/server/server/websocket-server.js +400 -77
  111. package/dist/server/server/websocket-server.js.map +1 -1
  112. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  113. package/dist/server/server/worktree-bootstrap.js +45 -2
  114. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  115. package/dist/server/shared/daemon-endpoints.d.ts +9 -1
  116. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
  117. package/dist/server/shared/daemon-endpoints.js +18 -3
  118. package/dist/server/shared/daemon-endpoints.js.map +1 -1
  119. package/dist/server/shared/messages.d.ts +4432 -380
  120. package/dist/server/shared/messages.d.ts.map +1 -1
  121. package/dist/server/shared/messages.js +139 -6
  122. package/dist/server/shared/messages.js.map +1 -1
  123. package/dist/server/shared/tool-call-display.d.ts.map +1 -1
  124. package/dist/server/shared/tool-call-display.js +7 -0
  125. package/dist/server/shared/tool-call-display.js.map +1 -1
  126. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  127. package/dist/server/terminal/terminal-manager.js +1 -13
  128. package/dist/server/terminal/terminal-manager.js.map +1 -1
  129. package/dist/server/terminal/terminal.d.ts.map +1 -1
  130. package/dist/server/terminal/terminal.js +29 -5
  131. package/dist/server/terminal/terminal.js.map +1 -1
  132. package/dist/server/utils/worktree.d.ts +1 -0
  133. package/dist/server/utils/worktree.d.ts.map +1 -1
  134. package/dist/server/utils/worktree.js +17 -2
  135. package/dist/server/utils/worktree.js.map +1 -1
  136. package/dist/src/server/agent/activity-curator.js +228 -0
  137. package/dist/src/server/agent/activity-curator.js.map +1 -0
  138. package/dist/src/server/agent/agent-manager.js +1712 -0
  139. package/dist/src/server/agent/agent-manager.js.map +1 -0
  140. package/dist/src/server/agent/agent-metadata-generator.js +163 -0
  141. package/dist/src/server/agent/agent-metadata-generator.js.map +1 -0
  142. package/dist/src/server/agent/agent-projections.js +262 -0
  143. package/dist/src/server/agent/agent-projections.js.map +1 -0
  144. package/dist/src/server/agent/agent-response-loop.js +304 -0
  145. package/dist/src/server/agent/agent-response-loop.js.map +1 -0
  146. package/dist/src/server/agent/agent-sdk-types.js +12 -0
  147. package/dist/src/server/agent/agent-sdk-types.js.map +1 -0
  148. package/dist/src/server/agent/agent-storage.js +299 -0
  149. package/dist/src/server/agent/agent-storage.js.map +1 -0
  150. package/dist/src/server/agent/agent-title-limits.js +3 -0
  151. package/dist/src/server/agent/agent-title-limits.js.map +1 -0
  152. package/dist/src/server/agent/audio-utils.js +19 -0
  153. package/dist/src/server/agent/audio-utils.js.map +1 -0
  154. package/dist/src/server/agent/dictation-debug.js +50 -0
  155. package/dist/src/server/agent/dictation-debug.js.map +1 -0
  156. package/dist/src/server/agent/mcp-server.js +787 -0
  157. package/dist/src/server/agent/mcp-server.js.map +1 -0
  158. package/dist/src/server/agent/orchestrator-instructions.js +51 -0
  159. package/dist/src/server/agent/orchestrator-instructions.js.map +1 -0
  160. package/dist/src/server/agent/pcm16-resampler.js +63 -0
  161. package/dist/src/server/agent/pcm16-resampler.js.map +1 -0
  162. package/dist/src/server/agent/provider-launch-config.js +83 -0
  163. package/dist/src/server/agent/provider-launch-config.js.map +1 -0
  164. package/dist/src/server/agent/provider-manifest.js +97 -0
  165. package/dist/src/server/agent/provider-manifest.js.map +1 -0
  166. package/dist/src/server/agent/provider-registry.js +45 -0
  167. package/dist/src/server/agent/provider-registry.js.map +1 -0
  168. package/dist/src/server/agent/providers/claude/model-catalog.js +70 -0
  169. package/dist/src/server/agent/providers/claude/model-catalog.js.map +1 -0
  170. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  171. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  172. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +109 -0
  173. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
  174. package/dist/src/server/agent/providers/claude/tool-call-mapper.js +238 -0
  175. package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
  176. package/dist/src/server/agent/providers/claude-agent.js +3747 -0
  177. package/dist/src/server/agent/providers/claude-agent.js.map +1 -0
  178. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  179. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
  180. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +720 -0
  181. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
  182. package/dist/src/server/agent/providers/codex-app-server-agent.js +2601 -0
  183. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -0
  184. package/dist/src/server/agent/providers/codex-rollout-timeline.js +487 -0
  185. package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +1 -0
  186. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
  187. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
  188. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +151 -0
  189. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
  190. package/dist/src/server/agent/providers/opencode-agent.js +905 -0
  191. package/dist/src/server/agent/providers/opencode-agent.js.map +1 -0
  192. package/dist/src/server/agent/providers/tool-call-detail-primitives.js +552 -0
  193. package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
  194. package/dist/src/server/agent/providers/tool-call-mapper-utils.js +109 -0
  195. package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
  196. package/dist/src/server/agent/recordings-debug.js +19 -0
  197. package/dist/src/server/agent/recordings-debug.js.map +1 -0
  198. package/dist/src/server/agent/stt-debug.js +33 -0
  199. package/dist/src/server/agent/stt-debug.js.map +1 -0
  200. package/dist/src/server/agent/stt-manager.js +233 -0
  201. package/dist/src/server/agent/stt-manager.js.map +1 -0
  202. package/dist/src/server/agent/timeline-append.js +27 -0
  203. package/dist/src/server/agent/timeline-append.js.map +1 -0
  204. package/dist/src/server/agent/timeline-projection.js +215 -0
  205. package/dist/src/server/agent/timeline-projection.js.map +1 -0
  206. package/dist/src/server/agent/tool-name-normalization.js +45 -0
  207. package/dist/src/server/agent/tool-name-normalization.js.map +1 -0
  208. package/dist/src/server/agent/tts-debug.js +24 -0
  209. package/dist/src/server/agent/tts-debug.js.map +1 -0
  210. package/dist/src/server/agent/tts-manager.js +249 -0
  211. package/dist/src/server/agent/tts-manager.js.map +1 -0
  212. package/dist/src/server/agent/wait-for-agent-tracker.js +53 -0
  213. package/dist/src/server/agent/wait-for-agent-tracker.js.map +1 -0
  214. package/dist/src/server/agent-attention-policy.js +40 -0
  215. package/dist/src/server/agent-attention-policy.js.map +1 -0
  216. package/dist/src/server/allowed-hosts.js +94 -0
  217. package/dist/src/server/allowed-hosts.js.map +1 -0
  218. package/dist/src/server/bootstrap.js +498 -0
  219. package/dist/src/server/bootstrap.js.map +1 -0
  220. package/dist/src/server/client-message-id.js +12 -0
  221. package/dist/src/server/client-message-id.js.map +1 -0
  222. package/dist/src/server/config.js +84 -0
  223. package/dist/src/server/config.js.map +1 -0
  224. package/dist/src/server/connection-offer.js +60 -0
  225. package/dist/src/server/connection-offer.js.map +1 -0
  226. package/dist/src/server/daemon-keypair.js +40 -0
  227. package/dist/src/server/daemon-keypair.js.map +1 -0
  228. package/dist/src/server/daemon-version.js +22 -0
  229. package/dist/src/server/daemon-version.js.map +1 -0
  230. package/dist/src/server/dictation/dictation-stream-manager.js +568 -0
  231. package/dist/src/server/dictation/dictation-stream-manager.js.map +1 -0
  232. package/dist/src/server/file-download/token-store.js +40 -0
  233. package/dist/src/server/file-download/token-store.js.map +1 -0
  234. package/dist/src/server/file-explorer/service.js +183 -0
  235. package/dist/src/server/file-explorer/service.js.map +1 -0
  236. package/dist/src/server/json-utils.js +45 -0
  237. package/dist/src/server/json-utils.js.map +1 -0
  238. package/dist/src/server/messages.js +29 -0
  239. package/dist/src/server/messages.js.map +1 -0
  240. package/dist/src/server/package-version.js +47 -0
  241. package/dist/src/server/package-version.js.map +1 -0
  242. package/dist/src/server/paseo-home.js +19 -0
  243. package/dist/src/server/paseo-home.js.map +1 -0
  244. package/dist/src/server/path-utils.js +20 -0
  245. package/dist/src/server/path-utils.js.map +1 -0
  246. package/dist/src/server/persisted-config.js +259 -0
  247. package/dist/src/server/persisted-config.js.map +1 -0
  248. package/dist/src/server/persistence-hooks.js +60 -0
  249. package/dist/src/server/persistence-hooks.js.map +1 -0
  250. package/dist/src/server/pid-lock.js +126 -0
  251. package/dist/src/server/pid-lock.js.map +1 -0
  252. package/dist/src/server/push/push-service.js +68 -0
  253. package/dist/src/server/push/push-service.js.map +1 -0
  254. package/dist/src/server/push/token-store.js +70 -0
  255. package/dist/src/server/push/token-store.js.map +1 -0
  256. package/dist/src/server/relay-transport.js +457 -0
  257. package/dist/src/server/relay-transport.js.map +1 -0
  258. package/dist/src/server/server-id.js +63 -0
  259. package/dist/src/server/server-id.js.map +1 -0
  260. package/dist/src/server/session.js +5947 -0
  261. package/dist/src/server/session.js.map +1 -0
  262. package/dist/src/server/speech/audio.js +101 -0
  263. package/dist/src/server/speech/audio.js.map +1 -0
  264. package/dist/src/server/speech/provider-resolver.js +7 -0
  265. package/dist/src/server/speech/provider-resolver.js.map +1 -0
  266. package/dist/src/server/speech/providers/local/config.js +83 -0
  267. package/dist/src/server/speech/providers/local/config.js.map +1 -0
  268. package/dist/src/server/speech/providers/local/models.js +17 -0
  269. package/dist/src/server/speech/providers/local/models.js.map +1 -0
  270. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +422 -0
  271. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -0
  272. package/dist/src/server/speech/providers/local/runtime.js +253 -0
  273. package/dist/src/server/speech/providers/local/runtime.js.map +1 -0
  274. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +166 -0
  275. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +1 -0
  276. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +165 -0
  277. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +1 -0
  278. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +68 -0
  279. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -0
  280. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +79 -0
  281. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -0
  282. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +11 -0
  283. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +1 -0
  284. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +102 -0
  285. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -0
  286. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +131 -0
  287. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -0
  288. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +132 -0
  289. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -0
  290. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +112 -0
  291. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -0
  292. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +140 -0
  293. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -0
  294. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +95 -0
  295. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -0
  296. package/dist/src/server/speech/providers/openai/config.js +99 -0
  297. package/dist/src/server/speech/providers/openai/config.js.map +1 -0
  298. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +165 -0
  299. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +1 -0
  300. package/dist/src/server/speech/providers/openai/runtime.js +114 -0
  301. package/dist/src/server/speech/providers/openai/runtime.js.map +1 -0
  302. package/dist/src/server/speech/providers/openai/stt.js +208 -0
  303. package/dist/src/server/speech/providers/openai/stt.js.map +1 -0
  304. package/dist/src/server/speech/providers/openai/tts.js +46 -0
  305. package/dist/src/server/speech/providers/openai/tts.js.map +1 -0
  306. package/dist/src/server/speech/speech-config-resolver.js +85 -0
  307. package/dist/src/server/speech/speech-config-resolver.js.map +1 -0
  308. package/dist/src/server/speech/speech-provider.js +2 -0
  309. package/dist/src/server/speech/speech-provider.js.map +1 -0
  310. package/dist/src/server/speech/speech-runtime.js +497 -0
  311. package/dist/src/server/speech/speech-runtime.js.map +1 -0
  312. package/dist/src/server/speech/speech-types.js +8 -0
  313. package/dist/src/server/speech/speech-types.js.map +1 -0
  314. package/dist/src/server/utils/diff-highlighter.js +244 -0
  315. package/dist/src/server/utils/diff-highlighter.js.map +1 -0
  316. package/dist/src/server/utils/syntax-highlighter.js +145 -0
  317. package/dist/src/server/utils/syntax-highlighter.js.map +1 -0
  318. package/dist/src/server/voice-config.js +51 -0
  319. package/dist/src/server/voice-config.js.map +1 -0
  320. package/dist/src/server/voice-mcp-bridge-command.js +31 -0
  321. package/dist/src/server/voice-mcp-bridge-command.js.map +1 -0
  322. package/dist/src/server/voice-mcp-bridge.js +109 -0
  323. package/dist/src/server/voice-mcp-bridge.js.map +1 -0
  324. package/dist/src/server/voice-permission-policy.js +13 -0
  325. package/dist/src/server/voice-permission-policy.js.map +1 -0
  326. package/dist/src/server/voice-types.js +2 -0
  327. package/dist/src/server/voice-types.js.map +1 -0
  328. package/dist/src/server/websocket-server.js +967 -0
  329. package/dist/src/server/websocket-server.js.map +1 -0
  330. package/dist/src/server/worktree-bootstrap.js +497 -0
  331. package/dist/src/server/worktree-bootstrap.js.map +1 -0
  332. package/dist/src/shared/agent-attention-notification.js +130 -0
  333. package/dist/src/shared/agent-attention-notification.js.map +1 -0
  334. package/dist/src/shared/agent-lifecycle.js +8 -0
  335. package/dist/src/shared/agent-lifecycle.js.map +1 -0
  336. package/dist/src/shared/binary-mux.js +114 -0
  337. package/dist/src/shared/binary-mux.js.map +1 -0
  338. package/dist/src/shared/connection-offer.js +17 -0
  339. package/dist/src/shared/connection-offer.js.map +1 -0
  340. package/dist/src/shared/daemon-endpoints.js +113 -0
  341. package/dist/src/shared/daemon-endpoints.js.map +1 -0
  342. package/dist/src/shared/messages.js +2001 -0
  343. package/dist/src/shared/messages.js.map +1 -0
  344. package/dist/src/shared/path-utils.js +16 -0
  345. package/dist/src/shared/path-utils.js.map +1 -0
  346. package/dist/src/shared/tool-call-display.js +93 -0
  347. package/dist/src/shared/tool-call-display.js.map +1 -0
  348. package/dist/src/terminal/terminal-manager.js +136 -0
  349. package/dist/src/terminal/terminal-manager.js.map +1 -0
  350. package/dist/src/terminal/terminal.js +410 -0
  351. package/dist/src/terminal/terminal.js.map +1 -0
  352. package/dist/src/utils/checkout-git.js +1397 -0
  353. package/dist/src/utils/checkout-git.js.map +1 -0
  354. package/dist/src/utils/directory-suggestions.js +655 -0
  355. package/dist/src/utils/directory-suggestions.js.map +1 -0
  356. package/dist/src/utils/path.js +15 -0
  357. package/dist/src/utils/path.js.map +1 -0
  358. package/dist/src/utils/project-icon.js +391 -0
  359. package/dist/src/utils/project-icon.js.map +1 -0
  360. package/dist/src/utils/worktree-metadata.js +116 -0
  361. package/dist/src/utils/worktree-metadata.js.map +1 -0
  362. package/dist/src/utils/worktree.js +741 -0
  363. package/dist/src/utils/worktree.js.map +1 -0
  364. package/package.json +15 -7
@@ -4,7 +4,7 @@ import { hostname as getHostname } from "node:os";
4
4
  import { WSInboundMessageSchema, wrapSessionMessage, } from "./messages.js";
5
5
  import { asUint8Array, decodeBinaryMuxFrame, encodeBinaryMuxFrame, } from "../shared/binary-mux.js";
6
6
  import { isHostAllowed } from "./allowed-hosts.js";
7
- import { Session } from "./session.js";
7
+ import { Session, } from "./session.js";
8
8
  import { PushTokenStore } from "./push/token-store.js";
9
9
  import { PushService } from "./push/push-service.js";
10
10
  import { computeShouldNotifyClient, computeShouldSendPush, } from "./agent-attention-policy.js";
@@ -68,6 +68,12 @@ function bufferFromWsData(data) {
68
68
  return Buffer.from(data);
69
69
  }
70
70
  const EXTERNAL_SESSION_DISCONNECT_GRACE_MS = 90000;
71
+ const HELLO_TIMEOUT_MS = 15000;
72
+ const WS_CLOSE_HELLO_TIMEOUT = 4001;
73
+ const WS_CLOSE_INVALID_HELLO = 4002;
74
+ const WS_CLOSE_INCOMPATIBLE_PROTOCOL = 4003;
75
+ const WS_PROTOCOL_VERSION = 1;
76
+ const WS_RUNTIME_METRICS_FLUSH_MS = 30000;
71
77
  export class MissingDaemonVersionError extends Error {
72
78
  constructor() {
73
79
  super("VoiceAssistantWebSocketServer requires a non-empty daemonVersion.");
@@ -78,12 +84,33 @@ export class MissingDaemonVersionError extends Error {
78
84
  * WebSocket server that only accepts sockets + parses/forwards messages to the session layer.
79
85
  */
80
86
  export class VoiceAssistantWebSocketServer {
81
- constructor(server, logger, serverId, agentManager, agentStorage, downloadTokenStore, paseoHome, createAgentMcpTransport, wsConfig, speech, terminalManager, voice, dictation, agentProviderRuntimeSettings, daemonVersion) {
87
+ constructor(server, logger, serverId, agentManager, agentStorage, downloadTokenStore, paseoHome, createAgentMcpTransport, wsConfig, speech, terminalManager, voice, dictation, agentProviderRuntimeSettings, daemonVersion, onLifecycleIntent) {
88
+ this.pendingConnections = new Map();
82
89
  this.sessions = new Map();
83
90
  this.externalSessionsByKey = new Map();
84
- this.clientIdCounter = 0;
85
91
  this.voiceSpeakHandlers = new Map();
86
92
  this.voiceCallerContexts = new Map();
93
+ this.runtimeWindowStartedAt = Date.now();
94
+ this.runtimeCounters = {
95
+ connectedAwaitingHello: 0,
96
+ helloResumed: 0,
97
+ helloNew: 0,
98
+ pendingDisconnected: 0,
99
+ sessionDisconnectedWaitingReconnect: 0,
100
+ sessionSocketDisconnectedAttached: 0,
101
+ sessionCleanup: 0,
102
+ validationFailed: 0,
103
+ binaryBeforeHelloRejected: 0,
104
+ pendingMessageRejectedBeforeHello: 0,
105
+ missingConnectionForMessage: 0,
106
+ unexpectedHelloOnActiveConnection: 0,
107
+ relayExternalSocketAttached: 0,
108
+ originRejected: 0,
109
+ hostRejected: 0,
110
+ };
111
+ this.inboundMessageCounts = new Map();
112
+ this.inboundSessionRequestCounts = new Map();
113
+ this.runtimeMetricsInterval = null;
87
114
  this.ACTIVITY_THRESHOLD_MS = 120000;
88
115
  this.logger = logger.child({ module: "websocket-server" });
89
116
  this.serverId = serverId;
@@ -102,6 +129,7 @@ export class VoiceAssistantWebSocketServer {
102
129
  this.voice = voice ?? null;
103
130
  this.dictation = dictation ?? null;
104
131
  this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
132
+ this.onLifecycleIntent = onLifecycleIntent ?? null;
105
133
  this.serverCapabilities = buildServerCapabilities({
106
134
  readiness: this.dictation?.getSpeechReadiness?.() ?? null,
107
135
  });
@@ -120,6 +148,7 @@ export class VoiceAssistantWebSocketServer {
120
148
  const origin = requestMetadata.origin;
121
149
  const requestHost = requestMetadata.host ?? null;
122
150
  if (requestHost && !isHostAllowed(requestHost, allowedHosts)) {
151
+ this.incrementRuntimeCounter("hostRejected");
123
152
  this.logger.warn({ ...requestMetadata, host: requestHost }, "Rejected connection from disallowed host");
124
153
  callback(false, 403, "Host not allowed");
125
154
  return;
@@ -131,6 +160,7 @@ export class VoiceAssistantWebSocketServer {
131
160
  callback(true);
132
161
  }
133
162
  else {
163
+ this.incrementRuntimeCounter("originRejected");
134
164
  this.logger.warn({ ...requestMetadata, origin }, "Rejected connection from origin");
135
165
  callback(false, 403, "Origin not allowed");
136
166
  }
@@ -139,6 +169,11 @@ export class VoiceAssistantWebSocketServer {
139
169
  this.wss.on("connection", (ws, request) => {
140
170
  void this.attachSocket(ws, request);
141
171
  });
172
+ const runtimeMetricsInterval = setInterval(() => {
173
+ this.flushRuntimeMetrics();
174
+ }, WS_RUNTIME_METRICS_FLUSH_MS);
175
+ this.runtimeMetricsInterval = runtimeMetricsInterval;
176
+ runtimeMetricsInterval.unref?.();
142
177
  this.logger.info("WebSocket server initialized on /ws");
143
178
  }
144
179
  broadcast(message) {
@@ -159,26 +194,52 @@ export class VoiceAssistantWebSocketServer {
159
194
  return;
160
195
  }
161
196
  this.serverCapabilities = next;
162
- this.broadcastServerInfo();
197
+ this.broadcastCapabilitiesUpdate();
163
198
  }
164
199
  async attachExternalSocket(ws, metadata) {
200
+ if (metadata?.transport === "relay") {
201
+ this.incrementRuntimeCounter("relayExternalSocketAttached");
202
+ }
165
203
  await this.attachSocket(ws, undefined, metadata);
166
204
  }
167
205
  async close() {
206
+ if (this.runtimeMetricsInterval) {
207
+ clearInterval(this.runtimeMetricsInterval);
208
+ this.runtimeMetricsInterval = null;
209
+ }
210
+ this.flushRuntimeMetrics({ final: true });
168
211
  const uniqueConnections = new Set([
169
212
  ...this.sessions.values(),
170
213
  ...this.externalSessionsByKey.values(),
171
214
  ]);
215
+ const pendingSockets = new Set(this.pendingConnections.keys());
216
+ for (const pending of this.pendingConnections.values()) {
217
+ if (pending.helloTimeout) {
218
+ clearTimeout(pending.helloTimeout);
219
+ pending.helloTimeout = null;
220
+ }
221
+ }
172
222
  const cleanupPromises = [];
173
223
  for (const connection of uniqueConnections) {
174
224
  if (connection.externalDisconnectCleanupTimeout) {
175
225
  clearTimeout(connection.externalDisconnectCleanupTimeout);
176
226
  connection.externalDisconnectCleanupTimeout = null;
177
227
  }
178
- const ws = connection.socketRef.current;
179
228
  cleanupPromises.push(connection.session.cleanup());
229
+ for (const ws of connection.sockets) {
230
+ cleanupPromises.push(new Promise((resolve) => {
231
+ // WebSocket.CLOSED = 3
232
+ if (ws.readyState === 3) {
233
+ resolve();
234
+ return;
235
+ }
236
+ ws.once("close", () => resolve());
237
+ ws.close();
238
+ }));
239
+ }
240
+ }
241
+ for (const ws of pendingSockets) {
180
242
  cleanupPromises.push(new Promise((resolve) => {
181
- // WebSocket.CLOSED = 3
182
243
  if (ws.readyState === 3) {
183
244
  resolve();
184
245
  return;
@@ -188,6 +249,7 @@ export class VoiceAssistantWebSocketServer {
188
249
  }));
189
250
  }
190
251
  await Promise.all(cleanupPromises);
252
+ this.pendingConnections.clear();
191
253
  this.sessions.clear();
192
254
  this.externalSessionsByKey.clear();
193
255
  this.wss.close();
@@ -204,38 +266,20 @@ export class VoiceAssistantWebSocketServer {
204
266
  }
205
267
  ws.send(encodeBinaryMuxFrame(frame));
206
268
  }
207
- async attachSocket(ws, request, metadata) {
208
- const externalSessionKey = metadata?.transport === "relay" && metadata.externalSessionKey.trim().length > 0
209
- ? metadata.externalSessionKey
210
- : null;
211
- if (externalSessionKey) {
212
- const existing = this.externalSessionsByKey.get(externalSessionKey);
213
- if (existing) {
214
- if (existing.externalDisconnectCleanupTimeout) {
215
- clearTimeout(existing.externalDisconnectCleanupTimeout);
216
- existing.externalDisconnectCleanupTimeout = null;
217
- }
218
- const previousSocket = existing.socketRef.current;
219
- if (previousSocket !== ws) {
220
- this.sessions.delete(previousSocket);
221
- existing.socketRef.current = ws;
222
- }
223
- this.sessions.set(ws, existing);
224
- this.sendServerInfo(ws);
225
- existing.connectionLogger.trace({
226
- clientId: existing.clientId,
227
- externalSessionKey,
228
- totalSessions: this.sessions.size,
229
- }, "Client reconnected");
230
- this.bindSocketHandlers(ws, existing);
231
- return;
232
- }
269
+ sendToConnection(connection, message) {
270
+ for (const ws of connection.sockets) {
271
+ this.sendToClient(ws, message);
272
+ }
273
+ }
274
+ sendBinaryToConnection(connection, frame) {
275
+ for (const ws of connection.sockets) {
276
+ this.sendBinaryToClient(ws, frame);
233
277
  }
234
- const clientId = `client-${++this.clientIdCounter}`;
278
+ }
279
+ async attachSocket(ws, request, metadata) {
235
280
  const requestMetadata = extractSocketRequestMetadata(request);
236
281
  const connectionLoggerFields = {
237
- clientId,
238
- transport: externalSessionKey ? "relay" : "direct",
282
+ transport: metadata?.transport === "relay" ? "relay" : "direct",
239
283
  };
240
284
  if (requestMetadata.host) {
241
285
  connectionLoggerFields.host = requestMetadata.host;
@@ -250,14 +294,52 @@ export class VoiceAssistantWebSocketServer {
250
294
  connectionLoggerFields.remoteAddress = requestMetadata.remoteAddress;
251
295
  }
252
296
  const connectionLogger = this.logger.child(connectionLoggerFields);
253
- const socketRef = { current: ws };
297
+ const pending = {
298
+ connectionLogger,
299
+ helloTimeout: null,
300
+ };
301
+ const timeout = setTimeout(() => {
302
+ if (this.pendingConnections.get(ws) !== pending) {
303
+ return;
304
+ }
305
+ pending.helloTimeout = null;
306
+ this.pendingConnections.delete(ws);
307
+ pending.connectionLogger.warn({ timeoutMs: HELLO_TIMEOUT_MS }, "Closing connection due to missing hello");
308
+ try {
309
+ ws.close(WS_CLOSE_HELLO_TIMEOUT, "Hello timeout");
310
+ }
311
+ catch {
312
+ // ignore close errors
313
+ }
314
+ }, HELLO_TIMEOUT_MS);
315
+ pending.helloTimeout = timeout;
316
+ timeout.unref?.();
317
+ this.pendingConnections.set(ws, pending);
318
+ this.incrementRuntimeCounter("connectedAwaitingHello");
319
+ this.bindSocketHandlers(ws);
320
+ pending.connectionLogger.trace({
321
+ totalPendingConnections: this.pendingConnections.size,
322
+ }, "Client connected; awaiting hello");
323
+ }
324
+ createSessionConnection(params) {
325
+ const { ws, clientId, connectionLogger } = params;
326
+ let connection = null;
254
327
  const session = new Session({
255
328
  clientId,
256
329
  onMessage: (msg) => {
257
- this.sendToClient(socketRef.current, wrapSessionMessage(msg));
330
+ if (!connection) {
331
+ return;
332
+ }
333
+ this.sendToConnection(connection, wrapSessionMessage(msg));
258
334
  },
259
335
  onBinaryMessage: (frame) => {
260
- this.sendBinaryToClient(socketRef.current, frame);
336
+ if (!connection) {
337
+ return;
338
+ }
339
+ this.sendBinaryToConnection(connection, frame);
340
+ },
341
+ onLifecycleIntent: (intent) => {
342
+ this.onLifecycleIntent?.(intent);
261
343
  },
262
344
  logger: connectionLogger.child({ module: "session" }),
263
345
  downloadTokenStore: this.downloadTokenStore,
@@ -289,21 +371,98 @@ export class VoiceAssistantWebSocketServer {
289
371
  dictation: this.dictation ?? undefined,
290
372
  agentProviderRuntimeSettings: this.agentProviderRuntimeSettings,
291
373
  });
292
- const connection = {
374
+ connection = {
293
375
  session,
294
376
  clientId,
295
377
  connectionLogger,
296
- socketRef,
297
- externalSessionKey,
378
+ sockets: new Set([ws]),
298
379
  externalDisconnectCleanupTimeout: null,
299
380
  };
300
- this.sessions.set(ws, connection);
301
- if (externalSessionKey) {
302
- this.externalSessionsByKey.set(externalSessionKey, connection);
381
+ return connection;
382
+ }
383
+ clearPendingConnection(ws) {
384
+ const pending = this.pendingConnections.get(ws);
385
+ if (!pending) {
386
+ return null;
387
+ }
388
+ if (pending.helloTimeout) {
389
+ clearTimeout(pending.helloTimeout);
390
+ pending.helloTimeout = null;
391
+ }
392
+ this.pendingConnections.delete(ws);
393
+ return pending;
394
+ }
395
+ buildWelcomeMessage(params) {
396
+ return {
397
+ type: "welcome",
398
+ serverId: this.serverId,
399
+ hostname: getHostname(),
400
+ version: this.daemonVersion,
401
+ resumed: params.resumed,
402
+ ...(this.serverCapabilities ? { capabilities: this.serverCapabilities } : {}),
403
+ };
404
+ }
405
+ handleHello(params) {
406
+ const { ws, message, pending } = params;
407
+ if (message.protocolVersion !== WS_PROTOCOL_VERSION) {
408
+ this.clearPendingConnection(ws);
409
+ pending.connectionLogger.warn({
410
+ receivedProtocolVersion: message.protocolVersion,
411
+ expectedProtocolVersion: WS_PROTOCOL_VERSION,
412
+ }, "Rejected hello due to protocol version mismatch");
413
+ try {
414
+ ws.close(WS_CLOSE_INCOMPATIBLE_PROTOCOL, "Incompatible protocol version");
415
+ }
416
+ catch {
417
+ // ignore close errors
418
+ }
419
+ return;
420
+ }
421
+ const clientId = message.clientId.trim();
422
+ if (clientId.length === 0) {
423
+ this.clearPendingConnection(ws);
424
+ pending.connectionLogger.warn("Rejected hello with empty clientId");
425
+ try {
426
+ ws.close(WS_CLOSE_INVALID_HELLO, "Invalid hello");
427
+ }
428
+ catch {
429
+ // ignore close errors
430
+ }
431
+ return;
432
+ }
433
+ this.clearPendingConnection(ws);
434
+ const existing = this.externalSessionsByKey.get(clientId);
435
+ if (existing) {
436
+ this.incrementRuntimeCounter("helloResumed");
437
+ if (existing.externalDisconnectCleanupTimeout) {
438
+ clearTimeout(existing.externalDisconnectCleanupTimeout);
439
+ existing.externalDisconnectCleanupTimeout = null;
440
+ }
441
+ existing.sockets.add(ws);
442
+ this.sessions.set(ws, existing);
443
+ this.sendToClient(ws, this.buildWelcomeMessage({ resumed: true }));
444
+ existing.connectionLogger.trace({
445
+ clientId,
446
+ resumed: true,
447
+ totalSessions: this.sessions.size,
448
+ }, "Client connected via hello");
449
+ return;
303
450
  }
304
- this.sendServerInfo(ws);
305
- connectionLogger.trace({ clientId, externalSessionKey, totalSessions: this.sessions.size }, "Client connected");
306
- this.bindSocketHandlers(ws, connection);
451
+ const connectionLogger = pending.connectionLogger.child({ clientId });
452
+ this.incrementRuntimeCounter("helloNew");
453
+ const connection = this.createSessionConnection({
454
+ ws,
455
+ clientId,
456
+ connectionLogger,
457
+ });
458
+ this.sessions.set(ws, connection);
459
+ this.externalSessionsByKey.set(clientId, connection);
460
+ this.sendToClient(ws, this.buildWelcomeMessage({ resumed: false }));
461
+ connection.connectionLogger.trace({
462
+ clientId,
463
+ resumed: false,
464
+ totalSessions: this.sessions.size,
465
+ }, "Client connected via hello");
307
466
  }
308
467
  buildServerInfoStatusPayload() {
309
468
  return {
@@ -314,33 +473,29 @@ export class VoiceAssistantWebSocketServer {
314
473
  ...(this.serverCapabilities ? { capabilities: this.serverCapabilities } : {}),
315
474
  };
316
475
  }
317
- broadcastServerInfo() {
476
+ broadcastCapabilitiesUpdate() {
318
477
  this.broadcast(wrapSessionMessage({
319
478
  type: "status",
320
479
  payload: this.buildServerInfoStatusPayload(),
321
480
  }));
322
481
  }
323
- sendServerInfo(ws) {
324
- // Advertise stable server identity immediately on connect (used for URL/shareable IDs).
325
- this.sendToClient(ws, wrapSessionMessage({
326
- type: "status",
327
- payload: this.buildServerInfoStatusPayload(),
328
- }));
329
- }
330
- bindSocketHandlers(ws, connection) {
482
+ bindSocketHandlers(ws) {
331
483
  ws.on("message", (data) => {
332
484
  void this.handleRawMessage(ws, data);
333
485
  });
334
486
  ws.on("close", async (code, reason) => {
335
- await this.detachSocket(ws, connection, {
487
+ await this.detachSocket(ws, {
336
488
  code: typeof code === "number" ? code : undefined,
337
489
  reason,
338
490
  });
339
491
  });
340
492
  ws.on("error", async (error) => {
341
493
  const err = error instanceof Error ? error : new Error(String(error));
342
- connection.connectionLogger.error({ err }, "Client error");
343
- await this.detachSocket(ws, connection, { error: err });
494
+ const active = this.sessions.get(ws);
495
+ const pending = this.pendingConnections.get(ws);
496
+ const log = active?.connectionLogger ?? pending?.connectionLogger ?? this.logger;
497
+ log.error({ err }, "Client error");
498
+ await this.detachSocket(ws, { error: err });
344
499
  });
345
500
  }
346
501
  resolveVoiceSpeakHandler(callerAgentId) {
@@ -349,13 +504,24 @@ export class VoiceAssistantWebSocketServer {
349
504
  resolveVoiceCallerContext(callerAgentId) {
350
505
  return this.voiceCallerContexts.get(callerAgentId) ?? null;
351
506
  }
352
- async detachSocket(ws, connection, details) {
353
- const activeConnection = this.sessions.get(ws);
354
- if (activeConnection !== connection)
507
+ async detachSocket(ws, details) {
508
+ const pending = this.clearPendingConnection(ws);
509
+ if (pending) {
510
+ this.incrementRuntimeCounter("pendingDisconnected");
511
+ pending.connectionLogger.trace({
512
+ code: details.code,
513
+ reason: stringifyCloseReason(details.reason),
514
+ }, "Pending client disconnected");
355
515
  return;
516
+ }
517
+ const connection = this.sessions.get(ws);
518
+ if (!connection) {
519
+ return;
520
+ }
356
521
  this.sessions.delete(ws);
357
- if (connection.externalSessionKey &&
358
- connection.socketRef.current === ws) {
522
+ connection.sockets.delete(ws);
523
+ if (connection.sockets.size === 0) {
524
+ this.incrementRuntimeCounter("sessionDisconnectedWaitingReconnect");
359
525
  if (connection.externalDisconnectCleanupTimeout) {
360
526
  clearTimeout(connection.externalDisconnectCleanupTimeout);
361
527
  }
@@ -369,41 +535,61 @@ export class VoiceAssistantWebSocketServer {
369
535
  connection.externalDisconnectCleanupTimeout = timeout;
370
536
  connection.connectionLogger.trace({
371
537
  clientId: connection.clientId,
372
- externalSessionKey: connection.externalSessionKey,
373
538
  code: details.code,
374
539
  reason: stringifyCloseReason(details.reason),
375
540
  reconnectGraceMs: EXTERNAL_SESSION_DISCONNECT_GRACE_MS,
376
541
  }, "Client disconnected; waiting for reconnect");
377
542
  return;
378
543
  }
544
+ if (connection.sockets.size > 0) {
545
+ this.incrementRuntimeCounter("sessionSocketDisconnectedAttached");
546
+ connection.connectionLogger.trace({
547
+ clientId: connection.clientId,
548
+ remainingSockets: connection.sockets.size,
549
+ code: details.code,
550
+ reason: stringifyCloseReason(details.reason),
551
+ }, "Client socket disconnected; session remains attached");
552
+ return;
553
+ }
379
554
  await this.cleanupConnection(connection, "Client disconnected");
380
555
  }
381
556
  async cleanupConnection(connection, logMessage) {
557
+ this.incrementRuntimeCounter("sessionCleanup");
382
558
  if (connection.externalDisconnectCleanupTimeout) {
383
559
  clearTimeout(connection.externalDisconnectCleanupTimeout);
384
560
  connection.externalDisconnectCleanupTimeout = null;
385
561
  }
386
- const currentSocket = connection.socketRef.current;
387
- this.sessions.delete(currentSocket);
388
- if (connection.externalSessionKey) {
389
- const existing = this.externalSessionsByKey.get(connection.externalSessionKey);
390
- if (existing === connection) {
391
- this.externalSessionsByKey.delete(connection.externalSessionKey);
392
- }
562
+ for (const socket of connection.sockets) {
563
+ this.sessions.delete(socket);
564
+ }
565
+ connection.sockets.clear();
566
+ const existing = this.externalSessionsByKey.get(connection.clientId);
567
+ if (existing === connection) {
568
+ this.externalSessionsByKey.delete(connection.clientId);
393
569
  }
394
570
  connection.connectionLogger.trace({ clientId: connection.clientId, totalSessions: this.sessions.size }, logMessage);
395
571
  await connection.session.cleanup();
396
572
  }
397
573
  async handleRawMessage(ws, data) {
574
+ const activeConnection = this.sessions.get(ws);
575
+ const pendingConnection = this.pendingConnections.get(ws);
576
+ const log = activeConnection?.connectionLogger ?? pendingConnection?.connectionLogger ?? this.logger;
398
577
  try {
399
- const activeConnection = this.sessions.get(ws);
400
578
  const buffer = bufferFromWsData(data);
401
579
  const asBytes = asUint8Array(buffer);
402
580
  if (asBytes) {
403
581
  const frame = decodeBinaryMuxFrame(asBytes);
404
582
  if (frame) {
405
583
  if (!activeConnection) {
406
- this.logger.error("No session found for client");
584
+ this.incrementRuntimeCounter("binaryBeforeHelloRejected");
585
+ log.warn("Rejected binary frame before hello");
586
+ this.clearPendingConnection(ws);
587
+ try {
588
+ ws.close(WS_CLOSE_INVALID_HELLO, "Session message before hello");
589
+ }
590
+ catch {
591
+ // ignore close errors
592
+ }
407
593
  return;
408
594
  }
409
595
  activeConnection.session.handleBinaryFrame(frame);
@@ -413,13 +599,26 @@ export class VoiceAssistantWebSocketServer {
413
599
  const parsed = JSON.parse(buffer.toString());
414
600
  const parsedMessage = WSInboundMessageSchema.safeParse(parsed);
415
601
  if (!parsedMessage.success) {
602
+ this.incrementRuntimeCounter("validationFailed");
603
+ if (pendingConnection) {
604
+ pendingConnection.connectionLogger.warn({
605
+ error: parsedMessage.error.message,
606
+ }, "Rejected pending message before hello");
607
+ this.clearPendingConnection(ws);
608
+ try {
609
+ ws.close(WS_CLOSE_INVALID_HELLO, "Invalid hello");
610
+ }
611
+ catch {
612
+ // ignore close errors
613
+ }
614
+ return;
615
+ }
416
616
  const requestInfo = extractRequestInfoFromUnknownWsInbound(parsed);
417
617
  const isUnknownSchema = requestInfo?.requestId != null &&
418
618
  typeof parsed === "object" &&
419
619
  parsed != null &&
420
620
  "type" in parsed &&
421
621
  parsed.type === "session";
422
- const log = activeConnection?.connectionLogger ?? this.logger;
423
622
  log.warn({
424
623
  clientId: activeConnection?.clientId,
425
624
  requestId: requestInfo?.requestId,
@@ -449,6 +648,7 @@ export class VoiceAssistantWebSocketServer {
449
648
  return;
450
649
  }
451
650
  const message = parsedMessage.data;
651
+ this.recordInboundMessageType(message.type);
452
652
  if (message.type === "ping") {
453
653
  this.sendToClient(ws, { type: "pong" });
454
654
  return;
@@ -456,11 +656,46 @@ export class VoiceAssistantWebSocketServer {
456
656
  if (message.type === "recording_state") {
457
657
  return;
458
658
  }
659
+ if (pendingConnection) {
660
+ if (message.type === "hello") {
661
+ this.handleHello({
662
+ ws,
663
+ message,
664
+ pending: pendingConnection,
665
+ });
666
+ return;
667
+ }
668
+ pendingConnection.connectionLogger.warn({
669
+ messageType: message.type,
670
+ }, "Rejected pending message before hello");
671
+ this.incrementRuntimeCounter("pendingMessageRejectedBeforeHello");
672
+ this.clearPendingConnection(ws);
673
+ try {
674
+ ws.close(WS_CLOSE_INVALID_HELLO, "Session message before hello");
675
+ }
676
+ catch {
677
+ // ignore close errors
678
+ }
679
+ return;
680
+ }
459
681
  if (!activeConnection) {
460
- this.logger.error("No session found for client");
682
+ this.incrementRuntimeCounter("missingConnectionForMessage");
683
+ this.logger.error("No connection found for websocket");
684
+ return;
685
+ }
686
+ if (message.type === "hello") {
687
+ this.incrementRuntimeCounter("unexpectedHelloOnActiveConnection");
688
+ activeConnection.connectionLogger.warn("Received hello on active connection");
689
+ try {
690
+ ws.close(WS_CLOSE_INVALID_HELLO, "Unexpected hello");
691
+ }
692
+ catch {
693
+ // ignore close errors
694
+ }
461
695
  return;
462
696
  }
463
697
  if (message.type === "session") {
698
+ this.recordInboundSessionRequestType(message.message.type);
464
699
  await activeConnection.session.handleMessage(message.message);
465
700
  }
466
701
  }
@@ -482,11 +717,21 @@ export class VoiceAssistantWebSocketServer {
482
717
  const trimmedRawPayload = typeof rawPayload === "string" && rawPayload.length > 2000
483
718
  ? `${rawPayload.slice(0, 2000)}... (truncated)`
484
719
  : rawPayload;
485
- this.logger.error({
720
+ log.error({
486
721
  err,
487
722
  rawPayload: trimmedRawPayload,
488
723
  parsedPayload,
489
724
  }, "Failed to parse/handle message");
725
+ if (this.pendingConnections.has(ws)) {
726
+ this.clearPendingConnection(ws);
727
+ try {
728
+ ws.close(WS_CLOSE_INVALID_HELLO, "Invalid hello");
729
+ }
730
+ catch {
731
+ // ignore close errors
732
+ }
733
+ return;
734
+ }
490
735
  const requestInfo = extractRequestInfoFromUnknownWsInbound(parsedPayload);
491
736
  if (requestInfo) {
492
737
  this.sendToClient(ws, wrapSessionMessage({
@@ -509,6 +754,84 @@ export class VoiceAssistantWebSocketServer {
509
754
  }));
510
755
  }
511
756
  }
757
+ incrementRuntimeCounter(counter) {
758
+ this.runtimeCounters[counter] += 1;
759
+ }
760
+ incrementCount(map, key) {
761
+ map.set(key, (map.get(key) ?? 0) + 1);
762
+ }
763
+ recordInboundMessageType(type) {
764
+ this.incrementCount(this.inboundMessageCounts, type);
765
+ }
766
+ recordInboundSessionRequestType(type) {
767
+ this.incrementCount(this.inboundSessionRequestCounts, type);
768
+ }
769
+ getTopCounts(map, limit) {
770
+ return [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit);
771
+ }
772
+ collectSessionRuntimeMetrics() {
773
+ const uniqueConnections = new Set(this.externalSessionsByKey.values());
774
+ let checkoutDiffTargetCount = 0;
775
+ let checkoutDiffSubscriptionCount = 0;
776
+ let checkoutDiffWatcherCount = 0;
777
+ let checkoutDiffFallbackRefreshTargetCount = 0;
778
+ let terminalDirectorySubscriptionCount = 0;
779
+ let terminalSubscriptionCount = 0;
780
+ let terminalStreamCount = 0;
781
+ for (const connection of uniqueConnections) {
782
+ const sessionMetrics = connection.session.getRuntimeMetrics();
783
+ checkoutDiffTargetCount += sessionMetrics.checkoutDiffTargetCount;
784
+ checkoutDiffSubscriptionCount += sessionMetrics.checkoutDiffSubscriptionCount;
785
+ checkoutDiffWatcherCount += sessionMetrics.checkoutDiffWatcherCount;
786
+ checkoutDiffFallbackRefreshTargetCount +=
787
+ sessionMetrics.checkoutDiffFallbackRefreshTargetCount;
788
+ terminalDirectorySubscriptionCount += sessionMetrics.terminalDirectorySubscriptionCount;
789
+ terminalSubscriptionCount += sessionMetrics.terminalSubscriptionCount;
790
+ terminalStreamCount += sessionMetrics.terminalStreamCount;
791
+ }
792
+ return {
793
+ checkoutDiffTargetCount,
794
+ checkoutDiffSubscriptionCount,
795
+ checkoutDiffWatcherCount,
796
+ checkoutDiffFallbackRefreshTargetCount,
797
+ terminalDirectorySubscriptionCount,
798
+ terminalSubscriptionCount,
799
+ terminalStreamCount,
800
+ };
801
+ }
802
+ flushRuntimeMetrics(options) {
803
+ const now = Date.now();
804
+ const windowMs = Math.max(0, now - this.runtimeWindowStartedAt);
805
+ const activeConnections = new Set(this.sessions.values()).size;
806
+ const activeSockets = this.sessions.size;
807
+ const pendingConnections = this.pendingConnections.size;
808
+ const reconnectGraceSessions = [...this.externalSessionsByKey.values()].filter((connection) => connection.sockets.size === 0 &&
809
+ connection.externalDisconnectCleanupTimeout !== null).length;
810
+ const sessionMetrics = this.collectSessionRuntimeMetrics();
811
+ this.logger.info({
812
+ windowMs,
813
+ final: Boolean(options?.final),
814
+ sessions: {
815
+ activeConnections,
816
+ externalSessionKeys: this.externalSessionsByKey.size,
817
+ reconnectGraceSessions,
818
+ },
819
+ sockets: {
820
+ activeSockets,
821
+ pendingConnections,
822
+ },
823
+ counters: { ...this.runtimeCounters },
824
+ inboundMessageTypesTop: this.getTopCounts(this.inboundMessageCounts, 12),
825
+ inboundSessionRequestTypesTop: this.getTopCounts(this.inboundSessionRequestCounts, 20),
826
+ runtime: sessionMetrics,
827
+ }, "ws_runtime_metrics");
828
+ for (const counter of Object.keys(this.runtimeCounters)) {
829
+ this.runtimeCounters[counter] = 0;
830
+ }
831
+ this.inboundMessageCounts.clear();
832
+ this.inboundSessionRequestCounts.clear();
833
+ this.runtimeWindowStartedAt = now;
834
+ }
512
835
  getClientActivityState(session) {
513
836
  const activity = session.getClientActivity();
514
837
  if (!activity) {