@getpaseo/server 0.1.62 → 0.1.65

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 (590) 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 +15 -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 +3 -2
  7. package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -1
  8. package/dist/server/client/daemon-client-websocket-transport.js +9 -8
  9. package/dist/server/client/daemon-client-websocket-transport.js.map +1 -1
  10. package/dist/server/client/daemon-client.d.ts +88 -56
  11. package/dist/server/client/daemon-client.d.ts.map +1 -1
  12. package/dist/server/client/daemon-client.js +264 -111
  13. package/dist/server/client/daemon-client.js.map +1 -1
  14. package/dist/server/client/terminal-stream-router.d.ts +24 -0
  15. package/dist/server/client/terminal-stream-router.d.ts.map +1 -0
  16. package/dist/server/client/terminal-stream-router.js +100 -0
  17. package/dist/server/client/terminal-stream-router.js.map +1 -0
  18. package/dist/server/server/agent/activity-curator.d.ts +6 -3
  19. package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
  20. package/dist/server/server/agent/activity-curator.js +45 -138
  21. package/dist/server/server/agent/activity-curator.js.map +1 -1
  22. package/dist/server/server/agent/agent-loading.d.ts.map +1 -1
  23. package/dist/server/server/agent/agent-loading.js +5 -3
  24. package/dist/server/server/agent/agent-loading.js.map +1 -1
  25. package/dist/server/server/agent/agent-manager.d.ts +46 -31
  26. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  27. package/dist/server/server/agent/agent-manager.js +457 -419
  28. package/dist/server/server/agent/agent-manager.js.map +1 -1
  29. package/dist/server/server/agent/agent-metadata-generator.d.ts +6 -11
  30. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  31. package/dist/server/server/agent/agent-metadata-generator.js +3 -85
  32. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  33. package/dist/server/server/agent/agent-projections.d.ts +4 -6
  34. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  35. package/dist/server/server/agent/agent-projections.js +59 -65
  36. package/dist/server/server/agent/agent-projections.js.map +1 -1
  37. package/dist/server/server/agent/agent-response-loop.d.ts +4 -4
  38. package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
  39. package/dist/server/server/agent/agent-response-loop.js +58 -45
  40. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  41. package/dist/server/server/agent/agent-sdk-types.d.ts +58 -41
  42. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  43. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  44. package/dist/server/server/agent/agent-storage.d.ts +2 -2
  45. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  46. package/dist/server/server/agent/agent-storage.js +29 -36
  47. package/dist/server/server/agent/agent-storage.js.map +1 -1
  48. package/dist/server/server/agent/agent-stream-coalescer.d.ts +7 -7
  49. package/dist/server/server/agent/agent-stream-coalescer.d.ts.map +1 -1
  50. package/dist/server/server/agent/agent-stream-coalescer.js +1 -1
  51. package/dist/server/server/agent/agent-stream-coalescer.js.map +1 -1
  52. package/dist/server/server/agent/agent-timeline-store-types.d.ts +10 -10
  53. package/dist/server/server/agent/agent-timeline-store-types.d.ts.map +1 -1
  54. package/dist/server/server/agent/agent-timeline-store.d.ts +2 -2
  55. package/dist/server/server/agent/agent-timeline-store.d.ts.map +1 -1
  56. package/dist/server/server/agent/agent-timeline-store.js +103 -85
  57. package/dist/server/server/agent/agent-timeline-store.js.map +1 -1
  58. package/dist/server/server/agent/foreground-run-state.d.ts +50 -0
  59. package/dist/server/server/agent/foreground-run-state.d.ts.map +1 -0
  60. package/dist/server/server/agent/foreground-run-state.js +162 -0
  61. package/dist/server/server/agent/foreground-run-state.js.map +1 -0
  62. package/dist/server/server/agent/mcp-server.d.ts +5 -3
  63. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  64. package/dist/server/server/agent/mcp-server.js +282 -234
  65. package/dist/server/server/agent/mcp-server.js.map +1 -1
  66. package/dist/server/server/agent/mcp-shared.d.ts +9 -2
  67. package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
  68. package/dist/server/server/agent/mcp-shared.js +9 -1
  69. package/dist/server/server/agent/mcp-shared.js.map +1 -1
  70. package/dist/server/server/agent/model-resolver.d.ts +2 -2
  71. package/dist/server/server/agent/model-resolver.d.ts.map +1 -1
  72. package/dist/server/server/agent/model-resolver.js +9 -5
  73. package/dist/server/server/agent/model-resolver.js.map +1 -1
  74. package/dist/server/server/agent/prompt-attachments.d.ts +4 -3
  75. package/dist/server/server/agent/prompt-attachments.d.ts.map +1 -1
  76. package/dist/server/server/agent/prompt-attachments.js +43 -4
  77. package/dist/server/server/agent/prompt-attachments.js.map +1 -1
  78. package/dist/server/server/agent/provider-launch-config.d.ts +28 -17
  79. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  80. package/dist/server/server/agent/provider-launch-config.js +20 -9
  81. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  82. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  83. package/dist/server/server/agent/provider-manifest.js +7 -0
  84. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  85. package/dist/server/server/agent/provider-registry.d.ts +4 -2
  86. package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
  87. package/dist/server/server/agent/provider-registry.js +24 -21
  88. package/dist/server/server/agent/provider-registry.js.map +1 -1
  89. package/dist/server/server/agent/provider-snapshot-manager.d.ts +6 -5
  90. package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
  91. package/dist/server/server/agent/provider-snapshot-manager.js +40 -31
  92. package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -1
  93. package/dist/server/server/agent/providers/acp-agent.d.ts +49 -13
  94. package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
  95. package/dist/server/server/agent/providers/acp-agent.js +404 -261
  96. package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
  97. package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +2 -0
  98. package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts.map +1 -1
  99. package/dist/server/server/agent/providers/claude/sidechain-tracker.js +47 -45
  100. package/dist/server/server/agent/providers/claude/sidechain-tracker.js.map +1 -1
  101. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
  102. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -1
  103. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +10 -5
  104. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -1
  105. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  106. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +11 -2
  107. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  108. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +2 -2
  109. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  110. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +83 -206
  111. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  112. package/dist/server/server/agent/providers/claude-agent.d.ts +20 -8
  113. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  114. package/dist/server/server/agent/providers/claude-agent.js +654 -554
  115. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  116. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +2 -2
  117. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts.map +1 -1
  118. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +2 -2
  119. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  120. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +174 -185
  121. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  122. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +62 -13
  123. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  124. package/dist/server/server/agent/providers/codex-app-server-agent.js +873 -646
  125. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  126. package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts +2 -2
  127. package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts.map +1 -1
  128. package/dist/server/server/agent/providers/codex-rollout-timeline.js +58 -47
  129. package/dist/server/server/agent/providers/codex-rollout-timeline.js.map +1 -1
  130. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +2 -2
  131. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +1 -1
  132. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +3 -3
  133. package/dist/server/server/agent/providers/diagnostic-utils.d.ts.map +1 -1
  134. package/dist/server/server/agent/providers/diagnostic-utils.js +82 -9
  135. package/dist/server/server/agent/providers/diagnostic-utils.js.map +1 -1
  136. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +2 -2
  137. package/dist/server/server/agent/providers/generic-acp-agent.d.ts.map +1 -1
  138. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +6 -2
  139. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts.map +1 -1
  140. package/dist/server/server/agent/providers/mock-load-test-agent.js +294 -113
  141. package/dist/server/server/agent/providers/mock-load-test-agent.js.map +1 -1
  142. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +1 -1
  143. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts.map +1 -1
  144. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +94 -2
  145. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -1
  146. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +2 -2
  147. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +1 -1
  148. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +24 -115
  149. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js.map +1 -1
  150. package/dist/server/server/agent/providers/opencode-agent.d.ts +104 -3
  151. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  152. package/dist/server/server/agent/providers/opencode-agent.js +786 -503
  153. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  154. package/dist/server/server/agent/providers/pi-direct-agent.d.ts +1 -0
  155. package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -1
  156. package/dist/server/server/agent/providers/pi-direct-agent.js +109 -140
  157. package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -1
  158. package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
  159. package/dist/server/server/agent/providers/provider-runner.d.ts.map +1 -0
  160. package/dist/server/server/agent/providers/provider-runner.js +80 -0
  161. package/dist/server/server/agent/providers/provider-runner.js.map +1 -0
  162. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts.map +1 -1
  163. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +3 -1
  164. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js.map +1 -1
  165. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +9 -6
  166. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  167. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +102 -73
  168. package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
  169. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +4 -2
  170. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts.map +1 -1
  171. package/dist/server/server/agent/providers/tool-call-mapper-utils.js +31 -0
  172. package/dist/server/server/agent/providers/tool-call-mapper-utils.js.map +1 -1
  173. package/dist/server/server/agent/stt-manager.d.ts.map +1 -1
  174. package/dist/server/server/agent/stt-manager.js +63 -53
  175. package/dist/server/server/agent/stt-manager.js.map +1 -1
  176. package/dist/server/server/agent/timeline-projection.d.ts +27 -11
  177. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  178. package/dist/server/server/agent/timeline-projection.js +70 -15
  179. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  180. package/dist/server/server/agent/tts-manager.d.ts.map +1 -1
  181. package/dist/server/server/agent/tts-manager.js +1 -0
  182. package/dist/server/server/agent/tts-manager.js.map +1 -1
  183. package/dist/server/server/agent-attention-policy.d.ts +2 -2
  184. package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
  185. package/dist/server/server/auth.d.ts +25 -0
  186. package/dist/server/server/auth.d.ts.map +1 -0
  187. package/dist/server/server/auth.js +93 -0
  188. package/dist/server/server/auth.js.map +1 -0
  189. package/dist/server/server/bootstrap.d.ts +7 -5
  190. package/dist/server/server/bootstrap.d.ts.map +1 -1
  191. package/dist/server/server/bootstrap.js +550 -485
  192. package/dist/server/server/bootstrap.js.map +1 -1
  193. package/dist/server/server/chat/chat-service.d.ts +1 -1
  194. package/dist/server/server/chat/chat-service.d.ts.map +1 -1
  195. package/dist/server/server/chat/chat-service.js +3 -3
  196. package/dist/server/server/chat/chat-service.js.map +1 -1
  197. package/dist/server/server/checkout-diff-manager.d.ts +2 -2
  198. package/dist/server/server/checkout-diff-manager.d.ts.map +1 -1
  199. package/dist/server/server/checkout-git-utils.d.ts +5 -3
  200. package/dist/server/server/checkout-git-utils.d.ts.map +1 -1
  201. package/dist/server/server/checkout-git-utils.js +1 -2
  202. package/dist/server/server/checkout-git-utils.js.map +1 -1
  203. package/dist/server/server/config.d.ts.map +1 -1
  204. package/dist/server/server/config.js +79 -39
  205. package/dist/server/server/config.js.map +1 -1
  206. package/dist/server/server/connection-offer.d.ts +2 -2
  207. package/dist/server/server/connection-offer.d.ts.map +1 -1
  208. package/dist/server/server/daemon-config-store.d.ts +5 -3
  209. package/dist/server/server/daemon-config-store.d.ts.map +1 -1
  210. package/dist/server/server/daemon-config-store.js +26 -0
  211. package/dist/server/server/daemon-config-store.js.map +1 -1
  212. package/dist/server/server/daemon-keypair.d.ts +2 -2
  213. package/dist/server/server/daemon-keypair.d.ts.map +1 -1
  214. package/dist/server/server/editor-targets.d.ts +4 -4
  215. package/dist/server/server/editor-targets.d.ts.map +1 -1
  216. package/dist/server/server/editor-targets.js +11 -15
  217. package/dist/server/server/editor-targets.js.map +1 -1
  218. package/dist/server/server/exports.d.ts +10 -4
  219. package/dist/server/server/exports.d.ts.map +1 -1
  220. package/dist/server/server/exports.js +7 -4
  221. package/dist/server/server/exports.js.map +1 -1
  222. package/dist/server/server/file-download/token-store.d.ts +4 -4
  223. package/dist/server/server/file-download/token-store.d.ts.map +1 -1
  224. package/dist/server/server/file-explorer/service.d.ts +10 -0
  225. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  226. package/dist/server/server/file-explorer/service.js +38 -4
  227. package/dist/server/server/file-explorer/service.js.map +1 -1
  228. package/dist/server/server/index.js +25 -18
  229. package/dist/server/server/index.js.map +1 -1
  230. package/dist/server/server/logger.d.ts +4 -4
  231. package/dist/server/server/logger.d.ts.map +1 -1
  232. package/dist/server/server/logger.js +41 -21
  233. package/dist/server/server/logger.js.map +1 -1
  234. package/dist/server/server/loop/rpc-schemas.d.ts +52 -52
  235. package/dist/server/server/loop-service.d.ts +13 -12
  236. package/dist/server/server/loop-service.d.ts.map +1 -1
  237. package/dist/server/server/loop-service.js +22 -18
  238. package/dist/server/server/loop-service.js.map +1 -1
  239. package/dist/server/server/package-version.d.ts +2 -2
  240. package/dist/server/server/package-version.d.ts.map +1 -1
  241. package/dist/server/server/package-version.js +19 -17
  242. package/dist/server/server/package-version.js.map +1 -1
  243. package/dist/server/server/pagination/cursor.d.ts +16 -0
  244. package/dist/server/server/pagination/cursor.d.ts.map +1 -0
  245. package/dist/server/server/pagination/cursor.js +62 -0
  246. package/dist/server/server/pagination/cursor.js.map +1 -0
  247. package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
  248. package/dist/server/server/pagination/sortable-pager.d.ts.map +1 -0
  249. package/dist/server/server/pagination/sortable-pager.js +68 -0
  250. package/dist/server/server/pagination/sortable-pager.js.map +1 -0
  251. package/dist/server/server/pairing-offer.d.ts +2 -2
  252. package/dist/server/server/pairing-offer.d.ts.map +1 -1
  253. package/dist/server/server/paseo-env.d.ts +9 -0
  254. package/dist/server/server/paseo-env.d.ts.map +1 -0
  255. package/dist/server/server/paseo-env.js +70 -0
  256. package/dist/server/server/paseo-env.js.map +1 -0
  257. package/dist/server/server/paseo-worktree-archive-service.d.ts +7 -5
  258. package/dist/server/server/paseo-worktree-archive-service.d.ts.map +1 -1
  259. package/dist/server/server/paseo-worktree-archive-service.js +70 -62
  260. package/dist/server/server/paseo-worktree-archive-service.js.map +1 -1
  261. package/dist/server/server/paseo-worktree-service.d.ts +13 -0
  262. package/dist/server/server/paseo-worktree-service.d.ts.map +1 -1
  263. package/dist/server/server/paseo-worktree-service.js +72 -3
  264. package/dist/server/server/paseo-worktree-service.js.map +1 -1
  265. package/dist/server/server/persisted-config.d.ts +87 -62
  266. package/dist/server/server/persisted-config.d.ts.map +1 -1
  267. package/dist/server/server/persisted-config.js +13 -4
  268. package/dist/server/server/persisted-config.js.map +1 -1
  269. package/dist/server/server/persistence-hooks.d.ts +8 -9
  270. package/dist/server/server/persistence-hooks.d.ts.map +1 -1
  271. package/dist/server/server/persistence-hooks.js +4 -12
  272. package/dist/server/server/persistence-hooks.js.map +1 -1
  273. package/dist/server/server/pid-lock.js.map +1 -1
  274. package/dist/server/server/push/push-service.d.ts.map +1 -1
  275. package/dist/server/server/push/push-service.js +1 -3
  276. package/dist/server/server/push/push-service.js.map +1 -1
  277. package/dist/server/server/relay-transport.d.ts +8 -8
  278. package/dist/server/server/relay-transport.d.ts.map +1 -1
  279. package/dist/server/server/relay-transport.js +43 -20
  280. package/dist/server/server/relay-transport.js.map +1 -1
  281. package/dist/server/server/resolve-worktree-creation-intent.d.ts +0 -10
  282. package/dist/server/server/resolve-worktree-creation-intent.d.ts.map +1 -1
  283. package/dist/server/server/resolve-worktree-creation-intent.js +1 -45
  284. package/dist/server/server/resolve-worktree-creation-intent.js.map +1 -1
  285. package/dist/server/server/schedule/service.d.ts.map +1 -1
  286. package/dist/server/server/schedule/service.js +2 -2
  287. package/dist/server/server/schedule/service.js.map +1 -1
  288. package/dist/server/server/script-health-monitor.d.ts.map +1 -1
  289. package/dist/server/server/script-health-monitor.js +7 -6
  290. package/dist/server/server/script-health-monitor.js.map +1 -1
  291. package/dist/server/server/script-proxy.js +1 -1
  292. package/dist/server/server/script-proxy.js.map +1 -1
  293. package/dist/server/server/script-status-projection.d.ts +10 -5
  294. package/dist/server/server/script-status-projection.d.ts.map +1 -1
  295. package/dist/server/server/script-status-projection.js +66 -47
  296. package/dist/server/server/script-status-projection.js.map +1 -1
  297. package/dist/server/server/server-id.d.ts +4 -4
  298. package/dist/server/server/server-id.d.ts.map +1 -1
  299. package/dist/server/server/session.d.ts +64 -65
  300. package/dist/server/server/session.d.ts.map +1 -1
  301. package/dist/server/server/session.js +1356 -1734
  302. package/dist/server/server/session.js.map +1 -1
  303. package/dist/server/server/speech/audio.js +1 -1
  304. package/dist/server/server/speech/audio.js.map +1 -1
  305. package/dist/server/server/speech/providers/local/config.d.ts +6 -6
  306. package/dist/server/server/speech/providers/local/config.d.ts.map +1 -1
  307. package/dist/server/server/speech/providers/local/config.js +41 -16
  308. package/dist/server/server/speech/providers/local/config.js.map +1 -1
  309. package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.d.ts +2 -2
  310. package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.d.ts.map +1 -1
  311. package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.js +42 -19
  312. package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -1
  313. package/dist/server/server/speech/providers/local/runtime.d.ts +4 -4
  314. package/dist/server/server/speech/providers/local/runtime.d.ts.map +1 -1
  315. package/dist/server/server/speech/providers/local/runtime.js +108 -77
  316. package/dist/server/server/speech/providers/local/runtime.js.map +1 -1
  317. package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +2 -2
  318. package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts.map +1 -1
  319. package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +1 -4
  320. package/dist/server/server/speech/providers/local/sherpa/model-catalog.js.map +1 -1
  321. package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +2 -2
  322. package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts.map +1 -1
  323. package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +19 -19
  324. package/dist/server/server/speech/providers/local/sherpa/model-downloader.js.map +1 -1
  325. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +28 -7
  326. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts.map +1 -1
  327. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -1
  328. package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.d.ts +23 -4
  329. package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.d.ts.map +1 -1
  330. package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +35 -28
  331. package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -1
  332. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-loader.d.ts +5 -5
  333. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-loader.d.ts.map +1 -1
  334. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +7 -7
  335. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts.map +1 -1
  336. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +5 -0
  337. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -1
  338. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts.map +1 -1
  339. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +3 -1
  340. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -1
  341. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +2 -2
  342. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts.map +1 -1
  343. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +3 -1
  344. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -1
  345. package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.d.ts.map +1 -1
  346. package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.js +10 -4
  347. package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -1
  348. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +2 -2
  349. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts.map +1 -1
  350. package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.d.ts +2 -2
  351. package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.d.ts.map +1 -1
  352. package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.js +4 -1
  353. package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -1
  354. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +2 -2
  355. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts.map +1 -1
  356. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +18 -11
  357. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -1
  358. package/dist/server/server/speech/providers/openai/config.d.ts +2 -2
  359. package/dist/server/server/speech/providers/openai/config.d.ts.map +1 -1
  360. package/dist/server/server/speech/providers/openai/config.js +58 -31
  361. package/dist/server/server/speech/providers/openai/config.js.map +1 -1
  362. package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts.map +1 -1
  363. package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +2 -2
  364. package/dist/server/server/speech/providers/openai/realtime-transcription-session.js.map +1 -1
  365. package/dist/server/server/speech/providers/openai/runtime.d.ts +4 -4
  366. package/dist/server/server/speech/providers/openai/runtime.d.ts.map +1 -1
  367. package/dist/server/server/speech/providers/openai/runtime.js +37 -32
  368. package/dist/server/server/speech/providers/openai/runtime.js.map +1 -1
  369. package/dist/server/server/speech/providers/openai/stt.d.ts.map +1 -1
  370. package/dist/server/server/speech/providers/openai/stt.js +4 -3
  371. package/dist/server/server/speech/providers/openai/stt.js.map +1 -1
  372. package/dist/server/server/speech/providers/openai/tts.d.ts.map +1 -1
  373. package/dist/server/server/speech/providers/openai/tts.js +3 -2
  374. package/dist/server/server/speech/providers/openai/tts.js.map +1 -1
  375. package/dist/server/server/speech/speech-config-resolver.d.ts.map +1 -1
  376. package/dist/server/server/speech/speech-config-resolver.js +46 -17
  377. package/dist/server/server/speech/speech-config-resolver.js.map +1 -1
  378. package/dist/server/server/speech/speech-provider.d.ts +2 -2
  379. package/dist/server/server/speech/speech-provider.d.ts.map +1 -1
  380. package/dist/server/server/speech/speech-runtime.d.ts +6 -6
  381. package/dist/server/server/speech/speech-runtime.d.ts.map +1 -1
  382. package/dist/server/server/speech/speech-runtime.js +17 -17
  383. package/dist/server/server/speech/speech-runtime.js.map +1 -1
  384. package/dist/server/server/speech/speech-types.d.ts +2 -2
  385. package/dist/server/server/speech/speech-types.d.ts.map +1 -1
  386. package/dist/server/server/speech/turn-detection-provider.d.ts +2 -2
  387. package/dist/server/server/speech/turn-detection-provider.d.ts.map +1 -1
  388. package/dist/server/server/utils/diff-highlighter.d.ts +0 -3
  389. package/dist/server/server/utils/diff-highlighter.d.ts.map +1 -1
  390. package/dist/server/server/utils/diff-highlighter.js +67 -66
  391. package/dist/server/server/utils/diff-highlighter.js.map +1 -1
  392. package/dist/server/server/voice/voice-turn-controller.d.ts.map +1 -1
  393. package/dist/server/server/voice/voice-turn-controller.js +1 -0
  394. package/dist/server/server/voice/voice-turn-controller.js.map +1 -1
  395. package/dist/server/server/voice-types.d.ts +2 -2
  396. package/dist/server/server/voice-types.d.ts.map +1 -1
  397. package/dist/server/server/websocket-server.d.ts +34 -22
  398. package/dist/server/server/websocket-server.d.ts.map +1 -1
  399. package/dist/server/server/websocket-server.js +360 -205
  400. package/dist/server/server/websocket-server.js.map +1 -1
  401. package/dist/server/server/workspace-directory.d.ts +69 -0
  402. package/dist/server/server/workspace-directory.d.ts.map +1 -0
  403. package/dist/server/server/workspace-directory.js +229 -0
  404. package/dist/server/server/workspace-directory.js.map +1 -0
  405. package/dist/server/server/workspace-git-metadata.d.ts +2 -2
  406. package/dist/server/server/workspace-git-metadata.d.ts.map +1 -1
  407. package/dist/server/server/workspace-git-metadata.js +2 -32
  408. package/dist/server/server/workspace-git-metadata.js.map +1 -1
  409. package/dist/server/server/workspace-git-service.d.ts +8 -4
  410. package/dist/server/server/workspace-git-service.d.ts.map +1 -1
  411. package/dist/server/server/workspace-git-service.js +163 -115
  412. package/dist/server/server/workspace-git-service.js.map +1 -1
  413. package/dist/server/server/workspace-reconciliation-service.d.ts +5 -4
  414. package/dist/server/server/workspace-reconciliation-service.d.ts.map +1 -1
  415. package/dist/server/server/workspace-reconciliation-service.js +82 -82
  416. package/dist/server/server/workspace-reconciliation-service.js.map +1 -1
  417. package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -1
  418. package/dist/server/server/workspace-registry-bootstrap.js +40 -33
  419. package/dist/server/server/workspace-registry-bootstrap.js.map +1 -1
  420. package/dist/server/server/workspace-registry-model.d.ts +19 -6
  421. package/dist/server/server/workspace-registry-model.d.ts.map +1 -1
  422. package/dist/server/server/workspace-registry-model.js +35 -21
  423. package/dist/server/server/workspace-registry-model.js.map +1 -1
  424. package/dist/server/server/workspace-registry.d.ts +2 -2
  425. package/dist/server/server/workspace-script-runtime-store.d.ts +2 -2
  426. package/dist/server/server/workspace-script-runtime-store.d.ts.map +1 -1
  427. package/dist/server/server/workspace-service-env.js +3 -3
  428. package/dist/server/server/workspace-service-env.js.map +1 -1
  429. package/dist/server/server/worktree-bootstrap.d.ts +4 -4
  430. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  431. package/dist/server/server/worktree-bootstrap.js +101 -69
  432. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  433. package/dist/server/server/worktree-core.d.ts +2 -0
  434. package/dist/server/server/worktree-core.d.ts.map +1 -1
  435. package/dist/server/server/worktree-core.js.map +1 -1
  436. package/dist/server/server/worktree-errors.d.ts +1 -1
  437. package/dist/server/server/worktree-errors.d.ts.map +1 -1
  438. package/dist/server/server/worktree-errors.js +1 -4
  439. package/dist/server/server/worktree-errors.js.map +1 -1
  440. package/dist/server/server/worktree-session.d.ts +54 -27
  441. package/dist/server/server/worktree-session.d.ts.map +1 -1
  442. package/dist/server/server/worktree-session.js +95 -44
  443. package/dist/server/server/worktree-session.js.map +1 -1
  444. package/dist/server/services/github-service.d.ts +1 -7
  445. package/dist/server/services/github-service.d.ts.map +1 -1
  446. package/dist/server/services/github-service.js +123 -143
  447. package/dist/server/services/github-service.js.map +1 -1
  448. package/dist/server/shared/agent-attention-notification.d.ts +9 -8
  449. package/dist/server/shared/agent-attention-notification.d.ts.map +1 -1
  450. package/dist/server/shared/agent-attention-notification.js +27 -17
  451. package/dist/server/shared/agent-attention-notification.js.map +1 -1
  452. package/dist/server/shared/binary-frames/file-transfer.d.ts +56 -0
  453. package/dist/server/shared/binary-frames/file-transfer.d.ts.map +1 -0
  454. package/dist/server/shared/binary-frames/file-transfer.js +108 -0
  455. package/dist/server/shared/binary-frames/file-transfer.js.map +1 -0
  456. package/dist/server/shared/binary-frames/index.d.ts +3 -0
  457. package/dist/server/shared/binary-frames/index.d.ts.map +1 -0
  458. package/dist/server/shared/binary-frames/index.js +3 -0
  459. package/dist/server/shared/binary-frames/index.js.map +1 -0
  460. package/dist/server/shared/{terminal-stream-protocol.d.ts → binary-frames/terminal.d.ts} +4 -4
  461. package/dist/server/shared/binary-frames/terminal.d.ts.map +1 -0
  462. package/dist/server/shared/{terminal-stream-protocol.js → binary-frames/terminal.js} +2 -2
  463. package/dist/server/shared/binary-frames/terminal.js.map +1 -0
  464. package/dist/server/shared/client-capabilities.d.ts +5 -0
  465. package/dist/server/shared/client-capabilities.d.ts.map +1 -0
  466. package/dist/server/shared/client-capabilities.js +4 -0
  467. package/dist/server/shared/client-capabilities.js.map +1 -0
  468. package/dist/server/shared/connection-offer.d.ts +8 -0
  469. package/dist/server/shared/connection-offer.d.ts.map +1 -1
  470. package/dist/server/shared/connection-offer.js +35 -0
  471. package/dist/server/shared/connection-offer.js.map +1 -1
  472. package/dist/server/shared/daemon-endpoints.d.ts +18 -3
  473. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
  474. package/dist/server/shared/daemon-endpoints.js +82 -8
  475. package/dist/server/shared/daemon-endpoints.js.map +1 -1
  476. package/dist/server/shared/host-connection-schema.d.ts +23 -0
  477. package/dist/server/shared/host-connection-schema.d.ts.map +1 -0
  478. package/dist/server/shared/host-connection-schema.js +9 -0
  479. package/dist/server/shared/host-connection-schema.js.map +1 -0
  480. package/dist/server/shared/messages.d.ts +25073 -3453
  481. package/dist/server/shared/messages.d.ts.map +1 -1
  482. package/dist/server/shared/messages.js +152 -36
  483. package/dist/server/shared/messages.js.map +1 -1
  484. package/dist/server/shared/tool-call-display.d.ts +2 -2
  485. package/dist/server/shared/tool-call-display.d.ts.map +1 -1
  486. package/dist/server/terminal/terminal-manager-factory.d.ts +7 -0
  487. package/dist/server/terminal/terminal-manager-factory.d.ts.map +1 -0
  488. package/dist/server/terminal/terminal-manager-factory.js +13 -0
  489. package/dist/server/terminal/terminal-manager-factory.js.map +1 -0
  490. package/dist/server/terminal/terminal-manager.d.ts +7 -1
  491. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  492. package/dist/server/terminal/terminal-manager.js +15 -4
  493. package/dist/server/terminal/terminal-manager.js.map +1 -1
  494. package/dist/server/terminal/terminal-output-coalescer.d.ts +6 -6
  495. package/dist/server/terminal/terminal-output-coalescer.d.ts.map +1 -1
  496. package/dist/server/terminal/terminal-session-controller.d.ts +63 -0
  497. package/dist/server/terminal/terminal-session-controller.d.ts.map +1 -0
  498. package/dist/server/terminal/terminal-session-controller.js +615 -0
  499. package/dist/server/terminal/terminal-session-controller.js.map +1 -0
  500. package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
  501. package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
  502. package/dist/server/terminal/terminal-worker-process.d.ts.map +1 -0
  503. package/dist/server/terminal/terminal-worker-process.js +221 -0
  504. package/dist/server/terminal/terminal-worker-process.js.map +1 -0
  505. package/dist/server/terminal/terminal-worker-protocol.d.ts +113 -0
  506. package/dist/server/terminal/terminal-worker-protocol.d.ts.map +1 -0
  507. package/dist/server/terminal/terminal-worker-protocol.js +2 -0
  508. package/dist/server/terminal/terminal-worker-protocol.js.map +1 -0
  509. package/dist/server/terminal/terminal.d.ts +10 -2
  510. package/dist/server/terminal/terminal.d.ts.map +1 -1
  511. package/dist/server/terminal/terminal.js +79 -28
  512. package/dist/server/terminal/terminal.js.map +1 -1
  513. package/dist/server/terminal/worker-terminal-manager.d.ts +19 -0
  514. package/dist/server/terminal/worker-terminal-manager.d.ts.map +1 -0
  515. package/dist/server/terminal/worker-terminal-manager.js +466 -0
  516. package/dist/server/terminal/worker-terminal-manager.js.map +1 -0
  517. package/dist/server/utils/checkout-git.d.ts +13 -12
  518. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  519. package/dist/server/utils/checkout-git.js +351 -281
  520. package/dist/server/utils/checkout-git.js.map +1 -1
  521. package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
  522. package/dist/server/utils/directory-suggestions.js +22 -34
  523. package/dist/server/utils/directory-suggestions.js.map +1 -1
  524. package/dist/server/utils/executable.d.ts +1 -14
  525. package/dist/server/utils/executable.d.ts.map +1 -1
  526. package/dist/server/utils/executable.js +13 -49
  527. package/dist/server/utils/executable.js.map +1 -1
  528. package/dist/server/utils/github-remote.d.ts +13 -0
  529. package/dist/server/utils/github-remote.d.ts.map +1 -0
  530. package/dist/server/utils/github-remote.js +128 -0
  531. package/dist/server/utils/github-remote.js.map +1 -0
  532. package/dist/server/utils/paseo-config-file.d.ts +30 -0
  533. package/dist/server/utils/paseo-config-file.d.ts.map +1 -0
  534. package/dist/server/utils/paseo-config-file.js +90 -0
  535. package/dist/server/utils/paseo-config-file.js.map +1 -0
  536. package/dist/server/utils/paseo-config-schema.d.ts +290 -0
  537. package/dist/server/utils/paseo-config-schema.d.ts.map +1 -0
  538. package/dist/server/utils/paseo-config-schema.js +60 -0
  539. package/dist/server/utils/paseo-config-schema.js.map +1 -0
  540. package/dist/server/utils/process-tree.d.ts +25 -0
  541. package/dist/server/utils/process-tree.d.ts.map +1 -0
  542. package/dist/server/utils/process-tree.js +96 -0
  543. package/dist/server/utils/process-tree.js.map +1 -0
  544. package/dist/server/utils/project-icon.d.ts.map +1 -1
  545. package/dist/server/utils/project-icon.js +84 -109
  546. package/dist/server/utils/project-icon.js.map +1 -1
  547. package/dist/server/utils/promise-timeout.d.ts +2 -2
  548. package/dist/server/utils/promise-timeout.d.ts.map +1 -1
  549. package/dist/server/utils/run-git-command.d.ts +3 -1
  550. package/dist/server/utils/run-git-command.d.ts.map +1 -1
  551. package/dist/server/utils/run-git-command.js +10 -1
  552. package/dist/server/utils/run-git-command.js.map +1 -1
  553. package/dist/server/utils/script-hostname.d.ts +2 -2
  554. package/dist/server/utils/script-hostname.d.ts.map +1 -1
  555. package/dist/server/utils/spawn.d.ts +10 -3
  556. package/dist/server/utils/spawn.d.ts.map +1 -1
  557. package/dist/server/utils/spawn.js +30 -5
  558. package/dist/server/utils/spawn.js.map +1 -1
  559. package/dist/server/utils/windows-command.d.ts +15 -0
  560. package/dist/server/utils/windows-command.d.ts.map +1 -0
  561. package/dist/server/utils/windows-command.js +41 -0
  562. package/dist/server/utils/windows-command.js.map +1 -0
  563. package/dist/server/utils/worktree-metadata.d.ts +44 -0
  564. package/dist/server/utils/worktree-metadata.d.ts.map +1 -1
  565. package/dist/server/utils/worktree-metadata.js +58 -0
  566. package/dist/server/utils/worktree-metadata.js.map +1 -1
  567. package/dist/server/utils/worktree.d.ts +23 -8
  568. package/dist/server/utils/worktree.d.ts.map +1 -1
  569. package/dist/server/utils/worktree.js +81 -63
  570. package/dist/server/utils/worktree.js.map +1 -1
  571. package/dist/src/server/pid-lock.js.map +1 -1
  572. package/package.json +17 -21
  573. package/dist/server/server/agent/llm-openai.d.ts +0 -7
  574. package/dist/server/server/agent/llm-openai.d.ts.map +0 -1
  575. package/dist/server/server/agent/llm-openai.js +0 -8
  576. package/dist/server/server/agent/llm-openai.js.map +0 -1
  577. package/dist/server/server/agent/orchestrator.d.ts +0 -12
  578. package/dist/server/server/agent/orchestrator.d.ts.map +0 -1
  579. package/dist/server/server/agent/orchestrator.js +0 -12
  580. package/dist/server/server/agent/orchestrator.js.map +0 -1
  581. package/dist/server/server/types.d.ts +0 -5
  582. package/dist/server/server/types.d.ts.map +0 -1
  583. package/dist/server/server/types.js +0 -3
  584. package/dist/server/server/types.js.map +0 -1
  585. package/dist/server/server/workspace-registry.test-helpers.d.ts +0 -37
  586. package/dist/server/server/workspace-registry.test-helpers.d.ts.map +0 -1
  587. package/dist/server/server/workspace-registry.test-helpers.js +0 -121
  588. package/dist/server/server/workspace-registry.test-helpers.js.map +0 -1
  589. package/dist/server/shared/terminal-stream-protocol.d.ts.map +0 -1
  590. package/dist/server/shared/terminal-stream-protocol.js.map +0 -1
