@getpaseo/server 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/dist/scripts/daemon-runner.js +53 -14
  2. package/dist/scripts/daemon-runner.js.map +1 -1
  3. package/dist/scripts/dev-runner.js +9 -16
  4. package/dist/scripts/dev-runner.js.map +1 -1
  5. package/dist/scripts/supervisor.js +40 -13
  6. package/dist/scripts/supervisor.js.map +1 -1
  7. package/dist/server/client/daemon-client.d.ts +63 -6
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +436 -92
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/server/agent/agent-manager.d.ts +13 -1
  12. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-manager.js +404 -39
  14. package/dist/server/server/agent/agent-manager.js.map +1 -1
  15. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  16. package/dist/server/server/agent/agent-metadata-generator.js +13 -4
  17. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  18. package/dist/server/server/agent/agent-projections.d.ts +5 -0
  19. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  20. package/dist/server/server/agent/agent-projections.js +24 -0
  21. package/dist/server/server/agent/agent-projections.js.map +1 -1
  22. package/dist/server/server/agent/agent-response-loop.js +1 -1
  23. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  24. package/dist/server/server/agent/agent-sdk-types.d.ts +20 -0
  25. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  26. package/dist/server/server/agent/agent-sdk-types.js +11 -1
  27. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  28. package/dist/server/server/agent/agent-storage.d.ts +20 -6
  29. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  30. package/dist/server/server/agent/agent-storage.js +43 -72
  31. package/dist/server/server/agent/agent-storage.js.map +1 -1
  32. package/dist/server/server/agent/agent-title-limits.d.ts +3 -0
  33. package/dist/server/server/agent/agent-title-limits.d.ts.map +1 -0
  34. package/dist/server/server/agent/agent-title-limits.js +3 -0
  35. package/dist/server/server/agent/agent-title-limits.js.map +1 -0
  36. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +29 -0
  37. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -0
  38. package/dist/server/server/agent/providers/claude/model-catalog.js +70 -0
  39. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -0
  40. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +44 -0
  41. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -0
  42. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  43. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  44. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  45. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +17 -0
  46. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  47. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  48. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +2 -0
  49. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  50. package/dist/server/server/agent/providers/claude-agent.d.ts +10 -3
  51. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  52. package/dist/server/server/agent/providers/claude-agent.js +1702 -330
  53. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  54. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  55. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +81 -28
  56. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  57. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  58. package/dist/server/server/agent/providers/codex-app-server-agent.js +50 -9
  59. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  60. package/dist/server/server/agent/providers/opencode-agent.d.ts +10 -1
  61. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  62. package/dist/server/server/agent/providers/opencode-agent.js +207 -176
  63. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  64. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +55 -0
  65. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  66. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +1 -0
  67. package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
  68. package/dist/server/server/agent/timeline-projection.d.ts +20 -0
  69. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  70. package/dist/server/server/agent/timeline-projection.js +73 -0
  71. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  72. package/dist/server/server/bootstrap.d.ts +15 -0
  73. package/dist/server/server/bootstrap.d.ts.map +1 -1
  74. package/dist/server/server/bootstrap.js +27 -4
  75. package/dist/server/server/bootstrap.js.map +1 -1
  76. package/dist/server/server/client-message-id.d.ts +3 -0
  77. package/dist/server/server/client-message-id.d.ts.map +1 -0
  78. package/dist/server/server/client-message-id.js +12 -0
  79. package/dist/server/server/client-message-id.js.map +1 -0
  80. package/dist/server/server/file-download/token-store.d.ts +0 -1
  81. package/dist/server/server/file-download/token-store.d.ts.map +1 -1
  82. package/dist/server/server/file-download/token-store.js.map +1 -1
  83. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  84. package/dist/server/server/file-explorer/service.js +56 -36
  85. package/dist/server/server/file-explorer/service.js.map +1 -1
  86. package/dist/server/server/index.js +85 -29
  87. package/dist/server/server/index.js.map +1 -1
  88. package/dist/server/server/logger.d.ts +24 -3
  89. package/dist/server/server/logger.d.ts.map +1 -1
  90. package/dist/server/server/logger.js +157 -21
  91. package/dist/server/server/logger.js.map +1 -1
  92. package/dist/server/server/persisted-config.d.ts +94 -8
  93. package/dist/server/server/persisted-config.d.ts.map +1 -1
  94. package/dist/server/server/persisted-config.js +25 -3
  95. package/dist/server/server/persisted-config.js.map +1 -1
  96. package/dist/server/server/persistence-hooks.js +1 -1
  97. package/dist/server/server/persistence-hooks.js.map +1 -1
  98. package/dist/server/server/pid-lock.d.ts +6 -2
  99. package/dist/server/server/pid-lock.d.ts.map +1 -1
  100. package/dist/server/server/pid-lock.js +7 -10
  101. package/dist/server/server/pid-lock.js.map +1 -1
  102. package/dist/server/server/relay-transport.js +28 -28
  103. package/dist/server/server/relay-transport.js.map +1 -1
  104. package/dist/server/server/session.d.ts +60 -4
  105. package/dist/server/server/session.d.ts.map +1 -1
  106. package/dist/server/server/session.js +854 -190
  107. package/dist/server/server/session.js.map +1 -1
  108. package/dist/server/server/websocket-server.d.ts +24 -5
  109. package/dist/server/server/websocket-server.d.ts.map +1 -1
  110. package/dist/server/server/websocket-server.js +400 -77
  111. package/dist/server/server/websocket-server.js.map +1 -1
  112. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  113. package/dist/server/server/worktree-bootstrap.js +45 -2
  114. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  115. package/dist/server/shared/daemon-endpoints.d.ts +9 -1
  116. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
  117. package/dist/server/shared/daemon-endpoints.js +18 -3
  118. package/dist/server/shared/daemon-endpoints.js.map +1 -1
  119. package/dist/server/shared/messages.d.ts +4432 -380
  120. package/dist/server/shared/messages.d.ts.map +1 -1
  121. package/dist/server/shared/messages.js +139 -6
  122. package/dist/server/shared/messages.js.map +1 -1
  123. package/dist/server/shared/tool-call-display.d.ts.map +1 -1
  124. package/dist/server/shared/tool-call-display.js +7 -0
  125. package/dist/server/shared/tool-call-display.js.map +1 -1
  126. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  127. package/dist/server/terminal/terminal-manager.js +1 -13
  128. package/dist/server/terminal/terminal-manager.js.map +1 -1
  129. package/dist/server/terminal/terminal.d.ts.map +1 -1
  130. package/dist/server/terminal/terminal.js +29 -5
  131. package/dist/server/terminal/terminal.js.map +1 -1
  132. package/dist/server/utils/worktree.d.ts +1 -0
  133. package/dist/server/utils/worktree.d.ts.map +1 -1
  134. package/dist/server/utils/worktree.js +17 -2
  135. package/dist/server/utils/worktree.js.map +1 -1
  136. package/dist/src/server/agent/activity-curator.js +228 -0
  137. package/dist/src/server/agent/activity-curator.js.map +1 -0
  138. package/dist/src/server/agent/agent-manager.js +1712 -0
  139. package/dist/src/server/agent/agent-manager.js.map +1 -0
  140. package/dist/src/server/agent/agent-metadata-generator.js +163 -0
  141. package/dist/src/server/agent/agent-metadata-generator.js.map +1 -0
  142. package/dist/src/server/agent/agent-projections.js +262 -0
  143. package/dist/src/server/agent/agent-projections.js.map +1 -0
  144. package/dist/src/server/agent/agent-response-loop.js +304 -0
  145. package/dist/src/server/agent/agent-response-loop.js.map +1 -0
  146. package/dist/src/server/agent/agent-sdk-types.js +12 -0
  147. package/dist/src/server/agent/agent-sdk-types.js.map +1 -0
  148. package/dist/src/server/agent/agent-storage.js +299 -0
  149. package/dist/src/server/agent/agent-storage.js.map +1 -0
  150. package/dist/src/server/agent/agent-title-limits.js +3 -0
  151. package/dist/src/server/agent/agent-title-limits.js.map +1 -0
  152. package/dist/src/server/agent/audio-utils.js +19 -0
  153. package/dist/src/server/agent/audio-utils.js.map +1 -0
  154. package/dist/src/server/agent/dictation-debug.js +50 -0
  155. package/dist/src/server/agent/dictation-debug.js.map +1 -0
  156. package/dist/src/server/agent/mcp-server.js +787 -0
  157. package/dist/src/server/agent/mcp-server.js.map +1 -0
  158. package/dist/src/server/agent/orchestrator-instructions.js +51 -0
  159. package/dist/src/server/agent/orchestrator-instructions.js.map +1 -0
  160. package/dist/src/server/agent/pcm16-resampler.js +63 -0
  161. package/dist/src/server/agent/pcm16-resampler.js.map +1 -0
  162. package/dist/src/server/agent/provider-launch-config.js +83 -0
  163. package/dist/src/server/agent/provider-launch-config.js.map +1 -0
  164. package/dist/src/server/agent/provider-manifest.js +97 -0
  165. package/dist/src/server/agent/provider-manifest.js.map +1 -0
  166. package/dist/src/server/agent/provider-registry.js +45 -0
  167. package/dist/src/server/agent/provider-registry.js.map +1 -0
  168. package/dist/src/server/agent/providers/claude/model-catalog.js +70 -0
  169. package/dist/src/server/agent/providers/claude/model-catalog.js.map +1 -0
  170. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  171. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  172. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +109 -0
  173. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
  174. package/dist/src/server/agent/providers/claude/tool-call-mapper.js +238 -0
  175. package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
  176. package/dist/src/server/agent/providers/claude-agent.js +3747 -0
  177. package/dist/src/server/agent/providers/claude-agent.js.map +1 -0
  178. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  179. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
  180. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +720 -0
  181. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
  182. package/dist/src/server/agent/providers/codex-app-server-agent.js +2601 -0
  183. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -0
  184. package/dist/src/server/agent/providers/codex-rollout-timeline.js +487 -0
  185. package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +1 -0
  186. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
  187. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
  188. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +151 -0
  189. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
  190. package/dist/src/server/agent/providers/opencode-agent.js +905 -0
  191. package/dist/src/server/agent/providers/opencode-agent.js.map +1 -0
  192. package/dist/src/server/agent/providers/tool-call-detail-primitives.js +552 -0
  193. package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
  194. package/dist/src/server/agent/providers/tool-call-mapper-utils.js +109 -0
  195. package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
  196. package/dist/src/server/agent/recordings-debug.js +19 -0
  197. package/dist/src/server/agent/recordings-debug.js.map +1 -0
  198. package/dist/src/server/agent/stt-debug.js +33 -0
  199. package/dist/src/server/agent/stt-debug.js.map +1 -0
  200. package/dist/src/server/agent/stt-manager.js +233 -0
  201. package/dist/src/server/agent/stt-manager.js.map +1 -0
  202. package/dist/src/server/agent/timeline-append.js +27 -0
  203. package/dist/src/server/agent/timeline-append.js.map +1 -0
  204. package/dist/src/server/agent/timeline-projection.js +215 -0
  205. package/dist/src/server/agent/timeline-projection.js.map +1 -0
  206. package/dist/src/server/agent/tool-name-normalization.js +45 -0
  207. package/dist/src/server/agent/tool-name-normalization.js.map +1 -0
  208. package/dist/src/server/agent/tts-debug.js +24 -0
  209. package/dist/src/server/agent/tts-debug.js.map +1 -0
  210. package/dist/src/server/agent/tts-manager.js +249 -0
  211. package/dist/src/server/agent/tts-manager.js.map +1 -0
  212. package/dist/src/server/agent/wait-for-agent-tracker.js +53 -0
  213. package/dist/src/server/agent/wait-for-agent-tracker.js.map +1 -0
  214. package/dist/src/server/agent-attention-policy.js +40 -0
  215. package/dist/src/server/agent-attention-policy.js.map +1 -0
  216. package/dist/src/server/allowed-hosts.js +94 -0
  217. package/dist/src/server/allowed-hosts.js.map +1 -0
  218. package/dist/src/server/bootstrap.js +498 -0
  219. package/dist/src/server/bootstrap.js.map +1 -0
  220. package/dist/src/server/client-message-id.js +12 -0
  221. package/dist/src/server/client-message-id.js.map +1 -0
  222. package/dist/src/server/config.js +84 -0
  223. package/dist/src/server/config.js.map +1 -0
  224. package/dist/src/server/connection-offer.js +60 -0
  225. package/dist/src/server/connection-offer.js.map +1 -0
  226. package/dist/src/server/daemon-keypair.js +40 -0
  227. package/dist/src/server/daemon-keypair.js.map +1 -0
  228. package/dist/src/server/daemon-version.js +22 -0
  229. package/dist/src/server/daemon-version.js.map +1 -0
  230. package/dist/src/server/dictation/dictation-stream-manager.js +568 -0
  231. package/dist/src/server/dictation/dictation-stream-manager.js.map +1 -0
  232. package/dist/src/server/file-download/token-store.js +40 -0
  233. package/dist/src/server/file-download/token-store.js.map +1 -0
  234. package/dist/src/server/file-explorer/service.js +183 -0
  235. package/dist/src/server/file-explorer/service.js.map +1 -0
  236. package/dist/src/server/json-utils.js +45 -0
  237. package/dist/src/server/json-utils.js.map +1 -0
  238. package/dist/src/server/messages.js +29 -0
  239. package/dist/src/server/messages.js.map +1 -0
  240. package/dist/src/server/package-version.js +47 -0
  241. package/dist/src/server/package-version.js.map +1 -0
  242. package/dist/src/server/paseo-home.js +19 -0
  243. package/dist/src/server/paseo-home.js.map +1 -0
  244. package/dist/src/server/path-utils.js +20 -0
  245. package/dist/src/server/path-utils.js.map +1 -0
  246. package/dist/src/server/persisted-config.js +259 -0
  247. package/dist/src/server/persisted-config.js.map +1 -0
  248. package/dist/src/server/persistence-hooks.js +60 -0
  249. package/dist/src/server/persistence-hooks.js.map +1 -0
  250. package/dist/src/server/pid-lock.js +126 -0
  251. package/dist/src/server/pid-lock.js.map +1 -0
  252. package/dist/src/server/push/push-service.js +68 -0
  253. package/dist/src/server/push/push-service.js.map +1 -0
  254. package/dist/src/server/push/token-store.js +70 -0
  255. package/dist/src/server/push/token-store.js.map +1 -0
  256. package/dist/src/server/relay-transport.js +457 -0
  257. package/dist/src/server/relay-transport.js.map +1 -0
  258. package/dist/src/server/server-id.js +63 -0
  259. package/dist/src/server/server-id.js.map +1 -0
  260. package/dist/src/server/session.js +5947 -0
  261. package/dist/src/server/session.js.map +1 -0
  262. package/dist/src/server/speech/audio.js +101 -0
  263. package/dist/src/server/speech/audio.js.map +1 -0
  264. package/dist/src/server/speech/provider-resolver.js +7 -0
  265. package/dist/src/server/speech/provider-resolver.js.map +1 -0
  266. package/dist/src/server/speech/providers/local/config.js +83 -0
  267. package/dist/src/server/speech/providers/local/config.js.map +1 -0
  268. package/dist/src/server/speech/providers/local/models.js +17 -0
  269. package/dist/src/server/speech/providers/local/models.js.map +1 -0
  270. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +422 -0
  271. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -0
  272. package/dist/src/server/speech/providers/local/runtime.js +253 -0
  273. package/dist/src/server/speech/providers/local/runtime.js.map +1 -0
  274. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +166 -0
  275. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +1 -0
  276. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +165 -0
  277. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +1 -0
  278. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +68 -0
  279. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -0
  280. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +79 -0
  281. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -0
  282. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +11 -0
  283. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +1 -0
  284. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +102 -0
  285. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -0
  286. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +131 -0
  287. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -0
  288. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +132 -0
  289. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -0
  290. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +112 -0
  291. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -0
  292. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +140 -0
  293. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -0
  294. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +95 -0
  295. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -0
  296. package/dist/src/server/speech/providers/openai/config.js +99 -0
  297. package/dist/src/server/speech/providers/openai/config.js.map +1 -0
  298. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +165 -0
  299. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +1 -0
  300. package/dist/src/server/speech/providers/openai/runtime.js +114 -0
  301. package/dist/src/server/speech/providers/openai/runtime.js.map +1 -0
  302. package/dist/src/server/speech/providers/openai/stt.js +208 -0
  303. package/dist/src/server/speech/providers/openai/stt.js.map +1 -0
  304. package/dist/src/server/speech/providers/openai/tts.js +46 -0
  305. package/dist/src/server/speech/providers/openai/tts.js.map +1 -0
  306. package/dist/src/server/speech/speech-config-resolver.js +85 -0
  307. package/dist/src/server/speech/speech-config-resolver.js.map +1 -0
  308. package/dist/src/server/speech/speech-provider.js +2 -0
  309. package/dist/src/server/speech/speech-provider.js.map +1 -0
  310. package/dist/src/server/speech/speech-runtime.js +497 -0
  311. package/dist/src/server/speech/speech-runtime.js.map +1 -0
  312. package/dist/src/server/speech/speech-types.js +8 -0
  313. package/dist/src/server/speech/speech-types.js.map +1 -0
  314. package/dist/src/server/utils/diff-highlighter.js +244 -0
  315. package/dist/src/server/utils/diff-highlighter.js.map +1 -0
  316. package/dist/src/server/utils/syntax-highlighter.js +145 -0
  317. package/dist/src/server/utils/syntax-highlighter.js.map +1 -0
  318. package/dist/src/server/voice-config.js +51 -0
  319. package/dist/src/server/voice-config.js.map +1 -0
  320. package/dist/src/server/voice-mcp-bridge-command.js +31 -0
  321. package/dist/src/server/voice-mcp-bridge-command.js.map +1 -0
  322. package/dist/src/server/voice-mcp-bridge.js +109 -0
  323. package/dist/src/server/voice-mcp-bridge.js.map +1 -0
  324. package/dist/src/server/voice-permission-policy.js +13 -0
  325. package/dist/src/server/voice-permission-policy.js.map +1 -0
  326. package/dist/src/server/voice-types.js +2 -0
  327. package/dist/src/server/voice-types.js.map +1 -0
  328. package/dist/src/server/websocket-server.js +967 -0
  329. package/dist/src/server/websocket-server.js.map +1 -0
  330. package/dist/src/server/worktree-bootstrap.js +497 -0
  331. package/dist/src/server/worktree-bootstrap.js.map +1 -0
  332. package/dist/src/shared/agent-attention-notification.js +130 -0
  333. package/dist/src/shared/agent-attention-notification.js.map +1 -0
  334. package/dist/src/shared/agent-lifecycle.js +8 -0
  335. package/dist/src/shared/agent-lifecycle.js.map +1 -0
  336. package/dist/src/shared/binary-mux.js +114 -0
  337. package/dist/src/shared/binary-mux.js.map +1 -0
  338. package/dist/src/shared/connection-offer.js +17 -0
  339. package/dist/src/shared/connection-offer.js.map +1 -0
  340. package/dist/src/shared/daemon-endpoints.js +113 -0
  341. package/dist/src/shared/daemon-endpoints.js.map +1 -0
  342. package/dist/src/shared/messages.js +2001 -0
  343. package/dist/src/shared/messages.js.map +1 -0
  344. package/dist/src/shared/path-utils.js +16 -0
  345. package/dist/src/shared/path-utils.js.map +1 -0
  346. package/dist/src/shared/tool-call-display.js +93 -0
  347. package/dist/src/shared/tool-call-display.js.map +1 -0
  348. package/dist/src/terminal/terminal-manager.js +136 -0
  349. package/dist/src/terminal/terminal-manager.js.map +1 -0
  350. package/dist/src/terminal/terminal.js +410 -0
  351. package/dist/src/terminal/terminal.js.map +1 -0
  352. package/dist/src/utils/checkout-git.js +1397 -0
  353. package/dist/src/utils/checkout-git.js.map +1 -0
  354. package/dist/src/utils/directory-suggestions.js +655 -0
  355. package/dist/src/utils/directory-suggestions.js.map +1 -0
  356. package/dist/src/utils/path.js +15 -0
  357. package/dist/src/utils/path.js.map +1 -0
  358. package/dist/src/utils/project-icon.js +391 -0
  359. package/dist/src/utils/project-icon.js.map +1 -0
  360. package/dist/src/utils/worktree-metadata.js +116 -0
  361. package/dist/src/utils/worktree-metadata.js.map +1 -0
  362. package/dist/src/utils/worktree.js +741 -0
  363. package/dist/src/utils/worktree.js.map +1 -0
  364. package/package.json +15 -7
