@getpaseo/server 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/dist/scripts/daemon-runner.js +53 -14
  2. package/dist/scripts/daemon-runner.js.map +1 -1
  3. package/dist/scripts/dev-runner.js +9 -16
  4. package/dist/scripts/dev-runner.js.map +1 -1
  5. package/dist/scripts/supervisor.js +40 -13
  6. package/dist/scripts/supervisor.js.map +1 -1
  7. package/dist/server/client/daemon-client.d.ts +63 -6
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +436 -92
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/server/agent/agent-manager.d.ts +13 -1
  12. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-manager.js +404 -39
  14. package/dist/server/server/agent/agent-manager.js.map +1 -1
  15. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  16. package/dist/server/server/agent/agent-metadata-generator.js +13 -4
  17. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  18. package/dist/server/server/agent/agent-projections.d.ts +5 -0
  19. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  20. package/dist/server/server/agent/agent-projections.js +24 -0
  21. package/dist/server/server/agent/agent-projections.js.map +1 -1
  22. package/dist/server/server/agent/agent-response-loop.js +1 -1
  23. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  24. package/dist/server/server/agent/agent-sdk-types.d.ts +20 -0
  25. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  26. package/dist/server/server/agent/agent-sdk-types.js +11 -1
  27. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  28. package/dist/server/server/agent/agent-storage.d.ts +20 -6
  29. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  30. package/dist/server/server/agent/agent-storage.js +43 -72
  31. package/dist/server/server/agent/agent-storage.js.map +1 -1
  32. package/dist/server/server/agent/agent-title-limits.d.ts +3 -0
  33. package/dist/server/server/agent/agent-title-limits.d.ts.map +1 -0
  34. package/dist/server/server/agent/agent-title-limits.js +3 -0
  35. package/dist/server/server/agent/agent-title-limits.js.map +1 -0
  36. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +29 -0
  37. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -0
  38. package/dist/server/server/agent/providers/claude/model-catalog.js +70 -0
  39. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -0
  40. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +44 -0
  41. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -0
  42. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  43. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  44. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  45. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +17 -0
  46. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  47. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  48. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +2 -0
  49. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  50. package/dist/server/server/agent/providers/claude-agent.d.ts +10 -3
  51. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  52. package/dist/server/server/agent/providers/claude-agent.js +1702 -330
  53. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  54. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  55. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +81 -28
  56. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  57. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  58. package/dist/server/server/agent/providers/codex-app-server-agent.js +50 -9
  59. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  60. package/dist/server/server/agent/providers/opencode-agent.d.ts +10 -1
  61. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  62. package/dist/server/server/agent/providers/opencode-agent.js +207 -176
  63. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  64. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +55 -0
  65. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  66. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +1 -0
  67. package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
  68. package/dist/server/server/agent/timeline-projection.d.ts +20 -0
  69. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  70. package/dist/server/server/agent/timeline-projection.js +73 -0
  71. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  72. package/dist/server/server/bootstrap.d.ts +15 -0
  73. package/dist/server/server/bootstrap.d.ts.map +1 -1
  74. package/dist/server/server/bootstrap.js +27 -4
  75. package/dist/server/server/bootstrap.js.map +1 -1
  76. package/dist/server/server/client-message-id.d.ts +3 -0
  77. package/dist/server/server/client-message-id.d.ts.map +1 -0
  78. package/dist/server/server/client-message-id.js +12 -0
  79. package/dist/server/server/client-message-id.js.map +1 -0
  80. package/dist/server/server/file-download/token-store.d.ts +0 -1
  81. package/dist/server/server/file-download/token-store.d.ts.map +1 -1
  82. package/dist/server/server/file-download/token-store.js.map +1 -1
  83. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  84. package/dist/server/server/file-explorer/service.js +56 -36
  85. package/dist/server/server/file-explorer/service.js.map +1 -1
  86. package/dist/server/server/index.js +85 -29
  87. package/dist/server/server/index.js.map +1 -1
  88. package/dist/server/server/logger.d.ts +24 -3
  89. package/dist/server/server/logger.d.ts.map +1 -1
  90. package/dist/server/server/logger.js +157 -21
  91. package/dist/server/server/logger.js.map +1 -1
  92. package/dist/server/server/persisted-config.d.ts +94 -8
  93. package/dist/server/server/persisted-config.d.ts.map +1 -1
  94. package/dist/server/server/persisted-config.js +25 -3
  95. package/dist/server/server/persisted-config.js.map +1 -1
  96. package/dist/server/server/persistence-hooks.js +1 -1
  97. package/dist/server/server/persistence-hooks.js.map +1 -1
  98. package/dist/server/server/pid-lock.d.ts +6 -2
  99. package/dist/server/server/pid-lock.d.ts.map +1 -1
  100. package/dist/server/server/pid-lock.js +7 -10
  101. package/dist/server/server/pid-lock.js.map +1 -1
  102. package/dist/server/server/relay-transport.js +28 -28
  103. package/dist/server/server/relay-transport.js.map +1 -1
  104. package/dist/server/server/session.d.ts +60 -4
  105. package/dist/server/server/session.d.ts.map +1 -1
  106. package/dist/server/server/session.js +854 -190
  107. package/dist/server/server/session.js.map +1 -1
  108. package/dist/server/server/websocket-server.d.ts +24 -5
  109. package/dist/server/server/websocket-server.d.ts.map +1 -1
  110. package/dist/server/server/websocket-server.js +400 -77
  111. package/dist/server/server/websocket-server.js.map +1 -1
  112. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  113. package/dist/server/server/worktree-bootstrap.js +45 -2
  114. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  115. package/dist/server/shared/daemon-endpoints.d.ts +9 -1
  116. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
  117. package/dist/server/shared/daemon-endpoints.js +18 -3
  118. package/dist/server/shared/daemon-endpoints.js.map +1 -1
  119. package/dist/server/shared/messages.d.ts +4432 -380
  120. package/dist/server/shared/messages.d.ts.map +1 -1
  121. package/dist/server/shared/messages.js +139 -6
  122. package/dist/server/shared/messages.js.map +1 -1
  123. package/dist/server/shared/tool-call-display.d.ts.map +1 -1
  124. package/dist/server/shared/tool-call-display.js +7 -0
  125. package/dist/server/shared/tool-call-display.js.map +1 -1
  126. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  127. package/dist/server/terminal/terminal-manager.js +1 -13
  128. package/dist/server/terminal/terminal-manager.js.map +1 -1
  129. package/dist/server/terminal/terminal.d.ts.map +1 -1
  130. package/dist/server/terminal/terminal.js +29 -5
  131. package/dist/server/terminal/terminal.js.map +1 -1
  132. package/dist/server/utils/worktree.d.ts +1 -0
  133. package/dist/server/utils/worktree.d.ts.map +1 -1
  134. package/dist/server/utils/worktree.js +17 -2
  135. package/dist/server/utils/worktree.js.map +1 -1
  136. package/dist/src/server/agent/activity-curator.js +228 -0
  137. package/dist/src/server/agent/activity-curator.js.map +1 -0
  138. package/dist/src/server/agent/agent-manager.js +1712 -0
  139. package/dist/src/server/agent/agent-manager.js.map +1 -0
  140. package/dist/src/server/agent/agent-metadata-generator.js +163 -0
  141. package/dist/src/server/agent/agent-metadata-generator.js.map +1 -0
  142. package/dist/src/server/agent/agent-projections.js +262 -0
  143. package/dist/src/server/agent/agent-projections.js.map +1 -0
  144. package/dist/src/server/agent/agent-response-loop.js +304 -0
  145. package/dist/src/server/agent/agent-response-loop.js.map +1 -0
  146. package/dist/src/server/agent/agent-sdk-types.js +12 -0
  147. package/dist/src/server/agent/agent-sdk-types.js.map +1 -0
  148. package/dist/src/server/agent/agent-storage.js +299 -0
  149. package/dist/src/server/agent/agent-storage.js.map +1 -0
  150. package/dist/src/server/agent/agent-title-limits.js +3 -0
  151. package/dist/src/server/agent/agent-title-limits.js.map +1 -0
  152. package/dist/src/server/agent/audio-utils.js +19 -0
  153. package/dist/src/server/agent/audio-utils.js.map +1 -0
  154. package/dist/src/server/agent/dictation-debug.js +50 -0
  155. package/dist/src/server/agent/dictation-debug.js.map +1 -0
  156. package/dist/src/server/agent/mcp-server.js +787 -0
  157. package/dist/src/server/agent/mcp-server.js.map +1 -0
  158. package/dist/src/server/agent/orchestrator-instructions.js +51 -0
  159. package/dist/src/server/agent/orchestrator-instructions.js.map +1 -0
  160. package/dist/src/server/agent/pcm16-resampler.js +63 -0
  161. package/dist/src/server/agent/pcm16-resampler.js.map +1 -0
  162. package/dist/src/server/agent/provider-launch-config.js +83 -0
  163. package/dist/src/server/agent/provider-launch-config.js.map +1 -0
  164. package/dist/src/server/agent/provider-manifest.js +97 -0
  165. package/dist/src/server/agent/provider-manifest.js.map +1 -0
  166. package/dist/src/server/agent/provider-registry.js +45 -0
  167. package/dist/src/server/agent/provider-registry.js.map +1 -0
  168. package/dist/src/server/agent/providers/claude/model-catalog.js +70 -0
  169. package/dist/src/server/agent/providers/claude/model-catalog.js.map +1 -0
  170. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  171. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  172. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +109 -0
  173. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
  174. package/dist/src/server/agent/providers/claude/tool-call-mapper.js +238 -0
  175. package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
  176. package/dist/src/server/agent/providers/claude-agent.js +3747 -0
  177. package/dist/src/server/agent/providers/claude-agent.js.map +1 -0
  178. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  179. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
  180. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +720 -0
  181. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
  182. package/dist/src/server/agent/providers/codex-app-server-agent.js +2601 -0
  183. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -0
  184. package/dist/src/server/agent/providers/codex-rollout-timeline.js +487 -0
  185. package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +1 -0
  186. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
  187. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
  188. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +151 -0
  189. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
  190. package/dist/src/server/agent/providers/opencode-agent.js +905 -0
  191. package/dist/src/server/agent/providers/opencode-agent.js.map +1 -0
  192. package/dist/src/server/agent/providers/tool-call-detail-primitives.js +552 -0
  193. package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
  194. package/dist/src/server/agent/providers/tool-call-mapper-utils.js +109 -0
  195. package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
  196. package/dist/src/server/agent/recordings-debug.js +19 -0
  197. package/dist/src/server/agent/recordings-debug.js.map +1 -0
  198. package/dist/src/server/agent/stt-debug.js +33 -0
  199. package/dist/src/server/agent/stt-debug.js.map +1 -0
  200. package/dist/src/server/agent/stt-manager.js +233 -0
  201. package/dist/src/server/agent/stt-manager.js.map +1 -0
  202. package/dist/src/server/agent/timeline-append.js +27 -0
  203. package/dist/src/server/agent/timeline-append.js.map +1 -0
  204. package/dist/src/server/agent/timeline-projection.js +215 -0
  205. package/dist/src/server/agent/timeline-projection.js.map +1 -0
  206. package/dist/src/server/agent/tool-name-normalization.js +45 -0
  207. package/dist/src/server/agent/tool-name-normalization.js.map +1 -0
  208. package/dist/src/server/agent/tts-debug.js +24 -0
  209. package/dist/src/server/agent/tts-debug.js.map +1 -0
  210. package/dist/src/server/agent/tts-manager.js +249 -0
  211. package/dist/src/server/agent/tts-manager.js.map +1 -0
  212. package/dist/src/server/agent/wait-for-agent-tracker.js +53 -0
  213. package/dist/src/server/agent/wait-for-agent-tracker.js.map +1 -0
  214. package/dist/src/server/agent-attention-policy.js +40 -0
  215. package/dist/src/server/agent-attention-policy.js.map +1 -0
  216. package/dist/src/server/allowed-hosts.js +94 -0
  217. package/dist/src/server/allowed-hosts.js.map +1 -0
  218. package/dist/src/server/bootstrap.js +498 -0
  219. package/dist/src/server/bootstrap.js.map +1 -0
  220. package/dist/src/server/client-message-id.js +12 -0
  221. package/dist/src/server/client-message-id.js.map +1 -0
  222. package/dist/src/server/config.js +84 -0
  223. package/dist/src/server/config.js.map +1 -0
  224. package/dist/src/server/connection-offer.js +60 -0
  225. package/dist/src/server/connection-offer.js.map +1 -0
  226. package/dist/src/server/daemon-keypair.js +40 -0
  227. package/dist/src/server/daemon-keypair.js.map +1 -0
  228. package/dist/src/server/daemon-version.js +22 -0
  229. package/dist/src/server/daemon-version.js.map +1 -0
  230. package/dist/src/server/dictation/dictation-stream-manager.js +568 -0
  231. package/dist/src/server/dictation/dictation-stream-manager.js.map +1 -0
  232. package/dist/src/server/file-download/token-store.js +40 -0
  233. package/dist/src/server/file-download/token-store.js.map +1 -0
  234. package/dist/src/server/file-explorer/service.js +183 -0
  235. package/dist/src/server/file-explorer/service.js.map +1 -0
  236. package/dist/src/server/json-utils.js +45 -0
  237. package/dist/src/server/json-utils.js.map +1 -0
  238. package/dist/src/server/messages.js +29 -0
  239. package/dist/src/server/messages.js.map +1 -0
  240. package/dist/src/server/package-version.js +47 -0
  241. package/dist/src/server/package-version.js.map +1 -0
  242. package/dist/src/server/paseo-home.js +19 -0
  243. package/dist/src/server/paseo-home.js.map +1 -0
  244. package/dist/src/server/path-utils.js +20 -0
  245. package/dist/src/server/path-utils.js.map +1 -0
  246. package/dist/src/server/persisted-config.js +259 -0
  247. package/dist/src/server/persisted-config.js.map +1 -0
  248. package/dist/src/server/persistence-hooks.js +60 -0
  249. package/dist/src/server/persistence-hooks.js.map +1 -0
  250. package/dist/src/server/pid-lock.js +126 -0
  251. package/dist/src/server/pid-lock.js.map +1 -0
  252. package/dist/src/server/push/push-service.js +68 -0
  253. package/dist/src/server/push/push-service.js.map +1 -0
  254. package/dist/src/server/push/token-store.js +70 -0
  255. package/dist/src/server/push/token-store.js.map +1 -0
  256. package/dist/src/server/relay-transport.js +457 -0
  257. package/dist/src/server/relay-transport.js.map +1 -0
  258. package/dist/src/server/server-id.js +63 -0
  259. package/dist/src/server/server-id.js.map +1 -0
  260. package/dist/src/server/session.js +5947 -0
  261. package/dist/src/server/session.js.map +1 -0
  262. package/dist/src/server/speech/audio.js +101 -0
  263. package/dist/src/server/speech/audio.js.map +1 -0
  264. package/dist/src/server/speech/provider-resolver.js +7 -0
  265. package/dist/src/server/speech/provider-resolver.js.map +1 -0
  266. package/dist/src/server/speech/providers/local/config.js +83 -0
  267. package/dist/src/server/speech/providers/local/config.js.map +1 -0
  268. package/dist/src/server/speech/providers/local/models.js +17 -0
  269. package/dist/src/server/speech/providers/local/models.js.map +1 -0
  270. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +422 -0
  271. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -0
  272. package/dist/src/server/speech/providers/local/runtime.js +253 -0
  273. package/dist/src/server/speech/providers/local/runtime.js.map +1 -0
  274. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +166 -0
  275. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +1 -0
  276. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +165 -0
  277. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +1 -0
  278. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +68 -0
  279. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -0
  280. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +79 -0
  281. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -0
  282. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +11 -0
  283. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +1 -0
  284. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +102 -0
  285. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -0
  286. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +131 -0
  287. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -0
  288. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +132 -0
  289. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -0
  290. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +112 -0
  291. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -0
  292. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +140 -0
  293. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -0
  294. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +95 -0
  295. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -0
  296. package/dist/src/server/speech/providers/openai/config.js +99 -0
  297. package/dist/src/server/speech/providers/openai/config.js.map +1 -0
  298. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +165 -0
  299. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +1 -0
  300. package/dist/src/server/speech/providers/openai/runtime.js +114 -0
  301. package/dist/src/server/speech/providers/openai/runtime.js.map +1 -0
  302. package/dist/src/server/speech/providers/openai/stt.js +208 -0
  303. package/dist/src/server/speech/providers/openai/stt.js.map +1 -0
  304. package/dist/src/server/speech/providers/openai/tts.js +46 -0
  305. package/dist/src/server/speech/providers/openai/tts.js.map +1 -0
  306. package/dist/src/server/speech/speech-config-resolver.js +85 -0
  307. package/dist/src/server/speech/speech-config-resolver.js.map +1 -0
  308. package/dist/src/server/speech/speech-provider.js +2 -0
  309. package/dist/src/server/speech/speech-provider.js.map +1 -0
  310. package/dist/src/server/speech/speech-runtime.js +497 -0
  311. package/dist/src/server/speech/speech-runtime.js.map +1 -0
  312. package/dist/src/server/speech/speech-types.js +8 -0
  313. package/dist/src/server/speech/speech-types.js.map +1 -0
  314. package/dist/src/server/utils/diff-highlighter.js +244 -0
  315. package/dist/src/server/utils/diff-highlighter.js.map +1 -0
  316. package/dist/src/server/utils/syntax-highlighter.js +145 -0
  317. package/dist/src/server/utils/syntax-highlighter.js.map +1 -0
  318. package/dist/src/server/voice-config.js +51 -0
  319. package/dist/src/server/voice-config.js.map +1 -0
  320. package/dist/src/server/voice-mcp-bridge-command.js +31 -0
  321. package/dist/src/server/voice-mcp-bridge-command.js.map +1 -0
  322. package/dist/src/server/voice-mcp-bridge.js +109 -0
  323. package/dist/src/server/voice-mcp-bridge.js.map +1 -0
  324. package/dist/src/server/voice-permission-policy.js +13 -0
  325. package/dist/src/server/voice-permission-policy.js.map +1 -0
  326. package/dist/src/server/voice-types.js +2 -0
  327. package/dist/src/server/voice-types.js.map +1 -0
  328. package/dist/src/server/websocket-server.js +967 -0
  329. package/dist/src/server/websocket-server.js.map +1 -0
  330. package/dist/src/server/worktree-bootstrap.js +497 -0
  331. package/dist/src/server/worktree-bootstrap.js.map +1 -0
  332. package/dist/src/shared/agent-attention-notification.js +130 -0
  333. package/dist/src/shared/agent-attention-notification.js.map +1 -0
  334. package/dist/src/shared/agent-lifecycle.js +8 -0
  335. package/dist/src/shared/agent-lifecycle.js.map +1 -0
  336. package/dist/src/shared/binary-mux.js +114 -0
  337. package/dist/src/shared/binary-mux.js.map +1 -0
  338. package/dist/src/shared/connection-offer.js +17 -0
  339. package/dist/src/shared/connection-offer.js.map +1 -0
  340. package/dist/src/shared/daemon-endpoints.js +113 -0
  341. package/dist/src/shared/daemon-endpoints.js.map +1 -0
  342. package/dist/src/shared/messages.js +2001 -0
  343. package/dist/src/shared/messages.js.map +1 -0
  344. package/dist/src/shared/path-utils.js +16 -0
  345. package/dist/src/shared/path-utils.js.map +1 -0
  346. package/dist/src/shared/tool-call-display.js +93 -0
  347. package/dist/src/shared/tool-call-display.js.map +1 -0
  348. package/dist/src/terminal/terminal-manager.js +136 -0
  349. package/dist/src/terminal/terminal-manager.js.map +1 -0
  350. package/dist/src/terminal/terminal.js +410 -0
  351. package/dist/src/terminal/terminal.js.map +1 -0
  352. package/dist/src/utils/checkout-git.js +1397 -0
  353. package/dist/src/utils/checkout-git.js.map +1 -0
  354. package/dist/src/utils/directory-suggestions.js +655 -0
  355. package/dist/src/utils/directory-suggestions.js.map +1 -0
  356. package/dist/src/utils/path.js +15 -0
  357. package/dist/src/utils/path.js.map +1 -0
  358. package/dist/src/utils/project-icon.js +391 -0
  359. package/dist/src/utils/project-icon.js.map +1 -0
  360. package/dist/src/utils/worktree-metadata.js +116 -0
  361. package/dist/src/utils/worktree-metadata.js.map +1 -0
  362. package/dist/src/utils/worktree.js +741 -0
  363. package/dist/src/utils/worktree.js.map +1 -0
  364. package/package.json +15 -7
