@getpaseo/server 0.1.61 → 0.1.63

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 (464) hide show
  1. package/README.md +4 -0
  2. package/dist/server/client/daemon-client-runtime-metrics.d.ts +6 -6
  3. package/dist/server/client/daemon-client-runtime-metrics.d.ts.map +1 -1
  4. package/dist/server/client/daemon-client-transport-types.d.ts +13 -13
  5. package/dist/server/client/daemon-client-transport-types.d.ts.map +1 -1
  6. package/dist/server/client/daemon-client-websocket-transport.d.ts +1 -1
  7. package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -1
  8. package/dist/server/client/daemon-client-websocket-transport.js +5 -4
  9. package/dist/server/client/daemon-client-websocket-transport.js.map +1 -1
  10. package/dist/server/client/daemon-client.d.ts +59 -37
  11. package/dist/server/client/daemon-client.d.ts.map +1 -1
  12. package/dist/server/client/daemon-client.js +62 -17
  13. package/dist/server/client/daemon-client.js.map +1 -1
  14. package/dist/server/server/agent/agent-loading.d.ts.map +1 -1
  15. package/dist/server/server/agent/agent-loading.js +5 -3
  16. package/dist/server/server/agent/agent-loading.js.map +1 -1
  17. package/dist/server/server/agent/agent-manager.d.ts +45 -19
  18. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  19. package/dist/server/server/agent/agent-manager.js +393 -290
  20. package/dist/server/server/agent/agent-manager.js.map +1 -1
  21. package/dist/server/server/agent/agent-metadata-generator.d.ts +6 -6
  22. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  23. package/dist/server/server/agent/agent-metadata-generator.js +46 -38
  24. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  25. package/dist/server/server/agent/agent-projections.d.ts +4 -6
  26. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  27. package/dist/server/server/agent/agent-projections.js +59 -65
  28. package/dist/server/server/agent/agent-projections.js.map +1 -1
  29. package/dist/server/server/agent/agent-response-loop.d.ts +4 -4
  30. package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
  31. package/dist/server/server/agent/agent-response-loop.js +58 -45
  32. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  33. package/dist/server/server/agent/agent-sdk-types.d.ts +43 -40
  34. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  35. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  36. package/dist/server/server/agent/agent-storage.d.ts +2 -2
  37. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  38. package/dist/server/server/agent/agent-storage.js +29 -36
  39. package/dist/server/server/agent/agent-storage.js.map +1 -1
  40. package/dist/server/server/agent/agent-stream-coalescer.d.ts +6 -6
  41. package/dist/server/server/agent/agent-stream-coalescer.d.ts.map +1 -1
  42. package/dist/server/server/agent/agent-timeline-store-types.d.ts +10 -10
  43. package/dist/server/server/agent/agent-timeline-store-types.d.ts.map +1 -1
  44. package/dist/server/server/agent/agent-timeline-store.d.ts +2 -2
  45. package/dist/server/server/agent/agent-timeline-store.d.ts.map +1 -1
  46. package/dist/server/server/agent/agent-timeline-store.js +85 -64
  47. package/dist/server/server/agent/agent-timeline-store.js.map +1 -1
  48. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  49. package/dist/server/server/agent/mcp-server.js +185 -148
  50. package/dist/server/server/agent/mcp-server.js.map +1 -1
  51. package/dist/server/server/agent/mcp-shared.d.ts +9 -2
  52. package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
  53. package/dist/server/server/agent/mcp-shared.js +2 -0
  54. package/dist/server/server/agent/mcp-shared.js.map +1 -1
  55. package/dist/server/server/agent/model-resolver.d.ts +2 -2
  56. package/dist/server/server/agent/model-resolver.d.ts.map +1 -1
  57. package/dist/server/server/agent/model-resolver.js +9 -5
  58. package/dist/server/server/agent/model-resolver.js.map +1 -1
  59. package/dist/server/server/agent/provider-launch-config.d.ts +28 -17
  60. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  61. package/dist/server/server/agent/provider-launch-config.js +20 -9
  62. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  63. package/dist/server/server/agent/provider-registry.d.ts +4 -2
  64. package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
  65. package/dist/server/server/agent/provider-registry.js +24 -21
  66. package/dist/server/server/agent/provider-registry.js.map +1 -1
  67. package/dist/server/server/agent/provider-snapshot-manager.d.ts +6 -5
  68. package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
  69. package/dist/server/server/agent/provider-snapshot-manager.js +40 -31
  70. package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -1
  71. package/dist/server/server/agent/providers/acp-agent.d.ts +11 -12
  72. package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
  73. package/dist/server/server/agent/providers/acp-agent.js +148 -122
  74. package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
  75. package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +2 -0
  76. package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts.map +1 -1
  77. package/dist/server/server/agent/providers/claude/sidechain-tracker.js +47 -45
  78. package/dist/server/server/agent/providers/claude/sidechain-tracker.js.map +1 -1
  79. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
  80. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -1
  81. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +10 -5
  82. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -1
  83. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  84. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +11 -2
  85. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  86. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +2 -2
  87. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  88. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +20 -13
  89. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  90. package/dist/server/server/agent/providers/claude-agent.d.ts +20 -8
  91. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  92. package/dist/server/server/agent/providers/claude-agent.js +610 -460
  93. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  94. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +2 -2
  95. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts.map +1 -1
  96. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +2 -2
  97. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  98. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +49 -44
  99. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  100. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +27 -8
  101. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  102. package/dist/server/server/agent/providers/codex-app-server-agent.js +564 -492
  103. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  104. package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts +2 -2
  105. package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts.map +1 -1
  106. package/dist/server/server/agent/providers/codex-rollout-timeline.js +58 -47
  107. package/dist/server/server/agent/providers/codex-rollout-timeline.js.map +1 -1
  108. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +2 -2
  109. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +1 -1
  110. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +3 -3
  111. package/dist/server/server/agent/providers/diagnostic-utils.d.ts.map +1 -1
  112. package/dist/server/server/agent/providers/diagnostic-utils.js +82 -9
  113. package/dist/server/server/agent/providers/diagnostic-utils.js.map +1 -1
  114. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +2 -2
  115. package/dist/server/server/agent/providers/generic-acp-agent.d.ts.map +1 -1
  116. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts.map +1 -1
  117. package/dist/server/server/agent/providers/mock-load-test-agent.js.map +1 -1
  118. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +2 -2
  119. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +1 -1
  120. package/dist/server/server/agent/providers/opencode-agent.d.ts +2 -2
  121. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  122. package/dist/server/server/agent/providers/opencode-agent.js +385 -360
  123. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  124. package/dist/server/server/agent/providers/pi-direct-agent.d.ts +1 -0
  125. package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -1
  126. package/dist/server/server/agent/providers/pi-direct-agent.js +109 -140
  127. package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -1
  128. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts.map +1 -1
  129. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +3 -1
  130. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js.map +1 -1
  131. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +3 -3
  132. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  133. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +102 -73
  134. package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
  135. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +2 -2
  136. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts.map +1 -1
  137. package/dist/server/server/agent/stt-manager.d.ts.map +1 -1
  138. package/dist/server/server/agent/stt-manager.js +63 -53
  139. package/dist/server/server/agent/stt-manager.js.map +1 -1
  140. package/dist/server/server/agent/timeline-projection.d.ts +6 -6
  141. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  142. package/dist/server/server/agent/timeline-projection.js +11 -6
  143. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  144. package/dist/server/server/agent/tts-manager.d.ts.map +1 -1
  145. package/dist/server/server/agent/tts-manager.js +1 -0
  146. package/dist/server/server/agent/tts-manager.js.map +1 -1
  147. package/dist/server/server/agent-attention-policy.d.ts +2 -2
  148. package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
  149. package/dist/server/server/bootstrap.d.ts +4 -4
  150. package/dist/server/server/bootstrap.d.ts.map +1 -1
  151. package/dist/server/server/bootstrap.js +493 -485
  152. package/dist/server/server/bootstrap.js.map +1 -1
  153. package/dist/server/server/chat/chat-service.d.ts +1 -1
  154. package/dist/server/server/chat/chat-service.d.ts.map +1 -1
  155. package/dist/server/server/chat/chat-service.js +3 -3
  156. package/dist/server/server/chat/chat-service.js.map +1 -1
  157. package/dist/server/server/checkout-diff-manager.d.ts +2 -2
  158. package/dist/server/server/checkout-diff-manager.d.ts.map +1 -1
  159. package/dist/server/server/checkout-git-utils.d.ts +5 -3
  160. package/dist/server/server/checkout-git-utils.d.ts.map +1 -1
  161. package/dist/server/server/checkout-git-utils.js +1 -2
  162. package/dist/server/server/checkout-git-utils.js.map +1 -1
  163. package/dist/server/server/config.d.ts.map +1 -1
  164. package/dist/server/server/config.js +68 -39
  165. package/dist/server/server/config.js.map +1 -1
  166. package/dist/server/server/connection-offer.d.ts +2 -2
  167. package/dist/server/server/connection-offer.d.ts.map +1 -1
  168. package/dist/server/server/daemon-config-store.d.ts +5 -3
  169. package/dist/server/server/daemon-config-store.d.ts.map +1 -1
  170. package/dist/server/server/daemon-config-store.js +26 -0
  171. package/dist/server/server/daemon-config-store.js.map +1 -1
  172. package/dist/server/server/daemon-keypair.d.ts +2 -2
  173. package/dist/server/server/daemon-keypair.d.ts.map +1 -1
  174. package/dist/server/server/editor-targets.d.ts +4 -4
  175. package/dist/server/server/editor-targets.d.ts.map +1 -1
  176. package/dist/server/server/editor-targets.js +11 -15
  177. package/dist/server/server/editor-targets.js.map +1 -1
  178. package/dist/server/server/exports.d.ts +3 -3
  179. package/dist/server/server/exports.d.ts.map +1 -1
  180. package/dist/server/server/exports.js +1 -3
  181. package/dist/server/server/exports.js.map +1 -1
  182. package/dist/server/server/file-download/token-store.d.ts +4 -4
  183. package/dist/server/server/file-download/token-store.d.ts.map +1 -1
  184. package/dist/server/server/index.js +16 -12
  185. package/dist/server/server/index.js.map +1 -1
  186. package/dist/server/server/logger.d.ts +4 -4
  187. package/dist/server/server/logger.d.ts.map +1 -1
  188. package/dist/server/server/logger.js +26 -20
  189. package/dist/server/server/logger.js.map +1 -1
  190. package/dist/server/server/loop/rpc-schemas.d.ts +52 -52
  191. package/dist/server/server/loop-service.d.ts +13 -12
  192. package/dist/server/server/loop-service.d.ts.map +1 -1
  193. package/dist/server/server/loop-service.js +22 -18
  194. package/dist/server/server/loop-service.js.map +1 -1
  195. package/dist/server/server/package-version.d.ts +2 -2
  196. package/dist/server/server/package-version.d.ts.map +1 -1
  197. package/dist/server/server/package-version.js +19 -17
  198. package/dist/server/server/package-version.js.map +1 -1
  199. package/dist/server/server/pairing-offer.d.ts +2 -2
  200. package/dist/server/server/pairing-offer.d.ts.map +1 -1
  201. package/dist/server/server/paseo-env.d.ts +9 -0
  202. package/dist/server/server/paseo-env.d.ts.map +1 -0
  203. package/dist/server/server/paseo-env.js +70 -0
  204. package/dist/server/server/paseo-env.js.map +1 -0
  205. package/dist/server/server/paseo-worktree-archive-service.d.ts +4 -4
  206. package/dist/server/server/paseo-worktree-archive-service.d.ts.map +1 -1
  207. package/dist/server/server/paseo-worktree-archive-service.js +11 -11
  208. package/dist/server/server/paseo-worktree-archive-service.js.map +1 -1
  209. package/dist/server/server/persisted-config.d.ts +62 -62
  210. package/dist/server/server/persisted-config.d.ts.map +1 -1
  211. package/dist/server/server/persisted-config.js +4 -4
  212. package/dist/server/server/persisted-config.js.map +1 -1
  213. package/dist/server/server/persistence-hooks.d.ts +8 -9
  214. package/dist/server/server/persistence-hooks.d.ts.map +1 -1
  215. package/dist/server/server/persistence-hooks.js +4 -12
  216. package/dist/server/server/persistence-hooks.js.map +1 -1
  217. package/dist/server/server/pid-lock.js.map +1 -1
  218. package/dist/server/server/push/push-service.d.ts.map +1 -1
  219. package/dist/server/server/push/push-service.js +1 -3
  220. package/dist/server/server/push/push-service.js.map +1 -1
  221. package/dist/server/server/relay-transport.d.ts +8 -8
  222. package/dist/server/server/relay-transport.d.ts.map +1 -1
  223. package/dist/server/server/relay-transport.js +27 -16
  224. package/dist/server/server/relay-transport.js.map +1 -1
  225. package/dist/server/server/schedule/service.d.ts.map +1 -1
  226. package/dist/server/server/schedule/service.js +2 -2
  227. package/dist/server/server/schedule/service.js.map +1 -1
  228. package/dist/server/server/script-health-monitor.d.ts.map +1 -1
  229. package/dist/server/server/script-health-monitor.js +7 -6
  230. package/dist/server/server/script-health-monitor.js.map +1 -1
  231. package/dist/server/server/script-proxy.js +1 -1
  232. package/dist/server/server/script-proxy.js.map +1 -1
  233. package/dist/server/server/script-status-projection.d.ts +4 -4
  234. package/dist/server/server/script-status-projection.d.ts.map +1 -1
  235. package/dist/server/server/script-status-projection.js +54 -44
  236. package/dist/server/server/script-status-projection.js.map +1 -1
  237. package/dist/server/server/server-id.d.ts +4 -4
  238. package/dist/server/server/server-id.d.ts.map +1 -1
  239. package/dist/server/server/session.d.ts +50 -19
  240. package/dist/server/server/session.d.ts.map +1 -1
  241. package/dist/server/server/session.js +1116 -783
  242. package/dist/server/server/session.js.map +1 -1
  243. package/dist/server/server/speech/audio.js +1 -1
  244. package/dist/server/server/speech/audio.js.map +1 -1
  245. package/dist/server/server/speech/providers/local/config.d.ts +6 -6
  246. package/dist/server/server/speech/providers/local/config.d.ts.map +1 -1
  247. package/dist/server/server/speech/providers/local/config.js +41 -16
  248. package/dist/server/server/speech/providers/local/config.js.map +1 -1
  249. package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.d.ts +2 -2
  250. package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.d.ts.map +1 -1
  251. package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.js +42 -19
  252. package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -1
  253. package/dist/server/server/speech/providers/local/runtime.d.ts +4 -4
  254. package/dist/server/server/speech/providers/local/runtime.d.ts.map +1 -1
  255. package/dist/server/server/speech/providers/local/runtime.js +108 -77
  256. package/dist/server/server/speech/providers/local/runtime.js.map +1 -1
  257. package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +2 -2
  258. package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts.map +1 -1
  259. package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +1 -4
  260. package/dist/server/server/speech/providers/local/sherpa/model-catalog.js.map +1 -1
  261. package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +2 -2
  262. package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts.map +1 -1
  263. package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +19 -19
  264. package/dist/server/server/speech/providers/local/sherpa/model-downloader.js.map +1 -1
  265. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +28 -7
  266. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts.map +1 -1
  267. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -1
  268. package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.d.ts +23 -4
  269. package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.d.ts.map +1 -1
  270. package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +35 -28
  271. package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -1
  272. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-loader.d.ts +5 -5
  273. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-loader.d.ts.map +1 -1
  274. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +7 -7
  275. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts.map +1 -1
  276. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +5 -0
  277. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -1
  278. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts.map +1 -1
  279. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +3 -1
  280. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -1
  281. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +2 -2
  282. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts.map +1 -1
  283. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +3 -1
  284. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -1
  285. package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.d.ts.map +1 -1
  286. package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.js +10 -4
  287. package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -1
  288. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +2 -2
  289. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts.map +1 -1
  290. package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.d.ts +2 -2
  291. package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.d.ts.map +1 -1
  292. package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.js +4 -1
  293. package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -1
  294. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +2 -2
  295. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts.map +1 -1
  296. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +18 -11
  297. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -1
  298. package/dist/server/server/speech/providers/openai/config.d.ts +2 -2
  299. package/dist/server/server/speech/providers/openai/config.d.ts.map +1 -1
  300. package/dist/server/server/speech/providers/openai/config.js +58 -31
  301. package/dist/server/server/speech/providers/openai/config.js.map +1 -1
  302. package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts.map +1 -1
  303. package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +2 -2
  304. package/dist/server/server/speech/providers/openai/realtime-transcription-session.js.map +1 -1
  305. package/dist/server/server/speech/providers/openai/runtime.d.ts +4 -4
  306. package/dist/server/server/speech/providers/openai/runtime.d.ts.map +1 -1
  307. package/dist/server/server/speech/providers/openai/runtime.js +37 -32
  308. package/dist/server/server/speech/providers/openai/runtime.js.map +1 -1
  309. package/dist/server/server/speech/providers/openai/stt.d.ts.map +1 -1
  310. package/dist/server/server/speech/providers/openai/stt.js +4 -3
  311. package/dist/server/server/speech/providers/openai/stt.js.map +1 -1
  312. package/dist/server/server/speech/providers/openai/tts.d.ts.map +1 -1
  313. package/dist/server/server/speech/providers/openai/tts.js +3 -2
  314. package/dist/server/server/speech/providers/openai/tts.js.map +1 -1
  315. package/dist/server/server/speech/speech-config-resolver.d.ts.map +1 -1
  316. package/dist/server/server/speech/speech-config-resolver.js +46 -17
  317. package/dist/server/server/speech/speech-config-resolver.js.map +1 -1
  318. package/dist/server/server/speech/speech-provider.d.ts +2 -2
  319. package/dist/server/server/speech/speech-provider.d.ts.map +1 -1
  320. package/dist/server/server/speech/speech-runtime.d.ts +6 -6
  321. package/dist/server/server/speech/speech-runtime.d.ts.map +1 -1
  322. package/dist/server/server/speech/speech-runtime.js +17 -17
  323. package/dist/server/server/speech/speech-runtime.js.map +1 -1
  324. package/dist/server/server/speech/speech-types.d.ts +2 -2
  325. package/dist/server/server/speech/speech-types.d.ts.map +1 -1
  326. package/dist/server/server/speech/turn-detection-provider.d.ts +2 -2
  327. package/dist/server/server/speech/turn-detection-provider.d.ts.map +1 -1
  328. package/dist/server/server/utils/diff-highlighter.d.ts +0 -3
  329. package/dist/server/server/utils/diff-highlighter.d.ts.map +1 -1
  330. package/dist/server/server/utils/diff-highlighter.js +67 -66
  331. package/dist/server/server/utils/diff-highlighter.js.map +1 -1
  332. package/dist/server/server/voice/voice-turn-controller.d.ts.map +1 -1
  333. package/dist/server/server/voice/voice-turn-controller.js +1 -0
  334. package/dist/server/server/voice/voice-turn-controller.js.map +1 -1
  335. package/dist/server/server/voice-types.d.ts +2 -2
  336. package/dist/server/server/voice-types.d.ts.map +1 -1
  337. package/dist/server/server/websocket-server.d.ts +33 -23
  338. package/dist/server/server/websocket-server.d.ts.map +1 -1
  339. package/dist/server/server/websocket-server.js +349 -241
  340. package/dist/server/server/websocket-server.js.map +1 -1
  341. package/dist/server/server/workspace-git-metadata.d.ts +2 -2
  342. package/dist/server/server/workspace-git-metadata.d.ts.map +1 -1
  343. package/dist/server/server/workspace-git-metadata.js +2 -32
  344. package/dist/server/server/workspace-git-metadata.js.map +1 -1
  345. package/dist/server/server/workspace-git-service.d.ts +17 -13
  346. package/dist/server/server/workspace-git-service.d.ts.map +1 -1
  347. package/dist/server/server/workspace-git-service.js +232 -140
  348. package/dist/server/server/workspace-git-service.js.map +1 -1
  349. package/dist/server/server/workspace-reconciliation-service.d.ts +5 -4
  350. package/dist/server/server/workspace-reconciliation-service.d.ts.map +1 -1
  351. package/dist/server/server/workspace-reconciliation-service.js +82 -82
  352. package/dist/server/server/workspace-reconciliation-service.js.map +1 -1
  353. package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -1
  354. package/dist/server/server/workspace-registry-bootstrap.js +40 -33
  355. package/dist/server/server/workspace-registry-bootstrap.js.map +1 -1
  356. package/dist/server/server/workspace-registry-model.d.ts +19 -6
  357. package/dist/server/server/workspace-registry-model.d.ts.map +1 -1
  358. package/dist/server/server/workspace-registry-model.js +35 -21
  359. package/dist/server/server/workspace-registry-model.js.map +1 -1
  360. package/dist/server/server/workspace-registry.d.ts +2 -2
  361. package/dist/server/server/workspace-script-runtime-store.d.ts +2 -2
  362. package/dist/server/server/workspace-script-runtime-store.d.ts.map +1 -1
  363. package/dist/server/server/workspace-service-env.js +3 -3
  364. package/dist/server/server/workspace-service-env.js.map +1 -1
  365. package/dist/server/server/worktree-bootstrap.d.ts +4 -4
  366. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  367. package/dist/server/server/worktree-bootstrap.js +95 -67
  368. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  369. package/dist/server/server/worktree-session.d.ts +8 -8
  370. package/dist/server/server/worktree-session.d.ts.map +1 -1
  371. package/dist/server/server/worktree-session.js +27 -19
  372. package/dist/server/server/worktree-session.js.map +1 -1
  373. package/dist/server/services/github-service.d.ts +2 -7
  374. package/dist/server/services/github-service.d.ts.map +1 -1
  375. package/dist/server/services/github-service.js +156 -157
  376. package/dist/server/services/github-service.js.map +1 -1
  377. package/dist/server/shared/agent-attention-notification.d.ts +9 -8
  378. package/dist/server/shared/agent-attention-notification.d.ts.map +1 -1
  379. package/dist/server/shared/agent-attention-notification.js +27 -17
  380. package/dist/server/shared/agent-attention-notification.js.map +1 -1
  381. package/dist/server/shared/daemon-endpoints.d.ts +2 -2
  382. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
  383. package/dist/server/shared/daemon-endpoints.js +17 -2
  384. package/dist/server/shared/daemon-endpoints.js.map +1 -1
  385. package/dist/server/shared/messages.d.ts +21962 -3049
  386. package/dist/server/shared/messages.d.ts.map +1 -1
  387. package/dist/server/shared/messages.js +79 -2
  388. package/dist/server/shared/messages.js.map +1 -1
  389. package/dist/server/shared/terminal-stream-protocol.d.ts +2 -2
  390. package/dist/server/shared/terminal-stream-protocol.d.ts.map +1 -1
  391. package/dist/server/shared/tool-call-display.d.ts +2 -2
  392. package/dist/server/shared/tool-call-display.d.ts.map +1 -1
  393. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  394. package/dist/server/terminal/terminal-manager.js +1 -3
  395. package/dist/server/terminal/terminal-manager.js.map +1 -1
  396. package/dist/server/terminal/terminal-output-coalescer.d.ts +6 -6
  397. package/dist/server/terminal/terminal-output-coalescer.d.ts.map +1 -1
  398. package/dist/server/terminal/terminal.d.ts +3 -2
  399. package/dist/server/terminal/terminal.d.ts.map +1 -1
  400. package/dist/server/terminal/terminal.js +57 -19
  401. package/dist/server/terminal/terminal.js.map +1 -1
  402. package/dist/server/utils/checkout-git.d.ts +13 -12
  403. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  404. package/dist/server/utils/checkout-git.js +416 -282
  405. package/dist/server/utils/checkout-git.js.map +1 -1
  406. package/dist/server/utils/directory-suggestions.js +12 -33
  407. package/dist/server/utils/directory-suggestions.js.map +1 -1
  408. package/dist/server/utils/executable.d.ts +1 -14
  409. package/dist/server/utils/executable.d.ts.map +1 -1
  410. package/dist/server/utils/executable.js +13 -49
  411. package/dist/server/utils/executable.js.map +1 -1
  412. package/dist/server/utils/github-remote.d.ts +13 -0
  413. package/dist/server/utils/github-remote.d.ts.map +1 -0
  414. package/dist/server/utils/github-remote.js +128 -0
  415. package/dist/server/utils/github-remote.js.map +1 -0
  416. package/dist/server/utils/paseo-config-file.d.ts +30 -0
  417. package/dist/server/utils/paseo-config-file.d.ts.map +1 -0
  418. package/dist/server/utils/paseo-config-file.js +90 -0
  419. package/dist/server/utils/paseo-config-file.js.map +1 -0
  420. package/dist/server/utils/paseo-config-schema.d.ts +290 -0
  421. package/dist/server/utils/paseo-config-schema.d.ts.map +1 -0
  422. package/dist/server/utils/paseo-config-schema.js +60 -0
  423. package/dist/server/utils/paseo-config-schema.js.map +1 -0
  424. package/dist/server/utils/project-icon.d.ts.map +1 -1
  425. package/dist/server/utils/project-icon.js +84 -109
  426. package/dist/server/utils/project-icon.js.map +1 -1
  427. package/dist/server/utils/promise-timeout.d.ts +2 -2
  428. package/dist/server/utils/promise-timeout.d.ts.map +1 -1
  429. package/dist/server/utils/run-git-command.d.ts +3 -1
  430. package/dist/server/utils/run-git-command.d.ts.map +1 -1
  431. package/dist/server/utils/run-git-command.js +10 -1
  432. package/dist/server/utils/run-git-command.js.map +1 -1
  433. package/dist/server/utils/script-hostname.d.ts +2 -2
  434. package/dist/server/utils/script-hostname.d.ts.map +1 -1
  435. package/dist/server/utils/spawn.d.ts +10 -3
  436. package/dist/server/utils/spawn.d.ts.map +1 -1
  437. package/dist/server/utils/spawn.js +30 -5
  438. package/dist/server/utils/spawn.js.map +1 -1
  439. package/dist/server/utils/windows-command.d.ts +15 -0
  440. package/dist/server/utils/windows-command.d.ts.map +1 -0
  441. package/dist/server/utils/windows-command.js +37 -0
  442. package/dist/server/utils/windows-command.js.map +1 -0
  443. package/dist/server/utils/worktree.d.ts +10 -7
  444. package/dist/server/utils/worktree.d.ts.map +1 -1
  445. package/dist/server/utils/worktree.js +64 -55
  446. package/dist/server/utils/worktree.js.map +1 -1
  447. package/dist/src/server/pid-lock.js.map +1 -1
  448. package/package.json +15 -20
  449. package/dist/server/server/agent/llm-openai.d.ts +0 -7
  450. package/dist/server/server/agent/llm-openai.d.ts.map +0 -1
  451. package/dist/server/server/agent/llm-openai.js +0 -8
  452. package/dist/server/server/agent/llm-openai.js.map +0 -1
  453. package/dist/server/server/agent/orchestrator.d.ts +0 -12
  454. package/dist/server/server/agent/orchestrator.d.ts.map +0 -1
  455. package/dist/server/server/agent/orchestrator.js +0 -12
  456. package/dist/server/server/agent/orchestrator.js.map +0 -1
  457. package/dist/server/server/types.d.ts +0 -5
  458. package/dist/server/server/types.d.ts.map +0 -1
  459. package/dist/server/server/types.js +0 -3
  460. package/dist/server/server/types.js.map +0 -1
  461. package/dist/server/server/workspace-registry.test-helpers.d.ts +0 -37
  462. package/dist/server/server/workspace-registry.test-helpers.d.ts.map +0 -1
  463. package/dist/server/server/workspace-registry.test-helpers.js +0 -121
  464. package/dist/server/server/workspace-registry.test-helpers.js.map +0 -1