@@ -17,9 +17,10 @@ import { buildConfigOverrides, buildSessionConfig, extractTimestamps } from './p
17
17
  import { experimental_createMCPClient } from 'ai';
18
18
  import { buildProviderRegistry } from './agent/provider-registry.js';
19
19
  import { scheduleAgentMetadataGeneration } from './agent/agent-metadata-generator.js';
20
- import { toAgentPayload } from './agent/agent-projections.js';
20
+ import { resolveEffectiveThinkingOptionId, toAgentPayload } from './agent/agent-projections.js';
21
+ import { MAX_EXPLICIT_AGENT_TITLE_CHARS } from './agent/agent-title-limits.js';
21
22
  import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from './agent/timeline-append.js';
22
- import { projectTimelineRows } from './agent/timeline-projection.js';
23
+ import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from './agent/timeline-projection.js';
23
24
  import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from './agent/agent-response-loop.js';
24
25
  import { isValidAgentProvider, AGENT_PROVIDER_IDS } from './agent/provider-manifest.js';
25
26
  import { buildVoiceAgentMcpServerConfig, buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, } from './voice-config.js';
@@ -32,20 +33,46 @@ import { getProjectIcon } from '../utils/project-icon.js';
32
33
  import { expandTilde } from '../utils/path.js';
33
34
  import { searchHomeDirectories, searchWorkspaceEntries } from '../utils/directory-suggestions.js';
34
35
  import { ensureLocalSpeechModels, getLocalSpeechModelDir, listLocalSpeechModels, } from './speech/providers/local/models.js';
36
+ import { resolveClientMessageId } from './client-message-id.js';
35
37
  const execAsync = promisify(exec);
38
+ const MAX_INITIAL_AGENT_TITLE_CHARS = Math.min(60, MAX_EXPLICIT_AGENT_TITLE_CHARS);
36
39
  const READ_ONLY_GIT_ENV = {
37
40
  ...process.env,
38
41
  GIT_OPTIONAL_LOCKS: '0',
39
42
  };
40
43
  const pendingAgentInitializations = new Map();
41
- let restartRequested = false;
42
44
  const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
43
- const RESTART_EXIT_DELAY_MS = 250;
44
45
  const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
45
46
  const CHECKOUT_DIFF_FALLBACK_REFRESH_MS = 5000;
46
47
  const TERMINAL_STREAM_WINDOW_BYTES = 256 * 1024;
47
48
  const TERMINAL_STREAM_MAX_PENDING_BYTES = 2 * 1024 * 1024;
48
49
  const TERMINAL_STREAM_MAX_PENDING_CHUNKS = 2048;