@@ -0,0 +1,1712 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+ import { stat } from "node:fs/promises";
4
+ import { AGENT_LIFECYCLE_STATUSES, } from "../../shared/agent-lifecycle.js";
5
+ import { z } from "zod";
6
+ import { AGENT_PROVIDER_IDS } from "./provider-manifest.js";
7
+ export { AGENT_LIFECYCLE_STATUSES };
8
+ const SYSTEM_ERROR_PREFIX = "[System Error]";
9
+ function attachPersistenceCwd(handle, cwd) {
10
+ if (!handle) {
11
+ return null;
12
+ }
13
+ return {
14
+ ...handle,
15
+ metadata: {
16
+ ...(handle.metadata ?? {}),
17
+ cwd,
18
+ },
19
+ };
20
+ }
21
+ const DEFAULT_TIMELINE_FETCH_LIMIT = 200;
22
+ const LIVE_BACKLOG_TERMINAL_REPLAY_DELAY_MS = 300;
23
+ const BUSY_STATUSES = [
24
+ "initializing",
25
+ "running",
26
+ ];
27
+ const AgentIdSchema = z.string().uuid();
28
+ function isAgentBusy(status) {
29
+ return BUSY_STATUSES.includes(status);
30
+ }
31
+ function isTurnTerminalEvent(event) {
32
+ return (event.type === "turn_completed" ||
33
+ event.type === "turn_failed" ||
34
+ event.type === "turn_canceled");
35
+ }
36
+ function createAbortError(signal, fallbackMessage) {
37
+ const reason = signal?.reason;
38
+ const message = typeof reason === "string"
39
+ ? reason
40
+ : reason instanceof Error
41
+ ? reason.message
42
+ : fallbackMessage;
43
+ return Object.assign(new Error(message), { name: "AbortError" });
44
+ }
45
+ function validateAgentId(agentId, source) {
46
+ const result = AgentIdSchema.safeParse(agentId);
47
+ if (!result.success) {
48
+ throw new Error(`${source}: agentId must be a UUID`);
49
+ }
50
+ return result.data;
51
+ }
52
+ function supportsLiveEventStream(session) {
53
+ return ("streamLiveEvents" in session &&
54
+ typeof session.streamLiveEvents ===
55
+ "function");
56
+ }
57
+ function normalizeMessageId(messageId) {
58
+ if (typeof messageId !== "string") {
59
+ return undefined;
60
+ }
61
+ const trimmed = messageId.trim();
62
+ return trimmed.length > 0 ? trimmed : undefined;
63
+ }
64
+ export class AgentManager {
65
+ constructor(options) {
66
+ this.clients = new Map();
67
+ this.agents = new Map();
68
+ this.subscribers = new Set();
69
+ this.previousStatuses = new Map();
70
+ this.backgroundTasks = new Set();
71
+ this.liveEventPumps = new Map();
72
+ this.liveEventBacklog = new Map();
73
+ this.liveEventBacklogFlushTimers = new Map();
74
+ const maxTimelineItems = options?.maxTimelineItems;
75
+ this.maxTimelineItems =
76
+ typeof maxTimelineItems === "number" &&
77
+ Number.isFinite(maxTimelineItems) &&
78
+ maxTimelineItems >= 0
79
+ ? Math.floor(maxTimelineItems)
80
+ : null;
81
+ this.idFactory = options?.idFactory ?? (() => randomUUID());
82
+ this.registry = options?.registry;
83
+ this.onAgentAttention = options?.onAgentAttention;
84
+ this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
85
+ if (options?.clients) {
86
+ for (const [provider, client] of Object.entries(options.clients)) {
87
+ if (client) {
88
+ this.registerClient(provider, client);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ registerClient(provider, client) {
94
+ this.clients.set(provider, client);
95
+ }
96
+ setAgentAttentionCallback(callback) {
97
+ this.onAgentAttention = callback;
98
+ }
99
+ touchUpdatedAt(agent) {
100
+ const nowMs = Date.now();
101
+ const previousMs = agent.updatedAt.getTime();
102
+ const nextMs = nowMs > previousMs ? nowMs : previousMs + 1;
103
+ const next = new Date(nextMs);
104
+ agent.updatedAt = next;
105
+ return next;
106
+ }
107
+ subscribe(callback, options) {
108
+ const targetAgentId = options?.agentId == null
109
+ ? null
110
+ : validateAgentId(options.agentId, "subscribe");
111
+ const record = {
112
+ callback,
113
+ agentId: targetAgentId,
114
+ };
115
+ this.subscribers.add(record);
116
+ if (options?.replayState !== false) {
117
+ if (record.agentId) {
118
+ const agent = this.agents.get(record.agentId);
119
+ if (agent) {
120
+ callback({
121
+ type: "agent_state",
122
+ agent: { ...agent },
123
+ });
124
+ }
125
+ }
126
+ else {
127
+ // For global subscribers, skip internal agents during replay
128
+ for (const agent of this.agents.values()) {
129
+ if (agent.internal) {
130
+ continue;
131
+ }
132
+ callback({
133
+ type: "agent_state",
134
+ agent: { ...agent },
135
+ });
136
+ }
137
+ }
138
+ }
139
+ return () => {
140
+ this.subscribers.delete(record);
141
+ };
142
+ }
143
+ listAgents() {
144
+ return Array.from(this.agents.values())
145
+ .filter((agent) => !agent.internal)
146
+ .map((agent) => ({
147
+ ...agent,
148
+ }));
149
+ }
150
+ async listPersistedAgents(options) {
151
+ if (options?.provider) {
152
+ const client = this.requireClient(options.provider);
153
+ if (!client.listPersistedAgents) {
154
+ return [];
155
+ }
156
+ return client.listPersistedAgents({ limit: options.limit });
157
+ }
158
+ const descriptors = [];
159
+ for (const [provider, client] of this.clients.entries()) {
160
+ if (!client.listPersistedAgents) {
161
+ continue;
162
+ }
163
+ try {
164
+ const entries = await client.listPersistedAgents({
165
+ limit: options?.limit,
166
+ });
167
+ descriptors.push(...entries);
168
+ }
169
+ catch (error) {
170
+ this.logger.warn({ err: error, provider }, "Failed to list persisted agents for provider");
171
+ }
172
+ }
173
+ const limit = options?.limit ?? 20;
174
+ return descriptors
175
+ .sort((a, b) => b.lastActivityAt.getTime() - a.lastActivityAt.getTime())
176
+ .slice(0, limit);
177
+ }
178
+ async listProviderAvailability() {
179
+ const checks = AGENT_PROVIDER_IDS.map(async (providerId) => {
180
+ const provider = providerId;
181
+ const client = this.clients.get(provider);
182
+ if (!client) {
183
+ return {
184
+ provider,
185
+ available: false,
186
+ error: `No client registered for provider '${provider}'`,
187
+ };
188
+ }
189
+ try {
190
+ const available = await client.isAvailable();
191
+ return {
192
+ provider,
193
+ available,
194
+ error: null,
195
+ };
196
+ }
197
+ catch (error) {
198
+ const message = error instanceof Error ? error.message : String(error);
199
+ this.logger.warn({ err: error, provider }, "Failed to check provider availability");
200
+ return {
201
+ provider,
202
+ available: false,
203
+ error: message,
204
+ };
205
+ }
206
+ });
207
+ return Promise.all(checks);
208
+ }
209
+ async listDraftCommands(config) {
210
+ const normalizedConfig = await this.normalizeConfig(config);
211
+ const client = this.requireClient(normalizedConfig.provider);
212
+ const available = await client.isAvailable();
213
+ if (!available) {
214
+ throw new Error(`Provider '${normalizedConfig.provider}' is not available. Please ensure the CLI is installed.`);
215
+ }
216
+ const session = await client.createSession(normalizedConfig);
217
+ try {
218
+ if (!session.listCommands) {
219
+ throw new Error(`Provider '${normalizedConfig.provider}' does not support listing commands`);
220
+ }
221
+ return await session.listCommands();
222
+ }
223
+ finally {
224
+ try {
225
+ await session.close();
226
+ }
227
+ catch (error) {
228
+ this.logger.warn({ err: error, provider: normalizedConfig.provider }, "Failed to close draft command listing session");
229
+ }
230
+ }
231
+ }
232
+ getAgent(id) {
233
+ const agent = this.agents.get(id);
234
+ return agent ? { ...agent } : null;
235
+ }
236
+ getTimeline(id) {
237
+ const agent = this.requireAgent(id);
238
+ return [...agent.timeline];
239
+ }
240
+ getTimelineRows(id) {
241
+ const agent = this.requireAgent(id);
242
+ const { rows } = this.ensureTimelineState(agent);
243
+ return rows.map((row) => ({ ...row }));
244
+ }
245
+ fetchTimeline(id, options) {
246
+ const agent = this.requireAgent(id);
247
+ const { rows, epoch, nextSeq, minSeq, maxSeq, } = this.ensureTimelineState(agent);
248
+ const direction = options?.direction ?? "tail";
249
+ const requestedLimit = options?.limit;
250
+ const limit = requestedLimit === undefined
251
+ ? DEFAULT_TIMELINE_FETCH_LIMIT
252
+ : Math.max(0, Math.floor(requestedLimit));
253
+ const cursor = options?.cursor;
254
+ const window = { minSeq, maxSeq, nextSeq };
255
+ if (cursor && cursor.epoch !== epoch) {
256
+ return {
257
+ epoch,
258
+ direction,
259
+ reset: true,
260
+ staleCursor: true,
261
+ gap: false,
262
+ window,
263
+ hasOlder: false,
264
+ hasNewer: false,
265
+ rows: rows.map((row) => ({ ...row })),
266
+ };
267
+ }
268
+ const selectAll = limit === 0;
269
+ const cloneRows = (items) => items.map((row) => ({ ...row }));
270
+ if (direction === "after" && cursor && rows.length > 0 && cursor.seq < minSeq - 1) {
271
+ return {
272
+ epoch,
273
+ direction,
274
+ reset: true,
275
+ staleCursor: false,
276
+ gap: true,
277
+ window,
278
+ hasOlder: false,
279
+ hasNewer: false,
280
+ rows: cloneRows(rows),
281
+ };
282
+ }
283
+ if (rows.length === 0) {
284
+ return {
285
+ epoch,
286
+ direction,
287
+ reset: false,
288
+ staleCursor: false,
289
+ gap: false,
290
+ window,
291
+ hasOlder: false,
292
+ hasNewer: false,
293
+ rows: [],
294
+ };
295
+ }
296
+ if (direction === "tail") {
297
+ const selected = selectAll || limit >= rows.length ? rows : rows.slice(rows.length - limit);
298
+ const hasOlder = selected.length > 0 && selected[0].seq > minSeq;
299
+ return {
300
+ epoch,
301
+ direction,
302
+ reset: false,
303
+ staleCursor: false,
304
+ gap: false,
305
+ window,
306
+ hasOlder,
307
+ hasNewer: false,
308
+ rows: cloneRows(selected),
309
+ };
310
+ }
311
+ if (direction === "after") {
312
+ const baseSeq = cursor?.seq ?? 0;
313
+ const startIdx = rows.findIndex((row) => row.seq > baseSeq);
314
+ if (startIdx < 0) {
315
+ return {
316
+ epoch,
317
+ direction,
318
+ reset: false,
319
+ staleCursor: false,
320
+ gap: false,
321
+ window,
322
+ hasOlder: baseSeq >= minSeq,
323
+ hasNewer: false,
324
+ rows: [],
325
+ };
326
+ }
327
+ const selected = selectAll ? rows.slice(startIdx) : rows.slice(startIdx, startIdx + limit);
328
+ const lastSelected = selected[selected.length - 1];
329
+ return {
330
+ epoch,
331
+ direction,
332
+ reset: false,
333
+ staleCursor: false,
334
+ gap: false,
335
+ window,
336
+ hasOlder: selected[0].seq > minSeq,
337
+ hasNewer: Boolean(lastSelected && lastSelected.seq < maxSeq),
338
+ rows: cloneRows(selected),
339
+ };
340
+ }
341
+ // direction === "before"
342
+ const beforeSeq = cursor?.seq ?? nextSeq;
343
+ const endExclusive = rows.findIndex((row) => row.seq >= beforeSeq);
344
+ const boundedRows = endExclusive < 0 ? rows : rows.slice(0, endExclusive);
345
+ const selected = selectAll || limit >= boundedRows.length
346
+ ? boundedRows
347
+ : boundedRows.slice(boundedRows.length - limit);
348
+ const hasOlder = selected.length > 0 && selected[0].seq > minSeq;
349
+ const hasNewer = endExclusive >= 0;
350
+ return {
351
+ epoch,
352
+ direction,
353
+ reset: false,
354
+ staleCursor: false,
355
+ gap: false,
356
+ window,
357
+ hasOlder,
358
+ hasNewer,
359
+ rows: cloneRows(selected),
360
+ };
361
+ }
362
+ async createAgent(config, agentId, options) {
363
+ // Generate agent ID early so we can use it in MCP config
364
+ const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "createAgent");
365
+ const normalizedConfig = await this.normalizeConfig(config, {
366
+ labels: options?.labels,
367
+ agentId: resolvedAgentId,
368
+ });
369
+ const client = this.requireClient(normalizedConfig.provider);
370
+ const available = await client.isAvailable();
371
+ if (!available) {
372
+ throw new Error(`Provider '${normalizedConfig.provider}' is not available. Please ensure the CLI is installed.`);
373
+ }
374
+ const session = await client.createSession(normalizedConfig);
375
+ return this.registerSession(session, normalizedConfig, resolvedAgentId, { labels: options?.labels });
376
+ }
377
+ // Reconstruct an agent from provider persistence. Callers should explicitly
378
+ // hydrate timeline history after resume.
379
+ async resumeAgentFromPersistence(handle, overrides, agentId, options) {
380
+ const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "resumeAgentFromPersistence");
381
+ const metadata = (handle.metadata ?? {});
382
+ const mergedConfig = {
383
+ ...metadata,
384
+ ...overrides,
385
+ provider: handle.provider,
386
+ };
387
+ const normalizedConfig = await this.normalizeConfig(mergedConfig);
388
+ const resumeOverrides = normalizedConfig.model !== mergedConfig.model
389
+ ? { ...overrides, model: normalizedConfig.model }
390
+ : overrides;
391
+ const client = this.requireClient(handle.provider);
392
+ const session = await client.resumeSession(handle, resumeOverrides);
393
+ return this.registerSession(session, normalizedConfig, resolvedAgentId, options);
394
+ }
395
+ // Hot-reload an active agent session with config overrides while preserving
396
+ // in-memory timeline state.
397
+ async reloadAgentSession(agentId, overrides) {
398
+ let existing = this.requireAgent(agentId);
399
+ if (existing.lifecycle === "running" || existing.pendingRun) {
400
+ await this.cancelAgentRun(agentId);
401
+ existing = this.requireAgent(agentId);
402
+ }
403
+ const timelineState = this.ensureTimelineState(existing);
404
+ const preservedTimeline = [...existing.timeline];
405
+ const preservedTimelineRows = timelineState.rows.map((row) => ({ ...row }));
406
+ const preservedTimelineEpoch = timelineState.epoch;
407
+ const preservedTimelineNextSeq = timelineState.nextSeq;
408
+ const preservedHistoryPrimed = existing.historyPrimed;
409
+ const preservedLastUsage = existing.lastUsage;
410
+ const preservedLastError = existing.lastError;
411
+ const preservedAttention = existing.attention;
412
+ const handle = existing.persistence;
413
+ const provider = handle?.provider ?? existing.provider;
414
+ const client = this.requireClient(provider);
415
+ const refreshConfig = {
416
+ ...existing.config,
417
+ ...overrides,
418
+ provider,
419
+ };
420
+ const normalizedConfig = await this.normalizeConfig(refreshConfig);
421
+ const session = handle
422
+ ? await client.resumeSession(handle, normalizedConfig)
423
+ : await client.createSession(normalizedConfig);
424
+ // Remove the existing agent entry before swapping sessions
425
+ this.agents.delete(agentId);
426
+ this.liveEventPumps.delete(agentId);
427
+ this.liveEventBacklog.delete(agentId);
428
+ this.clearLiveEventBacklogFlushTimer(agentId);
429
+ try {
430
+ await existing.session.close();
431
+ }
432
+ catch (error) {
433
+ this.logger.warn({ err: error, agentId }, "Failed to close previous session during refresh");
434
+ }
435
+ // Preserve existing labels and timeline during reload.
436
+ return this.registerSession(session, normalizedConfig, agentId, {
437
+ labels: existing.labels,
438
+ createdAt: existing.createdAt,
439
+ updatedAt: existing.updatedAt,
440
+ lastUserMessageAt: existing.lastUserMessageAt,
441
+ timeline: preservedTimeline,
442
+ timelineRows: preservedTimelineRows,
443
+ timelineEpoch: preservedTimelineEpoch,
444
+ timelineNextSeq: preservedTimelineNextSeq,
445
+ historyPrimed: preservedHistoryPrimed,
446
+ lastUsage: preservedLastUsage,
447
+ lastError: preservedLastError,
448
+ attention: preservedAttention,
449
+ });
450
+ }
451
+ async closeAgent(agentId) {
452
+ const agent = this.requireAgent(agentId);
453
+ this.logger.trace({
454
+ agentId,
455
+ lifecycle: agent.lifecycle,
456
+ hasPendingRun: Boolean(agent.pendingRun),
457
+ pendingPermissions: agent.pendingPermissions.size,
458
+ }, "closeAgent: start");
459
+ this.agents.delete(agentId);
460
+ this.liveEventPumps.delete(agentId);
461
+ this.liveEventBacklog.delete(agentId);
462
+ this.clearLiveEventBacklogFlushTimer(agentId);
463
+ // Clean up previousStatus to prevent memory leak
464
+ this.previousStatuses.delete(agentId);
465
+ const session = agent.session;
466
+ const closedAgent = {
467
+ ...agent,
468
+ lifecycle: "closed",
469
+ session: null,
470
+ pendingRun: null,
471
+ };
472
+ await session.close();
473
+ this.emitState(closedAgent);
474
+ this.logger.trace({ agentId }, "closeAgent: completed");
475
+ }
476
+ async setAgentMode(agentId, modeId) {
477
+ const agent = this.requireAgent(agentId);
478
+ await agent.session.setMode(modeId);
479
+ agent.currentModeId = modeId;
480
+ // Update runtimeInfo to reflect the new mode
481
+ if (agent.runtimeInfo) {
482
+ agent.runtimeInfo = { ...agent.runtimeInfo, modeId };
483
+ }
484
+ this.emitState(agent);
485
+ }
486
+ async setAgentModel(agentId, modelId) {
487
+ const agent = this.requireAgent(agentId);
488
+ const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
489
+ if (agent.session.setModel) {
490
+ await agent.session.setModel(normalizedModelId);
491
+ }
492
+ agent.config.model = normalizedModelId ?? undefined;
493
+ if (agent.runtimeInfo) {
494
+ agent.runtimeInfo = { ...agent.runtimeInfo, model: normalizedModelId };
495
+ }
496
+ this.emitState(agent);
497
+ }
498
+ async setAgentThinkingOption(agentId, thinkingOptionId) {
499
+ const agent = this.requireAgent(agentId);
500
+ const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
501
+ ? thinkingOptionId
502
+ : null;
503
+ if (agent.session.setThinkingOption) {
504
+ await agent.session.setThinkingOption(normalizedThinkingOptionId);
505
+ }
506
+ agent.config.thinkingOptionId = normalizedThinkingOptionId ?? undefined;
507
+ this.emitState(agent);
508
+ }
509
+ async setTitle(agentId, title) {
510
+ const agent = this.requireAgent(agentId);
511
+ const normalizedTitle = title.trim();
512
+ if (!normalizedTitle) {
513
+ return;
514
+ }
515
+ this.touchUpdatedAt(agent);
516
+ await this.persistSnapshot(agent, { title: normalizedTitle });
517
+ this.emitState(agent);
518
+ }
519
+ async setLabels(agentId, labels) {
520
+ const agent = this.requireAgent(agentId);
521
+ agent.labels = { ...agent.labels, ...labels };
522
+ await this.persistSnapshot(agent);
523
+ this.emitState(agent);
524
+ }
525
+ notifyAgentState(agentId) {
526
+ const agent = this.agents.get(agentId);
527
+ if (!agent || agent.internal) {
528
+ return;
529
+ }
530
+ this.touchUpdatedAt(agent);
531
+ this.emitState(agent);
532
+ }
533
+ async clearAgentAttention(agentId) {
534
+ const agent = this.requireAgent(agentId);
535
+ if (agent.attention.requiresAttention) {
536
+ agent.attention = { requiresAttention: false };
537
+ await this.persistSnapshot(agent);
538
+ this.emitState(agent);
539
+ }
540
+ }
541
+ async runAgent(agentId, prompt, options) {
542
+ const events = this.streamAgent(agentId, prompt, options);
543
+ const timeline = [];
544
+ let finalText = "";
545
+ let usage;
546
+ let canceled = false;
547
+ for await (const event of events) {
548
+ if (event.type === "timeline") {
549
+ timeline.push(event.item);
550
+ }
551
+ else if (event.type === "turn_completed") {
552
+ usage = event.usage;
553
+ }
554
+ else if (event.type === "turn_failed") {
555
+ throw new Error(this.formatTurnFailedMessage(event));
556
+ }
557
+ else if (event.type === "turn_canceled") {
558
+ canceled = true;
559
+ }
560
+ }
561
+ finalText = this.getLastAssistantMessageFromTimeline(timeline) ?? "";
562
+ const agent = this.requireAgent(agentId);
563
+ const sessionId = agent.persistence?.sessionId;
564
+ if (!sessionId) {
565
+ throw new Error(`Agent ${agentId} has no persistence.sessionId after run completed`);
566
+ }
567
+ return {
568
+ sessionId,
569
+ finalText,
570
+ usage,
571
+ timeline,
572
+ canceled,
573
+ };
574
+ }
575
+ recordUserMessage(agentId, text, options) {
576
+ const agent = this.requireAgent(agentId);
577
+ const normalizedMessageId = normalizeMessageId(options?.messageId);
578
+ const item = {
579
+ type: "user_message",
580
+ text,
581
+ messageId: normalizedMessageId,
582
+ };
583
+ const updatedAt = this.touchUpdatedAt(agent);
584
+ agent.lastUserMessageAt = updatedAt;
585
+ const row = this.recordTimeline(agent, item);
586
+ this.dispatchStream(agentId, {
587
+ type: "timeline",
588
+ item,
589
+ provider: agent.provider,
590
+ }, {
591
+ seq: row.seq,
592
+ epoch: this.ensureTimelineState(agent).epoch,
593
+ });
594
+ if (options?.emitState !== false) {
595
+ this.emitState(agent);
596
+ }
597
+ }
598
+ async appendTimelineItem(agentId, item) {
599
+ const agent = this.requireAgent(agentId);
600
+ this.touchUpdatedAt(agent);
601
+ const row = this.recordTimeline(agent, item);
602
+ this.dispatchStream(agentId, {
603
+ type: "timeline",
604
+ item,
605
+ provider: agent.provider,
606
+ }, {
607
+ seq: row.seq,
608
+ epoch: this.ensureTimelineState(agent).epoch,
609
+ });
610
+ await this.persistSnapshot(agent);
611
+ }
612
+ async emitLiveTimelineItem(agentId, item) {
613
+ const agent = this.requireAgent(agentId);
614
+ this.touchUpdatedAt(agent);
615
+ this.dispatchStream(agentId, {
616
+ type: "timeline",
617
+ item,
618
+ provider: agent.provider,
619
+ });
620
+ }
621
+ streamAgent(agentId, prompt, options) {
622
+ const existingAgent = this.requireAgent(agentId);
623
+ this.logger.trace({
624
+ agentId,
625
+ lifecycle: existingAgent.lifecycle,
626
+ hasPendingRun: Boolean(existingAgent.pendingRun),
627
+ promptType: typeof prompt === "string" ? "string" : "structured",
628
+ hasRunOptions: Boolean(options),
629
+ }, "streamAgent: requested");
630
+ if (existingAgent.pendingRun) {
631
+ this.logger.trace({
632
+ agentId,
633
+ lifecycle: existingAgent.lifecycle,
634
+ }, "streamAgent: rejected because pendingRun already exists");
635
+ throw new Error(`Agent ${agentId} already has an active run`);
636
+ }
637
+ const agent = existingAgent;
638
+ const iterator = agent.session.stream(prompt, options);
639
+ agent.lastError = undefined;
640
+ let finalized = false;
641
+ const finalize = (error) => {
642
+ this.logger.trace({
643
+ agentId,
644
+ error,
645
+ alreadyFinalized: finalized,
646
+ lifecycle: agent.lifecycle,
647
+ hasPendingRun: Boolean(agent.pendingRun),
648
+ }, "streamAgent.finalize: invoked");
649
+ if (finalized) {
650
+ return;
651
+ }
652
+ finalized = true;
653
+ if (agent.pendingRun !== streamForwarder) {
654
+ this.logger.trace({
655
+ agentId,
656
+ error,
657
+ lifecycle: agent.lifecycle,
658
+ hasPendingRun: Boolean(agent.pendingRun),
659
+ }, "streamAgent.finalize: skipped because pendingRun no longer points to streamForwarder");
660
+ if (error) {
661
+ agent.lastError = error;
662
+ }
663
+ return;
664
+ }
665
+ const mutableAgent = agent;
666
+ mutableAgent.pendingRun = null;
667
+ const terminalError = error ?? mutableAgent.lastError;
668
+ mutableAgent.lifecycle = terminalError ? "error" : "idle";
669
+ mutableAgent.lastError = terminalError;
670
+ const persistenceHandle = mutableAgent.session.describePersistence() ??
671
+ (mutableAgent.runtimeInfo?.sessionId
672
+ ? { provider: mutableAgent.provider, sessionId: mutableAgent.runtimeInfo.sessionId }
673
+ : null);
674
+ if (persistenceHandle) {
675
+ mutableAgent.persistence = attachPersistenceCwd(persistenceHandle, mutableAgent.cwd);
676
+ }
677
+ this.logger.trace({
678
+ agentId,
679
+ lifecycle: mutableAgent.lifecycle,
680
+ hasPendingRun: Boolean(mutableAgent.pendingRun),
681
+ terminalError,
682
+ }, "streamAgent.finalize: applying terminal state");
683
+ this.emitState(mutableAgent);
684
+ this.flushLiveEventBacklog(mutableAgent);
685
+ };
686
+ const self = this;
687
+ const streamForwarder = (async function* streamForwarder() {
688
+ let finalizeError;
689
+ try {
690
+ for await (const event of iterator) {
691
+ self.handleStreamEvent(agent, event);
692
+ if (event.type === "turn_started" ||
693
+ event.type === "turn_completed" ||
694
+ event.type === "turn_failed" ||
695
+ event.type === "turn_canceled") {
696
+ self.logger.trace({
697
+ agentId,
698
+ eventType: event.type,
699
+ lifecycle: agent.lifecycle,
700
+ hasPendingRun: Boolean(agent.pendingRun),
701
+ }, "streamAgent: forwarded terminal/turn event");
702
+ }
703
+ yield event;
704
+ }
705
+ }
706
+ catch (error) {
707
+ finalizeError =
708
+ error instanceof Error ? error.message : "Agent stream failed";
709
+ self.handleStreamEvent(agent, {
710
+ type: "turn_failed",
711
+ provider: agent.provider,
712
+ error: finalizeError,
713
+ });
714
+ throw error;
715
+ }
716
+ finally {
717
+ await self.refreshRuntimeInfo(agent);
718
+ // Ensure we always clear the pending run and emit state when the stream is
719
+ // cancelled early (e.g., via .return()) so the UI can exit the cancelling state.
720
+ finalize(finalizeError);
721
+ }
722
+ })();
723
+ agent.pendingRun = streamForwarder;
724
+ agent.lifecycle = "running";
725
+ // Bump updatedAt when lifecycle changes so downstream consumers can
726
+ // deterministically order idle->running transitions.
727
+ this.touchUpdatedAt(agent);
728
+ self.emitState(agent);
729
+ this.logger.trace({
730
+ agentId,
731
+ lifecycle: agent.lifecycle,
732
+ hasPendingRun: Boolean(agent.pendingRun),
733
+ }, "streamAgent: started");
734
+ return streamForwarder;
735
+ }
736
+ async waitForAgentRunStart(agentId, options) {
737
+ const snapshot = this.getAgent(agentId);
738
+ if (!snapshot) {
739
+ throw new Error(`Agent ${agentId} not found`);
740
+ }
741
+ if (snapshot.lifecycle === "running") {
742
+ return;
743
+ }
744
+ if (!("pendingRun" in snapshot) || !snapshot.pendingRun) {
745
+ throw new Error(`Agent ${agentId} has no pending run`);
746
+ }
747
+ if (options?.signal?.aborted) {
748
+ throw createAbortError(options.signal, "wait_for_agent_start aborted");
749
+ }
750
+ await new Promise((resolve, reject) => {
751
+ if (options?.signal?.aborted) {
752
+ reject(createAbortError(options.signal, "wait_for_agent_start aborted"));
753
+ return;
754
+ }
755
+ let unsubscribe = null;
756
+ let abortHandler = null;
757
+ const cleanup = () => {
758
+ if (unsubscribe) {
759
+ try {
760
+ unsubscribe();
761
+ }
762
+ catch {
763
+ // ignore cleanup errors
764
+ }
765
+ unsubscribe = null;
766
+ }
767
+ if (abortHandler && options?.signal) {
768
+ try {
769
+ options.signal.removeEventListener("abort", abortHandler);
770
+ }
771
+ catch {
772
+ // ignore cleanup errors
773
+ }
774
+ abortHandler = null;
775
+ }
776
+ };
777
+ const finishOk = () => {
778
+ cleanup();
779
+ resolve();
780
+ };
781
+ const finishErr = (error) => {
782
+ cleanup();
783
+ reject(error);
784
+ };
785
+ if (options?.signal) {
786
+ abortHandler = () => finishErr(createAbortError(options.signal, "wait_for_agent_start aborted"));
787
+ options.signal.addEventListener("abort", abortHandler, { once: true });
788
+ }
789
+ unsubscribe = this.subscribe((event) => {
790
+ if (event.type === "agent_state") {
791
+ if (event.agent.id !== agentId) {
792
+ return;
793
+ }
794
+ if (event.agent.lifecycle === "running") {
795
+ finishOk();
796
+ return;
797
+ }
798
+ if (event.agent.lifecycle === "error") {
799
+ finishErr(new Error(event.agent.lastError ?? `Agent ${agentId} failed to start`));
800
+ return;
801
+ }
802
+ if ("pendingRun" in event.agent && !event.agent.pendingRun) {
803
+ finishErr(new Error(`Agent ${agentId} run finished before starting`));
804
+ return;
805
+ }
806
+ return;
807
+ }
808
+ }, { agentId, replayState: true });
809
+ });
810
+ }
811
+ async respondToPermission(agentId, requestId, response) {
812
+ const agent = this.requireAgent(agentId);
813
+ await agent.session.respondToPermission(requestId, response);
814
+ agent.pendingPermissions.delete(requestId);
815
+ // Update currentModeId - the session may have changed mode internally
816
+ // (e.g., plan approval changes mode from "plan" to "acceptEdits")
817
+ try {
818
+ agent.currentModeId = await agent.session.getCurrentMode();
819
+ }
820
+ catch {
821
+ // Ignore errors from getCurrentMode - mode tracking is best effort
822
+ }
823
+ this.emitState(agent);
824
+ }
825
+ async cancelAgentRun(agentId) {
826
+ const agent = this.requireAgent(agentId);
827
+ const pendingRun = agent.pendingRun;
828
+ const hasForegroundPendingRun = Boolean(pendingRun) && typeof pendingRun?.return === "function";
829
+ const isAutonomousRunning = agent.lifecycle === "running" && !hasForegroundPendingRun;
830
+ if (!hasForegroundPendingRun && !isAutonomousRunning) {
831
+ return false;
832
+ }
833
+ try {
834
+ await agent.session.interrupt();
835
+ }
836
+ catch (error) {
837
+ this.logger.error({ err: error, agentId }, "Failed to interrupt session");
838
+ }
839
+ if (hasForegroundPendingRun && pendingRun) {
840
+ try {
841
+ // Await the generator's .return() to ensure the finally block runs
842
+ // and pendingRun is properly cleared before we return.
843
+ await pendingRun.return(undefined);
844
+ }
845
+ catch (error) {
846
+ this.logger.error({ err: error, agentId }, "Failed to cancel run");
847
+ throw error;
848
+ }
849
+ }
850
+ // Clear any pending permissions that weren't cleaned up by handleStreamEvent.
851
+ // Due to microtask ordering, .return() may force the generator to its finally
852
+ // block before it consumes the turn_canceled event, skipping our cleanup code.
853
+ if (agent.pendingPermissions.size > 0) {
854
+ for (const [requestId] of agent.pendingPermissions) {
855
+ this.dispatchStream(agent.id, {
856
+ type: "permission_resolved",
857
+ provider: agent.provider,
858
+ requestId,
859
+ resolution: { behavior: "deny", message: "Interrupted" },
860
+ });
861
+ }
862
+ agent.pendingPermissions.clear();
863
+ this.emitState(agent);
864
+ }
865
+ return true;
866
+ }
867
+ getPendingPermissions(agentId) {
868
+ const agent = this.requireAgent(agentId);
869
+ return Array.from(agent.pendingPermissions.values());
870
+ }
871
+ peekPendingPermission(agent) {
872
+ const iterator = agent.pendingPermissions.values().next();
873
+ return iterator.done ? null : iterator.value;
874
+ }
875
+ async hydrateTimelineFromProvider(agentId) {
876
+ const agent = this.requireAgent(agentId);
877
+ await this.hydrateTimeline(agent);
878
+ }
879
+ getLastAssistantMessage(agentId) {
880
+ const agent = this.agents.get(agentId);
881
+ if (!agent) {
882
+ return null;
883
+ }
884
+ return this.getLastAssistantMessageFromTimeline(agent.timeline);
885
+ }
886
+ getLastAssistantMessageFromTimeline(timeline) {
887
+ // Collect the last contiguous assistant messages (Claude streams chunks)
888
+ const chunks = [];
889
+ for (let i = timeline.length - 1; i >= 0; i--) {
890
+ const item = timeline[i];
891
+ if (item.type !== "assistant_message") {
892
+ if (chunks.length) {
893
+ break;
894
+ }
895
+ continue;
896
+ }
897
+ chunks.push(item.text);
898
+ }
899
+ if (!chunks.length) {
900
+ return null;
901
+ }
902
+ return chunks.reverse().join("");
903
+ }
904
+ async waitForAgentEvent(agentId, options) {
905
+ const snapshot = this.getAgent(agentId);
906
+ if (!snapshot) {
907
+ throw new Error(`Agent ${agentId} not found`);
908
+ }
909
+ const hasPendingRun = "pendingRun" in snapshot && Boolean(snapshot.pendingRun);
910
+ const immediatePermission = this.peekPendingPermission(snapshot);
911
+ if (immediatePermission) {
912
+ return {
913
+ status: snapshot.lifecycle,
914
+ permission: immediatePermission,
915
+ lastMessage: this.getLastAssistantMessage(agentId)
916
+ };
917
+ }
918
+ const initialStatus = snapshot.lifecycle;
919
+ const initialBusy = isAgentBusy(initialStatus) || hasPendingRun;
920
+ const waitForActive = options?.waitForActive ?? false;
921
+ if (!waitForActive && !initialBusy) {
922
+ return {
923
+ status: initialStatus,
924
+ permission: null,
925
+ lastMessage: this.getLastAssistantMessage(agentId)
926
+ };
927
+ }
928
+ if (waitForActive && !initialBusy && !hasPendingRun) {
929
+ return {
930
+ status: initialStatus,
931
+ permission: null,
932
+ lastMessage: this.getLastAssistantMessage(agentId)
933
+ };
934
+ }
935
+ if (options?.signal?.aborted) {
936
+ throw createAbortError(options.signal, "wait_for_agent aborted");
937
+ }
938
+ return await new Promise((resolve, reject) => {
939
+ // Bug #1 Fix: Check abort signal AGAIN inside Promise constructor
940
+ // to avoid race condition between pre-Promise check and abort listener registration
941
+ if (options?.signal?.aborted) {
942
+ reject(createAbortError(options.signal, "wait_for_agent aborted"));
943
+ return;
944
+ }
945
+ let currentStatus = initialStatus;
946
+ let hasStarted = initialBusy || hasPendingRun;
947
+ let terminalStatusOverride = null;
948
+ // Bug #3 Fix: Declare unsubscribe and abortHandler upfront so cleanup can reference them
949
+ let unsubscribe = null;
950
+ let abortHandler = null;
951
+ const cleanup = () => {
952
+ // Clean up subscription
953
+ if (unsubscribe) {
954
+ try {
955
+ unsubscribe();
956
+ }
957
+ catch {
958
+ // ignore cleanup errors
959
+ }
960
+ unsubscribe = null;
961
+ }
962
+ // Clean up abort listener
963
+ if (abortHandler && options?.signal) {
964
+ try {
965
+ options.signal.removeEventListener("abort", abortHandler);
966
+ }
967
+ catch {
968
+ // ignore cleanup errors
969
+ }
970
+ abortHandler = null;
971
+ }
972
+ };
973
+ const finish = (permission) => {
974
+ cleanup();
975
+ resolve({
976
+ status: currentStatus,
977
+ permission,
978
+ lastMessage: this.getLastAssistantMessage(agentId)
979
+ });
980
+ };
981
+ // Bug #3 Fix: Set up abort handler BEFORE subscription
982
+ // to ensure cleanup handlers exist before callback can fire
983
+ if (options?.signal) {
984
+ abortHandler = () => {
985
+ cleanup();
986
+ reject(createAbortError(options.signal, "wait_for_agent aborted"));
987
+ };
988
+ options.signal.addEventListener("abort", abortHandler, { once: true });
989
+ }
990
+ // Bug #3 Fix: Now subscribe with cleanup handlers already in place
991
+ // This prevents race condition if callback fires synchronously with replayState: true
992
+ unsubscribe = this.subscribe((event) => {
993
+ if (event.type === "agent_state") {
994
+ currentStatus = event.agent.lifecycle;
995
+ const pending = this.peekPendingPermission(event.agent);
996
+ if (pending) {
997
+ finish(pending);
998
+ return;
999
+ }
1000
+ if (isAgentBusy(event.agent.lifecycle)) {
1001
+ hasStarted = true;
1002
+ return;
1003
+ }
1004
+ if (!waitForActive || hasStarted) {
1005
+ if (terminalStatusOverride) {
1006
+ currentStatus = terminalStatusOverride;
1007
+ }
1008
+ finish(null);
1009
+ }
1010
+ return;
1011
+ }
1012
+ if (event.type === "agent_stream") {
1013
+ if (event.event.type === "permission_requested") {
1014
+ finish(event.event.request);
1015
+ return;
1016
+ }
1017
+ if (event.event.type === "turn_failed") {
1018
+ hasStarted = true;
1019
+ terminalStatusOverride = "error";
1020
+ return;
1021
+ }
1022
+ if (event.event.type === "turn_completed") {
1023
+ hasStarted = true;
1024
+ }
1025
+ if (event.event.type === "turn_canceled") {
1026
+ hasStarted = true;
1027
+ }
1028
+ }
1029
+ }, { agentId, replayState: true });
1030
+ });
1031
+ }
1032
+ async registerSession(session, config, agentId, options) {
1033
+ const resolvedAgentId = validateAgentId(agentId, "registerSession");
1034
+ if (this.agents.has(resolvedAgentId)) {
1035
+ throw new Error(`Agent with id ${resolvedAgentId} already exists`);
1036
+ }
1037
+ const initialPersistedTitle = await this.resolveInitialPersistedTitle(resolvedAgentId, config);
1038
+ const now = new Date();
1039
+ const initialTimeline = options?.timeline ? [...options.timeline] : [];
1040
+ const initialTimelineRows = options?.timelineRows?.length
1041
+ ? options.timelineRows.map((row) => ({ ...row }))
1042
+ : this.buildTimelineRowsFromItems(initialTimeline, options?.timelineNextSeq ?? 1, (options?.updatedAt ?? options?.createdAt ?? now).toISOString());
1043
+ const derivedNextSeq = options?.timelineNextSeq ??
1044
+ (initialTimelineRows.length
1045
+ ? initialTimelineRows[initialTimelineRows.length - 1].seq + 1
1046
+ : 1);
1047
+ const managed = {
1048
+ id: resolvedAgentId,
1049
+ provider: config.provider,
1050
+ cwd: config.cwd,
1051
+ session,
1052
+ capabilities: session.capabilities,
1053
+ config,
1054
+ runtimeInfo: undefined,
1055
+ lifecycle: "initializing",
1056
+ createdAt: options?.createdAt ?? now,
1057
+ updatedAt: options?.updatedAt ?? now,
1058
+ availableModes: [],
1059
+ currentModeId: null,
1060
+ pendingPermissions: new Map(),
1061
+ pendingRun: null,
1062
+ timeline: initialTimeline,
1063
+ timelineRows: initialTimelineRows,
1064
+ timelineEpoch: options?.timelineEpoch ?? randomUUID(),
1065
+ timelineNextSeq: derivedNextSeq,
1066
+ persistence: attachPersistenceCwd(session.describePersistence(), config.cwd),
1067
+ historyPrimed: options?.historyPrimed ?? false,
1068
+ lastUserMessageAt: options?.lastUserMessageAt ?? null,
1069
+ lastUsage: options?.lastUsage,
1070
+ lastError: options?.lastError,
1071
+ attention: options?.attention != null
1072
+ ? options.attention.requiresAttention
1073
+ ? {
1074
+ requiresAttention: true,
1075
+ attentionReason: options.attention.attentionReason,
1076
+ attentionTimestamp: new Date(options.attention.attentionTimestamp),
1077
+ }
1078
+ : { requiresAttention: false }
1079
+ : { requiresAttention: false },
1080
+ internal: config.internal ?? false,
1081
+ labels: options?.labels ?? {},
1082
+ };
1083
+ this.agents.set(resolvedAgentId, managed);
1084
+ // Initialize previousStatus to track transitions
1085
+ this.previousStatuses.set(resolvedAgentId, managed.lifecycle);
1086
+ await this.refreshRuntimeInfo(managed);
1087
+ await this.persistSnapshot(managed, {
1088
+ title: initialPersistedTitle,
1089
+ });
1090
+ this.emitState(managed);
1091
+ await this.refreshSessionState(managed);
1092
+ managed.lifecycle = "idle";
1093
+ await this.persistSnapshot(managed);
1094
+ this.emitState(managed);
1095
+ this.startLiveEventPump(managed);
1096
+ return { ...managed };
1097
+ }
1098
+ async resolveInitialPersistedTitle(agentId, config) {
1099
+ const existing = await this.registry?.get(agentId);
1100
+ if (existing) {
1101
+ return existing.title ?? null;
1102
+ }
1103
+ if (Object.prototype.hasOwnProperty.call(config, "title")) {
1104
+ return config.title ?? null;
1105
+ }
1106
+ return null;
1107
+ }
1108
+ buildTimelineRowsFromItems(items, startSeq, timestamp) {
1109
+ let nextSeq = startSeq;
1110
+ return items.map((item) => {
1111
+ const row = {
1112
+ seq: nextSeq,
1113
+ timestamp,
1114
+ item,
1115
+ };
1116
+ nextSeq += 1;
1117
+ return row;
1118
+ });
1119
+ }
1120
+ ensureTimelineState(agent) {
1121
+ const minSeq = agent.timelineRows.length ? agent.timelineRows[0].seq : 0;
1122
+ const maxSeq = agent.timelineRows.length
1123
+ ? agent.timelineRows[agent.timelineRows.length - 1].seq
1124
+ : 0;
1125
+ return {
1126
+ rows: agent.timelineRows,
1127
+ epoch: agent.timelineEpoch,
1128
+ nextSeq: agent.timelineNextSeq,
1129
+ minSeq,
1130
+ maxSeq,
1131
+ };
1132
+ }
1133
+ async persistSnapshot(agent, options) {
1134
+ if (!this.registry) {
1135
+ return;
1136
+ }
1137
+ // Don't persist internal agents - they're ephemeral system tasks
1138
+ if (agent.internal) {
1139
+ return;
1140
+ }
1141
+ await this.registry.applySnapshot(agent, options);
1142
+ }
1143
+ async refreshSessionState(agent) {
1144
+ try {
1145
+ const modes = await agent.session.getAvailableModes();
1146
+ agent.availableModes = modes;
1147
+ }
1148
+ catch {
1149
+ agent.availableModes = [];
1150
+ }
1151
+ try {
1152
+ agent.currentModeId = await agent.session.getCurrentMode();
1153
+ }
1154
+ catch {
1155
+ agent.currentModeId = null;
1156
+ }
1157
+ try {
1158
+ const pending = agent.session.getPendingPermissions();
1159
+ agent.pendingPermissions = new Map(pending.map((request) => [request.id, request]));
1160
+ }
1161
+ catch {
1162
+ agent.pendingPermissions.clear();
1163
+ }
1164
+ await this.refreshRuntimeInfo(agent);
1165
+ }
1166
+ async refreshRuntimeInfo(agent) {
1167
+ try {
1168
+ const newInfo = await agent.session.getRuntimeInfo();
1169
+ const changed = newInfo.model !== agent.runtimeInfo?.model ||
1170
+ newInfo.thinkingOptionId !== agent.runtimeInfo?.thinkingOptionId ||
1171
+ newInfo.sessionId !== agent.runtimeInfo?.sessionId ||
1172
+ newInfo.modeId !== agent.runtimeInfo?.modeId;
1173
+ agent.runtimeInfo = newInfo;
1174
+ if (!agent.persistence && newInfo.sessionId) {
1175
+ agent.persistence = attachPersistenceCwd({ provider: agent.provider, sessionId: newInfo.sessionId }, agent.cwd);
1176
+ }
1177
+ // Emit state if runtimeInfo changed so clients get the updated model
1178
+ if (changed) {
1179
+ this.emitState(agent);
1180
+ }
1181
+ }
1182
+ catch {
1183
+ // Keep existing runtimeInfo if refresh fails.
1184
+ }
1185
+ }
1186
+ async hydrateTimeline(agent) {
1187
+ if (agent.historyPrimed) {
1188
+ return;
1189
+ }
1190
+ agent.historyPrimed = true;
1191
+ const canonicalUserMessagesById = new Map(agent.timelineRows.flatMap((row) => {
1192
+ if (row.item.type !== "user_message") {
1193
+ return [];
1194
+ }
1195
+ const messageId = normalizeMessageId(row.item.messageId);
1196
+ if (!messageId) {
1197
+ return [];
1198
+ }
1199
+ return [[messageId, row.item.text]];
1200
+ }));
1201
+ try {
1202
+ for await (const event of agent.session.streamHistory()) {
1203
+ this.handleStreamEvent(agent, event, {
1204
+ fromHistory: true,
1205
+ canonicalUserMessagesById: canonicalUserMessagesById.size > 0 ? canonicalUserMessagesById : undefined,
1206
+ });
1207
+ }
1208
+ }
1209
+ catch {
1210
+ // ignore history failures
1211
+ }
1212
+ }
1213
+ handleStreamEvent(agent, event, options) {
1214
+ // Only update timestamp for live events, not history replay
1215
+ if (!options?.fromHistory) {
1216
+ this.touchUpdatedAt(agent);
1217
+ }
1218
+ let timelineRow = null;
1219
+ switch (event.type) {
1220
+ case "thread_started":
1221
+ // Update persistence with the new session ID from the provider.
1222
+ // persistence.sessionId is the single source of truth for session identity.
1223
+ {
1224
+ const previousSessionId = agent.persistence?.sessionId ?? null;
1225
+ const handle = agent.session.describePersistence();
1226
+ if (handle) {
1227
+ agent.persistence = attachPersistenceCwd(handle, agent.cwd);
1228
+ if (agent.persistence?.sessionId !== previousSessionId) {
1229
+ this.emitState(agent);
1230
+ }
1231
+ }
1232
+ }
1233
+ break;
1234
+ case "timeline":
1235
+ // Skip provider-replayed user_message items during history hydration.
1236
+ // These are already canonically recorded by recordUserMessage() and replaying them would
1237
+ // create duplicates. Match by messageId (not text) to avoid dropping legitimate
1238
+ // provider-origin messages that happen to reuse the same text.
1239
+ if (options?.fromHistory &&
1240
+ event.item.type === "user_message") {
1241
+ const eventMessageId = normalizeMessageId(event.item.messageId);
1242
+ if (eventMessageId) {
1243
+ const canonicalText = options?.canonicalUserMessagesById?.get(eventMessageId);
1244
+ if (canonicalText === event.item.text) {
1245
+ break;
1246
+ }
1247
+ }
1248
+ }
1249
+ if (this.shouldSuppressLiveUserMessageEcho(agent, event, options)) {
1250
+ break;
1251
+ }
1252
+ timelineRow = this.recordTimeline(agent, event.item);
1253
+ if (!options?.fromHistory &&
1254
+ event.item.type === "user_message") {
1255
+ agent.lastUserMessageAt = new Date();
1256
+ this.emitState(agent);
1257
+ }
1258
+ break;
1259
+ case "turn_completed":
1260
+ this.logger.trace({
1261
+ agentId: agent.id,
1262
+ lifecycle: agent.lifecycle,
1263
+ hasPendingRun: Boolean(agent.pendingRun),
1264
+ }, "handleStreamEvent: turn_completed");
1265
+ agent.lastUsage = event.usage;
1266
+ agent.lastError = undefined;
1267
+ if (!agent.pendingRun && agent.lifecycle !== "idle") {
1268
+ agent.lifecycle = "idle";
1269
+ this.emitState(agent);
1270
+ }
1271
+ void this.refreshRuntimeInfo(agent);
1272
+ break;
1273
+ case "turn_failed":
1274
+ agent.lifecycle = "error";
1275
+ agent.lastError = event.error;
1276
+ this.appendSystemErrorTimelineMessage(agent, event.provider, this.formatTurnFailedMessage(event), options);
1277
+ for (const [requestId] of agent.pendingPermissions) {
1278
+ agent.pendingPermissions.delete(requestId);
1279
+ if (!options?.fromHistory) {
1280
+ this.dispatchStream(agent.id, {
1281
+ type: "permission_resolved",
1282
+ provider: event.provider,
1283
+ requestId,
1284
+ resolution: { behavior: "deny", message: "Turn failed" },
1285
+ });
1286
+ }
1287
+ }
1288
+ this.emitState(agent);
1289
+ break;
1290
+ case "turn_canceled":
1291
+ this.logger.trace({
1292
+ agentId: agent.id,
1293
+ lifecycle: agent.lifecycle,
1294
+ hasPendingRun: Boolean(agent.pendingRun),
1295
+ }, "handleStreamEvent: turn_canceled");
1296
+ if (!agent.pendingRun) {
1297
+ agent.lifecycle = "idle";
1298
+ }
1299
+ agent.lastError = undefined;
1300
+ for (const [requestId] of agent.pendingPermissions) {
1301
+ agent.pendingPermissions.delete(requestId);
1302
+ if (!options?.fromHistory) {
1303
+ this.dispatchStream(agent.id, {
1304
+ type: "permission_resolved",
1305
+ provider: event.provider,
1306
+ requestId,
1307
+ resolution: { behavior: "deny", message: "Interrupted" },
1308
+ });
1309
+ }
1310
+ }
1311
+ this.emitState(agent);
1312
+ break;
1313
+ case "turn_started":
1314
+ this.logger.trace({
1315
+ agentId: agent.id,
1316
+ lifecycle: agent.lifecycle,
1317
+ hasPendingRun: Boolean(agent.pendingRun),
1318
+ }, "handleStreamEvent: turn_started");
1319
+ if (!agent.pendingRun) {
1320
+ agent.lifecycle = "running";
1321
+ this.emitState(agent);
1322
+ }
1323
+ break;
1324
+ case "permission_requested":
1325
+ {
1326
+ const hadPendingPermissions = agent.pendingPermissions.size > 0;
1327
+ agent.pendingPermissions.set(event.request.id, event.request);
1328
+ if (!hadPendingPermissions && !agent.internal) {
1329
+ this.broadcastAgentAttention(agent, "permission");
1330
+ }
1331
+ }
1332
+ this.emitState(agent);
1333
+ break;
1334
+ case "permission_resolved":
1335
+ agent.pendingPermissions.delete(event.requestId);
1336
+ this.emitState(agent);
1337
+ break;
1338
+ default:
1339
+ break;
1340
+ }
1341
+ // Skip dispatching individual stream events during history replay.
1342
+ if (!options?.fromHistory) {
1343
+ this.dispatchStream(agent.id, event, timelineRow
1344
+ ? {
1345
+ seq: timelineRow.seq,
1346
+ epoch: this.ensureTimelineState(agent).epoch,
1347
+ }
1348
+ : undefined);
1349
+ }
1350
+ }
1351
+ appendSystemErrorTimelineMessage(agent, provider, message, options) {
1352
+ if (options?.fromHistory) {
1353
+ return;
1354
+ }
1355
+ const normalized = message.trim();
1356
+ if (!normalized) {
1357
+ return;
1358
+ }
1359
+ const text = `${SYSTEM_ERROR_PREFIX} ${normalized}`;
1360
+ const lastItem = agent.timelineRows[agent.timelineRows.length - 1]?.item;
1361
+ if (lastItem?.type === "assistant_message" && lastItem.text === text) {
1362
+ return;
1363
+ }
1364
+ const item = { type: "assistant_message", text };
1365
+ const row = this.recordTimeline(agent, item);
1366
+ this.dispatchStream(agent.id, {
1367
+ type: "timeline",
1368
+ item,
1369
+ provider,
1370
+ }, {
1371
+ seq: row.seq,
1372
+ epoch: this.ensureTimelineState(agent).epoch,
1373
+ });
1374
+ }
1375
+ formatTurnFailedMessage(event) {
1376
+ const base = event.error.trim();
1377
+ const parts = [base.length > 0 ? base : "Provider run failed"];
1378
+ const code = event.code?.trim();
1379
+ if (code) {
1380
+ parts.push(`code: ${code}`);
1381
+ }
1382
+ const diagnostic = event.diagnostic?.trim();
1383
+ if (diagnostic && diagnostic !== base) {
1384
+ parts.push(diagnostic);
1385
+ }
1386
+ return parts.join("\n\n");
1387
+ }
1388
+ shouldSuppressLiveUserMessageEcho(agent, event, options) {
1389
+ if (options?.fromHistory || event.type !== "timeline") {
1390
+ return false;
1391
+ }
1392
+ if (event.item.type !== "user_message" || !agent.pendingRun) {
1393
+ return false;
1394
+ }
1395
+ const eventMessageId = normalizeMessageId(event.item.messageId);
1396
+ const eventText = event.item.text;
1397
+ if (!eventMessageId) {
1398
+ return false;
1399
+ }
1400
+ return agent.timelineRows.some((row) => {
1401
+ const rowItem = row.item;
1402
+ if (rowItem.type !== "user_message") {
1403
+ return false;
1404
+ }
1405
+ const rowMessageId = normalizeMessageId(rowItem.messageId);
1406
+ return rowMessageId === eventMessageId && rowItem.text === eventText;
1407
+ });
1408
+ }
1409
+ recordTimeline(agent, item) {
1410
+ const timelineState = this.ensureTimelineState(agent);
1411
+ const row = {
1412
+ seq: timelineState.nextSeq,
1413
+ timestamp: new Date().toISOString(),
1414
+ item,
1415
+ };
1416
+ agent.timelineNextSeq = timelineState.nextSeq + 1;
1417
+ agent.timeline.push(item);
1418
+ timelineState.rows.push(row);
1419
+ if (typeof this.maxTimelineItems === "number" &&
1420
+ agent.timeline.length > this.maxTimelineItems) {
1421
+ const removeCount = agent.timeline.length - this.maxTimelineItems;
1422
+ agent.timeline.splice(0, removeCount);
1423
+ timelineState.rows.splice(0, removeCount);
1424
+ }
1425
+ return row;
1426
+ }
1427
+ emitState(agent) {
1428
+ // Keep attention as an edge-triggered unread signal, not a level signal.
1429
+ this.checkAndSetAttention(agent);
1430
+ this.dispatch({
1431
+ type: "agent_state",
1432
+ agent: { ...agent },
1433
+ });
1434
+ }
1435
+ checkAndSetAttention(agent) {
1436
+ const previousStatus = this.previousStatuses.get(agent.id);
1437
+ const currentStatus = agent.lifecycle;
1438
+ // Track the new status
1439
+ this.previousStatuses.set(agent.id, currentStatus);
1440
+ // Skip attention tracking for internal agents
1441
+ if (agent.internal) {
1442
+ return;
1443
+ }
1444
+ // Skip if already requires attention
1445
+ if (agent.attention.requiresAttention) {
1446
+ return;
1447
+ }
1448
+ // Check if agent transitioned from running to idle (finished)
1449
+ if (previousStatus === "running" && currentStatus === "idle") {
1450
+ agent.attention = {
1451
+ requiresAttention: true,
1452
+ attentionReason: "finished",
1453
+ attentionTimestamp: new Date(),
1454
+ };
1455
+ this.broadcastAgentAttention(agent, "finished");
1456
+ this.enqueueBackgroundPersist(agent);
1457
+ return;
1458
+ }
1459
+ // Check if agent entered error state
1460
+ if (previousStatus !== "error" && currentStatus === "error") {
1461
+ agent.attention = {
1462
+ requiresAttention: true,
1463
+ attentionReason: "error",
1464
+ attentionTimestamp: new Date(),
1465
+ };
1466
+ this.broadcastAgentAttention(agent, "error");
1467
+ this.enqueueBackgroundPersist(agent);
1468
+ return;
1469
+ }
1470
+ }
1471
+ enqueueBackgroundPersist(agent) {
1472
+ const task = this.persistSnapshot(agent).catch((err) => {
1473
+ this.logger.error({ err, agentId: agent.id }, "Failed to persist agent snapshot");
1474
+ });
1475
+ this.trackBackgroundTask(task);
1476
+ }
1477
+ trackBackgroundTask(task) {
1478
+ this.backgroundTasks.add(task);
1479
+ void task.finally(() => {
1480
+ this.backgroundTasks.delete(task);
1481
+ });
1482
+ }
1483
+ /**
1484
+ * Flush any background persistence work (best-effort).
1485
+ * Used by daemon shutdown paths to avoid unhandled rejections after cleanup.
1486
+ */
1487
+ async flush() {
1488
+ // Drain tasks, including tasks spawned while awaiting.
1489
+ while (this.backgroundTasks.size > 0) {
1490
+ const pending = Array.from(this.backgroundTasks);
1491
+ await Promise.allSettled(pending);
1492
+ }
1493
+ }
1494
+ broadcastAgentAttention(agent, reason) {
1495
+ this.onAgentAttention?.({
1496
+ agentId: agent.id,
1497
+ provider: agent.provider,
1498
+ reason,
1499
+ });
1500
+ }
1501
+ dispatchStream(agentId, event, metadata) {
1502
+ this.dispatch({ type: "agent_stream", agentId, event, ...metadata });
1503
+ }
1504
+ dispatch(event) {
1505
+ for (const subscriber of this.subscribers) {
1506
+ if (subscriber.agentId &&
1507
+ event.type === "agent_stream" &&
1508
+ subscriber.agentId !== event.agentId) {
1509
+ continue;
1510
+ }
1511
+ if (subscriber.agentId &&
1512
+ event.type === "agent_state" &&
1513
+ subscriber.agentId !== event.agent.id) {
1514
+ continue;
1515
+ }
1516
+ // Skip internal agents for global subscribers (those without a specific agentId)
1517
+ if (!subscriber.agentId) {
1518
+ if (event.type === "agent_state" && event.agent.internal) {
1519
+ continue;
1520
+ }
1521
+ if (event.type === "agent_stream") {
1522
+ const agent = this.agents.get(event.agentId);
1523
+ if (agent?.internal) {
1524
+ continue;
1525
+ }
1526
+ }
1527
+ }
1528
+ subscriber.callback(event);
1529
+ }
1530
+ }
1531
+ async normalizeConfig(config, _options) {
1532
+ const normalized = { ...config };
1533
+ // Always resolve cwd to absolute path for consistent history file lookup
1534
+ if (normalized.cwd) {
1535
+ normalized.cwd = resolve(normalized.cwd);
1536
+ try {
1537
+ const cwdStats = await stat(normalized.cwd);
1538
+ if (!cwdStats.isDirectory()) {
1539
+ throw new Error(`Working directory is not a directory: ${normalized.cwd}`);
1540
+ }
1541
+ }
1542
+ catch (error) {
1543
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1544
+ throw new Error(`Working directory does not exist: ${normalized.cwd}`);
1545
+ }
1546
+ if (error instanceof Error) {
1547
+ throw error;
1548
+ }
1549
+ throw new Error(`Failed to access working directory: ${normalized.cwd}`);
1550
+ }
1551
+ }
1552
+ if (typeof normalized.model === "string") {
1553
+ const trimmed = normalized.model.trim();
1554
+ const normalizedId = trimmed.toLowerCase();
1555
+ normalized.model =
1556
+ trimmed.length > 0 && normalizedId !== "default" ? trimmed : undefined;
1557
+ }
1558
+ return normalized;
1559
+ }
1560
+ requireClient(provider) {
1561
+ const client = this.clients.get(provider);
1562
+ if (!client) {
1563
+ throw new Error(`No client registered for provider '${provider}'`);
1564
+ }
1565
+ return client;
1566
+ }
1567
+ requireAgent(id) {
1568
+ const normalizedId = validateAgentId(id, "requireAgent");
1569
+ const agent = this.agents.get(normalizedId);
1570
+ if (!agent) {
1571
+ throw new Error(`Unknown agent '${normalizedId}'`);
1572
+ }
1573
+ return agent;
1574
+ }
1575
+ startLiveEventPump(agent) {
1576
+ if (!supportsLiveEventStream(agent.session)) {
1577
+ return;
1578
+ }
1579
+ if (this.liveEventPumps.has(agent.id)) {
1580
+ return;
1581
+ }
1582
+ const pump = (async () => {
1583
+ while (true) {
1584
+ const current = this.agents.get(agent.id);
1585
+ if (!current) {
1586
+ return;
1587
+ }
1588
+ if (!supportsLiveEventStream(current.session)) {
1589
+ return;
1590
+ }
1591
+ try {
1592
+ for await (const event of current.session.streamLiveEvents()) {
1593
+ const latest = this.agents.get(agent.id);
1594
+ if (!latest) {
1595
+ return;
1596
+ }
1597
+ // Keep consuming provider events even during an active foreground run,
1598
+ // then replay them immediately once that run settles.
1599
+ if (latest.pendingRun) {
1600
+ this.logger.trace({
1601
+ agentId: latest.id,
1602
+ eventType: event.type,
1603
+ backlogSize: (this.liveEventBacklog.get(latest.id)?.length ?? 0) + 1,
1604
+ }, "Live event pump: queued event because pendingRun is active");
1605
+ this.enqueueLiveEvent(latest.id, event);
1606
+ continue;
1607
+ }
1608
+ this.flushLiveEventBacklog(latest);
1609
+ this.handleStreamEvent(latest, event);
1610
+ }
1611
+ this.logger.warn({ agentId: agent.id }, "Live event pump stream ended; restarting");
1612
+ }
1613
+ catch (error) {
1614
+ this.logger.warn({ err: error, agentId: agent.id }, "Live event pump failed");
1615
+ }
1616
+ // Keep pump alive unless the agent is gone.
1617
+ await new Promise((resolve) => setTimeout(resolve, 250));
1618
+ const latest = this.agents.get(agent.id);
1619
+ if (!latest) {
1620
+ return;
1621
+ }
1622
+ if (!latest.pendingRun) {
1623
+ this.flushLiveEventBacklog(latest);
1624
+ }
1625
+ }
1626
+ })();
1627
+ this.liveEventPumps.set(agent.id, pump);
1628
+ pump.finally(() => {
1629
+ const current = this.liveEventPumps.get(agent.id);
1630
+ if (current === pump) {
1631
+ this.liveEventPumps.delete(agent.id);
1632
+ }
1633
+ });
1634
+ }
1635
+ enqueueLiveEvent(agentId, event) {
1636
+ const existing = this.liveEventBacklog.get(agentId);
1637
+ if (existing) {
1638
+ existing.push(event);
1639
+ return;
1640
+ }
1641
+ this.liveEventBacklog.set(agentId, [event]);
1642
+ }
1643
+ clearLiveEventBacklogFlushTimer(agentId) {
1644
+ const timer = this.liveEventBacklogFlushTimers.get(agentId);
1645
+ if (!timer) {
1646
+ return;
1647
+ }
1648
+ clearTimeout(timer);
1649
+ this.liveEventBacklogFlushTimers.delete(agentId);
1650
+ }
1651
+ scheduleLiveEventBacklogFlush(agentId, delayMs) {
1652
+ if (this.liveEventBacklogFlushTimers.has(agentId)) {
1653
+ return;
1654
+ }
1655
+ const timer = setTimeout(() => {
1656
+ this.liveEventBacklogFlushTimers.delete(agentId);
1657
+ const latest = this.agents.get(agentId);
1658
+ if (!latest) {
1659
+ return;
1660
+ }
1661
+ if (latest.pendingRun) {
1662
+ this.scheduleLiveEventBacklogFlush(agentId, LIVE_BACKLOG_TERMINAL_REPLAY_DELAY_MS);
1663
+ return;
1664
+ }
1665
+ this.flushLiveEventBacklog(latest);
1666
+ }, delayMs);
1667
+ this.liveEventBacklogFlushTimers.set(agentId, timer);
1668
+ }
1669
+ flushLiveEventBacklog(agent) {
1670
+ if (agent.pendingRun) {
1671
+ return;
1672
+ }
1673
+ const pending = this.liveEventBacklog.get(agent.id);
1674
+ if (!pending || pending.length === 0) {
1675
+ return;
1676
+ }
1677
+ this.clearLiveEventBacklogFlushTimer(agent.id);
1678
+ this.liveEventBacklog.delete(agent.id);
1679
+ const immediate = [];
1680
+ const deferred = [];
1681
+ let sawTurnStarted = false;
1682
+ let deferRemainder = false;
1683
+ for (const event of pending) {
1684
+ if (!deferRemainder && sawTurnStarted && isTurnTerminalEvent(event)) {
1685
+ deferRemainder = true;
1686
+ }
1687
+ if (deferRemainder) {
1688
+ deferred.push(event);
1689
+ continue;
1690
+ }
1691
+ immediate.push(event);
1692
+ if (event.type === "turn_started") {
1693
+ sawTurnStarted = true;
1694
+ }
1695
+ }
1696
+ for (const event of immediate) {
1697
+ this.handleStreamEvent(agent, event);
1698
+ }
1699
+ if (deferred.length === 0) {
1700
+ return;
1701
+ }
1702
+ const existing = this.liveEventBacklog.get(agent.id);
1703
+ if (existing && existing.length > 0) {
1704
+ this.liveEventBacklog.set(agent.id, [...deferred, ...existing]);
1705
+ }
1706
+ else {
1707
+ this.liveEventBacklog.set(agent.id, deferred);
1708
+ }
1709
+ this.scheduleLiveEventBacklogFlush(agent.id, LIVE_BACKLOG_TERMINAL_REPLAY_DELAY_MS);
1710
+ }
1711
+ }
1712
+ //# sourceMappingURL=agent-manager.js.map