@getpaseo/server 0.1.16 → 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 (346) 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 +23 -3
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +81 -8
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/server/agent/agent-manager.d.ts +3 -1
  12. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-manager.js +146 -24
  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-response-loop.js +1 -1
  19. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  20. package/dist/server/server/agent/agent-sdk-types.d.ts +9 -0
  21. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  22. package/dist/server/server/agent/agent-sdk-types.js +11 -1
  23. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  24. package/dist/server/server/agent/agent-storage.d.ts +5 -1
  25. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  26. package/dist/server/server/agent/agent-storage.js +41 -72
  27. package/dist/server/server/agent/agent-storage.js.map +1 -1
  28. package/dist/server/server/agent/agent-title-limits.d.ts +3 -0
  29. package/dist/server/server/agent/agent-title-limits.d.ts.map +1 -0
  30. package/dist/server/server/agent/agent-title-limits.js +3 -0
  31. package/dist/server/server/agent/agent-title-limits.js.map +1 -0
  32. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +29 -0
  33. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -0
  34. package/dist/server/server/agent/providers/claude/model-catalog.js +70 -0
  35. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -0
  36. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +44 -0
  37. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -0
  38. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  39. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  40. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  41. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +15 -0
  42. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  43. package/dist/server/server/agent/providers/claude-agent.d.ts +3 -2
  44. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  45. package/dist/server/server/agent/providers/claude-agent.js +240 -106
  46. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  47. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  48. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +81 -28
  49. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  50. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  51. package/dist/server/server/agent/providers/codex-app-server-agent.js +31 -5
  52. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  53. package/dist/server/server/agent/providers/opencode-agent.d.ts +10 -1
  54. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  55. package/dist/server/server/agent/providers/opencode-agent.js +207 -176
  56. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  57. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +15 -0
  58. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  59. package/dist/server/server/agent/timeline-projection.d.ts +20 -0
  60. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  61. package/dist/server/server/agent/timeline-projection.js +73 -0
  62. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  63. package/dist/server/server/bootstrap.d.ts +15 -0
  64. package/dist/server/server/bootstrap.d.ts.map +1 -1
  65. package/dist/server/server/bootstrap.js +27 -4
  66. package/dist/server/server/bootstrap.js.map +1 -1
  67. package/dist/server/server/file-download/token-store.d.ts +0 -1
  68. package/dist/server/server/file-download/token-store.d.ts.map +1 -1
  69. package/dist/server/server/file-download/token-store.js.map +1 -1
  70. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  71. package/dist/server/server/file-explorer/service.js +56 -36
  72. package/dist/server/server/file-explorer/service.js.map +1 -1
  73. package/dist/server/server/index.js +85 -29
  74. package/dist/server/server/index.js.map +1 -1
  75. package/dist/server/server/logger.d.ts +24 -3
  76. package/dist/server/server/logger.d.ts.map +1 -1
  77. package/dist/server/server/logger.js +157 -21
  78. package/dist/server/server/logger.js.map +1 -1
  79. package/dist/server/server/persisted-config.d.ts +86 -0
  80. package/dist/server/server/persisted-config.d.ts.map +1 -1
  81. package/dist/server/server/persisted-config.js +25 -3
  82. package/dist/server/server/persisted-config.js.map +1 -1
  83. package/dist/server/server/pid-lock.d.ts +6 -2
  84. package/dist/server/server/pid-lock.d.ts.map +1 -1
  85. package/dist/server/server/pid-lock.js +7 -10
  86. package/dist/server/server/pid-lock.js.map +1 -1
  87. package/dist/server/server/relay-transport.d.ts.map +1 -1
  88. package/dist/server/server/relay-transport.js +1 -0
  89. package/dist/server/server/relay-transport.js.map +1 -1
  90. package/dist/server/server/session.d.ts +57 -3
  91. package/dist/server/server/session.d.ts.map +1 -1
  92. package/dist/server/server/session.js +755 -182
  93. package/dist/server/server/session.js.map +1 -1
  94. package/dist/server/server/websocket-server.d.ts +16 -1
  95. package/dist/server/server/websocket-server.d.ts.map +1 -1
  96. package/dist/server/server/websocket-server.js +135 -9
  97. package/dist/server/server/websocket-server.js.map +1 -1
  98. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  99. package/dist/server/server/worktree-bootstrap.js +45 -2
  100. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  101. package/dist/server/shared/messages.d.ts +2841 -541
  102. package/dist/server/shared/messages.d.ts.map +1 -1
  103. package/dist/server/shared/messages.js +99 -5
  104. package/dist/server/shared/messages.js.map +1 -1
  105. package/dist/server/shared/tool-call-display.d.ts.map +1 -1
  106. package/dist/server/shared/tool-call-display.js +3 -0
  107. package/dist/server/shared/tool-call-display.js.map +1 -1
  108. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  109. package/dist/server/terminal/terminal-manager.js +1 -13
  110. package/dist/server/terminal/terminal-manager.js.map +1 -1
  111. package/dist/server/terminal/terminal.d.ts.map +1 -1
  112. package/dist/server/terminal/terminal.js +29 -5
  113. package/dist/server/terminal/terminal.js.map +1 -1
  114. package/dist/server/utils/worktree.d.ts +1 -0
  115. package/dist/server/utils/worktree.d.ts.map +1 -1
  116. package/dist/server/utils/worktree.js +17 -2
  117. package/dist/server/utils/worktree.js.map +1 -1
  118. package/dist/src/server/agent/activity-curator.js +228 -0
  119. package/dist/src/server/agent/activity-curator.js.map +1 -0
  120. package/dist/src/server/agent/agent-manager.js +1712 -0
  121. package/dist/src/server/agent/agent-manager.js.map +1 -0
  122. package/dist/src/server/agent/agent-metadata-generator.js +163 -0
  123. package/dist/src/server/agent/agent-metadata-generator.js.map +1 -0
  124. package/dist/src/server/agent/agent-projections.js +262 -0
  125. package/dist/src/server/agent/agent-projections.js.map +1 -0
  126. package/dist/src/server/agent/agent-response-loop.js +304 -0
  127. package/dist/src/server/agent/agent-response-loop.js.map +1 -0
  128. package/dist/src/server/agent/agent-sdk-types.js +12 -0
  129. package/dist/src/server/agent/agent-sdk-types.js.map +1 -0
  130. package/dist/src/server/agent/agent-storage.js +299 -0
  131. package/dist/src/server/agent/agent-storage.js.map +1 -0
  132. package/dist/src/server/agent/agent-title-limits.js +3 -0
  133. package/dist/src/server/agent/agent-title-limits.js.map +1 -0
  134. package/dist/src/server/agent/audio-utils.js +19 -0
  135. package/dist/src/server/agent/audio-utils.js.map +1 -0
  136. package/dist/src/server/agent/dictation-debug.js +50 -0
  137. package/dist/src/server/agent/dictation-debug.js.map +1 -0
  138. package/dist/src/server/agent/mcp-server.js +787 -0
  139. package/dist/src/server/agent/mcp-server.js.map +1 -0
  140. package/dist/src/server/agent/orchestrator-instructions.js +51 -0
  141. package/dist/src/server/agent/orchestrator-instructions.js.map +1 -0
  142. package/dist/src/server/agent/pcm16-resampler.js +63 -0
  143. package/dist/src/server/agent/pcm16-resampler.js.map +1 -0
  144. package/dist/src/server/agent/provider-launch-config.js +83 -0
  145. package/dist/src/server/agent/provider-launch-config.js.map +1 -0
  146. package/dist/src/server/agent/provider-manifest.js +97 -0
  147. package/dist/src/server/agent/provider-manifest.js.map +1 -0
  148. package/dist/src/server/agent/provider-registry.js +45 -0
  149. package/dist/src/server/agent/provider-registry.js.map +1 -0
  150. package/dist/src/server/agent/providers/claude/model-catalog.js +70 -0
  151. package/dist/src/server/agent/providers/claude/model-catalog.js.map +1 -0
  152. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  153. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  154. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +109 -0
  155. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
  156. package/dist/src/server/agent/providers/claude/tool-call-mapper.js +238 -0
  157. package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
  158. package/dist/src/server/agent/providers/claude-agent.js +3747 -0
  159. package/dist/src/server/agent/providers/claude-agent.js.map +1 -0
  160. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  161. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
  162. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +720 -0
  163. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
  164. package/dist/src/server/agent/providers/codex-app-server-agent.js +2601 -0
  165. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -0
  166. package/dist/src/server/agent/providers/codex-rollout-timeline.js +487 -0
  167. package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +1 -0
  168. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
  169. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
  170. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +151 -0
  171. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
  172. package/dist/src/server/agent/providers/opencode-agent.js +905 -0
  173. package/dist/src/server/agent/providers/opencode-agent.js.map +1 -0
  174. package/dist/src/server/agent/providers/tool-call-detail-primitives.js +552 -0
  175. package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
  176. package/dist/src/server/agent/providers/tool-call-mapper-utils.js +109 -0
  177. package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
  178. package/dist/src/server/agent/recordings-debug.js +19 -0
  179. package/dist/src/server/agent/recordings-debug.js.map +1 -0
  180. package/dist/src/server/agent/stt-debug.js +33 -0
  181. package/dist/src/server/agent/stt-debug.js.map +1 -0
  182. package/dist/src/server/agent/stt-manager.js +233 -0
  183. package/dist/src/server/agent/stt-manager.js.map +1 -0
  184. package/dist/src/server/agent/timeline-append.js +27 -0
  185. package/dist/src/server/agent/timeline-append.js.map +1 -0
  186. package/dist/src/server/agent/timeline-projection.js +215 -0
  187. package/dist/src/server/agent/timeline-projection.js.map +1 -0
  188. package/dist/src/server/agent/tool-name-normalization.js +45 -0
  189. package/dist/src/server/agent/tool-name-normalization.js.map +1 -0
  190. package/dist/src/server/agent/tts-debug.js +24 -0
  191. package/dist/src/server/agent/tts-debug.js.map +1 -0
  192. package/dist/src/server/agent/tts-manager.js +249 -0
  193. package/dist/src/server/agent/tts-manager.js.map +1 -0
  194. package/dist/src/server/agent/wait-for-agent-tracker.js +53 -0
  195. package/dist/src/server/agent/wait-for-agent-tracker.js.map +1 -0
  196. package/dist/src/server/agent-attention-policy.js +40 -0
  197. package/dist/src/server/agent-attention-policy.js.map +1 -0
  198. package/dist/src/server/allowed-hosts.js +94 -0
  199. package/dist/src/server/allowed-hosts.js.map +1 -0
  200. package/dist/src/server/bootstrap.js +498 -0
  201. package/dist/src/server/bootstrap.js.map +1 -0
  202. package/dist/src/server/client-message-id.js +12 -0
  203. package/dist/src/server/client-message-id.js.map +1 -0
  204. package/dist/src/server/config.js +84 -0
  205. package/dist/src/server/config.js.map +1 -0
  206. package/dist/src/server/connection-offer.js +60 -0
  207. package/dist/src/server/connection-offer.js.map +1 -0
  208. package/dist/src/server/daemon-keypair.js +40 -0
  209. package/dist/src/server/daemon-keypair.js.map +1 -0
  210. package/dist/src/server/daemon-version.js +22 -0
  211. package/dist/src/server/daemon-version.js.map +1 -0
  212. package/dist/src/server/dictation/dictation-stream-manager.js +568 -0
  213. package/dist/src/server/dictation/dictation-stream-manager.js.map +1 -0
  214. package/dist/src/server/file-download/token-store.js +40 -0
  215. package/dist/src/server/file-download/token-store.js.map +1 -0
  216. package/dist/src/server/file-explorer/service.js +183 -0
  217. package/dist/src/server/file-explorer/service.js.map +1 -0
  218. package/dist/src/server/json-utils.js +45 -0
  219. package/dist/src/server/json-utils.js.map +1 -0
  220. package/dist/src/server/messages.js +29 -0
  221. package/dist/src/server/messages.js.map +1 -0
  222. package/dist/src/server/package-version.js +47 -0
  223. package/dist/src/server/package-version.js.map +1 -0
  224. package/dist/src/server/paseo-home.js +19 -0
  225. package/dist/src/server/paseo-home.js.map +1 -0
  226. package/dist/src/server/path-utils.js +20 -0
  227. package/dist/src/server/path-utils.js.map +1 -0
  228. package/dist/src/server/persisted-config.js +259 -0
  229. package/dist/src/server/persisted-config.js.map +1 -0
  230. package/dist/src/server/persistence-hooks.js +60 -0
  231. package/dist/src/server/persistence-hooks.js.map +1 -0
  232. package/dist/src/server/pid-lock.js +126 -0
  233. package/dist/src/server/pid-lock.js.map +1 -0
  234. package/dist/src/server/push/push-service.js +68 -0
  235. package/dist/src/server/push/push-service.js.map +1 -0
  236. package/dist/src/server/push/token-store.js +70 -0
  237. package/dist/src/server/push/token-store.js.map +1 -0
  238. package/dist/src/server/relay-transport.js +457 -0
  239. package/dist/src/server/relay-transport.js.map +1 -0
  240. package/dist/src/server/server-id.js +63 -0
  241. package/dist/src/server/server-id.js.map +1 -0
  242. package/dist/src/server/session.js +5947 -0
  243. package/dist/src/server/session.js.map +1 -0
  244. package/dist/src/server/speech/audio.js +101 -0
  245. package/dist/src/server/speech/audio.js.map +1 -0
  246. package/dist/src/server/speech/provider-resolver.js +7 -0
  247. package/dist/src/server/speech/provider-resolver.js.map +1 -0
  248. package/dist/src/server/speech/providers/local/config.js +83 -0
  249. package/dist/src/server/speech/providers/local/config.js.map +1 -0
  250. package/dist/src/server/speech/providers/local/models.js +17 -0
  251. package/dist/src/server/speech/providers/local/models.js.map +1 -0
  252. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +422 -0
  253. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -0
  254. package/dist/src/server/speech/providers/local/runtime.js +253 -0
  255. package/dist/src/server/speech/providers/local/runtime.js.map +1 -0
  256. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +166 -0
  257. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +1 -0
  258. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +165 -0
  259. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +1 -0
  260. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +68 -0
  261. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -0
  262. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +79 -0
  263. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -0
  264. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +11 -0
  265. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +1 -0
  266. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +102 -0
  267. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -0
  268. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +131 -0
  269. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -0
  270. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +132 -0
  271. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -0
  272. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +112 -0
  273. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -0
  274. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +140 -0
  275. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -0
  276. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +95 -0
  277. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -0
  278. package/dist/src/server/speech/providers/openai/config.js +99 -0
  279. package/dist/src/server/speech/providers/openai/config.js.map +1 -0
  280. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +165 -0
  281. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +1 -0
  282. package/dist/src/server/speech/providers/openai/runtime.js +114 -0
  283. package/dist/src/server/speech/providers/openai/runtime.js.map +1 -0
  284. package/dist/src/server/speech/providers/openai/stt.js +208 -0
  285. package/dist/src/server/speech/providers/openai/stt.js.map +1 -0
  286. package/dist/src/server/speech/providers/openai/tts.js +46 -0
  287. package/dist/src/server/speech/providers/openai/tts.js.map +1 -0
  288. package/dist/src/server/speech/speech-config-resolver.js +85 -0
  289. package/dist/src/server/speech/speech-config-resolver.js.map +1 -0
  290. package/dist/src/server/speech/speech-provider.js +2 -0
  291. package/dist/src/server/speech/speech-provider.js.map +1 -0
  292. package/dist/src/server/speech/speech-runtime.js +497 -0
  293. package/dist/src/server/speech/speech-runtime.js.map +1 -0
  294. package/dist/src/server/speech/speech-types.js +8 -0
  295. package/dist/src/server/speech/speech-types.js.map +1 -0
  296. package/dist/src/server/utils/diff-highlighter.js +244 -0
  297. package/dist/src/server/utils/diff-highlighter.js.map +1 -0
  298. package/dist/src/server/utils/syntax-highlighter.js +145 -0
  299. package/dist/src/server/utils/syntax-highlighter.js.map +1 -0
  300. package/dist/src/server/voice-config.js +51 -0
  301. package/dist/src/server/voice-config.js.map +1 -0
  302. package/dist/src/server/voice-mcp-bridge-command.js +31 -0
  303. package/dist/src/server/voice-mcp-bridge-command.js.map +1 -0
  304. package/dist/src/server/voice-mcp-bridge.js +109 -0
  305. package/dist/src/server/voice-mcp-bridge.js.map +1 -0
  306. package/dist/src/server/voice-permission-policy.js +13 -0
  307. package/dist/src/server/voice-permission-policy.js.map +1 -0
  308. package/dist/src/server/voice-types.js +2 -0
  309. package/dist/src/server/voice-types.js.map +1 -0
  310. package/dist/src/server/websocket-server.js +967 -0
  311. package/dist/src/server/websocket-server.js.map +1 -0
  312. package/dist/src/server/worktree-bootstrap.js +497 -0
  313. package/dist/src/server/worktree-bootstrap.js.map +1 -0
  314. package/dist/src/shared/agent-attention-notification.js +130 -0
  315. package/dist/src/shared/agent-attention-notification.js.map +1 -0
  316. package/dist/src/shared/agent-lifecycle.js +8 -0
  317. package/dist/src/shared/agent-lifecycle.js.map +1 -0
  318. package/dist/src/shared/binary-mux.js +114 -0
  319. package/dist/src/shared/binary-mux.js.map +1 -0
  320. package/dist/src/shared/connection-offer.js +17 -0
  321. package/dist/src/shared/connection-offer.js.map +1 -0
  322. package/dist/src/shared/daemon-endpoints.js +113 -0
  323. package/dist/src/shared/daemon-endpoints.js.map +1 -0
  324. package/dist/src/shared/messages.js +2001 -0
  325. package/dist/src/shared/messages.js.map +1 -0
  326. package/dist/src/shared/path-utils.js +16 -0
  327. package/dist/src/shared/path-utils.js.map +1 -0
  328. package/dist/src/shared/tool-call-display.js +93 -0
  329. package/dist/src/shared/tool-call-display.js.map +1 -0
  330. package/dist/src/terminal/terminal-manager.js +136 -0
  331. package/dist/src/terminal/terminal-manager.js.map +1 -0
  332. package/dist/src/terminal/terminal.js +410 -0
  333. package/dist/src/terminal/terminal.js.map +1 -0
  334. package/dist/src/utils/checkout-git.js +1397 -0
  335. package/dist/src/utils/checkout-git.js.map +1 -0
  336. package/dist/src/utils/directory-suggestions.js +655 -0
  337. package/dist/src/utils/directory-suggestions.js.map +1 -0
  338. package/dist/src/utils/path.js +15 -0
  339. package/dist/src/utils/path.js.map +1 -0
  340. package/dist/src/utils/project-icon.js +391 -0
  341. package/dist/src/utils/project-icon.js.map +1 -0
  342. package/dist/src/utils/worktree-metadata.js +116 -0
  343. package/dist/src/utils/worktree-metadata.js.map +1 -0
  344. package/dist/src/utils/worktree.js +741 -0
  345. package/dist/src/utils/worktree.js.map +1 -0
  346. package/package.json +14 -6