@@ -2,7 +2,8 @@ import equal from "fast-deep-equal";
2
2
  import { v4 as uuidv4 } from "uuid";
3
3
  import { TTLCache } from "@isaacs/ttlcache";
4
4
  import pMemoize from "p-memoize";
5
- import { resolve, sep } from "path";
5
+ import { realpathSync } from "node:fs";
6
+ import { basename, resolve, sep } from "path";
6
7
  import { homedir } from "node:os";
7
8
  import { z } from "zod";
8
9
  import { isLegacyEditorTargetId, serializeAgentStreamEvent, } from "./messages.js";
@@ -16,7 +17,7 @@ import { isPaseoDictationDebugEnabled } from "./agent/recordings-debug.js";
16
17
  import { listAvailableEditorTargets, openInEditorTarget } from "./editor-targets.js";
17
18
  import { DictationStreamManager, } from "./dictation/dictation-stream-manager.js";
18
19
  import { createVoiceTurnController, } from "./voice/voice-turn-controller.js";
19
- import { buildConfigOverrides, extractTimestamps, toAgentPersistenceHandle, } from "./persistence-hooks.js";
20
+ import { buildConfigOverrides, extractTimestamps, isStoredAgentProviderAvailable, toAgentPersistenceHandle, } from "./persistence-hooks.js";
20
21
  import { ensureAgentLoaded } from "./agent/agent-loading.js";
21
22
  import { sendPromptToAgent, unarchiveAgentState } from "./agent/mcp-shared.js";
22
23
  import { experimental_createMCPClient } from "ai";
@@ -24,6 +25,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
24
25
  import { buildWorkspaceScriptPayloads } from "./script-status-projection.js";
25
26
  import { deriveProjectSlug } from "./workspace-git-metadata.js";
26
27
  import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
28
+ import { applyMutableProviderConfigToOverrides } from "./daemon-config-store.js";
27
29
  import { buildProviderRegistry } from "./agent/provider-registry.js";
28
30
  import { resolveSnapshotCwd } from "./agent/provider-snapshot-manager.js";
29
31
  import { scheduleAgentMetadataGeneration } from "./agent/agent-metadata-generator.js";
@@ -32,11 +34,12 @@ import { MAX_EXPLICIT_AGENT_TITLE_CHARS } from "./agent/agent-title-limits.js";
32
34
  import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
33
35
  import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from "./agent/timeline-projection.js";
34
36
  import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from "./agent/agent-response-loop.js";
35
- import { checkoutLiteFromGitSnapshot, normalizeWorkspaceId as normalizePersistedWorkspaceId, deriveProjectGroupingName, deriveWorkspaceId, deriveProjectRootPath, deriveProjectKind, deriveWorkspaceKind, deriveWorkspaceDisplayName, buildProjectPlacementForCwd as buildProjectPlacementForCwdStandalone, } from "./workspace-registry-model.js";
37
+ import { checkoutLiteFromGitSnapshot, normalizeWorkspaceId as normalizePersistedWorkspaceId, deriveProjectGroupingName, classifyDirectoryForProjectMembership, deriveWorkspaceDisplayName, } from "./workspace-registry-model.js";
36
38
  import { createPersistedProjectRecord, createPersistedWorkspaceRecord, } from "./workspace-registry.js";
37
39
  import { buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, wrapSpokenInput, } from "./voice-config.js";
38
40
  import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
39
41
  import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from "./file-explorer/service.js";