@@ -2,13 +2,16 @@ 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";
9
+ import { CLIENT_CAPS } from "../shared/client-capabilities.js";
8
10
  import { isLegacyEditorTargetId, serializeAgentStreamEvent, } from "./messages.js";
9
- import { captureTerminalLines } from "../terminal/terminal.js";
10
- import { TerminalOutputCoalescer } from "../terminal/terminal-output-coalescer.js";
11
- import { TerminalStreamOpcode, encodeTerminalSnapshotPayload, encodeTerminalStreamFrame, decodeTerminalResizePayload, } from "../shared/terminal-stream-protocol.js";
11
+ import { TerminalSessionController } from "../terminal/terminal-session-controller.js";
12
+ import { encodeFileTransferFrame, FileTransferOpcode, } from "../shared/binary-frames/index.js";
13
+ import { CursorError } from "./pagination/cursor.js";
14
+ import { SortablePager } from "./pagination/sortable-pager.js";
12
15
  import { TTSManager } from "./agent/tts-manager.js";
13
16
  import { STTManager } from "./agent/stt-manager.js";
14
17
  import { maybePersistTtsDebugAudio } from "./agent/tts-debug.js";
@@ -16,14 +19,15 @@ import { isPaseoDictationDebugEnabled } from "./agent/recordings-debug.js";
16
19
  import { listAvailableEditorTargets, openInEditorTarget } from "./editor-targets.js";
17
20
  import { DictationStreamManager, } from "./dictation/dictation-stream-manager.js";
18
21
  import { createVoiceTurnController, } from "./voice/voice-turn-controller.js";
19
- import { buildConfigOverrides, extractTimestamps, toAgentPersistenceHandle, } from "./persistence-hooks.js";
22
+ import { buildConfigOverrides, extractTimestamps, isStoredAgentProviderAvailable, toAgentPersistenceHandle, } from "./persistence-hooks.js";
20
23
  import { ensureAgentLoaded } from "./agent/agent-loading.js";
21
24
  import { sendPromptToAgent, unarchiveAgentState } from "./agent/mcp-shared.js";
22
25
  import { experimental_createMCPClient } from "ai";
23
26
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
24
- import { buildWorkspaceScriptPayloads } from "./script-status-projection.js";
27
+ import { buildWorkspaceScriptPayloads, readPaseoConfigForProjection, } from "./script-status-projection.js";
25
28
  import { deriveProjectSlug } from "./workspace-git-metadata.js";
26
29
  import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
30
+ import { applyMutableProviderConfigToOverrides } from "./daemon-config-store.js";
27
31
  import { buildProviderRegistry } from "./agent/provider-registry.js";
28
32
  import { resolveSnapshotCwd } from "./agent/provider-snapshot-manager.js";
29
33
  import { scheduleAgentMetadataGeneration } from "./agent/agent-metadata-generator.js";
@@ -32,12 +36,12 @@ import { MAX_EXPLICIT_AGENT_TITLE_CHARS } from "./agent/agent-title-limits.js";
32
36
  import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
33
37
  import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from "./agent/timeline-projection.js";
34
38
  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";
39
+ import { checkoutLiteFromGitSnapshot, normalizeWorkspaceId as normalizePersistedWorkspaceId, deriveProjectGroupingName, classifyDirectoryForProjectMembership, deriveWorkspaceDisplayName, } from "./workspace-registry-model.js";
36
40
  import { createPersistedProjectRecord, createPersistedWorkspaceRecord, } from "./workspace-registry.js";
37
41
  import { buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, wrapSpokenInput, } from "./voice-config.js";
38
42
  import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