@@ -18,8 +18,9 @@ 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
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';
@@ -34,19 +35,44 @@ import { searchHomeDirectories, searchWorkspaceEntries } from '../utils/director
34
35
  import { ensureLocalSpeechModels, getLocalSpeechModelDir, listLocalSpeechModels, } from './speech/providers/local/models.js';
35
36
  import { resolveClientMessageId } from './client-message-id.js';
36
37
  const execAsync = promisify(exec);
38
+ const MAX_INITIAL_AGENT_TITLE_CHARS = Math.min(60, MAX_EXPLICIT_AGENT_TITLE_CHARS);
37
39
  const READ_ONLY_GIT_ENV = {
38
40
  ...process.env,
39
41
  GIT_OPTIONAL_LOCKS: '0',
40
42
  };
41
43
  const pendingAgentInitializations = new Map();
42
- let restartRequested = false;
43
44
  const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
44
- const RESTART_EXIT_DELAY_MS = 250;
45
45
  const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
46
46
  const CHECKOUT_DIFF_FALLBACK_REFRESH_MS = 5000;
47
47
  const TERMINAL_STREAM_WINDOW_BYTES = 256 * 1024;
48
48
  const TERMINAL_STREAM_MAX_PENDING_BYTES = 2 * 1024 * 1024;
