@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
@@ -0,0 +1,967 @@
1
+ import { WebSocketServer } from "ws";
2
+ import { join } from "path";
3
+ import { hostname as getHostname } from "node:os";
4
+ import { WSInboundMessageSchema, wrapSessionMessage, } from "./messages.js";
5
+ import { asUint8Array, decodeBinaryMuxFrame, encodeBinaryMuxFrame, } from "../shared/binary-mux.js";
6
+ import { isHostAllowed } from "./allowed-hosts.js";
7
+ import { Session, } from "./session.js";
8
+ import { PushTokenStore } from "./push/token-store.js";
9
+ import { PushService } from "./push/push-service.js";
10
+ import { computeShouldNotifyClient, computeShouldSendPush, } from "./agent-attention-policy.js";
11
+ import { buildAgentAttentionNotificationPayload, findLatestAssistantMessageFromTimeline, findLatestPermissionRequest, } from "../shared/agent-attention-notification.js";
12
+ function toServerCapabilityState(params) {
13
+ const { state, reason } = params;
14
+ return {
15
+ enabled: state.enabled,
16
+ reason,
17
+ };
18
+ }
19
+ function resolveCapabilityReason(params) {
20
+ const { state, readiness } = params;
21
+ if (state.available) {
22
+ return "";
23
+ }
24
+ if (readiness.voiceFeature.reasonCode === "model_download_in_progress") {
25
+ const baseMessage = readiness.voiceFeature.message.trim();
26
+ if (baseMessage.includes("Try again in a few minutes")) {
27
+ return baseMessage;
28
+ }
29
+ return `${baseMessage} Try again in a few minutes.`;
30
+ }
31
+ return state.message;
32
+ }
33
+ function buildServerCapabilities(params) {
34
+ const readiness = params.readiness;
35
+ if (!readiness) {
36
+ return undefined;
37
+ }
38
+ return {
39
+ voice: {
40
+ dictation: toServerCapabilityState({
41
+ state: readiness.dictation,
42
+ reason: resolveCapabilityReason({
43
+ state: readiness.dictation,
44
+ readiness,
45
+ }),
46
+ }),
47
+ voice: toServerCapabilityState({
48
+ state: readiness.realtimeVoice,
49
+ reason: resolveCapabilityReason({
50
+ state: readiness.realtimeVoice,
51
+ readiness,
52
+ }),
53
+ }),
54
+ },
55
+ };
56
+ }
57
+ function areServerCapabilitiesEqual(current, next) {
58
+ return JSON.stringify(current ?? null) === JSON.stringify(next ?? null);
59
+ }
60
+ function bufferFromWsData(data) {
61
+ if (typeof data === "string")
62
+ return Buffer.from(data, "utf8");
63
+ if (Array.isArray(data)) {
64
+ return Buffer.concat(data.map((item) => Buffer.isBuffer(item) ? item : Buffer.from(item)));
65
+ }
66
+ if (Buffer.isBuffer(data))
67
+ return data;
68
+ return Buffer.from(data);
69
+ }
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;
77
+ export class MissingDaemonVersionError extends Error {
78
+ constructor() {
79
+ super("VoiceAssistantWebSocketServer requires a non-empty daemonVersion.");
80
+ this.name = "MissingDaemonVersionError";
81
+ }
82
+ }
83
+ /**
84
+ * WebSocket server that only accepts sockets + parses/forwards messages to the session layer.
85
+ */
86
+ export class VoiceAssistantWebSocketServer {
87
+ constructor(server, logger, serverId, agentManager, agentStorage, downloadTokenStore, paseoHome, createAgentMcpTransport, wsConfig, speech, terminalManager, voice, dictation, agentProviderRuntimeSettings, daemonVersion, onLifecycleIntent) {
88
+ this.pendingConnections = new Map();
89
+ this.sessions = new Map();
90
+ this.externalSessionsByKey = new Map();
91
+ this.voiceSpeakHandlers = new Map();
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;
114
+ this.ACTIVITY_THRESHOLD_MS = 120000;
115
+ this.logger = logger.child({ module: "websocket-server" });
116
+ this.serverId = serverId;
117
+ if (typeof daemonVersion !== "string" || daemonVersion.trim().length === 0) {
118
+ throw new MissingDaemonVersionError();
119
+ }
120
+ this.daemonVersion = daemonVersion.trim();
121
+ this.agentManager = agentManager;
122
+ this.agentStorage = agentStorage;
123
+ this.downloadTokenStore = downloadTokenStore;
124
+ this.paseoHome = paseoHome;
125
+ this.createAgentMcpTransport = createAgentMcpTransport;
126
+ this.stt = speech?.stt ?? null;
127
+ this.tts = speech?.tts ?? null;
128
+ this.terminalManager = terminalManager ?? null;
129
+ this.voice = voice ?? null;
130
+ this.dictation = dictation ?? null;
131
+ this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
132
+ this.onLifecycleIntent = onLifecycleIntent ?? null;
133
+ this.serverCapabilities = buildServerCapabilities({
134
+ readiness: this.dictation?.getSpeechReadiness?.() ?? null,
135
+ });
136
+ const pushLogger = this.logger.child({ module: "push" });
137
+ this.pushTokenStore = new PushTokenStore(pushLogger, join(paseoHome, "push-tokens.json"));
138
+ this.pushService = new PushService(pushLogger, this.pushTokenStore);
139
+ this.agentManager.setAgentAttentionCallback((params) => {
140
+ this.broadcastAgentAttention(params);
141
+ });
142
+ const { allowedOrigins, allowedHosts } = wsConfig;
143
+ this.wss = new WebSocketServer({
144
+ server,
145
+ path: "/ws",
146
+ verifyClient: ({ req }, callback) => {
147
+ const requestMetadata = extractSocketRequestMetadata(req);
148
+ const origin = requestMetadata.origin;
149
+ const requestHost = requestMetadata.host ?? null;
150
+ if (requestHost && !isHostAllowed(requestHost, allowedHosts)) {
151
+ this.incrementRuntimeCounter("hostRejected");
152
+ this.logger.warn({ ...requestMetadata, host: requestHost }, "Rejected connection from disallowed host");
153
+ callback(false, 403, "Host not allowed");
154
+ return;
155
+ }
156
+ const sameOrigin = !!origin &&
157
+ !!requestHost &&
158
+ (origin === `http://${requestHost}` || origin === `https://${requestHost}`);
159
+ if (!origin || allowedOrigins.has(origin) || sameOrigin) {
160
+ callback(true);
161
+ }
162
+ else {
163
+ this.incrementRuntimeCounter("originRejected");
164
+ this.logger.warn({ ...requestMetadata, origin }, "Rejected connection from origin");
165
+ callback(false, 403, "Origin not allowed");
166
+ }
167
+ },
168
+ });
169
+ this.wss.on("connection", (ws, request) => {
170
+ void this.attachSocket(ws, request);
171
+ });
172
+ const runtimeMetricsInterval = setInterval(() => {
173
+ this.flushRuntimeMetrics();
174
+ }, WS_RUNTIME_METRICS_FLUSH_MS);
175
+ this.runtimeMetricsInterval = runtimeMetricsInterval;
176
+ runtimeMetricsInterval.unref?.();
177
+ this.logger.info("WebSocket server initialized on /ws");
178
+ }
179
+ broadcast(message) {
180
+ const payload = JSON.stringify(message);
181
+ for (const ws of this.sessions.keys()) {
182
+ // WebSocket.OPEN = 1
183
+ if (ws.readyState === 1) {
184
+ ws.send(payload);
185
+ }
186
+ }
187
+ }
188
+ publishSpeechReadiness(readiness) {
189
+ this.updateServerCapabilities(buildServerCapabilities({ readiness }));
190
+ }
191
+ updateServerCapabilities(capabilities) {
192
+ const next = capabilities ?? undefined;
193
+ if (areServerCapabilitiesEqual(this.serverCapabilities, next)) {
194
+ return;
195
+ }
196
+ this.serverCapabilities = next;
197
+ this.broadcastCapabilitiesUpdate();
198
+ }
199
+ async attachExternalSocket(ws, metadata) {
200
+ if (metadata?.transport === "relay") {
201
+ this.incrementRuntimeCounter("relayExternalSocketAttached");
202
+ }
203
+ await this.attachSocket(ws, undefined, metadata);
204
+ }
205
+ async close() {
206
+ if (this.runtimeMetricsInterval) {
207
+ clearInterval(this.runtimeMetricsInterval);
208
+ this.runtimeMetricsInterval = null;
209
+ }
210
+ this.flushRuntimeMetrics({ final: true });
211
+ const uniqueConnections = new Set([
212
+ ...this.sessions.values(),
213
+ ...this.externalSessionsByKey.values(),
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
+ }
222
+ const cleanupPromises = [];
223
+ for (const connection of uniqueConnections) {
224
+ if (connection.externalDisconnectCleanupTimeout) {
225
+ clearTimeout(connection.externalDisconnectCleanupTimeout);
226
+ connection.externalDisconnectCleanupTimeout = null;
227
+ }
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) {
242
+ cleanupPromises.push(new Promise((resolve) => {
243
+ if (ws.readyState === 3) {
244
+ resolve();
245
+ return;
246
+ }
247
+ ws.once("close", () => resolve());
248
+ ws.close();
249
+ }));
250
+ }
251
+ await Promise.all(cleanupPromises);
252
+ this.pendingConnections.clear();
253
+ this.sessions.clear();
254
+ this.externalSessionsByKey.clear();
255
+ this.wss.close();
256
+ }
257
+ sendToClient(ws, message) {
258
+ // WebSocket.OPEN = 1
259
+ if (ws.readyState === 1) {
260
+ ws.send(JSON.stringify(message));
261
+ }
262
+ }
263
+ sendBinaryToClient(ws, frame) {
264
+ if (ws.readyState !== 1) {
265
+ return;
266
+ }
267
+ ws.send(encodeBinaryMuxFrame(frame));
268
+ }
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);
277
+ }
278
+ }
279
+ async attachSocket(ws, request, metadata) {
280
+ const requestMetadata = extractSocketRequestMetadata(request);
281
+ const connectionLoggerFields = {
282
+ transport: metadata?.transport === "relay" ? "relay" : "direct",
283
+ };
284
+ if (requestMetadata.host) {
285
+ connectionLoggerFields.host = requestMetadata.host;
286
+ }
287
+ if (requestMetadata.origin) {
288
+ connectionLoggerFields.origin = requestMetadata.origin;
289
+ }
290
+ if (requestMetadata.userAgent) {
291
+ connectionLoggerFields.userAgent = requestMetadata.userAgent;
292
+ }
293
+ if (requestMetadata.remoteAddress) {
294
+ connectionLoggerFields.remoteAddress = requestMetadata.remoteAddress;
295
+ }
296
+ const connectionLogger = this.logger.child(connectionLoggerFields);
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;
327
+ const session = new Session({
328
+ clientId,
329
+ onMessage: (msg) => {
330
+ if (!connection) {
331
+ return;
332
+ }
333
+ this.sendToConnection(connection, wrapSessionMessage(msg));
334
+ },
335
+ onBinaryMessage: (frame) => {
336
+ if (!connection) {
337
+ return;
338
+ }
339
+ this.sendBinaryToConnection(connection, frame);
340
+ },
341
+ onLifecycleIntent: (intent) => {
342
+ this.onLifecycleIntent?.(intent);
343
+ },
344
+ logger: connectionLogger.child({ module: "session" }),
345
+ downloadTokenStore: this.downloadTokenStore,
346
+ pushTokenStore: this.pushTokenStore,
347
+ paseoHome: this.paseoHome,
348
+ agentManager: this.agentManager,
349
+ agentStorage: this.agentStorage,
350
+ createAgentMcpTransport: this.createAgentMcpTransport,
351
+ stt: this.stt,
352
+ tts: this.tts,
353
+ terminalManager: this.terminalManager,
354
+ voice: this.voice ?? undefined,
355
+ voiceBridge: {
356
+ registerVoiceSpeakHandler: (agentId, handler) => {
357
+ this.voiceSpeakHandlers.set(agentId, handler);
358
+ },
359
+ unregisterVoiceSpeakHandler: (agentId) => {
360
+ this.voiceSpeakHandlers.delete(agentId);
361
+ },
362
+ registerVoiceCallerContext: (agentId, context) => {
363
+ this.voiceCallerContexts.set(agentId, context);
364
+ },
365
+ unregisterVoiceCallerContext: (agentId) => {
366
+ this.voiceCallerContexts.delete(agentId);
367
+ },
368
+ ensureVoiceMcpSocketForAgent: this.voice?.ensureVoiceMcpSocketForAgent,
369
+ removeVoiceMcpSocketForAgent: this.voice?.removeVoiceMcpSocketForAgent,
370
+ },
371
+ dictation: this.dictation ?? undefined,
372
+ agentProviderRuntimeSettings: this.agentProviderRuntimeSettings,
373
+ });
374
+ connection = {
375
+ session,
376
+ clientId,
377
+ connectionLogger,
378
+ sockets: new Set([ws]),
379
+ externalDisconnectCleanupTimeout: null,
380
+ };
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;
450
+ }
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");
466
+ }
467
+ buildServerInfoStatusPayload() {
468
+ return {
469
+ status: "server_info",
470
+ serverId: this.serverId,
471
+ hostname: getHostname(),
472
+ version: this.daemonVersion,
473
+ ...(this.serverCapabilities ? { capabilities: this.serverCapabilities } : {}),
474
+ };
475
+ }
476
+ broadcastCapabilitiesUpdate() {
477
+ this.broadcast(wrapSessionMessage({
478
+ type: "status",
479
+ payload: this.buildServerInfoStatusPayload(),
480
+ }));
481
+ }
482
+ bindSocketHandlers(ws) {
483
+ ws.on("message", (data) => {
484
+ void this.handleRawMessage(ws, data);
485
+ });
486
+ ws.on("close", async (code, reason) => {
487
+ await this.detachSocket(ws, {
488
+ code: typeof code === "number" ? code : undefined,
489
+ reason,
490
+ });
491
+ });
492
+ ws.on("error", async (error) => {
493
+ const err = error instanceof Error ? error : new Error(String(error));
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 });
499
+ });
500
+ }
501
+ resolveVoiceSpeakHandler(callerAgentId) {
502
+ return this.voiceSpeakHandlers.get(callerAgentId) ?? null;
503
+ }
504
+ resolveVoiceCallerContext(callerAgentId) {
505
+ return this.voiceCallerContexts.get(callerAgentId) ?? null;
506
+ }
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");
515
+ return;
516
+ }
517
+ const connection = this.sessions.get(ws);
518
+ if (!connection) {
519
+ return;
520
+ }
521
+ this.sessions.delete(ws);
522
+ connection.sockets.delete(ws);
523
+ if (connection.sockets.size === 0) {
524
+ this.incrementRuntimeCounter("sessionDisconnectedWaitingReconnect");
525
+ if (connection.externalDisconnectCleanupTimeout) {
526
+ clearTimeout(connection.externalDisconnectCleanupTimeout);
527
+ }
528
+ const timeout = setTimeout(() => {
529
+ if (connection.externalDisconnectCleanupTimeout !== timeout) {
530
+ return;
531
+ }
532
+ connection.externalDisconnectCleanupTimeout = null;
533
+ void this.cleanupConnection(connection, "Client disconnected (grace timeout)");
534
+ }, EXTERNAL_SESSION_DISCONNECT_GRACE_MS);
535
+ connection.externalDisconnectCleanupTimeout = timeout;
536
+ connection.connectionLogger.trace({
537
+ clientId: connection.clientId,
538
+ code: details.code,
539
+ reason: stringifyCloseReason(details.reason),
540
+ reconnectGraceMs: EXTERNAL_SESSION_DISCONNECT_GRACE_MS,
541
+ }, "Client disconnected; waiting for reconnect");
542
+ return;
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
+ }
554
+ await this.cleanupConnection(connection, "Client disconnected");
555
+ }
556
+ async cleanupConnection(connection, logMessage) {
557
+ this.incrementRuntimeCounter("sessionCleanup");
558
+ if (connection.externalDisconnectCleanupTimeout) {
559
+ clearTimeout(connection.externalDisconnectCleanupTimeout);
560
+ connection.externalDisconnectCleanupTimeout = null;
561
+ }
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);
569
+ }
570
+ connection.connectionLogger.trace({ clientId: connection.clientId, totalSessions: this.sessions.size }, logMessage);
571
+ await connection.session.cleanup();
572
+ }
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;
577
+ try {
578
+ const buffer = bufferFromWsData(data);
579
+ const asBytes = asUint8Array(buffer);
580
+ if (asBytes) {
581
+ const frame = decodeBinaryMuxFrame(asBytes);
582
+ if (frame) {
583
+ if (!activeConnection) {
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
+ }
593
+ return;
594
+ }
595
+ activeConnection.session.handleBinaryFrame(frame);
596
+ return;
597
+ }
598
+ }
599
+ const parsed = JSON.parse(buffer.toString());
600
+ const parsedMessage = WSInboundMessageSchema.safeParse(parsed);
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
+ }
616
+ const requestInfo = extractRequestInfoFromUnknownWsInbound(parsed);
617
+ const isUnknownSchema = requestInfo?.requestId != null &&
618
+ typeof parsed === "object" &&
619
+ parsed != null &&
620
+ "type" in parsed &&
621
+ parsed.type === "session";
622
+ log.warn({
623
+ clientId: activeConnection?.clientId,
624
+ requestId: requestInfo?.requestId,
625
+ requestType: requestInfo?.requestType,
626
+ error: parsedMessage.error.message,
627
+ }, "WS inbound message validation failed");
628
+ if (requestInfo) {
629
+ this.sendToClient(ws, wrapSessionMessage({
630
+ type: "rpc_error",
631
+ payload: {
632
+ requestId: requestInfo.requestId,
633
+ requestType: requestInfo.requestType,
634
+ error: isUnknownSchema ? "Unknown request schema" : "Invalid message",
635
+ code: isUnknownSchema ? "unknown_schema" : "invalid_message",
636
+ },
637
+ }));
638
+ return;
639
+ }
640
+ const errorMessage = `Invalid message: ${parsedMessage.error.message}`;
641
+ this.sendToClient(ws, wrapSessionMessage({
642
+ type: "status",
643
+ payload: {
644
+ status: "error",
645
+ message: errorMessage,
646
+ },
647
+ }));
648
+ return;
649
+ }
650
+ const message = parsedMessage.data;
651
+ this.recordInboundMessageType(message.type);
652
+ if (message.type === "ping") {
653
+ this.sendToClient(ws, { type: "pong" });
654
+ return;
655
+ }
656
+ if (message.type === "recording_state") {
657
+ return;
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
+ }
681
+ if (!activeConnection) {
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
+ }
695
+ return;
696
+ }
697
+ if (message.type === "session") {
698
+ this.recordInboundSessionRequestType(message.message.type);
699
+ await activeConnection.session.handleMessage(message.message);
700
+ }
701
+ }
702
+ catch (error) {
703
+ const err = error instanceof Error ? error : new Error(String(error));
704
+ let rawPayload = null;
705
+ let parsedPayload = null;
706
+ try {
707
+ const buffer = bufferFromWsData(data);
708
+ rawPayload = buffer.toString();
709
+ parsedPayload = JSON.parse(rawPayload);
710
+ }
711
+ catch (payloadError) {
712
+ rawPayload = rawPayload ?? "<unreadable>";
713
+ parsedPayload = parsedPayload ?? rawPayload;
714
+ const payloadErr = payloadError instanceof Error ? payloadError : new Error(String(payloadError));
715
+ this.logger.error({ err: payloadErr }, "Failed to decode raw payload");
716
+ }
717
+ const trimmedRawPayload = typeof rawPayload === "string" && rawPayload.length > 2000
718
+ ? `${rawPayload.slice(0, 2000)}... (truncated)`
719
+ : rawPayload;
720
+ log.error({
721
+ err,
722
+ rawPayload: trimmedRawPayload,
723
+ parsedPayload,
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
+ }
735
+ const requestInfo = extractRequestInfoFromUnknownWsInbound(parsedPayload);
736
+ if (requestInfo) {
737
+ this.sendToClient(ws, wrapSessionMessage({
738
+ type: "rpc_error",
739
+ payload: {
740
+ requestId: requestInfo.requestId,
741
+ requestType: requestInfo.requestType,
742
+ error: "Invalid message",
743
+ code: "invalid_message",
744
+ },
745
+ }));
746
+ return;
747
+ }
748
+ this.sendToClient(ws, wrapSessionMessage({
749
+ type: "status",
750
+ payload: {
751
+ status: "error",
752
+ message: `Invalid message: ${err.message}`,
753
+ },
754
+ }));
755
+ }
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
+ }
835
+ getClientActivityState(session) {
836
+ const activity = session.getClientActivity();
837
+ if (!activity) {
838
+ return { deviceType: null, focusedAgentId: null, isStale: true, appVisible: false };
839
+ }
840
+ const now = Date.now();
841
+ const ageMs = now - activity.lastActivityAt.getTime();
842
+ const isStale = ageMs >= this.ACTIVITY_THRESHOLD_MS;
843
+ return {
844
+ deviceType: activity.deviceType,
845
+ focusedAgentId: activity.focusedAgentId,
846
+ isStale,
847
+ appVisible: activity.appVisible,
848
+ };
849
+ }
850
+ broadcastAgentAttention(params) {
851
+ const clientEntries = [];
852
+ for (const [ws, connection] of this.sessions) {
853
+ clientEntries.push({
854
+ ws,
855
+ state: this.getClientActivityState(connection.session),
856
+ });
857
+ }
858
+ const allStates = clientEntries.map((e) => e.state);
859
+ const agent = this.agentManager.getAgent(params.agentId);
860
+ const notification = buildAgentAttentionNotificationPayload({
861
+ reason: params.reason,
862
+ serverId: this.serverId,
863
+ agentId: params.agentId,
864
+ assistantMessage: agent
865
+ ? findLatestAssistantMessageFromTimeline(agent.timeline)
866
+ : null,
867
+ permissionRequest: agent
868
+ ? findLatestPermissionRequest(agent.pendingPermissions)
869
+ : null,
870
+ });
871
+ // Push is only a fallback when the user is away from desktop/web.
872
+ // Also suppress push if they're actively using the mobile app.
873
+ const shouldSendPush = computeShouldSendPush({
874
+ reason: params.reason,
875
+ allClientStates: allStates,
876
+ });
877
+ if (shouldSendPush) {
878
+ const tokens = this.pushTokenStore.getAllTokens();
879
+ this.logger.info({ tokenCount: tokens.length }, "Sending push notification");
880
+ if (tokens.length > 0) {
881
+ void this.pushService.sendPush(tokens, notification);
882
+ }
883
+ }
884
+ for (const { ws, state } of clientEntries) {
885
+ const shouldNotify = computeShouldNotifyClient({
886
+ clientState: state,
887
+ allClientStates: allStates,
888
+ agentId: params.agentId,
889
+ });
890
+ const message = wrapSessionMessage({
891
+ type: "agent_stream",
892
+ payload: {
893
+ agentId: params.agentId,
894
+ event: {
895
+ type: "attention_required",
896
+ provider: params.provider,
897
+ reason: params.reason,
898
+ timestamp: new Date().toISOString(),
899
+ shouldNotify,
900
+ notification,
901
+ },
902
+ timestamp: new Date().toISOString(),
903
+ },
904
+ });
905
+ this.sendToClient(ws, message);
906
+ }
907
+ }
908
+ }
909
+ function extractSocketRequestMetadata(request) {
910
+ if (!request || typeof request !== "object") {
911
+ return {};
912
+ }
913
+ const record = request;
914
+ const host = typeof record.headers?.host === "string" ? record.headers.host : undefined;
915
+ const origin = typeof record.headers?.origin === "string" ? record.headers.origin : undefined;
916
+ const userAgent = typeof record.headers?.["user-agent"] === "string"
917
+ ? record.headers["user-agent"]
918
+ : undefined;
919
+ const remoteAddress = typeof record.socket?.remoteAddress === "string"
920
+ ? record.socket.remoteAddress
921
+ : undefined;
922
+ return {
923
+ ...(host ? { host } : {}),
924
+ ...(origin ? { origin } : {}),
925
+ ...(userAgent ? { userAgent } : {}),
926
+ ...(remoteAddress ? { remoteAddress } : {}),
927
+ };
928
+ }
929
+ function stringifyCloseReason(reason) {
930
+ if (typeof reason === "string") {
931
+ return reason.length > 0 ? reason : null;
932
+ }
933
+ if (Buffer.isBuffer(reason)) {
934
+ const text = reason.toString();
935
+ return text.length > 0 ? text : null;
936
+ }
937
+ if (reason == null) {
938
+ return null;
939
+ }
940
+ const text = String(reason);
941
+ return text.length > 0 ? text : null;
942
+ }
943
+ function extractRequestInfoFromUnknownWsInbound(payload) {
944
+ if (!payload || typeof payload !== "object") {
945
+ return null;
946
+ }
947
+ const record = payload;
948
+ // Session-wrapped messages
949
+ if (record.type === "session" && record.message && typeof record.message === "object") {
950
+ const msg = record.message;
951
+ if (typeof msg.requestId === "string") {
952
+ return {
953
+ requestId: msg.requestId,
954
+ ...(typeof msg.type === "string" ? { requestType: msg.type } : {}),
955
+ };
956
+ }
957
+ }
958
+ // Non-session messages (future-proof)
959
+ if (typeof record.requestId === "string") {
960
+ return {
961
+ requestId: record.requestId,
962
+ ...(typeof record.type === "string" ? { requestType: record.type } : {}),
963
+ };
964
+ }
965
+ return null;
966
+ }
967
+ //# sourceMappingURL=websocket-server.js.map