50
+ function deriveInitialAgentTitle(prompt) {
51
+ const firstContentLine = prompt
52
+ .split(/\r?\n/)
53
+ .map((line) => line.trim())
54
+ .find((line) => line.length > 0);
55
+ if (!firstContentLine) {
56
+ return null;
57
+ }
58
+ const normalized = firstContentLine.replace(/\s+/g, ' ').trim();
59
+ if (!normalized) {
60
+ return null;
61
+ }
62
+ const clamped = normalized.slice(0, MAX_INITIAL_AGENT_TITLE_CHARS).trim();
63
+ return clamped.length > 0 ? clamped : null;
64
+ }
65
+ export function resolveCreateAgentTitles(options) {
66
+ const explicitTitle = typeof options.configTitle === 'string' && options.configTitle.trim().length > 0
67
+ ? options.configTitle.trim()
68
+ : null;
69
+ const trimmedPrompt = options.initialPrompt?.trim();
70
+ const provisionalTitle = explicitTitle ?? (trimmedPrompt ? deriveInitialAgentTitle(trimmedPrompt) : null);
71
+ return {
72
+ explicitTitle,
73
+ provisionalTitle,
74
+ };
75
+ }
49
76
  function deriveRemoteProjectKey(remoteUrl) {
50
77
  if (!remoteUrl) {
51
78
  return null;
@@ -92,10 +119,9 @@ function deriveProjectGroupingKey(options) {
92
119
  if (remoteKey) {
93
120
  return remoteKey;
94
121
  }
95
- const worktreeMarker = '.paseo/worktrees/';
96
- const idx = options.cwd.indexOf(worktreeMarker);
97
- if (idx !== -1) {
98
- return options.cwd.slice(0, idx).replace(/\/$/, '');
122
+ const mainRepoRoot = options.mainRepoRoot?.trim();
123
+ if (options.isPaseoOwnedWorktree && mainRepoRoot) {
124
+ return mainRepoRoot;
99
125
  }
100
126
  return options.cwd;
101
127
  }
@@ -213,6 +239,7 @@ export class Session {
213
239
  this.agentTools = null;
214
240
  this.unsubscribeAgentEvents = null;
215
241
  this.agentUpdatesSubscription = null;
242
+ this.workspaceUpdatesSubscription = null;
216
243
  this.clientActivity = null;
217
244
  this.MOBILE_BACKGROUND_STREAM_GRACE_MS = 60000;
218
245
  this.subscribedTerminalDirectories = new Set();
@@ -226,11 +253,19 @@ export class Session {
226
253
  this.checkoutDiffTargets = new Map();
227
254
  this.voiceModeAgentId = null;
228
255
  this.voiceModeBaseConfig = null;
229
- const { clientId, onMessage, onBinaryMessage, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
256
+ this.workspaceStatePriority = {
257
+ needs_input: 0,
258
+ failed: 1,
259
+ running: 2,
260
+ attention: 3,
261
+ done: 4,
262
+ };
263
+ const { clientId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
230
264
  this.clientId = clientId;
231
265
  this.sessionId = uuidv4();
232
266
  this.onMessage = onMessage;
233
267
  this.onBinaryMessage = onBinaryMessage ?? null;
268
+ this.onLifecycleIntent = onLifecycleIntent ?? null;
234
269
  this.downloadTokenStore = downloadTokenStore;
235
270
  this.pushTokenStore = pushTokenStore;
236
271
  this.paseoHome = paseoHome;
@@ -296,6 +331,25 @@ export class Session {
296
331
  getClientActivity() {
297
332
  return this.clientActivity;
298
333
  }
334
+ getRuntimeMetrics() {
335
+ let checkoutDiffWatcherCount = 0;
336
+ let checkoutDiffFallbackRefreshTargetCount = 0;
337
+ for (const target of this.checkoutDiffTargets.values()) {
338
+ checkoutDiffWatcherCount += target.watchers.length;
339
+ if (target.fallbackRefreshInterval) {
340
+ checkoutDiffFallbackRefreshTargetCount += 1;
341
+ }
342
+ }
343
+ return {
344
+ checkoutDiffTargetCount: this.checkoutDiffTargets.size,
345
+ checkoutDiffSubscriptionCount: this.checkoutDiffSubscriptions.size,
346
+ checkoutDiffWatcherCount,
347
+ checkoutDiffFallbackRefreshTargetCount,
348
+ terminalDirectorySubscriptionCount: this.subscribedTerminalDirectories.size,
349
+ terminalSubscriptionCount: this.terminalSubscriptions.size,
350
+ terminalStreamCount: this.terminalStreams.size,
351
+ };
352
+ }
299
353
  /**
300
354
  * Send initial state to client after connection
301
355
  */
@@ -326,10 +380,11 @@ export class Session {
326
380
  async interruptAgentIfRunning(agentId) {
327
381
  const snapshot = this.agentManager.getAgent(agentId);
328
382
  if (!snapshot) {
383
+ this.sessionLogger.trace({ agentId }, 'interruptAgentIfRunning: agent not found');
329
384
  throw new Error(`Agent ${agentId} not found`);
330
385
  }
331
386
  if (snapshot.lifecycle !== 'running' && !snapshot.pendingRun) {
332
- this.sessionLogger.debug({ agentId, lifecycle: snapshot.lifecycle, pendingRun: Boolean(snapshot.pendingRun) }, 'interruptAgentIfRunning: not running, skipping');
387
+ this.sessionLogger.trace({ agentId, lifecycle: snapshot.lifecycle, pendingRun: Boolean(snapshot.pendingRun) }, 'interruptAgentIfRunning: skipping because agent is not running');
333
388
  return;
334
389
  }
335
390
  this.sessionLogger.debug({ agentId, lifecycle: snapshot.lifecycle, pendingRun: Boolean(snapshot.pendingRun) }, 'interruptAgentIfRunning: interrupting');
@@ -359,10 +414,15 @@ export class Session {
359
414
  * Start streaming an agent run and forward results via the websocket broadcast
360
415
  */
361
416
  startAgentStream(agentId, prompt, runOptions) {
362
- this.sessionLogger.info({ agentId }, `Starting agent stream for ${agentId}`);
417
+ this.sessionLogger.trace({
418
+ agentId,
419
+ promptType: typeof prompt === 'string' ? 'string' : 'structured',
420
+ hasRunOptions: Boolean(runOptions),
421
+ }, 'startAgentStream: requested');
363
422
  let iterator;
364
423
  try {
365
424
  iterator = this.agentManager.streamAgent(agentId, prompt, runOptions);
425
+ this.sessionLogger.trace({ agentId }, 'startAgentStream: streamAgent returned iterator');
366
426
  }
367
427
  catch (error) {
368
428
  this.handleAgentRunError(agentId, error, 'Failed to start agent run');
@@ -374,8 +434,10 @@ export class Session {
374
434
  for await (const _ of iterator) {
375
435
  // Events are forwarded via the session's AgentManager subscription.
376
436
  }
437
+ this.sessionLogger.trace({ agentId }, 'startAgentStream: iterator drained');
377
438
  }
378
439
  catch (error) {
440
+ this.sessionLogger.trace({ agentId, err: error }, 'startAgentStream: iterator threw');
379
441
  this.handleAgentRunError(agentId, error, 'Agent stream failed');
380
442
  }
381
443
  })();
@@ -498,7 +560,7 @@ export class Session {
498
560
  }
499
561
  async buildAgentPayload(agent) {
500
562
  const storedRecord = await this.agentStorage.get(agent.id);
501
- const title = storedRecord?.title ?? null;
563
+ const title = storedRecord?.title ?? storedRecord?.config?.title ?? null;
502
564
  const payload = toAgentPayload(agent, { title });
503
565
  payload.archivedAt = storedRecord?.archivedAt ?? null;
504
566
  return payload;
@@ -516,12 +578,33 @@ export class Session {
516
578
  const updatedAt = new Date(record.lastActivityAt ?? record.updatedAt);
517
579
  const lastUserMessageAt = record.lastUserMessageAt ? new Date(record.lastUserMessageAt) : null;
518
580
  const provider = coerceAgentProvider(this.sessionLogger, record.provider, record.id);
581
+ const runtimeInfo = record.runtimeInfo
582
+ ? {
583
+ provider: coerceAgentProvider(this.sessionLogger, record.runtimeInfo.provider, record.id),
584
+ sessionId: record.runtimeInfo.sessionId,
585
+ ...(Object.prototype.hasOwnProperty.call(record.runtimeInfo, 'model')
586
+ ? { model: record.runtimeInfo.model ?? null }
587
+ : {}),
588
+ ...(Object.prototype.hasOwnProperty.call(record.runtimeInfo, 'thinkingOptionId')
589
+ ? { thinkingOptionId: record.runtimeInfo.thinkingOptionId ?? null }
590
+ : {}),
591
+ ...(Object.prototype.hasOwnProperty.call(record.runtimeInfo, 'modeId')
592
+ ? { modeId: record.runtimeInfo.modeId ?? null }
593
+ : {}),
594
+ ...(record.runtimeInfo.extra ? { extra: record.runtimeInfo.extra } : {}),
595
+ }
596
+ : undefined;
519
597
  return {
520
598
  id: record.id,
521
599
  provider,
522
600
  cwd: record.cwd,
523
601
  model: record.config?.model ?? null,
524
602
  thinkingOptionId: record.config?.thinkingOptionId ?? null,
603
+ effectiveThinkingOptionId: resolveEffectiveThinkingOptionId({
604
+ runtimeInfo,
605
+ configuredThinkingOptionId: record.config?.thinkingOptionId ?? null,
606
+ }),
607
+ ...(runtimeInfo ? { runtimeInfo } : {}),
525
608
  createdAt: createdAt.toISOString(),
526
609
  updatedAt: updatedAt.toISOString(),
527
610
  lastUserMessageAt: lastUserMessageAt ? lastUserMessageAt.toISOString() : null,
@@ -533,7 +616,7 @@ export class Session {
533
616
  persistence: toAgentPersistenceHandle(this.sessionLogger, record.persistence),
534
617
  lastUsage: undefined,
535
618
  lastError: undefined,
536
- title: record.title ?? null,
619
+ title: record.title ?? record.config?.title ?? null,
537
620
  requiresAttention: record.requiresAttention ?? false,
538
621
  attentionReason: record.attentionReason ?? null,
539
622
  attentionTimestamp: record.attentionTimestamp ?? null,
@@ -592,6 +675,19 @@ export class Session {
592
675
  if (!includeArchived && agent.archivedAt) {
593
676
  return false;
594
677
  }
678
+ if (filter?.thinkingOptionId !== undefined) {
679
+ const expectedThinkingOptionId = resolveEffectiveThinkingOptionId({
680
+ configuredThinkingOptionId: filter.thinkingOptionId ?? null,
681
+ });
682
+ const resolvedThinkingOptionId = agent.effectiveThinkingOptionId ??
683
+ resolveEffectiveThinkingOptionId({
684
+ runtimeInfo: agent.runtimeInfo,
685
+ configuredThinkingOptionId: agent.thinkingOptionId ?? null,
686
+ });
687
+ if (resolvedThinkingOptionId !== expectedThinkingOptionId) {
688
+ return false;
689
+ }
690
+ }
595
691
  if (filter?.statuses && filter.statuses.length > 0) {
596
692
  const statuses = new Set(filter.statuses);
597
693
  if (!statuses.has(agent.status)) {
@@ -689,6 +785,8 @@ export class Session {
689
785
  const projectKey = deriveProjectGroupingKey({
690
786
  cwd,
691
787
  remoteUrl: checkout.remoteUrl,
788
+ isPaseoOwnedWorktree: checkout.isPaseoOwnedWorktree,
789
+ mainRepoRoot: checkout.mainRepoRoot,
692
790
  });
693
791
  return {
694
792
  projectKey,
@@ -699,28 +797,29 @@ export class Session {
699
797
  async forwardAgentUpdate(agent) {
700
798
  try {
701
799
  const subscription = this.agentUpdatesSubscription;
702
- if (!subscription) {
703
- return;
704
- }
705
800
  const payload = await this.buildAgentPayload(agent);
706
- const project = await this.buildProjectPlacement(payload.cwd);
707
- const matches = this.matchesAgentFilter({
708
- agent: payload,
709
- project,
710
- filter: subscription.filter,
711
- });
712
- if (matches) {
713
- this.bufferOrEmitAgentUpdate(subscription, {
714
- kind: 'upsert',
801
+ if (subscription) {
802
+ const project = await this.buildProjectPlacement(payload.cwd);
803
+ const matches = this.matchesAgentFilter({
715
804
  agent: payload,
716
805
  project,
806
+ filter: subscription.filter,
717
807
  });
718
- return;
808
+ if (matches) {
809
+ this.bufferOrEmitAgentUpdate(subscription, {
810
+ kind: 'upsert',
811
+ agent: payload,
812
+ project,
813
+ });
814
+ }
815
+ else {
816
+ this.bufferOrEmitAgentUpdate(subscription, {
817
+ kind: 'remove',
818
+ agentId: payload.id,
819
+ });
820
+ }
719
821
  }
720
- this.bufferOrEmitAgentUpdate(subscription, {
721
- kind: 'remove',
722
- agentId: payload.id,
723
- });
822
+ await this.emitWorkspaceUpdateForCwd(payload.cwd);
724
823
  }
725
824
  catch (error) {
726
825
  this.sessionLogger.error({ err: error }, 'Failed to emit agent update');
@@ -744,6 +843,9 @@ export class Session {
744
843
  case 'fetch_agents_request':
745
844
  await this.handleFetchAgents(msg);
746
845
  break;
846
+ case 'fetch_workspaces_request':
847
+ await this.handleFetchWorkspacesRequest(msg);
848
+ break;
747
849
  case 'fetch_agent_request':
748
850
  await this.handleFetchAgent(msg.agentId, msg.requestId);
749
851
  break;
@@ -813,6 +915,9 @@ export class Session {
813
915
  case 'restart_server_request':
814
916
  await this.handleRestartServerRequest(msg.requestId, msg.reason);
815
917
  break;
918
+ case 'shutdown_server_request':
919
+ await this.handleShutdownServerRequest(msg.requestId);
920
+ break;
816
921
  case 'fetch_agent_timeline_request':
817
922
  await this.handleFetchAgentTimelineRequest(msg);
818
923
  break;
@@ -1032,11 +1137,6 @@ export class Session {
1032
1137
  this.sessionLogger.warn({ streamId: frame.streamId, messageType: frame.messageType }, 'Unhandled terminal binary frame');
1033
1138
  }
1034
1139
  async handleRestartServerRequest(requestId, reason) {
1035
- if (restartRequested) {
1036
- this.sessionLogger.debug('Restart already requested, ignoring duplicate');
1037
- return;
1038
- }
1039
- restartRequested = true;
1040
1140
  const payload = {
1041
1141
  status: 'restart_requested',
1042
1142
  clientId: this.clientId,
@@ -1050,19 +1150,45 @@ export class Session {
1050
1150
  type: 'status',
1051
1151
  payload,
1052
1152
  });
1053
- if (typeof process.send === 'function') {
1054
- process.send({
1055
- type: 'paseo:restart',
1056
- ...(reason ? { reason } : {}),
1057
- });
1153
+ this.emitLifecycleIntent({
1154
+ type: 'restart',
1155
+ clientId: this.clientId,
1156
+ requestId,
1157
+ ...(reason ? { reason } : {}),
1158
+ });
1159
+ }
1160
+ async handleShutdownServerRequest(requestId) {
1161
+ this.sessionLogger.warn('Shutdown requested via websocket');
1162
+ this.emit({
1163
+ type: 'status',
1164
+ payload: {
1165
+ status: 'shutdown_requested',
1166
+ clientId: this.clientId,
1167
+ requestId,
1168
+ },
1169
+ });
1170
+ this.emitLifecycleIntent({
1171
+ type: 'shutdown',
1172
+ clientId: this.clientId,
1173
+ requestId,
1174
+ });
1175
+ }
1176
+ emitLifecycleIntent(intent) {
1177
+ if (!this.onLifecycleIntent) {
1058
1178
  return;
1059
1179
  }
1060
- setTimeout(() => {
1061
- process.exit(0);
1062
- }, RESTART_EXIT_DELAY_MS);
1180
+ try {
1181
+ this.onLifecycleIntent(intent);
1182
+ }
1183
+ catch (error) {
1184
+ this.sessionLogger.error({ err: error, intent }, 'Lifecycle intent handler failed');
1185
+ }
1063
1186
  }
1064
1187
  async handleDeleteAgentRequest(agentId, requestId) {
1065
1188
  this.sessionLogger.info({ agentId }, `Deleting agent ${agentId} from registry`);
1189
+ const knownCwd = this.agentManager.getAgent(agentId)?.cwd ??
1190
+ (await this.agentStorage.get(agentId))?.cwd ??
1191
+ null;
1066
1192
  // Prevent the persistence hook from re-creating the record while we close/delete.
1067
1193
  this.agentStorage.beginDelete(agentId);
1068
1194
  try {
@@ -1090,9 +1216,32 @@ export class Session {
1090
1216
  agentId,
1091
1217
  });
1092
1218
  }
1219
+ if (knownCwd) {
1220
+ await this.emitWorkspaceUpdateForCwd(knownCwd);
1221
+ }
1093
1222
  }
1094
1223
  async handleArchiveAgentRequest(agentId, requestId) {
1095
1224
  this.sessionLogger.info({ agentId }, `Archiving agent ${agentId}`);
1225
+ const { archivedAt, archivedRecord } = await this.archiveAgentState(agentId);
1226
+ this.emit({
1227
+ type: 'agent_archived',
1228
+ payload: {
1229
+ agentId,
1230
+ archivedAt,
1231
+ requestId,
1232
+ },
1233
+ });
1234
+ await this.maybeArchiveWorktreeAfterLastAgentArchived({
1235
+ archivedAgentId: agentId,
1236
+ archivedAgentCwd: archivedRecord.cwd,
1237
+ requestId,
1238
+ });
1239
+ }
1240
+ async archiveAgentState(agentId) {
1241
+ if (this.agentManager.getAgent(agentId)) {
1242
+ await this.interruptAgentIfRunning(agentId);
1243
+ await this.agentManager.clearAgentAttention(agentId).catch(() => undefined);
1244
+ }
1096
1245
  const archivedAt = new Date().toISOString();
1097
1246
  const existing = await this.agentStorage.get(agentId);
1098
1247
  let archivedRecord = existing;
@@ -1102,7 +1251,6 @@ export class Session {
1102
1251
  throw new Error(`Agent not found: ${agentId}`);
1103
1252
  }
1104
1253
  await this.agentStorage.applySnapshot(liveAgent, {
1105
- title: liveAgent.config.title ?? null,
1106
1254
  internal: liveAgent.internal,
1107
1255
  });
1108
1256
  archivedRecord = await this.agentStorage.get(agentId);
@@ -1110,25 +1258,41 @@ export class Session {
1110
1258
  throw new Error(`Agent not found in storage after snapshot: ${agentId}`);
1111
1259
  }
1112
1260
  }
1113
- archivedRecord = {
1261
+ const normalizedStatus = archivedRecord.lastStatus === 'running' || archivedRecord.lastStatus === 'initializing'
1262
+ ? 'idle'
1263
+ : archivedRecord.lastStatus;
1264
+ const nextRecord = {
1114
1265
  ...archivedRecord,
1115
1266
  archivedAt,
1267
+ lastStatus: normalizedStatus,
1268
+ requiresAttention: false,
1269
+ attentionReason: null,
1270
+ attentionTimestamp: null,
1116
1271
  };
1117
- await this.agentStorage.upsert(archivedRecord);
1272
+ await this.agentStorage.upsert(nextRecord);
1118
1273
  this.agentManager.notifyAgentState(agentId);
1119
- this.emit({
1120
- type: 'agent_archived',
1121
- payload: {
1122
- agentId,
1123
- archivedAt,
1124
- requestId,
1125
- },
1126
- });
1127
- await this.maybeArchiveWorktreeAfterLastAgentArchived({
1128
- archivedAgentId: agentId,
1129
- archivedAgentCwd: archivedRecord.cwd,
1130
- requestId,
1274
+ return { archivedAt, archivedRecord: nextRecord };
1275
+ }
1276
+ async unarchiveAgentState(agentId) {
1277
+ const record = await this.agentStorage.get(agentId);
1278
+ if (!record || !record.archivedAt) {
1279
+ return false;
1280
+ }
1281
+ await this.agentStorage.upsert({
1282
+ ...record,
1283
+ archivedAt: null,
1131
1284
  });
1285
+ this.agentManager.notifyAgentState(agentId);
1286
+ return true;
1287
+ }
1288
+ async unarchiveAgentByHandle(handle) {
1289
+ const records = await this.agentStorage.list();
1290
+ const matched = records.find((record) => record.persistence?.provider === handle.provider &&
1291
+ record.persistence?.sessionId === handle.sessionId);
1292
+ if (!matched) {
1293
+ return;
1294
+ }
1295
+ await this.unarchiveAgentState(matched.id);
1132
1296
  }
1133
1297
  async handleUpdateAgentRequest(agentId, name, labels, requestId) {
1134
1298
  this.sessionLogger.info({
@@ -1601,6 +1765,7 @@ export class Session {
1601
1765
  */
1602
1766
  async handleSendAgentMessage(agentId, text, messageId, images, runOptions) {
1603
1767
  this.sessionLogger.info({ agentId, textPreview: text.substring(0, 50), imageCount: images?.length ?? 0 }, `Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ''}`);
1768
+ await this.unarchiveAgentState(agentId);
1604
1769
  try {
1605
1770
  await this.ensureAgentLoaded(agentId);
1606
1771
  }
@@ -1631,27 +1796,47 @@ export class Session {
1631
1796
  * Handle create agent request
1632
1797
  */
1633
1798
  async handleCreateAgentRequest(msg) {
1634
- const { config, worktreeName, requestId, initialPrompt, outputSchema, git, images, labels } = msg;
1799
+ const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, git, images, labels, } = msg;
1635
1800
  this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ''}`);
1636
1801
  try {
1637
- const { sessionConfig, worktreeConfig } = await this.buildAgentSessionConfig(config, git, worktreeName, labels);
1802
+ const trimmedPrompt = initialPrompt?.trim();
1803
+ const { explicitTitle, provisionalTitle } = resolveCreateAgentTitles({
1804
+ configTitle: config.title,
1805
+ initialPrompt: trimmedPrompt,
1806
+ });
1807
+ const resolvedConfig = {
1808
+ ...config,
1809
+ ...(provisionalTitle ? { title: provisionalTitle } : {}),
1810
+ };
1811
+ const { sessionConfig, worktreeConfig } = await this.buildAgentSessionConfig(resolvedConfig, git, worktreeName, labels);
1638
1812
  const snapshot = await this.agentManager.createAgent(sessionConfig, undefined, { labels });
1639
1813
  await this.forwardAgentUpdate(snapshot);
1640
- const trimmedPrompt = initialPrompt?.trim();
1814
+ if (requestId) {
1815
+ const agentPayload = await this.getAgentPayloadById(snapshot.id);
1816
+ if (!agentPayload) {
1817
+ throw new Error(`Agent ${snapshot.id} not found after creation`);
1818
+ }
1819
+ this.emit({
1820
+ type: 'status',
1821
+ payload: {
1822
+ status: 'agent_created',
1823
+ agentId: snapshot.id,
1824
+ requestId,
1825
+ agent: agentPayload,
1826
+ },
1827
+ });
1828
+ }
1641
1829
  if (trimmedPrompt) {
1642
1830
  scheduleAgentMetadataGeneration({
1643
1831
  agentManager: this.agentManager,
1644
1832
  agentId: snapshot.id,
1645
1833
  cwd: snapshot.cwd,
1646
1834
  initialPrompt: trimmedPrompt,
1647
- explicitTitle: snapshot.config.title,
1835
+ explicitTitle,
1648
1836
  paseoHome: this.paseoHome,
1649
1837
  logger: this.sessionLogger,
1650
1838
  });
1651
- try {
1652
- await this.handleSendAgentMessage(snapshot.id, trimmedPrompt, uuidv4(), images, outputSchema ? { outputSchema } : undefined);
1653
- }
1654
- catch (promptError) {
1839
+ void this.handleSendAgentMessage(snapshot.id, trimmedPrompt, resolveClientMessageId(clientMessageId), images, outputSchema ? { outputSchema } : undefined).catch((promptError) => {
1655
1840
  this.sessionLogger.error({ err: promptError, agentId: snapshot.id }, `Failed to run initial prompt for agent ${snapshot.id}`);
1656
1841
  this.emit({
1657
1842
  type: 'activity_log',
@@ -1662,21 +1847,6 @@ export class Session {
1662
1847
  content: `Initial prompt failed: ${promptError?.message ?? promptError}`,
1663
1848
  },
1664
1849
  });
1665
- }
1666
- }
1667
- if (requestId) {
1668
- const agentPayload = await this.getAgentPayloadById(snapshot.id);
1669
- if (!agentPayload) {
1670
- throw new Error(`Agent ${snapshot.id} not found after creation`);
1671
- }
1672
- this.emit({
1673
- type: 'status',
1674
- payload: {
1675
- status: 'agent_created',
1676
- agentId: snapshot.id,
1677
- requestId,
1678
- agent: agentPayload,
1679
- },
1680
1850
  });
1681
1851
  }
1682
1852
  if (worktreeConfig) {
@@ -1739,7 +1909,9 @@ export class Session {
1739
1909
  }
1740
1910
  this.sessionLogger.info({ sessionId: handle.sessionId, provider: handle.provider }, `Resuming agent ${handle.sessionId} (${handle.provider})`);
1741
1911
  try {
1912
+ await this.unarchiveAgentByHandle(handle);
1742
1913
  const snapshot = await this.agentManager.resumeAgentFromPersistence(handle, overrides);
1914
+ await this.unarchiveAgentState(snapshot.id);
1743
1915
  await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
1744
1916
  await this.forwardAgentUpdate(snapshot);
1745
1917
  const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
@@ -1777,6 +1949,7 @@ export class Session {
1777
1949
  const { agentId, requestId } = msg;
1778
1950
  this.sessionLogger.info({ agentId }, `Refreshing agent ${agentId} from persistence`);
1779
1951
  try {
1952
+ await this.unarchiveAgentState(agentId);
1780
1953
  let snapshot;
1781
1954
  const existing = this.agentManager.getAgent(agentId);
1782
1955
  if (existing) {
@@ -2400,7 +2573,6 @@ export class Session {
2400
2573
  */
2401
2574
  async handleClearAgentAttention(agentId) {
2402
2575
  const agentIds = Array.isArray(agentId) ? agentId : [agentId];
2403
- this.sessionLogger.debug({ agentIds }, `Clearing attention for ${agentIds.length} agent(s): ${agentIds.join(', ')}`);
2404
2576
  try {
2405
2577
  await Promise.all(agentIds.map((id) => this.agentManager.clearAgentAttention(id)));
2406
2578
  }
@@ -3277,6 +3449,7 @@ export class Session {
3277
3449
  payload: {
3278
3450
  worktrees: worktrees.map((entry) => ({
3279
3451
  worktreePath: entry.path,
3452
+ createdAt: entry.createdAt,
3280
3453
  branchName: entry.branchName ?? null,
3281
3454
  head: entry.head ?? null,
3282
3455
  })),
@@ -3358,10 +3531,12 @@ export class Session {
3358
3531
  targetPath = resolvedWorktree.worktreePath;
3359
3532
  }
3360
3533
  const removedAgents = new Set();
3534
+ const affectedWorkspaceCwds = new Set([targetPath]);
3361
3535
  const agents = this.agentManager.listAgents();
3362
3536
  for (const agent of agents) {
3363
3537
  if (this.isPathWithinRoot(targetPath, agent.cwd)) {
3364
3538
  removedAgents.add(agent.id);
3539
+ affectedWorkspaceCwds.add(agent.cwd);
3365
3540
  try {
3366
3541
  await this.agentManager.closeAgent(agent.id);
3367
3542
  }
@@ -3380,6 +3555,7 @@ export class Session {
3380
3555
  for (const record of registryRecords) {
3381
3556
  if (this.isPathWithinRoot(targetPath, record.cwd)) {
3382
3557
  removedAgents.add(record.id);
3558
+ affectedWorkspaceCwds.add(record.cwd);
3383
3559
  try {
3384
3560
  await this.agentStorage.remove(record.id);
3385
3561
  }
@@ -3403,6 +3579,7 @@ export class Session {
3403
3579
  },
3404
3580
  });
3405
3581
  }
3582
+ await this.emitWorkspaceUpdatesForCwds(affectedWorkspaceCwds);
3406
3583
  return Array.from(removedAgents);
3407
3584
  }
3408
3585
  async handlePaseoWorktreeArchiveRequest(msg) {
@@ -3471,38 +3648,36 @@ export class Session {
3471
3648
  }
3472
3649
  }
3473
3650
  /**
3474
- * Handle read-only file explorer requests scoped to an agent's cwd
3651
+ * Handle read-only file explorer requests scoped to a workspace cwd
3475
3652
  */
3476
3653
  async handleFileExplorerRequest(request) {
3477
- const { agentId, path: requestedPath = '.', mode, requestId } = request;
3478
- this.sessionLogger.debug({ agentId, mode, path: requestedPath }, `Handling file explorer request for agent ${agentId} (${mode} ${requestedPath})`);
3654
+ const { cwd: workspaceCwd, path: requestedPath = '.', mode, requestId } = request;
3655
+ const cwd = workspaceCwd.trim();
3656
+ if (!cwd) {
3657
+ this.emit({
3658
+ type: 'file_explorer_response',
3659
+ payload: {
3660
+ cwd: workspaceCwd,
3661
+ path: requestedPath,
3662
+ mode,
3663
+ directory: null,
3664
+ file: null,
3665
+ error: 'cwd is required',
3666
+ requestId,
3667
+ },
3668
+ });
3669
+ return;
3670
+ }
3479
3671
  try {
3480
- const agents = this.agentManager.listAgents();
3481
- const agent = agents.find((a) => a.id === agentId);
3482
- if (!agent) {
3483
- this.emit({
3484
- type: 'file_explorer_response',
3485
- payload: {
3486
- agentId,
3487
- path: requestedPath,
3488
- mode,
3489
- directory: null,
3490
- file: null,
3491
- error: `Agent not found: ${agentId}`,
3492
- requestId,
3493
- },
3494
- });
3495
- return;
3496
- }
3497
3672
  if (mode === 'list') {
3498
3673
  const directory = await listDirectoryEntries({
3499
- root: agent.cwd,
3674
+ root: cwd,
3500
3675
  relativePath: requestedPath,
3501
3676
  });
3502
3677
  this.emit({
3503
3678
  type: 'file_explorer_response',
3504
3679
  payload: {
3505
- agentId,
3680
+ cwd,
3506
3681
  path: directory.path,
3507
3682
  mode,
3508
3683
  directory,
@@ -3514,13 +3689,13 @@ export class Session {
3514
3689
  }
3515
3690
  else {
3516
3691
  const file = await readExplorerFile({
3517
- root: agent.cwd,
3692
+ root: cwd,
3518
3693
  relativePath: requestedPath,
3519
3694
  });
3520
3695
  this.emit({
3521
3696
  type: 'file_explorer_response',
3522
3697
  payload: {
3523
- agentId,
3698
+ cwd,
3524
3699
  path: file.path,
3525
3700
  mode,
3526
3701
  directory: null,
@@ -3532,11 +3707,11 @@ export class Session {
3532
3707
  }
3533
3708
  }
3534
3709
  catch (error) {
3535
- this.sessionLogger.error({ err: error, agentId, path: requestedPath }, `Failed to fulfill file explorer request for agent ${agentId}`);
3710
+ this.sessionLogger.error({ err: error, cwd, path: requestedPath }, `Failed to fulfill file explorer request for workspace ${cwd}`);
3536
3711
  this.emit({
3537
3712
  type: 'file_explorer_response',
3538
3713
  payload: {
3539
- agentId,
3714
+ cwd,
3540
3715
  path: requestedPath,
3541
3716
  mode,
3542
3717
  directory: null,
@@ -3577,36 +3752,34 @@ export class Session {
3577
3752
  }
3578
3753
  }
3579
3754
  /**
3580
- * Handle file download token request scoped to an agent's cwd
3755
+ * Handle file download token request scoped to a workspace cwd
3581
3756
  */
3582
3757
  async handleFileDownloadTokenRequest(request) {
3583
- const { agentId, path: requestedPath, requestId } = request;
3584
- this.sessionLogger.debug({ agentId, path: requestedPath }, `Handling file download token request for agent ${agentId} (${requestedPath})`);
3758
+ const { cwd: workspaceCwd, path: requestedPath, requestId } = request;
3759
+ const cwd = workspaceCwd.trim();
3760
+ if (!cwd) {
3761
+ this.emit({
3762
+ type: 'file_download_token_response',
3763
+ payload: {
3764
+ cwd: workspaceCwd,
3765
+ path: requestedPath,
3766
+ token: null,
3767
+ fileName: null,
3768
+ mimeType: null,
3769
+ size: null,
3770
+ error: 'cwd is required',
3771
+ requestId,
3772
+ },
3773
+ });
3774
+ return;
3775
+ }
3776
+ this.sessionLogger.debug({ cwd, path: requestedPath }, `Handling file download token request for workspace ${cwd} (${requestedPath})`);
3585
3777
  try {
3586
- const agents = this.agentManager.listAgents();
3587
- const agent = agents.find((a) => a.id === agentId);
3588
- if (!agent) {
3589
- this.emit({
3590
- type: 'file_download_token_response',
3591
- payload: {
3592
- agentId,
3593
- path: requestedPath,
3594
- token: null,
3595
- fileName: null,
3596
- mimeType: null,
3597
- size: null,
3598
- error: `Agent not found: ${agentId}`,
3599
- requestId,
3600
- },
3601
- });
3602
- return;
3603
- }
3604
3778
  const info = await getDownloadableFileInfo({
3605
- root: agent.cwd,
3779
+ root: cwd,
3606
3780
  relativePath: requestedPath,
3607
3781
  });
3608
3782
  const entry = this.downloadTokenStore.issueToken({
3609
- agentId,
3610
3783
  path: info.path,
3611
3784
  absolutePath: info.absolutePath,
3612
3785
  fileName: info.fileName,
@@ -3616,7 +3789,7 @@ export class Session {
3616
3789
  this.emit({
3617
3790
  type: 'file_download_token_response',
3618
3791
  payload: {
3619
- agentId,
3792
+ cwd,
3620
3793
  path: info.path,
3621
3794
  token: entry.token,
3622
3795
  fileName: entry.fileName,
@@ -3628,11 +3801,11 @@ export class Session {
3628
3801
  });
3629
3802
  }
3630
3803
  catch (error) {
3631
- this.sessionLogger.error({ err: error, agentId, path: requestedPath }, `Failed to issue download token for agent ${agentId}`);
3804
+ this.sessionLogger.error({ err: error, cwd, path: requestedPath }, `Failed to issue download token for workspace ${cwd}`);
3632
3805
  this.emit({
3633
3806
  type: 'file_download_token_response',
3634
3807
  payload: {
3635
- agentId,
3808
+ cwd,
3636
3809
  path: requestedPath,
3637
3810
  token: null,
3638
3811
  fileName: null,
@@ -3739,9 +3912,9 @@ export class Session {
3739
3912
  return deduped.length > 0 ? deduped : fallback;
3740
3913
  }
3741
3914
  getStatusPriority(agent) {
3742
- const requiresAttention = agent.requiresAttention ?? false;
3743
3915
  const attentionReason = agent.attentionReason ?? null;
3744
- if (requiresAttention && attentionReason === 'permission') {
3916
+ const hasPendingPermission = (agent.pendingPermissions?.length ?? 0) > 0;
3917
+ if (hasPendingPermission || attentionReason === 'permission') {
3745
3918
  return 0;
3746
3919
  }
3747
3920
  if (agent.status === 'error' || attentionReason === 'error') {
@@ -3767,6 +3940,18 @@ export class Session {
3767
3940
  return entry.agent.title?.toLocaleLowerCase() ?? '';
3768
3941
  }
3769
3942
  }
3943
+ getFetchAgentsSortValueFromAgent(agent, key) {
3944
+ switch (key) {
3945
+ case 'status_priority':
3946
+ return this.getStatusPriority(agent);
3947
+ case 'created_at':
3948
+ return Date.parse(agent.createdAt);
3949
+ case 'updated_at':
3950
+ return Date.parse(agent.updatedAt);
3951
+ case 'title':
3952
+ return agent.title?.toLocaleLowerCase() ?? '';
3953
+ }
3954
+ }
3770
3955
  compareSortValues(left, right) {
3771
3956
  if (left === right) {
3772
3957
  return 0;
@@ -3782,17 +3967,17 @@ export class Session {
3782
3967
  }
3783
3968
  return String(left).localeCompare(String(right));
3784
3969
  }
3785
- compareFetchAgentsEntries(left, right, sort) {
3970
+ compareFetchAgentsAgents(left, right, sort) {
3786
3971
  for (const spec of sort) {
3787
- const leftValue = this.getFetchAgentsSortValue(left, spec.key);
3788
- const rightValue = this.getFetchAgentsSortValue(right, spec.key);
3972
+ const leftValue = this.getFetchAgentsSortValueFromAgent(left, spec.key);
3973
+ const rightValue = this.getFetchAgentsSortValueFromAgent(right, spec.key);
3789
3974
  const base = this.compareSortValues(leftValue, rightValue);
3790
3975
  if (base === 0) {
3791
3976
  continue;
3792
3977
  }
3793
3978
  return spec.direction === 'asc' ? base : -base;
3794
3979
  }
3795
- return left.agent.id.localeCompare(right.agent.id);
3980
+ return left.id.localeCompare(right.id);
3796
3981
  }
3797
3982
  encodeFetchAgentsCursor(entry, sort) {
3798
3983
  const values = {};
@@ -3852,9 +4037,9 @@ export class Session {
3852
4037
  id: payload.id,
3853
4038
  };
3854
4039
  }
3855
- compareEntryWithCursor(entry, cursor, sort) {
4040
+ compareAgentWithCursor(agent, cursor, sort) {
3856
4041
  for (const spec of sort) {
3857
- const leftValue = this.getFetchAgentsSortValue(entry, spec.key);
4042
+ const leftValue = this.getFetchAgentsSortValueFromAgent(agent, spec.key);
3858
4043
  const rightValue = cursor.values[spec.key] !== undefined ? (cursor.values[spec.key] ?? null) : null;
3859
4044
  const base = this.compareSortValues(leftValue, rightValue);
3860
4045
  if (base === 0) {
@@ -3862,7 +4047,7 @@ export class Session {
3862
4047
  }
3863
4048
  return spec.direction === 'asc' ? base : -base;
3864
4049
  }
3865
- return entry.agent.id.localeCompare(cursor.id);
4050
+ return agent.id.localeCompare(cursor.id);
3866
4051
  }
3867
4052
  async listFetchAgentsEntries(request) {
3868
4053
  const filter = request.filter;
@@ -3880,26 +4065,293 @@ export class Session {
3880
4065
  placementByCwd.set(cwd, placementPromise);
3881
4066
  return placementPromise;
3882
4067
  };
3883
- let entries = await Promise.all(agents.map(async (agent) => ({
3884
- agent,
3885
- project: await getPlacement(agent.cwd),
3886
- })));
3887
- entries = entries.filter((entry) => this.matchesAgentFilter({
3888
- agent: entry.agent,
3889
- project: entry.project,
3890
- filter,
3891
- }));
3892
- entries.sort((left, right) => this.compareFetchAgentsEntries(left, right, sort));
4068
+ let candidates = [...agents];
4069
+ candidates.sort((left, right) => this.compareFetchAgentsAgents(left, right, sort));
3893
4070
  const cursorToken = request.page?.cursor;
3894
4071
  if (cursorToken) {
3895
4072
  const cursor = this.decodeFetchAgentsCursor(cursorToken, sort);
3896
- entries = entries.filter((entry) => this.compareEntryWithCursor(entry, cursor, sort) > 0);
4073
+ candidates = candidates.filter((agent) => this.compareAgentWithCursor(agent, cursor, sort) > 0);
4074
+ }
4075
+ const limit = request.page?.limit ?? 200;
4076
+ const matchedEntries = [];
4077
+ const batchSize = 25;
4078
+ for (let start = 0; start < candidates.length && matchedEntries.length <= limit; start += batchSize) {
4079
+ const batch = candidates.slice(start, start + batchSize);
4080
+ const batchEntries = await Promise.all(batch.map(async (agent) => ({
4081
+ agent,
4082
+ project: await getPlacement(agent.cwd),
4083
+ })));
4084
+ for (const entry of batchEntries) {
4085
+ if (!this.matchesAgentFilter({
4086
+ agent: entry.agent,
4087
+ project: entry.project,
4088
+ filter,
4089
+ })) {
4090
+ continue;
4091
+ }
4092
+ matchedEntries.push(entry);
4093
+ if (matchedEntries.length > limit) {
4094
+ break;
4095
+ }
4096
+ }
4097
+ }
4098
+ const pagedEntries = matchedEntries.slice(0, limit);
4099
+ const hasMore = matchedEntries.length > limit;
4100
+ const nextCursor = hasMore && pagedEntries.length > 0
4101
+ ? this.encodeFetchAgentsCursor(pagedEntries[pagedEntries.length - 1], sort)
4102
+ : null;
4103
+ return {
4104
+ entries: pagedEntries,
4105
+ pageInfo: {
4106
+ nextCursor,
4107
+ prevCursor: request.page?.cursor ?? null,
4108
+ hasMore,
4109
+ },
4110
+ };
4111
+ }
4112
+ normalizeWorkspaceId(cwd) {
4113
+ const trimmed = cwd.trim();
4114
+ if (!trimmed) {
4115
+ return cwd;
3897
4116
  }
3898
- const limit = request.page?.limit ?? entries.length;
4117
+ return resolve(trimmed);
4118
+ }
4119
+ deriveWorkspaceStateBucket(agent) {
4120
+ const pendingPermissionCount = agent.pendingPermissions?.length ?? 0;
4121
+ if (pendingPermissionCount > 0 || agent.attentionReason === 'permission') {
4122
+ return 'needs_input';
4123
+ }
4124
+ if (agent.status === 'error' || agent.attentionReason === 'error') {
4125
+ return 'failed';
4126
+ }
4127
+ if (agent.status === 'running' || agent.status === 'initializing') {
4128
+ return 'running';
4129
+ }
4130
+ if (agent.requiresAttention) {
4131
+ return 'attention';
4132
+ }
4133
+ return 'done';
4134
+ }
4135
+ deriveWorkspaceDirectoryName(cwd) {
4136
+ const normalized = cwd.replace(/\\/g, '/');
4137
+ const segments = normalized.split('/').filter(Boolean);
4138
+ return segments[segments.length - 1] ?? cwd;
4139
+ }
4140
+ deriveWorkspaceName(input) {
4141
+ const branch = input.checkout.currentBranch?.trim() ?? null;
4142
+ if (branch && branch.toUpperCase() !== 'HEAD') {
4143
+ return branch;
4144
+ }
4145
+ return this.deriveWorkspaceDirectoryName(input.cwd);
4146
+ }
4147
+ accumulateLatestActivityAt(current, agent) {
4148
+ const candidateRaw = agent.lastUserMessageAt ?? agent.updatedAt;
4149
+ const candidateMs = Date.parse(candidateRaw);
4150
+ if (Number.isNaN(candidateMs)) {
4151
+ return current;
4152
+ }
4153
+ if (!current) {
4154
+ return new Date(candidateMs).toISOString();
4155
+ }
4156
+ const currentMs = Date.parse(current);
4157
+ if (Number.isNaN(currentMs) || candidateMs > currentMs) {
4158
+ return new Date(candidateMs).toISOString();
4159
+ }
4160
+ return current;
4161
+ }
4162
+ async listWorkspaceDescriptors() {
4163
+ const agents = await this.listAgentPayloads();
4164
+ const descriptorsByWorkspaceId = new Map();
4165
+ const placementByWorkspaceId = new Map();
4166
+ const getPlacement = (workspaceCwd) => {
4167
+ const key = this.normalizeWorkspaceId(workspaceCwd);
4168
+ const existing = placementByWorkspaceId.get(key);
4169
+ if (existing) {
4170
+ return existing;
4171
+ }
4172
+ const next = this.buildProjectPlacement(workspaceCwd);
4173
+ placementByWorkspaceId.set(key, next);
4174
+ return next;
4175
+ };
4176
+ for (const agent of agents) {
4177
+ if (agent.archivedAt) {
4178
+ continue;
4179
+ }
4180
+ const workspaceId = this.normalizeWorkspaceId(agent.cwd);
4181
+ const placement = await getPlacement(workspaceId);
4182
+ const existing = descriptorsByWorkspaceId.get(workspaceId);
4183
+ if (!existing) {
4184
+ const bucket = this.deriveWorkspaceStateBucket(agent);
4185
+ descriptorsByWorkspaceId.set(workspaceId, {
4186
+ id: workspaceId,
4187
+ projectId: placement.projectKey,
4188
+ name: this.deriveWorkspaceName({
4189
+ cwd: workspaceId,
4190
+ checkout: placement.checkout,
4191
+ }),
4192
+ status: bucket,
4193
+ activityAt: this.accumulateLatestActivityAt(null, agent),
4194
+ });
4195
+ continue;
4196
+ }
4197
+ const bucket = this.deriveWorkspaceStateBucket(agent);
4198
+ if (this.workspaceStatePriority[bucket] < this.workspaceStatePriority[existing.status]) {
4199
+ existing.status = bucket;
4200
+ }
4201
+ existing.activityAt = this.accumulateLatestActivityAt(existing.activityAt, agent);
4202
+ }
4203
+ return Array.from(descriptorsByWorkspaceId.values());
4204
+ }
4205
+ normalizeFetchWorkspacesSort(sort) {
4206
+ const fallback = [{ key: 'activity_at', direction: 'desc' }];
4207
+ if (!sort || sort.length === 0) {
4208
+ return fallback;
4209
+ }
4210
+ const deduped = [];
4211
+ const seen = new Set();
4212
+ for (const entry of sort) {
4213
+ if (seen.has(entry.key)) {
4214
+ continue;
4215
+ }
4216
+ seen.add(entry.key);
4217
+ deduped.push(entry);
4218
+ }
4219
+ return deduped.length > 0 ? deduped : fallback;
4220
+ }
4221
+ getFetchWorkspacesSortValue(workspace, key) {
4222
+ switch (key) {
4223
+ case 'status_priority':
4224
+ return this.workspaceStatePriority[workspace.status];
4225
+ case 'activity_at':
4226
+ return workspace.activityAt ? Date.parse(workspace.activityAt) : null;
4227
+ case 'name':
4228
+ return workspace.name.toLocaleLowerCase();
4229
+ case 'project_id':
4230
+ return workspace.projectId.toLocaleLowerCase();
4231
+ }
4232
+ }
4233
+ compareFetchWorkspacesEntries(left, right, sort) {
4234
+ for (const spec of sort) {
4235
+ const leftValue = this.getFetchWorkspacesSortValue(left, spec.key);
4236
+ const rightValue = this.getFetchWorkspacesSortValue(right, spec.key);
4237
+ const base = this.compareSortValues(leftValue, rightValue);
4238
+ if (base === 0) {
4239
+ continue;
4240
+ }
4241
+ return spec.direction === 'asc' ? base : -base;
4242
+ }
4243
+ return left.id.localeCompare(right.id);
4244
+ }
4245
+ encodeFetchWorkspacesCursor(entry, sort) {
4246
+ const values = {};
4247
+ for (const spec of sort) {
4248
+ values[spec.key] = this.getFetchWorkspacesSortValue(entry, spec.key);
4249
+ }
4250
+ return Buffer.from(JSON.stringify({
4251
+ sort,
4252
+ values,
4253
+ id: entry.id,
4254
+ }), 'utf8').toString('base64url');
4255
+ }
4256
+ decodeFetchWorkspacesCursor(cursor, sort) {
4257
+ let parsed;
4258
+ try {
4259
+ parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
4260
+ }
4261
+ catch {
4262
+ throw new SessionRequestError('invalid_cursor', 'Invalid fetch_workspaces cursor');
4263
+ }
4264
+ if (!parsed || typeof parsed !== 'object') {
4265
+ throw new SessionRequestError('invalid_cursor', 'Invalid fetch_workspaces cursor');
4266
+ }
4267
+ const payload = parsed;
4268
+ if (!Array.isArray(payload.sort) || typeof payload.id !== 'string') {
4269
+ throw new SessionRequestError('invalid_cursor', 'Invalid fetch_workspaces cursor');
4270
+ }
4271
+ if (!payload.values || typeof payload.values !== 'object') {
4272
+ throw new SessionRequestError('invalid_cursor', 'Invalid fetch_workspaces cursor');
4273
+ }
4274
+ const cursorSort = [];
4275
+ for (const item of payload.sort) {
4276
+ if (!item ||
4277
+ typeof item !== 'object' ||
4278
+ typeof item.key !== 'string' ||
4279
+ typeof item.direction !== 'string') {
4280
+ throw new SessionRequestError('invalid_cursor', 'Invalid fetch_workspaces cursor');
4281
+ }
4282
+ const key = item.key;
4283
+ const direction = item.direction;
4284
+ if ((key !== 'status_priority' &&
4285
+ key !== 'activity_at' &&
4286
+ key !== 'name' &&
4287
+ key !== 'project_id') ||
4288
+ (direction !== 'asc' && direction !== 'desc')) {
4289
+ throw new SessionRequestError('invalid_cursor', 'Invalid fetch_workspaces cursor');
4290
+ }
4291
+ cursorSort.push({ key, direction });
4292
+ }
4293
+ if (cursorSort.length !== sort.length ||
4294
+ cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
4295
+ throw new SessionRequestError('invalid_cursor', 'fetch_workspaces cursor does not match current sort');
4296
+ }
4297
+ return {
4298
+ sort: cursorSort,
4299
+ values: payload.values,
4300
+ id: payload.id,
4301
+ };
4302
+ }
4303
+ compareWorkspaceWithCursor(workspace, cursor, sort) {
4304
+ for (const spec of sort) {
4305
+ const leftValue = this.getFetchWorkspacesSortValue(workspace, spec.key);
4306
+ const rightValue = cursor.values[spec.key] !== undefined ? (cursor.values[spec.key] ?? null) : null;
4307
+ const base = this.compareSortValues(leftValue, rightValue);
4308
+ if (base === 0) {
4309
+ continue;
4310
+ }
4311
+ return spec.direction === 'asc' ? base : -base;
4312
+ }
4313
+ return workspace.id.localeCompare(cursor.id);
4314
+ }
4315
+ matchesWorkspaceFilter(input) {
4316
+ const { workspace, filter } = input;
4317
+ if (!filter) {
4318
+ return true;
4319
+ }
4320
+ if (filter.projectId && filter.projectId.trim().length > 0) {
4321
+ if (workspace.projectId !== filter.projectId.trim()) {
4322
+ return false;
4323
+ }
4324
+ }
4325
+ if (filter.idPrefix && filter.idPrefix.trim().length > 0) {
4326
+ if (!workspace.id.startsWith(filter.idPrefix.trim())) {
4327
+ return false;
4328
+ }
4329
+ }
4330
+ if (filter.query && filter.query.trim().length > 0) {
4331
+ const query = filter.query.trim().toLocaleLowerCase();
4332
+ const haystacks = [workspace.name, workspace.projectId, workspace.id];
4333
+ if (!haystacks.some((value) => value.toLocaleLowerCase().includes(query))) {
4334
+ return false;
4335
+ }
4336
+ }
4337
+ return true;
4338
+ }
4339
+ async listFetchWorkspacesEntries(request) {
4340
+ const filter = request.filter;
4341
+ const sort = this.normalizeFetchWorkspacesSort(request.sort);
4342
+ let entries = await this.listWorkspaceDescriptors();
4343
+ entries = entries.filter((workspace) => this.matchesWorkspaceFilter({ workspace, filter }));
4344
+ entries.sort((left, right) => this.compareFetchWorkspacesEntries(left, right, sort));
4345
+ const cursorToken = request.page?.cursor;
4346
+ if (cursorToken) {
4347
+ const cursor = this.decodeFetchWorkspacesCursor(cursorToken, sort);
4348
+ entries = entries.filter((workspace) => this.compareWorkspaceWithCursor(workspace, cursor, sort) > 0);
4349
+ }
4350
+ const limit = request.page?.limit ?? 200;
3899
4351
  const pagedEntries = entries.slice(0, limit);
3900
4352
  const hasMore = entries.length > limit;
3901
4353
  const nextCursor = hasMore && pagedEntries.length > 0
3902
- ? this.encodeFetchAgentsCursor(pagedEntries[pagedEntries.length - 1], sort)
4354
+ ? this.encodeFetchWorkspacesCursor(pagedEntries[pagedEntries.length - 1], sort)
3903
4355
  : null;
3904
4356
  return {
3905
4357
  entries: pagedEntries,
@@ -3910,6 +4362,86 @@ export class Session {
3910
4362
  },
3911
4363
  };
3912
4364
  }
4365
+ bufferOrEmitWorkspaceUpdate(subscription, payload) {
4366
+ if (subscription.isBootstrapping) {
4367
+ const workspaceId = payload.kind === 'upsert' ? payload.workspace.id : payload.id;
4368
+ subscription.pendingUpdatesByWorkspaceId.set(workspaceId, payload);
4369
+ return;
4370
+ }
4371
+ this.emit({
4372
+ type: 'workspace_update',
4373
+ payload,
4374
+ });
4375
+ }
4376
+ flushBootstrappedWorkspaceUpdates(options) {
4377
+ const subscription = this.workspaceUpdatesSubscription;
4378
+ if (!subscription || !subscription.isBootstrapping) {
4379
+ return;
4380
+ }
4381
+ subscription.isBootstrapping = false;
4382
+ const pending = Array.from(subscription.pendingUpdatesByWorkspaceId.values());
4383
+ subscription.pendingUpdatesByWorkspaceId.clear();
4384
+ for (const payload of pending) {
4385
+ if (payload.kind === 'upsert') {
4386
+ const snapshotLatestActivity = options?.snapshotLatestActivityByWorkspaceId?.get(payload.workspace.id);
4387
+ if (typeof snapshotLatestActivity === 'number') {
4388
+ const updateLatestActivity = payload.workspace.activityAt
4389
+ ? Date.parse(payload.workspace.activityAt)
4390
+ : Number.NEGATIVE_INFINITY;
4391
+ if (!Number.isNaN(updateLatestActivity) && updateLatestActivity <= snapshotLatestActivity) {
4392
+ continue;
4393
+ }
4394
+ }
4395
+ }
4396
+ this.emit({
4397
+ type: 'workspace_update',
4398
+ payload,
4399
+ });
4400
+ }
4401
+ }
4402
+ async emitWorkspaceUpdateForCwd(cwd) {
4403
+ const subscription = this.workspaceUpdatesSubscription;
4404
+ if (!subscription) {
4405
+ return;
4406
+ }
4407
+ const workspaceId = this.normalizeWorkspaceId(cwd);
4408
+ const all = await this.listWorkspaceDescriptors();
4409
+ const workspace = all.find((entry) => entry.id === workspaceId);
4410
+ if (!workspace) {
4411
+ this.bufferOrEmitWorkspaceUpdate(subscription, {
4412
+ kind: 'remove',
4413
+ id: workspaceId,
4414
+ });
4415
+ return;
4416
+ }
4417
+ if (!this.matchesWorkspaceFilter({ workspace, filter: subscription.filter })) {
4418
+ this.bufferOrEmitWorkspaceUpdate(subscription, {
4419
+ kind: 'remove',
4420
+ id: workspaceId,
4421
+ });
4422
+ return;
4423
+ }
4424
+ this.bufferOrEmitWorkspaceUpdate(subscription, {
4425
+ kind: 'upsert',
4426
+ workspace,
4427
+ });
4428
+ }
4429
+ async emitWorkspaceUpdatesForCwds(cwds) {
4430
+ if (!this.workspaceUpdatesSubscription) {
4431
+ return;
4432
+ }
4433
+ const uniqueWorkspaceCwds = new Set();
4434
+ for (const cwd of cwds) {
4435
+ const normalized = this.normalizeWorkspaceId(cwd);
4436
+ if (!normalized) {
4437
+ continue;
4438
+ }
4439
+ uniqueWorkspaceCwds.add(normalized);
4440
+ }
4441
+ for (const workspaceCwd of uniqueWorkspaceCwds) {
4442
+ await this.emitWorkspaceUpdateForCwd(workspaceCwd);
4443
+ }
4444
+ }
3913
4445
  async handleFetchAgents(request) {
3914
4446
  const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
3915
4447
  const subscriptionId = request.subscribe
@@ -3964,12 +4496,68 @@ export class Session {
3964
4496
  });
3965
4497
  }
3966
4498
  }
4499
+ async handleFetchWorkspacesRequest(request) {
4500
+ const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
4501
+ const subscriptionId = request.subscribe
4502
+ ? requestedSubscriptionId && requestedSubscriptionId.length > 0
4503
+ ? requestedSubscriptionId
4504
+ : uuidv4()
4505
+ : null;
4506
+ try {
4507
+ if (subscriptionId) {
4508
+ this.workspaceUpdatesSubscription = {
4509
+ subscriptionId,
4510
+ filter: request.filter,
4511
+ isBootstrapping: true,
4512
+ pendingUpdatesByWorkspaceId: new Map(),
4513
+ };
4514
+ }
4515
+ const payload = await this.listFetchWorkspacesEntries(request);
4516
+ const snapshotLatestActivityByWorkspaceId = new Map();
4517
+ for (const entry of payload.entries) {
4518
+ const parsedLatestActivity = entry.activityAt
4519
+ ? Date.parse(entry.activityAt)
4520
+ : Number.NEGATIVE_INFINITY;
4521
+ if (!Number.isNaN(parsedLatestActivity)) {
4522
+ snapshotLatestActivityByWorkspaceId.set(entry.id, parsedLatestActivity);
4523
+ }
4524
+ }
4525
+ this.emit({
4526
+ type: 'fetch_workspaces_response',
4527
+ payload: {
4528
+ requestId: request.requestId,
4529
+ ...(subscriptionId ? { subscriptionId } : {}),
4530
+ ...payload,
4531
+ },
4532
+ });
4533
+ if (subscriptionId && this.workspaceUpdatesSubscription?.subscriptionId === subscriptionId) {
4534
+ this.flushBootstrappedWorkspaceUpdates({ snapshotLatestActivityByWorkspaceId });
4535
+ }
4536
+ }
4537
+ catch (error) {
4538
+ if (subscriptionId && this.workspaceUpdatesSubscription?.subscriptionId === subscriptionId) {
4539
+ this.workspaceUpdatesSubscription = null;
4540
+ }
4541
+ const code = error instanceof SessionRequestError ? error.code : 'fetch_workspaces_failed';
4542
+ const message = error instanceof Error ? error.message : 'Failed to fetch workspaces';
4543
+ this.sessionLogger.error({ err: error }, 'Failed to handle fetch_workspaces_request');
4544
+ this.emit({
4545
+ type: 'rpc_error',
4546
+ payload: {
4547
+ requestId: request.requestId,
4548
+ requestType: request.type,
4549
+ error: message,
4550
+ code,
4551
+ },
4552
+ });
4553
+ }
4554
+ }
3967
4555
  async handleFetchAgent(agentIdOrIdentifier, requestId) {
3968
4556
  const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
3969
4557
  if (!resolved.ok) {
3970
4558
  this.emit({
3971
4559
  type: 'fetch_agent_response',
3972
- payload: { requestId, agent: null, error: resolved.error },
4560
+ payload: { requestId, agent: null, project: null, error: resolved.error },
3973
4561
  });
3974
4562
  return;
3975
4563
  }
@@ -3977,19 +4565,30 @@ export class Session {
3977
4565
  if (!agent) {
3978
4566
  this.emit({
3979
4567
  type: 'fetch_agent_response',
3980
- payload: { requestId, agent: null, error: `Agent not found: ${resolved.agentId}` },
4568
+ payload: {
4569
+ requestId,
4570
+ agent: null,
4571
+ project: null,
4572
+ error: `Agent not found: ${resolved.agentId}`,
4573
+ },
3981
4574
  });
3982
4575
  return;
3983
4576
  }
4577
+ const project = await this.buildProjectPlacement(agent.cwd);
3984
4578
  this.emit({
3985
4579
  type: 'fetch_agent_response',
3986
- payload: { requestId, agent, error: null },
4580
+ payload: { requestId, agent, project, error: null },
3987
4581
  });
3988
4582
  }
3989
4583
  async handleFetchAgentTimelineRequest(msg) {
3990
4584
  const direction = msg.direction ?? (msg.cursor ? 'after' : 'tail');
3991
4585
  const projection = msg.projection ?? 'projected';
3992
- const limit = msg.limit ?? (direction === 'after' ? 0 : undefined);
4586
+ const requestedLimit = msg.limit;
4587
+ const limit = requestedLimit ?? (direction === 'after' ? 0 : undefined);
4588
+ const shouldLimitByProjectedWindow = projection === 'canonical' &&
4589
+ direction === 'tail' &&
4590
+ typeof requestedLimit === 'number' &&
4591
+ requestedLimit > 0;
3993
4592
  const cursor = msg.cursor
3994
4593
  ? {
3995
4594
  epoch: msg.cursor.epoch,
@@ -3998,21 +4597,81 @@ export class Session {
3998
4597
  : undefined;
3999
4598
  try {
4000
4599
  const snapshot = await this.ensureAgentLoaded(msg.agentId);
4001
- const timeline = this.agentManager.fetchTimeline(msg.agentId, {
4600
+ const agentPayload = await this.buildAgentPayload(snapshot);
4601
+ let timeline = this.agentManager.fetchTimeline(msg.agentId, {
4002
4602
  direction,
4003
4603
  cursor,
4004
- limit,
4005
- });
4006
- const projected = projectTimelineRows(timeline.rows, snapshot.provider, projection);
4007
- const firstRow = timeline.rows[0];
4008
- const lastRow = timeline.rows[timeline.rows.length - 1];
4009
- const startCursor = firstRow ? { epoch: timeline.epoch, seq: firstRow.seq } : null;
4010
- const endCursor = lastRow ? { epoch: timeline.epoch, seq: lastRow.seq } : null;
4604
+ limit: shouldLimitByProjectedWindow && typeof requestedLimit === 'number'
4605
+ ? Math.max(1, Math.floor(requestedLimit))
4606
+ : limit,
4607
+ });
4608
+ let hasOlder = timeline.hasOlder;
4609
+ let hasNewer = timeline.hasNewer;
4610
+ let startCursor = null;
4611
+ let endCursor = null;
4612
+ let entries;
4613
+ if (shouldLimitByProjectedWindow) {
4614
+ const projectedLimit = Math.max(1, Math.floor(requestedLimit));
4615
+ let fetchLimit = projectedLimit;
4616
+ let projectedWindow = selectTimelineWindowByProjectedLimit({
4617
+ rows: timeline.rows,
4618
+ provider: snapshot.provider,
4619
+ direction,
4620
+ limit: projectedLimit,
4621
+ collapseToolLifecycle: false,
4622
+ });
4623
+ while (timeline.hasOlder) {
4624
+ const needsMoreProjectedEntries = projectedWindow.projectedEntries.length < projectedLimit;
4625
+ const firstLoadedRow = timeline.rows[0];
4626
+ const firstSelectedRow = projectedWindow.selectedRows[0];
4627
+ const startsAtLoadedBoundary = firstLoadedRow != null &&
4628
+ firstSelectedRow != null &&
4629
+ firstSelectedRow.seq === firstLoadedRow.seq;
4630
+ const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === 'assistant_message';
4631
+ if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
4632
+ break;
4633
+ }
4634
+ const maxRows = Math.max(0, timeline.window.maxSeq - timeline.window.minSeq + 1);
4635
+ const nextFetchLimit = Math.min(maxRows, fetchLimit * 2);
4636
+ if (nextFetchLimit <= fetchLimit) {
4637
+ break;
4638
+ }
4639
+ fetchLimit = nextFetchLimit;
4640
+ timeline = this.agentManager.fetchTimeline(msg.agentId, {
4641
+ direction,
4642
+ cursor,
4643
+ limit: fetchLimit,
4644
+ });
4645
+ projectedWindow = selectTimelineWindowByProjectedLimit({
4646
+ rows: timeline.rows,
4647
+ provider: snapshot.provider,
4648
+ direction,
4649
+ limit: projectedLimit,
4650
+ collapseToolLifecycle: false,
4651
+ });
4652
+ }
4653
+ const selectedRows = projectedWindow.selectedRows;
4654
+ entries = projectTimelineRows(selectedRows, snapshot.provider, projection);
4655
+ if (projectedWindow.minSeq !== null && projectedWindow.maxSeq !== null) {
4656
+ startCursor = { epoch: timeline.epoch, seq: projectedWindow.minSeq };
4657
+ endCursor = { epoch: timeline.epoch, seq: projectedWindow.maxSeq };
4658
+ hasOlder = projectedWindow.minSeq > timeline.window.minSeq;
4659
+ hasNewer = false;
4660
+ }
4661
+ }
4662
+ else {
4663
+ const firstRow = timeline.rows[0];
4664
+ const lastRow = timeline.rows[timeline.rows.length - 1];
4665
+ startCursor = firstRow ? { epoch: timeline.epoch, seq: firstRow.seq } : null;
4666
+ endCursor = lastRow ? { epoch: timeline.epoch, seq: lastRow.seq } : null;
4667
+ entries = projectTimelineRows(timeline.rows, snapshot.provider, projection);
4668
+ }
4011
4669
  this.emit({
4012
4670
  type: 'fetch_agent_timeline_response',
4013
4671
  payload: {
4014
4672
  requestId: msg.requestId,
4015
4673
  agentId: msg.agentId,
4674
+ agent: agentPayload,
4016
4675
  direction,
4017
4676
  projection,
4018
4677
  epoch: timeline.epoch,
@@ -4022,9 +4681,9 @@ export class Session {
4022
4681
  window: timeline.window,
4023
4682
  startCursor,
4024
4683
  endCursor,
4025
- hasOlder: timeline.hasOlder,
4026
- hasNewer: timeline.hasNewer,
4027
- entries: projected,
4684
+ hasOlder,
4685
+ hasNewer,
4686
+ entries,
4028
4687
  error: null,
4029
4688
  },
4030
4689
  });
@@ -4036,6 +4695,7 @@ export class Session {
4036
4695
  payload: {
4037
4696
  requestId: msg.requestId,
4038
4697
  agentId: msg.agentId,
4698
+ agent: null,
4039
4699
  direction,
4040
4700
  projection,
4041
4701
  epoch: '',
@@ -4069,6 +4729,7 @@ export class Session {
4069
4729
  }
4070
4730
  try {
4071
4731
  const agentId = resolved.agentId;
4732
+ await this.unarchiveAgentState(agentId);
4072
4733
  await this.ensureAgentLoaded(agentId);
4073
4734
  await this.interruptAgentIfRunning(agentId);
4074
4735
  try {
@@ -4188,19 +4849,21 @@ export class Session {
4188
4849
  return;
4189
4850
  }
4190
4851
  const abortController = new AbortController();
4191
- const effectiveTimeoutMs = timeoutMs ?? 600000; // 10 minutes default
4192
- const timeoutHandle = setTimeout(() => {
4193
- abortController.abort('timeout');
4194
- }, effectiveTimeoutMs);
4852
+ const hasTimeout = typeof timeoutMs === 'number' && timeoutMs > 0;
4853
+ const timeoutHandle = hasTimeout
4854
+ ? setTimeout(() => {
4855
+ abortController.abort('timeout');
4856
+ }, timeoutMs)
4857
+ : null;
4195
4858
  try {
4196
- const result = await this.agentManager.waitForAgentEvent(agentId, {
4859
+ let result = await this.agentManager.waitForAgentEvent(agentId, {
4197
4860
  signal: abortController.signal,
4198
4861
  });
4199
- const final = await this.getAgentPayloadById(agentId);
4862
+ let final = await this.getAgentPayloadById(agentId);
4200
4863
  if (!final) {
4201
4864
  throw new Error(`Agent ${agentId} disappeared while waiting`);
4202
4865
  }
4203
- const status = result.permission ? 'permission' : result.status === 'error' ? 'error' : 'idle';
4866
+ let status = result.permission ? 'permission' : result.status === 'error' ? 'error' : 'idle';
4204
4867
  this.emit({
4205
4868
  type: 'wait_for_finish_response',
4206
4869
  payload: { requestId, status, final, error: null, lastMessage: result.lastMessage },
@@ -4239,7 +4902,9 @@ export class Session {
4239
4902
  });
4240
4903
  }
4241
4904
  finally {
4242
- clearTimeout(timeoutHandle);
4905
+ if (timeoutHandle) {
4906
+ clearTimeout(timeoutHandle);
4907
+ }
4243
4908
  }
4244
4909
  }
4245
4910
  /**
@@ -4512,7 +5177,7 @@ export class Session {
4512
5177
  });
4513
5178
  });
4514
5179
  this.registerVoiceCallerContext?.(agentId, {
4515
- childAgentDefaultLabels: { ui: 'true' },
5180
+ childAgentDefaultLabels: {},
4516
5181
  allowCustomCwd: false,
4517
5182
  enableVoiceTools: true,
4518
5183
  });
@@ -4736,7 +5401,6 @@ export class Session {
4736
5401
  if (this.agentMcpClient) {
4737
5402
  try {
4738
5403
  await this.agentMcpClient.close();
4739
- this.sessionLogger.debug('Agent MCP client closed');
4740
5404
  }
4741
5405
  catch (error) {
4742
5406
  this.sessionLogger.error({ err: error }, 'Failed to close Agent MCP client');
@@ -4832,17 +5496,11 @@ export class Session {
4832
5496
  if (!this.terminalManager || !this.subscribedTerminalDirectories.has(cwd)) {
4833
5497
  return;
4834
5498
  }
4835
- const hadDirectoryBeforeSubscribe = this.terminalManager.listDirectories().includes(cwd);
4836
5499
  try {
4837
5500
  const terminals = await this.terminalManager.getTerminals(cwd);
4838
5501
  for (const terminal of terminals) {
4839
5502
  this.ensureTerminalExitSubscription(terminal);
4840
5503
  }
4841
- // New directories auto-create Terminal 1, which already emits through
4842
- // terminal-manager change listeners.
4843
- if (!hadDirectoryBeforeSubscribe) {
4844
- return;
4845
- }
4846
5504
  if (!this.subscribedTerminalDirectories.has(cwd)) {
4847
5505
  return;
4848
5506
  }
@@ -5115,10 +5773,16 @@ export class Session {
5115
5773
  }
5116
5774
  const existingStreamId = this.terminalStreamByTerminalId.get(msg.terminalId);
5117
5775
  if (typeof existingStreamId === 'number') {
5118
- this.detachTerminalStream(existingStreamId, { emitExit: false });
5776
+ // Replacing an active stream can happen when multiple UI surfaces attach to the
5777
+ // same terminal. Emit exit for the replaced stream so stale listeners reconnect
5778
+ // instead of continuing to send input to an invalid stream id.
5779
+ this.detachTerminalStream(existingStreamId, { emitExit: true });
5119
5780
  }
5120
5781
  const streamId = this.allocateTerminalStreamId();
5121
- const initialOffset = Math.max(0, Math.floor(msg.resumeOffset ?? 0));
5782
+ const requestedResumeOffset = typeof msg.resumeOffset === 'number'
5783
+ ? msg.resumeOffset
5784
+ : session.getOutputOffset();
5785
+ const initialOffset = Math.max(0, Math.floor(requestedResumeOffset));
5122
5786
  const binding = {
5123
5787
  terminalId: msg.terminalId,
5124
5788
  unsubscribe: () => { },
@@ -5142,7 +5806,7 @@ export class Session {
5142
5806
  endOffset: chunk.endOffset,
5143
5807
  replay: chunk.replay,
5144
5808
  });
5145
- }, { fromOffset: msg.resumeOffset ?? 0 });
5809
+ }, { fromOffset: requestedResumeOffset });
5146
5810
  }
5147
5811
  catch (error) {
5148
5812
  this.terminalStreams.delete(streamId);