49
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
+ }
50
76
  function deriveRemoteProjectKey(remoteUrl) {
51
77
  if (!remoteUrl) {
52
78
  return null;
@@ -93,10 +119,9 @@ function deriveProjectGroupingKey(options) {
93
119
  if (remoteKey) {
94
120
  return remoteKey;
95
121
  }
96
- const worktreeMarker = '.paseo/worktrees/';
97
- const idx = options.cwd.indexOf(worktreeMarker);
98
- if (idx !== -1) {
99
- return options.cwd.slice(0, idx).replace(/\/$/, '');
122
+ const mainRepoRoot = options.mainRepoRoot?.trim();
123
+ if (options.isPaseoOwnedWorktree && mainRepoRoot) {
124
+ return mainRepoRoot;
100
125
  }
101
126
  return options.cwd;
102
127
  }
@@ -214,6 +239,7 @@ export class Session {
214
239
  this.agentTools = null;
215
240
  this.unsubscribeAgentEvents = null;
216
241
  this.agentUpdatesSubscription = null;
242
+ this.workspaceUpdatesSubscription = null;
217
243
  this.clientActivity = null;
218
244
  this.MOBILE_BACKGROUND_STREAM_GRACE_MS = 60000;
219
245
  this.subscribedTerminalDirectories = new Set();
@@ -227,11 +253,19 @@ export class Session {
227
253
  this.checkoutDiffTargets = new Map();
228
254
  this.voiceModeAgentId = null;
229
255
  this.voiceModeBaseConfig = null;
230
- 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;
231
264
  this.clientId = clientId;
232
265
  this.sessionId = uuidv4();
233
266
  this.onMessage = onMessage;
234
267
  this.onBinaryMessage = onBinaryMessage ?? null;
268
+ this.onLifecycleIntent = onLifecycleIntent ?? null;
235
269
  this.downloadTokenStore = downloadTokenStore;
236
270
  this.pushTokenStore = pushTokenStore;
237
271
  this.paseoHome = paseoHome;
@@ -297,6 +331,25 @@ export class Session {
297
331
  getClientActivity() {
298
332
  return this.clientActivity;
299
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
+ }
300
353
  /**
301
354
  * Send initial state to client after connection
302
355
  */
@@ -327,10 +380,11 @@ export class Session {
327
380
  async interruptAgentIfRunning(agentId) {
328
381
  const snapshot = this.agentManager.getAgent(agentId);
329
382
  if (!snapshot) {
383
+ this.sessionLogger.trace({ agentId }, 'interruptAgentIfRunning: agent not found');
330
384
  throw new Error(`Agent ${agentId} not found`);
331
385
  }
332
386
  if (snapshot.lifecycle !== 'running' && !snapshot.pendingRun) {
333
- 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');
334
388
  return;
335
389
  }
336
390
  this.sessionLogger.debug({ agentId, lifecycle: snapshot.lifecycle, pendingRun: Boolean(snapshot.pendingRun) }, 'interruptAgentIfRunning: interrupting');
@@ -360,10 +414,15 @@ export class Session {
360
414
  * Start streaming an agent run and forward results via the websocket broadcast
361
415
  */
362
416
  startAgentStream(agentId, prompt, runOptions) {
363
- 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');
364
422
  let iterator;
365
423
  try {
366
424
  iterator = this.agentManager.streamAgent(agentId, prompt, runOptions);
425
+ this.sessionLogger.trace({ agentId }, 'startAgentStream: streamAgent returned iterator');
367
426
  }
368
427
  catch (error) {
369
428
  this.handleAgentRunError(agentId, error, 'Failed to start agent run');
@@ -375,8 +434,10 @@ export class Session {
375
434
  for await (const _ of iterator) {
376
435
  // Events are forwarded via the session's AgentManager subscription.
377
436
  }
437
+ this.sessionLogger.trace({ agentId }, 'startAgentStream: iterator drained');
378
438
  }
379
439
  catch (error) {
440
+ this.sessionLogger.trace({ agentId, err: error }, 'startAgentStream: iterator threw');
380
441
  this.handleAgentRunError(agentId, error, 'Agent stream failed');
381
442
  }
382
443
  })();
@@ -499,7 +560,7 @@ export class Session {
499
560
  }
500
561
  async buildAgentPayload(agent) {
501
562
  const storedRecord = await this.agentStorage.get(agent.id);
502
- const title = storedRecord?.title ?? null;
563
+ const title = storedRecord?.title ?? storedRecord?.config?.title ?? null;
503
564
  const payload = toAgentPayload(agent, { title });
504
565
  payload.archivedAt = storedRecord?.archivedAt ?? null;
505
566
  return payload;
@@ -555,7 +616,7 @@ export class Session {
555
616
  persistence: toAgentPersistenceHandle(this.sessionLogger, record.persistence),
556
617
  lastUsage: undefined,
557
618
  lastError: undefined,
558
- title: record.title ?? null,
619
+ title: record.title ?? record.config?.title ?? null,
559
620
  requiresAttention: record.requiresAttention ?? false,
560
621
  attentionReason: record.attentionReason ?? null,
561
622
  attentionTimestamp: record.attentionTimestamp ?? null,
@@ -724,6 +785,8 @@ export class Session {
724
785
  const projectKey = deriveProjectGroupingKey({
725
786
  cwd,
726
787
  remoteUrl: checkout.remoteUrl,
788
+ isPaseoOwnedWorktree: checkout.isPaseoOwnedWorktree,
789
+ mainRepoRoot: checkout.mainRepoRoot,
727
790
  });
728
791
  return {
729
792
  projectKey,
@@ -734,28 +797,29 @@ export class Session {
734
797
  async forwardAgentUpdate(agent) {
735
798
  try {
736
799
  const subscription = this.agentUpdatesSubscription;
737
- if (!subscription) {
738
- return;
739
- }
740
800
  const payload = await this.buildAgentPayload(agent);
741
- const project = await this.buildProjectPlacement(payload.cwd);
742
- const matches = this.matchesAgentFilter({
743
- agent: payload,
744
- project,
745
- filter: subscription.filter,
746
- });
747
- if (matches) {
748
- this.bufferOrEmitAgentUpdate(subscription, {
749
- kind: 'upsert',
801
+ if (subscription) {
802
+ const project = await this.buildProjectPlacement(payload.cwd);
803
+ const matches = this.matchesAgentFilter({
750
804
  agent: payload,
751
805
  project,
806
+ filter: subscription.filter,
752
807
  });
753
- 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
+ }
754
821
  }
755
- this.bufferOrEmitAgentUpdate(subscription, {
756
- kind: 'remove',
757
- agentId: payload.id,
758
- });
822
+ await this.emitWorkspaceUpdateForCwd(payload.cwd);
759
823
  }
760
824
  catch (error) {
761
825
  this.sessionLogger.error({ err: error }, 'Failed to emit agent update');
@@ -779,6 +843,9 @@ export class Session {
779
843
  case 'fetch_agents_request':
780
844
  await this.handleFetchAgents(msg);
781
845
  break;
846
+ case 'fetch_workspaces_request':
847
+ await this.handleFetchWorkspacesRequest(msg);
848
+ break;
782
849
  case 'fetch_agent_request':
783
850
  await this.handleFetchAgent(msg.agentId, msg.requestId);
784
851
  break;
@@ -848,6 +915,9 @@ export class Session {
848
915
  case 'restart_server_request':
849
916
  await this.handleRestartServerRequest(msg.requestId, msg.reason);
850
917
  break;
918
+ case 'shutdown_server_request':
919
+ await this.handleShutdownServerRequest(msg.requestId);
920
+ break;
851
921
  case 'fetch_agent_timeline_request':
852
922
  await this.handleFetchAgentTimelineRequest(msg);
853
923
  break;
@@ -1067,11 +1137,6 @@ export class Session {
1067
1137
  this.sessionLogger.warn({ streamId: frame.streamId, messageType: frame.messageType }, 'Unhandled terminal binary frame');
1068
1138
  }
1069
1139
  async handleRestartServerRequest(requestId, reason) {
1070
- if (restartRequested) {
1071
- this.sessionLogger.debug('Restart already requested, ignoring duplicate');
1072
- return;
1073
- }
1074
- restartRequested = true;
1075
1140
  const payload = {
1076
1141
  status: 'restart_requested',
1077
1142
  clientId: this.clientId,
@@ -1085,19 +1150,45 @@ export class Session {
1085
1150
  type: 'status',
1086
1151
  payload,
1087
1152
  });
1088
- if (typeof process.send === 'function') {
1089
- process.send({
1090
- type: 'paseo:restart',
1091
- ...(reason ? { reason } : {}),
1092
- });
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) {
1093
1178
  return;
1094
1179
  }
1095
- setTimeout(() => {
1096
- process.exit(0);
1097
- }, 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
+ }
1098
1186
  }
1099
1187
  async handleDeleteAgentRequest(agentId, requestId) {
1100
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;
1101
1192
  // Prevent the persistence hook from re-creating the record while we close/delete.
1102
1193
  this.agentStorage.beginDelete(agentId);
1103
1194
  try {
@@ -1125,11 +1216,31 @@ export class Session {
1125
1216
  agentId,
1126
1217
  });
1127
1218
  }
1219
+ if (knownCwd) {
1220
+ await this.emitWorkspaceUpdateForCwd(knownCwd);
1221
+ }
1128
1222
  }
1129
1223
  async handleArchiveAgentRequest(agentId, requestId) {
1130
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) {
1131
1241
  if (this.agentManager.getAgent(agentId)) {
1132
1242
  await this.interruptAgentIfRunning(agentId);
1243
+ await this.agentManager.clearAgentAttention(agentId).catch(() => undefined);
1133
1244
  }
1134
1245
  const archivedAt = new Date().toISOString();
1135
1246
  const existing = await this.agentStorage.get(agentId);
@@ -1147,29 +1258,41 @@ export class Session {
1147
1258
  throw new Error(`Agent not found in storage after snapshot: ${agentId}`);
1148
1259
  }
1149
1260
  }
1150
- archivedRecord = {
1261
+ const normalizedStatus = archivedRecord.lastStatus === 'running' || archivedRecord.lastStatus === 'initializing'
1262
+ ? 'idle'
1263
+ : archivedRecord.lastStatus;
1264
+ const nextRecord = {
1151
1265
  ...archivedRecord,
1152
1266
  archivedAt,
1267
+ lastStatus: normalizedStatus,
1268
+ requiresAttention: false,
1269
+ attentionReason: null,
1270
+ attentionTimestamp: null,
1153
1271
  };
1154
- await this.agentStorage.upsert(archivedRecord);
1272
+ await this.agentStorage.upsert(nextRecord);
1155
1273
  this.agentManager.notifyAgentState(agentId);
1156
- this.emit({
1157
- type: 'agent_archived',
1158
- payload: {
1159
- agentId,
1160
- archivedAt,
1161
- requestId,
1162
- },
1163
- });
1164
- await this.maybeArchiveWorktreeAfterLastAgentArchived({
1165
- archivedAgentId: agentId,
1166
- archivedAgentCwd: archivedRecord.cwd,
1167
- requestId,
1168
- });
1274
+ return { archivedAt, archivedRecord: nextRecord };
1169
1275
  }
1170
- async getArchivedAt(agentId) {
1276
+ async unarchiveAgentState(agentId) {
1171
1277
  const record = await this.agentStorage.get(agentId);
1172
- return record?.archivedAt ?? null;
1278
+ if (!record || !record.archivedAt) {
1279
+ return false;
1280
+ }
1281
+ await this.agentStorage.upsert({
1282
+ ...record,
1283
+ archivedAt: null,
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);
1173
1296
  }
1174
1297
  async handleUpdateAgentRequest(agentId, name, labels, requestId) {
1175
1298
  this.sessionLogger.info({
@@ -1642,6 +1765,7 @@ export class Session {
1642
1765
  */
1643
1766
  async handleSendAgentMessage(agentId, text, messageId, images, runOptions) {
1644
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);
1645
1769
  try {
1646
1770
  await this.ensureAgentLoaded(agentId);
1647
1771
  }
@@ -1649,11 +1773,6 @@ export class Session {
1649
1773
  this.handleAgentRunError(agentId, error, 'Failed to initialize agent before sending prompt');
1650
1774
  return;
1651
1775
  }
1652
- const archivedAt = await this.getArchivedAt(agentId);
1653
- if (archivedAt) {
1654
- this.handleAgentRunError(agentId, new Error(`Agent ${agentId} is archived`), 'Refusing to send prompt to archived agent');
1655
- return;
1656
- }
1657
1776
  try {
1658
1777
  await this.interruptAgentIfRunning(agentId);
1659
1778
  }
@@ -1680,24 +1799,44 @@ export class Session {
1680
1799
  const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, git, images, labels, } = msg;
1681
1800
  this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ''}`);
1682
1801
  try {
1683
- 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);
1684
1812
  const snapshot = await this.agentManager.createAgent(sessionConfig, undefined, { labels });
1685
1813
  await this.forwardAgentUpdate(snapshot);
1686
- 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
+ }
1687
1829
  if (trimmedPrompt) {
1688
1830
  scheduleAgentMetadataGeneration({
1689
1831
  agentManager: this.agentManager,
1690
1832
  agentId: snapshot.id,
1691
1833
  cwd: snapshot.cwd,
1692
1834
  initialPrompt: trimmedPrompt,
1693
- explicitTitle: snapshot.config.title,
1835
+ explicitTitle,
1694
1836
  paseoHome: this.paseoHome,
1695
1837
  logger: this.sessionLogger,
1696
1838
  });
1697
- try {
1698
- await this.handleSendAgentMessage(snapshot.id, trimmedPrompt, resolveClientMessageId(clientMessageId), images, outputSchema ? { outputSchema } : undefined);
1699
- }
1700
- catch (promptError) {
1839
+ void this.handleSendAgentMessage(snapshot.id, trimmedPrompt, resolveClientMessageId(clientMessageId), images, outputSchema ? { outputSchema } : undefined).catch((promptError) => {
1701
1840
  this.sessionLogger.error({ err: promptError, agentId: snapshot.id }, `Failed to run initial prompt for agent ${snapshot.id}`);
1702
1841
  this.emit({
1703
1842
  type: 'activity_log',
@@ -1708,21 +1847,6 @@ export class Session {
1708
1847
  content: `Initial prompt failed: ${promptError?.message ?? promptError}`,
1709
1848
  },
1710
1849
  });
1711
- }
1712
- }
1713
- if (requestId) {
1714
- const agentPayload = await this.getAgentPayloadById(snapshot.id);
1715
- if (!agentPayload) {
1716
- throw new Error(`Agent ${snapshot.id} not found after creation`);
1717
- }
1718
- this.emit({
1719
- type: 'status',
1720
- payload: {
1721
- status: 'agent_created',
1722
- agentId: snapshot.id,
1723
- requestId,
1724
- agent: agentPayload,
1725
- },
1726
1850
  });
1727
1851
  }
1728
1852
  if (worktreeConfig) {
@@ -1785,7 +1909,9 @@ export class Session {
1785
1909
  }
1786
1910
  this.sessionLogger.info({ sessionId: handle.sessionId, provider: handle.provider }, `Resuming agent ${handle.sessionId} (${handle.provider})`);
1787
1911
  try {
1912
+ await this.unarchiveAgentByHandle(handle);
1788
1913
  const snapshot = await this.agentManager.resumeAgentFromPersistence(handle, overrides);
1914
+ await this.unarchiveAgentState(snapshot.id);
1789
1915
  await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
1790
1916
  await this.forwardAgentUpdate(snapshot);
1791
1917
  const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
@@ -1823,6 +1949,7 @@ export class Session {
1823
1949
  const { agentId, requestId } = msg;
1824
1950
  this.sessionLogger.info({ agentId }, `Refreshing agent ${agentId} from persistence`);
1825
1951
  try {
1952
+ await this.unarchiveAgentState(agentId);
1826
1953
  let snapshot;
1827
1954
  const existing = this.agentManager.getAgent(agentId);
1828
1955
  if (existing) {
@@ -2446,7 +2573,6 @@ export class Session {
2446
2573
  */
2447
2574
  async handleClearAgentAttention(agentId) {
2448
2575
  const agentIds = Array.isArray(agentId) ? agentId : [agentId];
2449
- this.sessionLogger.debug({ agentIds }, `Clearing attention for ${agentIds.length} agent(s): ${agentIds.join(', ')}`);
2450
2576
  try {
2451
2577
  await Promise.all(agentIds.map((id) => this.agentManager.clearAgentAttention(id)));
2452
2578
  }
@@ -3323,6 +3449,7 @@ export class Session {
3323
3449
  payload: {
3324
3450
  worktrees: worktrees.map((entry) => ({
3325
3451
  worktreePath: entry.path,
3452
+ createdAt: entry.createdAt,
3326
3453
  branchName: entry.branchName ?? null,
3327
3454
  head: entry.head ?? null,
3328
3455
  })),
@@ -3404,10 +3531,12 @@ export class Session {
3404
3531
  targetPath = resolvedWorktree.worktreePath;
3405
3532
  }
3406
3533
  const removedAgents = new Set();
3534
+ const affectedWorkspaceCwds = new Set([targetPath]);
3407
3535
  const agents = this.agentManager.listAgents();
3408
3536
  for (const agent of agents) {
3409
3537
  if (this.isPathWithinRoot(targetPath, agent.cwd)) {
3410
3538
  removedAgents.add(agent.id);
3539
+ affectedWorkspaceCwds.add(agent.cwd);
3411
3540
  try {
3412
3541
  await this.agentManager.closeAgent(agent.id);
3413
3542
  }
@@ -3426,6 +3555,7 @@ export class Session {
3426
3555
  for (const record of registryRecords) {
3427
3556
  if (this.isPathWithinRoot(targetPath, record.cwd)) {
3428
3557
  removedAgents.add(record.id);
3558
+ affectedWorkspaceCwds.add(record.cwd);
3429
3559
  try {
3430
3560
  await this.agentStorage.remove(record.id);
3431
3561
  }
@@ -3449,6 +3579,7 @@ export class Session {
3449
3579
  },
3450
3580
  });
3451
3581
  }
3582
+ await this.emitWorkspaceUpdatesForCwds(affectedWorkspaceCwds);
3452
3583
  return Array.from(removedAgents);
3453
3584
  }
3454
3585
  async handlePaseoWorktreeArchiveRequest(msg) {
@@ -3517,38 +3648,36 @@ export class Session {
3517
3648
  }
3518
3649
  }
3519
3650
  /**
3520
- * Handle read-only file explorer requests scoped to an agent's cwd
3651
+ * Handle read-only file explorer requests scoped to a workspace cwd
3521
3652
  */
3522
3653
  async handleFileExplorerRequest(request) {
3523
- const { agentId, path: requestedPath = '.', mode, requestId } = request;
3524
- 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
+ }
3525
3671
  try {
3526
- const agents = this.agentManager.listAgents();
3527
- const agent = agents.find((a) => a.id === agentId);
3528
- if (!agent) {
3529
- this.emit({
3530
- type: 'file_explorer_response',
3531
- payload: {
3532
- agentId,
3533
- path: requestedPath,
3534
- mode,
3535
- directory: null,
3536
- file: null,
3537
- error: `Agent not found: ${agentId}`,
3538
- requestId,
3539
- },
3540
- });
3541
- return;
3542
- }
3543
3672
  if (mode === 'list') {
3544
3673
  const directory = await listDirectoryEntries({
3545
- root: agent.cwd,
3674
+ root: cwd,
3546
3675
  relativePath: requestedPath,
3547
3676
  });
3548
3677
  this.emit({
3549
3678
  type: 'file_explorer_response',
3550
3679
  payload: {
3551
- agentId,
3680
+ cwd,
3552
3681
  path: directory.path,
3553
3682
  mode,
3554
3683
  directory,
@@ -3560,13 +3689,13 @@ export class Session {
3560
3689
  }
3561
3690
  else {
3562
3691
  const file = await readExplorerFile({
3563
- root: agent.cwd,
3692
+ root: cwd,
3564
3693
  relativePath: requestedPath,
3565
3694
  });
3566
3695
  this.emit({
3567
3696
  type: 'file_explorer_response',
3568
3697
  payload: {
3569
- agentId,
3698
+ cwd,
3570
3699
  path: file.path,
3571
3700
  mode,
3572
3701
  directory: null,
@@ -3578,11 +3707,11 @@ export class Session {
3578
3707
  }
3579
3708
  }
3580
3709
  catch (error) {
3581
- 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}`);
3582
3711
  this.emit({
3583
3712
  type: 'file_explorer_response',
3584
3713
  payload: {
3585
- agentId,
3714
+ cwd,
3586
3715
  path: requestedPath,
3587
3716
  mode,
3588
3717
  directory: null,
@@ -3623,36 +3752,34 @@ export class Session {
3623
3752
  }
3624
3753
  }
3625
3754
  /**
3626
- * Handle file download token request scoped to an agent's cwd
3755
+ * Handle file download token request scoped to a workspace cwd
3627
3756
  */
3628
3757
  async handleFileDownloadTokenRequest(request) {
3629
- const { agentId, path: requestedPath, requestId } = request;
3630
- 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})`);
3631
3777
  try {
3632
- const agents = this.agentManager.listAgents();
3633
- const agent = agents.find((a) => a.id === agentId);
3634
- if (!agent) {
3635
- this.emit({
3636
- type: 'file_download_token_response',
3637
- payload: {
3638
- agentId,
3639
- path: requestedPath,
3640
- token: null,
3641
- fileName: null,
3642
- mimeType: null,
3643
- size: null,
3644
- error: `Agent not found: ${agentId}`,
3645
- requestId,
3646
- },
3647
- });
3648
- return;
3649
- }
3650
3778
  const info = await getDownloadableFileInfo({
3651
- root: agent.cwd,
3779
+ root: cwd,
3652
3780
  relativePath: requestedPath,
3653
3781
  });
3654
3782
  const entry = this.downloadTokenStore.issueToken({
3655
- agentId,
3656
3783
  path: info.path,
3657
3784
  absolutePath: info.absolutePath,
3658
3785
  fileName: info.fileName,
@@ -3662,7 +3789,7 @@ export class Session {
3662
3789
  this.emit({
3663
3790
  type: 'file_download_token_response',
3664
3791
  payload: {
3665
- agentId,
3792
+ cwd,
3666
3793
  path: info.path,
3667
3794
  token: entry.token,
3668
3795
  fileName: entry.fileName,
@@ -3674,11 +3801,11 @@ export class Session {
3674
3801
  });
3675
3802
  }
3676
3803
  catch (error) {
3677
- 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}`);
3678
3805
  this.emit({
3679
3806
  type: 'file_download_token_response',
3680
3807
  payload: {
3681
- agentId,
3808
+ cwd,
3682
3809
  path: requestedPath,
3683
3810
  token: null,
3684
3811
  fileName: null,
@@ -3785,9 +3912,9 @@ export class Session {
3785
3912
  return deduped.length > 0 ? deduped : fallback;
3786
3913
  }
3787
3914
  getStatusPriority(agent) {
3788
- const requiresAttention = agent.requiresAttention ?? false;
3789
3915
  const attentionReason = agent.attentionReason ?? null;
3790
- if (requiresAttention && attentionReason === 'permission') {
3916
+ const hasPendingPermission = (agent.pendingPermissions?.length ?? 0) > 0;
3917
+ if (hasPendingPermission || attentionReason === 'permission') {
3791
3918
  return 0;
3792
3919
  }
3793
3920
  if (agent.status === 'error' || attentionReason === 'error') {
@@ -3982,6 +4109,339 @@ export class Session {
3982
4109
  },
3983
4110
  };
3984
4111
  }
4112
+ normalizeWorkspaceId(cwd) {
4113
+ const trimmed = cwd.trim();
4114
+ if (!trimmed) {
4115
+ return cwd;
4116
+ }
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;
4351
+ const pagedEntries = entries.slice(0, limit);
4352
+ const hasMore = entries.length > limit;
4353
+ const nextCursor = hasMore && pagedEntries.length > 0
4354
+ ? this.encodeFetchWorkspacesCursor(pagedEntries[pagedEntries.length - 1], sort)
4355
+ : null;
4356
+ return {
4357
+ entries: pagedEntries,
4358
+ pageInfo: {
4359
+ nextCursor,
4360
+ prevCursor: request.page?.cursor ?? null,
4361
+ hasMore,
4362
+ },
4363
+ };
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
+ }
3985
4445
  async handleFetchAgents(request) {
3986
4446
  const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
3987
4447
  const subscriptionId = request.subscribe
@@ -4036,6 +4496,62 @@ export class Session {
4036
4496
  });
4037
4497
  }
4038
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
+ }
4039
4555
  async handleFetchAgent(agentIdOrIdentifier, requestId) {
4040
4556
  const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
4041
4557
  if (!resolved.ok) {
@@ -4067,7 +4583,12 @@ export class Session {
4067
4583
  async handleFetchAgentTimelineRequest(msg) {
4068
4584
  const direction = msg.direction ?? (msg.cursor ? 'after' : 'tail');
4069
4585
  const projection = msg.projection ?? 'projected';
4070
- 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;
4071
4592
  const cursor = msg.cursor
4072
4593
  ? {
4073
4594
  epoch: msg.cursor.epoch,
@@ -4076,21 +4597,81 @@ export class Session {
4076
4597
  : undefined;
4077
4598
  try {
4078
4599
  const snapshot = await this.ensureAgentLoaded(msg.agentId);
4079
- const timeline = this.agentManager.fetchTimeline(msg.agentId, {
4600
+ const agentPayload = await this.buildAgentPayload(snapshot);
4601
+ let timeline = this.agentManager.fetchTimeline(msg.agentId, {
4080
4602
  direction,
4081
4603
  cursor,
4082
- limit,
4083
- });
4084
- const projected = projectTimelineRows(timeline.rows, snapshot.provider, projection);
4085
- const firstRow = timeline.rows[0];
4086
- const lastRow = timeline.rows[timeline.rows.length - 1];
4087
- const startCursor = firstRow ? { epoch: timeline.epoch, seq: firstRow.seq } : null;
4088
- 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
+ }
4089
4669
  this.emit({
4090
4670
  type: 'fetch_agent_timeline_response',
4091
4671
  payload: {
4092
4672
  requestId: msg.requestId,
4093
4673
  agentId: msg.agentId,
4674
+ agent: agentPayload,
4094
4675
  direction,
4095
4676
  projection,
4096
4677
  epoch: timeline.epoch,
@@ -4100,9 +4681,9 @@ export class Session {
4100
4681
  window: timeline.window,
4101
4682
  startCursor,
4102
4683
  endCursor,
4103
- hasOlder: timeline.hasOlder,
4104
- hasNewer: timeline.hasNewer,
4105
- entries: projected,
4684
+ hasOlder,
4685
+ hasNewer,
4686
+ entries,
4106
4687
  error: null,
4107
4688
  },
4108
4689
  });
@@ -4114,6 +4695,7 @@ export class Session {
4114
4695
  payload: {
4115
4696
  requestId: msg.requestId,
4116
4697
  agentId: msg.agentId,
4698
+ agent: null,
4117
4699
  direction,
4118
4700
  projection,
4119
4701
  epoch: '',
@@ -4147,19 +4729,7 @@ export class Session {
4147
4729
  }
4148
4730
  try {
4149
4731
  const agentId = resolved.agentId;
4150
- const archivedAt = await this.getArchivedAt(agentId);
4151
- if (archivedAt) {
4152
- this.emit({
4153
- type: 'send_agent_message_response',
4154
- payload: {
4155
- requestId: msg.requestId,
4156
- agentId,
4157
- accepted: false,
4158
- error: `Agent ${agentId} is archived`,
4159
- },
4160
- });
4161
- return;
4162
- }
4732
+ await this.unarchiveAgentState(agentId);
4163
4733
  await this.ensureAgentLoaded(agentId);
4164
4734
  await this.interruptAgentIfRunning(agentId);
4165
4735
  try {
@@ -4279,10 +4849,12 @@ export class Session {
4279
4849
  return;
4280
4850
  }
4281
4851
  const abortController = new AbortController();
4282
- const effectiveTimeoutMs = timeoutMs ?? 600000; // 10 minutes default
4283
- const timeoutHandle = setTimeout(() => {
4284
- abortController.abort('timeout');
4285
- }, effectiveTimeoutMs);
4852
+ const hasTimeout = typeof timeoutMs === 'number' && timeoutMs > 0;
4853
+ const timeoutHandle = hasTimeout
4854
+ ? setTimeout(() => {
4855
+ abortController.abort('timeout');
4856
+ }, timeoutMs)
4857
+ : null;
4286
4858
  try {
4287
4859
  let result = await this.agentManager.waitForAgentEvent(agentId, {
4288
4860
  signal: abortController.signal,
@@ -4330,7 +4902,9 @@ export class Session {
4330
4902
  });
4331
4903
  }
4332
4904
  finally {
4333
- clearTimeout(timeoutHandle);
4905
+ if (timeoutHandle) {
4906
+ clearTimeout(timeoutHandle);
4907
+ }
4334
4908
  }
4335
4909
  }
4336
4910
  /**
@@ -4603,7 +5177,7 @@ export class Session {
4603
5177
  });
4604
5178
  });
4605
5179
  this.registerVoiceCallerContext?.(agentId, {
4606
- childAgentDefaultLabels: { ui: 'true' },
5180
+ childAgentDefaultLabels: {},
4607
5181
  allowCustomCwd: false,
4608
5182
  enableVoiceTools: true,
4609
5183
  });
@@ -4827,7 +5401,6 @@ export class Session {
4827
5401
  if (this.agentMcpClient) {
4828
5402
  try {
4829
5403
  await this.agentMcpClient.close();
4830
- this.sessionLogger.debug('Agent MCP client closed');
4831
5404
  }
4832
5405
  catch (error) {
4833
5406
  this.sessionLogger.error({ err: error }, 'Failed to close Agent MCP client');
@@ -4923,17 +5496,11 @@ export class Session {
4923
5496
  if (!this.terminalManager || !this.subscribedTerminalDirectories.has(cwd)) {
4924
5497
  return;
4925
5498
  }
4926
- const hadDirectoryBeforeSubscribe = this.terminalManager.listDirectories().includes(cwd);
4927
5499
  try {
4928
5500
  const terminals = await this.terminalManager.getTerminals(cwd);
4929
5501
  for (const terminal of terminals) {
4930
5502
  this.ensureTerminalExitSubscription(terminal);
4931
5503
  }
4932
- // New directories auto-create Terminal 1, which already emits through
4933
- // terminal-manager change listeners.
4934
- if (!hadDirectoryBeforeSubscribe) {
4935
- return;
4936
- }
4937
5504
  if (!this.subscribedTerminalDirectories.has(cwd)) {
4938
5505
  return;
4939
5506
  }
@@ -5206,10 +5773,16 @@ export class Session {
5206
5773
  }
5207
5774
  const existingStreamId = this.terminalStreamByTerminalId.get(msg.terminalId);
5208
5775
  if (typeof existingStreamId === 'number') {
5209
- 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 });
5210
5780
  }
5211
5781
  const streamId = this.allocateTerminalStreamId();
5212
- 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));
5213
5786
  const binding = {
5214
5787
  terminalId: msg.terminalId,
5215
5788
  unsubscribe: () => { },
@@ -5233,7 +5806,7 @@ export class Session {
5233
5806
  endOffset: chunk.endOffset,
5234
5807
  replay: chunk.replay,
5235
5808
  });
5236
- }, { fromOffset: msg.resumeOffset ?? 0 });
5809
+ }, { fromOffset: requestedResumeOffset });
5237
5810
  }
5238
5811
  catch (error) {
5239
5812
  this.terminalStreams.delete(streamId);