42
+ import { readPaseoConfigForEdit, writePaseoConfigForEdit, } from "../utils/paseo-config-file.js";
40
43
  import { runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
41
44
  import { archivePersistedWorkspaceRecord } from "./workspace-archive-service.js";
42
45
  import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
@@ -57,13 +60,98 @@ import { assertSafeGitRef as assertWorktreeSafeGitRef, buildAgentSessionConfig a
57
60
  import { killTerminalsUnderPath as killWorktreeTerminalsUnderPath } from "./paseo-worktree-archive-service.js";
58
61
  import { toWorktreeWireError } from "./worktree-errors.js";
59
62
  const MAX_INITIAL_AGENT_TITLE_CHARS = Math.min(60, MAX_EXPLICIT_AGENT_TITLE_CHARS);
60
- const WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT = "__removed__";
63
+ const WORKSPACE_GIT_WATCH_REMOVED_STATE_KEY = "__removed__";
64
+ async function resolveKnownProjectRootForConfig(input) {
65
+ const requestedRoot = canonicalizeConfigRoot(input.repoRoot);
66
+ const projects = await input.projectRegistry.list();
67
+ for (const project of projects) {
68
+ if (project.archivedAt !== null) {
69
+ continue;
70
+ }
71
+ const projectRoot = canonicalizeConfigRoot(project.rootPath);
72
+ if (requestedRoot === projectRoot) {
73
+ return projectRoot;
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+ function canonicalizeConfigRoot(repoRoot) {
79
+ const resolved = resolve(repoRoot);
80
+ try {
81
+ return stripTrailingPathSeparators(realpathSync(resolved));
82
+ }
83
+ catch {
84
+ return stripTrailingPathSeparators(resolved);
85
+ }
86
+ }
87
+ function stripTrailingPathSeparators(path) {
88
+ let normalized = path;
89
+ while (normalized.length > 1 && normalized.endsWith(sep)) {
90
+ normalized = normalized.slice(0, -1);
91
+ }
92
+ return normalized;
93
+ }
61
94
  // TODO: Remove once all app store clients are on >=0.1.45 and understand arbitrary provider strings.
62
95
  // Clients before 0.1.45 validate providers with z.enum(["claude", "codex", "opencode"]) and reject
63
96
  // the entire session message if they encounter an unknown provider.
64
97
  const LEGACY_PROVIDER_IDS = new Set(["claude", "codex", "opencode"]);
65
98
  const MIN_VERSION_ALL_PROVIDERS = "0.1.45";
66
99
  const MIN_VERSION_FLEXIBLE_EDITOR_IDS = "0.1.50";
100
+ function errorToFriendlyMessage(error) {
101
+ if (error instanceof Error)
102
+ return error.message;
103
+ if (typeof error === "string")
104
+ return error;
105
+ return "Unknown error";
106
+ }
107
+ function resolveSubscriptionId(subscribe, requestedSubscriptionId) {
108
+ if (!subscribe)
109
+ return null;
110
+ if (requestedSubscriptionId && requestedSubscriptionId.length > 0) {
111
+ return requestedSubscriptionId;
112
+ }
113
+ return uuidv4();
114
+ }
115
+ function diffChangeTypeFor(file) {
116
+ if (file.isNew)
117
+ return "A";
118
+ if (file.isDeleted)
119
+ return "D";
120
+ return "M";
121
+ }
122
+ function buildWorkspaceCheckout(workspace, project) {
123
+ if (project.kind !== "git") {
124
+ return {
125
+ cwd: workspace.cwd,
126
+ isGit: false,
127
+ currentBranch: null,
128
+ remoteUrl: null,
129
+ worktreeRoot: null,
130
+ isPaseoOwnedWorktree: false,
131
+ mainRepoRoot: null,
132
+ };
133
+ }
134
+ if (workspace.kind === "worktree") {
135
+ return {
136
+ cwd: workspace.cwd,
137
+ isGit: true,
138
+ currentBranch: workspace.displayName,
139
+ remoteUrl: null,
140
+ worktreeRoot: workspace.cwd,
141
+ isPaseoOwnedWorktree: true,
142
+ mainRepoRoot: project.rootPath,
143
+ };
144
+ }
145
+ return {
146
+ cwd: workspace.cwd,
147
+ isGit: true,
148
+ currentBranch: workspace.displayName,
149
+ remoteUrl: null,
150
+ worktreeRoot: workspace.cwd,
151
+ isPaseoOwnedWorktree: false,
152
+ mainRepoRoot: null,
153
+ };
154
+ }
67
155
  function isAppVersionAtLeast(appVersion, minVersion) {
68
156
  if (!appVersion)
69
157
  return false;
@@ -119,6 +207,50 @@ export function resolveCreateAgentTitles(options) {
119
207
  provisionalTitle,
120
208
  };
121
209
  }
210
+ function parseFetchWorkspacesCursorSort(raw) {
211
+ const cursorSort = [];
212
+ for (const item of raw) {
213
+ if (!item ||
214
+ typeof item !== "object" ||
215
+ typeof item.key !== "string" ||
216
+ typeof item.direction !== "string") {
217
+ throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
218
+ }
219
+ const key = item.key;
220
+ const direction = item.direction;
221
+ if ((key !== "status_priority" &&
222
+ key !== "activity_at" &&
223
+ key !== "name" &&
224
+ key !== "project_id") ||
225
+ (direction !== "asc" && direction !== "desc")) {
226
+ throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
227
+ }
228
+ cursorSort.push({ key, direction });
229
+ }
230
+ return cursorSort;
231
+ }
232
+ function parseFetchAgentsCursorSort(raw) {
233
+ const cursorSort = [];
234
+ for (const item of raw) {
235
+ if (!item ||
236
+ typeof item !== "object" ||
237
+ typeof item.key !== "string" ||
238
+ typeof item.direction !== "string") {
239
+ throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
240
+ }
241
+ const key = item.key;
242
+ const direction = item.direction;
243
+ if ((key !== "status_priority" &&
244
+ key !== "created_at" &&
245
+ key !== "updated_at" &&
246
+ key !== "title") ||
247
+ (direction !== "asc" && direction !== "desc")) {
248
+ throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
249
+ }
250
+ cursorSort.push({ key, direction });
251
+ }
252
+ return cursorSort;
253
+ }
122
254
  export function resolveWaitForFinishError(options) {
123
255
  if (options.status !== "error") {
124
256
  return null;
@@ -292,53 +424,13 @@ export class Session {
292
424
  this.getDaemonTcpPort = getDaemonTcpPort ?? null;
293
425
  this.getDaemonTcpHost = getDaemonTcpHost ?? null;
294
426
  this.resolveScriptHealth = resolveScriptHealth ?? null;
295
- if (this.terminalManager) {
296
- this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => this.handleTerminalsChanged(event));
297
- }
298
- if (this.providerSnapshotManager) {
299
- const handleProviderSnapshotChange = (entries, cwd) => {
300
- // COMPAT(providersSnapshot): keep provider visibility gating for older clients.
301
- const visibleEntries = entries.filter((entry) => this.isProviderVisibleToClient(entry.provider));
302
- this.emit({
303
- type: "providers_snapshot_update",
304
- payload: {
305
- cwd,
306
- entries: visibleEntries,
307
- generatedAt: new Date().toISOString(),
308
- },
309
- });
310
- };
311
- this.providerSnapshotManager.on("change", handleProviderSnapshotChange);
312
- this.unsubscribeProviderSnapshotEvents = () => {
313
- this.providerSnapshotManager?.off("change", handleProviderSnapshotChange);
314
- };
315
- }
316
- this.resolveVoiceTurnDetection = toResolver(voice?.turnDetection ?? null);
317
- this.registerVoiceSpeakHandler = voiceBridge?.registerVoiceSpeakHandler;
318
- this.unregisterVoiceSpeakHandler = voiceBridge?.unregisterVoiceSpeakHandler;
319
- this.registerVoiceCallerContext = voiceBridge?.registerVoiceCallerContext;
320
- this.unregisterVoiceCallerContext = voiceBridge?.unregisterVoiceCallerContext;
321
- this.getSpeechReadiness = dictation?.getSpeechReadiness;
427
+ this.subscribeToOptionalManagers();
428
+ this.bindVoiceBridges({ voice, voiceBridge, dictation });
322
429
  this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
323
430
  this.providerOverrides = providerOverrides;
324
431
  this.isDev = isDev === true;
325
432
  this.abortController = new AbortController();
326
- this.providerRegistry = buildProviderRegistry(this.sessionLogger, {
327
- runtimeSettings: this.agentProviderRuntimeSettings,
328
- providerOverrides: this.providerOverrides,
329
- workspaceGitService: this.workspaceGitService,
330
- isDev: this.isDev,
331
- });
332
- // Initialize per-session managers
333
- this.ttsManager = new TTSManager(this.sessionId, this.sessionLogger, tts);
334
- this.sttManager = new STTManager(this.sessionId, this.sessionLogger, stt);
335
- this.dictationStreamManager = new DictationStreamManager({
336
- logger: this.sessionLogger,
337
- sessionId: this.sessionId,
338
- emit: (msg) => this.handleDictationManagerMessage(msg),
339
- stt: dictation?.stt ?? null,
340
- finalTimeoutMs: dictation?.finalTimeoutMs,
341
- });
433
+ this.initializePerSessionManagers({ tts, stt, dictation });
342
434
  // Initialize agent MCP client asynchronously
343
435
  void this.initializeAgentMcp();
344
436
  this.subscribeToAgentEvents();
@@ -349,9 +441,9 @@ export class Session {
349
441
  this.appVersion = appVersion;
350
442
  }
351
443
  }
352
- async primeWorkspaceGitWatchFingerprintForWorkspace(workspace) {
444
+ async syncWorkspaceGitObserverForWorkspace(workspace) {
353
445
  const descriptor = await this.describeWorkspaceRecordWithGitData(workspace);
354
- await this.primeWorkspaceGitWatchFingerprints([descriptor]);
446
+ this.syncWorkspaceGitObservers([descriptor]);
355
447
  }
356
448
  async emitWorkspaceUpdateForWorkspaceId(workspaceId) {
357
449
  await this.emitWorkspaceUpdatesForWorkspaceIds([workspaceId], { skipReconcile: true });
@@ -363,7 +455,7 @@ export class Session {
363
455
  await this.emitWorkspaceUpdatesForCwds(cwds);
364
456
  }
365
457
  async warmWorkspaceGitDataForWorkspace(workspace) {
366
- await this.primeWorkspaceGitWatchFingerprintForWorkspace(workspace);
458
+ await this.syncWorkspaceGitObserverForWorkspace(workspace);
367
459
  await this.emitWorkspaceUpdateForWorkspaceId(workspace.workspaceId);
368
460
  }
369
461
  /**
@@ -427,16 +519,11 @@ export class Session {
427
519
  return;
428
520
  }
429
521
  this.sessionLogger.debug({ agentId, lifecycle: snapshot.lifecycle, hasInFlightRun }, "interruptAgentIfRunning: interrupting");
430
- try {
431
- const t0 = Date.now();
432
- const cancelled = await this.agentManager.cancelAgentRun(agentId);
433
- this.sessionLogger.debug({ agentId, cancelled, durationMs: Date.now() - t0 }, "interruptAgentIfRunning: cancelAgentRun completed");
434
- if (!cancelled) {
435
- this.sessionLogger.warn({ agentId }, "interruptAgentIfRunning: reported running but no active run was cancelled");
436
- }
437
- }
438
- catch (error) {
439
- throw error;
522
+ const t0 = Date.now();
523
+ const cancelled = await this.agentManager.cancelAgentRun(agentId);
524
+ this.sessionLogger.debug({ agentId, cancelled, durationMs: Date.now() - t0 }, "interruptAgentIfRunning: cancelAgentRun completed");
525
+ if (!cancelled) {
526
+ this.sessionLogger.warn({ agentId }, "interruptAgentIfRunning: reported running but no active run was cancelled");
440
527
  }
441
528
  }
442
529
  hasActiveAgentRun(agentId) {
@@ -464,12 +551,7 @@ export class Session {
464
551
  }
465
552
  catch (error) {
466
553
  this.handleAgentRunError(agentId, error, "Failed to start agent run");
467
- const message = error instanceof Error
468
- ? error.message
469
- : typeof error === "string"
470
- ? error
471
- : "Unknown error";
472
- return { ok: false, error: message };
554
+ return { ok: false, error: errorToFriendlyMessage(error) };
473
555
  }
474
556
  void (async () => {
475
557
  try {
@@ -486,7 +568,7 @@ export class Session {
486
568
  return { ok: true };
487
569
  }
488
570
  handleAgentRunError(agentId, error, context) {
489
- const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
571
+ const message = errorToFriendlyMessage(error);
490
572
  this.sessionLogger.error({ err: error, agentId, context }, `${context} for agent ${agentId}`);
491
573
  this.emit({
492
574
  type: "activity_log",
@@ -522,6 +604,50 @@ export class Session {
522
604
  /**
523
605
  * Subscribe to AgentManager events and forward them to the client
524
606
  */
607
+ subscribeToOptionalManagers() {
608
+ if (this.terminalManager) {
609
+ this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => this.handleTerminalsChanged(event));
610
+ }
611
+ if (this.providerSnapshotManager) {
612
+ const handleProviderSnapshotChange = (entries, cwd) => {
613
+ // COMPAT(providersSnapshot): keep provider visibility gating for older clients.
614
+ const visibleEntries = entries.filter((entry) => this.isProviderVisibleToClient(entry.provider));
615
+ this.emit({
616
+ type: "providers_snapshot_update",
617
+ payload: {
618
+ cwd,
619
+ entries: visibleEntries,
620
+ generatedAt: new Date().toISOString(),
621
+ },
622
+ });
623
+ };
624
+ this.providerSnapshotManager.on("change", handleProviderSnapshotChange);
625
+ this.unsubscribeProviderSnapshotEvents = () => {
626
+ this.providerSnapshotManager?.off("change", handleProviderSnapshotChange);
627
+ };
628
+ }
629
+ }
630
+ bindVoiceBridges(params) {
631
+ const { voice, voiceBridge, dictation } = params;
632
+ this.resolveVoiceTurnDetection = toResolver(voice?.turnDetection ?? null);
633
+ this.registerVoiceSpeakHandler = voiceBridge?.registerVoiceSpeakHandler;
634
+ this.unregisterVoiceSpeakHandler = voiceBridge?.unregisterVoiceSpeakHandler;
635
+ this.registerVoiceCallerContext = voiceBridge?.registerVoiceCallerContext;
636
+ this.unregisterVoiceCallerContext = voiceBridge?.unregisterVoiceCallerContext;
637
+ this.getSpeechReadiness = dictation?.getSpeechReadiness;
638
+ }
639
+ initializePerSessionManagers(params) {
640
+ const { tts, stt, dictation } = params;
641
+ this.ttsManager = new TTSManager(this.sessionId, this.sessionLogger, tts);
642
+ this.sttManager = new STTManager(this.sessionId, this.sessionLogger, stt);
643
+ this.dictationStreamManager = new DictationStreamManager({
644
+ logger: this.sessionLogger,
645
+ sessionId: this.sessionId,
646
+ emit: (msg) => this.handleDictationManagerMessage(msg),
647
+ stt: dictation?.stt ?? null,
648
+ finalTimeoutMs: dictation?.finalTimeoutMs,
649
+ });
650
+ }
525
651
  subscribeToAgentEvents() {
526
652
  if (this.unsubscribeAgentEvents) {
527
653
  this.unsubscribeAgentEvents();
@@ -618,8 +744,19 @@ export class Session {
618
744
  payload.archivedAt = storedRecord?.archivedAt ?? null;
619
745
  return payload;
620
746
  }
621
- buildStoredAgentPayload(record) {
622
- return buildStoredAgentPayload(record, this.providerRegistry, this.sessionLogger);
747
+ getProviderRegistry() {
748
+ return buildProviderRegistry(this.sessionLogger, {
749
+ runtimeSettings: this.agentProviderRuntimeSettings,
750
+ providerOverrides: applyMutableProviderConfigToOverrides(this.providerOverrides, this.daemonConfigStore.get().providers),
751
+ workspaceGitService: this.workspaceGitService,
752
+ isDev: this.isDev,
753
+ });
754
+ }
755
+ getRegisteredProviderIds() {
756
+ return Object.keys(this.getProviderRegistry());
757
+ }
758
+ buildStoredAgentPayload(record, registeredProviderIds = this.getRegisteredProviderIds()) {
759
+ return buildStoredAgentPayload(record, registeredProviderIds);
623
760
  }
624
761
  isProviderVisibleToClient(provider) {
625
762
  if (clientSupportsAllProviders(this.appVersion)) {
@@ -633,44 +770,34 @@ export class Session {
633
770
  }
634
771
  return editors.filter((editor) => isLegacyEditorTargetId(editor.id));
635
772
  }
636
- matchesAgentFilter(options) {
637
- const { agent, project, filter } = options;
638
- if (filter?.labels) {
639
- const matchesLabels = Object.entries(filter.labels).every(([key, value]) => agent.labels[key] === value);
640
- if (!matchesLabels) {
641
- return false;
642
- }
643
- }
644
- const includeArchived = filter?.includeArchived ?? false;
645
- if (!includeArchived && agent.archivedAt) {
646
- return false;
773
+ agentThinkingOptionMatchesFilter(agent, filter) {
774
+ if (filter.thinkingOptionId === undefined) {
775
+ return true;
647
776
  }
648
- if (filter?.thinkingOptionId !== undefined) {
649
- const expectedThinkingOptionId = resolveEffectiveThinkingOptionId({
650
- configuredThinkingOptionId: filter.thinkingOptionId ?? null,
777
+ const expectedThinkingOptionId = resolveEffectiveThinkingOptionId({
778
+ configuredThinkingOptionId: filter.thinkingOptionId ?? null,
779
+ });
780
+ const resolvedThinkingOptionId = agent.effectiveThinkingOptionId ??
781
+ resolveEffectiveThinkingOptionId({
782
+ runtimeInfo: agent.runtimeInfo,
783
+ configuredThinkingOptionId: agent.thinkingOptionId ?? null,
651
784
  });
652
- const resolvedThinkingOptionId = agent.effectiveThinkingOptionId ??
653
- resolveEffectiveThinkingOptionId({
654
- runtimeInfo: agent.runtimeInfo,
655
- configuredThinkingOptionId: agent.thinkingOptionId ?? null,
656
- });
657
- if (resolvedThinkingOptionId !== expectedThinkingOptionId) {
658
- return false;
659
- }
660
- }
661
- if (filter?.statuses && filter.statuses.length > 0) {
785
+ return resolvedThinkingOptionId === expectedThinkingOptionId;
786
+ }
787
+ matchesAgentStructuralFilter(agent, project, filter) {
788
+ if (filter.statuses && filter.statuses.length > 0) {
662
789
  const statuses = new Set(filter.statuses);
663
790
  if (!statuses.has(agent.status)) {
664
791
  return false;
665
792
  }
666
793
  }
667
- if (typeof filter?.requiresAttention === "boolean") {
794
+ if (typeof filter.requiresAttention === "boolean") {
668
795
  const requiresAttention = agent.requiresAttention ?? false;
669
796
  if (requiresAttention !== filter.requiresAttention) {
670
797
  return false;
671
798
  }
672
799
  }
673
- if (filter?.projectKeys && filter.projectKeys.length > 0) {
800
+ if (filter.projectKeys && filter.projectKeys.length > 0) {
674
801
  const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
675
802
  if (projectKeys.size > 0 && !projectKeys.has(project.projectKey)) {
676
803
  return false;
@@ -678,6 +805,26 @@ export class Session {
678
805
  }
679
806
  return true;
680
807
  }
808
+ matchesAgentFilter(options) {
809
+ const { agent, project, filter } = options;
810
+ if (filter?.labels) {
811
+ const matchesLabels = Object.entries(filter.labels).every(([key, value]) => agent.labels[key] === value);
812
+ if (!matchesLabels) {
813
+ return false;
814
+ }
815
+ }
816
+ const includeArchived = filter?.includeArchived ?? false;
817
+ if (!includeArchived && agent.archivedAt) {
818
+ return false;
819
+ }
820
+ if (filter && !this.agentThinkingOptionMatchesFilter(agent, filter)) {
821
+ return false;
822
+ }
823
+ if (filter && !this.matchesAgentStructuralFilter(agent, project, filter)) {
824
+ return false;
825
+ }
826
+ return true;
827
+ }
681
828
  getAgentUpdateTargetId(update) {
682
829
  return update.kind === "remove" ? update.agentId : update.agent.id;
683
830
  }
@@ -724,54 +871,26 @@ export class Session {
724
871
  const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(normalizedCwd, workspaces);
725
872
  return workspaces.find((workspace) => workspace.workspaceId === workspaceId) ?? null;
726
873
  }
874
+ async findExactWorkspaceByDirectory(cwd, options) {
875
+ const normalizedCwd = await this.resolveWorkspaceDirectory(cwd, options);
876
+ const workspaces = await this.workspaceRegistry.list();
877
+ return workspaces.find((workspace) => workspace.cwd === normalizedCwd) ?? null;
878
+ }
727
879
  async resolveWorkspaceDirectory(cwd, options) {
728
880
  const normalizedCwd = normalizePersistedWorkspaceId(cwd);
729
881
  if (options?.refreshGit === false) {
730
882
  const snapshot = this.workspaceGitService.peekSnapshot(normalizedCwd);
731
883
  return normalizePersistedWorkspaceId(snapshot?.git.repoRoot ?? normalizedCwd);
732
884
  }
733
- try {
734
- const snapshot = await this.workspaceGitService.getSnapshot(normalizedCwd);
735
- return normalizePersistedWorkspaceId(snapshot.git.repoRoot ?? normalizedCwd);
736
- }
737
- catch {
738
- return normalizedCwd;
739
- }
885
+ const checkout = await this.workspaceGitService.getCheckout(normalizedCwd);
886
+ return normalizePersistedWorkspaceId(checkout.worktreeRoot ?? normalizedCwd);
740
887
  }
741
888
  async buildProjectPlacementForWorkspace(workspace, projectRecord) {
742
889
  const project = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
743
890
  if (!project) {
744
891
  throw new Error(`Project not found for workspace ${workspace.workspaceId}`);
745
892
  }
746
- const checkout = project.kind !== "git"
747
- ? {
748
- cwd: workspace.cwd,
749
- isGit: false,
750
- currentBranch: null,
751
- remoteUrl: null,
752
- worktreeRoot: null,
753
- isPaseoOwnedWorktree: false,
754
- mainRepoRoot: null,
755
- }
756
- : workspace.kind === "worktree"
757
- ? {
758
- cwd: workspace.cwd,
759
- isGit: true,
760
- currentBranch: workspace.displayName,
761
- remoteUrl: null,
762
- worktreeRoot: workspace.cwd,
763
- isPaseoOwnedWorktree: true,
764
- mainRepoRoot: project.rootPath,
765
- }
766
- : {
767
- cwd: workspace.cwd,
768
- isGit: true,
769
- currentBranch: workspace.displayName,
770
- remoteUrl: null,
771
- worktreeRoot: workspace.cwd,
772
- isPaseoOwnedWorktree: false,
773
- mainRepoRoot: null,
774
- };
893
+ const checkout = buildWorkspaceCheckout(workspace, project);
775
894
  return {
776
895
  projectKey: project.projectId,
777
896
  projectName: project.displayName,
@@ -851,360 +970,7 @@ export class Session {
851
970
  try {
852
971
  this.sessionLogger.trace({ messageType: msg.type, payloadBytes: JSON.stringify(msg).length }, "inbound message");
853
972
  try {
854
- switch (msg.type) {
855
- case "voice_audio_chunk":
856
- await this.handleAudioChunk(msg);
857
- break;
858
- case "abort_request":
859
- await this.handleAbort();
860
- break;
861
- case "audio_played":
862
- this.handleAudioPlayed(msg.id);
863
- break;
864
- case "fetch_agents_request":
865
- await this.handleFetchAgents(msg);
866
- break;
867
- case "fetch_agent_history_request":
868
- await this.handleFetchAgentHistory(msg);
869
- break;
870
- case "fetch_workspaces_request":
871
- await this.handleFetchWorkspacesRequest(msg);
872
- break;
873
- case "fetch_agent_request":
874
- await this.handleFetchAgent(msg.agentId, msg.requestId);
875
- break;
876
- case "delete_agent_request":
877
- await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
878
- break;
879
- case "archive_agent_request":
880
- await this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
881
- break;
882
- case "close_items_request":
883
- await this.handleCloseItemsRequest(msg);
884
- break;
885
- case "update_agent_request":
886
- await this.handleUpdateAgentRequest(msg.agentId, msg.name, msg.labels, msg.requestId);
887
- break;
888
- case "set_voice_mode":
889
- await this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
890
- break;
891
- case "send_agent_message_request":
892
- await this.handleSendAgentMessageRequest(msg);
893
- break;
894
- case "wait_for_finish_request":
895
- await this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
896
- break;
897
- case "get_daemon_config_request":
898
- this.emit({
899
- type: "get_daemon_config_response",
900
- payload: {
901
- requestId: msg.requestId,
902
- config: this.daemonConfigStore.get(),
903
- },
904
- });
905
- break;
906
- case "set_daemon_config_request":
907
- this.emit({
908
- type: "set_daemon_config_response",
909
- payload: {
910
- requestId: msg.requestId,
911
- config: this.daemonConfigStore.patch(msg.config),
912
- },
913
- });
914
- break;
915
- case "dictation_stream_start":
916
- {
917
- const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
918
- if (unavailable) {
919
- this.emit({
920
- type: "dictation_stream_error",
921
- payload: {
922
- dictationId: msg.dictationId,
923
- error: unavailable.message,
924
- retryable: unavailable.retryable,
925
- reasonCode: unavailable.reasonCode,
926
- missingModelIds: unavailable.missingModelIds,
927
- },
928
- });
929
- break;
930
- }
931
- }
932
- await this.dictationStreamManager.handleStart(msg.dictationId, msg.format);
933
- break;
934
- case "dictation_stream_chunk":
935
- await this.dictationStreamManager.handleChunk({
936
- dictationId: msg.dictationId,
937
- seq: msg.seq,
938
- audioBase64: msg.audio,
939
- format: msg.format,
940
- });
941
- break;
942
- case "dictation_stream_finish":
943
- await this.dictationStreamManager.handleFinish(msg.dictationId, msg.finalSeq);
944
- break;
945
- case "dictation_stream_cancel":
946
- this.dictationStreamManager.handleCancel(msg.dictationId);
947
- break;
948
- case "create_agent_request":
949
- await this.handleCreateAgentRequest(msg);
950
- break;
951
- case "resume_agent_request":
952
- await this.handleResumeAgentRequest(msg);
953
- break;
954
- case "refresh_agent_request":
955
- await this.handleRefreshAgentRequest(msg);
956
- break;
957
- case "cancel_agent_request":
958
- await this.handleCancelAgentRequest(msg.agentId, msg.requestId);
959
- break;
960
- case "restart_server_request":
961
- await this.handleRestartServerRequest(msg.requestId, msg.reason);
962
- break;
963
- case "shutdown_server_request":
964
- await this.handleShutdownServerRequest(msg.requestId);
965
- break;
966
- case "fetch_agent_timeline_request":
967
- await this.handleFetchAgentTimelineRequest(msg);
968
- break;
969
- case "set_agent_mode_request":
970
- await this.handleSetAgentModeRequest(msg.agentId, msg.modeId, msg.requestId);
971
- break;
972
- case "set_agent_model_request":
973
- await this.handleSetAgentModelRequest(msg.agentId, msg.modelId, msg.requestId);
974
- break;
975
- case "set_agent_feature_request":
976
- await this.handleSetAgentFeatureRequest(msg.agentId, msg.featureId, msg.value, msg.requestId);
977
- break;
978
- case "set_agent_thinking_request":
979
- await this.handleSetAgentThinkingRequest(msg.agentId, msg.thinkingOptionId, msg.requestId);
980
- break;
981
- case "agent_permission_response":
982
- await this.handleAgentPermissionResponse(msg.agentId, msg.requestId, msg.response);
983
- break;
984
- case "checkout_status_request":
985
- await this.handleCheckoutStatusRequest(msg);
986
- break;
987
- case "validate_branch_request":
988
- await this.handleValidateBranchRequest(msg);
989
- break;
990
- case "branch_suggestions_request":
991
- await this.handleBranchSuggestionsRequest(msg);
992
- break;
993
- case "directory_suggestions_request":
994
- await this.handleDirectorySuggestionsRequest(msg);
995
- break;
996
- case "subscribe_checkout_diff_request":
997
- await this.handleSubscribeCheckoutDiffRequest(msg);
998
- break;
999
- case "unsubscribe_checkout_diff_request":
1000
- this.handleUnsubscribeCheckoutDiffRequest(msg);
1001
- break;
1002
- case "checkout_switch_branch_request":
1003
- await this.handleCheckoutSwitchBranchRequest(msg);
1004
- break;
1005
- case "stash_save_request":
1006
- await this.handleStashSaveRequest(msg);
1007
- break;
1008
- case "stash_pop_request":
1009
- await this.handleStashPopRequest(msg);
1010
- break;
1011
- case "stash_list_request":
1012
- await this.handleStashListRequest(msg);
1013
- break;
1014
- case "checkout_commit_request":
1015
- await this.handleCheckoutCommitRequest(msg);
1016
- break;
1017
- case "checkout_merge_request":
1018
- await this.handleCheckoutMergeRequest(msg);
1019
- break;
1020
- case "checkout_merge_from_base_request":
1021
- await this.handleCheckoutMergeFromBaseRequest(msg);
1022
- break;
1023
- case "checkout_pull_request":
1024
- await this.handleCheckoutPullRequest(msg);
1025
- break;
1026
- case "checkout_push_request":
1027
- await this.handleCheckoutPushRequest(msg);
1028
- break;
1029
- case "checkout_pr_create_request":
1030
- await this.handleCheckoutPrCreateRequest(msg);
1031
- break;
1032
- case "checkout_pr_status_request":
1033
- await this.handleCheckoutPrStatusRequest(msg);
1034
- break;
1035
- case "pull_request_timeline_request":
1036
- await this.handlePullRequestTimelineRequest(msg);
1037
- break;
1038
- case "github_search_request":
1039
- await this.handleGitHubSearchRequest(msg);
1040
- break;
1041
- case "paseo_worktree_list_request":
1042
- await this.handlePaseoWorktreeListRequest(msg);
1043
- break;
1044
- case "paseo_worktree_archive_request":
1045
- await this.handlePaseoWorktreeArchiveRequest(msg);
1046
- break;
1047
- case "create_paseo_worktree_request":
1048
- await this.handleCreatePaseoWorktreeRequest(msg);
1049
- break;
1050
- case "workspace_setup_status_request":
1051
- await this.handleWorkspaceSetupStatusRequest(msg);
1052
- break;
1053
- case "list_available_editors_request":
1054
- await this.handleListAvailableEditorsRequest(msg);
1055
- break;
1056
- case "open_in_editor_request":
1057
- await this.handleOpenInEditorRequest(msg);
1058
- break;
1059
- case "open_project_request":
1060
- await this.handleOpenProjectRequest(msg);
1061
- break;
1062
- case "archive_workspace_request":
1063
- await this.handleArchiveWorkspaceRequest(msg);
1064
- break;
1065
- case "file_explorer_request":
1066
- await this.handleFileExplorerRequest(msg);
1067
- break;
1068
- case "project_icon_request":
1069
- await this.handleProjectIconRequest(msg);
1070
- break;
1071
- case "file_download_token_request":
1072
- await this.handleFileDownloadTokenRequest(msg);
1073
- break;
1074
- case "list_provider_models_request":
1075
- await this.handleListProviderModelsRequest(msg);
1076
- break;
1077
- case "list_provider_modes_request":
1078
- await this.handleListProviderModesRequest(msg);
1079
- break;
1080
- case "list_provider_features_request":
1081
- await this.handleListProviderFeaturesRequest(msg);
1082
- break;
1083
- case "list_available_providers_request":
1084
- await this.handleListAvailableProvidersRequest(msg);
1085
- break;
1086
- case "get_providers_snapshot_request":
1087
- await this.handleGetProvidersSnapshotRequest(msg);
1088
- break;
1089
- case "refresh_providers_snapshot_request":
1090
- await this.handleRefreshProvidersSnapshotRequest(msg);
1091
- break;
1092
- case "provider_diagnostic_request":
1093
- await this.handleProviderDiagnosticRequest(msg);
1094
- break;
1095
- case "clear_agent_attention":
1096
- await this.handleClearAgentAttention(msg.agentId, msg.requestId);
1097
- break;
1098
- case "client_heartbeat":
1099
- this.handleClientHeartbeat(msg);
1100
- break;
1101
- case "ping": {
1102
- const now = Date.now();
1103
- this.emit({
1104
- type: "pong",
1105
- payload: {
1106
- requestId: msg.requestId,
1107
- clientSentAt: msg.clientSentAt,
1108
- serverReceivedAt: now,
1109
- serverSentAt: now,
1110
- },
1111
- });
1112
- break;
1113
- }
1114
- case "list_commands_request":
1115
- await this.handleListCommandsRequest(msg);
1116
- break;
1117
- case "register_push_token":
1118
- this.handleRegisterPushToken(msg.token);
1119
- break;
1120
- case "subscribe_terminals_request":
1121
- this.handleSubscribeTerminalsRequest(msg);
1122
- break;
1123
- case "unsubscribe_terminals_request":
1124
- this.handleUnsubscribeTerminalsRequest(msg);
1125
- break;
1126
- case "list_terminals_request":
1127
- await this.handleListTerminalsRequest(msg);
1128
- break;
1129
- case "create_terminal_request":
1130
- await this.handleCreateTerminalRequest(msg);
1131
- break;
1132
- case "start_workspace_script_request":
1133
- await this.handleStartWorkspaceScriptRequest(msg);
1134
- break;
1135
- case "subscribe_terminal_request":
1136
- await this.handleSubscribeTerminalRequest(msg);
1137
- break;
1138
- case "unsubscribe_terminal_request":
1139
- this.handleUnsubscribeTerminalRequest(msg);
1140
- break;
1141
- case "terminal_input":
1142
- this.handleTerminalInput(msg);
1143
- break;
1144
- case "kill_terminal_request":
1145
- await this.handleKillTerminalRequest(msg);
1146
- break;
1147
- case "capture_terminal_request":
1148
- await this.handleCaptureTerminalRequest(msg);
1149
- break;
1150
- case "chat/create":
1151
- await this.handleChatCreateRequest(msg);
1152
- break;
1153
- case "chat/list":
1154
- await this.handleChatListRequest(msg);
1155
- break;
1156
- case "chat/inspect":
1157
- await this.handleChatInspectRequest(msg);
1158
- break;
1159
- case "chat/delete":
1160
- await this.handleChatDeleteRequest(msg);
1161
- break;
1162
- case "chat/post":
1163
- await this.handleChatPostRequest(msg);
1164
- break;
1165
- case "chat/read":
1166
- await this.handleChatReadRequest(msg);
1167
- break;
1168
- case "chat/wait":
1169
- await this.handleChatWaitRequest(msg);
1170
- break;
1171
- case "schedule/create":
1172
- await this.handleScheduleCreateRequest(msg);
1173
- break;
1174
- case "schedule/list":
1175
- await this.handleScheduleListRequest(msg);
1176
- break;
1177
- case "schedule/inspect":
1178
- await this.handleScheduleInspectRequest(msg);
1179
- break;
1180
- case "schedule/logs":
1181
- await this.handleScheduleLogsRequest(msg);
1182
- break;
1183
- case "schedule/pause":
1184
- await this.handleSchedulePauseRequest(msg);
1185
- break;
1186
- case "schedule/resume":
1187
- await this.handleScheduleResumeRequest(msg);
1188
- break;
1189
- case "schedule/delete":
1190
- await this.handleScheduleDeleteRequest(msg);
1191
- break;
1192
- case "loop/run":
1193
- await this.handleLoopRunRequest(msg);
1194
- break;
1195
- case "loop/list":
1196
- await this.handleLoopListRequest(msg);
1197
- break;
1198
- case "loop/inspect":
1199
- await this.handleLoopInspectRequest(msg);
1200
- break;
1201
- case "loop/logs":
1202
- await this.handleLoopLogsRequest(msg);
1203
- break;
1204
- case "loop/stop":
1205
- await this.handleLoopStopRequest(msg);
1206
- break;
1207
- }
973
+ await this.dispatchInboundMessage(msg);
1208
974
  }
1209
975
  catch (error) {
1210
976
  const err = error instanceof Error ? error : new Error(String(error));
@@ -1241,6 +1007,418 @@ export class Session {
1241
1007
  this.inflightRequests--;
1242
1008
  }
1243
1009
  }
1010
+ async dispatchInboundMessage(msg) {
1011
+ const promise = this.dispatchVoiceAndControlMessage(msg) ??
1012
+ this.dispatchAgentLifecycleMessage(msg) ??
1013
+ this.dispatchAgentConfigMessage(msg) ??
1014
+ this.dispatchCheckoutMessage(msg) ??
1015
+ this.dispatchWorkspaceAndProjectMessage(msg) ??
1016
+ this.dispatchProviderMessage(msg) ??
1017
+ this.dispatchTerminalMessage(msg) ??
1018
+ this.dispatchChatScheduleLoopMessage(msg) ??
1019
+ this.dispatchMiscMessage(msg);
1020
+ if (promise)
1021
+ await promise;
1022
+ }
1023
+ dispatchVoiceAndControlMessage(msg) {
1024
+ switch (msg.type) {
1025
+ case "voice_audio_chunk":
1026
+ return this.handleAudioChunk(msg);
1027
+ case "abort_request":
1028
+ return this.handleAbort();
1029
+ case "audio_played":
1030
+ this.handleAudioPlayed(msg.id);
1031
+ return undefined;
1032
+ case "set_voice_mode":
1033
+ return this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
1034
+ case "dictation_stream_start":
1035
+ return this.handleDictationStreamStart(msg);
1036
+ case "dictation_stream_chunk":
1037
+ return this.dictationStreamManager.handleChunk({
1038
+ dictationId: msg.dictationId,
1039
+ seq: msg.seq,
1040
+ audioBase64: msg.audio,
1041
+ format: msg.format,
1042
+ });
1043
+ case "dictation_stream_finish":
1044
+ return this.dictationStreamManager.handleFinish(msg.dictationId, msg.finalSeq);
1045
+ case "dictation_stream_cancel":
1046
+ this.dictationStreamManager.handleCancel(msg.dictationId);
1047
+ return undefined;
1048
+ case "restart_server_request":
1049
+ return this.handleRestartServerRequest(msg.requestId, msg.reason);
1050
+ case "shutdown_server_request":
1051
+ return this.handleShutdownServerRequest(msg.requestId);
1052
+ case "client_heartbeat":
1053
+ this.handleClientHeartbeat(msg);
1054
+ return undefined;
1055
+ case "ping": {
1056
+ const now = Date.now();
1057
+ this.emit({
1058
+ type: "pong",
1059
+ payload: {
1060
+ requestId: msg.requestId,
1061
+ clientSentAt: msg.clientSentAt,
1062
+ serverReceivedAt: now,
1063
+ serverSentAt: now,
1064
+ },
1065
+ });
1066
+ return undefined;
1067
+ }
1068
+ default:
1069
+ return undefined;
1070
+ }
1071
+ }
1072
+ async handleDictationStreamStart(msg) {
1073
+ const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
1074
+ if (unavailable) {
1075
+ this.emit({
1076
+ type: "dictation_stream_error",
1077
+ payload: {
1078
+ dictationId: msg.dictationId,
1079
+ error: unavailable.message,
1080
+ retryable: unavailable.retryable,
1081
+ reasonCode: unavailable.reasonCode,
1082
+ missingModelIds: unavailable.missingModelIds,
1083
+ },
1084
+ });
1085
+ return;
1086
+ }
1087
+ await this.dictationStreamManager.handleStart(msg.dictationId, msg.format);
1088
+ }
1089
+ dispatchAgentLifecycleMessage(msg) {
1090
+ switch (msg.type) {
1091
+ case "fetch_agents_request":
1092
+ return this.handleFetchAgents(msg);
1093
+ case "fetch_agent_history_request":
1094
+ return this.handleFetchAgentHistory(msg);
1095
+ case "fetch_agent_request":
1096
+ return this.handleFetchAgent(msg.agentId, msg.requestId);
1097
+ case "delete_agent_request":
1098
+ return this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
1099
+ case "archive_agent_request":
1100
+ return this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
1101
+ case "close_items_request":
1102
+ return this.handleCloseItemsRequest(msg);
1103
+ case "update_agent_request":
1104
+ return this.handleUpdateAgentRequest(msg.agentId, msg.name, msg.labels, msg.requestId);
1105
+ case "send_agent_message_request":
1106
+ return this.handleSendAgentMessageRequest(msg);
1107
+ case "wait_for_finish_request":
1108
+ return this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
1109
+ case "create_agent_request":
1110
+ return this.handleCreateAgentRequest(msg);
1111
+ case "resume_agent_request":
1112
+ return this.handleResumeAgentRequest(msg);
1113
+ case "refresh_agent_request":
1114
+ return this.handleRefreshAgentRequest(msg);
1115
+ case "cancel_agent_request":
1116
+ return this.handleCancelAgentRequest(msg.agentId, msg.requestId);
1117
+ case "fetch_agent_timeline_request":
1118
+ return this.handleFetchAgentTimelineRequest(msg);
1119
+ case "agent_permission_response":
1120
+ return this.handleAgentPermissionResponse(msg.agentId, msg.requestId, msg.response);
1121
+ case "clear_agent_attention":
1122
+ return this.handleClearAgentAttention(msg.agentId, msg.requestId);
1123
+ default:
1124
+ return undefined;
1125
+ }
1126
+ }
1127
+ dispatchAgentConfigMessage(msg) {
1128
+ switch (msg.type) {
1129
+ case "set_agent_mode_request":
1130
+ return this.handleSetAgentModeRequest(msg.agentId, msg.modeId, msg.requestId);
1131
+ case "set_agent_model_request":
1132
+ return this.handleSetAgentModelRequest(msg.agentId, msg.modelId, msg.requestId);
1133
+ case "set_agent_feature_request":
1134
+ return this.handleSetAgentFeatureRequest(msg.agentId, msg.featureId, msg.value, msg.requestId);
1135
+ case "set_agent_thinking_request":
1136
+ return this.handleSetAgentThinkingRequest(msg.agentId, msg.thinkingOptionId, msg.requestId);
1137
+ case "get_daemon_config_request":
1138
+ this.emit({
1139
+ type: "get_daemon_config_response",
1140
+ payload: { requestId: msg.requestId, config: this.daemonConfigStore.get() },
1141
+ });
1142
+ return undefined;
1143
+ case "set_daemon_config_request":
1144
+ this.emit({
1145
+ type: "set_daemon_config_response",
1146
+ payload: {
1147
+ requestId: msg.requestId,
1148
+ config: this.daemonConfigStore.patch(msg.config),
1149
+ },
1150
+ });
1151
+ return undefined;
1152
+ case "read_project_config_request":
1153
+ return this.handleReadProjectConfigRequest(msg);
1154
+ case "write_project_config_request":
1155
+ return this.handleWriteProjectConfigRequest(msg);
1156
+ default:
1157
+ return undefined;
1158
+ }
1159
+ }
1160
+ async handleReadProjectConfigRequest(msg) {
1161
+ const repoRoot = await resolveKnownProjectRootForConfig({
1162
+ repoRoot: msg.repoRoot,
1163
+ projectRegistry: this.projectRegistry,
1164
+ });
1165
+ if (!repoRoot) {
1166
+ this.emitProjectConfigReadFailure(msg, { code: "project_not_found" });
1167
+ return;
1168
+ }
1169
+ const result = readPaseoConfigForEdit(repoRoot);
1170
+ if (!result.ok) {
1171
+ this.sessionLogger.warn({ repoRoot, requestId: msg.requestId, outcome: result.error.code }, "Failed to read project config");
1172
+ this.emitProjectConfigReadFailure(msg, result.error, repoRoot);
1173
+ return;
1174
+ }
1175
+ if (result.config === null) {
1176
+ this.sessionLogger.debug({ repoRoot, requestId: msg.requestId, outcome: "missing_project_config" }, "Project config missing");
1177
+ }
1178
+ this.emit({
1179
+ type: "read_project_config_response",
1180
+ payload: {
1181
+ requestId: msg.requestId,
1182
+ repoRoot,
1183
+ ok: true,
1184
+ config: result.config,
1185
+ revision: result.revision,
1186
+ },
1187
+ });
1188
+ }
1189
+ async handleWriteProjectConfigRequest(msg) {
1190
+ const repoRoot = await resolveKnownProjectRootForConfig({
1191
+ repoRoot: msg.repoRoot,
1192
+ projectRegistry: this.projectRegistry,
1193
+ });
1194
+ if (!repoRoot) {
1195
+ this.emitProjectConfigWriteFailure(msg, { code: "project_not_found" });
1196
+ return;
1197
+ }
1198
+ this.sessionLogger.debug({ repoRoot, requestId: msg.requestId, outcome: "write_attempt" }, "Writing project config");
1199
+ const result = writePaseoConfigForEdit({
1200
+ repoRoot,
1201
+ config: msg.config,
1202
+ expectedRevision: msg.expectedRevision,
1203
+ });
1204
+ if (!result.ok) {
1205
+ this.sessionLogger.debug({ repoRoot, requestId: msg.requestId, outcome: result.error.code }, "Project config write did not complete");
1206
+ this.emitProjectConfigWriteFailure(msg, result.error, repoRoot);
1207
+ return;
1208
+ }
1209
+ this.sessionLogger.debug({ repoRoot, requestId: msg.requestId, outcome: "written" }, "Project config written");
1210
+ this.emit({
1211
+ type: "write_project_config_response",
1212
+ payload: {
1213
+ requestId: msg.requestId,
1214
+ repoRoot,
1215
+ ok: true,
1216
+ config: result.config,
1217
+ revision: result.revision,
1218
+ },
1219
+ });
1220
+ }
1221
+ emitProjectConfigReadFailure(msg, error, repoRoot = msg.repoRoot) {
1222
+ this.emit({
1223
+ type: "read_project_config_response",
1224
+ payload: {
1225
+ requestId: msg.requestId,
1226
+ repoRoot,
1227
+ ok: false,
1228
+ error,
1229
+ },
1230
+ });
1231
+ }
1232
+ emitProjectConfigWriteFailure(msg, error, repoRoot = msg.repoRoot) {
1233
+ this.emit({
1234
+ type: "write_project_config_response",
1235
+ payload: {
1236
+ requestId: msg.requestId,
1237
+ repoRoot,
1238
+ ok: false,
1239
+ error,
1240
+ },
1241
+ });
1242
+ }
1243
+ dispatchCheckoutMessage(msg) {
1244
+ switch (msg.type) {
1245
+ case "checkout_status_request":
1246
+ return this.handleCheckoutStatusRequest(msg);
1247
+ case "validate_branch_request":
1248
+ return this.handleValidateBranchRequest(msg);
1249
+ case "branch_suggestions_request":
1250
+ return this.handleBranchSuggestionsRequest(msg);
1251
+ case "directory_suggestions_request":
1252
+ return this.handleDirectorySuggestionsRequest(msg);
1253
+ case "subscribe_checkout_diff_request":
1254
+ return this.handleSubscribeCheckoutDiffRequest(msg);
1255
+ case "unsubscribe_checkout_diff_request":
1256
+ this.handleUnsubscribeCheckoutDiffRequest(msg);
1257
+ return undefined;
1258
+ case "checkout_switch_branch_request":
1259
+ return this.handleCheckoutSwitchBranchRequest(msg);
1260
+ case "stash_save_request":
1261
+ return this.handleStashSaveRequest(msg);
1262
+ case "stash_pop_request":
1263
+ return this.handleStashPopRequest(msg);
1264
+ case "stash_list_request":
1265
+ return this.handleStashListRequest(msg);
1266
+ case "checkout_commit_request":
1267
+ return this.handleCheckoutCommitRequest(msg);
1268
+ case "checkout_merge_request":
1269
+ return this.handleCheckoutMergeRequest(msg);
1270
+ case "checkout_merge_from_base_request":
1271
+ return this.handleCheckoutMergeFromBaseRequest(msg);
1272
+ case "checkout_pull_request":
1273
+ return this.handleCheckoutPullRequest(msg);
1274
+ case "checkout_push_request":
1275
+ return this.handleCheckoutPushRequest(msg);
1276
+ case "checkout_pr_create_request":
1277
+ return this.handleCheckoutPrCreateRequest(msg);
1278
+ case "checkout_pr_status_request":
1279
+ return this.handleCheckoutPrStatusRequest(msg);
1280
+ case "pull_request_timeline_request":
1281
+ return this.handlePullRequestTimelineRequest(msg);
1282
+ case "github_search_request":
1283
+ return this.handleGitHubSearchRequest(msg);
1284
+ default:
1285
+ return undefined;
1286
+ }
1287
+ }
1288
+ dispatchWorkspaceAndProjectMessage(msg) {
1289
+ switch (msg.type) {
1290
+ case "fetch_workspaces_request":
1291
+ return this.handleFetchWorkspacesRequest(msg);
1292
+ case "paseo_worktree_list_request":
1293
+ return this.handlePaseoWorktreeListRequest(msg);
1294
+ case "paseo_worktree_archive_request":
1295
+ return this.handlePaseoWorktreeArchiveRequest(msg);
1296
+ case "create_paseo_worktree_request":
1297
+ return this.handleCreatePaseoWorktreeRequest(msg);
1298
+ case "workspace_setup_status_request":
1299
+ return this.handleWorkspaceSetupStatusRequest(msg);
1300
+ case "list_available_editors_request":
1301
+ return this.handleListAvailableEditorsRequest(msg);
1302
+ case "open_in_editor_request":
1303
+ return this.handleOpenInEditorRequest(msg);
1304
+ case "open_project_request":
1305
+ return this.handleOpenProjectRequest(msg);
1306
+ case "archive_workspace_request":
1307
+ return this.handleArchiveWorkspaceRequest(msg);
1308
+ case "file_explorer_request":
1309
+ return this.handleFileExplorerRequest(msg);
1310
+ case "project_icon_request":
1311
+ return this.handleProjectIconRequest(msg);
1312
+ case "file_download_token_request":
1313
+ return this.handleFileDownloadTokenRequest(msg);
1314
+ default:
1315
+ return undefined;
1316
+ }
1317
+ }
1318
+ dispatchProviderMessage(msg) {
1319
+ switch (msg.type) {
1320
+ case "list_provider_models_request":
1321
+ return this.handleListProviderModelsRequest(msg);
1322
+ case "list_provider_modes_request":
1323
+ return this.handleListProviderModesRequest(msg);
1324
+ case "list_provider_features_request":
1325
+ return this.handleListProviderFeaturesRequest(msg);
1326
+ case "list_available_providers_request":
1327
+ return this.handleListAvailableProvidersRequest(msg);
1328
+ case "get_providers_snapshot_request":
1329
+ return this.handleGetProvidersSnapshotRequest(msg);
1330
+ case "refresh_providers_snapshot_request":
1331
+ return this.handleRefreshProvidersSnapshotRequest(msg);
1332
+ case "provider_diagnostic_request":
1333
+ return this.handleProviderDiagnosticRequest(msg);
1334
+ default:
1335
+ return undefined;
1336
+ }
1337
+ }
1338
+ dispatchTerminalMessage(msg) {
1339
+ switch (msg.type) {
1340
+ case "subscribe_terminals_request":
1341
+ this.handleSubscribeTerminalsRequest(msg);
1342
+ return undefined;
1343
+ case "unsubscribe_terminals_request":
1344
+ this.handleUnsubscribeTerminalsRequest(msg);
1345
+ return undefined;
1346
+ case "list_terminals_request":
1347
+ return this.handleListTerminalsRequest(msg);
1348
+ case "create_terminal_request":
1349
+ return this.handleCreateTerminalRequest(msg);
1350
+ case "start_workspace_script_request":
1351
+ return this.handleStartWorkspaceScriptRequest(msg);
1352
+ case "subscribe_terminal_request":
1353
+ return this.handleSubscribeTerminalRequest(msg);
1354
+ case "unsubscribe_terminal_request":
1355
+ this.handleUnsubscribeTerminalRequest(msg);
1356
+ return undefined;
1357
+ case "terminal_input":
1358
+ this.handleTerminalInput(msg);
1359
+ return undefined;
1360
+ case "kill_terminal_request":
1361
+ return this.handleKillTerminalRequest(msg);
1362
+ case "capture_terminal_request":
1363
+ return this.handleCaptureTerminalRequest(msg);
1364
+ default:
1365
+ return undefined;
1366
+ }
1367
+ }
1368
+ dispatchChatScheduleLoopMessage(msg) {
1369
+ switch (msg.type) {
1370
+ case "chat/create":
1371
+ return this.handleChatCreateRequest(msg);
1372
+ case "chat/list":
1373
+ return this.handleChatListRequest(msg);
1374
+ case "chat/inspect":
1375
+ return this.handleChatInspectRequest(msg);
1376
+ case "chat/delete":
1377
+ return this.handleChatDeleteRequest(msg);
1378
+ case "chat/post":
1379
+ return this.handleChatPostRequest(msg);
1380
+ case "chat/read":
1381
+ return this.handleChatReadRequest(msg);
1382
+ case "chat/wait":
1383
+ return this.handleChatWaitRequest(msg);
1384
+ case "schedule/create":
1385
+ return this.handleScheduleCreateRequest(msg);
1386
+ case "schedule/list":
1387
+ return this.handleScheduleListRequest(msg);
1388
+ case "schedule/inspect":
1389
+ return this.handleScheduleInspectRequest(msg);
1390
+ case "schedule/logs":
1391
+ return this.handleScheduleLogsRequest(msg);
1392
+ case "schedule/pause":
1393
+ return this.handleSchedulePauseRequest(msg);
1394
+ case "schedule/resume":
1395
+ return this.handleScheduleResumeRequest(msg);
1396
+ case "schedule/delete":
1397
+ return this.handleScheduleDeleteRequest(msg);
1398
+ case "loop/run":
1399
+ return this.handleLoopRunRequest(msg);
1400
+ case "loop/list":
1401
+ return this.handleLoopListRequest(msg);
1402
+ case "loop/inspect":
1403
+ return this.handleLoopInspectRequest(msg);
1404
+ case "loop/logs":
1405
+ return this.handleLoopLogsRequest(msg);
1406
+ case "loop/stop":
1407
+ return this.handleLoopStopRequest(msg);
1408
+ default:
1409
+ return undefined;
1410
+ }
1411
+ }
1412
+ async dispatchMiscMessage(msg) {
1413
+ switch (msg.type) {
1414
+ case "list_commands_request":
1415
+ await this.handleListCommandsRequest(msg);
1416
+ return;
1417
+ case "register_push_token":
1418
+ this.handleRegisterPushToken(msg.token);
1419
+ return;
1420
+ }
1421
+ }
1244
1422
  resetPeakInflight() {
1245
1423
  this.peakInflightRequests = this.inflightRequests;
1246
1424
  }
@@ -1441,13 +1619,15 @@ export class Session {
1441
1619
  return { agentId, archivedAt: archivedRecord.archivedAt };
1442
1620
  }
1443
1621
  async handleCloseItemsRequest(msg) {
1622
+ const archiveResults = await Promise.allSettled(msg.agentIds.map((agentId) => this.archiveAgentForClose(agentId)));
1444
1623
  const agents = [];
1445
- for (const agentId of msg.agentIds) {
1446
- try {
1447
- agents.push(await this.archiveAgentForClose(agentId));
1624
+ for (let i = 0; i < archiveResults.length; i += 1) {
1625
+ const result = archiveResults[i];
1626
+ if (result.status === "fulfilled") {
1627
+ agents.push(result.value);
1448
1628
  }
1449
- catch (error) {
1450
- this.sessionLogger.warn({ err: error, agentId, requestId: msg.requestId }, "Failed to archive agent during close_items batch");
1629
+ else {
1630
+ this.sessionLogger.warn({ err: result.reason, agentId: msg.agentIds[i], requestId: msg.requestId }, "Failed to archive agent during close_items batch");
1451
1631
  }
1452
1632
  }
1453
1633
  const terminals = [];
@@ -1529,7 +1709,9 @@ export class Session {
1529
1709
  requestId,
1530
1710
  agentId,
1531
1711
  accepted: false,
1532
- error: error?.message ? String(error.message) : "Failed to update agent",
1712
+ error: error?.message
1713
+ ? String(error.message)
1714
+ : "Failed to update agent",
1533
1715
  },
1534
1716
  });
1535
1717
  }
@@ -1899,24 +2081,15 @@ export class Session {
1899
2081
  initialPrompt: trimmedPrompt,
1900
2082
  });
1901
2083
  await this.forwardAgentUpdate(snapshot);
1902
- if (trimmedPrompt || (images?.length ?? 0) > 0 || (attachments?.length ?? 0) > 0) {
1903
- scheduleAgentMetadataGeneration({
1904
- agentManager: this.agentManager,
1905
- agentId: snapshot.id,
1906
- cwd: snapshot.cwd,
1907
- initialPrompt: trimmedPrompt,
1908
- explicitTitle,
1909
- paseoHome: this.paseoHome,
1910
- logger: this.sessionLogger,
1911
- deps: {
1912
- workspaceGitService: this.workspaceGitService,
1913
- },
1914
- });
1915
- const started = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt || "", resolveClientMessageId(clientMessageId), images, attachments, outputSchema ? { outputSchema } : undefined);
1916
- if (!started.ok) {
1917
- throw new Error(started.error);
1918
- }
1919
- }
2084
+ await this.sendInitialCreateAgentPrompt({
2085
+ snapshot,
2086
+ trimmedPrompt,
2087
+ images,
2088
+ attachments,
2089
+ clientMessageId,
2090
+ outputSchema,
2091
+ explicitTitle,
2092
+ });
1920
2093
  if (requestId) {
1921
2094
  const agentPayload = await this.buildAgentPayload(snapshot);
1922
2095
  this.emit({
@@ -1975,6 +2148,31 @@ export class Session {
1975
2148
  });
1976
2149
  }
1977
2150
  }
2151
+ async sendInitialCreateAgentPrompt(params) {
2152
+ const { snapshot, trimmedPrompt, images, attachments, clientMessageId, outputSchema } = params;
2153
+ const hasPrompt = Boolean(trimmedPrompt);
2154
+ const hasImages = (images?.length ?? 0) > 0;
2155
+ const hasAttachments = (attachments?.length ?? 0) > 0;
2156
+ if (!hasPrompt && !hasImages && !hasAttachments) {
2157
+ return;
2158
+ }
2159
+ scheduleAgentMetadataGeneration({
2160
+ agentManager: this.agentManager,
2161
+ agentId: snapshot.id,
2162
+ cwd: snapshot.cwd,
2163
+ initialPrompt: trimmedPrompt,
2164
+ explicitTitle: params.explicitTitle,
2165
+ paseoHome: this.paseoHome,
2166
+ logger: this.sessionLogger,
2167
+ deps: {
2168
+ workspaceGitService: this.workspaceGitService,
2169
+ },
2170
+ });
2171
+ const started = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt || "", resolveClientMessageId(clientMessageId), images, attachments, outputSchema ? { outputSchema } : undefined);
2172
+ if (!started.ok) {
2173
+ throw new Error(started.error);
2174
+ }
2175
+ }
1978
2176
  async handleResumeAgentRequest(msg) {
1979
2177
  const { handle, overrides, requestId } = msg;
1980
2178
  if (!handle) {
@@ -2041,7 +2239,11 @@ export class Session {
2041
2239
  if (!record) {
2042
2240
  throw new Error(`Agent not found: ${agentId}`);
2043
2241
  }
2044
- const handle = toAgentPersistenceHandle(this.sessionLogger, this.providerRegistry, record.persistence);
2242
+ const providerRegistry = this.getProviderRegistry();
2243
+ if (!isStoredAgentProviderAvailable(record, Object.keys(providerRegistry))) {
2244
+ throw new Error(`Agent ${agentId} references unavailable provider '${record.provider}'`);
2245
+ }
2246
+ const handle = toAgentPersistenceHandle(providerRegistry, record.persistence);
2045
2247
  if (!handle) {
2046
2248
  throw new Error(`Agent ${agentId} cannot be refreshed because it lacks persistence`);
2047
2249
  }
@@ -2107,13 +2309,32 @@ export class Session {
2107
2309
  github: this.github,
2108
2310
  }, config, gitOptions, legacyWorktreeName, attachments);
2109
2311
  }
2312
+ emitProviderDisabledResponse(kind, provider, requestId, fetchedAt) {
2313
+ const payload = {
2314
+ provider,
2315
+ error: `Provider ${provider} is disabled`,
2316
+ fetchedAt,
2317
+ requestId,
2318
+ };
2319
+ if (kind === "models") {
2320
+ this.emit({ type: "list_provider_models_response", payload });
2321
+ }
2322
+ else {
2323
+ this.emit({ type: "list_provider_modes_response", payload });
2324
+ }
2325
+ }
2110
2326
  async handleListProviderModelsRequest(msg) {
2111
2327
  const cwd = resolveSnapshotCwd(msg.cwd ? expandTilde(msg.cwd) : undefined);
2112
2328
  const fetchedAt = new Date().toISOString();
2113
2329
  const manager = this.providerSnapshotManager;
2114
2330
  if (!manager) {
2115
2331
  try {
2116
- const models = await this.providerRegistry[msg.provider].fetchModels({
2332
+ const definition = this.getProviderRegistry()[msg.provider];
2333
+ if (!definition.enabled) {
2334
+ this.emitProviderDisabledResponse("models", msg.provider, msg.requestId, fetchedAt);
2335
+ return;
2336
+ }
2337
+ const models = await definition.fetchModels({
2117
2338
  cwd,
2118
2339
  force: false,
2119
2340
  });
@@ -2155,6 +2376,10 @@ export class Session {
2155
2376
  });
2156
2377
  return;
2157
2378
  }
2379
+ if (!entry.enabled) {
2380
+ this.emitProviderDisabledResponse("models", msg.provider, msg.requestId, fetchedAt);
2381
+ return;
2382
+ }
2158
2383
  if (entry.status === "ready") {
2159
2384
  this.emit({
2160
2385
  type: "list_provider_models_response",
@@ -2199,6 +2424,10 @@ export class Session {
2199
2424
  });
2200
2425
  return;
2201
2426
  }
2427
+ if (!entry.enabled) {
2428
+ this.emitProviderDisabledResponse("modes", msg.provider, msg.requestId, fetchedAt);
2429
+ return;
2430
+ }
2202
2431
  if (entry.status === "ready") {
2203
2432
  this.emit({
2204
2433
  type: "list_provider_modes_response",
@@ -2227,7 +2456,12 @@ export class Session {
2227
2456
  return;
2228
2457
  }
2229
2458
  try {
2230
- const modes = await this.providerRegistry[msg.provider].fetchModes({
2459
+ const definition = this.getProviderRegistry()[msg.provider];
2460
+ if (!definition.enabled) {
2461
+ this.emitProviderDisabledResponse("modes", msg.provider, msg.requestId, fetchedAt);
2462
+ return;
2463
+ }
2464
+ const modes = await definition.fetchModes({
2231
2465
  cwd,
2232
2466
  force: false,
2233
2467
  });
@@ -2262,6 +2496,9 @@ export class Session {
2262
2496
  }
2263
2497
  const findEntry = () => manager.getSnapshot(cwd).find((candidate) => candidate.provider === provider);
2264
2498
  let entry = findEntry();
2499
+ if (entry && !entry.enabled) {
2500
+ return entry;
2501
+ }
2265
2502
  if (!entry || entry.status === "loading") {
2266
2503
  // Awaits the in-flight warmup (deduped per-cwd) so old clients still get
2267
2504
  // a resolved answer rather than a loading placeholder.
@@ -2374,7 +2611,7 @@ export class Session {
2374
2611
  }
2375
2612
  async handleProviderDiagnosticRequest(msg) {
2376
2613
  try {
2377
- const client = this.providerRegistry[msg.provider].createClient(this.sessionLogger);
2614
+ const client = this.getProviderRegistry()[msg.provider].createClient(this.sessionLogger);
2378
2615
  const diagnostic = client.getDiagnostic
2379
2616
  ? (await client.getDiagnostic()).diagnostic
2380
2617
  : "No diagnostic available for this provider.";
@@ -2431,7 +2668,7 @@ export class Session {
2431
2668
  ? [
2432
2669
  "Files changed:",
2433
2670
  ...diff.structured.map((file) => {
2434
- const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M";
2671
+ const changeType = diffChangeTypeFor(file);
2435
2672
  const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
2436
2673
  return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
2437
2674
  }),
@@ -2487,7 +2724,7 @@ export class Session {
2487
2724
  ? [
2488
2725
  "Files changed:",
2489
2726
  ...diff.structured.map((file) => {
2490
- const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M";
2727
+ const changeType = diffChangeTypeFor(file);
2491
2728
  const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
2492
2729
  return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
2493
2730
  }),
@@ -2543,7 +2780,9 @@ export class Session {
2543
2780
  return snapshot.git.isDirty === true;
2544
2781
  }
2545
2782
  catch (error) {
2546
- throw new Error(`Unable to inspect git status for ${cwd}: ${error.message}`);
2783
+ throw new Error(`Unable to inspect git status for ${cwd}: ${error.message}`, {
2784
+ cause: error,
2785
+ });
2547
2786
  }
2548
2787
  }
2549
2788
  async checkoutExistingBranch(cwd, branch) {
@@ -2623,7 +2862,9 @@ export class Session {
2623
2862
  requestId,
2624
2863
  agentId,
2625
2864
  accepted: false,
2626
- error: error?.message ? String(error.message) : "Failed to set agent mode",
2865
+ error: error?.message
2866
+ ? String(error.message)
2867
+ : "Failed to set agent mode",
2627
2868
  },
2628
2869
  });
2629
2870
  }
@@ -2655,7 +2896,9 @@ export class Session {
2655
2896
  requestId,
2656
2897
  agentId,
2657
2898
  accepted: false,
2658
- error: error?.message ? String(error.message) : "Failed to set agent model",
2899
+ error: error?.message
2900
+ ? String(error.message)
2901
+ : "Failed to set agent model",
2659
2902
  },
2660
2903
  });
2661
2904
  }
@@ -2687,7 +2930,9 @@ export class Session {
2687
2930
  requestId,
2688
2931
  agentId,
2689
2932
  accepted: false,
2690
- error: error?.message ? String(error.message) : "Failed to set agent feature",
2933
+ error: error?.message
2934
+ ? String(error.message)
2935
+ : "Failed to set agent feature",
2691
2936
  },
2692
2937
  });
2693
2938
  }
@@ -2719,7 +2964,9 @@ export class Session {
2719
2964
  requestId,
2720
2965
  agentId,
2721
2966
  accepted: false,
2722
- error: error?.message ? String(error.message) : "Failed to set agent thinking option",
2967
+ error: error?.message
2968
+ ? String(error.message)
2969
+ : "Failed to set agent thinking option",
2723
2970
  },
2724
2971
  });
2725
2972
  }
@@ -3105,9 +3352,9 @@ export class Session {
3105
3352
  this.workspaceGitSubscriptions.get(normalizedCwd)?.();
3106
3353
  this.workspaceGitSubscriptions.delete(normalizedCwd);
3107
3354
  }
3108
- workspaceGitDescriptorFingerprint(workspace) {
3355
+ workspaceGitDescriptorStateKey(workspace) {
3109
3356
  if (!workspace) {
3110
- return WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT;
3357
+ return WORKSPACE_GIT_WATCH_REMOVED_STATE_KEY;
3111
3358
  }
3112
3359
  return JSON.stringify([
3113
3360
  workspace.name,
@@ -3119,34 +3366,30 @@ export class Session {
3119
3366
  if (!target) {
3120
3367
  return false;
3121
3368
  }
3122
- const nextFingerprint = this.workspaceGitDescriptorFingerprint(workspace);
3123
- if (target.latestFingerprint === nextFingerprint) {
3369
+ const nextStateKey = this.workspaceGitDescriptorStateKey(workspace);
3370
+ if (target.latestDescriptorStateKey === nextStateKey) {
3124
3371
  return true;
3125
3372
  }
3126
- target.latestFingerprint = nextFingerprint;
3373
+ target.latestDescriptorStateKey = nextStateKey;
3127
3374
  return false;
3128
3375
  }
3129
- rememberWorkspaceGitWatchFingerprint(workspaceId, workspace) {
3376
+ rememberWorkspaceGitDescriptorState(workspaceId, workspace) {
3130
3377
  const target = this.workspaceGitWatchTargets.get(workspaceId);
3131
3378
  if (!target) {
3132
3379
  return;
3133
3380
  }
3134
- target.latestFingerprint = this.workspaceGitDescriptorFingerprint(workspace);
3381
+ target.latestDescriptorStateKey = this.workspaceGitDescriptorStateKey(workspace);
3135
3382
  target.lastBranchName = workspace?.name ?? null;
3136
3383
  }
3137
- async primeWorkspaceGitWatchFingerprints(workspaces) {
3384
+ syncWorkspaceGitObservers(workspaces) {
3138
3385
  for (const workspace of workspaces) {
3139
- const persistedWorkspace = await this.workspaceRegistry.get(workspace.id);
3140
- if (!persistedWorkspace) {
3141
- continue;
3142
- }
3143
- await this.syncWorkspaceGitWatchTarget(persistedWorkspace.cwd, {
3386
+ this.syncWorkspaceGitObserver(workspace.workspaceDirectory, {
3144
3387
  isGit: workspace.projectKind === "git",
3145
3388
  });
3146
- this.rememberWorkspaceGitWatchFingerprint(persistedWorkspace.cwd, workspace);
3389
+ this.rememberWorkspaceGitDescriptorState(workspace.workspaceDirectory, workspace);
3147
3390
  }
3148
3391
  }
3149
- async syncWorkspaceGitWatchTarget(cwd, options) {
3392
+ syncWorkspaceGitObserver(cwd, options) {
3150
3393
  const normalizedCwd = normalizePersistedWorkspaceId(cwd);
3151
3394
  if (!options.isGit) {
3152
3395
  this.removeWorkspaceGitSubscription(normalizedCwd);
@@ -3155,7 +3398,7 @@ export class Session {
3155
3398
  if (this.workspaceGitSubscriptions.has(normalizedCwd)) {
3156
3399
  return;
3157
3400
  }
3158
- const subscription = await this.workspaceGitService.subscribe({ cwd: normalizedCwd }, (snapshot) => {
3401
+ const subscription = this.workspaceGitService.registerWorkspace({ cwd: normalizedCwd }, (snapshot) => {
3159
3402
  void this.emitWorkspaceUpdateForCwd(normalizedCwd);
3160
3403
  this.emitCheckoutStatusUpdate(normalizedCwd, snapshot);
3161
3404
  });
@@ -3236,6 +3479,7 @@ export class Session {
3236
3479
  cwd,
3237
3480
  isGit: true,
3238
3481
  repoRoot: snapshot.git.repoRoot,
3482
+ mainRepoRoot: snapshot.git.mainRepoRoot,
3239
3483
  currentBranch: snapshot.git.currentBranch ?? null,
3240
3484
  isDirty: snapshot.git.isDirty,
3241
3485
  baseRef: snapshot.git.baseRef ?? null,
@@ -3326,7 +3570,9 @@ export class Session {
3326
3570
  const message = branchLabel
3327
3571
  ? `${Session.PASEO_STASH_PREFIX} ${branchLabel}`
3328
3572
  : `${Session.PASEO_STASH_PREFIX} unnamed`;
3329
- await execCommand("git", ["stash", "push", "--include-untracked", "-m", message], { cwd });
3573
+ await execCommand("git", ["stash", "push", "--include-untracked", "-m", message], {
3574
+ cwd,
3575
+ });
3330
3576
  await this.notifyGitMutation(cwd, "stash-push");
3331
3577
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
3332
3578
  this.emit({
@@ -3344,7 +3590,9 @@ export class Session {
3344
3590
  async handleStashPopRequest(msg) {
3345
3591
  const { cwd, stashIndex, requestId } = msg;
3346
3592
  try {
3347
- await execCommand("git", ["stash", "pop", `stash@{${stashIndex}}`], { cwd });
3593
+ await execCommand("git", ["stash", "pop", `stash@{${stashIndex}}`], {
3594
+ cwd,
3595
+ });
3348
3596
  await this.notifyGitMutation(cwd, "stash-pop");
3349
3597
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
3350
3598
  this.emit({
@@ -3572,7 +3820,7 @@ export class Session {
3572
3820
  title,
3573
3821
  body,
3574
3822
  base: msg.baseRef,
3575
- }, this.github, this.workspaceGitService);
3823
+ }, this.github);
3576
3824
  await this.notifyGitMutation(cwd, "create-pr", { invalidateGithub: true });
3577
3825
  this.emit({
3578
3826
  type: "checkout_pr_create_response",
@@ -3916,9 +4164,12 @@ export class Session {
3916
4164
  // (excluding internal agents which are for ephemeral system tasks)
3917
4165
  const registryRecords = await this.agentStorage.list();
3918
4166
  const liveIds = new Set(agentSnapshots.map((a) => a.id));
4167
+ const registeredProviderIds = this.getRegisteredProviderIds();
3919
4168
  const persistedAgents = registryRecords
3920
4169
  .filter((record) => !liveIds.has(record.id) && !record.internal)
3921
- .map((record) => this.buildStoredAgentPayload(record));
4170
+ .filter((record) => filter?.includeUnavailablePersisted === true ||
4171
+ isStoredAgentProviderAvailable(record, registeredProviderIds))
4172
+ .map((record) => this.buildStoredAgentPayload(record, registeredProviderIds));
3922
4173
  let agents = [...liveAgents, ...persistedAgents];
3923
4174
  agents = agents.filter((agent) => this.isProviderVisibleToClient(agent.provider));
3924
4175
  // Filter by labels if filter provided
@@ -4099,25 +4350,7 @@ export class Session {
4099
4350
  if (!payload.values || typeof payload.values !== "object") {
4100
4351
  throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4101
4352
  }
4102
- const cursorSort = [];
4103
- for (const item of payload.sort) {
4104
- if (!item ||
4105
- typeof item !== "object" ||
4106
- typeof item.key !== "string" ||
4107
- typeof item.direction !== "string") {
4108
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4109
- }
4110
- const key = item.key;
4111
- const direction = item.direction;
4112
- if ((key !== "status_priority" &&
4113
- key !== "created_at" &&
4114
- key !== "updated_at" &&
4115
- key !== "title") ||
4116
- (direction !== "asc" && direction !== "desc")) {
4117
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4118
- }
4119
- cursorSort.push({ key, direction });
4120
- }
4353
+ const cursorSort = parseFetchAgentsCursorSort(payload.sort);
4121
4354
  if (cursorSort.length !== sort.length ||
4122
4355
  cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
4123
4356
  throw new SessionRequestError("invalid_cursor", "fetch_agents cursor does not match current sort");
@@ -4149,18 +4382,49 @@ export class Session {
4149
4382
  .filter((project) => !project.archivedAt)
4150
4383
  .map((project) => [project.projectId, project]));
4151
4384
  const placementsByCwd = new Map();
4152
- for (const workspace of persistedWorkspaces) {
4153
- if (workspace.archivedAt) {
4154
- continue;
4155
- }
4385
+ const pairs = persistedWorkspaces.flatMap((workspace) => {
4386
+ if (workspace.archivedAt)
4387
+ return [];
4156
4388
  const project = activeProjects.get(workspace.projectId);
4157
- if (!project) {
4158
- continue;
4159
- }
4160
- placementsByCwd.set(normalizePersistedWorkspaceId(workspace.cwd), await this.buildProjectPlacementForWorkspace(workspace, project));
4389
+ if (!project)
4390
+ return [];
4391
+ return [{ workspace, project }];
4392
+ });
4393
+ const placements = await Promise.all(pairs.map(({ workspace, project }) => this.buildProjectPlacementForWorkspace(workspace, project)));
4394
+ for (let i = 0; i < pairs.length; i += 1) {
4395
+ placementsByCwd.set(normalizePersistedWorkspaceId(pairs[i].workspace.cwd), placements[i]);
4161
4396
  }
4162
4397
  return placementsByCwd;
4163
4398
  }
4399
+ async collectFetchAgentsEntries(params) {
4400
+ const { candidates, limit, getPlacement, filter } = params;
4401
+ const matchedEntries = [];
4402
+ const batchSize = 25;
4403
+ for (let start = 0; start < candidates.length && matchedEntries.length <= limit; start += batchSize) {
4404
+ const batch = candidates.slice(start, start + batchSize);
4405
+ const batchEntries = await Promise.all(batch.map(async (agent) => {
4406
+ const project = await getPlacement(agent.cwd);
4407
+ return project ? { agent, project } : null;
4408
+ }));
4409
+ for (const entry of batchEntries) {
4410
+ if (!entry) {
4411
+ continue;
4412
+ }
4413
+ if (!this.matchesAgentFilter({
4414
+ agent: entry.agent,
4415
+ project: entry.project,
4416
+ filter,
4417
+ })) {
4418
+ continue;
4419
+ }
4420
+ matchedEntries.push(entry);
4421
+ if (matchedEntries.length > limit) {
4422
+ break;
4423
+ }
4424
+ }
4425
+ }
4426
+ return matchedEntries;
4427
+ }
4164
4428
  async listFetchAgentsEntries(request) {
4165
4429
  const filter = request.type === "fetch_agent_history_request" &&
4166
4430
  request.filter?.includeArchived === undefined
@@ -4170,6 +4434,7 @@ export class Session {
4170
4434
  const sort = this.normalizeFetchAgentsSort(request.sort);
4171
4435
  let agents = await this.listAgentPayloads({
4172
4436
  labels: filter?.labels,
4437
+ includeUnavailablePersisted: request.type === "fetch_agent_history_request",
4173
4438
  });
4174
4439
  const activePlacementsByCwd = scope === "active" ? await this.buildActiveProjectPlacementsByWorkspaceCwd() : null;
4175
4440
  if (activePlacementsByCwd) {
@@ -4196,31 +4461,12 @@ export class Session {
4196
4461
  candidates = candidates.filter((agent) => this.compareAgentWithCursor(agent, cursor, sort) > 0);
4197
4462
  }
4198
4463
  const limit = request.page?.limit ?? 200;
4199
- const matchedEntries = [];
4200
- const batchSize = 25;
4201
- for (let start = 0; start < candidates.length && matchedEntries.length <= limit; start += batchSize) {
4202
- const batch = candidates.slice(start, start + batchSize);
4203
- const batchEntries = await Promise.all(batch.map(async (agent) => {
4204
- const project = await getPlacement(agent.cwd);
4205
- return project ? { agent, project } : null;
4206
- }));
4207
- for (const entry of batchEntries) {
4208
- if (!entry) {
4209
- continue;
4210
- }
4211
- if (!this.matchesAgentFilter({
4212
- agent: entry.agent,
4213
- project: entry.project,
4214
- filter,
4215
- })) {
4216
- continue;
4217
- }
4218
- matchedEntries.push(entry);
4219
- if (matchedEntries.length > limit) {
4220
- break;
4221
- }
4222
- }
4223
- }
4464
+ const matchedEntries = await this.collectFetchAgentsEntries({
4465
+ candidates,
4466
+ limit,
4467
+ getPlacement,
4468
+ filter,
4469
+ });
4224
4470
  const pagedEntries = matchedEntries.slice(0, limit);
4225
4471
  const hasMore = matchedEntries.length > limit;
4226
4472
  const nextCursor = hasMore && pagedEntries.length > 0
@@ -4281,6 +4527,11 @@ export class Session {
4281
4527
  resolveHealth: this.resolveScriptHealth ?? undefined,
4282
4528
  })
4283
4529
  : [],
4530
+ ...(resolvedProjectRecord
4531
+ ? {
4532
+ project: await this.buildProjectPlacementForWorkspace(workspace, resolvedProjectRecord),
4533
+ }
4534
+ : {}),
4284
4535
  };
4285
4536
  }
4286
4537
  buildWorkspaceGitRuntimePayload(snapshot) {
@@ -4367,16 +4618,14 @@ export class Session {
4367
4618
  const descriptorsByWorkspaceId = new Map();
4368
4619
  const workspaceIds = options.workspaceIds ? new Set(options.workspaceIds) : null;
4369
4620
  const workspaceIdsByDirectory = new Map(activeRecords.map((workspace) => [normalizePersistedWorkspaceId(workspace.cwd), workspace.workspaceId]));
4370
- for (const workspace of activeRecords) {
4371
- if (workspaceIds && !workspaceIds.has(workspace.workspaceId)) {
4372
- continue;
4373
- }
4374
- const projectRecord = activeProjects.get(workspace.projectId) ?? null;
4375
- descriptorsByWorkspaceId.set(workspace.workspaceId, await this.buildWorkspaceDescriptor({
4376
- workspace,
4377
- projectRecord,
4378
- includeGitData: options.includeGitData,
4379
- }));
4621
+ const includedWorkspaces = activeRecords.filter((workspace) => !workspaceIds || workspaceIds.has(workspace.workspaceId));
4622
+ const workspaceDescriptors = await Promise.all(includedWorkspaces.map((workspace) => this.buildWorkspaceDescriptor({
4623
+ workspace,
4624
+ projectRecord: activeProjects.get(workspace.projectId) ?? null,
4625
+ includeGitData: options.includeGitData,
4626
+ })));
4627
+ for (let i = 0; i < includedWorkspaces.length; i += 1) {
4628
+ descriptorsByWorkspaceId.set(includedWorkspaces[i].workspaceId, workspaceDescriptors[i]);
4380
4629
  }
4381
4630
  for (const agent of agents) {
4382
4631
  if (agent.archivedAt) {
@@ -4406,8 +4655,13 @@ export class Session {
4406
4655
  if (exact) {
4407
4656
  return exact.workspaceId;
4408
4657
  }
4658
+ const userHome = homedir();
4409
4659
  let bestMatch = null;
4410
4660
  for (const workspace of workspaces) {
4661
+ if (workspace.cwd === userHome)
4662
+ continue;
4663
+ if (workspace.archivedAt)
4664
+ continue;
4411
4665
  const prefix = workspace.cwd.endsWith(sep) ? workspace.cwd : `${workspace.cwd}${sep}`;
4412
4666
  if (!normalizedCwd.startsWith(prefix)) {
4413
4667
  continue;
@@ -4492,25 +4746,7 @@ export class Session {
4492
4746
  if (!payload.values || typeof payload.values !== "object") {
4493
4747
  throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4494
4748
  }
4495
- const cursorSort = [];
4496
- for (const item of payload.sort) {
4497
- if (!item ||
4498
- typeof item !== "object" ||
4499
- typeof item.key !== "string" ||
4500
- typeof item.direction !== "string") {
4501
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4502
- }
4503
- const key = item.key;
4504
- const direction = item.direction;
4505
- if ((key !== "status_priority" &&
4506
- key !== "activity_at" &&
4507
- key !== "name" &&
4508
- key !== "project_id") ||
4509
- (direction !== "asc" && direction !== "desc")) {
4510
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4511
- }
4512
- cursorSort.push({ key, direction });
4513
- }
4749
+ const cursorSort = parseFetchWorkspacesCursorSort(payload.sort);
4514
4750
  if (cursorSort.length !== sort.length ||
4515
4751
  cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
4516
4752
  throw new SessionRequestError("invalid_cursor", "fetch_workspaces cursor does not match current sort");
@@ -4637,41 +4873,121 @@ export class Session {
4637
4873
  }
4638
4874
  }
4639
4875
  async findOrCreateWorkspaceForDirectory(cwd) {
4876
+ const inputCwd = normalizePersistedWorkspaceId(cwd);
4640
4877
  const normalizedCwd = await this.resolveWorkspaceDirectory(cwd);
4641
- const existingWorkspace = await this.findWorkspaceByDirectory(normalizedCwd);
4878
+ const existingWorkspace = await this.findExactWorkspaceByDirectory(normalizedCwd, {
4879
+ refreshGit: false,
4880
+ });
4642
4881
  if (existingWorkspace) {
4643
- return this.ensureWorkspaceRecordUnarchived(existingWorkspace);
4882
+ if (existingWorkspace.archivedAt && inputCwd !== normalizedCwd) {
4883
+ const timestamp = new Date().toISOString();
4884
+ const displayName = basename(inputCwd) || inputCwd;
4885
+ const projectRecord = createPersistedProjectRecord({
4886
+ projectId: inputCwd,
4887
+ rootPath: inputCwd,
4888
+ kind: "non_git",
4889
+ displayName,
4890
+ createdAt: timestamp,
4891
+ updatedAt: timestamp,
4892
+ });
4893
+ await this.projectRegistry.upsert(projectRecord);
4894
+ const workspaceRecord = createPersistedWorkspaceRecord({
4895
+ workspaceId: inputCwd,
4896
+ projectId: projectRecord.projectId,
4897
+ cwd: inputCwd,
4898
+ kind: "directory",
4899
+ displayName,
4900
+ createdAt: timestamp,
4901
+ updatedAt: timestamp,
4902
+ });
4903
+ await this.workspaceRegistry.upsert(workspaceRecord);
4904
+ return workspaceRecord;
4905
+ }
4906
+ return this.reclassifyOrUnarchiveWorkspaceForDirectory({
4907
+ workspace: existingWorkspace,
4908
+ project: await this.projectRegistry.get(existingWorkspace.projectId),
4909
+ cwd: normalizedCwd,
4910
+ });
4644
4911
  }
4645
- const placement = await buildProjectPlacementForCwdStandalone({
4646
- cwd: normalizedCwd,
4647
- workspaceGitService: this.workspaceGitService,
4648
- });
4649
- const workspaceId = deriveWorkspaceId(normalizedCwd, placement.checkout);
4912
+ return this.createWorkspaceForDirectory(normalizedCwd);
4913
+ }
4914
+ async createWorkspaceForDirectory(cwd) {
4915
+ const checkout = await this.workspaceGitService.getCheckout(cwd);
4916
+ const membership = classifyDirectoryForProjectMembership({ cwd, checkout });
4650
4917
  const timestamp = new Date().toISOString();
4651
- const projectRecord = createPersistedProjectRecord({
4652
- projectId: placement.projectKey,
4653
- rootPath: deriveProjectRootPath({ cwd: normalizedCwd, checkout: placement.checkout }),
4654
- kind: deriveProjectKind(placement.checkout),
4655
- displayName: placement.projectName,
4656
- createdAt: timestamp,
4657
- updatedAt: timestamp,
4918
+ const projectRecord = await this.resolveProjectRecordForPlacement({
4919
+ membership,
4920
+ timestamp,
4658
4921
  });
4659
4922
  await this.projectRegistry.upsert(projectRecord);
4660
4923
  const workspaceRecord = createPersistedWorkspaceRecord({
4661
- workspaceId,
4662
- projectId: placement.projectKey,
4663
- cwd: normalizedCwd,
4664
- kind: deriveWorkspaceKind(placement.checkout),
4665
- displayName: deriveWorkspaceDisplayName({
4666
- cwd: normalizedCwd,
4667
- checkout: placement.checkout,
4668
- }),
4924
+ workspaceId: membership.workspaceId,
4925
+ projectId: projectRecord.projectId,
4926
+ cwd,
4927
+ kind: membership.workspaceKind,
4928
+ displayName: membership.workspaceDisplayName,
4669
4929
  createdAt: timestamp,
4670
4930
  updatedAt: timestamp,
4671
4931
  });
4672
4932
  await this.workspaceRegistry.upsert(workspaceRecord);
4673
4933
  return workspaceRecord;
4674
4934
  }
4935
+ async reclassifyOrUnarchiveWorkspaceForDirectory(input) {
4936
+ const checkout = await this.workspaceGitService.getCheckout(input.cwd);
4937
+ const membership = classifyDirectoryForProjectMembership({ cwd: input.cwd, checkout });
4938
+ const timestamp = new Date().toISOString();
4939
+ const projectRecord = await this.resolveProjectRecordForPlacement({
4940
+ membership,
4941
+ timestamp,
4942
+ });
4943
+ const projectId = projectRecord.projectId;
4944
+ const kind = membership.workspaceKind;
4945
+ const displayName = membership.workspaceDisplayName;
4946
+ if (input.workspace.workspaceId === membership.workspaceId &&
4947
+ input.workspace.projectId === projectId &&
4948
+ input.workspace.kind === kind &&
4949
+ input.workspace.displayName === displayName) {
4950
+ return this.ensureWorkspaceRecordUnarchived(input.workspace);
4951
+ }
4952
+ await this.projectRegistry.upsert(projectRecord);
4953
+ const nextWorkspace = {
4954
+ ...input.workspace,
4955
+ workspaceId: membership.workspaceId,
4956
+ projectId,
4957
+ cwd: input.cwd,
4958
+ kind,
4959
+ displayName,
4960
+ archivedAt: null,
4961
+ updatedAt: timestamp,
4962
+ };
4963
+ await this.workspaceRegistry.upsert(nextWorkspace);
4964
+ return nextWorkspace;
4965
+ }
4966
+ async resolveProjectRecordForPlacement(input) {
4967
+ const rootPath = input.membership.projectRootPath;
4968
+ const kind = input.membership.projectKind;
4969
+ const projects = await this.projectRegistry.list();
4970
+ const existingProject = projects.find((project) => !project.archivedAt && project.rootPath === rootPath) ??
4971
+ projects.find((project) => project.rootPath === rootPath) ??
4972
+ null;
4973
+ if (!existingProject) {
4974
+ return createPersistedProjectRecord({
4975
+ projectId: input.membership.projectKey,
4976
+ rootPath,
4977
+ kind,
4978
+ displayName: input.membership.projectName,
4979
+ createdAt: input.timestamp,
4980
+ updatedAt: input.timestamp,
4981
+ });
4982
+ }
4983
+ return {
4984
+ ...existingProject,
4985
+ rootPath,
4986
+ kind,
4987
+ archivedAt: null,
4988
+ updatedAt: input.timestamp,
4989
+ };
4990
+ }
4675
4991
  async ensureWorkspaceRecordUnarchived(workspace) {
4676
4992
  const project = await this.projectRegistry.get(workspace.projectId);
4677
4993
  if (!workspace.archivedAt && (!project || !project.archivedAt)) {
@@ -4753,7 +5069,7 @@ export class Session {
4753
5069
  const result = await service.runOnce();
4754
5070
  const changedWorkspaceIds = new Set();
4755
5071
  const changedProjectIds = new Set();
4756
- for (const change of result.changesApplied) {
5072
+ await Promise.all(result.changesApplied.map(async (change) => {
4757
5073
  switch (change.kind) {
4758
5074
  case "workspace_archived":
4759
5075
  await this.removeWorkspaceGitWatchTarget(change.directory);
@@ -4769,7 +5085,7 @@ export class Session {
4769
5085
  changedProjectIds.add(change.projectId);
4770
5086
  break;
4771
5087
  }
4772
- }
5088
+ }));
4773
5089
  if (changedProjectIds.size > 0) {
4774
5090
  for (const workspace of await this.workspaceRegistry.list()) {
4775
5091
  if (changedProjectIds.has(workspace.projectId)) {
@@ -4808,7 +5124,7 @@ export class Session {
4808
5124
  this.onBranchChanged(workspaceId, watchTarget.lastBranchName, newBranchName);
4809
5125
  }
4810
5126
  }
4811
- this.rememberWorkspaceGitWatchFingerprint(workspaceId, nextWorkspace);
5127
+ this.rememberWorkspaceGitDescriptorState(workspaceId, nextWorkspace);
4812
5128
  if (!nextWorkspace) {
4813
5129
  subscription.lastEmittedByWorkspaceId.delete(workspaceId);
4814
5130
  this.bufferOrEmitWorkspaceUpdate(subscription, {
@@ -4848,11 +5164,7 @@ export class Session {
4848
5164
  }
4849
5165
  async handleFetchAgents(request) {
4850
5166
  const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
4851
- const subscriptionId = request.subscribe
4852
- ? requestedSubscriptionId && requestedSubscriptionId.length > 0
4853
- ? requestedSubscriptionId
4854
- : uuidv4()
4855
- : null;
5167
+ const subscriptionId = resolveSubscriptionId(request.subscribe, requestedSubscriptionId);
4856
5168
  try {
4857
5169
  if (subscriptionId) {
4858
5170
  this.agentUpdatesSubscription = {
@@ -4928,11 +5240,7 @@ export class Session {
4928
5240
  }
4929
5241
  async handleFetchWorkspacesRequest(request) {
4930
5242
  const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
4931
- const subscriptionId = request.subscribe
4932
- ? requestedSubscriptionId && requestedSubscriptionId.length > 0
4933
- ? requestedSubscriptionId
4934
- : uuidv4()
4935
- : null;
5243
+ const subscriptionId = resolveSubscriptionId(request.subscribe, requestedSubscriptionId);
4936
5244
  try {
4937
5245
  this.sessionLogger.debug({
4938
5246
  requestId: request.requestId,
@@ -4951,7 +5259,7 @@ export class Session {
4951
5259
  };
4952
5260
  }
4953
5261
  const payload = await this.listFetchWorkspacesEntries(request);
4954
- await this.primeWorkspaceGitWatchFingerprints(payload.entries);
5262
+ this.syncWorkspaceGitObservers(payload.entries);
4955
5263
  this.sessionLogger.debug({
4956
5264
  requestId: request.requestId,
4957
5265
  subscriptionId,
@@ -5001,8 +5309,9 @@ export class Session {
5001
5309
  async handleOpenProjectRequest(request) {
5002
5310
  try {
5003
5311
  const workspace = await this.findOrCreateWorkspaceForDirectory(request.cwd);
5312
+ await this.syncWorkspaceGitObserverForWorkspace(workspace);
5313
+ const descriptor = await this.describeWorkspaceRecord(workspace);
5004
5314
  await this.emitWorkspaceUpdateForCwd(workspace.cwd);
5005
- const descriptor = await this.describeWorkspaceRecordWithGitData(workspace);
5006
5315
  this.emit({
5007
5316
  type: "open_project_response",
5008
5317
  payload: {
@@ -5011,6 +5320,15 @@ export class Session {
5011
5320
  error: null,
5012
5321
  },
5013
5322
  });
5323
+ void this.workspaceGitService
5324
+ .getSnapshot(workspace.cwd, {
5325
+ force: true,
5326
+ includeGitHub: true,
5327
+ reason: "open_project",
5328
+ })
5329
+ .catch((error) => {
5330
+ this.sessionLogger.warn({ err: error, cwd: workspace.cwd }, "Background snapshot refresh failed after open_project");
5331
+ });
5014
5332
  }
5015
5333
  catch (error) {
5016
5334
  const message = error instanceof Error ? error.message : "Failed to open project";
@@ -5278,6 +5596,55 @@ export class Session {
5278
5596
  payload: { requestId, agent, project, error: null },
5279
5597
  });
5280
5598
  }
5599
+ loadProjectedTimelineWindow(params) {
5600
+ const { agentId, direction, cursor, requestedLimit, provider } = params;
5601
+ let timeline = params.timeline;
5602
+ const projectedLimit = Math.max(1, Math.floor(requestedLimit));
5603
+ let fetchLimit = projectedLimit;
5604
+ let projectedWindow = selectTimelineWindowByProjectedLimit({
5605
+ rows: timeline.rows,
5606
+ provider,
5607
+ direction,
5608
+ limit: projectedLimit,
5609
+ collapseToolLifecycle: false,
5610
+ });
5611
+ while (timeline.hasOlder) {
5612
+ const needsMoreProjectedEntries = projectedWindow.projectedEntries.length < projectedLimit;
5613
+ const firstLoadedRow = timeline.rows[0];
5614
+ const firstSelectedRow = projectedWindow.selectedRows[0];
5615
+ const startsAtLoadedBoundary = firstLoadedRow != null &&
5616
+ firstSelectedRow != null &&
5617
+ firstSelectedRow.seq === firstLoadedRow.seq;
5618
+ const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === "assistant_message";
5619
+ if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
5620
+ break;
5621
+ }
5622
+ const maxRows = Math.max(0, timeline.window.maxSeq - timeline.window.minSeq + 1);
5623
+ const nextFetchLimit = Math.min(maxRows, fetchLimit * 2);
5624
+ if (nextFetchLimit <= fetchLimit) {
5625
+ break;
5626
+ }
5627
+ fetchLimit = nextFetchLimit;
5628
+ timeline = this.agentManager.fetchTimeline(agentId, {
5629
+ direction,
5630
+ cursor,
5631
+ limit: fetchLimit,
5632
+ });
5633
+ projectedWindow = selectTimelineWindowByProjectedLimit({
5634
+ rows: timeline.rows,
5635
+ provider,
5636
+ direction,
5637
+ limit: projectedLimit,
5638
+ collapseToolLifecycle: false,
5639
+ });
5640
+ }
5641
+ return {
5642
+ timeline,
5643
+ selectedRows: projectedWindow.selectedRows,
5644
+ minSeq: projectedWindow.minSeq,
5645
+ maxSeq: projectedWindow.maxSeq,
5646
+ };
5647
+ }
5281
5648
  async handleFetchAgentTimelineRequest(msg) {
5282
5649
  const direction = msg.direction ?? (msg.cursor ? "after" : "tail");
5283
5650
  const projection = msg.projection ?? "projected";
@@ -5313,51 +5680,20 @@ export class Session {
5313
5680
  let endCursor = null;
5314
5681
  let entries;
5315
5682
  if (shouldLimitByProjectedWindow) {
5316
- const projectedLimit = Math.max(1, Math.floor(requestedLimit));
5317
- let fetchLimit = projectedLimit;
5318
- let projectedWindow = selectTimelineWindowByProjectedLimit({
5319
- rows: timeline.rows,
5320
- provider: snapshot.provider,
5683
+ const projectedResult = this.loadProjectedTimelineWindow({
5684
+ agentId: msg.agentId,
5321
5685
  direction,
5322
- limit: projectedLimit,
5323
- collapseToolLifecycle: false,
5686
+ cursor,
5687
+ requestedLimit,
5688
+ provider: snapshot.provider,
5689
+ timeline,
5324
5690
  });
5325
- while (timeline.hasOlder) {
5326
- const needsMoreProjectedEntries = projectedWindow.projectedEntries.length < projectedLimit;
5327
- const firstLoadedRow = timeline.rows[0];
5328
- const firstSelectedRow = projectedWindow.selectedRows[0];
5329
- const startsAtLoadedBoundary = firstLoadedRow != null &&
5330
- firstSelectedRow != null &&
5331
- firstSelectedRow.seq === firstLoadedRow.seq;
5332
- const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === "assistant_message";
5333
- if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
5334
- break;
5335
- }
5336
- const maxRows = Math.max(0, timeline.window.maxSeq - timeline.window.minSeq + 1);
5337
- const nextFetchLimit = Math.min(maxRows, fetchLimit * 2);
5338
- if (nextFetchLimit <= fetchLimit) {
5339
- break;
5340
- }
5341
- fetchLimit = nextFetchLimit;
5342
- timeline = this.agentManager.fetchTimeline(msg.agentId, {
5343
- direction,
5344
- cursor,
5345
- limit: fetchLimit,
5346
- });
5347
- projectedWindow = selectTimelineWindowByProjectedLimit({
5348
- rows: timeline.rows,
5349
- provider: snapshot.provider,
5350
- direction,
5351
- limit: projectedLimit,
5352
- collapseToolLifecycle: false,
5353
- });
5354
- }
5355
- const selectedRows = projectedWindow.selectedRows;
5356
- entries = projectTimelineRows(selectedRows, snapshot.provider, projection);
5357
- if (projectedWindow.minSeq !== null && projectedWindow.maxSeq !== null) {
5358
- startCursor = { epoch: timeline.epoch, seq: projectedWindow.minSeq };
5359
- endCursor = { epoch: timeline.epoch, seq: projectedWindow.maxSeq };
5360
- hasOlder = projectedWindow.minSeq > timeline.window.minSeq;
5691
+ timeline = projectedResult.timeline;
5692
+ entries = projectTimelineRows(projectedResult.selectedRows, snapshot.provider, projection);
5693
+ if (projectedResult.minSeq !== null && projectedResult.maxSeq !== null) {
5694
+ startCursor = { epoch: timeline.epoch, seq: projectedResult.minSeq };
5695
+ endCursor = { epoch: timeline.epoch, seq: projectedResult.maxSeq };
5696
+ hasOlder = projectedResult.minSeq > timeline.window.minSeq;
5361
5697
  hasNewer = false;
5362
5698
  }
5363
5699
  }
@@ -5465,18 +5801,13 @@ export class Session {
5465
5801
  await this.agentManager.waitForAgentRunStart(agentId, { signal: startAbort.signal });
5466
5802
  }
5467
5803
  catch (error) {
5468
- const message = error instanceof Error
5469
- ? error.message
5470
- : typeof error === "string"
5471
- ? error
5472
- : "Unknown error";
5473
5804
  this.emit({
5474
5805
  type: "send_agent_message_response",
5475
5806
  payload: {
5476
5807
  requestId: msg.requestId,
5477
5808
  agentId,
5478
5809
  accepted: false,
5479
- error: message,
5810
+ error: errorToFriendlyMessage(error),
5480
5811
  },
5481
5812
  });
5482
5813
  return;
@@ -5495,18 +5826,13 @@ export class Session {
5495
5826
  });
5496
5827
  }
5497
5828
  catch (error) {
5498
- const message = error instanceof Error
5499
- ? error.message
5500
- : typeof error === "string"
5501
- ? error
5502
- : "Unknown error";
5503
5829
  this.emit({
5504
5830
  type: "send_agent_message_response",
5505
5831
  payload: {
5506
5832
  requestId: msg.requestId,
5507
5833
  agentId: resolved.agentId,
5508
5834
  accepted: false,
5509
- error: message,
5835
+ error: errorToFriendlyMessage(error),
5510
5836
  },
5511
5837
  });
5512
5838
  }
@@ -5544,11 +5870,16 @@ export class Session {
5544
5870
  return;
5545
5871
  }
5546
5872
  const final = this.buildStoredAgentPayload(record);
5547
- const status = record.attentionReason === "permission"
5548
- ? "permission"
5549
- : record.lastStatus === "error"
5550
- ? "error"
5551
- : "idle";
5873
+ let status;
5874
+ if (record.attentionReason === "permission") {
5875
+ status = "permission";
5876
+ }
5877
+ else if (record.lastStatus === "error") {
5878
+ status = "error";
5879
+ }
5880
+ else {
5881
+ status = "idle";
5882
+ }
5552
5883
  const error = resolveWaitForFinishError({ status, final });
5553
5884
  this.emit({
5554
5885
  type: "wait_for_finish_response",
@@ -5572,11 +5903,16 @@ export class Session {
5572
5903
  if (!final) {
5573
5904
  throw new Error(`Agent ${agentId} disappeared while waiting`);
5574
5905
  }
5575
- let status = result.permission
5576
- ? "permission"
5577
- : result.status === "error"
5578
- ? "error"
5579
- : "idle";
5906
+ let status;
5907
+ if (result.permission) {
5908
+ status = "permission";
5909
+ }
5910
+ else if (result.status === "error") {
5911
+ status = "error";
5912
+ }
5913
+ else {
5914
+ status = "idle";
5915
+ }
5580
5916
  const error = resolveWaitForFinishError({ status, final });
5581
5917
  this.emit({
5582
5918
  type: "wait_for_finish_response",
@@ -5587,11 +5923,7 @@ export class Session {
5587
5923
  const isAbort = error instanceof Error &&
5588
5924
  (error.name === "AbortError" || error.message.toLowerCase().includes("aborted"));
5589
5925
  if (!isAbort) {
5590
- const message = error instanceof Error
5591
- ? error.message
5592
- : typeof error === "string"
5593
- ? error
5594
- : "Unknown error";
5926
+ const message = errorToFriendlyMessage(error);
5595
5927
  this.sessionLogger.error({ err: error, agentId }, "wait_for_finish_request failed");
5596
5928
  const final = await this.getAgentPayloadById(agentId);
5597
5929
  this.emit({
@@ -5608,7 +5940,7 @@ export class Session {
5608
5940
  }
5609
5941
  const final = await this.getAgentPayloadById(agentId);
5610
5942
  if (!final) {
5611
- throw new Error(`Agent ${agentId} disappeared while waiting`);
5943
+ throw new Error(`Agent ${agentId} disappeared while waiting`, { cause: error });
5612
5944
  }
5613
5945
  this.emit({
5614
5946
  type: "wait_for_finish_response",
@@ -5624,44 +5956,7 @@ export class Session {
5624
5956
  /**
5625
5957
  * Handle audio chunk for buffering and transcription
5626
5958
  */
5627
- async handleAudioChunk(msg) {
5628
- if (!this.isVoiceMode) {
5629
- this.sessionLogger.warn("Received voice_audio_chunk while voice mode is disabled; transcript will be emitted but voice assistant turn is skipped");
5630
- }
5631
- const chunkFormat = msg.format || "audio/wav";
5632
- if (this.isVoiceMode) {
5633
- if (!this.voiceTurnController) {
5634
- throw new Error("Voice mode is enabled but the voice turn controller is not running");
5635
- }
5636
- const chunkBytes = Buffer.byteLength(msg.audio, "base64");
5637
- this.voiceInputChunkCount += 1;
5638
- this.voiceInputBytes += chunkBytes;
5639
- if (this.voiceInputChunkCount === 1) {
5640
- this.sessionLogger.info({
5641
- format: chunkFormat,
5642
- audioBytes: chunkBytes,
5643
- }, "Received first voice_audio_chunk for active voice mode");
5644
- }
5645
- const now = Date.now();
5646
- if (this.voiceInputChunkCount % 50 === 0 || now - this.voiceInputWindowStartedAt >= 1000) {
5647
- this.sessionLogger.info({
5648
- chunkCount: this.voiceInputChunkCount,
5649
- audioBytes: this.voiceInputBytes,
5650
- windowMs: now - this.voiceInputWindowStartedAt,
5651
- format: chunkFormat,
5652
- }, "Voice input chunk summary");
5653
- this.voiceInputWindowStartedAt = now;
5654
- this.voiceInputChunkCount = 0;
5655
- this.voiceInputBytes = 0;
5656
- }
5657
- await this.voiceTurnController.appendClientChunk({
5658
- audioBase64: msg.audio,
5659
- format: chunkFormat,
5660
- });
5661
- return;
5662
- }
5663
- const chunkBuffer = Buffer.from(msg.audio, "base64");
5664
- const isPCMChunk = chunkFormat.toLowerCase().includes("pcm");
5959
+ async ensureAudioBufferForFormat(chunkFormat, isPCMChunk) {
5665
5960
  if (!this.audioBuffer) {
5666
5961
  this.audioBuffer = {
5667
5962
  chunks: [],
@@ -5669,8 +5964,8 @@ export class Session {
5669
5964
  isPCM: isPCMChunk,
5670
5965
  totalPCMBytes: 0,
5671
5966
  };
5967
+ return this.audioBuffer;
5672
5968
  }
5673
- // If the format changes mid-stream, flush what we have first
5674
5969
  if (this.audioBuffer.isPCM !== isPCMChunk) {
5675
5970
  this.sessionLogger.debug({
5676
5971
  oldFormat: this.audioBuffer.isPCM ? "pcm" : this.audioBuffer.format,
@@ -5686,19 +5981,61 @@ export class Session {
5686
5981
  isPCM: isPCMChunk,
5687
5982
  totalPCMBytes: 0,
5688
5983
  };
5984
+ return this.audioBuffer;
5689
5985
  }
5690
- else if (!this.audioBuffer.isPCM) {
5691
- // Keep latest format info for non-PCM blobs
5986
+ if (!this.audioBuffer.isPCM) {
5692
5987
  this.audioBuffer.format = chunkFormat;
5693
5988
  }
5694
- this.audioBuffer.chunks.push(chunkBuffer);
5695
- if (this.audioBuffer.isPCM) {
5696
- this.audioBuffer.totalPCMBytes += chunkBuffer.length;
5989
+ return this.audioBuffer;
5990
+ }
5991
+ async forwardAudioChunkToVoiceTurn(msg, chunkFormat) {
5992
+ if (!this.voiceTurnController) {
5993
+ throw new Error("Voice mode is enabled but the voice turn controller is not running");
5994
+ }
5995
+ const chunkBytes = Buffer.byteLength(msg.audio, "base64");
5996
+ this.voiceInputChunkCount += 1;
5997
+ this.voiceInputBytes += chunkBytes;
5998
+ if (this.voiceInputChunkCount === 1) {
5999
+ this.sessionLogger.info({
6000
+ format: chunkFormat,
6001
+ audioBytes: chunkBytes,
6002
+ }, "Received first voice_audio_chunk for active voice mode");
6003
+ }
6004
+ const now = Date.now();
6005
+ if (this.voiceInputChunkCount % 50 === 0 || now - this.voiceInputWindowStartedAt >= 1000) {
6006
+ this.sessionLogger.info({
6007
+ chunkCount: this.voiceInputChunkCount,
6008
+ audioBytes: this.voiceInputBytes,
6009
+ windowMs: now - this.voiceInputWindowStartedAt,
6010
+ format: chunkFormat,
6011
+ }, "Voice input chunk summary");
6012
+ this.voiceInputWindowStartedAt = now;
6013
+ this.voiceInputChunkCount = 0;
6014
+ this.voiceInputBytes = 0;
6015
+ }
6016
+ await this.voiceTurnController.appendClientChunk({
6017
+ audioBase64: msg.audio,
6018
+ format: chunkFormat,
6019
+ });
6020
+ }
6021
+ async handleAudioChunk(msg) {
6022
+ if (!this.isVoiceMode) {
6023
+ this.sessionLogger.warn("Received voice_audio_chunk while voice mode is disabled; transcript will be emitted but voice assistant turn is skipped");
6024
+ }
6025
+ const chunkFormat = msg.format || "audio/wav";
6026
+ if (this.isVoiceMode) {
6027
+ await this.forwardAudioChunkToVoiceTurn(msg, chunkFormat);
6028
+ return;
6029
+ }
6030
+ const chunkBuffer = Buffer.from(msg.audio, "base64");
6031
+ const isPCMChunk = chunkFormat.toLowerCase().includes("pcm");
6032
+ const buffer = await this.ensureAudioBufferForFormat(chunkFormat, isPCMChunk);
6033
+ buffer.chunks.push(chunkBuffer);
6034
+ if (buffer.isPCM) {
6035
+ buffer.totalPCMBytes += chunkBuffer.length;
5697
6036
  }
5698
6037
  // In non-voice mode, use streaming threshold to process chunks
5699
- const reachedStreamingThreshold = !this.isVoiceMode &&
5700
- this.audioBuffer.isPCM &&
5701
- this.audioBuffer.totalPCMBytes >= MIN_STREAMING_SEGMENT_BYTES;
6038
+ const reachedStreamingThreshold = !this.isVoiceMode && buffer.isPCM && buffer.totalPCMBytes >= MIN_STREAMING_SEGMENT_BYTES;
5702
6039
  if (!msg.isLast && reachedStreamingThreshold) {
5703
6040
  return;
5704
6041
  }
@@ -6265,7 +6602,7 @@ export class Session {
6265
6602
  async handleChatPostRequest(request) {
6266
6603
  try {
6267
6604
  const authorAgentId = request.authorAgentId?.trim() || this.clientId;
6268
- const message = await this.chatService.postMessage({
6605
+ const message = await this.chatService.dispatchMessage({
6269
6606
  room: request.room,
6270
6607
  authorAgentId,
6271
6608
  body: request.body,
@@ -6615,11 +6952,7 @@ export class Session {
6615
6952
  }
6616
6953
  this.emitTerminalsChangedSnapshot({
6617
6954
  cwd: event.cwd,
6618
- terminals: this.filterStandaloneTerminals(event.terminals).map((terminal) => ({
6619
- id: terminal.id,
6620
- name: terminal.name,
6621
- ...(terminal.title ? { title: terminal.title } : {}),
6622
- })),
6955
+ terminals: this.filterStandaloneTerminals(event.terminals).map((terminal) => Object.assign({ id: terminal.id, name: terminal.name }, terminal.title ? { title: terminal.title } : {})),
6623
6956
  });
6624
6957
  }
6625
6958
  handleSubscribeTerminalsRequest(msg) {
@@ -7041,7 +7374,7 @@ export class Session {
7041
7374
  return true;
7042
7375
  }
7043
7376
  disposeTerminalSubscriptions() {
7044
- for (const terminalId of [...this.terminalIdToSlot.keys()]) {
7377
+ for (const terminalId of Array.from(this.terminalIdToSlot.keys())) {
7045
7378
  this.detachTerminalStream(terminalId, { emitExit: false });
7046
7379
  }
7047
7380
  }