39
- import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from "./file-explorer/service.js";
40
- import { runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
43
+ import { listDirectoryEntries, readExplorerFile, readExplorerFileBytes, getDownloadableFileInfo, } from "./file-explorer/service.js";
44
+ import { readPaseoConfigForEdit, writePaseoConfigForEdit, } from "../utils/paseo-config-file.js";
41
45
  import { archivePersistedWorkspaceRecord } from "./workspace-archive-service.js";
42
46
  import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
43
47
  import { checkoutResolvedBranch, commitChanges, mergeToBase, mergeFromBase, pullCurrentBranch, pushCurrentBranch, createPullRequest, } from "../utils/checkout-git.js";
@@ -51,19 +55,104 @@ import { ChatServiceError } from "./chat/chat-service.js";
51
55
  import { notifyChatMentions } from "./chat/chat-mentions.js";
52
56
  import { execCommand } from "../utils/spawn.js";
53
57
  import { createGitHubService, } from "../services/github-service.js";
54
- import { createPaseoWorktree, } from "./paseo-worktree-service.js";
58
+ import { summarizeFetchWorkspacesEntries, WorkspaceDirectory, } from "./workspace-directory.js";
59
+ import { attemptFirstAgentBranchAutoName, createPaseoWorktree, } from "./paseo-worktree-service.js";
55
60
  import { createWorktreeCoreDeps } from "./worktree-core.js";
56
- import { assertSafeGitRef as assertWorktreeSafeGitRef, buildAgentSessionConfig as buildWorktreeAgentSessionConfig, runWorktreeSetupInBackground as runWorktreeSetupInBackgroundSession, handleCreatePaseoWorktreeRequest as handleCreateWorktreeRequest, handlePaseoWorktreeArchiveRequest as handleWorktreeArchiveRequest, handlePaseoWorktreeListRequest as handleWorktreeListRequest, handleWorkspaceSetupStatusRequest as handleWorkspaceSetupStatusRequestMessage, } from "./worktree-session.js";
57
- import { killTerminalsUnderPath as killWorktreeTerminalsUnderPath } from "./paseo-worktree-archive-service.js";
61
+ import { assertSafeGitRef as assertWorktreeSafeGitRef, buildAgentSessionConfig as buildWorktreeAgentSessionConfig, createPaseoWorktreeWorkflow as createWorktreeWorkflow, handleCreatePaseoWorktreeRequest as handleCreateWorktreeRequest, handlePaseoWorktreeArchiveRequest as handleWorktreeArchiveRequest, handlePaseoWorktreeListRequest as handleWorktreeListRequest, handleWorkspaceSetupStatusRequest as handleWorkspaceSetupStatusRequestMessage, } from "./worktree-session.js";
58
62
  import { toWorktreeWireError } from "./worktree-errors.js";
59
63
  const MAX_INITIAL_AGENT_TITLE_CHARS = Math.min(60, MAX_EXPLICIT_AGENT_TITLE_CHARS);
60
64
  const WORKSPACE_GIT_WATCH_REMOVED_STATE_KEY = "__removed__";
65
+ async function resolveKnownProjectRootForConfig(input) {
66
+ const requestedRoot = canonicalizeConfigRoot(input.repoRoot);
67
+ const projects = await input.projectRegistry.list();
68
+ for (const project of projects) {
69
+ if (project.archivedAt !== null) {
70
+ continue;
71
+ }
72
+ const projectRoot = canonicalizeConfigRoot(project.rootPath);
73
+ if (requestedRoot === projectRoot) {
74
+ return projectRoot;
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ function canonicalizeConfigRoot(repoRoot) {
80
+ const resolved = resolve(repoRoot);
81
+ try {
82
+ return stripTrailingPathSeparators(realpathSync(resolved));
83
+ }
84
+ catch {
85
+ return stripTrailingPathSeparators(resolved);
86
+ }
87
+ }
88
+ function stripTrailingPathSeparators(path) {
89
+ let normalized = path;
90
+ while (normalized.length > 1 && normalized.endsWith(sep)) {
91
+ normalized = normalized.slice(0, -1);
92
+ }
93
+ return normalized;
94
+ }
61
95
  // TODO: Remove once all app store clients are on >=0.1.45 and understand arbitrary provider strings.
62
96
  // Clients before 0.1.45 validate providers with z.enum(["claude", "codex", "opencode"]) and reject
63
97
  // the entire session message if they encounter an unknown provider.
64
98
  const LEGACY_PROVIDER_IDS = new Set(["claude", "codex", "opencode"]);
65
99
  const MIN_VERSION_ALL_PROVIDERS = "0.1.45";
66
100
  const MIN_VERSION_FLEXIBLE_EDITOR_IDS = "0.1.50";
101
+ function errorToFriendlyMessage(error) {
102
+ if (error instanceof Error)
103
+ return error.message;
104
+ if (typeof error === "string")
105
+ return error;
106
+ return "Unknown error";
107
+ }
108
+ function resolveSubscriptionId(subscribe, requestedSubscriptionId) {
109
+ if (!subscribe)
110
+ return null;
111
+ if (requestedSubscriptionId && requestedSubscriptionId.length > 0) {
112
+ return requestedSubscriptionId;
113
+ }
114
+ return uuidv4();
115
+ }
116
+ function diffChangeTypeFor(file) {
117
+ if (file.isNew)
118
+ return "A";
119
+ if (file.isDeleted)
120
+ return "D";
121
+ return "M";
122
+ }
123
+ function buildWorkspaceCheckout(workspace, project) {
124
+ if (project.kind !== "git") {
125
+ return {
126
+ cwd: workspace.cwd,
127
+ isGit: false,
128
+ currentBranch: null,
129
+ remoteUrl: null,
130
+ worktreeRoot: null,
131
+ isPaseoOwnedWorktree: false,
132
+ mainRepoRoot: null,
133
+ };
134
+ }
135
+ if (workspace.kind === "worktree") {
136
+ return {
137
+ cwd: workspace.cwd,
138
+ isGit: true,
139
+ currentBranch: workspace.displayName,
140
+ remoteUrl: null,
141
+ worktreeRoot: workspace.cwd,
142
+ isPaseoOwnedWorktree: true,
143
+ mainRepoRoot: project.rootPath,
144
+ };
145
+ }
146
+ return {
147
+ cwd: workspace.cwd,
148
+ isGit: true,
149
+ currentBranch: workspace.displayName,
150
+ remoteUrl: null,
151
+ worktreeRoot: workspace.cwd,
152
+ isPaseoOwnedWorktree: false,
153
+ mainRepoRoot: null,
154
+ };
155
+ }
67
156
  function isAppVersionAtLeast(appVersion, minVersion) {
68
157
  if (!appVersion)
69
158
  return false;
@@ -87,7 +176,6 @@ function clientSupportsAllProviders(appVersion) {
87
176
  function clientSupportsFlexibleEditorIds(appVersion) {
88
177
  return isAppVersionAtLeast(appVersion, MIN_VERSION_FLEXIBLE_EDITOR_IDS);
89
178
  }
90
- const MAX_TERMINAL_STREAM_SLOTS = 256;
91
179
  function beginAgentDeleteIfSupported(agentStorage, agentId) {
92
180
  if ("beginDelete" in agentStorage && typeof agentStorage.beginDelete === "function") {
93
181
  agentStorage.beginDelete(agentId);
@@ -119,6 +207,19 @@ export function resolveCreateAgentTitles(options) {
119
207
  provisionalTitle,
120
208
  };
121
209
  }
210
+ function getFirstUserMessageText(timeline) {
211
+ for (const item of timeline) {
212
+ if (item.type !== "user_message") {
213
+ continue;
214
+ }
215
+ const text = item.text.trim();
216
+ if (text) {
217
+ return text;
218
+ }
219
+ }
220
+ return null;
221
+ }
222
+ const FETCH_AGENTS_SORT_KEYS = ["status_priority", "created_at", "updated_at", "title"];
122
223
  export function resolveWaitForFinishError(options) {
123
224
  if (options.status !== "error") {
124
225
  return null;
@@ -126,27 +227,6 @@ export function resolveWaitForFinishError(options) {
126
227
  const message = options.final?.lastError;
127
228
  return typeof message === "string" && message.trim().length > 0 ? message : "Agent failed";
128
229
  }
129
- function summarizeFetchWorkspacesEntries(entries) {
130
- const workspaces = Array.from(entries, (entry) => ({
131
- id: entry.id,
132
- projectId: entry.projectId,
133
- projectDisplayName: entry.projectDisplayName,
134
- name: entry.name,
135
- status: entry.status,
136
- workspaceKind: entry.workspaceKind,
137
- activityAt: entry.activityAt,
138
- }));
139
- const statusCounts = new Map();
140
- for (const workspace of workspaces) {
141
- statusCounts.set(workspace.status, (statusCounts.get(workspace.status) ?? 0) + 1);
142
- }
143
- return {
144
- count: workspaces.length,
145
- projectIds: [...new Set(workspaces.map((workspace) => workspace.projectId))],
146
- statusCounts: Object.fromEntries(statusCounts),
147
- workspaces,
148
- };
149
- }
150
230
  class SessionRequestError extends Error {
151
231
  constructor(code, message) {
152
232
  super(message);
@@ -173,6 +253,31 @@ class VoiceFeatureUnavailableError extends Error {
173
253
  this.missingModelIds = [...context.missingModelIds];
174
254
  }
175
255
  }
256
+ function buildImportPersistenceHandle(input) {
257
+ const cwd = input.cwd ?? process.cwd();
258
+ return {
259
+ provider: input.provider,
260
+ sessionId: input.sessionId,
261
+ nativeHandle: input.sessionId,
262
+ metadata: {
263
+ provider: input.provider,
264
+ cwd,
265
+ },
266
+ };
267
+ }
268
+ function applyImportCwdOverride(handle, cwd) {
269
+ if (!cwd) {
270
+ return handle;
271
+ }
272
+ return {
273
+ ...handle,
274
+ metadata: {
275
+ ...handle.metadata,
276
+ provider: handle.provider,
277
+ cwd,
278
+ },
279
+ };
280
+ }
176
281
  function convertPCMToWavBuffer(pcmBuffer, sampleRate, channels, bitsPerSample) {
177
282
  const headerSize = 44;
178
283
  const wavBuffer = Buffer.alloc(headerSize + pcmBuffer.length);
@@ -194,6 +299,13 @@ function convertPCMToWavBuffer(pcmBuffer, sampleRate, channels, bitsPerSample) {
194
299
  pcmBuffer.copy(wavBuffer, 44);
195
300
  return wavBuffer;
196
301
  }
302
+ function parseClientCapabilities(capabilities) {
303
+ if (!capabilities) {
304
+ return new Set();
305
+ }
306
+ const known = new Set(Object.values(CLIENT_CAPS));
307
+ return new Set(Object.entries(capabilities).flatMap(([key, value]) => value === true && known.has(key) ? [key] : []));
308
+ }
197
309
  /**
198
310
  * Session represents a single connected client session.
199
311
  * It owns all state management, orchestration logic, and message processing.
@@ -226,12 +338,6 @@ export class Session {
226
338
  this.clientActivity = null;
227
339
  this.MOBILE_BACKGROUND_STREAM_GRACE_MS = 60000;
228
340
  this.unsubscribeProviderSnapshotEvents = null;
229
- this.subscribedTerminalDirectories = new Set();
230
- this.unsubscribeTerminalsChanged = null;
231
- this.terminalExitSubscriptions = new Map();
232
- this.activeTerminalStreams = new Map();
233
- this.terminalIdToSlot = new Map();
234
- this.nextTerminalSlot = 0;
235
341
  this.inflightRequests = 0;
236
342
  this.peakInflightRequests = 0;
237
343
  this.availableEditorTargetsCache = new TTLCache({
@@ -249,16 +355,28 @@ export class Session {
249
355
  this.workspaceGitSubscriptions = new Map();
250
356
  this.voiceModeAgentId = null;
251
357
  this.voiceModeBaseConfig = null;
252
- this.workspaceStatePriority = {
253
- needs_input: 0,
254
- failed: 1,
255
- running: 2,
256
- attention: 3,
257
- done: 4,
258
- };
259
- const { clientId, appVersion, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, tts, terminalManager, providerSnapshotManager, scriptRouteStore, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, voice, voiceBridge, dictation, agentProviderRuntimeSettings, providerOverrides, isDev, } = options;
358
+ this.agentsPager = new SortablePager({
359
+ validKeys: FETCH_AGENTS_SORT_KEYS,
360
+ defaultSort: [{ key: "updated_at", direction: "desc" }],
361
+ label: "fetch_agents",
362
+ getId: (agent) => agent.id,
363
+ getSortValue: (agent, key) => {
364
+ switch (key) {
365
+ case "status_priority":
366
+ return this.getStatusPriority(agent);
367
+ case "created_at":
368
+ return Date.parse(agent.createdAt);
369
+ case "updated_at":
370
+ return Date.parse(agent.updatedAt);
371
+ case "title":
372
+ return agent.title?.toLocaleLowerCase() ?? "";
373
+ }
374
+ },
375
+ });
376
+ const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, tts, terminalManager, providerSnapshotManager, scriptRouteStore, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, voice, voiceBridge, dictation, agentProviderRuntimeSettings, providerOverrides, isDev, } = options;
260
377
  this.clientId = clientId;
261
378
  this.appVersion = appVersion ?? null;
379
+ this.clientCapabilities = parseClientCapabilities(clientCapabilities);
262
380
  this.sessionId = uuidv4();
263
381
  this.onMessage = onMessage;
264
382
  this.onBinaryMessage = onBinaryMessage ?? null;
@@ -284,6 +402,14 @@ export class Session {
284
402
  this.daemonConfigStore = daemonConfigStore;
285
403
  this.mcpBaseUrl = mcpBaseUrl ?? null;
286
404
  this.terminalManager = terminalManager;
405
+ this.terminalController = new TerminalSessionController({
406
+ terminalManager,
407
+ emit: (msg) => this.emit(msg),
408
+ emitBinary: (frame) => this.emitBinary(frame),
409
+ hasBinaryChannel: () => this.onBinaryMessage !== null,
410
+ isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
411
+ sessionLogger: this.sessionLogger,
412
+ });
287
413
  this.providerSnapshotManager = providerSnapshotManager ?? null;
288
414
  this.scriptRouteStore = scriptRouteStore ?? null;
289
415
  this.scriptRuntimeStore = scriptRuntimeStore ?? null;
@@ -292,53 +418,21 @@ export class Session {
292
418
  this.getDaemonTcpPort = getDaemonTcpPort ?? null;
293
419
  this.getDaemonTcpHost = getDaemonTcpHost ?? null;
294
420
  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;
421
+ this.subscribeToOptionalManagers();
422
+ this.bindVoiceBridges({ voice, voiceBridge, dictation });
322
423
  this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
323
424
  this.providerOverrides = providerOverrides;
324
425
  this.isDev = isDev === true;
325
426
  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({
427
+ this.workspaceDirectory = new WorkspaceDirectory({
336
428
  logger: this.sessionLogger,
337
- sessionId: this.sessionId,
338
- emit: (msg) => this.handleDictationManagerMessage(msg),
339
- stt: dictation?.stt ?? null,
340
- finalTimeoutMs: dictation?.finalTimeoutMs,
429
+ projectRegistry: this.projectRegistry,
430
+ workspaceRegistry: this.workspaceRegistry,
431
+ listAgentPayloads: () => this.listAgentPayloads(),
432
+ isProviderVisibleToClient: (provider) => this.isProviderVisibleToClient(provider),
433
+ buildWorkspaceDescriptor: (input) => this.buildWorkspaceDescriptor(input),
341
434
  });
435
+ this.initializePerSessionManagers({ tts, stt, dictation });
342
436
  // Initialize agent MCP client asynchronously
343
437
  void this.initializeAgentMcp();
344
438
  this.subscribeToAgentEvents();
@@ -349,6 +443,12 @@ export class Session {
349
443
  this.appVersion = appVersion;
350
444
  }
351
445
  }
446
+ updateClientCapabilities(capabilities) {
447
+ this.clientCapabilities = parseClientCapabilities(capabilities);
448
+ }
449
+ supports(capability) {
450
+ return this.clientCapabilities.has(capability);
451
+ }
352
452
  async syncWorkspaceGitObserverForWorkspace(workspace) {
353
453
  const descriptor = await this.describeWorkspaceRecordWithGitData(workspace);
354
454
  this.syncWorkspaceGitObservers([descriptor]);
@@ -359,8 +459,17 @@ export class Session {
359
459
  async archiveWorkspaceRecordForExternalMutation(workspaceId) {
360
460
  await this.archiveWorkspaceRecord(workspaceId);
361
461
  }
462
+ markWorkspaceArchivingForExternalMutation(workspaceIds, archivingAt) {
463
+ this.markWorkspaceArchiving(workspaceIds, archivingAt);
464
+ }
465
+ clearWorkspaceArchivingForExternalMutation(workspaceIds) {
466
+ this.clearWorkspaceArchiving(workspaceIds);
467
+ }
468
+ async emitWorkspaceUpdatesForExternalWorkspaceIds(workspaceIds) {
469
+ await this.emitWorkspaceUpdatesForWorkspaceIds(workspaceIds);
470
+ }
362
471
  async emitWorkspaceUpdatesForExternalCwds(cwds) {
363
- await this.emitWorkspaceUpdatesForCwds(cwds);
472
+ await Promise.all(Array.from(cwds, (cwd) => this.emitWorkspaceUpdateForCwd(cwd)));
364
473
  }
365
474
  async warmWorkspaceGitDataForWorkspace(workspace) {
366
475
  await this.syncWorkspaceGitObserverForWorkspace(workspace);
@@ -373,9 +482,10 @@ export class Session {
373
482
  return this.clientActivity;
374
483
  }
375
484
  getRuntimeMetrics() {
485
+ const terminalMetrics = this.terminalController.getMetrics();
376
486
  return {
377
- terminalDirectorySubscriptionCount: this.subscribedTerminalDirectories.size,
378
- terminalSubscriptionCount: this.activeTerminalStreams.size,
487
+ terminalDirectorySubscriptionCount: terminalMetrics.directorySubscriptionCount,
488
+ terminalSubscriptionCount: terminalMetrics.streamSubscriptionCount,
379
489
  inflightRequests: this.inflightRequests,
380
490
  peakInflightRequests: this.peakInflightRequests,
381
491
  };
@@ -427,16 +537,11 @@ export class Session {
427
537
  return;
428
538
  }
429
539
  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;
540
+ const t0 = Date.now();
541
+ const cancelled = await this.agentManager.cancelAgentRun(agentId);
542
+ this.sessionLogger.debug({ agentId, cancelled, durationMs: Date.now() - t0 }, "interruptAgentIfRunning: cancelAgentRun completed");
543
+ if (!cancelled) {
544
+ this.sessionLogger.warn({ agentId }, "interruptAgentIfRunning: reported running but no active run was cancelled");
440
545
  }
441
546
  }
442
547
  hasActiveAgentRun(agentId) {
@@ -464,12 +569,7 @@ export class Session {
464
569
  }
465
570
  catch (error) {
466
571
  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 };
572
+ return { ok: false, error: errorToFriendlyMessage(error) };
473
573
  }
474
574
  void (async () => {
475
575
  try {
@@ -486,7 +586,7 @@ export class Session {
486
586
  return { ok: true };
487
587
  }
488
588
  handleAgentRunError(agentId, error, context) {
489
- const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
589
+ const message = errorToFriendlyMessage(error);
490
590
  this.sessionLogger.error({ err: error, agentId, context }, `${context} for agent ${agentId}`);
491
591
  this.emit({
492
592
  type: "activity_log",
@@ -522,6 +622,48 @@ export class Session {
522
622
  /**
523
623
  * Subscribe to AgentManager events and forward them to the client
524
624
  */
625
+ subscribeToOptionalManagers() {
626
+ this.terminalController.start();
627
+ if (this.providerSnapshotManager) {
628
+ const handleProviderSnapshotChange = (entries, cwd) => {
629
+ // COMPAT(providersSnapshot): keep provider visibility gating for older clients.
630
+ const visibleEntries = entries.filter((entry) => this.isProviderVisibleToClient(entry.provider));
631
+ this.emit({
632
+ type: "providers_snapshot_update",
633
+ payload: {
634
+ cwd,
635
+ entries: visibleEntries,
636
+ generatedAt: new Date().toISOString(),
637
+ },
638
+ });
639
+ };
640
+ this.providerSnapshotManager.on("change", handleProviderSnapshotChange);
641
+ this.unsubscribeProviderSnapshotEvents = () => {
642
+ this.providerSnapshotManager?.off("change", handleProviderSnapshotChange);
643
+ };
644
+ }
645
+ }
646
+ bindVoiceBridges(params) {
647
+ const { voice, voiceBridge, dictation } = params;
648
+ this.resolveVoiceTurnDetection = toResolver(voice?.turnDetection ?? null);
649
+ this.registerVoiceSpeakHandler = voiceBridge?.registerVoiceSpeakHandler;
650
+ this.unregisterVoiceSpeakHandler = voiceBridge?.unregisterVoiceSpeakHandler;
651
+ this.registerVoiceCallerContext = voiceBridge?.registerVoiceCallerContext;
652
+ this.unregisterVoiceCallerContext = voiceBridge?.unregisterVoiceCallerContext;
653
+ this.getSpeechReadiness = dictation?.getSpeechReadiness;
654
+ }
655
+ initializePerSessionManagers(params) {
656
+ const { tts, stt, dictation } = params;
657
+ this.ttsManager = new TTSManager(this.sessionId, this.sessionLogger, tts);
658
+ this.sttManager = new STTManager(this.sessionId, this.sessionLogger, stt);
659
+ this.dictationStreamManager = new DictationStreamManager({
660
+ logger: this.sessionLogger,
661
+ sessionId: this.sessionId,
662
+ emit: (msg) => this.handleDictationManagerMessage(msg),
663
+ stt: dictation?.stt ?? null,
664
+ finalTimeoutMs: dictation?.finalTimeoutMs,
665
+ });
666
+ }
525
667
  subscribeToAgentEvents() {
526
668
  if (this.unsubscribeAgentEvents) {
527
669
  this.unsubscribeAgentEvents();
@@ -618,8 +760,19 @@ export class Session {
618
760
  payload.archivedAt = storedRecord?.archivedAt ?? null;
619
761
  return payload;
620
762
  }
621
- buildStoredAgentPayload(record) {
622
- return buildStoredAgentPayload(record, this.providerRegistry, this.sessionLogger);
763
+ getProviderRegistry() {
764
+ return buildProviderRegistry(this.sessionLogger, {
765
+ runtimeSettings: this.agentProviderRuntimeSettings,
766
+ providerOverrides: applyMutableProviderConfigToOverrides(this.providerOverrides, this.daemonConfigStore.get().providers),
767
+ workspaceGitService: this.workspaceGitService,
768
+ isDev: this.isDev,
769
+ });
770
+ }
771
+ getRegisteredProviderIds() {
772
+ return Object.keys(this.getProviderRegistry());
773
+ }
774
+ buildStoredAgentPayload(record, registeredProviderIds = this.getRegisteredProviderIds()) {
775
+ return buildStoredAgentPayload(record, registeredProviderIds);
623
776
  }
624
777
  isProviderVisibleToClient(provider) {
625
778
  if (clientSupportsAllProviders(this.appVersion)) {
@@ -633,44 +786,34 @@ export class Session {
633
786
  }
634
787
  return editors.filter((editor) => isLegacyEditorTargetId(editor.id));
635
788
  }
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;
789
+ agentThinkingOptionMatchesFilter(agent, filter) {
790
+ if (filter.thinkingOptionId === undefined) {
791
+ return true;
647
792
  }
648
- if (filter?.thinkingOptionId !== undefined) {
649
- const expectedThinkingOptionId = resolveEffectiveThinkingOptionId({
650
- configuredThinkingOptionId: filter.thinkingOptionId ?? null,
793
+ const expectedThinkingOptionId = resolveEffectiveThinkingOptionId({
794
+ configuredThinkingOptionId: filter.thinkingOptionId ?? null,
795
+ });
796
+ const resolvedThinkingOptionId = agent.effectiveThinkingOptionId ??
797
+ resolveEffectiveThinkingOptionId({
798
+ runtimeInfo: agent.runtimeInfo,
799
+ configuredThinkingOptionId: agent.thinkingOptionId ?? null,
651
800
  });
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) {
801
+ return resolvedThinkingOptionId === expectedThinkingOptionId;
802
+ }
803
+ matchesAgentStructuralFilter(agent, project, filter) {
804
+ if (filter.statuses && filter.statuses.length > 0) {
662
805
  const statuses = new Set(filter.statuses);
663
806
  if (!statuses.has(agent.status)) {
664
807
  return false;
665
808
  }
666
809
  }
667
- if (typeof filter?.requiresAttention === "boolean") {
810
+ if (typeof filter.requiresAttention === "boolean") {
668
811
  const requiresAttention = agent.requiresAttention ?? false;
669
812
  if (requiresAttention !== filter.requiresAttention) {
670
813
  return false;
671
814
  }
672
815
  }
673
- if (filter?.projectKeys && filter.projectKeys.length > 0) {
816
+ if (filter.projectKeys && filter.projectKeys.length > 0) {
674
817
  const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
675
818
  if (projectKeys.size > 0 && !projectKeys.has(project.projectKey)) {
676
819
  return false;
@@ -678,6 +821,26 @@ export class Session {
678
821
  }
679
822
  return true;
680
823
  }
824
+ matchesAgentFilter(options) {
825
+ const { agent, project, filter } = options;
826
+ if (filter?.labels) {
827
+ const matchesLabels = Object.entries(filter.labels).every(([key, value]) => agent.labels[key] === value);
828
+ if (!matchesLabels) {
829
+ return false;
830
+ }
831
+ }
832
+ const includeArchived = filter?.includeArchived ?? false;
833
+ if (!includeArchived && agent.archivedAt) {
834
+ return false;
835
+ }
836
+ if (filter && !this.agentThinkingOptionMatchesFilter(agent, filter)) {
837
+ return false;
838
+ }
839
+ if (filter && !this.matchesAgentStructuralFilter(agent, project, filter)) {
840
+ return false;
841
+ }
842
+ return true;
843
+ }
681
844
  getAgentUpdateTargetId(update) {
682
845
  return update.kind === "remove" ? update.agentId : update.agent.id;
683
846
  }
@@ -724,54 +887,26 @@ export class Session {
724
887
  const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(normalizedCwd, workspaces);
725
888
  return workspaces.find((workspace) => workspace.workspaceId === workspaceId) ?? null;
726
889
  }
890
+ async findExactWorkspaceByDirectory(cwd, options) {
891
+ const normalizedCwd = await this.resolveWorkspaceDirectory(cwd, options);
892
+ const workspaces = await this.workspaceRegistry.list();
893
+ return workspaces.find((workspace) => workspace.cwd === normalizedCwd) ?? null;
894
+ }
727
895
  async resolveWorkspaceDirectory(cwd, options) {
728
896
  const normalizedCwd = normalizePersistedWorkspaceId(cwd);
729
897
  if (options?.refreshGit === false) {
730
898
  const snapshot = this.workspaceGitService.peekSnapshot(normalizedCwd);
731
899
  return normalizePersistedWorkspaceId(snapshot?.git.repoRoot ?? normalizedCwd);
732
900
  }
733
- try {
734
- const snapshot = await this.workspaceGitService.getSnapshot(normalizedCwd);
735
- return normalizePersistedWorkspaceId(snapshot.git.repoRoot ?? normalizedCwd);
736
- }
737
- catch {
738
- return normalizedCwd;
739
- }
901
+ const checkout = await this.workspaceGitService.getCheckout(normalizedCwd);
902
+ return normalizePersistedWorkspaceId(checkout.worktreeRoot ?? normalizedCwd);
740
903
  }
741
904
  async buildProjectPlacementForWorkspace(workspace, projectRecord) {
742
905
  const project = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
743
906
  if (!project) {
744
907
  throw new Error(`Project not found for workspace ${workspace.workspaceId}`);
745
908
  }
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
- };
909
+ const checkout = buildWorkspaceCheckout(workspace, project);
775
910
  return {
776
911
  projectKey: project.projectId,
777
912
  projectName: project.displayName,
@@ -851,360 +986,7 @@ export class Session {
851
986
  try {
852
987
  this.sessionLogger.trace({ messageType: msg.type, payloadBytes: JSON.stringify(msg).length }, "inbound message");
853
988
  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
- }
989
+ await this.dispatchInboundMessage(msg);
1208
990
  }
1209
991
  catch (error) {
1210
992
  const err = error instanceof Error ? error : new Error(String(error));
@@ -1241,43 +1023,402 @@ export class Session {
1241
1023
  this.inflightRequests--;
1242
1024
  }
1243
1025
  }
1244
- resetPeakInflight() {
1245
- this.peakInflightRequests = this.inflightRequests;
1026
+ async dispatchInboundMessage(msg) {
1027
+ const promise = this.dispatchVoiceAndControlMessage(msg) ??
1028
+ this.dispatchAgentLifecycleMessage(msg) ??
1029
+ this.dispatchAgentConfigMessage(msg) ??
1030
+ this.dispatchCheckoutMessage(msg) ??
1031
+ this.dispatchWorkspaceAndProjectMessage(msg) ??
1032
+ this.dispatchProviderMessage(msg) ??
1033
+ this.dispatchTerminalMessage(msg) ??
1034
+ this.dispatchChatScheduleLoopMessage(msg) ??
1035
+ this.dispatchMiscMessage(msg);
1036
+ if (promise)
1037
+ await promise;
1038
+ }
1039
+ dispatchVoiceAndControlMessage(msg) {
1040
+ switch (msg.type) {
1041
+ case "voice_audio_chunk":
1042
+ return this.handleAudioChunk(msg);
1043
+ case "abort_request":
1044
+ return this.handleAbort();
1045
+ case "audio_played":
1046
+ this.handleAudioPlayed(msg.id);
1047
+ return undefined;
1048
+ case "set_voice_mode":
1049
+ return this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
1050
+ case "dictation_stream_start":
1051
+ return this.handleDictationStreamStart(msg);
1052
+ case "dictation_stream_chunk":
1053
+ return this.dictationStreamManager.handleChunk({
1054
+ dictationId: msg.dictationId,
1055
+ seq: msg.seq,
1056
+ audioBase64: msg.audio,
1057
+ format: msg.format,
1058
+ });
1059
+ case "dictation_stream_finish":
1060
+ return this.dictationStreamManager.handleFinish(msg.dictationId, msg.finalSeq);
1061
+ case "dictation_stream_cancel":
1062
+ this.dictationStreamManager.handleCancel(msg.dictationId);
1063
+ return undefined;
1064
+ case "restart_server_request":
1065
+ return this.handleRestartServerRequest(msg.requestId, msg.reason);
1066
+ case "shutdown_server_request":
1067
+ return this.handleShutdownServerRequest(msg.requestId);
1068
+ case "client_heartbeat":
1069
+ this.handleClientHeartbeat(msg);
1070
+ return undefined;
1071
+ case "ping": {
1072
+ const now = Date.now();
1073
+ this.emit({
1074
+ type: "pong",
1075
+ payload: {
1076
+ requestId: msg.requestId,
1077
+ clientSentAt: msg.clientSentAt,
1078
+ serverReceivedAt: now,
1079
+ serverSentAt: now,
1080
+ },
1081
+ });
1082
+ return undefined;
1083
+ }
1084
+ default:
1085
+ return undefined;
1086
+ }
1246
1087
  }
1247
- handleBinaryFrame(frame) {
1248
- const activeStream = this.activeTerminalStreams.get(frame.slot);
1249
- if (!activeStream || !this.terminalManager) {
1088
+ async handleDictationStreamStart(msg) {
1089
+ const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
1090
+ if (unavailable) {
1091
+ this.emit({
1092
+ type: "dictation_stream_error",
1093
+ payload: {
1094
+ dictationId: msg.dictationId,
1095
+ error: unavailable.message,
1096
+ retryable: unavailable.retryable,
1097
+ reasonCode: unavailable.reasonCode,
1098
+ missingModelIds: unavailable.missingModelIds,
1099
+ },
1100
+ });
1250
1101
  return;
1251
1102
  }
1252
- const terminal = this.terminalManager.getTerminal(activeStream.terminalId);
1253
- if (!terminal) {
1254
- this.detachTerminalStream(activeStream.terminalId, { emitExit: true });
1103
+ await this.dictationStreamManager.handleStart(msg.dictationId, msg.format);
1104
+ }
1105
+ dispatchAgentLifecycleMessage(msg) {
1106
+ switch (msg.type) {
1107
+ case "fetch_agents_request":
1108
+ return this.handleFetchAgents(msg);
1109
+ case "fetch_agent_history_request":
1110
+ return this.handleFetchAgentHistory(msg);
1111
+ case "fetch_agent_request":
1112
+ return this.handleFetchAgent(msg.agentId, msg.requestId);
1113
+ case "delete_agent_request":
1114
+ return this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
1115
+ case "archive_agent_request":
1116
+ return this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
1117
+ case "close_items_request":
1118
+ return this.handleCloseItemsRequest(msg);
1119
+ case "update_agent_request":
1120
+ return this.handleUpdateAgentRequest(msg.agentId, msg.name, msg.labels, msg.requestId);
1121
+ case "send_agent_message_request":
1122
+ return this.handleSendAgentMessageRequest(msg);
1123
+ case "wait_for_finish_request":
1124
+ return this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
1125
+ case "create_agent_request":
1126
+ return this.handleCreateAgentRequest(msg);
1127
+ case "resume_agent_request":
1128
+ return this.handleResumeAgentRequest(msg);
1129
+ case "import_agent_request":
1130
+ return this.handleImportAgentRequest(msg);
1131
+ case "refresh_agent_request":
1132
+ return this.handleRefreshAgentRequest(msg);
1133
+ case "cancel_agent_request":
1134
+ return this.handleCancelAgentRequest(msg.agentId, msg.requestId);
1135
+ case "fetch_agent_timeline_request":
1136
+ return this.handleFetchAgentTimelineRequest(msg);
1137
+ case "agent_permission_response":
1138
+ return this.handleAgentPermissionResponse(msg.agentId, msg.requestId, msg.response);
1139
+ case "clear_agent_attention":
1140
+ return this.handleClearAgentAttention(msg.agentId, msg.requestId);
1141
+ default:
1142
+ return undefined;
1143
+ }
1144
+ }
1145
+ dispatchAgentConfigMessage(msg) {
1146
+ switch (msg.type) {
1147
+ case "set_agent_mode_request":
1148
+ return this.handleSetAgentModeRequest(msg.agentId, msg.modeId, msg.requestId);
1149
+ case "set_agent_model_request":
1150
+ return this.handleSetAgentModelRequest(msg.agentId, msg.modelId, msg.requestId);
1151
+ case "set_agent_feature_request":
1152
+ return this.handleSetAgentFeatureRequest(msg.agentId, msg.featureId, msg.value, msg.requestId);
1153
+ case "set_agent_thinking_request":
1154
+ return this.handleSetAgentThinkingRequest(msg.agentId, msg.thinkingOptionId, msg.requestId);
1155
+ case "get_daemon_config_request":
1156
+ this.emit({
1157
+ type: "get_daemon_config_response",
1158
+ payload: { requestId: msg.requestId, config: this.daemonConfigStore.get() },
1159
+ });
1160
+ return undefined;
1161
+ case "set_daemon_config_request":
1162
+ this.emit({
1163
+ type: "set_daemon_config_response",
1164
+ payload: {
1165
+ requestId: msg.requestId,
1166
+ config: this.daemonConfigStore.patch(msg.config),
1167
+ },
1168
+ });
1169
+ return undefined;
1170
+ case "read_project_config_request":
1171
+ return this.handleReadProjectConfigRequest(msg);
1172
+ case "write_project_config_request":
1173
+ return this.handleWriteProjectConfigRequest(msg);
1174
+ default:
1175
+ return undefined;
1176
+ }
1177
+ }
1178
+ async handleReadProjectConfigRequest(msg) {
1179
+ const repoRoot = await resolveKnownProjectRootForConfig({
1180
+ repoRoot: msg.repoRoot,
1181
+ projectRegistry: this.projectRegistry,
1182
+ });
1183
+ if (!repoRoot) {
1184
+ this.emitProjectConfigReadFailure(msg, { code: "project_not_found" });
1255
1185
  return;
1256
1186
  }
1257
- switch (frame.opcode) {
1258
- case TerminalStreamOpcode.Input: {
1259
- if (frame.payload.byteLength === 0) {
1260
- return;
1261
- }
1262
- const text = Buffer.from(frame.payload).toString("utf8");
1263
- if (!text) {
1264
- return;
1265
- }
1266
- terminal.send({ type: "input", data: text });
1267
- return;
1268
- }
1269
- case TerminalStreamOpcode.Resize: {
1270
- const resize = decodeTerminalResizePayload(frame.payload);
1271
- if (!resize) {
1272
- return;
1273
- }
1274
- terminal.send({ type: "resize", rows: resize.rows, cols: resize.cols });
1275
- return;
1276
- }
1187
+ const result = readPaseoConfigForEdit(repoRoot);
1188
+ if (!result.ok) {
1189
+ this.sessionLogger.warn({ repoRoot, requestId: msg.requestId, outcome: result.error.code }, "Failed to read project config");
1190
+ this.emitProjectConfigReadFailure(msg, result.error, repoRoot);
1191
+ return;
1192
+ }
1193
+ if (result.config === null) {
1194
+ this.sessionLogger.debug({ repoRoot, requestId: msg.requestId, outcome: "missing_project_config" }, "Project config missing");
1195
+ }
1196
+ this.emit({
1197
+ type: "read_project_config_response",
1198
+ payload: {
1199
+ requestId: msg.requestId,
1200
+ repoRoot,
1201
+ ok: true,
1202
+ config: result.config,
1203
+ revision: result.revision,
1204
+ },
1205
+ });
1206
+ }
1207
+ async handleWriteProjectConfigRequest(msg) {
1208
+ const repoRoot = await resolveKnownProjectRootForConfig({
1209
+ repoRoot: msg.repoRoot,
1210
+ projectRegistry: this.projectRegistry,
1211
+ });
1212
+ if (!repoRoot) {
1213
+ this.emitProjectConfigWriteFailure(msg, { code: "project_not_found" });
1214
+ return;
1215
+ }
1216
+ this.sessionLogger.debug({ repoRoot, requestId: msg.requestId, outcome: "write_attempt" }, "Writing project config");
1217
+ const result = writePaseoConfigForEdit({
1218
+ repoRoot,
1219
+ config: msg.config,
1220
+ expectedRevision: msg.expectedRevision,
1221
+ });
1222
+ if (!result.ok) {
1223
+ this.sessionLogger.debug({ repoRoot, requestId: msg.requestId, outcome: result.error.code }, "Project config write did not complete");
1224
+ this.emitProjectConfigWriteFailure(msg, result.error, repoRoot);
1225
+ return;
1226
+ }
1227
+ this.sessionLogger.debug({ repoRoot, requestId: msg.requestId, outcome: "written" }, "Project config written");
1228
+ this.emit({
1229
+ type: "write_project_config_response",
1230
+ payload: {
1231
+ requestId: msg.requestId,
1232
+ repoRoot,
1233
+ ok: true,
1234
+ config: result.config,
1235
+ revision: result.revision,
1236
+ },
1237
+ });
1238
+ }
1239
+ emitProjectConfigReadFailure(msg, error, repoRoot = msg.repoRoot) {
1240
+ this.emit({
1241
+ type: "read_project_config_response",
1242
+ payload: {
1243
+ requestId: msg.requestId,
1244
+ repoRoot,
1245
+ ok: false,
1246
+ error,
1247
+ },
1248
+ });
1249
+ }
1250
+ emitProjectConfigWriteFailure(msg, error, repoRoot = msg.repoRoot) {
1251
+ this.emit({
1252
+ type: "write_project_config_response",
1253
+ payload: {
1254
+ requestId: msg.requestId,
1255
+ repoRoot,
1256
+ ok: false,
1257
+ error,
1258
+ },
1259
+ });
1260
+ }
1261
+ dispatchCheckoutMessage(msg) {
1262
+ switch (msg.type) {
1263
+ case "checkout_status_request":
1264
+ return this.handleCheckoutStatusRequest(msg);
1265
+ case "validate_branch_request":
1266
+ return this.handleValidateBranchRequest(msg);
1267
+ case "branch_suggestions_request":
1268
+ return this.handleBranchSuggestionsRequest(msg);
1269
+ case "directory_suggestions_request":
1270
+ return this.handleDirectorySuggestionsRequest(msg);
1271
+ case "subscribe_checkout_diff_request":
1272
+ return this.handleSubscribeCheckoutDiffRequest(msg);
1273
+ case "unsubscribe_checkout_diff_request":
1274
+ this.handleUnsubscribeCheckoutDiffRequest(msg);
1275
+ return undefined;
1276
+ case "checkout_switch_branch_request":
1277
+ return this.handleCheckoutSwitchBranchRequest(msg);
1278
+ case "stash_save_request":
1279
+ return this.handleStashSaveRequest(msg);
1280
+ case "stash_pop_request":
1281
+ return this.handleStashPopRequest(msg);
1282
+ case "stash_list_request":
1283
+ return this.handleStashListRequest(msg);
1284
+ case "checkout_commit_request":
1285
+ return this.handleCheckoutCommitRequest(msg);
1286
+ case "checkout_merge_request":
1287
+ return this.handleCheckoutMergeRequest(msg);
1288
+ case "checkout_merge_from_base_request":
1289
+ return this.handleCheckoutMergeFromBaseRequest(msg);
1290
+ case "checkout_pull_request":
1291
+ return this.handleCheckoutPullRequest(msg);
1292
+ case "checkout_push_request":
1293
+ return this.handleCheckoutPushRequest(msg);
1294
+ case "checkout_pr_create_request":
1295
+ return this.handleCheckoutPrCreateRequest(msg);
1296
+ case "checkout_pr_status_request":
1297
+ return this.handleCheckoutPrStatusRequest(msg);
1298
+ case "pull_request_timeline_request":
1299
+ return this.handlePullRequestTimelineRequest(msg);
1300
+ case "github_search_request":
1301
+ return this.handleGitHubSearchRequest(msg);
1302
+ default:
1303
+ return undefined;
1304
+ }
1305
+ }
1306
+ dispatchWorkspaceAndProjectMessage(msg) {
1307
+ switch (msg.type) {
1308
+ case "fetch_workspaces_request":
1309
+ return this.handleFetchWorkspacesRequest(msg);
1310
+ case "paseo_worktree_list_request":
1311
+ return this.handlePaseoWorktreeListRequest(msg);
1312
+ case "paseo_worktree_archive_request":
1313
+ return this.handlePaseoWorktreeArchiveRequest(msg);
1314
+ case "create_paseo_worktree_request":
1315
+ return this.handleCreatePaseoWorktreeRequest(msg);
1316
+ case "workspace_setup_status_request":
1317
+ return this.handleWorkspaceSetupStatusRequest(msg);
1318
+ case "list_available_editors_request":
1319
+ return this.handleListAvailableEditorsRequest(msg);
1320
+ case "open_in_editor_request":
1321
+ return this.handleOpenInEditorRequest(msg);
1322
+ case "open_project_request":
1323
+ return this.handleOpenProjectRequest(msg);
1324
+ case "archive_workspace_request":
1325
+ return this.handleArchiveWorkspaceRequest(msg);
1326
+ case "file_explorer_request":
1327
+ return this.handleFileExplorerRequest(msg);
1328
+ case "project_icon_request":
1329
+ return this.handleProjectIconRequest(msg);
1330
+ case "file_download_token_request":
1331
+ return this.handleFileDownloadTokenRequest(msg);
1277
1332
  default:
1333
+ return undefined;
1334
+ }
1335
+ }
1336
+ dispatchProviderMessage(msg) {
1337
+ switch (msg.type) {
1338
+ case "list_provider_models_request":
1339
+ return this.handleListProviderModelsRequest(msg);
1340
+ case "list_provider_modes_request":
1341
+ return this.handleListProviderModesRequest(msg);
1342
+ case "list_provider_features_request":
1343
+ return this.handleListProviderFeaturesRequest(msg);
1344
+ case "list_available_providers_request":
1345
+ return this.handleListAvailableProvidersRequest(msg);
1346
+ case "get_providers_snapshot_request":
1347
+ return this.handleGetProvidersSnapshotRequest(msg);
1348
+ case "refresh_providers_snapshot_request":
1349
+ return this.handleRefreshProvidersSnapshotRequest(msg);
1350
+ case "provider_diagnostic_request":
1351
+ return this.handleProviderDiagnosticRequest(msg);
1352
+ default:
1353
+ return undefined;
1354
+ }
1355
+ }
1356
+ dispatchTerminalMessage(msg) {
1357
+ if (msg.type === "start_workspace_script_request") {
1358
+ return this.handleStartWorkspaceScriptRequest(msg);
1359
+ }
1360
+ return this.terminalController.dispatch(msg);
1361
+ }
1362
+ dispatchChatScheduleLoopMessage(msg) {
1363
+ switch (msg.type) {
1364
+ case "chat/create":
1365
+ return this.handleChatCreateRequest(msg);
1366
+ case "chat/list":
1367
+ return this.handleChatListRequest(msg);
1368
+ case "chat/inspect":
1369
+ return this.handleChatInspectRequest(msg);
1370
+ case "chat/delete":
1371
+ return this.handleChatDeleteRequest(msg);
1372
+ case "chat/post":
1373
+ return this.handleChatPostRequest(msg);
1374
+ case "chat/read":
1375
+ return this.handleChatReadRequest(msg);
1376
+ case "chat/wait":
1377
+ return this.handleChatWaitRequest(msg);
1378
+ case "schedule/create":
1379
+ return this.handleScheduleCreateRequest(msg);
1380
+ case "schedule/list":
1381
+ return this.handleScheduleListRequest(msg);
1382
+ case "schedule/inspect":
1383
+ return this.handleScheduleInspectRequest(msg);
1384
+ case "schedule/logs":
1385
+ return this.handleScheduleLogsRequest(msg);
1386
+ case "schedule/pause":
1387
+ return this.handleSchedulePauseRequest(msg);
1388
+ case "schedule/resume":
1389
+ return this.handleScheduleResumeRequest(msg);
1390
+ case "schedule/delete":
1391
+ return this.handleScheduleDeleteRequest(msg);
1392
+ case "loop/run":
1393
+ return this.handleLoopRunRequest(msg);
1394
+ case "loop/list":
1395
+ return this.handleLoopListRequest(msg);
1396
+ case "loop/inspect":
1397
+ return this.handleLoopInspectRequest(msg);
1398
+ case "loop/logs":
1399
+ return this.handleLoopLogsRequest(msg);
1400
+ case "loop/stop":
1401
+ return this.handleLoopStopRequest(msg);
1402
+ default:
1403
+ return undefined;
1404
+ }
1405
+ }
1406
+ async dispatchMiscMessage(msg) {
1407
+ switch (msg.type) {
1408
+ case "list_commands_request":
1409
+ await this.handleListCommandsRequest(msg);
1410
+ return;
1411
+ case "register_push_token":
1412
+ this.handleRegisterPushToken(msg.token);
1278
1413
  return;
1279
1414
  }
1280
1415
  }
1416
+ resetPeakInflight() {
1417
+ this.peakInflightRequests = this.inflightRequests;
1418
+ }
1419
+ handleBinaryFrame(frame) {
1420
+ this.terminalController.handleBinaryFrame(frame);
1421
+ }
1281
1422
  async handleRestartServerRequest(requestId, reason) {
1282
1423
  const payload = {
1283
1424
  status: "restart_requested",
@@ -1441,19 +1582,21 @@ export class Session {
1441
1582
  return { agentId, archivedAt: archivedRecord.archivedAt };
1442
1583
  }
1443
1584
  async handleCloseItemsRequest(msg) {
1585
+ const archiveResults = await Promise.allSettled(msg.agentIds.map((agentId) => this.archiveAgentForClose(agentId)));
1444
1586
  const agents = [];
1445
- for (const agentId of msg.agentIds) {
1446
- try {
1447
- agents.push(await this.archiveAgentForClose(agentId));
1587
+ for (let i = 0; i < archiveResults.length; i += 1) {
1588
+ const result = archiveResults[i];
1589
+ if (result.status === "fulfilled") {
1590
+ agents.push(result.value);
1448
1591
  }
1449
- catch (error) {
1450
- this.sessionLogger.warn({ err: error, agentId, requestId: msg.requestId }, "Failed to archive agent during close_items batch");
1592
+ else {
1593
+ this.sessionLogger.warn({ err: result.reason, agentId: msg.agentIds[i], requestId: msg.requestId }, "Failed to archive agent during close_items batch");
1451
1594
  }
1452
1595
  }
1453
1596
  const terminals = [];
1454
1597
  for (const terminalId of msg.terminalIds) {
1455
1598
  try {
1456
- terminals.push(this.killTerminalForClose(terminalId));
1599
+ terminals.push(this.terminalController.killTerminalForClose(terminalId));
1457
1600
  }
1458
1601
  catch (error) {
1459
1602
  this.sessionLogger.warn({ err: error, terminalId, requestId: msg.requestId }, "Failed to kill terminal during close_items batch");
@@ -1529,7 +1672,9 @@ export class Session {
1529
1672
  requestId,
1530
1673
  agentId,
1531
1674
  accepted: false,
1532
- error: error?.message ? String(error.message) : "Failed to update agent",
1675
+ error: error?.message
1676
+ ? String(error.message)
1677
+ : "Failed to update agent",
1533
1678
  },
1534
1679
  });
1535
1680
  }
@@ -1882,14 +2027,22 @@ export class Session {
1882
2027
  ...config,
1883
2028
  ...(provisionalTitle ? { title: provisionalTitle } : {}),
1884
2029
  };
1885
- const { sessionConfig, worktreeBootstrap } = await this.buildAgentSessionConfig(resolvedConfig, git, worktreeName, attachments);
1886
- const resolvedWorkspace = msg.workspaceId
2030
+ const firstAgentContext = {
2031
+ ...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
2032
+ ...(attachments && attachments.length > 0 ? { attachments } : {}),
2033
+ };
2034
+ const { sessionConfig, setupContinuation } = await this.buildAgentSessionConfig(resolvedConfig, git, worktreeName, firstAgentContext);
2035
+ let resolvedWorkspace = msg.workspaceId
1887
2036
  ? await this.workspaceRegistry.get(msg.workspaceId)
1888
2037
  : ((await this.findWorkspaceByDirectory(sessionConfig.cwd)) ??
1889
2038
  (await this.findOrCreateWorkspaceForDirectory(sessionConfig.cwd)));
1890
2039
  if (!resolvedWorkspace) {
1891
2040
  throw new Error(`Workspace not found: ${msg.workspaceId}`);
1892
2041
  }
2042
+ resolvedWorkspace = await this.maybeAutoNameWorkspaceBranchForFirstAgent({
2043
+ workspace: resolvedWorkspace,
2044
+ firstAgentContext,
2045
+ });
1893
2046
  const snapshot = await this.agentManager.createAgent({
1894
2047
  ...sessionConfig,
1895
2048
  cwd: resolvedWorkspace.cwd,
@@ -1899,24 +2052,15 @@ export class Session {
1899
2052
  initialPrompt: trimmedPrompt,
1900
2053
  });
1901
2054
  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
- }
2055
+ await this.sendInitialCreateAgentPrompt({
2056
+ snapshot,
2057
+ trimmedPrompt,
2058
+ images,
2059
+ attachments,
2060
+ clientMessageId,
2061
+ outputSchema,
2062
+ explicitTitle,
2063
+ });
1920
2064
  if (requestId) {
1921
2065
  const agentPayload = await this.buildAgentPayload(snapshot);
1922
2066
  this.emit({
@@ -1929,25 +2073,9 @@ export class Session {
1929
2073
  },
1930
2074
  });
1931
2075
  }
1932
- if (worktreeBootstrap) {
1933
- void runAsyncWorktreeBootstrap({
1934
- agentId: snapshot.id,
1935
- worktree: worktreeBootstrap.worktree,
1936
- shouldBootstrap: worktreeBootstrap.shouldBootstrap,
1937
- terminalManager: this.terminalManager,
1938
- appendTimelineItem: (item) => appendTimelineItemIfAgentKnown({
1939
- agentManager: this.agentManager,
1940
- agentId: snapshot.id,
1941
- item,
1942
- }),
1943
- emitLiveTimelineItem: (item) => emitLiveTimelineItemIfAgentKnown({
1944
- agentManager: this.agentManager,
1945
- agentId: snapshot.id,
1946
- item,
1947
- }),
1948
- logger: this.sessionLogger,
1949
- });
1950
- }
2076
+ setupContinuation?.startAfterAgentCreate({
2077
+ agentId: snapshot.id,
2078
+ });
1951
2079
  this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
1952
2080
  }
1953
2081
  catch (error) {
@@ -1975,6 +2103,28 @@ export class Session {
1975
2103
  });
1976
2104
  }
1977
2105
  }
2106
+ async sendInitialCreateAgentPrompt(params) {
2107
+ const { snapshot, trimmedPrompt, images, attachments, clientMessageId, outputSchema } = params;
2108
+ const hasPrompt = Boolean(trimmedPrompt);
2109
+ const hasImages = (images?.length ?? 0) > 0;
2110
+ const hasAttachments = (attachments?.length ?? 0) > 0;
2111
+ if (!hasPrompt && !hasImages && !hasAttachments) {
2112
+ return;
2113
+ }
2114
+ scheduleAgentMetadataGeneration({
2115
+ agentManager: this.agentManager,
2116
+ agentId: snapshot.id,
2117
+ cwd: snapshot.cwd,
2118
+ initialPrompt: trimmedPrompt,
2119
+ explicitTitle: params.explicitTitle,
2120
+ paseoHome: this.paseoHome,
2121
+ logger: this.sessionLogger,
2122
+ });
2123
+ const started = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt || "", resolveClientMessageId(clientMessageId), images, attachments, outputSchema ? { outputSchema } : undefined);
2124
+ if (!started.ok) {
2125
+ throw new Error(started.error);
2126
+ }
2127
+ }
1978
2128
  async handleResumeAgentRequest(msg) {
1979
2129
  const { handle, overrides, requestId } = msg;
1980
2130
  if (!handle) {
@@ -2025,6 +2175,83 @@ export class Session {
2025
2175
  });
2026
2176
  }
2027
2177
  }
2178
+ async handleImportAgentRequest(msg) {
2179
+ const { provider, sessionId, cwd, labels, requestId } = msg;
2180
+ this.sessionLogger.info({ sessionId, provider }, `Importing agent ${sessionId} (${provider})`);
2181
+ try {
2182
+ const descriptor = await this.agentManager.findPersistedAgent(provider, sessionId);
2183
+ if (!descriptor && provider === "opencode" && !cwd) {
2184
+ throw new Error("OpenCode sessions require --cwd when the session cannot be found in persisted agents");
2185
+ }
2186
+ const handle = descriptor
2187
+ ? applyImportCwdOverride(descriptor.persistence, cwd)
2188
+ : buildImportPersistenceHandle({ provider, sessionId, cwd });
2189
+ const overrides = cwd ? { cwd } : undefined;
2190
+ await this.unarchiveAgentByHandle(handle);
2191
+ const snapshot = await this.agentManager.resumeAgentFromPersistence(handle, overrides, undefined, {
2192
+ labels,
2193
+ });
2194
+ await unarchiveAgentState(this.agentStorage, this.agentManager, snapshot.id);
2195
+ await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
2196
+ await this.applyImportedAgentTitle(snapshot);
2197
+ await this.forwardAgentUpdate(snapshot);
2198
+ const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
2199
+ const agentPayload = await this.buildAgentPayload(snapshot);
2200
+ this.emit({
2201
+ type: "status",
2202
+ payload: {
2203
+ status: "agent_resumed",
2204
+ agentId: snapshot.id,
2205
+ requestId,
2206
+ timelineSize,
2207
+ agent: agentPayload,
2208
+ },
2209
+ });
2210
+ }
2211
+ catch (error) {
2212
+ const message = error instanceof Error ? error.message : String(error);
2213
+ this.sessionLogger.error({ err: error }, "Failed to import agent");
2214
+ this.emit({
2215
+ type: "status",
2216
+ payload: {
2217
+ status: "agent_create_failed",
2218
+ requestId,
2219
+ error: message,
2220
+ },
2221
+ });
2222
+ this.emit({
2223
+ type: "activity_log",
2224
+ payload: {
2225
+ id: uuidv4(),
2226
+ timestamp: new Date(),
2227
+ type: "error",
2228
+ content: `Failed to import agent: ${message}`,
2229
+ },
2230
+ });
2231
+ }
2232
+ }
2233
+ async applyImportedAgentTitle(snapshot) {
2234
+ const initialPrompt = getFirstUserMessageText(this.agentManager.getTimeline(snapshot.id));
2235
+ if (!initialPrompt) {
2236
+ return;
2237
+ }
2238
+ const { explicitTitle, provisionalTitle } = resolveCreateAgentTitles({
2239
+ configTitle: snapshot.config.title,
2240
+ initialPrompt,
2241
+ });
2242
+ if (!explicitTitle && provisionalTitle) {
2243
+ await this.agentManager.setTitle(snapshot.id, provisionalTitle);
2244
+ }
2245
+ scheduleAgentMetadataGeneration({
2246
+ agentManager: this.agentManager,
2247
+ agentId: snapshot.id,
2248
+ cwd: snapshot.cwd,
2249
+ initialPrompt,
2250
+ explicitTitle,
2251
+ paseoHome: this.paseoHome,
2252
+ logger: this.sessionLogger,
2253
+ });
2254
+ }
2028
2255
  async handleRefreshAgentRequest(msg) {
2029
2256
  const { agentId, requestId } = msg;
2030
2257
  this.sessionLogger.info({ agentId }, `Refreshing agent ${agentId} from persistence`);
@@ -2041,7 +2268,11 @@ export class Session {
2041
2268
  if (!record) {
2042
2269
  throw new Error(`Agent not found: ${agentId}`);
2043
2270
  }
2044
- const handle = toAgentPersistenceHandle(this.sessionLogger, this.providerRegistry, record.persistence);
2271
+ const providerRegistry = this.getProviderRegistry();
2272
+ if (!isStoredAgentProviderAvailable(record, Object.keys(providerRegistry))) {
2273
+ throw new Error(`Agent ${agentId} references unavailable provider '${record.provider}'`);
2274
+ }
2275
+ const handle = toAgentPersistenceHandle(providerRegistry, record.persistence);
2045
2276
  if (!handle) {
2046
2277
  throw new Error(`Agent ${agentId} cannot be refreshed because it lacks persistence`);
2047
2278
  }
@@ -2096,16 +2327,67 @@ export class Session {
2096
2327
  this.handleAgentRunError(agentId, error, "Failed to cancel running agent on request");
2097
2328
  }
2098
2329
  }
2099
- async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, attachments) {
2330
+ async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, firstAgentContext) {
2100
2331
  return buildWorktreeAgentSessionConfig({
2101
2332
  paseoHome: this.paseoHome,
2102
2333
  sessionLogger: this.sessionLogger,
2103
2334
  workspaceGitService: this.workspaceGitService,
2104
- createPaseoWorktree: (input, serviceOptions) => this.createPaseoWorktree(input, serviceOptions),
2335
+ createPaseoWorktree: (input, serviceOptions) => this.createPaseoWorktreeWorkflow(input, {
2336
+ ...serviceOptions,
2337
+ setupContinuation: {
2338
+ kind: "agent",
2339
+ terminalManager: this.terminalManager,
2340
+ appendTimelineItem: ({ agentId, item }) => appendTimelineItemIfAgentKnown({
2341
+ agentManager: this.agentManager,
2342
+ agentId,
2343
+ item,
2344
+ }),
2345
+ emitLiveTimelineItem: ({ agentId, item }) => emitLiveTimelineItemIfAgentKnown({
2346
+ agentManager: this.agentManager,
2347
+ agentId,
2348
+ item,
2349
+ }),
2350
+ logger: this.sessionLogger,
2351
+ },
2352
+ }),
2105
2353
  checkoutExistingBranch: (cwd, branch) => this.checkoutExistingBranch(cwd, branch),
2106
2354
  createBranchFromBase: (params) => this.createBranchFromBase(params),
2107
2355
  github: this.github,
2108
- }, config, gitOptions, legacyWorktreeName, attachments);
2356
+ }, config, gitOptions, legacyWorktreeName, firstAgentContext);
2357
+ }
2358
+ async maybeAutoNameWorkspaceBranchForFirstAgent(input) {
2359
+ const coreDeps = createWorktreeCoreDeps(this.github);
2360
+ const result = await attemptFirstAgentBranchAutoName({
2361
+ cwd: input.workspace.cwd,
2362
+ firstAgentContext: input.firstAgentContext,
2363
+ generateBranchName: coreDeps.generateBranchName,
2364
+ });
2365
+ if (!result.renamed || !result.branchName) {
2366
+ return input.workspace;
2367
+ }
2368
+ const updatedWorkspace = {
2369
+ ...input.workspace,
2370
+ displayName: result.branchName,
2371
+ updatedAt: new Date().toISOString(),
2372
+ };
2373
+ await this.workspaceRegistry.upsert(updatedWorkspace);
2374
+ await this.notifyGitMutation(input.workspace.cwd, "rename-branch");
2375
+ await this.emitWorkspaceUpdateForCwd(input.workspace.cwd);
2376
+ return updatedWorkspace;
2377
+ }
2378
+ emitProviderDisabledResponse(kind, provider, requestId, fetchedAt) {
2379
+ const payload = {
2380
+ provider,
2381
+ error: `Provider ${provider} is disabled`,
2382
+ fetchedAt,
2383
+ requestId,
2384
+ };
2385
+ if (kind === "models") {
2386
+ this.emit({ type: "list_provider_models_response", payload });
2387
+ }
2388
+ else {
2389
+ this.emit({ type: "list_provider_modes_response", payload });
2390
+ }
2109
2391
  }
2110
2392
  async handleListProviderModelsRequest(msg) {
2111
2393
  const cwd = resolveSnapshotCwd(msg.cwd ? expandTilde(msg.cwd) : undefined);
@@ -2113,7 +2395,12 @@ export class Session {
2113
2395
  const manager = this.providerSnapshotManager;
2114
2396
  if (!manager) {
2115
2397
  try {
2116
- const models = await this.providerRegistry[msg.provider].fetchModels({
2398
+ const definition = this.getProviderRegistry()[msg.provider];
2399
+ if (!definition.enabled) {
2400
+ this.emitProviderDisabledResponse("models", msg.provider, msg.requestId, fetchedAt);
2401
+ return;
2402
+ }
2403
+ const models = await definition.fetchModels({
2117
2404
  cwd,
2118
2405
  force: false,
2119
2406
  });
@@ -2155,6 +2442,10 @@ export class Session {
2155
2442
  });
2156
2443
  return;
2157
2444
  }
2445
+ if (!entry.enabled) {
2446
+ this.emitProviderDisabledResponse("models", msg.provider, msg.requestId, fetchedAt);
2447
+ return;
2448
+ }
2158
2449
  if (entry.status === "ready") {
2159
2450
  this.emit({
2160
2451
  type: "list_provider_models_response",
@@ -2199,6 +2490,10 @@ export class Session {
2199
2490
  });
2200
2491
  return;
2201
2492
  }
2493
+ if (!entry.enabled) {
2494
+ this.emitProviderDisabledResponse("modes", msg.provider, msg.requestId, fetchedAt);
2495
+ return;
2496
+ }
2202
2497
  if (entry.status === "ready") {
2203
2498
  this.emit({
2204
2499
  type: "list_provider_modes_response",
@@ -2227,7 +2522,12 @@ export class Session {
2227
2522
  return;
2228
2523
  }
2229
2524
  try {
2230
- const modes = await this.providerRegistry[msg.provider].fetchModes({
2525
+ const definition = this.getProviderRegistry()[msg.provider];
2526
+ if (!definition.enabled) {
2527
+ this.emitProviderDisabledResponse("modes", msg.provider, msg.requestId, fetchedAt);
2528
+ return;
2529
+ }
2530
+ const modes = await definition.fetchModes({
2231
2531
  cwd,
2232
2532
  force: false,
2233
2533
  });
@@ -2262,6 +2562,9 @@ export class Session {
2262
2562
  }
2263
2563
  const findEntry = () => manager.getSnapshot(cwd).find((candidate) => candidate.provider === provider);
2264
2564
  let entry = findEntry();
2565
+ if (entry && !entry.enabled) {
2566
+ return entry;
2567
+ }
2265
2568
  if (!entry || entry.status === "loading") {
2266
2569
  // Awaits the in-flight warmup (deduped per-cwd) so old clients still get
2267
2570
  // a resolved answer rather than a loading placeholder.
@@ -2374,7 +2677,7 @@ export class Session {
2374
2677
  }
2375
2678
  async handleProviderDiagnosticRequest(msg) {
2376
2679
  try {
2377
- const client = this.providerRegistry[msg.provider].createClient(this.sessionLogger);
2680
+ const client = this.getProviderRegistry()[msg.provider].createClient(this.sessionLogger);
2378
2681
  const diagnostic = client.getDiagnostic
2379
2682
  ? (await client.getDiagnostic()).diagnostic
2380
2683
  : "No diagnostic available for this provider.";
@@ -2431,7 +2734,7 @@ export class Session {
2431
2734
  ? [
2432
2735
  "Files changed:",
2433
2736
  ...diff.structured.map((file) => {
2434
- const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M";
2737
+ const changeType = diffChangeTypeFor(file);
2435
2738
  const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
2436
2739
  return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
2437
2740
  }),
@@ -2487,7 +2790,7 @@ export class Session {
2487
2790
  ? [
2488
2791
  "Files changed:",
2489
2792
  ...diff.structured.map((file) => {
2490
- const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M";
2793
+ const changeType = diffChangeTypeFor(file);
2491
2794
  const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
2492
2795
  return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
2493
2796
  }),
@@ -2543,7 +2846,9 @@ export class Session {
2543
2846
  return snapshot.git.isDirty === true;
2544
2847
  }
2545
2848
  catch (error) {
2546
- throw new Error(`Unable to inspect git status for ${cwd}: ${error.message}`);
2849
+ throw new Error(`Unable to inspect git status for ${cwd}: ${error.message}`, {
2850
+ cause: error,
2851
+ });
2547
2852
  }
2548
2853
  }
2549
2854
  async checkoutExistingBranch(cwd, branch) {
@@ -2623,7 +2928,9 @@ export class Session {
2623
2928
  requestId,
2624
2929
  agentId,
2625
2930
  accepted: false,
2626
- error: error?.message ? String(error.message) : "Failed to set agent mode",
2931
+ error: error?.message
2932
+ ? String(error.message)
2933
+ : "Failed to set agent mode",
2627
2934
  },
2628
2935
  });
2629
2936
  }
@@ -2655,7 +2962,9 @@ export class Session {
2655
2962
  requestId,
2656
2963
  agentId,
2657
2964
  accepted: false,
2658
- error: error?.message ? String(error.message) : "Failed to set agent model",
2965
+ error: error?.message
2966
+ ? String(error.message)
2967
+ : "Failed to set agent model",
2659
2968
  },
2660
2969
  });
2661
2970
  }
@@ -2687,7 +2996,9 @@ export class Session {
2687
2996
  requestId,
2688
2997
  agentId,
2689
2998
  accepted: false,
2690
- error: error?.message ? String(error.message) : "Failed to set agent feature",
2999
+ error: error?.message
3000
+ ? String(error.message)
3001
+ : "Failed to set agent feature",
2691
3002
  },
2692
3003
  });
2693
3004
  }
@@ -2719,7 +3030,9 @@ export class Session {
2719
3030
  requestId,
2720
3031
  agentId,
2721
3032
  accepted: false,
2722
- error: error?.message ? String(error.message) : "Failed to set agent thinking option",
3033
+ error: error?.message
3034
+ ? String(error.message)
3035
+ : "Failed to set agent thinking option",
2723
3036
  },
2724
3037
  });
2725
3038
  }
@@ -3232,6 +3545,7 @@ export class Session {
3232
3545
  cwd,
3233
3546
  isGit: true,
3234
3547
  repoRoot: snapshot.git.repoRoot,
3548
+ mainRepoRoot: snapshot.git.mainRepoRoot,
3235
3549
  currentBranch: snapshot.git.currentBranch ?? null,
3236
3550
  isDirty: snapshot.git.isDirty,
3237
3551
  baseRef: snapshot.git.baseRef ?? null,
@@ -3322,7 +3636,9 @@ export class Session {
3322
3636
  const message = branchLabel
3323
3637
  ? `${Session.PASEO_STASH_PREFIX} ${branchLabel}`
3324
3638
  : `${Session.PASEO_STASH_PREFIX} unnamed`;
3325
- await execCommand("git", ["stash", "push", "--include-untracked", "-m", message], { cwd });
3639
+ await execCommand("git", ["stash", "push", "--include-untracked", "-m", message], {
3640
+ cwd,
3641
+ });
3326
3642
  await this.notifyGitMutation(cwd, "stash-push");
3327
3643
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
3328
3644
  this.emit({
@@ -3340,7 +3656,9 @@ export class Session {
3340
3656
  async handleStashPopRequest(msg) {
3341
3657
  const { cwd, stashIndex, requestId } = msg;
3342
3658
  try {
3343
- await execCommand("git", ["stash", "pop", `stash@{${stashIndex}}`], { cwd });
3659
+ await execCommand("git", ["stash", "pop", `stash@{${stashIndex}}`], {
3660
+ cwd,
3661
+ });
3344
3662
  await this.notifyGitMutation(cwd, "stash-pop");
3345
3663
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
3346
3664
  this.emit({
@@ -3568,7 +3886,7 @@ export class Session {
3568
3886
  title,
3569
3887
  body,
3570
3888
  base: msg.baseRef,
3571
- }, this.github, this.workspaceGitService);
3889
+ }, this.github);
3572
3890
  await this.notifyGitMutation(cwd, "create-pr", { invalidateGithub: true });
3573
3891
  this.emit({
3574
3892
  type: "checkout_pr_create_response",
@@ -3718,16 +4036,13 @@ export class Session {
3718
4036
  workspaceGitService: this.workspaceGitService,
3719
4037
  agentManager: this.agentManager,
3720
4038
  agentStorage: this.agentStorage,
3721
- archiveWorkspaceRecord: async (workspaceDirectory) => {
3722
- const workspace = await this.findWorkspaceByDirectory(workspaceDirectory);
3723
- if (workspace) {
3724
- await this.archiveWorkspaceRecord(workspace.workspaceId);
3725
- }
3726
- },
4039
+ archiveWorkspaceRecord: (workspaceId) => this.archiveWorkspaceRecord(workspaceId),
3727
4040
  emit: (message) => this.emit(message),
3728
- emitWorkspaceUpdatesForCwds: (cwds) => this.emitWorkspaceUpdatesForCwds(cwds),
4041
+ emitWorkspaceUpdatesForWorkspaceIds: (workspaceIds) => this.emitWorkspaceUpdatesForWorkspaceIds(workspaceIds),
4042
+ markWorkspaceArchiving: (workspaceIds, archivingAt) => this.markWorkspaceArchiving(workspaceIds, archivingAt),
4043
+ clearWorkspaceArchiving: (workspaceIds) => this.clearWorkspaceArchiving(workspaceIds),
3729
4044
  isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
3730
- killTerminalsUnderPath: (rootPath) => this.killTerminalsUnderPath(rootPath),
4045
+ killTerminalsUnderPath: (rootPath) => this.terminalController.killTerminalsUnderPath(rootPath),
3731
4046
  sessionLogger: this.sessionLogger,
3732
4047
  }, msg);
3733
4048
  }
@@ -3772,22 +4087,49 @@ export class Session {
3772
4087
  });
3773
4088
  }
3774
4089
  else {
3775
- const file = await readExplorerFile({
3776
- root: cwd,
3777
- relativePath: requestedPath,
3778
- });
3779
- this.emit({
3780
- type: "file_explorer_response",
3781
- payload: {
3782
- cwd,
3783
- path: file.path,
3784
- mode,
3785
- directory: null,
3786
- file,
3787
- error: null,
4090
+ if (request.acceptBinary && this.onBinaryMessage) {
4091
+ const file = await readExplorerFileBytes({
4092
+ root: cwd,
4093
+ relativePath: requestedPath,
4094
+ });
4095
+ this.emitBinary(encodeFileTransferFrame({
4096
+ opcode: FileTransferOpcode.FileBegin,
3788
4097
  requestId,
3789
- },
3790
- });
4098
+ metadata: {
4099
+ mime: file.mimeType,
4100
+ size: file.size,
4101
+ encoding: file.encoding,
4102
+ modifiedAt: file.modifiedAt,
4103
+ },
4104
+ }));
4105
+ this.emitBinary(encodeFileTransferFrame({
4106
+ opcode: FileTransferOpcode.FileChunk,
4107
+ requestId,
4108
+ payload: file.bytes,
4109
+ }));
4110
+ this.emitBinary(encodeFileTransferFrame({
4111
+ opcode: FileTransferOpcode.FileEnd,
4112
+ requestId,
4113
+ }));
4114
+ }
4115
+ else {
4116
+ const file = await readExplorerFile({
4117
+ root: cwd,
4118
+ relativePath: requestedPath,
4119
+ });
4120
+ this.emit({
4121
+ type: "file_explorer_response",
4122
+ payload: {
4123
+ cwd,
4124
+ path: file.path,
4125
+ mode,
4126
+ directory: null,
4127
+ file,
4128
+ error: null,
4129
+ requestId,
4130
+ },
4131
+ });
4132
+ }
3791
4133
  }
3792
4134
  }
3793
4135
  catch (error) {
@@ -3912,9 +4254,12 @@ export class Session {
3912
4254
  // (excluding internal agents which are for ephemeral system tasks)
3913
4255
  const registryRecords = await this.agentStorage.list();
3914
4256
  const liveIds = new Set(agentSnapshots.map((a) => a.id));
4257
+ const registeredProviderIds = this.getRegisteredProviderIds();
3915
4258
  const persistedAgents = registryRecords
3916
4259
  .filter((record) => !liveIds.has(record.id) && !record.internal)
3917
- .map((record) => this.buildStoredAgentPayload(record));
4260
+ .filter((record) => filter?.includeUnavailablePersisted === true ||
4261
+ isStoredAgentProviderAvailable(record, registeredProviderIds))
4262
+ .map((record) => this.buildStoredAgentPayload(record, registeredProviderIds));
3918
4263
  let agents = [...liveAgents, ...persistedAgents];
3919
4264
  agents = agents.filter((agent) => this.isProviderVisibleToClient(agent.provider));
3920
4265
  // Filter by labels if filter provided
@@ -3982,159 +4327,22 @@ export class Session {
3982
4327
  const payload = this.buildStoredAgentPayload(record);
3983
4328
  return this.isProviderVisibleToClient(payload.provider) ? payload : null;
3984
4329
  }
3985
- normalizeFetchAgentsSort(sort) {
3986
- const fallback = [{ key: "updated_at", direction: "desc" }];
3987
- if (!sort || sort.length === 0) {
3988
- return fallback;
3989
- }
3990
- const deduped = [];
3991
- const seen = new Set();
3992
- for (const entry of sort) {
3993
- if (seen.has(entry.key)) {
3994
- continue;
3995
- }
3996
- seen.add(entry.key);
3997
- deduped.push(entry);
3998
- }
3999
- return deduped.length > 0 ? deduped : fallback;
4000
- }
4001
4330
  getStatusPriority(agent) {
4002
4331
  const attentionReason = agent.attentionReason ?? null;
4003
4332
  const hasPendingPermission = (agent.pendingPermissions?.length ?? 0) > 0;
4004
- if (hasPendingPermission || attentionReason === "permission") {
4005
- return 0;
4006
- }
4007
- if (agent.status === "error" || attentionReason === "error") {
4008
- return 1;
4009
- }
4010
- if (agent.status === "running") {
4011
- return 2;
4012
- }
4013
- if (agent.status === "initializing") {
4014
- return 3;
4015
- }
4016
- return 4;
4017
- }
4018
- getFetchAgentsSortValue(entry, key) {
4019
- switch (key) {
4020
- case "status_priority":
4021
- return this.getStatusPriority(entry.agent);
4022
- case "created_at":
4023
- return Date.parse(entry.agent.createdAt);
4024
- case "updated_at":
4025
- return Date.parse(entry.agent.updatedAt);
4026
- case "title":
4027
- return entry.agent.title?.toLocaleLowerCase() ?? "";
4028
- }
4029
- }
4030
- getFetchAgentsSortValueFromAgent(agent, key) {
4031
- switch (key) {
4032
- case "status_priority":
4033
- return this.getStatusPriority(agent);
4034
- case "created_at":
4035
- return Date.parse(agent.createdAt);
4036
- case "updated_at":
4037
- return Date.parse(agent.updatedAt);
4038
- case "title":
4039
- return agent.title?.toLocaleLowerCase() ?? "";
4040
- }
4041
- }
4042
- compareSortValues(left, right) {
4043
- if (left === right) {
4044
- return 0;
4045
- }
4046
- if (left === null) {
4047
- return -1;
4048
- }
4049
- if (right === null) {
4050
- return 1;
4051
- }
4052
- if (typeof left === "number" && typeof right === "number") {
4053
- return left < right ? -1 : 1;
4054
- }
4055
- return String(left).localeCompare(String(right));
4056
- }
4057
- compareFetchAgentsAgents(left, right, sort) {
4058
- for (const spec of sort) {
4059
- const leftValue = this.getFetchAgentsSortValueFromAgent(left, spec.key);
4060
- const rightValue = this.getFetchAgentsSortValueFromAgent(right, spec.key);
4061
- const base = this.compareSortValues(leftValue, rightValue);
4062
- if (base === 0) {
4063
- continue;
4064
- }
4065
- return spec.direction === "asc" ? base : -base;
4066
- }
4067
- return left.id.localeCompare(right.id);
4068
- }
4069
- encodeFetchAgentsCursor(entry, sort) {
4070
- const values = {};
4071
- for (const spec of sort) {
4072
- values[spec.key] = this.getFetchAgentsSortValue(entry, spec.key);
4073
- }
4074
- return Buffer.from(JSON.stringify({
4075
- sort,
4076
- values,
4077
- id: entry.agent.id,
4078
- }), "utf8").toString("base64url");
4079
- }
4080
- decodeFetchAgentsCursor(cursor, sort) {
4081
- let parsed;
4082
- try {
4083
- parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
4084
- }
4085
- catch {
4086
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4087
- }
4088
- if (!parsed || typeof parsed !== "object") {
4089
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4090
- }
4091
- const payload = parsed;
4092
- if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
4093
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4094
- }
4095
- if (!payload.values || typeof payload.values !== "object") {
4096
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4333
+ if (hasPendingPermission || attentionReason === "permission") {
4334
+ return 0;
4097
4335
  }
4098
- const cursorSort = [];
4099
- for (const item of payload.sort) {
4100
- if (!item ||
4101
- typeof item !== "object" ||
4102
- typeof item.key !== "string" ||
4103
- typeof item.direction !== "string") {
4104
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4105
- }
4106
- const key = item.key;
4107
- const direction = item.direction;
4108
- if ((key !== "status_priority" &&
4109
- key !== "created_at" &&
4110
- key !== "updated_at" &&
4111
- key !== "title") ||
4112
- (direction !== "asc" && direction !== "desc")) {
4113
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4114
- }
4115
- cursorSort.push({ key, direction });
4336
+ if (agent.status === "error" || attentionReason === "error") {
4337
+ return 1;
4116
4338
  }
4117
- if (cursorSort.length !== sort.length ||
4118
- cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
4119
- throw new SessionRequestError("invalid_cursor", "fetch_agents cursor does not match current sort");
4339
+ if (agent.status === "running") {
4340
+ return 2;
4120
4341
  }
4121
- return {
4122
- sort: cursorSort,
4123
- values: payload.values,
4124
- id: payload.id,
4125
- };
4126
- }
4127
- compareAgentWithCursor(agent, cursor, sort) {
4128
- for (const spec of sort) {
4129
- const leftValue = this.getFetchAgentsSortValueFromAgent(agent, spec.key);
4130
- const rightValue = cursor.values[spec.key] !== undefined ? (cursor.values[spec.key] ?? null) : null;
4131
- const base = this.compareSortValues(leftValue, rightValue);
4132
- if (base === 0) {
4133
- continue;
4134
- }
4135
- return spec.direction === "asc" ? base : -base;
4342
+ if (agent.status === "initializing") {
4343
+ return 3;
4136
4344
  }
4137
- return agent.id.localeCompare(cursor.id);
4345
+ return 4;
4138
4346
  }
4139
4347
  async buildActiveProjectPlacementsByWorkspaceCwd() {
4140
4348
  const [persistedWorkspaces, persistedProjects] = await Promise.all([
@@ -4145,27 +4353,59 @@ export class Session {
4145
4353
  .filter((project) => !project.archivedAt)
4146
4354
  .map((project) => [project.projectId, project]));
4147
4355
  const placementsByCwd = new Map();
4148
- for (const workspace of persistedWorkspaces) {
4149
- if (workspace.archivedAt) {
4150
- continue;
4151
- }
4356
+ const pairs = persistedWorkspaces.flatMap((workspace) => {
4357
+ if (workspace.archivedAt)
4358
+ return [];
4152
4359
  const project = activeProjects.get(workspace.projectId);
4153
- if (!project) {
4154
- continue;
4155
- }
4156
- placementsByCwd.set(normalizePersistedWorkspaceId(workspace.cwd), await this.buildProjectPlacementForWorkspace(workspace, project));
4360
+ if (!project)
4361
+ return [];
4362
+ return [{ workspace, project }];
4363
+ });
4364
+ const placements = await Promise.all(pairs.map(({ workspace, project }) => this.buildProjectPlacementForWorkspace(workspace, project)));
4365
+ for (let i = 0; i < pairs.length; i += 1) {
4366
+ placementsByCwd.set(normalizePersistedWorkspaceId(pairs[i].workspace.cwd), placements[i]);
4157
4367
  }
4158
4368
  return placementsByCwd;
4159
4369
  }
4370
+ async collectFetchAgentsEntries(params) {
4371
+ const { candidates, limit, getPlacement, filter } = params;
4372
+ const matchedEntries = [];
4373
+ const batchSize = 25;
4374
+ for (let start = 0; start < candidates.length && matchedEntries.length <= limit; start += batchSize) {
4375
+ const batch = candidates.slice(start, start + batchSize);
4376
+ const batchEntries = await Promise.all(batch.map(async (agent) => {
4377
+ const project = await getPlacement(agent.cwd);
4378
+ return project ? { agent, project } : null;
4379
+ }));
4380
+ for (const entry of batchEntries) {
4381
+ if (!entry) {
4382
+ continue;
4383
+ }
4384
+ if (!this.matchesAgentFilter({
4385
+ agent: entry.agent,
4386
+ project: entry.project,
4387
+ filter,
4388
+ })) {
4389
+ continue;
4390
+ }
4391
+ matchedEntries.push(entry);
4392
+ if (matchedEntries.length > limit) {
4393
+ break;
4394
+ }
4395
+ }
4396
+ }
4397
+ return matchedEntries;
4398
+ }
4160
4399
  async listFetchAgentsEntries(request) {
4161
4400
  const filter = request.type === "fetch_agent_history_request" &&
4162
4401
  request.filter?.includeArchived === undefined
4163
4402
  ? { ...request.filter, includeArchived: true }
4164
4403
  : request.filter;
4165
4404
  const scope = request.type === "fetch_agents_request" ? request.scope : undefined;
4166
- const sort = this.normalizeFetchAgentsSort(request.sort);
4405
+ const sort = this.agentsPager.normalizeSort(request.sort);
4167
4406
  let agents = await this.listAgentPayloads({
4168
4407
  labels: filter?.labels,
4408
+ includeUnavailablePersisted: request.type === "fetch_agent_history_request",
4169
4409
  });
4170
4410
  const activePlacementsByCwd = scope === "active" ? await this.buildActiveProjectPlacementsByWorkspaceCwd() : null;
4171
4411
  if (activePlacementsByCwd) {
@@ -4185,42 +4425,23 @@ export class Session {
4185
4425
  return placementPromise;
4186
4426
  };
4187
4427
  let candidates = [...agents];
4188
- candidates.sort((left, right) => this.compareFetchAgentsAgents(left, right, sort));
4428
+ candidates.sort((left, right) => this.agentsPager.compare(left, right, sort));
4189
4429
  const cursorToken = request.page?.cursor;
4190
4430
  if (cursorToken) {
4191
- const cursor = this.decodeFetchAgentsCursor(cursorToken, sort);
4192
- candidates = candidates.filter((agent) => this.compareAgentWithCursor(agent, cursor, sort) > 0);
4431
+ const cursor = this.decodeAgentCursor(cursorToken, sort);
4432
+ candidates = candidates.filter((agent) => this.agentsPager.compareWithCursor(agent, cursor, sort) > 0);
4193
4433
  }
4194
4434
  const limit = request.page?.limit ?? 200;
4195
- const matchedEntries = [];
4196
- const batchSize = 25;
4197
- for (let start = 0; start < candidates.length && matchedEntries.length <= limit; start += batchSize) {
4198
- const batch = candidates.slice(start, start + batchSize);
4199
- const batchEntries = await Promise.all(batch.map(async (agent) => {
4200
- const project = await getPlacement(agent.cwd);
4201
- return project ? { agent, project } : null;
4202
- }));
4203
- for (const entry of batchEntries) {
4204
- if (!entry) {
4205
- continue;
4206
- }
4207
- if (!this.matchesAgentFilter({
4208
- agent: entry.agent,
4209
- project: entry.project,
4210
- filter,
4211
- })) {
4212
- continue;
4213
- }
4214
- matchedEntries.push(entry);
4215
- if (matchedEntries.length > limit) {
4216
- break;
4217
- }
4218
- }
4219
- }
4435
+ const matchedEntries = await this.collectFetchAgentsEntries({
4436
+ candidates,
4437
+ limit,
4438
+ getPlacement,
4439
+ filter,
4440
+ });
4220
4441
  const pagedEntries = matchedEntries.slice(0, limit);
4221
4442
  const hasMore = matchedEntries.length > limit;
4222
4443
  const nextCursor = hasMore && pagedEntries.length > 0
4223
- ? this.encodeFetchAgentsCursor(pagedEntries[pagedEntries.length - 1], sort)
4444
+ ? this.agentsPager.encode(pagedEntries[pagedEntries.length - 1].agent, sort)
4224
4445
  : null;
4225
4446
  return {
4226
4447
  entries: pagedEntries,
@@ -4231,21 +4452,16 @@ export class Session {
4231
4452
  },
4232
4453
  };
4233
4454
  }
4234
- deriveWorkspaceStateBucket(agent) {
4235
- const pendingPermissionCount = agent.pendingPermissions?.length ?? 0;
4236
- if (pendingPermissionCount > 0 || agent.attentionReason === "permission") {
4237
- return "needs_input";
4238
- }
4239
- if (agent.status === "error" || agent.attentionReason === "error") {
4240
- return "failed";
4241
- }
4242
- if (agent.status === "running") {
4243
- return "running";
4455
+ decodeAgentCursor(token, sort) {
4456
+ try {
4457
+ return this.agentsPager.decode(token, sort);
4244
4458
  }
4245
- if (agent.requiresAttention) {
4246
- return "attention";
4459
+ catch (error) {
4460
+ if (error instanceof CursorError) {
4461
+ throw new SessionRequestError("invalid_cursor", error.message);
4462
+ }
4463
+ throw error;
4247
4464
  }
4248
- return "done";
4249
4465
  }
4250
4466
  async describeWorkspaceRecord(workspace, projectRecord) {
4251
4467
  const resolvedProjectRecord = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
@@ -4263,6 +4479,7 @@ export class Session {
4263
4479
  projectKind: (resolvedProjectRecord?.kind ?? "directory") === "git" ? "git" : "non_git",
4264
4480
  workspaceKind: workspace.kind,
4265
4481
  name: workspace.displayName,
4482
+ archivingAt: null,
4266
4483
  status: "done",
4267
4484
  activityAt: null,
4268
4485
  diffStat,
@@ -4270,6 +4487,7 @@ export class Session {
4270
4487
  ? buildWorkspaceScriptPayloads({
4271
4488
  workspaceId: workspace.workspaceId,
4272
4489
  workspaceDirectory: workspace.cwd,
4490
+ paseoConfig: readPaseoConfigForProjection(workspace.cwd, this.sessionLogger),
4273
4491
  routeStore: this.scriptRouteStore,
4274
4492
  runtimeStore: this.scriptRuntimeStore,
4275
4493
  daemonPort: this.getDaemonTcpPort?.() ?? null,
@@ -4277,6 +4495,11 @@ export class Session {
4277
4495
  resolveHealth: this.resolveScriptHealth ?? undefined,
4278
4496
  })
4279
4497
  : [],
4498
+ ...(resolvedProjectRecord
4499
+ ? {
4500
+ project: await this.buildProjectPlacementForWorkspace(workspace, resolvedProjectRecord),
4501
+ }
4502
+ : {}),
4280
4503
  };
4281
4504
  }
4282
4505
  buildWorkspaceGitRuntimePayload(snapshot) {
@@ -4327,6 +4550,7 @@ export class Session {
4327
4550
  projectKind: "git",
4328
4551
  workspaceKind: result.workspace.kind,
4329
4552
  name: result.worktree.branchName || result.workspace.displayName,
4553
+ archivingAt: null,
4330
4554
  status: "done",
4331
4555
  activityAt: null,
4332
4556
  diffStat: { additions: 0, deletions: 0 },
@@ -4349,248 +4573,31 @@ export class Session {
4349
4573
  }
4350
4574
  return this.describeWorkspaceRecord(input.workspace, input.projectRecord);
4351
4575
  }
4352
- async buildWorkspaceDescriptorMap(options) {
4353
- const [agents, persistedWorkspaces, persistedProjects] = await Promise.all([
4354
- this.listAgentPayloads(),
4355
- this.workspaceRegistry.list(),
4356
- this.projectRegistry.list(),
4357
- ]);
4358
- const activeProjects = new Map(persistedProjects
4359
- .filter((project) => !project.archivedAt)
4360
- .map((project) => [project.projectId, project]));
4361
- const archivedProjectIds = new Set(persistedProjects.filter((project) => project.archivedAt).map((project) => project.projectId));
4362
- const activeRecords = persistedWorkspaces.filter((workspace) => !workspace.archivedAt && !archivedProjectIds.has(workspace.projectId));
4363
- const descriptorsByWorkspaceId = new Map();
4364
- const workspaceIds = options.workspaceIds ? new Set(options.workspaceIds) : null;
4365
- const workspaceIdsByDirectory = new Map(activeRecords.map((workspace) => [normalizePersistedWorkspaceId(workspace.cwd), workspace.workspaceId]));
4366
- for (const workspace of activeRecords) {
4367
- if (workspaceIds && !workspaceIds.has(workspace.workspaceId)) {
4368
- continue;
4369
- }
4370
- const projectRecord = activeProjects.get(workspace.projectId) ?? null;
4371
- descriptorsByWorkspaceId.set(workspace.workspaceId, await this.buildWorkspaceDescriptor({
4372
- workspace,
4373
- projectRecord,
4374
- includeGitData: options.includeGitData,
4375
- }));
4376
- }
4377
- for (const agent of agents) {
4378
- if (agent.archivedAt) {
4379
- continue;
4380
- }
4381
- if (!this.isProviderVisibleToClient(agent.provider)) {
4382
- continue;
4383
- }
4384
- const workspaceId = workspaceIdsByDirectory.get(normalizePersistedWorkspaceId(agent.cwd));
4385
- if (workspaceId === undefined) {
4386
- continue;
4387
- }
4388
- const existing = descriptorsByWorkspaceId.get(workspaceId);
4389
- if (!existing) {
4390
- continue;
4391
- }
4392
- const bucket = this.deriveWorkspaceStateBucket(agent);
4393
- if (this.workspaceStatePriority[bucket] < this.workspaceStatePriority[existing.status]) {
4394
- existing.status = bucket;
4395
- }
4396
- }
4397
- return descriptorsByWorkspaceId;
4576
+ markWorkspaceArchiving(workspaceIds, archivingAt) {
4577
+ this.workspaceDirectory.markArchiving(workspaceIds, archivingAt);
4398
4578
  }
4399
- resolveRegisteredWorkspaceIdForCwd(cwd, workspaces) {
4400
- const normalizedCwd = normalizePersistedWorkspaceId(cwd);
4401
- const exact = workspaces.find((workspace) => workspace.cwd === normalizedCwd);
4402
- if (exact) {
4403
- return exact.workspaceId;
4404
- }
4405
- let bestMatch = null;
4406
- for (const workspace of workspaces) {
4407
- const prefix = workspace.cwd.endsWith(sep) ? workspace.cwd : `${workspace.cwd}${sep}`;
4408
- if (!normalizedCwd.startsWith(prefix)) {
4409
- continue;
4410
- }
4411
- if (!bestMatch || workspace.cwd.length > bestMatch.cwd.length) {
4412
- bestMatch = workspace;
4413
- }
4414
- }
4415
- return bestMatch?.workspaceId ?? normalizedCwd;
4579
+ clearWorkspaceArchiving(workspaceIds) {
4580
+ this.workspaceDirectory.clearArchiving(workspaceIds);
4416
4581
  }
4417
- async listWorkspaceDescriptors() {
4418
- return Array.from((await this.buildWorkspaceDescriptorMap({
4419
- includeGitData: true,
4420
- })).values());
4582
+ async buildWorkspaceDescriptorMap(options) {
4583
+ return this.workspaceDirectory.buildDescriptorMap(options);
4421
4584
  }
4422
- normalizeFetchWorkspacesSort(sort) {
4423
- const fallback = [{ key: "activity_at", direction: "desc" }];
4424
- if (!sort || sort.length === 0) {
4425
- return fallback;
4426
- }
4427
- const deduped = [];
4428
- const seen = new Set();
4429
- for (const entry of sort) {
4430
- if (seen.has(entry.key)) {
4431
- continue;
4432
- }
4433
- seen.add(entry.key);
4434
- deduped.push(entry);
4435
- }
4436
- return deduped.length > 0 ? deduped : fallback;
4437
- }
4438
- getFetchWorkspacesSortValue(workspace, key) {
4439
- switch (key) {
4440
- case "status_priority":
4441
- return this.workspaceStatePriority[workspace.status];
4442
- case "activity_at":
4443
- return workspace.activityAt ? Date.parse(workspace.activityAt) : null;
4444
- case "name":
4445
- return workspace.name.toLocaleLowerCase();
4446
- case "project_id":
4447
- return workspace.projectId.toLocaleLowerCase();
4448
- }
4449
- }
4450
- compareFetchWorkspacesEntries(left, right, sort) {
4451
- for (const spec of sort) {
4452
- const leftValue = this.getFetchWorkspacesSortValue(left, spec.key);
4453
- const rightValue = this.getFetchWorkspacesSortValue(right, spec.key);
4454
- const base = this.compareSortValues(leftValue, rightValue);
4455
- if (base === 0) {
4456
- continue;
4457
- }
4458
- return spec.direction === "asc" ? base : -base;
4459
- }
4460
- return left.id.localeCompare(right.id);
4585
+ resolveRegisteredWorkspaceIdForCwd(cwd, workspaces) {
4586
+ return this.workspaceDirectory.resolveRegisteredWorkspaceIdForCwd(cwd, workspaces);
4461
4587
  }
4462
- encodeFetchWorkspacesCursor(entry, sort) {
4463
- const values = {};
4464
- for (const spec of sort) {
4465
- values[spec.key] = this.getFetchWorkspacesSortValue(entry, spec.key);
4466
- }
4467
- return Buffer.from(JSON.stringify({
4468
- sort,
4469
- values,
4470
- id: entry.id,
4471
- }), "utf8").toString("base64url");
4588
+ matchesWorkspaceFilter(input) {
4589
+ return this.workspaceDirectory.matchesFilter(input);
4472
4590
  }
4473
- decodeFetchWorkspacesCursor(cursor, sort) {
4474
- let parsed;
4591
+ async listFetchWorkspacesEntries(request) {
4475
4592
  try {
4476
- parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
4477
- }
4478
- catch {
4479
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4480
- }
4481
- if (!parsed || typeof parsed !== "object") {
4482
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4483
- }
4484
- const payload = parsed;
4485
- if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
4486
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4593
+ return await this.workspaceDirectory.listFetchEntries(request);
4487
4594
  }
4488
- if (!payload.values || typeof payload.values !== "object") {
4489
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4490
- }
4491
- const cursorSort = [];
4492
- for (const item of payload.sort) {
4493
- if (!item ||
4494
- typeof item !== "object" ||
4495
- typeof item.key !== "string" ||
4496
- typeof item.direction !== "string") {
4497
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4498
- }
4499
- const key = item.key;
4500
- const direction = item.direction;
4501
- if ((key !== "status_priority" &&
4502
- key !== "activity_at" &&
4503
- key !== "name" &&
4504
- key !== "project_id") ||
4505
- (direction !== "asc" && direction !== "desc")) {
4506
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4507
- }
4508
- cursorSort.push({ key, direction });
4509
- }
4510
- if (cursorSort.length !== sort.length ||
4511
- cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
4512
- throw new SessionRequestError("invalid_cursor", "fetch_workspaces cursor does not match current sort");
4513
- }
4514
- return {
4515
- sort: cursorSort,
4516
- values: payload.values,
4517
- id: String(payload.id),
4518
- };
4519
- }
4520
- compareWorkspaceWithCursor(workspace, cursor, sort) {
4521
- for (const spec of sort) {
4522
- const leftValue = this.getFetchWorkspacesSortValue(workspace, spec.key);
4523
- const rightValue = cursor.values[spec.key] !== undefined ? (cursor.values[spec.key] ?? null) : null;
4524
- const base = this.compareSortValues(leftValue, rightValue);
4525
- if (base === 0) {
4526
- continue;
4527
- }
4528
- return spec.direction === "asc" ? base : -base;
4529
- }
4530
- return workspace.id.localeCompare(cursor.id);
4531
- }
4532
- matchesWorkspaceFilter(input) {
4533
- const { workspace, filter } = input;
4534
- if (!filter) {
4535
- return true;
4536
- }
4537
- if (filter.projectId && filter.projectId.trim().length > 0) {
4538
- if (workspace.projectId !== filter.projectId.trim()) {
4539
- return false;
4540
- }
4541
- }
4542
- if (filter.idPrefix && filter.idPrefix.trim().length > 0) {
4543
- if (!String(workspace.id).startsWith(filter.idPrefix.trim())) {
4544
- return false;
4545
- }
4546
- }
4547
- if (filter.query && filter.query.trim().length > 0) {
4548
- const query = filter.query.trim().toLocaleLowerCase();
4549
- const haystacks = [workspace.name, String(workspace.projectId), String(workspace.id)];
4550
- if (!haystacks.some((value) => value.toLocaleLowerCase().includes(query))) {
4551
- return false;
4595
+ catch (error) {
4596
+ if (error instanceof CursorError) {
4597
+ throw new SessionRequestError("invalid_cursor", error.message);
4552
4598
  }
4599
+ throw error;
4553
4600
  }
4554
- return true;
4555
- }
4556
- async listFetchWorkspacesEntries(request) {
4557
- const filter = request.filter;
4558
- const sort = this.normalizeFetchWorkspacesSort(request.sort);
4559
- let entries = await this.listWorkspaceDescriptors();
4560
- const listedCount = entries.length;
4561
- entries = entries.filter((workspace) => this.matchesWorkspaceFilter({ workspace, filter }));
4562
- const filteredCount = entries.length;
4563
- entries.sort((left, right) => this.compareFetchWorkspacesEntries(left, right, sort));
4564
- const cursorToken = request.page?.cursor;
4565
- if (cursorToken) {
4566
- const cursor = this.decodeFetchWorkspacesCursor(cursorToken, sort);
4567
- entries = entries.filter((workspace) => this.compareWorkspaceWithCursor(workspace, cursor, sort) > 0);
4568
- }
4569
- const limit = request.page?.limit ?? 200;
4570
- const pagedEntries = entries.slice(0, limit);
4571
- const hasMore = entries.length > limit;
4572
- const nextCursor = hasMore && pagedEntries.length > 0
4573
- ? this.encodeFetchWorkspacesCursor(pagedEntries[pagedEntries.length - 1], sort)
4574
- : null;
4575
- this.sessionLogger.debug({
4576
- requestId: request.requestId,
4577
- filter: request.filter ?? null,
4578
- sort,
4579
- page: request.page ?? null,
4580
- listedCount,
4581
- filteredCount,
4582
- returnedCount: pagedEntries.length,
4583
- hasMore,
4584
- nextCursor,
4585
- }, "fetch_workspaces_entries_listed");
4586
- return {
4587
- entries: pagedEntries,
4588
- pageInfo: {
4589
- nextCursor,
4590
- prevCursor: request.page?.cursor ?? null,
4591
- hasMore,
4592
- },
4593
- };
4594
4601
  }
4595
4602
  bufferOrEmitWorkspaceUpdate(subscription, payload) {
4596
4603
  if (subscription.isBootstrapping) {
@@ -4633,41 +4640,121 @@ export class Session {
4633
4640
  }
4634
4641
  }
4635
4642
  async findOrCreateWorkspaceForDirectory(cwd) {
4643
+ const inputCwd = normalizePersistedWorkspaceId(cwd);
4636
4644
  const normalizedCwd = await this.resolveWorkspaceDirectory(cwd);
4637
- const existingWorkspace = await this.findWorkspaceByDirectory(normalizedCwd);
4645
+ const existingWorkspace = await this.findExactWorkspaceByDirectory(normalizedCwd, {
4646
+ refreshGit: false,
4647
+ });
4638
4648
  if (existingWorkspace) {
4639
- return this.ensureWorkspaceRecordUnarchived(existingWorkspace);
4649
+ if (existingWorkspace.archivedAt && inputCwd !== normalizedCwd) {
4650
+ const timestamp = new Date().toISOString();
4651
+ const displayName = basename(inputCwd) || inputCwd;
4652
+ const projectRecord = createPersistedProjectRecord({
4653
+ projectId: inputCwd,
4654
+ rootPath: inputCwd,
4655
+ kind: "non_git",
4656
+ displayName,
4657
+ createdAt: timestamp,
4658
+ updatedAt: timestamp,
4659
+ });
4660
+ await this.projectRegistry.upsert(projectRecord);
4661
+ const workspaceRecord = createPersistedWorkspaceRecord({
4662
+ workspaceId: inputCwd,
4663
+ projectId: projectRecord.projectId,
4664
+ cwd: inputCwd,
4665
+ kind: "directory",
4666
+ displayName,
4667
+ createdAt: timestamp,
4668
+ updatedAt: timestamp,
4669
+ });
4670
+ await this.workspaceRegistry.upsert(workspaceRecord);
4671
+ return workspaceRecord;
4672
+ }
4673
+ return this.reclassifyOrUnarchiveWorkspaceForDirectory({
4674
+ workspace: existingWorkspace,
4675
+ project: await this.projectRegistry.get(existingWorkspace.projectId),
4676
+ cwd: normalizedCwd,
4677
+ });
4640
4678
  }
4641
- const placement = await buildProjectPlacementForCwdStandalone({
4642
- cwd: normalizedCwd,
4643
- workspaceGitService: this.workspaceGitService,
4644
- });
4645
- const workspaceId = deriveWorkspaceId(normalizedCwd, placement.checkout);
4679
+ return this.createWorkspaceForDirectory(normalizedCwd);
4680
+ }
4681
+ async createWorkspaceForDirectory(cwd) {
4682
+ const checkout = await this.workspaceGitService.getCheckout(cwd);
4683
+ const membership = classifyDirectoryForProjectMembership({ cwd, checkout });
4646
4684
  const timestamp = new Date().toISOString();
4647
- const projectRecord = createPersistedProjectRecord({
4648
- projectId: placement.projectKey,
4649
- rootPath: deriveProjectRootPath({ cwd: normalizedCwd, checkout: placement.checkout }),
4650
- kind: deriveProjectKind(placement.checkout),
4651
- displayName: placement.projectName,
4652
- createdAt: timestamp,
4653
- updatedAt: timestamp,
4685
+ const projectRecord = await this.resolveProjectRecordForPlacement({
4686
+ membership,
4687
+ timestamp,
4654
4688
  });
4655
4689
  await this.projectRegistry.upsert(projectRecord);
4656
4690
  const workspaceRecord = createPersistedWorkspaceRecord({
4657
- workspaceId,
4658
- projectId: placement.projectKey,
4659
- cwd: normalizedCwd,
4660
- kind: deriveWorkspaceKind(placement.checkout),
4661
- displayName: deriveWorkspaceDisplayName({
4662
- cwd: normalizedCwd,
4663
- checkout: placement.checkout,
4664
- }),
4691
+ workspaceId: membership.workspaceId,
4692
+ projectId: projectRecord.projectId,
4693
+ cwd,
4694
+ kind: membership.workspaceKind,
4695
+ displayName: membership.workspaceDisplayName,
4665
4696
  createdAt: timestamp,
4666
4697
  updatedAt: timestamp,
4667
4698
  });
4668
4699
  await this.workspaceRegistry.upsert(workspaceRecord);
4669
4700
  return workspaceRecord;
4670
4701
  }
4702
+ async reclassifyOrUnarchiveWorkspaceForDirectory(input) {
4703
+ const checkout = await this.workspaceGitService.getCheckout(input.cwd);
4704
+ const membership = classifyDirectoryForProjectMembership({ cwd: input.cwd, checkout });
4705
+ const timestamp = new Date().toISOString();
4706
+ const projectRecord = await this.resolveProjectRecordForPlacement({
4707
+ membership,
4708
+ timestamp,
4709
+ });
4710
+ const projectId = projectRecord.projectId;
4711
+ const kind = membership.workspaceKind;
4712
+ const displayName = membership.workspaceDisplayName;
4713
+ if (input.workspace.workspaceId === membership.workspaceId &&
4714
+ input.workspace.projectId === projectId &&
4715
+ input.workspace.kind === kind &&
4716
+ input.workspace.displayName === displayName) {
4717
+ return this.ensureWorkspaceRecordUnarchived(input.workspace);
4718
+ }
4719
+ await this.projectRegistry.upsert(projectRecord);
4720
+ const nextWorkspace = {
4721
+ ...input.workspace,
4722
+ workspaceId: membership.workspaceId,
4723
+ projectId,
4724
+ cwd: input.cwd,
4725
+ kind,
4726
+ displayName,
4727
+ archivedAt: null,
4728
+ updatedAt: timestamp,
4729
+ };
4730
+ await this.workspaceRegistry.upsert(nextWorkspace);
4731
+ return nextWorkspace;
4732
+ }
4733
+ async resolveProjectRecordForPlacement(input) {
4734
+ const rootPath = input.membership.projectRootPath;
4735
+ const kind = input.membership.projectKind;
4736
+ const projects = await this.projectRegistry.list();
4737
+ const existingProject = projects.find((project) => !project.archivedAt && project.rootPath === rootPath) ??
4738
+ projects.find((project) => project.rootPath === rootPath) ??
4739
+ null;
4740
+ if (!existingProject) {
4741
+ return createPersistedProjectRecord({
4742
+ projectId: input.membership.projectKey,
4743
+ rootPath,
4744
+ kind,
4745
+ displayName: input.membership.projectName,
4746
+ createdAt: input.timestamp,
4747
+ updatedAt: input.timestamp,
4748
+ });
4749
+ }
4750
+ return {
4751
+ ...existingProject,
4752
+ rootPath,
4753
+ kind,
4754
+ archivedAt: null,
4755
+ updatedAt: input.timestamp,
4756
+ };
4757
+ }
4671
4758
  async ensureWorkspaceRecordUnarchived(workspace) {
4672
4759
  const project = await this.projectRegistry.get(workspace.projectId);
4673
4760
  if (!workspace.archivedAt && (!project || !project.archivedAt)) {
@@ -4749,7 +4836,7 @@ export class Session {
4749
4836
  const result = await service.runOnce();
4750
4837
  const changedWorkspaceIds = new Set();
4751
4838
  const changedProjectIds = new Set();
4752
- for (const change of result.changesApplied) {
4839
+ await Promise.all(result.changesApplied.map(async (change) => {
4753
4840
  switch (change.kind) {
4754
4841
  case "workspace_archived":
4755
4842
  await this.removeWorkspaceGitWatchTarget(change.directory);
@@ -4765,7 +4852,7 @@ export class Session {
4765
4852
  changedProjectIds.add(change.projectId);
4766
4853
  break;
4767
4854
  }
4768
- }
4855
+ }));
4769
4856
  if (changedProjectIds.size > 0) {
4770
4857
  for (const workspace of await this.workspaceRegistry.list()) {
4771
4858
  if (changedProjectIds.has(workspace.projectId)) {
@@ -4834,21 +4921,9 @@ export class Session {
4834
4921
  const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(cwd, workspaces);
4835
4922
  await this.emitWorkspaceUpdatesForWorkspaceIds([workspaceId], options);
4836
4923
  }
4837
- async emitWorkspaceUpdatesForCwds(cwds) {
4838
- const workspaces = await this.workspaceRegistry.list();
4839
- const uniqueWorkspaceIds = new Set();
4840
- for (const cwd of cwds) {
4841
- uniqueWorkspaceIds.add(this.resolveRegisteredWorkspaceIdForCwd(cwd, workspaces));
4842
- }
4843
- await this.emitWorkspaceUpdatesForWorkspaceIds(uniqueWorkspaceIds);
4844
- }
4845
4924
  async handleFetchAgents(request) {
4846
4925
  const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
4847
- const subscriptionId = request.subscribe
4848
- ? requestedSubscriptionId && requestedSubscriptionId.length > 0
4849
- ? requestedSubscriptionId
4850
- : uuidv4()
4851
- : null;
4926
+ const subscriptionId = resolveSubscriptionId(request.subscribe, requestedSubscriptionId);
4852
4927
  try {
4853
4928
  if (subscriptionId) {
4854
4929
  this.agentUpdatesSubscription = {
@@ -4924,11 +4999,7 @@ export class Session {
4924
4999
  }
4925
5000
  async handleFetchWorkspacesRequest(request) {
4926
5001
  const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
4927
- const subscriptionId = request.subscribe
4928
- ? requestedSubscriptionId && requestedSubscriptionId.length > 0
4929
- ? requestedSubscriptionId
4930
- : uuidv4()
4931
- : null;
5002
+ const subscriptionId = resolveSubscriptionId(request.subscribe, requestedSubscriptionId);
4932
5003
  try {
4933
5004
  this.sessionLogger.debug({
4934
5005
  requestId: request.requestId,
@@ -4997,8 +5068,9 @@ export class Session {
4997
5068
  async handleOpenProjectRequest(request) {
4998
5069
  try {
4999
5070
  const workspace = await this.findOrCreateWorkspaceForDirectory(request.cwd);
5071
+ await this.syncWorkspaceGitObserverForWorkspace(workspace);
5072
+ const descriptor = await this.describeWorkspaceRecord(workspace);
5000
5073
  await this.emitWorkspaceUpdateForCwd(workspace.cwd);
5001
- const descriptor = await this.describeWorkspaceRecordWithGitData(workspace);
5002
5074
  this.emit({
5003
5075
  type: "open_project_response",
5004
5076
  payload: {
@@ -5007,6 +5079,15 @@ export class Session {
5007
5079
  error: null,
5008
5080
  },
5009
5081
  });
5082
+ void this.workspaceGitService
5083
+ .getSnapshot(workspace.cwd, {
5084
+ force: true,
5085
+ includeGitHub: true,
5086
+ reason: "open_project",
5087
+ })
5088
+ .catch((error) => {
5089
+ this.sessionLogger.warn({ err: error, cwd: workspace.cwd }, "Background snapshot refresh failed after open_project");
5090
+ });
5010
5091
  }
5011
5092
  catch (error) {
5012
5093
  const message = error instanceof Error ? error.message : "Failed to open project";
@@ -5028,6 +5109,7 @@ export class Session {
5028
5109
  return buildWorkspaceScriptPayloads({
5029
5110
  workspaceId,
5030
5111
  workspaceDirectory,
5112
+ paseoConfig: readPaseoConfigForProjection(workspaceDirectory, this.sessionLogger),
5031
5113
  routeStore: this.scriptRouteStore,
5032
5114
  runtimeStore: this.scriptRuntimeStore,
5033
5115
  daemonPort: this.getDaemonTcpPort?.() ?? null,
@@ -5178,15 +5260,15 @@ export class Session {
5178
5260
  paseoHome: this.paseoHome,
5179
5261
  describeWorkspaceRecord: (result) => this.describeCreatedWorktreeWorkspace(result),
5180
5262
  emit: (message) => this.emit(message),
5181
- createPaseoWorktree: (input) => this.createPaseoWorktree(input),
5182
- warmWorkspaceGitData: (workspace) => this.warmWorkspaceGitDataForWorkspace(workspace),
5183
5263
  sessionLogger: this.sessionLogger,
5184
- runWorktreeSetupInBackground: (options) => this.runWorktreeSetupInBackground(options),
5264
+ createPaseoWorktreeWorkflow: (input) => this.createPaseoWorktreeWorkflow(input),
5185
5265
  }, request);
5186
5266
  }
5187
- async runWorktreeSetupInBackground(options) {
5188
- return runWorktreeSetupInBackgroundSession({
5267
+ async createPaseoWorktreeWorkflow(input, options) {
5268
+ return createWorktreeWorkflow({
5189
5269
  paseoHome: this.paseoHome,
5270
+ createPaseoWorktree: (workflowInput, serviceOptions) => this.createPaseoWorktree(workflowInput, serviceOptions),
5271
+ warmWorkspaceGitData: (workspace) => this.warmWorkspaceGitDataForWorkspace(workspace),
5190
5272
  emitWorkspaceUpdateForCwd: (cwd, emitOptions) => this.emitWorkspaceUpdateForCwd(cwd, emitOptions),
5191
5273
  cacheWorkspaceSetupSnapshot: (workspaceId, snapshot) => {
5192
5274
  this.workspaceSetupSnapshots.set(workspaceId, snapshot);
@@ -5202,7 +5284,7 @@ export class Session {
5202
5284
  onScriptsChanged: (workspaceId, workspaceDirectory) => {
5203
5285
  this.emitWorkspaceScriptStatusUpdate(workspaceId, workspaceDirectory);
5204
5286
  },
5205
- }, options);
5287
+ }, input, options);
5206
5288
  }
5207
5289
  async handleWorkspaceSetupStatusRequest(request) {
5208
5290
  return handleWorkspaceSetupStatusRequestMessage({
@@ -5274,6 +5356,53 @@ export class Session {
5274
5356
  payload: { requestId, agent, project, error: null },
5275
5357
  });
5276
5358
  }
5359
+ loadProjectedTimelineWindow(params) {
5360
+ const { agentId, direction, cursor, requestedLimit } = params;
5361
+ let timeline = params.timeline;
5362
+ const projectedLimit = Math.max(1, Math.floor(requestedLimit));
5363
+ let fetchLimit = projectedLimit;
5364
+ let projectedWindow = selectTimelineWindowByProjectedLimit({
5365
+ rows: timeline.rows,
5366
+ direction,
5367
+ limit: projectedLimit,
5368
+ collapseToolLifecycle: false,
5369
+ });
5370
+ while (timeline.hasOlder) {
5371
+ const needsMoreProjectedEntries = projectedWindow.projectedEntries.length < projectedLimit;
5372
+ const firstLoadedRow = timeline.rows[0];
5373
+ const firstSelectedRow = projectedWindow.selectedRows[0];
5374
+ const startsAtLoadedBoundary = firstLoadedRow != null &&
5375
+ firstSelectedRow != null &&
5376
+ firstSelectedRow.seq === firstLoadedRow.seq;
5377
+ const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === "assistant_message";
5378
+ if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
5379
+ break;
5380
+ }
5381
+ const maxRows = Math.max(0, timeline.window.maxSeq - timeline.window.minSeq + 1);
5382
+ const nextFetchLimit = Math.min(maxRows, fetchLimit * 2);
5383
+ if (nextFetchLimit <= fetchLimit) {
5384
+ break;
5385
+ }
5386
+ fetchLimit = nextFetchLimit;
5387
+ timeline = this.agentManager.fetchTimeline(agentId, {
5388
+ direction,
5389
+ cursor,
5390
+ limit: fetchLimit,
5391
+ });
5392
+ projectedWindow = selectTimelineWindowByProjectedLimit({
5393
+ rows: timeline.rows,
5394
+ direction,
5395
+ limit: projectedLimit,
5396
+ collapseToolLifecycle: false,
5397
+ });
5398
+ }
5399
+ return {
5400
+ timeline,
5401
+ selectedRows: projectedWindow.selectedRows,
5402
+ minSeq: projectedWindow.minSeq,
5403
+ maxSeq: projectedWindow.maxSeq,
5404
+ };
5405
+ }
5277
5406
  async handleFetchAgentTimelineRequest(msg) {
5278
5407
  const direction = msg.direction ?? (msg.cursor ? "after" : "tail");
5279
5408
  const projection = msg.projection ?? "projected";
@@ -5309,51 +5438,19 @@ export class Session {
5309
5438
  let endCursor = null;
5310
5439
  let entries;
5311
5440
  if (shouldLimitByProjectedWindow) {
5312
- const projectedLimit = Math.max(1, Math.floor(requestedLimit));
5313
- let fetchLimit = projectedLimit;
5314
- let projectedWindow = selectTimelineWindowByProjectedLimit({
5315
- rows: timeline.rows,
5316
- provider: snapshot.provider,
5441
+ const projectedResult = this.loadProjectedTimelineWindow({
5442
+ agentId: msg.agentId,
5317
5443
  direction,
5318
- limit: projectedLimit,
5319
- collapseToolLifecycle: false,
5444
+ cursor,
5445
+ requestedLimit,
5446
+ timeline,
5320
5447
  });
5321
- while (timeline.hasOlder) {
5322
- const needsMoreProjectedEntries = projectedWindow.projectedEntries.length < projectedLimit;
5323
- const firstLoadedRow = timeline.rows[0];
5324
- const firstSelectedRow = projectedWindow.selectedRows[0];
5325
- const startsAtLoadedBoundary = firstLoadedRow != null &&
5326
- firstSelectedRow != null &&
5327
- firstSelectedRow.seq === firstLoadedRow.seq;
5328
- const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === "assistant_message";
5329
- if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
5330
- break;
5331
- }
5332
- const maxRows = Math.max(0, timeline.window.maxSeq - timeline.window.minSeq + 1);
5333
- const nextFetchLimit = Math.min(maxRows, fetchLimit * 2);
5334
- if (nextFetchLimit <= fetchLimit) {
5335
- break;
5336
- }
5337
- fetchLimit = nextFetchLimit;
5338
- timeline = this.agentManager.fetchTimeline(msg.agentId, {
5339
- direction,
5340
- cursor,
5341
- limit: fetchLimit,
5342
- });
5343
- projectedWindow = selectTimelineWindowByProjectedLimit({
5344
- rows: timeline.rows,
5345
- provider: snapshot.provider,
5346
- direction,
5347
- limit: projectedLimit,
5348
- collapseToolLifecycle: false,
5349
- });
5350
- }
5351
- const selectedRows = projectedWindow.selectedRows;
5352
- entries = projectTimelineRows(selectedRows, snapshot.provider, projection);
5353
- if (projectedWindow.minSeq !== null && projectedWindow.maxSeq !== null) {
5354
- startCursor = { epoch: timeline.epoch, seq: projectedWindow.minSeq };
5355
- endCursor = { epoch: timeline.epoch, seq: projectedWindow.maxSeq };
5356
- hasOlder = projectedWindow.minSeq > timeline.window.minSeq;
5448
+ timeline = projectedResult.timeline;
5449
+ entries = projectTimelineRows({ rows: projectedResult.selectedRows, mode: projection });
5450
+ if (projectedResult.minSeq !== null && projectedResult.maxSeq !== null) {
5451
+ startCursor = { epoch: timeline.epoch, seq: projectedResult.minSeq };
5452
+ endCursor = { epoch: timeline.epoch, seq: projectedResult.maxSeq };
5453
+ hasOlder = projectedResult.minSeq > timeline.window.minSeq;
5357
5454
  hasNewer = false;
5358
5455
  }
5359
5456
  }
@@ -5362,7 +5459,7 @@ export class Session {
5362
5459
  const lastRow = timeline.rows[timeline.rows.length - 1];
5363
5460
  startCursor = firstRow ? { epoch: timeline.epoch, seq: firstRow.seq } : null;
5364
5461
  endCursor = lastRow ? { epoch: timeline.epoch, seq: lastRow.seq } : null;
5365
- entries = projectTimelineRows(timeline.rows, snapshot.provider, projection);
5462
+ entries = projectTimelineRows({ rows: timeline.rows, mode: projection });
5366
5463
  }
5367
5464
  this.emit({
5368
5465
  type: "fetch_agent_timeline_response",
@@ -5381,7 +5478,17 @@ export class Session {
5381
5478
  endCursor,
5382
5479
  hasOlder,
5383
5480
  hasNewer,
5384
- entries,
5481
+ entries: entries.map((entry) => ({
5482
+ provider: snapshot.provider,
5483
+ item: entry.item,
5484
+ timestamp: entry.timestamp,
5485
+ seqStart: entry.seqStart,
5486
+ seqEnd: entry.seqEnd,
5487
+ sourceSeqRanges: entry.sourceSeqRanges,
5488
+ collapsed: this.supports(CLIENT_CAPS.reasoningMergeEnum)
5489
+ ? entry.collapsed
5490
+ : entry.collapsed.filter((value) => value !== "reasoning_merge"),
5491
+ })),
5385
5492
  error: null,
5386
5493
  },
5387
5494
  });
@@ -5461,18 +5568,13 @@ export class Session {
5461
5568
  await this.agentManager.waitForAgentRunStart(agentId, { signal: startAbort.signal });
5462
5569
  }
5463
5570
  catch (error) {
5464
- const message = error instanceof Error
5465
- ? error.message
5466
- : typeof error === "string"
5467
- ? error
5468
- : "Unknown error";
5469
5571
  this.emit({
5470
5572
  type: "send_agent_message_response",
5471
5573
  payload: {
5472
5574
  requestId: msg.requestId,
5473
5575
  agentId,
5474
5576
  accepted: false,
5475
- error: message,
5577
+ error: errorToFriendlyMessage(error),
5476
5578
  },
5477
5579
  });
5478
5580
  return;
@@ -5491,18 +5593,13 @@ export class Session {
5491
5593
  });
5492
5594
  }
5493
5595
  catch (error) {
5494
- const message = error instanceof Error
5495
- ? error.message
5496
- : typeof error === "string"
5497
- ? error
5498
- : "Unknown error";
5499
5596
  this.emit({
5500
5597
  type: "send_agent_message_response",
5501
5598
  payload: {
5502
5599
  requestId: msg.requestId,
5503
5600
  agentId: resolved.agentId,
5504
5601
  accepted: false,
5505
- error: message,
5602
+ error: errorToFriendlyMessage(error),
5506
5603
  },
5507
5604
  });
5508
5605
  }
@@ -5540,11 +5637,16 @@ export class Session {
5540
5637
  return;
5541
5638
  }
5542
5639
  const final = this.buildStoredAgentPayload(record);
5543
- const status = record.attentionReason === "permission"
5544
- ? "permission"
5545
- : record.lastStatus === "error"
5546
- ? "error"
5547
- : "idle";
5640
+ let status;
5641
+ if (record.attentionReason === "permission") {
5642
+ status = "permission";
5643
+ }
5644
+ else if (record.lastStatus === "error") {
5645
+ status = "error";
5646
+ }
5647
+ else {
5648
+ status = "idle";
5649
+ }
5548
5650
  const error = resolveWaitForFinishError({ status, final });
5549
5651
  this.emit({
5550
5652
  type: "wait_for_finish_response",
@@ -5568,11 +5670,16 @@ export class Session {
5568
5670
  if (!final) {
5569
5671
  throw new Error(`Agent ${agentId} disappeared while waiting`);
5570
5672
  }
5571
- let status = result.permission
5572
- ? "permission"
5573
- : result.status === "error"
5574
- ? "error"
5575
- : "idle";
5673
+ let status;
5674
+ if (result.permission) {
5675
+ status = "permission";
5676
+ }
5677
+ else if (result.status === "error") {
5678
+ status = "error";
5679
+ }
5680
+ else {
5681
+ status = "idle";
5682
+ }
5576
5683
  const error = resolveWaitForFinishError({ status, final });
5577
5684
  this.emit({
5578
5685
  type: "wait_for_finish_response",
@@ -5583,11 +5690,7 @@ export class Session {
5583
5690
  const isAbort = error instanceof Error &&
5584
5691
  (error.name === "AbortError" || error.message.toLowerCase().includes("aborted"));
5585
5692
  if (!isAbort) {
5586
- const message = error instanceof Error
5587
- ? error.message
5588
- : typeof error === "string"
5589
- ? error
5590
- : "Unknown error";
5693
+ const message = errorToFriendlyMessage(error);
5591
5694
  this.sessionLogger.error({ err: error, agentId }, "wait_for_finish_request failed");
5592
5695
  const final = await this.getAgentPayloadById(agentId);
5593
5696
  this.emit({
@@ -5604,7 +5707,7 @@ export class Session {
5604
5707
  }
5605
5708
  const final = await this.getAgentPayloadById(agentId);
5606
5709
  if (!final) {
5607
- throw new Error(`Agent ${agentId} disappeared while waiting`);
5710
+ throw new Error(`Agent ${agentId} disappeared while waiting`, { cause: error });
5608
5711
  }
5609
5712
  this.emit({
5610
5713
  type: "wait_for_finish_response",
@@ -5614,50 +5717,13 @@ export class Session {
5614
5717
  finally {
5615
5718
  if (timeoutHandle) {
5616
5719
  clearTimeout(timeoutHandle);
5617
- }
5618
- }
5619
- }
5620
- /**
5621
- * Handle audio chunk for buffering and transcription
5622
- */
5623
- async handleAudioChunk(msg) {
5624
- if (!this.isVoiceMode) {
5625
- this.sessionLogger.warn("Received voice_audio_chunk while voice mode is disabled; transcript will be emitted but voice assistant turn is skipped");
5626
- }
5627
- const chunkFormat = msg.format || "audio/wav";
5628
- if (this.isVoiceMode) {
5629
- if (!this.voiceTurnController) {
5630
- throw new Error("Voice mode is enabled but the voice turn controller is not running");
5631
- }
5632
- const chunkBytes = Buffer.byteLength(msg.audio, "base64");
5633
- this.voiceInputChunkCount += 1;
5634
- this.voiceInputBytes += chunkBytes;
5635
- if (this.voiceInputChunkCount === 1) {
5636
- this.sessionLogger.info({
5637
- format: chunkFormat,
5638
- audioBytes: chunkBytes,
5639
- }, "Received first voice_audio_chunk for active voice mode");
5640
- }
5641
- const now = Date.now();
5642
- if (this.voiceInputChunkCount % 50 === 0 || now - this.voiceInputWindowStartedAt >= 1000) {
5643
- this.sessionLogger.info({
5644
- chunkCount: this.voiceInputChunkCount,
5645
- audioBytes: this.voiceInputBytes,
5646
- windowMs: now - this.voiceInputWindowStartedAt,
5647
- format: chunkFormat,
5648
- }, "Voice input chunk summary");
5649
- this.voiceInputWindowStartedAt = now;
5650
- this.voiceInputChunkCount = 0;
5651
- this.voiceInputBytes = 0;
5652
- }
5653
- await this.voiceTurnController.appendClientChunk({
5654
- audioBase64: msg.audio,
5655
- format: chunkFormat,
5656
- });
5657
- return;
5720
+ }
5658
5721
  }
5659
- const chunkBuffer = Buffer.from(msg.audio, "base64");
5660
- const isPCMChunk = chunkFormat.toLowerCase().includes("pcm");
5722
+ }
5723
+ /**
5724
+ * Handle audio chunk for buffering and transcription
5725
+ */
5726
+ async ensureAudioBufferForFormat(chunkFormat, isPCMChunk) {
5661
5727
  if (!this.audioBuffer) {
5662
5728
  this.audioBuffer = {
5663
5729
  chunks: [],
@@ -5665,8 +5731,8 @@ export class Session {
5665
5731
  isPCM: isPCMChunk,
5666
5732
  totalPCMBytes: 0,
5667
5733
  };
5734
+ return this.audioBuffer;
5668
5735
  }
5669
- // If the format changes mid-stream, flush what we have first
5670
5736
  if (this.audioBuffer.isPCM !== isPCMChunk) {
5671
5737
  this.sessionLogger.debug({
5672
5738
  oldFormat: this.audioBuffer.isPCM ? "pcm" : this.audioBuffer.format,
@@ -5682,19 +5748,61 @@ export class Session {
5682
5748
  isPCM: isPCMChunk,
5683
5749
  totalPCMBytes: 0,
5684
5750
  };
5751
+ return this.audioBuffer;
5685
5752
  }
5686
- else if (!this.audioBuffer.isPCM) {
5687
- // Keep latest format info for non-PCM blobs
5753
+ if (!this.audioBuffer.isPCM) {
5688
5754
  this.audioBuffer.format = chunkFormat;
5689
5755
  }
5690
- this.audioBuffer.chunks.push(chunkBuffer);
5691
- if (this.audioBuffer.isPCM) {
5692
- this.audioBuffer.totalPCMBytes += chunkBuffer.length;
5756
+ return this.audioBuffer;
5757
+ }
5758
+ async forwardAudioChunkToVoiceTurn(msg, chunkFormat) {
5759
+ if (!this.voiceTurnController) {
5760
+ throw new Error("Voice mode is enabled but the voice turn controller is not running");
5761
+ }
5762
+ const chunkBytes = Buffer.byteLength(msg.audio, "base64");
5763
+ this.voiceInputChunkCount += 1;
5764
+ this.voiceInputBytes += chunkBytes;
5765
+ if (this.voiceInputChunkCount === 1) {
5766
+ this.sessionLogger.info({
5767
+ format: chunkFormat,
5768
+ audioBytes: chunkBytes,
5769
+ }, "Received first voice_audio_chunk for active voice mode");
5770
+ }
5771
+ const now = Date.now();
5772
+ if (this.voiceInputChunkCount % 50 === 0 || now - this.voiceInputWindowStartedAt >= 1000) {
5773
+ this.sessionLogger.info({
5774
+ chunkCount: this.voiceInputChunkCount,
5775
+ audioBytes: this.voiceInputBytes,
5776
+ windowMs: now - this.voiceInputWindowStartedAt,
5777
+ format: chunkFormat,
5778
+ }, "Voice input chunk summary");
5779
+ this.voiceInputWindowStartedAt = now;
5780
+ this.voiceInputChunkCount = 0;
5781
+ this.voiceInputBytes = 0;
5782
+ }
5783
+ await this.voiceTurnController.appendClientChunk({
5784
+ audioBase64: msg.audio,
5785
+ format: chunkFormat,
5786
+ });
5787
+ }
5788
+ async handleAudioChunk(msg) {
5789
+ if (!this.isVoiceMode) {
5790
+ this.sessionLogger.warn("Received voice_audio_chunk while voice mode is disabled; transcript will be emitted but voice assistant turn is skipped");
5791
+ }
5792
+ const chunkFormat = msg.format || "audio/wav";
5793
+ if (this.isVoiceMode) {
5794
+ await this.forwardAudioChunkToVoiceTurn(msg, chunkFormat);
5795
+ return;
5796
+ }
5797
+ const chunkBuffer = Buffer.from(msg.audio, "base64");
5798
+ const isPCMChunk = chunkFormat.toLowerCase().includes("pcm");
5799
+ const buffer = await this.ensureAudioBufferForFormat(chunkFormat, isPCMChunk);
5800
+ buffer.chunks.push(chunkBuffer);
5801
+ if (buffer.isPCM) {
5802
+ buffer.totalPCMBytes += chunkBuffer.length;
5693
5803
  }
5694
5804
  // In non-voice mode, use streaming threshold to process chunks
5695
- const reachedStreamingThreshold = !this.isVoiceMode &&
5696
- this.audioBuffer.isPCM &&
5697
- this.audioBuffer.totalPCMBytes >= MIN_STREAMING_SEGMENT_BYTES;
5805
+ const reachedStreamingThreshold = !this.isVoiceMode && buffer.isPCM && buffer.totalPCMBytes >= MIN_STREAMING_SEGMENT_BYTES;
5698
5806
  if (!msg.isLast && reachedStreamingThreshold) {
5699
5807
  return;
5700
5808
  }
@@ -6133,17 +6241,7 @@ export class Session {
6133
6241
  }
6134
6242
  await this.disableVoiceModeForActiveAgent(true);
6135
6243
  this.isVoiceMode = false;
6136
- // Unsubscribe from all terminals
6137
- if (this.unsubscribeTerminalsChanged) {
6138
- this.unsubscribeTerminalsChanged();
6139
- this.unsubscribeTerminalsChanged = null;
6140
- }
6141
- this.subscribedTerminalDirectories.clear();
6142
- for (const unsubscribeExit of this.terminalExitSubscriptions.values()) {
6143
- unsubscribeExit();
6144
- }
6145
- this.terminalExitSubscriptions.clear();
6146
- this.disposeTerminalSubscriptions();
6244
+ this.terminalController.dispose();
6147
6245
  for (const unsubscribe of this.checkoutDiffSubscriptions.values()) {
6148
6246
  unsubscribe();
6149
6247
  }
@@ -6153,26 +6251,6 @@ export class Session {
6153
6251
  }
6154
6252
  this.workspaceGitSubscriptions.clear();
6155
6253
  }
6156
- // ----------------------------------------------------------------------------
6157
- // Terminal Handlers
6158
- // ----------------------------------------------------------------------------
6159
- ensureTerminalExitSubscription(terminal) {
6160
- if (this.terminalExitSubscriptions.has(terminal.id)) {
6161
- return;
6162
- }
6163
- const unsubscribeExit = terminal.onExit(() => {
6164
- this.handleTerminalExited(terminal.id);
6165
- });
6166
- this.terminalExitSubscriptions.set(terminal.id, unsubscribeExit);
6167
- }
6168
- handleTerminalExited(terminalId) {
6169
- const unsubscribeExit = this.terminalExitSubscriptions.get(terminalId);
6170
- if (unsubscribeExit) {
6171
- unsubscribeExit();
6172
- this.terminalExitSubscriptions.delete(terminalId);
6173
- }
6174
- this.detachTerminalStream(terminalId, { emitExit: true });
6175
- }
6176
6254
  emitChatRpcError(request, error) {
6177
6255
  const message = error instanceof Error ? error.message : "Chat request failed";
6178
6256
  const code = error instanceof ChatServiceError ? error.code : "chat_request_failed";
@@ -6261,7 +6339,7 @@ export class Session {
6261
6339
  async handleChatPostRequest(request) {
6262
6340
  try {
6263
6341
  const authorAgentId = request.authorAgentId?.trim() || this.clientId;
6264
- const message = await this.chatService.postMessage({
6342
+ const message = await this.chatService.dispatchMessage({
6265
6343
  room: request.room,
6266
6344
  authorAgentId,
6267
6345
  body: request.body,
@@ -6585,462 +6663,6 @@ export class Session {
6585
6663
  this.emitLoopRpcError(request, error);
6586
6664
  }
6587
6665
  }
6588
- emitTerminalsChangedSnapshot(input) {
6589
- this.emit({
6590
- type: "terminals_changed",
6591
- payload: {
6592
- cwd: input.cwd,
6593
- terminals: input.terminals,
6594
- },
6595
- });
6596
- }
6597
- filterStandaloneTerminals(terminals) {
6598
- return terminals;
6599
- }
6600
- toTerminalInfo(terminal) {
6601
- const title = terminal.getTitle();
6602
- return {
6603
- id: terminal.id,
6604
- name: terminal.name,
6605
- ...(title ? { title } : {}),
6606
- };
6607
- }
6608
- handleTerminalsChanged(event) {
6609
- if (!this.subscribedTerminalDirectories.has(event.cwd)) {
6610
- return;
6611
- }
6612
- this.emitTerminalsChangedSnapshot({
6613
- cwd: event.cwd,
6614
- terminals: this.filterStandaloneTerminals(event.terminals).map((terminal) => ({
6615
- id: terminal.id,
6616
- name: terminal.name,
6617
- ...(terminal.title ? { title: terminal.title } : {}),
6618
- })),
6619
- });
6620
- }
6621
- handleSubscribeTerminalsRequest(msg) {
6622
- this.subscribedTerminalDirectories.add(msg.cwd);
6623
- void this.emitInitialTerminalsChangedSnapshot(msg.cwd);
6624
- }
6625
- handleUnsubscribeTerminalsRequest(msg) {
6626
- this.subscribedTerminalDirectories.delete(msg.cwd);
6627
- }
6628
- async emitInitialTerminalsChangedSnapshot(cwd) {
6629
- if (!this.terminalManager || !this.subscribedTerminalDirectories.has(cwd)) {
6630
- return;
6631
- }
6632
- try {
6633
- const terminals = this.filterStandaloneTerminals(await this.terminalManager.getTerminals(cwd));
6634
- for (const terminal of terminals) {
6635
- this.ensureTerminalExitSubscription(terminal);
6636
- }
6637
- if (!this.subscribedTerminalDirectories.has(cwd)) {
6638
- return;
6639
- }
6640
- this.emitTerminalsChangedSnapshot({
6641
- cwd,
6642
- terminals: terminals.map((terminal) => this.toTerminalInfo(terminal)),
6643
- });
6644
- }
6645
- catch (error) {
6646
- this.sessionLogger.warn({ err: error, cwd }, "Failed to emit initial terminal snapshot");
6647
- }
6648
- }
6649
- async handleListTerminalsRequest(msg) {
6650
- if (!this.terminalManager) {
6651
- this.emit({
6652
- type: "list_terminals_response",
6653
- payload: {
6654
- ...(msg.cwd ? { cwd: msg.cwd } : {}),
6655
- terminals: [],
6656
- requestId: msg.requestId,
6657
- },
6658
- });
6659
- return;
6660
- }
6661
- try {
6662
- const terminals = this.filterStandaloneTerminals(typeof msg.cwd === "string"
6663
- ? await this.terminalManager.getTerminals(msg.cwd)
6664
- : await this.getAllTerminalSessions());
6665
- for (const terminal of terminals) {
6666
- this.ensureTerminalExitSubscription(terminal);
6667
- }
6668
- this.emit({
6669
- type: "list_terminals_response",
6670
- payload: {
6671
- ...(msg.cwd ? { cwd: msg.cwd } : {}),
6672
- terminals: terminals.map((terminal) => this.toTerminalInfo(terminal)),
6673
- requestId: msg.requestId,
6674
- },
6675
- });
6676
- }
6677
- catch (error) {
6678
- this.sessionLogger.error({ err: error, cwd: msg.cwd }, "Failed to list terminals");
6679
- this.emit({
6680
- type: "list_terminals_response",
6681
- payload: {
6682
- ...(msg.cwd ? { cwd: msg.cwd } : {}),
6683
- terminals: [],
6684
- requestId: msg.requestId,
6685
- },
6686
- });
6687
- }
6688
- }
6689
- async getAllTerminalSessions() {
6690
- if (!this.terminalManager) {
6691
- return [];
6692
- }
6693
- const directories = this.terminalManager.listDirectories();
6694
- const terminalsByDirectory = await Promise.all(directories.map((cwd) => this.terminalManager.getTerminals(cwd)));
6695
- return terminalsByDirectory.flat();
6696
- }
6697
- async handleCreateTerminalRequest(msg) {
6698
- if (!this.terminalManager) {
6699
- this.emit({
6700
- type: "create_terminal_response",
6701
- payload: {
6702
- terminal: null,
6703
- error: "Terminal manager not available",
6704
- requestId: msg.requestId,
6705
- },
6706
- });
6707
- return;
6708
- }
6709
- try {
6710
- if (msg.agentId) {
6711
- this.emit({
6712
- type: "create_terminal_response",
6713
- payload: {
6714
- terminal: null,
6715
- error: `Agent-backed terminals are no longer supported for agent ${msg.agentId}`,
6716
- requestId: msg.requestId,
6717
- },
6718
- });
6719
- return;
6720
- }
6721
- const session = await this.terminalManager.createTerminal({
6722
- cwd: msg.cwd,
6723
- name: msg.name,
6724
- command: msg.command,
6725
- args: msg.args,
6726
- });
6727
- this.ensureTerminalExitSubscription(session);
6728
- this.emit({
6729
- type: "create_terminal_response",
6730
- payload: {
6731
- terminal: {
6732
- id: session.id,
6733
- name: session.name,
6734
- cwd: session.cwd,
6735
- ...(session.getTitle() ? { title: session.getTitle() } : {}),
6736
- },
6737
- error: null,
6738
- requestId: msg.requestId,
6739
- },
6740
- });
6741
- }
6742
- catch (error) {
6743
- this.sessionLogger.error({ err: error, cwd: msg.cwd }, "Failed to create terminal");
6744
- this.emit({
6745
- type: "create_terminal_response",
6746
- payload: {
6747
- terminal: null,
6748
- error: error.message,
6749
- requestId: msg.requestId,
6750
- },
6751
- });
6752
- }
6753
- }
6754
- async handleSubscribeTerminalRequest(msg) {
6755
- if (!this.terminalManager) {
6756
- this.emit({
6757
- type: "subscribe_terminal_response",
6758
- payload: {
6759
- terminalId: msg.terminalId,
6760
- error: "Terminal manager not available",
6761
- requestId: msg.requestId,
6762
- },
6763
- });
6764
- return;
6765
- }
6766
- const session = this.terminalManager.getTerminal(msg.terminalId);
6767
- if (!session) {
6768
- this.emit({
6769
- type: "subscribe_terminal_response",
6770
- payload: {
6771
- terminalId: msg.terminalId,
6772
- error: "Terminal not found",
6773
- requestId: msg.requestId,
6774
- },
6775
- });
6776
- return;
6777
- }
6778
- this.ensureTerminalExitSubscription(session);
6779
- const slot = this.bindActiveTerminalStream(session);
6780
- if (slot === null) {
6781
- this.sessionLogger.warn({
6782
- terminalId: msg.terminalId,
6783
- activeTerminalStreamCount: this.activeTerminalStreams.size,
6784
- }, "Terminal stream slot exhaustion");
6785
- this.emit({
6786
- type: "subscribe_terminal_response",
6787
- payload: {
6788
- terminalId: msg.terminalId,
6789
- error: "No terminal stream slots available",
6790
- requestId: msg.requestId,
6791
- },
6792
- });
6793
- return;
6794
- }
6795
- this.emit({
6796
- type: "subscribe_terminal_response",
6797
- payload: {
6798
- terminalId: msg.terminalId,
6799
- slot,
6800
- error: null,
6801
- requestId: msg.requestId,
6802
- },
6803
- });
6804
- const activeStream = this.activeTerminalStreams.get(slot);
6805
- if (activeStream) {
6806
- this.trySendTerminalSnapshot(activeStream);
6807
- }
6808
- }
6809
- handleUnsubscribeTerminalRequest(msg) {
6810
- this.detachTerminalStream(msg.terminalId, { emitExit: false });
6811
- }
6812
- handleTerminalInput(msg) {
6813
- if (!this.terminalManager) {
6814
- return;
6815
- }
6816
- const session = this.terminalManager.getTerminal(msg.terminalId);
6817
- if (!session) {
6818
- this.sessionLogger.warn({ terminalId: msg.terminalId }, "Terminal not found for input");
6819
- return;
6820
- }
6821
- this.ensureTerminalExitSubscription(session);
6822
- if (msg.message.type === "resize") {
6823
- const currentSize = session.getSize();
6824
- if (currentSize.rows === msg.message.rows && currentSize.cols === msg.message.cols) {
6825
- return;
6826
- }
6827
- }
6828
- session.send(msg.message);
6829
- }
6830
- killTrackedTerminal(terminalId, options) {
6831
- this.detachTerminalStream(terminalId, { emitExit: options?.emitExit ?? true });
6832
- this.terminalManager?.killTerminal(terminalId);
6833
- }
6834
- async killTerminalsUnderPath(rootPath) {
6835
- return killWorktreeTerminalsUnderPath({
6836
- isPathWithinRoot: (pathRoot, candidatePath) => this.isPathWithinRoot(pathRoot, candidatePath),
6837
- killTrackedTerminal: (terminalId, options) => this.killTrackedTerminal(terminalId, options),
6838
- detachTerminalStream: (terminalId, options) => void this.detachTerminalStream(terminalId, options),
6839
- sessionLogger: this.sessionLogger,
6840
- terminalManager: this.terminalManager,
6841
- }, rootPath);
6842
- }
6843
- async handleKillTerminalRequest(msg) {
6844
- const result = this.killTerminalForClose(msg.terminalId);
6845
- this.emit({
6846
- type: "kill_terminal_response",
6847
- payload: {
6848
- terminalId: result.terminalId,
6849
- success: result.success,
6850
- requestId: msg.requestId,
6851
- },
6852
- });
6853
- }
6854
- killTerminalForClose(terminalId) {
6855
- if (!this.terminalManager) {
6856
- return {
6857
- terminalId,
6858
- success: false,
6859
- };
6860
- }
6861
- this.killTrackedTerminal(terminalId, { emitExit: true });
6862
- return {
6863
- terminalId,
6864
- success: true,
6865
- };
6866
- }
6867
- async handleCaptureTerminalRequest(msg) {
6868
- if (!this.terminalManager) {
6869
- this.emit({
6870
- type: "capture_terminal_response",
6871
- payload: {
6872
- terminalId: msg.terminalId,
6873
- lines: [],
6874
- totalLines: 0,
6875
- requestId: msg.requestId,
6876
- },
6877
- });
6878
- return;
6879
- }
6880
- const session = this.terminalManager.getTerminal(msg.terminalId);
6881
- if (!session) {
6882
- this.emit({
6883
- type: "capture_terminal_response",
6884
- payload: {
6885
- terminalId: msg.terminalId,
6886
- lines: [],
6887
- totalLines: 0,
6888
- requestId: msg.requestId,
6889
- },
6890
- });
6891
- return;
6892
- }
6893
- this.ensureTerminalExitSubscription(session);
6894
- try {
6895
- const capture = captureTerminalLines(session, {
6896
- start: msg.start,
6897
- end: msg.end,
6898
- stripAnsi: msg.stripAnsi,
6899
- });
6900
- this.emit({
6901
- type: "capture_terminal_response",
6902
- payload: {
6903
- terminalId: msg.terminalId,
6904
- lines: capture.lines,
6905
- totalLines: capture.totalLines,
6906
- requestId: msg.requestId,
6907
- },
6908
- });
6909
- }
6910
- catch (error) {
6911
- this.sessionLogger.error({ err: error, terminalId: msg.terminalId }, "Failed to capture terminal");
6912
- this.emit({
6913
- type: "capture_terminal_response",
6914
- payload: {
6915
- terminalId: msg.terminalId,
6916
- lines: [],
6917
- totalLines: 0,
6918
- requestId: msg.requestId,
6919
- },
6920
- });
6921
- }
6922
- }
6923
- bindActiveTerminalStream(terminal) {
6924
- if (!this.onBinaryMessage) {
6925
- return null;
6926
- }
6927
- const existingSlot = this.terminalIdToSlot.get(terminal.id);
6928
- if (typeof existingSlot === "number") {
6929
- const existingStream = this.activeTerminalStreams.get(existingSlot);
6930
- if (existingStream) {
6931
- existingStream.needsSnapshot = true;
6932
- return existingSlot;
6933
- }
6934
- this.terminalIdToSlot.delete(terminal.id);
6935
- }
6936
- const slot = this.allocateTerminalSlot();
6937
- if (slot === null) {
6938
- return null;
6939
- }
6940
- const activeStream = {
6941
- terminalId: terminal.id,
6942
- slot,
6943
- unsubscribe: () => { },
6944
- needsSnapshot: true,
6945
- outputCoalescer: new TerminalOutputCoalescer({
6946
- timers: { setTimeout, clearTimeout },
6947
- onFlush: ({ payload }) => {
6948
- if (this.activeTerminalStreams.get(slot) !== activeStream) {
6949
- return;
6950
- }
6951
- this.emitBinary(encodeTerminalStreamFrame({
6952
- opcode: TerminalStreamOpcode.Output,
6953
- slot,
6954
- payload,
6955
- }));
6956
- },
6957
- }),
6958
- };
6959
- this.activeTerminalStreams.set(slot, activeStream);
6960
- this.terminalIdToSlot.set(terminal.id, slot);
6961
- activeStream.unsubscribe = terminal.subscribe((message) => {
6962
- if (this.activeTerminalStreams.get(slot) !== activeStream) {
6963
- return;
6964
- }
6965
- if (message.type === "snapshot") {
6966
- activeStream.outputCoalescer.flush();
6967
- activeStream.needsSnapshot = true;
6968
- this.trySendTerminalSnapshot(activeStream);
6969
- return;
6970
- }
6971
- if (message.type === "titleChange") {
6972
- return;
6973
- }
6974
- if (activeStream.needsSnapshot || message.data.length === 0) {
6975
- return;
6976
- }
6977
- activeStream.outputCoalescer.handle(message.data);
6978
- });
6979
- return slot;
6980
- }
6981
- trySendTerminalSnapshot(activeStream) {
6982
- if (this.activeTerminalStreams.get(activeStream.slot) !== activeStream ||
6983
- !activeStream.needsSnapshot) {
6984
- return;
6985
- }
6986
- const terminal = this.terminalManager?.getTerminal(activeStream.terminalId);
6987
- if (!terminal) {
6988
- this.detachTerminalStream(activeStream.terminalId, { emitExit: true });
6989
- return;
6990
- }
6991
- activeStream.outputCoalescer.flush();
6992
- activeStream.needsSnapshot = false;
6993
- this.emitBinary(encodeTerminalStreamFrame({
6994
- opcode: TerminalStreamOpcode.Snapshot,
6995
- slot: activeStream.slot,
6996
- payload: encodeTerminalSnapshotPayload(terminal.getState()),
6997
- }));
6998
- }
6999
- allocateTerminalSlot() {
7000
- for (let attempt = 0; attempt < MAX_TERMINAL_STREAM_SLOTS; attempt += 1) {
7001
- const slot = (this.nextTerminalSlot + attempt) % MAX_TERMINAL_STREAM_SLOTS;
7002
- if (this.activeTerminalStreams.has(slot)) {
7003
- continue;
7004
- }
7005
- this.nextTerminalSlot = (slot + 1) % MAX_TERMINAL_STREAM_SLOTS;
7006
- return slot;
7007
- }
7008
- return null;
7009
- }
7010
- detachTerminalStream(terminalId, options) {
7011
- const slot = this.terminalIdToSlot.get(terminalId);
7012
- if (typeof slot !== "number") {
7013
- return false;
7014
- }
7015
- const activeStream = this.activeTerminalStreams.get(slot);
7016
- if (!activeStream) {
7017
- this.terminalIdToSlot.delete(terminalId);
7018
- return false;
7019
- }
7020
- activeStream.outputCoalescer.flush();
7021
- this.activeTerminalStreams.delete(slot);
7022
- this.terminalIdToSlot.delete(terminalId);
7023
- try {
7024
- activeStream.unsubscribe();
7025
- }
7026
- catch (error) {
7027
- this.sessionLogger.warn({ err: error }, "Failed to unsubscribe terminal stream");
7028
- }
7029
- if (options?.emitExit) {
7030
- this.emit({
7031
- type: "terminal_stream_exit",
7032
- payload: {
7033
- terminalId: activeStream.terminalId,
7034
- },
7035
- });
7036
- }
7037
- return true;
7038
- }
7039
- disposeTerminalSubscriptions() {
7040
- for (const terminalId of [...this.terminalIdToSlot.keys()]) {
7041
- this.detachTerminalStream(terminalId, { emitExit: false });
7042
- }
7043
- }
7044
6666
  }
7045
6667
  // ---------------------------------------------------------------------------
7046
6668
  // Stash handlers