@junctionpanel/server 0.1.16

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 (400) hide show
  1. package/.env.example +10 -0
  2. package/LICENSE +671 -0
  3. package/README.md +118 -0
  4. package/agent-prompt.md +339 -0
  5. package/dist/scripts/daemon-runner.js +141 -0
  6. package/dist/scripts/daemon-runner.js.map +1 -0
  7. package/dist/scripts/dev-runner.js +17 -0
  8. package/dist/scripts/dev-runner.js.map +1 -0
  9. package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
  10. package/dist/scripts/supervisor.js +122 -0
  11. package/dist/scripts/supervisor.js.map +1 -0
  12. package/dist/server/client/daemon-client-relay-e2ee-transport.d.ts +8 -0
  13. package/dist/server/client/daemon-client-relay-e2ee-transport.d.ts.map +1 -0
  14. package/dist/server/client/daemon-client-relay-e2ee-transport.js +161 -0
  15. package/dist/server/client/daemon-client-relay-e2ee-transport.js.map +1 -0
  16. package/dist/server/client/daemon-client-terminal-stream-manager.d.ts +43 -0
  17. package/dist/server/client/daemon-client-terminal-stream-manager.d.ts.map +1 -0
  18. package/dist/server/client/daemon-client-terminal-stream-manager.js +134 -0
  19. package/dist/server/client/daemon-client-terminal-stream-manager.js.map +1 -0
  20. package/dist/server/client/daemon-client-transport-types.d.ts +34 -0
  21. package/dist/server/client/daemon-client-transport-types.d.ts.map +1 -0
  22. package/dist/server/client/daemon-client-transport-types.js +2 -0
  23. package/dist/server/client/daemon-client-transport-types.js.map +1 -0
  24. package/dist/server/client/daemon-client-transport-utils.d.ts +9 -0
  25. package/dist/server/client/daemon-client-transport-utils.d.ts.map +1 -0
  26. package/dist/server/client/daemon-client-transport-utils.js +121 -0
  27. package/dist/server/client/daemon-client-transport-utils.js.map +1 -0
  28. package/dist/server/client/daemon-client-transport.d.ts +5 -0
  29. package/dist/server/client/daemon-client-transport.d.ts.map +1 -0
  30. package/dist/server/client/daemon-client-transport.js +4 -0
  31. package/dist/server/client/daemon-client-transport.js.map +1 -0
  32. package/dist/server/client/daemon-client-websocket-transport.d.ts +7 -0
  33. package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -0
  34. package/dist/server/client/daemon-client-websocket-transport.js +64 -0
  35. package/dist/server/client/daemon-client-websocket-transport.js.map +1 -0
  36. package/dist/server/client/daemon-client.d.ts +443 -0
  37. package/dist/server/client/daemon-client.d.ts.map +1 -0
  38. package/dist/server/client/daemon-client.js +2223 -0
  39. package/dist/server/client/daemon-client.js.map +1 -0
  40. package/dist/server/server/agent/activity-curator.d.ts +8 -0
  41. package/dist/server/server/agent/activity-curator.d.ts.map +1 -0
  42. package/dist/server/server/agent/activity-curator.js +228 -0
  43. package/dist/server/server/agent/activity-curator.js.map +1 -0
  44. package/dist/server/server/agent/agent-management-mcp.d.ts +36 -0
  45. package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -0
  46. package/dist/server/server/agent/agent-management-mcp.js +644 -0
  47. package/dist/server/server/agent/agent-management-mcp.js.map +1 -0
  48. package/dist/server/server/agent/agent-manager.d.ts +252 -0
  49. package/dist/server/server/agent/agent-manager.d.ts.map +1 -0
  50. package/dist/server/server/agent/agent-manager.js +1651 -0
  51. package/dist/server/server/agent/agent-manager.js.map +1 -0
  52. package/dist/server/server/agent/agent-metadata-generator.d.ts +29 -0
  53. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -0
  54. package/dist/server/server/agent/agent-metadata-generator.js +163 -0
  55. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -0
  56. package/dist/server/server/agent/agent-projections.d.ts +17 -0
  57. package/dist/server/server/agent/agent-projections.d.ts.map +1 -0
  58. package/dist/server/server/agent/agent-projections.js +270 -0
  59. package/dist/server/server/agent/agent-projections.js.map +1 -0
  60. package/dist/server/server/agent/agent-response-loop.d.ts +60 -0
  61. package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -0
  62. package/dist/server/server/agent/agent-response-loop.js +304 -0
  63. package/dist/server/server/agent/agent-response-loop.js.map +1 -0
  64. package/dist/server/server/agent/agent-sdk-types.d.ts +377 -0
  65. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -0
  66. package/dist/server/server/agent/agent-sdk-types.js +12 -0
  67. package/dist/server/server/agent/agent-sdk-types.js.map +1 -0
  68. package/dist/server/server/agent/agent-storage.d.ts +230 -0
  69. package/dist/server/server/agent/agent-storage.d.ts.map +1 -0
  70. package/dist/server/server/agent/agent-storage.js +346 -0
  71. package/dist/server/server/agent/agent-storage.js.map +1 -0
  72. package/dist/server/server/agent/agent-title-limits.d.ts +3 -0
  73. package/dist/server/server/agent/agent-title-limits.d.ts.map +1 -0
  74. package/dist/server/server/agent/agent-title-limits.js +3 -0
  75. package/dist/server/server/agent/agent-title-limits.js.map +1 -0
  76. package/dist/server/server/agent/mcp-server.d.ts +19 -0
  77. package/dist/server/server/agent/mcp-server.d.ts.map +1 -0
  78. package/dist/server/server/agent/mcp-server.js +742 -0
  79. package/dist/server/server/agent/mcp-server.js.map +1 -0
  80. package/dist/server/server/agent/model-resolver.d.ts +11 -0
  81. package/dist/server/server/agent/model-resolver.d.ts.map +1 -0
  82. package/dist/server/server/agent/model-resolver.js +21 -0
  83. package/dist/server/server/agent/model-resolver.js.map +1 -0
  84. package/dist/server/server/agent/orchestrator-instructions.d.ts +7 -0
  85. package/dist/server/server/agent/orchestrator-instructions.d.ts.map +1 -0
  86. package/dist/server/server/agent/orchestrator-instructions.js +51 -0
  87. package/dist/server/server/agent/orchestrator-instructions.js.map +1 -0
  88. package/dist/server/server/agent/orchestrator.d.ts +12 -0
  89. package/dist/server/server/agent/orchestrator.d.ts.map +1 -0
  90. package/dist/server/server/agent/orchestrator.js +12 -0
  91. package/dist/server/server/agent/orchestrator.js.map +1 -0
  92. package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
  93. package/dist/server/server/agent/pcm16-resampler.d.ts.map +1 -0
  94. package/dist/server/server/agent/pcm16-resampler.js +63 -0
  95. package/dist/server/server/agent/pcm16-resampler.js.map +1 -0
  96. package/dist/server/server/agent/provider-launch-config.d.ts +139 -0
  97. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -0
  98. package/dist/server/server/agent/provider-launch-config.js +83 -0
  99. package/dist/server/server/agent/provider-launch-config.js.map +1 -0
  100. package/dist/server/server/agent/provider-manifest.d.ts +15 -0
  101. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -0
  102. package/dist/server/server/agent/provider-manifest.js +83 -0
  103. package/dist/server/server/agent/provider-manifest.js.map +1 -0
  104. package/dist/server/server/agent/provider-registry.d.ts +18 -0
  105. package/dist/server/server/agent/provider-registry.d.ts.map +1 -0
  106. package/dist/server/server/agent/provider-registry.js +45 -0
  107. package/dist/server/server/agent/provider-registry.js.map +1 -0
  108. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +29 -0
  109. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -0
  110. package/dist/server/server/agent/providers/claude/model-catalog.js +64 -0
  111. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -0
  112. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +44 -0
  113. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -0
  114. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +250 -0
  115. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
  116. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts +3 -0
  117. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -0
  118. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +109 -0
  119. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
  120. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
  121. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -0
  122. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +238 -0
  123. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
  124. package/dist/server/server/agent/providers/claude-agent.d.ts +49 -0
  125. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -0
  126. package/dist/server/server/agent/providers/claude-agent.js +3701 -0
  127. package/dist/server/server/agent/providers/claude-agent.js.map +1 -0
  128. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
  129. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts.map +1 -0
  130. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  131. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
  132. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +15 -0
  133. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -0
  134. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +720 -0
  135. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
  136. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +34 -0
  137. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -0
  138. package/dist/server/server/agent/providers/codex-app-server-agent.js +2660 -0
  139. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -0
  140. package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts +9 -0
  141. package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts.map +1 -0
  142. package/dist/server/server/agent/providers/codex-rollout-timeline.js +487 -0
  143. package/dist/server/server/agent/providers/codex-rollout-timeline.js.map +1 -0
  144. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
  145. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts.map +1 -0
  146. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
  147. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
  148. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
  149. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +1 -0
  150. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +151 -0
  151. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
  152. package/dist/server/server/agent/providers/opencode-agent.d.ts +37 -0
  153. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -0
  154. package/dist/server/server/agent/providers/opencode-agent.js +874 -0
  155. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -0
  156. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +1460 -0
  157. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -0
  158. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +552 -0
  159. package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
  160. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +17 -0
  161. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts.map +1 -0
  162. package/dist/server/server/agent/providers/tool-call-mapper-utils.js +109 -0
  163. package/dist/server/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
  164. package/dist/server/server/agent/system-prompt.d.ts +3 -0
  165. package/dist/server/server/agent/system-prompt.d.ts.map +1 -0
  166. package/dist/server/server/agent/system-prompt.js +19 -0
  167. package/dist/server/server/agent/system-prompt.js.map +1 -0
  168. package/dist/server/server/agent/timeline-append.d.ts +10 -0
  169. package/dist/server/server/agent/timeline-append.d.ts.map +1 -0
  170. package/dist/server/server/agent/timeline-append.js +27 -0
  171. package/dist/server/server/agent/timeline-append.js.map +1 -0
  172. package/dist/server/server/agent/timeline-projection.d.ts +39 -0
  173. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -0
  174. package/dist/server/server/agent/timeline-projection.js +215 -0
  175. package/dist/server/server/agent/timeline-projection.js.map +1 -0
  176. package/dist/server/server/agent/tool-name-normalization.d.ts +7 -0
  177. package/dist/server/server/agent/tool-name-normalization.d.ts.map +1 -0
  178. package/dist/server/server/agent/tool-name-normalization.js +45 -0
  179. package/dist/server/server/agent/tool-name-normalization.js.map +1 -0
  180. package/dist/server/server/agent/wait-for-agent-tracker.d.ts +15 -0
  181. package/dist/server/server/agent/wait-for-agent-tracker.d.ts.map +1 -0
  182. package/dist/server/server/agent/wait-for-agent-tracker.js +53 -0
  183. package/dist/server/server/agent/wait-for-agent-tracker.js.map +1 -0
  184. package/dist/server/server/agent-attention-policy.d.ts +20 -0
  185. package/dist/server/server/agent-attention-policy.d.ts.map +1 -0
  186. package/dist/server/server/agent-attention-policy.js +40 -0
  187. package/dist/server/server/agent-attention-policy.js.map +1 -0
  188. package/dist/server/server/allowed-hosts.d.ts +13 -0
  189. package/dist/server/server/allowed-hosts.d.ts.map +1 -0
  190. package/dist/server/server/allowed-hosts.js +94 -0
  191. package/dist/server/server/allowed-hosts.js.map +1 -0
  192. package/dist/server/server/bootstrap.d.ts +49 -0
  193. package/dist/server/server/bootstrap.d.ts.map +1 -0
  194. package/dist/server/server/bootstrap.js +422 -0
  195. package/dist/server/server/bootstrap.js.map +1 -0
  196. package/dist/server/server/client-message-id.d.ts +3 -0
  197. package/dist/server/server/client-message-id.d.ts.map +1 -0
  198. package/dist/server/server/client-message-id.js +12 -0
  199. package/dist/server/server/client-message-id.js.map +1 -0
  200. package/dist/server/server/config.d.ts +13 -0
  201. package/dist/server/server/config.d.ts.map +1 -0
  202. package/dist/server/server/config.js +58 -0
  203. package/dist/server/server/config.js.map +1 -0
  204. package/dist/server/server/connection-offer.d.ts +19 -0
  205. package/dist/server/server/connection-offer.d.ts.map +1 -0
  206. package/dist/server/server/connection-offer.js +60 -0
  207. package/dist/server/server/connection-offer.js.map +1 -0
  208. package/dist/server/server/daemon-keypair.d.ts +8 -0
  209. package/dist/server/server/daemon-keypair.d.ts.map +1 -0
  210. package/dist/server/server/daemon-keypair.js +40 -0
  211. package/dist/server/server/daemon-keypair.js.map +1 -0
  212. package/dist/server/server/daemon-version.d.ts +5 -0
  213. package/dist/server/server/daemon-version.d.ts.map +1 -0
  214. package/dist/server/server/daemon-version.js +22 -0
  215. package/dist/server/server/daemon-version.js.map +1 -0
  216. package/dist/server/server/exports.d.ts +16 -0
  217. package/dist/server/server/exports.d.ts.map +1 -0
  218. package/dist/server/server/exports.js +16 -0
  219. package/dist/server/server/exports.js.map +1 -0
  220. package/dist/server/server/file-download/token-store.d.ts +25 -0
  221. package/dist/server/server/file-download/token-store.d.ts.map +1 -0
  222. package/dist/server/server/file-download/token-store.js +40 -0
  223. package/dist/server/server/file-download/token-store.js.map +1 -0
  224. package/dist/server/server/file-explorer/service.d.ts +41 -0
  225. package/dist/server/server/file-explorer/service.d.ts.map +1 -0
  226. package/dist/server/server/file-explorer/service.js +226 -0
  227. package/dist/server/server/file-explorer/service.js.map +1 -0
  228. package/dist/server/server/index.d.ts +2 -0
  229. package/dist/server/server/index.d.ts.map +1 -0
  230. package/dist/server/server/index.js +141 -0
  231. package/dist/server/server/index.js.map +1 -0
  232. package/dist/server/server/json-utils.d.ts +11 -0
  233. package/dist/server/server/json-utils.d.ts.map +1 -0
  234. package/dist/server/server/json-utils.js +45 -0
  235. package/dist/server/server/json-utils.js.map +1 -0
  236. package/dist/server/server/junction-home.d.ts +2 -0
  237. package/dist/server/server/junction-home.d.ts.map +1 -0
  238. package/dist/server/server/junction-home.js +19 -0
  239. package/dist/server/server/junction-home.js.map +1 -0
  240. package/dist/server/server/logger.d.ts +12 -0
  241. package/dist/server/server/logger.d.ts.map +1 -0
  242. package/dist/server/server/logger.js +29 -0
  243. package/dist/server/server/logger.js.map +1 -0
  244. package/dist/server/server/messages.d.ts +9 -0
  245. package/dist/server/server/messages.d.ts.map +1 -0
  246. package/dist/server/server/messages.js +29 -0
  247. package/dist/server/server/messages.js.map +1 -0
  248. package/dist/server/server/package-version.d.ts +13 -0
  249. package/dist/server/server/package-version.d.ts.map +1 -0
  250. package/dist/server/server/package-version.js +47 -0
  251. package/dist/server/server/package-version.js.map +1 -0
  252. package/dist/server/server/path-utils.d.ts +3 -0
  253. package/dist/server/server/path-utils.d.ts.map +1 -0
  254. package/dist/server/server/path-utils.js +20 -0
  255. package/dist/server/server/path-utils.js.map +1 -0
  256. package/dist/server/server/persisted-config.d.ts +270 -0
  257. package/dist/server/server/persisted-config.d.ts.map +1 -0
  258. package/dist/server/server/persisted-config.js +152 -0
  259. package/dist/server/server/persisted-config.js.map +1 -0
  260. package/dist/server/server/persistence-hooks.d.ts +30 -0
  261. package/dist/server/server/persistence-hooks.d.ts.map +1 -0
  262. package/dist/server/server/persistence-hooks.js +68 -0
  263. package/dist/server/server/persistence-hooks.js.map +1 -0
  264. package/dist/server/server/pid-lock.d.ts +26 -0
  265. package/dist/server/server/pid-lock.d.ts.map +1 -0
  266. package/dist/server/server/pid-lock.js +280 -0
  267. package/dist/server/server/pid-lock.js.map +1 -0
  268. package/dist/server/server/relay-transport.d.ts +23 -0
  269. package/dist/server/server/relay-transport.d.ts.map +1 -0
  270. package/dist/server/server/relay-transport.js +457 -0
  271. package/dist/server/server/relay-transport.js.map +1 -0
  272. package/dist/server/server/server-id.d.ts +17 -0
  273. package/dist/server/server/server-id.d.ts.map +1 -0
  274. package/dist/server/server/server-id.js +63 -0
  275. package/dist/server/server/server-id.js.map +1 -0
  276. package/dist/server/server/session.d.ts +280 -0
  277. package/dist/server/server/session.d.ts.map +1 -0
  278. package/dist/server/server/session.js +4395 -0
  279. package/dist/server/server/session.js.map +1 -0
  280. package/dist/server/server/terminal-mcp/index.d.ts +4 -0
  281. package/dist/server/server/terminal-mcp/index.d.ts.map +1 -0
  282. package/dist/server/server/terminal-mcp/index.js +3 -0
  283. package/dist/server/server/terminal-mcp/index.js.map +1 -0
  284. package/dist/server/server/terminal-mcp/server.d.ts +10 -0
  285. package/dist/server/server/terminal-mcp/server.d.ts.map +1 -0
  286. package/dist/server/server/terminal-mcp/server.js +217 -0
  287. package/dist/server/server/terminal-mcp/server.js.map +1 -0
  288. package/dist/server/server/terminal-mcp/terminal-manager.d.ts +123 -0
  289. package/dist/server/server/terminal-mcp/terminal-manager.d.ts.map +1 -0
  290. package/dist/server/server/terminal-mcp/terminal-manager.js +351 -0
  291. package/dist/server/server/terminal-mcp/terminal-manager.js.map +1 -0
  292. package/dist/server/server/terminal-mcp/tmux.d.ts +207 -0
  293. package/dist/server/server/terminal-mcp/tmux.d.ts.map +1 -0
  294. package/dist/server/server/terminal-mcp/tmux.js +924 -0
  295. package/dist/server/server/terminal-mcp/tmux.js.map +1 -0
  296. package/dist/server/server/types.d.ts +5 -0
  297. package/dist/server/server/types.d.ts.map +1 -0
  298. package/dist/server/server/types.js +3 -0
  299. package/dist/server/server/types.js.map +1 -0
  300. package/dist/server/server/utils/diff-highlighter.d.ts +52 -0
  301. package/dist/server/server/utils/diff-highlighter.d.ts.map +1 -0
  302. package/dist/server/server/utils/diff-highlighter.js +244 -0
  303. package/dist/server/server/utils/diff-highlighter.js.map +1 -0
  304. package/dist/server/server/utils/syntax-highlighter.d.ts +10 -0
  305. package/dist/server/server/utils/syntax-highlighter.d.ts.map +1 -0
  306. package/dist/server/server/utils/syntax-highlighter.js +145 -0
  307. package/dist/server/server/utils/syntax-highlighter.js.map +1 -0
  308. package/dist/server/server/websocket-server.d.ts +79 -0
  309. package/dist/server/server/websocket-server.d.ts.map +1 -0
  310. package/dist/server/server/websocket-server.js +742 -0
  311. package/dist/server/server/websocket-server.js.map +1 -0
  312. package/dist/server/server/worktree-bootstrap.d.ts +29 -0
  313. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -0
  314. package/dist/server/server/worktree-bootstrap.js +454 -0
  315. package/dist/server/server/worktree-bootstrap.js.map +1 -0
  316. package/dist/server/shared/agent-attention-notification.d.ts +40 -0
  317. package/dist/server/shared/agent-attention-notification.d.ts.map +1 -0
  318. package/dist/server/shared/agent-attention-notification.js +130 -0
  319. package/dist/server/shared/agent-attention-notification.js.map +1 -0
  320. package/dist/server/shared/agent-lifecycle.d.ts +3 -0
  321. package/dist/server/shared/agent-lifecycle.d.ts.map +1 -0
  322. package/dist/server/shared/agent-lifecycle.js +8 -0
  323. package/dist/server/shared/agent-lifecycle.js.map +1 -0
  324. package/dist/server/shared/binary-mux.d.ts +31 -0
  325. package/dist/server/shared/binary-mux.d.ts.map +1 -0
  326. package/dist/server/shared/binary-mux.js +114 -0
  327. package/dist/server/shared/binary-mux.js.map +1 -0
  328. package/dist/server/shared/connection-offer.d.ts +62 -0
  329. package/dist/server/shared/connection-offer.d.ts.map +1 -0
  330. package/dist/server/shared/connection-offer.js +17 -0
  331. package/dist/server/shared/connection-offer.js.map +1 -0
  332. package/dist/server/shared/daemon-endpoints.d.ts +27 -0
  333. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -0
  334. package/dist/server/shared/daemon-endpoints.js +113 -0
  335. package/dist/server/shared/daemon-endpoints.js.map +1 -0
  336. package/dist/server/shared/messages.d.ts +36982 -0
  337. package/dist/server/shared/messages.d.ts.map +1 -0
  338. package/dist/server/shared/messages.js +1793 -0
  339. package/dist/server/shared/messages.js.map +1 -0
  340. package/dist/server/shared/path-utils.d.ts +2 -0
  341. package/dist/server/shared/path-utils.d.ts.map +1 -0
  342. package/dist/server/shared/path-utils.js +16 -0
  343. package/dist/server/shared/path-utils.js.map +1 -0
  344. package/dist/server/shared/terminal-key-input.d.ts +9 -0
  345. package/dist/server/shared/terminal-key-input.d.ts.map +1 -0
  346. package/dist/server/shared/terminal-key-input.js +132 -0
  347. package/dist/server/shared/terminal-key-input.js.map +1 -0
  348. package/dist/server/shared/tool-call-display.d.ts +11 -0
  349. package/dist/server/shared/tool-call-display.d.ts.map +1 -0
  350. package/dist/server/shared/tool-call-display.js +102 -0
  351. package/dist/server/shared/tool-call-display.js.map +1 -0
  352. package/dist/server/shared/tool-call-interpretation.d.ts +14 -0
  353. package/dist/server/shared/tool-call-interpretation.d.ts.map +1 -0
  354. package/dist/server/shared/tool-call-interpretation.js +76 -0
  355. package/dist/server/shared/tool-call-interpretation.js.map +1 -0
  356. package/dist/server/terminal/terminal-manager.d.ts +30 -0
  357. package/dist/server/terminal/terminal-manager.d.ts.map +1 -0
  358. package/dist/server/terminal/terminal-manager.js +148 -0
  359. package/dist/server/terminal/terminal-manager.js.map +1 -0
  360. package/dist/server/terminal/terminal.d.ts +93 -0
  361. package/dist/server/terminal/terminal.d.ts.map +1 -0
  362. package/dist/server/terminal/terminal.js +386 -0
  363. package/dist/server/terminal/terminal.js.map +1 -0
  364. package/dist/server/utils/checkout-git.d.ts +150 -0
  365. package/dist/server/utils/checkout-git.d.ts.map +1 -0
  366. package/dist/server/utils/checkout-git.js +1427 -0
  367. package/dist/server/utils/checkout-git.js.map +1 -0
  368. package/dist/server/utils/city-names.d.ts +17 -0
  369. package/dist/server/utils/city-names.d.ts.map +1 -0
  370. package/dist/server/utils/city-names.js +122 -0
  371. package/dist/server/utils/city-names.js.map +1 -0
  372. package/dist/server/utils/directory-suggestions.d.ts +31 -0
  373. package/dist/server/utils/directory-suggestions.d.ts.map +1 -0
  374. package/dist/server/utils/directory-suggestions.js +678 -0
  375. package/dist/server/utils/directory-suggestions.js.map +1 -0
  376. package/dist/server/utils/git-clone.d.ts +10 -0
  377. package/dist/server/utils/git-clone.d.ts.map +1 -0
  378. package/dist/server/utils/git-clone.js +45 -0
  379. package/dist/server/utils/git-clone.js.map +1 -0
  380. package/dist/server/utils/git-init.d.ts +9 -0
  381. package/dist/server/utils/git-init.d.ts.map +1 -0
  382. package/dist/server/utils/git-init.js +91 -0
  383. package/dist/server/utils/git-init.js.map +1 -0
  384. package/dist/server/utils/path.d.ts +5 -0
  385. package/dist/server/utils/path.d.ts.map +1 -0
  386. package/dist/server/utils/path.js +15 -0
  387. package/dist/server/utils/path.js.map +1 -0
  388. package/dist/server/utils/project-icon.d.ts +39 -0
  389. package/dist/server/utils/project-icon.d.ts.map +1 -0
  390. package/dist/server/utils/project-icon.js +391 -0
  391. package/dist/server/utils/project-icon.js.map +1 -0
  392. package/dist/server/utils/worktree-metadata.d.ts +47 -0
  393. package/dist/server/utils/worktree-metadata.d.ts.map +1 -0
  394. package/dist/server/utils/worktree-metadata.js +116 -0
  395. package/dist/server/utils/worktree-metadata.js.map +1 -0
  396. package/dist/server/utils/worktree.d.ts +175 -0
  397. package/dist/server/utils/worktree.d.ts.map +1 -0
  398. package/dist/server/utils/worktree.js +858 -0
  399. package/dist/server/utils/worktree.js.map +1 -0
  400. package/package.json +107 -0
@@ -0,0 +1,2223 @@
1
+ import { AgentCreateFailedStatusPayloadSchema, AgentCreatedStatusPayloadSchema, AgentRefreshedStatusPayloadSchema, AgentResumedStatusPayloadSchema, RestartRequestedStatusPayloadSchema, ShutdownRequestedStatusPayloadSchema, SessionInboundMessageSchema, WSOutboundMessageSchema, } from '../shared/messages.js';
2
+ import { getAgentProviderDefinition } from '../server/agent/provider-manifest.js';
3
+ import { isRelayClientWebSocketUrl } from '../shared/daemon-endpoints.js';
4
+ import { asUint8Array, decodeBinaryMuxFrame, encodeBinaryMuxFrame, BinaryMuxChannel, TerminalBinaryFlags, TerminalBinaryMessageType, } from '../shared/binary-mux.js';
5
+ import { encodeTerminalKeyInput } from '../shared/terminal-key-input.js';
6
+ import { TerminalStreamManager, } from './daemon-client-terminal-stream-manager.js';
7
+ import { createRelayE2eeTransportFactory, createWebSocketTransportFactory, decodeMessageData, defaultWebSocketFactory, describeTransportClose, describeTransportError, encodeUtf8String, } from './daemon-client-transport.js';
8
+ const consoleLogger = {
9
+ debug: () => { },
10
+ info: (obj, msg) => console.info(msg, obj),
11
+ warn: (obj, msg) => console.warn(msg, obj),
12
+ error: (obj, msg) => console.error(msg, obj),
13
+ };
14
+ function getNowMs() {
15
+ if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
16
+ return performance.now();
17
+ }
18
+ return Date.now();
19
+ }
20
+ class DaemonRpcError extends Error {
21
+ constructor(params) {
22
+ super(params.error);
23
+ this.name = 'DaemonRpcError';
24
+ this.requestId = params.requestId;
25
+ this.requestType = params.requestType;
26
+ this.code = params.code;
27
+ }
28
+ }
29
+ const DEFAULT_RECONNECT_BASE_DELAY_MS = 1500;
30
+ const DEFAULT_RECONNECT_MAX_DELAY_MS = 30000;
31
+ const DEFAULT_CONNECT_TIMEOUT_MS = 15000;
32
+ /** Default timeout for waiting for connection before sending queued messages */
33
+ const DEFAULT_SEND_QUEUE_TIMEOUT_MS = 10000;
34
+ function normalizeClientId(value) {
35
+ if (typeof value !== 'string') {
36
+ return null;
37
+ }
38
+ const trimmed = value.trim();
39
+ return trimmed.length > 0 ? trimmed : null;
40
+ }
41
+ function hashForLog(value) {
42
+ let hash = 0;
43
+ for (let index = 0; index < value.length; index += 1) {
44
+ hash = (hash * 31 + value.charCodeAt(index)) | 0;
45
+ }
46
+ return `h_${Math.abs(hash).toString(16)}`;
47
+ }
48
+ function toReasonCode(reason) {
49
+ if (!reason) {
50
+ return null;
51
+ }
52
+ const normalized = reason.toLowerCase();
53
+ if (normalized.includes('timed out')) {
54
+ return 'connect_timeout';
55
+ }
56
+ if (normalized.includes('disposed')) {
57
+ return 'disposed';
58
+ }
59
+ if (normalized.includes('client closed')) {
60
+ return 'client_closed';
61
+ }
62
+ if (normalized.includes('transport')) {
63
+ return 'transport_error';
64
+ }
65
+ if (normalized.includes('failed to connect')) {
66
+ return 'connect_failed';
67
+ }
68
+ return 'unknown';
69
+ }
70
+ export class DaemonClient {
71
+ constructor(config) {
72
+ this.config = config;
73
+ this.transport = null;
74
+ this.transportCleanup = [];
75
+ this.rawMessageListeners = new Set();
76
+ this.messageHandlers = new Map();
77
+ this.eventListeners = new Set();
78
+ this.waiters = new Set();
79
+ this.checkoutStatusInFlight = new Map();
80
+ this.connectionListeners = new Set();
81
+ this.reconnectTimeout = null;
82
+ this.connectTimeout = null;
83
+ this.pendingGenericTransportErrorTimeout = null;
84
+ this.reconnectAttempt = 0;
85
+ this.shouldReconnect = true;
86
+ this.connectPromise = null;
87
+ this.connectResolve = null;
88
+ this.connectReject = null;
89
+ this.lastErrorValue = null;
90
+ this.connectionState = { status: 'idle' };
91
+ this.checkoutDiffSubscriptions = new Map();
92
+ this.terminalDirectorySubscriptions = new Set();
93
+ this.pendingSendQueue = [];
94
+ this.lastWelcomeMessage = null;
95
+ this.logger = config.logger ?? consoleLogger;
96
+ this.logConnectionPath = isRelayClientWebSocketUrl(this.config.url) ? 'relay' : 'direct';
97
+ let parsedUrlForLog = null;
98
+ try {
99
+ parsedUrlForLog = new URL(this.config.url);
100
+ }
101
+ catch {
102
+ parsedUrlForLog = null;
103
+ }
104
+ const parsedServerIdForLog = normalizeClientId(parsedUrlForLog?.searchParams.get('serverId'));
105
+ this.logServerId = parsedServerIdForLog ?? parsedUrlForLog?.host ?? null;
106
+ const resolvedClientId = normalizeClientId(this.config.clientId);
107
+ if (!resolvedClientId) {
108
+ throw new Error('Daemon client requires a non-empty clientId');
109
+ }
110
+ this.config.clientId = resolvedClientId;
111
+ this.logClientIdHash = hashForLog(resolvedClientId);
112
+ this.logGeneration =
113
+ typeof this.config.runtimeGeneration === 'number' && Number.isFinite(this.config.runtimeGeneration)
114
+ ? this.config.runtimeGeneration
115
+ : null;
116
+ this.terminalStreams = new TerminalStreamManager({
117
+ sendAck: (ack) => {
118
+ this.sendBinaryFrame({
119
+ channel: BinaryMuxChannel.Terminal,
120
+ messageType: TerminalBinaryMessageType.Ack,
121
+ streamId: ack.streamId,
122
+ offset: ack.offset,
123
+ payload: new Uint8Array(0),
124
+ });
125
+ },
126
+ });
127
+ }
128
+ emitDiagnosticsEvent(event) {
129
+ try {
130
+ this.config.onDiagnosticsEvent?.(event);
131
+ }
132
+ catch {
133
+ // Diagnostics hooks must never break daemon message handling.
134
+ }
135
+ }
136
+ // ============================================================================
137
+ // Connection
138
+ // ============================================================================
139
+ async connect() {
140
+ if (this.connectionState.status === 'disposed') {
141
+ throw new Error('Daemon client is disposed');
142
+ }
143
+ if (this.connectionState.status === 'connected') {
144
+ return;
145
+ }
146
+ if (this.connectPromise) {
147
+ return this.connectPromise;
148
+ }
149
+ this.shouldReconnect = true;
150
+ this.connectPromise = new Promise((resolve, reject) => {
151
+ this.connectResolve = resolve;
152
+ this.connectReject = reject;
153
+ this.attemptConnect();
154
+ });
155
+ return this.connectPromise;
156
+ }
157
+ attemptConnect() {
158
+ if (this.connectionState.status === 'disposed') {
159
+ this.rejectConnect(new Error('Daemon client is disposed'));
160
+ return;
161
+ }
162
+ if (!this.shouldReconnect) {
163
+ this.rejectConnect(new Error('Daemon client is closed'));
164
+ return;
165
+ }
166
+ if (this.connectionState.status === 'connecting') {
167
+ return;
168
+ }
169
+ const headers = {};
170
+ if (this.config.authHeader) {
171
+ headers['Authorization'] = this.config.authHeader;
172
+ }
173
+ try {
174
+ // Reconnect can overlap with browser close/error delivery ordering.
175
+ // Always dispose previous transport before constructing the next one.
176
+ this.disposeTransport();
177
+ const baseTransportFactory = this.config.transportFactory ??
178
+ createWebSocketTransportFactory(this.config.webSocketFactory ?? defaultWebSocketFactory);
179
+ const shouldUseRelayE2ee = this.config.e2ee?.enabled === true && isRelayClientWebSocketUrl(this.config.url);
180
+ let transportFactory = baseTransportFactory;
181
+ if (shouldUseRelayE2ee) {
182
+ const daemonPublicKeyB64 = this.config.e2ee?.daemonPublicKeyB64;
183
+ if (!daemonPublicKeyB64) {
184
+ throw new Error('daemonPublicKeyB64 is required for relay E2EE');
185
+ }
186
+ transportFactory = createRelayE2eeTransportFactory({
187
+ baseFactory: baseTransportFactory,
188
+ daemonPublicKeyB64,
189
+ logger: this.logger,
190
+ });
191
+ }
192
+ const transportUrl = this.resolveTransportUrlForAttempt();
193
+ const transport = transportFactory({ url: transportUrl, headers });
194
+ this.transport = transport;
195
+ this.lastWelcomeMessage = null;
196
+ this.updateConnectionState({
197
+ status: 'connecting',
198
+ attempt: this.reconnectAttempt,
199
+ }, { event: 'CONNECT_REQUEST' });
200
+ this.resetConnectTimeout();
201
+ const timeoutMs = Math.max(1, this.config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS);
202
+ this.connectTimeout = setTimeout(() => {
203
+ if (this.connectionState.status !== 'connecting') {
204
+ return;
205
+ }
206
+ this.lastErrorValue = 'Connection timed out';
207
+ this.disposeTransport(1001, 'Connection timed out');
208
+ this.scheduleReconnect({
209
+ reason: 'Connection timed out',
210
+ event: 'CONNECT_TIMEOUT',
211
+ reasonCode: 'connect_timeout',
212
+ });
213
+ }, timeoutMs);
214
+ this.transportCleanup = [
215
+ transport.onOpen(() => {
216
+ if (this.pendingGenericTransportErrorTimeout) {
217
+ clearTimeout(this.pendingGenericTransportErrorTimeout);
218
+ this.pendingGenericTransportErrorTimeout = null;
219
+ }
220
+ this.lastErrorValue = null;
221
+ this.sendHelloMessage();
222
+ }),
223
+ transport.onClose((event) => {
224
+ this.resetConnectTimeout();
225
+ if (this.pendingGenericTransportErrorTimeout) {
226
+ clearTimeout(this.pendingGenericTransportErrorTimeout);
227
+ this.pendingGenericTransportErrorTimeout = null;
228
+ }
229
+ const reason = describeTransportClose(event);
230
+ if (reason) {
231
+ this.lastErrorValue = reason;
232
+ }
233
+ this.scheduleReconnect({
234
+ reason,
235
+ event: 'TRANSPORT_CLOSE',
236
+ reasonCode: 'transport_closed',
237
+ });
238
+ }),
239
+ transport.onError((event) => {
240
+ this.resetConnectTimeout();
241
+ const reason = describeTransportError(event);
242
+ const isGeneric = reason === 'Transport error';
243
+ // Browser WebSocket.onerror often provides no useful details and is followed
244
+ // by a close event (often with code 1006). Prefer surfacing the close details
245
+ // instead of immediately disconnecting with a generic "Transport error".
246
+ if (isGeneric) {
247
+ this.lastErrorValue ?? (this.lastErrorValue = reason);
248
+ if (!this.pendingGenericTransportErrorTimeout) {
249
+ this.pendingGenericTransportErrorTimeout = setTimeout(() => {
250
+ this.pendingGenericTransportErrorTimeout = null;
251
+ if (this.connectionState.status === 'connected' ||
252
+ this.connectionState.status === 'connecting') {
253
+ this.lastErrorValue = reason;
254
+ this.scheduleReconnect({
255
+ reason,
256
+ event: 'TRANSPORT_ERROR',
257
+ reasonCode: 'transport_error',
258
+ });
259
+ }
260
+ }, 250);
261
+ }
262
+ return;
263
+ }
264
+ if (this.pendingGenericTransportErrorTimeout) {
265
+ clearTimeout(this.pendingGenericTransportErrorTimeout);
266
+ this.pendingGenericTransportErrorTimeout = null;
267
+ }
268
+ this.lastErrorValue = reason;
269
+ this.scheduleReconnect({
270
+ reason,
271
+ event: 'TRANSPORT_ERROR',
272
+ reasonCode: 'transport_error',
273
+ });
274
+ }),
275
+ transport.onMessage((data) => this.handleTransportMessage(data)),
276
+ ];
277
+ }
278
+ catch (error) {
279
+ this.resetConnectTimeout();
280
+ const message = error instanceof Error ? error.message : 'Failed to connect';
281
+ this.lastErrorValue = message;
282
+ this.scheduleReconnect({
283
+ reason: message,
284
+ event: 'CONNECT_FAILED',
285
+ reasonCode: 'connect_failed',
286
+ });
287
+ this.rejectConnect(error instanceof Error ? error : new Error(message));
288
+ }
289
+ }
290
+ resolveConnect() {
291
+ if (this.connectResolve) {
292
+ this.connectResolve();
293
+ }
294
+ this.connectPromise = null;
295
+ this.connectResolve = null;
296
+ this.connectReject = null;
297
+ }
298
+ rejectConnect(error) {
299
+ if (this.connectReject) {
300
+ this.connectReject(error);
301
+ }
302
+ this.connectPromise = null;
303
+ this.connectResolve = null;
304
+ this.connectReject = null;
305
+ }
306
+ async close() {
307
+ if (this.connectionState.status === 'disposed') {
308
+ return;
309
+ }
310
+ this.shouldReconnect = false;
311
+ this.connectPromise = null;
312
+ this.connectResolve = null;
313
+ this.connectReject = null;
314
+ if (this.reconnectTimeout) {
315
+ clearTimeout(this.reconnectTimeout);
316
+ this.reconnectTimeout = null;
317
+ }
318
+ this.resetConnectTimeout();
319
+ this.disposeTransport(1000, 'Client closed');
320
+ this.clearWaiters(new Error('Daemon client closed'));
321
+ this.rejectPendingSendQueue(new Error('Daemon client closed'));
322
+ this.terminalStreams.clearAll();
323
+ this.lastWelcomeMessage = null;
324
+ this.updateConnectionState({ status: 'disposed' }, { event: 'DISPOSE', reason: 'Client closed', reasonCode: 'disposed' });
325
+ }
326
+ ensureConnected() {
327
+ if (this.connectionState.status === 'disposed') {
328
+ return;
329
+ }
330
+ if (!this.shouldReconnect) {
331
+ this.shouldReconnect = true;
332
+ }
333
+ if (this.connectionState.status === 'connected' ||
334
+ this.connectionState.status === 'connecting') {
335
+ return;
336
+ }
337
+ void this.connect();
338
+ }
339
+ getConnectionState() {
340
+ return this.connectionState;
341
+ }
342
+ subscribeConnectionStatus(listener) {
343
+ this.connectionListeners.add(listener);
344
+ listener(this.connectionState);
345
+ return () => {
346
+ this.connectionListeners.delete(listener);
347
+ };
348
+ }
349
+ get isConnected() {
350
+ return this.connectionState.status === 'connected';
351
+ }
352
+ get isConnecting() {
353
+ return this.connectionState.status === 'connecting';
354
+ }
355
+ get lastError() {
356
+ return this.lastErrorValue;
357
+ }
358
+ // ============================================================================
359
+ // Message Subscription
360
+ // ============================================================================
361
+ subscribe(handler) {
362
+ this.eventListeners.add(handler);
363
+ return () => this.eventListeners.delete(handler);
364
+ }
365
+ subscribeRawMessages(handler) {
366
+ this.rawMessageListeners.add(handler);
367
+ return () => {
368
+ this.rawMessageListeners.delete(handler);
369
+ };
370
+ }
371
+ on(arg1, arg2) {
372
+ if (typeof arg1 === 'function') {
373
+ return this.subscribe(arg1);
374
+ }
375
+ const type = arg1;
376
+ const handler = arg2;
377
+ if (!this.messageHandlers.has(type)) {
378
+ this.messageHandlers.set(type, new Set());
379
+ }
380
+ this.messageHandlers.get(type).add(handler);
381
+ return () => {
382
+ const handlers = this.messageHandlers.get(type);
383
+ if (!handlers) {
384
+ return;
385
+ }
386
+ handlers.delete(handler);
387
+ if (handlers.size === 0) {
388
+ this.messageHandlers.delete(type);
389
+ }
390
+ };
391
+ }
392
+ // ============================================================================
393
+ // Core Send Helpers
394
+ // ============================================================================
395
+ /**
396
+ * Send a session message. For fire-and-forget messages (heartbeats, etc.),
397
+ * failures are suppressed if `suppressSendErrors` is configured.
398
+ * For RPC methods that wait for responses, use `sendSessionMessageOrThrow` instead.
399
+ */
400
+ sendSessionMessage(message) {
401
+ if (!this.transport || this.connectionState.status !== 'connected') {
402
+ if (this.config.suppressSendErrors) {
403
+ return;
404
+ }
405
+ throw new Error(`Transport not connected (status: ${this.connectionState.status})`);
406
+ }
407
+ const payload = SessionInboundMessageSchema.parse(message);
408
+ try {
409
+ this.transport.send(JSON.stringify({ type: 'session', message: payload }));
410
+ }
411
+ catch (error) {
412
+ if (this.config.suppressSendErrors) {
413
+ return;
414
+ }
415
+ throw error instanceof Error ? error : new Error(String(error));
416
+ }
417
+ }
418
+ sendBinaryFrame(frame) {
419
+ if (!this.transport || this.connectionState.status !== 'connected') {
420
+ if (this.config.suppressSendErrors) {
421
+ return;
422
+ }
423
+ throw new Error(`Transport not connected (status: ${this.connectionState.status})`);
424
+ }
425
+ try {
426
+ this.transport.send(encodeBinaryMuxFrame(frame));
427
+ }
428
+ catch (error) {
429
+ if (this.config.suppressSendErrors) {
430
+ return;
431
+ }
432
+ throw error instanceof Error ? error : new Error(String(error));
433
+ }
434
+ }
435
+ /**
436
+ * Send a session message for RPC methods that create waiters.
437
+ * If the connection is still being established ("connecting"), the message
438
+ * is queued and will be sent once connected (or rejected after timeout).
439
+ * This prevents waiters from hanging forever when called during connection.
440
+ */
441
+ sendSessionMessageOrThrow(message) {
442
+ const status = this.connectionState.status;
443
+ // If connected, send immediately
444
+ if (this.transport && status === 'connected') {
445
+ const payload = SessionInboundMessageSchema.parse(message);
446
+ this.transport.send(JSON.stringify({ type: 'session', message: payload }));
447
+ return Promise.resolve();
448
+ }
449
+ // If connecting, queue the message to be sent once connected
450
+ if (status === 'connecting') {
451
+ return new Promise((resolve, reject) => {
452
+ const timeoutHandle = setTimeout(() => {
453
+ // Remove from queue
454
+ const idx = this.pendingSendQueue.findIndex((p) => p.resolve === resolve);
455
+ if (idx !== -1) {
456
+ this.pendingSendQueue.splice(idx, 1);
457
+ }
458
+ reject(new Error(`Timed out waiting for connection to send message`));
459
+ }, DEFAULT_SEND_QUEUE_TIMEOUT_MS);
460
+ this.pendingSendQueue.push({ message, resolve, reject, timeoutHandle });
461
+ });
462
+ }
463
+ // Not connected and not connecting - fail immediately
464
+ return Promise.reject(new Error(`Transport not connected (status: ${status})`));
465
+ }
466
+ /**
467
+ * Flush pending send queue - called when connection is established.
468
+ */
469
+ flushPendingSendQueue() {
470
+ const queue = this.pendingSendQueue;
471
+ this.pendingSendQueue = [];
472
+ for (const pending of queue) {
473
+ clearTimeout(pending.timeoutHandle);
474
+ try {
475
+ if (this.transport && this.connectionState.status === 'connected') {
476
+ const payload = SessionInboundMessageSchema.parse(pending.message);
477
+ this.transport.send(JSON.stringify({ type: 'session', message: payload }));
478
+ pending.resolve();
479
+ }
480
+ else {
481
+ pending.reject(new Error('Connection lost before message could be sent'));
482
+ }
483
+ }
484
+ catch (error) {
485
+ pending.reject(error instanceof Error ? error : new Error(String(error)));
486
+ }
487
+ }
488
+ }
489
+ /**
490
+ * Reject all pending sends - called when connection fails or is closed.
491
+ */
492
+ rejectPendingSendQueue(error) {
493
+ const queue = this.pendingSendQueue;
494
+ this.pendingSendQueue = [];
495
+ for (const pending of queue) {
496
+ clearTimeout(pending.timeoutHandle);
497
+ pending.reject(error);
498
+ }
499
+ }
500
+ async sendRequest(params) {
501
+ const { promise, cancel } = this.waitForWithCancel((msg) => {
502
+ if (msg.type === 'rpc_error' && msg.payload.requestId === params.requestId) {
503
+ return {
504
+ kind: 'error',
505
+ error: new DaemonRpcError({
506
+ requestId: msg.payload.requestId,
507
+ error: msg.payload.error,
508
+ requestType: msg.payload.requestType,
509
+ code: msg.payload.code,
510
+ }),
511
+ };
512
+ }
513
+ const value = params.select(msg);
514
+ if (value === null) {
515
+ return null;
516
+ }
517
+ return { kind: 'ok', value };
518
+ }, params.timeout, params.options);
519
+ try {
520
+ await this.sendSessionMessageOrThrow(params.message);
521
+ }
522
+ catch (error) {
523
+ const err = error instanceof Error ? error : new Error(String(error));
524
+ cancel(err);
525
+ void promise.catch(() => undefined);
526
+ throw err;
527
+ }
528
+ const result = await promise;
529
+ if (result.kind === 'error') {
530
+ throw result.error;
531
+ }
532
+ return result.value;
533
+ }
534
+ async sendCorrelatedRequest(params) {
535
+ return this.sendRequest({
536
+ requestId: params.requestId,
537
+ message: params.message,
538
+ timeout: params.timeout,
539
+ options: params.options,
540
+ select: (msg) => {
541
+ const correlated = msg;
542
+ if (correlated.type !== params.responseType) {
543
+ return null;
544
+ }
545
+ const payload = correlated.payload;
546
+ if (payload.requestId !== params.requestId) {
547
+ return null;
548
+ }
549
+ if (!params.selectPayload) {
550
+ return payload;
551
+ }
552
+ return params.selectPayload(payload);
553
+ },
554
+ });
555
+ }
556
+ sendCorrelatedSessionRequest(params) {
557
+ const resolvedRequestId = this.createRequestId(params.requestId);
558
+ const message = SessionInboundMessageSchema.parse({
559
+ ...params.message,
560
+ requestId: resolvedRequestId,
561
+ });
562
+ return this.sendCorrelatedRequest({
563
+ requestId: resolvedRequestId,
564
+ message,
565
+ responseType: params.responseType,
566
+ timeout: params.timeout,
567
+ options: { skipQueue: true },
568
+ ...(params.selectPayload ? { selectPayload: params.selectPayload } : {}),
569
+ });
570
+ }
571
+ clearAgentAttention(agentId) {
572
+ this.sendSessionMessage({ type: 'clear_agent_attention', agentId });
573
+ }
574
+ sendHeartbeat(params) {
575
+ this.sendSessionMessage({
576
+ type: 'client_heartbeat',
577
+ deviceType: params.deviceType,
578
+ focusedAgentId: params.focusedAgentId,
579
+ lastActivityAt: params.lastActivityAt,
580
+ appVisible: params.appVisible,
581
+ appVisibilityChangedAt: params.appVisibilityChangedAt,
582
+ });
583
+ }
584
+ async ping(params) {
585
+ const requestId = params?.requestId ?? `ping-${Date.now()}-${Math.random().toString(36).slice(2)}`;
586
+ const clientSentAt = Date.now();
587
+ const payload = await this.sendRequest({
588
+ requestId,
589
+ message: { type: 'ping', requestId, clientSentAt },
590
+ timeout: params?.timeoutMs ?? 5000,
591
+ select: (msg) => {
592
+ if (msg.type !== 'pong')
593
+ return null;
594
+ if (msg.payload.requestId !== requestId)
595
+ return null;
596
+ if (typeof msg.payload.serverReceivedAt !== 'number')
597
+ return null;
598
+ if (typeof msg.payload.serverSentAt !== 'number')
599
+ return null;
600
+ return msg.payload;
601
+ },
602
+ });
603
+ return {
604
+ requestId,
605
+ clientSentAt,
606
+ serverReceivedAt: payload.serverReceivedAt,
607
+ serverSentAt: payload.serverSentAt,
608
+ rttMs: Date.now() - clientSentAt,
609
+ };
610
+ }
611
+ // ============================================================================
612
+ // Agent RPCs (requestId-correlated)
613
+ // ============================================================================
614
+ async fetchAgents(options) {
615
+ const resolvedRequestId = this.createRequestId(options?.requestId);
616
+ const message = SessionInboundMessageSchema.parse({
617
+ type: 'fetch_agents_request',
618
+ requestId: resolvedRequestId,
619
+ ...(options?.filter ? { filter: options.filter } : {}),
620
+ ...(options?.sort ? { sort: options.sort } : {}),
621
+ ...(options?.page ? { page: options.page } : {}),
622
+ ...(options?.subscribe ? { subscribe: options.subscribe } : {}),
623
+ });
624
+ return this.sendRequest({
625
+ requestId: resolvedRequestId,
626
+ message,
627
+ timeout: 10000,
628
+ options: { skipQueue: true },
629
+ select: (msg) => {
630
+ if (msg.type !== 'fetch_agents_response') {
631
+ return null;
632
+ }
633
+ if (msg.payload.requestId !== resolvedRequestId) {
634
+ return null;
635
+ }
636
+ return msg.payload;
637
+ },
638
+ });
639
+ }
640
+ async fetchAgent(agentId, requestId) {
641
+ const resolvedRequestId = this.createRequestId(requestId);
642
+ const message = SessionInboundMessageSchema.parse({
643
+ type: 'fetch_agent_request',
644
+ requestId: resolvedRequestId,
645
+ agentId,
646
+ });
647
+ const payload = await this.sendRequest({
648
+ requestId: resolvedRequestId,
649
+ message,
650
+ timeout: 10000,
651
+ options: { skipQueue: true },
652
+ select: (msg) => {
653
+ if (msg.type !== 'fetch_agent_response') {
654
+ return null;
655
+ }
656
+ if (msg.payload.requestId !== resolvedRequestId) {
657
+ return null;
658
+ }
659
+ return msg.payload;
660
+ },
661
+ });
662
+ if (payload.error) {
663
+ throw new Error(payload.error);
664
+ }
665
+ if (!payload.agent) {
666
+ return null;
667
+ }
668
+ return { agent: payload.agent, project: payload.project ?? null };
669
+ }
670
+ resubscribeCheckoutDiffSubscriptions() {
671
+ if (this.checkoutDiffSubscriptions.size === 0) {
672
+ return;
673
+ }
674
+ for (const [subscriptionId, subscription] of this.checkoutDiffSubscriptions) {
675
+ const message = SessionInboundMessageSchema.parse({
676
+ type: 'subscribe_checkout_diff_request',
677
+ subscriptionId,
678
+ cwd: subscription.cwd,
679
+ compare: subscription.compare,
680
+ requestId: this.createRequestId(),
681
+ });
682
+ this.sendSessionMessage(message);
683
+ }
684
+ }
685
+ resubscribeTerminalDirectorySubscriptions() {
686
+ if (this.terminalDirectorySubscriptions.size === 0) {
687
+ return;
688
+ }
689
+ for (const cwd of this.terminalDirectorySubscriptions) {
690
+ this.sendSessionMessage({
691
+ type: 'subscribe_terminals_request',
692
+ cwd,
693
+ });
694
+ }
695
+ }
696
+ // ============================================================================
697
+ // Agent Lifecycle
698
+ // ============================================================================
699
+ async createAgent(options) {
700
+ const requestId = this.createRequestId(options.requestId);
701
+ const config = resolveAgentConfig(options);
702
+ const message = SessionInboundMessageSchema.parse({
703
+ type: 'create_agent_request',
704
+ requestId,
705
+ config,
706
+ ...(options.initialPrompt ? { initialPrompt: options.initialPrompt } : {}),
707
+ ...(options.clientMessageId ? { clientMessageId: options.clientMessageId } : {}),
708
+ ...(options.outputSchema ? { outputSchema: options.outputSchema } : {}),
709
+ ...(options.images && options.images.length > 0 ? { images: options.images } : {}),
710
+ ...(options.git ? { git: options.git } : {}),
711
+ ...(options.worktreeName ? { worktreeName: options.worktreeName } : {}),
712
+ ...(options.labels && Object.keys(options.labels).length > 0
713
+ ? { labels: options.labels }
714
+ : {}),
715
+ });
716
+ const status = await this.sendRequest({
717
+ requestId,
718
+ message,
719
+ timeout: 15000,
720
+ options: { skipQueue: true },
721
+ select: (msg) => {
722
+ if (msg.type !== 'status') {
723
+ return null;
724
+ }
725
+ const created = AgentCreatedStatusPayloadSchema.safeParse(msg.payload);
726
+ if (created.success && created.data.requestId === requestId) {
727
+ return created.data;
728
+ }
729
+ const failed = AgentCreateFailedStatusPayloadSchema.safeParse(msg.payload);
730
+ if (failed.success && failed.data.requestId === requestId) {
731
+ return failed.data;
732
+ }
733
+ return null;
734
+ },
735
+ });
736
+ if (status.status === 'agent_create_failed') {
737
+ throw new Error(status.error);
738
+ }
739
+ return status.agent;
740
+ }
741
+ async deleteAgent(agentId) {
742
+ const requestId = this.createRequestId();
743
+ const message = SessionInboundMessageSchema.parse({
744
+ type: 'delete_agent_request',
745
+ agentId,
746
+ requestId,
747
+ });
748
+ await this.sendRequest({
749
+ requestId,
750
+ message,
751
+ timeout: 10000,
752
+ options: { skipQueue: true },
753
+ select: (msg) => {
754
+ if (msg.type !== 'agent_deleted') {
755
+ return null;
756
+ }
757
+ if (msg.payload.requestId !== requestId) {
758
+ return null;
759
+ }
760
+ return msg.payload;
761
+ },
762
+ });
763
+ }
764
+ async archiveAgent(agentId) {
765
+ const requestId = this.createRequestId();
766
+ const message = SessionInboundMessageSchema.parse({
767
+ type: 'archive_agent_request',
768
+ agentId,
769
+ requestId,
770
+ });
771
+ const result = await this.sendRequest({
772
+ requestId,
773
+ message,
774
+ timeout: 10000,
775
+ options: { skipQueue: true },
776
+ select: (msg) => {
777
+ if (msg.type !== 'agent_archived') {
778
+ return null;
779
+ }
780
+ if (msg.payload.requestId !== requestId) {
781
+ return null;
782
+ }
783
+ return msg.payload;
784
+ },
785
+ });
786
+ return { archivedAt: result.archivedAt };
787
+ }
788
+ async updateAgent(agentId, updates) {
789
+ const requestId = this.createRequestId();
790
+ const message = SessionInboundMessageSchema.parse({
791
+ type: 'update_agent_request',
792
+ agentId,
793
+ ...(updates.name !== undefined ? { name: updates.name } : {}),
794
+ ...(updates.labels && Object.keys(updates.labels).length > 0
795
+ ? { labels: updates.labels }
796
+ : {}),
797
+ requestId,
798
+ });
799
+ const payload = await this.sendRequest({
800
+ requestId,
801
+ message,
802
+ timeout: 10000,
803
+ options: { skipQueue: true },
804
+ select: (msg) => {
805
+ if (msg.type !== 'update_agent_response') {
806
+ return null;
807
+ }
808
+ if (msg.payload.requestId !== requestId) {
809
+ return null;
810
+ }
811
+ return msg.payload;
812
+ },
813
+ });
814
+ if (!payload.accepted) {
815
+ throw new Error(payload.error ?? 'updateAgent rejected');
816
+ }
817
+ }
818
+ async resumeAgent(handle, overrides) {
819
+ const requestId = this.createRequestId();
820
+ const message = SessionInboundMessageSchema.parse({
821
+ type: 'resume_agent_request',
822
+ requestId,
823
+ handle,
824
+ ...(overrides ? { overrides } : {}),
825
+ });
826
+ const status = await this.sendRequest({
827
+ requestId,
828
+ message,
829
+ timeout: 15000,
830
+ options: { skipQueue: true },
831
+ select: (msg) => {
832
+ if (msg.type !== 'status') {
833
+ return null;
834
+ }
835
+ const resumed = AgentResumedStatusPayloadSchema.safeParse(msg.payload);
836
+ if (resumed.success && resumed.data.requestId === requestId) {
837
+ return resumed.data;
838
+ }
839
+ return null;
840
+ },
841
+ });
842
+ return status.agent;
843
+ }
844
+ async refreshAgent(agentId, requestId) {
845
+ const resolvedRequestId = this.createRequestId(requestId);
846
+ const message = SessionInboundMessageSchema.parse({
847
+ type: 'refresh_agent_request',
848
+ agentId,
849
+ requestId: resolvedRequestId,
850
+ });
851
+ return this.sendRequest({
852
+ requestId: resolvedRequestId,
853
+ message,
854
+ timeout: 15000,
855
+ options: { skipQueue: true },
856
+ select: (msg) => {
857
+ if (msg.type !== 'status') {
858
+ return null;
859
+ }
860
+ const refreshed = AgentRefreshedStatusPayloadSchema.safeParse(msg.payload);
861
+ if (refreshed.success && refreshed.data.requestId === resolvedRequestId) {
862
+ return refreshed.data;
863
+ }
864
+ return null;
865
+ },
866
+ });
867
+ }
868
+ async fetchAgentTimeline(agentId, options = {}) {
869
+ const resolvedRequestId = this.createRequestId(options.requestId);
870
+ const message = SessionInboundMessageSchema.parse({
871
+ type: 'fetch_agent_timeline_request',
872
+ agentId,
873
+ requestId: resolvedRequestId,
874
+ ...(options.direction ? { direction: options.direction } : {}),
875
+ ...(options.cursor ? { cursor: options.cursor } : {}),
876
+ ...(typeof options.limit === 'number' ? { limit: options.limit } : {}),
877
+ ...(options.projection ? { projection: options.projection } : {}),
878
+ });
879
+ const payload = await this.sendRequest({
880
+ requestId: resolvedRequestId,
881
+ message,
882
+ timeout: 15000,
883
+ options: { skipQueue: true },
884
+ select: (msg) => {
885
+ if (msg.type !== 'fetch_agent_timeline_response') {
886
+ return null;
887
+ }
888
+ if (msg.payload.requestId !== resolvedRequestId) {
889
+ return null;
890
+ }
891
+ return msg.payload;
892
+ },
893
+ });
894
+ if (payload.error) {
895
+ throw new Error(payload.error);
896
+ }
897
+ return payload;
898
+ }
899
+ // ============================================================================
900
+ // Agent Interaction
901
+ // ============================================================================
902
+ async sendAgentMessage(agentId, text, options) {
903
+ const requestId = this.createRequestId();
904
+ const messageId = options?.messageId ?? crypto.randomUUID();
905
+ const message = SessionInboundMessageSchema.parse({
906
+ type: 'send_agent_message_request',
907
+ requestId,
908
+ agentId,
909
+ text,
910
+ ...(messageId ? { messageId } : {}),
911
+ ...(options?.images ? { images: options.images } : {}),
912
+ });
913
+ const payload = await this.sendRequest({
914
+ requestId,
915
+ message,
916
+ timeout: 15000,
917
+ options: { skipQueue: true },
918
+ select: (msg) => {
919
+ if (msg.type !== 'send_agent_message_response') {
920
+ return null;
921
+ }
922
+ if (msg.payload.requestId !== requestId) {
923
+ return null;
924
+ }
925
+ return msg.payload;
926
+ },
927
+ });
928
+ if (!payload.accepted) {
929
+ throw new Error(payload.error ?? 'sendAgentMessage rejected');
930
+ }
931
+ }
932
+ async sendMessage(agentId, text, options) {
933
+ await this.sendAgentMessage(agentId, text, options);
934
+ }
935
+ async cancelAgent(agentId) {
936
+ this.sendSessionMessage({ type: 'cancel_agent_request', agentId });
937
+ }
938
+ async setAgentMode(agentId, modeId) {
939
+ const requestId = this.createRequestId();
940
+ const message = SessionInboundMessageSchema.parse({
941
+ type: 'set_agent_mode_request',
942
+ agentId,
943
+ modeId,
944
+ requestId,
945
+ });
946
+ const payload = await this.sendRequest({
947
+ requestId,
948
+ message,
949
+ timeout: 15000,
950
+ options: { skipQueue: true },
951
+ select: (msg) => {
952
+ if (msg.type !== 'set_agent_mode_response') {
953
+ return null;
954
+ }
955
+ if (msg.payload.requestId !== requestId) {
956
+ return null;
957
+ }
958
+ return msg.payload;
959
+ },
960
+ });
961
+ if (!payload.accepted) {
962
+ throw new Error(payload.error ?? 'setAgentMode rejected');
963
+ }
964
+ }
965
+ async setAgentModel(agentId, modelId) {
966
+ const requestId = this.createRequestId();
967
+ const message = SessionInboundMessageSchema.parse({
968
+ type: 'set_agent_model_request',
969
+ agentId,
970
+ modelId,
971
+ requestId,
972
+ });
973
+ const payload = await this.sendRequest({
974
+ requestId,
975
+ message,
976
+ timeout: 15000,
977
+ options: { skipQueue: true },
978
+ select: (msg) => {
979
+ if (msg.type !== 'set_agent_model_response') {
980
+ return null;
981
+ }
982
+ if (msg.payload.requestId !== requestId) {
983
+ return null;
984
+ }
985
+ return msg.payload;
986
+ },
987
+ });
988
+ if (!payload.accepted) {
989
+ throw new Error(payload.error ?? 'setAgentModel rejected');
990
+ }
991
+ }
992
+ async setAgentThinkingOption(agentId, thinkingOptionId) {
993
+ const requestId = this.createRequestId();
994
+ const message = SessionInboundMessageSchema.parse({
995
+ type: 'set_agent_thinking_request',
996
+ agentId,
997
+ thinkingOptionId,
998
+ requestId,
999
+ });
1000
+ const payload = await this.sendRequest({
1001
+ requestId,
1002
+ message,
1003
+ timeout: 15000,
1004
+ options: { skipQueue: true },
1005
+ select: (msg) => {
1006
+ if (msg.type !== 'set_agent_thinking_response') {
1007
+ return null;
1008
+ }
1009
+ if (msg.payload.requestId !== requestId) {
1010
+ return null;
1011
+ }
1012
+ return msg.payload;
1013
+ },
1014
+ });
1015
+ if (!payload.accepted) {
1016
+ throw new Error(payload.error ?? 'setAgentThinkingOption rejected');
1017
+ }
1018
+ }
1019
+ async restartServer(reason, requestId) {
1020
+ const resolvedRequestId = this.createRequestId(requestId);
1021
+ const message = SessionInboundMessageSchema.parse({
1022
+ type: 'restart_server_request',
1023
+ ...(reason && reason.trim().length > 0 ? { reason } : {}),
1024
+ requestId: resolvedRequestId,
1025
+ });
1026
+ return this.sendRequest({
1027
+ requestId: resolvedRequestId,
1028
+ message,
1029
+ timeout: 10000,
1030
+ options: { skipQueue: true },
1031
+ select: (msg) => {
1032
+ if (msg.type !== 'status') {
1033
+ return null;
1034
+ }
1035
+ const restarted = RestartRequestedStatusPayloadSchema.safeParse(msg.payload);
1036
+ if (!restarted.success) {
1037
+ return null;
1038
+ }
1039
+ if (restarted.data.requestId !== resolvedRequestId) {
1040
+ return null;
1041
+ }
1042
+ return restarted.data;
1043
+ },
1044
+ });
1045
+ }
1046
+ async shutdownServer(requestId) {
1047
+ const resolvedRequestId = this.createRequestId(requestId);
1048
+ const message = SessionInboundMessageSchema.parse({
1049
+ type: 'shutdown_server_request',
1050
+ requestId: resolvedRequestId,
1051
+ });
1052
+ return this.sendRequest({
1053
+ requestId: resolvedRequestId,
1054
+ message,
1055
+ timeout: 10000,
1056
+ options: { skipQueue: true },
1057
+ select: (msg) => {
1058
+ if (msg.type !== 'status') {
1059
+ return null;
1060
+ }
1061
+ const shutdown = ShutdownRequestedStatusPayloadSchema.safeParse(msg.payload);
1062
+ if (!shutdown.success) {
1063
+ return null;
1064
+ }
1065
+ if (shutdown.data.requestId !== resolvedRequestId) {
1066
+ return null;
1067
+ }
1068
+ return shutdown.data;
1069
+ },
1070
+ });
1071
+ }
1072
+ async abortRequest() {
1073
+ this.sendSessionMessage({ type: 'abort_request' });
1074
+ }
1075
+ // ============================================================================
1076
+ // Git Operations
1077
+ // ============================================================================
1078
+ async getCheckoutStatus(cwd, options) {
1079
+ const requestId = options?.requestId;
1080
+ if (!requestId) {
1081
+ const existing = this.checkoutStatusInFlight.get(cwd);
1082
+ if (existing) {
1083
+ return existing;
1084
+ }
1085
+ }
1086
+ const resolvedRequestId = this.createRequestId(requestId);
1087
+ const message = SessionInboundMessageSchema.parse({
1088
+ type: 'checkout_status_request',
1089
+ cwd,
1090
+ requestId: resolvedRequestId,
1091
+ });
1092
+ const responsePromise = this.sendRequest({
1093
+ requestId: resolvedRequestId,
1094
+ message,
1095
+ timeout: 60000,
1096
+ options: { skipQueue: true },
1097
+ select: (msg) => {
1098
+ if (msg.type !== 'checkout_status_response') {
1099
+ return null;
1100
+ }
1101
+ if (msg.payload.requestId !== resolvedRequestId) {
1102
+ return null;
1103
+ }
1104
+ return msg.payload;
1105
+ },
1106
+ });
1107
+ if (!requestId) {
1108
+ this.checkoutStatusInFlight.set(cwd, responsePromise);
1109
+ void responsePromise
1110
+ .finally(() => {
1111
+ if (this.checkoutStatusInFlight.get(cwd) === responsePromise) {
1112
+ this.checkoutStatusInFlight.delete(cwd);
1113
+ }
1114
+ })
1115
+ .catch(() => undefined);
1116
+ }
1117
+ return responsePromise;
1118
+ }
1119
+ normalizeCheckoutDiffCompare(compare) {
1120
+ if (compare.mode === 'uncommitted' || compare.mode === 'staged' || compare.mode === 'unstaged') {
1121
+ return { mode: compare.mode };
1122
+ }
1123
+ const trimmedBaseRef = compare.baseRef?.trim();
1124
+ if (!trimmedBaseRef) {
1125
+ return { mode: 'base' };
1126
+ }
1127
+ return { mode: 'base', baseRef: trimmedBaseRef };
1128
+ }
1129
+ async getCheckoutDiff(cwd, compare, requestId) {
1130
+ const oneShotSubscriptionId = `oneshot-checkout-diff:${crypto.randomUUID()}`;
1131
+ try {
1132
+ const payload = await this.subscribeCheckoutDiff(cwd, compare, {
1133
+ subscriptionId: oneShotSubscriptionId,
1134
+ requestId,
1135
+ });
1136
+ return {
1137
+ cwd: payload.cwd,
1138
+ files: payload.files,
1139
+ error: payload.error,
1140
+ requestId: payload.requestId,
1141
+ };
1142
+ }
1143
+ finally {
1144
+ try {
1145
+ this.unsubscribeCheckoutDiff(oneShotSubscriptionId);
1146
+ }
1147
+ catch {
1148
+ // Ignore disconnect races during one-shot cleanup.
1149
+ }
1150
+ }
1151
+ }
1152
+ async subscribeCheckoutDiff(cwd, compare, options) {
1153
+ const subscriptionId = options?.subscriptionId ?? crypto.randomUUID();
1154
+ const normalizedCompare = this.normalizeCheckoutDiffCompare(compare);
1155
+ const previousSubscription = this.checkoutDiffSubscriptions.get(subscriptionId) ?? null;
1156
+ this.checkoutDiffSubscriptions.set(subscriptionId, {
1157
+ cwd,
1158
+ compare: normalizedCompare,
1159
+ });
1160
+ const resolvedRequestId = this.createRequestId(options?.requestId);
1161
+ const message = SessionInboundMessageSchema.parse({
1162
+ type: 'subscribe_checkout_diff_request',
1163
+ subscriptionId,
1164
+ cwd,
1165
+ compare: normalizedCompare,
1166
+ requestId: resolvedRequestId,
1167
+ });
1168
+ try {
1169
+ return await this.sendCorrelatedRequest({
1170
+ requestId: resolvedRequestId,
1171
+ message,
1172
+ responseType: 'subscribe_checkout_diff_response',
1173
+ timeout: 60000,
1174
+ options: { skipQueue: true },
1175
+ selectPayload: (payload) => {
1176
+ if (payload.subscriptionId !== subscriptionId) {
1177
+ return null;
1178
+ }
1179
+ return payload;
1180
+ },
1181
+ });
1182
+ }
1183
+ catch (error) {
1184
+ if (previousSubscription) {
1185
+ this.checkoutDiffSubscriptions.set(subscriptionId, previousSubscription);
1186
+ }
1187
+ else {
1188
+ this.checkoutDiffSubscriptions.delete(subscriptionId);
1189
+ }
1190
+ throw error;
1191
+ }
1192
+ }
1193
+ unsubscribeCheckoutDiff(subscriptionId) {
1194
+ this.checkoutDiffSubscriptions.delete(subscriptionId);
1195
+ this.sendSessionMessage({
1196
+ type: 'unsubscribe_checkout_diff_request',
1197
+ subscriptionId,
1198
+ });
1199
+ }
1200
+ async checkoutCommit(cwd, input, requestId) {
1201
+ return this.sendCorrelatedSessionRequest({
1202
+ requestId,
1203
+ message: {
1204
+ type: 'checkout_commit_request',
1205
+ cwd,
1206
+ message: input.message,
1207
+ addAll: input.addAll,
1208
+ },
1209
+ responseType: 'checkout_commit_response',
1210
+ timeout: 60000,
1211
+ });
1212
+ }
1213
+ async checkoutMerge(cwd, input, requestId) {
1214
+ return this.sendCorrelatedSessionRequest({
1215
+ requestId,
1216
+ message: {
1217
+ type: 'checkout_merge_request',
1218
+ cwd,
1219
+ baseRef: input.baseRef,
1220
+ strategy: input.strategy,
1221
+ requireCleanTarget: input.requireCleanTarget,
1222
+ },
1223
+ responseType: 'checkout_merge_response',
1224
+ timeout: 60000,
1225
+ });
1226
+ }
1227
+ async checkoutMergeFromBase(cwd, input, requestId) {
1228
+ return this.sendCorrelatedSessionRequest({
1229
+ requestId,
1230
+ message: {
1231
+ type: 'checkout_merge_from_base_request',
1232
+ cwd,
1233
+ baseRef: input.baseRef,
1234
+ requireCleanTarget: input.requireCleanTarget,
1235
+ },
1236
+ responseType: 'checkout_merge_from_base_response',
1237
+ timeout: 60000,
1238
+ });
1239
+ }
1240
+ async checkoutPush(cwd, requestId) {
1241
+ return this.sendCorrelatedSessionRequest({
1242
+ requestId,
1243
+ message: {
1244
+ type: 'checkout_push_request',
1245
+ cwd,
1246
+ },
1247
+ responseType: 'checkout_push_response',
1248
+ timeout: 60000,
1249
+ });
1250
+ }
1251
+ async checkoutPrCreate(cwd, input, requestId) {
1252
+ return this.sendCorrelatedSessionRequest({
1253
+ requestId,
1254
+ message: {
1255
+ type: 'checkout_pr_create_request',
1256
+ cwd,
1257
+ title: input.title,
1258
+ body: input.body,
1259
+ baseRef: input.baseRef,
1260
+ },
1261
+ responseType: 'checkout_pr_create_response',
1262
+ timeout: 60000,
1263
+ });
1264
+ }
1265
+ async checkoutPrStatus(cwd, requestId) {
1266
+ return this.sendCorrelatedSessionRequest({
1267
+ requestId,
1268
+ message: {
1269
+ type: 'checkout_pr_status_request',
1270
+ cwd,
1271
+ },
1272
+ responseType: 'checkout_pr_status_response',
1273
+ timeout: 60000,
1274
+ });
1275
+ }
1276
+ async getJunctionWorktreeList(input, requestId) {
1277
+ return this.sendCorrelatedSessionRequest({
1278
+ requestId,
1279
+ message: {
1280
+ type: 'junction_worktree_list_request',
1281
+ cwd: input.cwd,
1282
+ repoRoot: input.repoRoot,
1283
+ },
1284
+ responseType: 'junction_worktree_list_response',
1285
+ timeout: 60000,
1286
+ });
1287
+ }
1288
+ async archiveJunctionWorktree(input, requestId) {
1289
+ return this.sendCorrelatedSessionRequest({
1290
+ requestId,
1291
+ message: {
1292
+ type: 'junction_worktree_archive_request',
1293
+ worktreePath: input.worktreePath,
1294
+ repoRoot: input.repoRoot,
1295
+ branchName: input.branchName,
1296
+ },
1297
+ responseType: 'junction_worktree_archive_response',
1298
+ timeout: 20000,
1299
+ });
1300
+ }
1301
+ async validateBranch(options, requestId) {
1302
+ return this.sendCorrelatedSessionRequest({
1303
+ requestId,
1304
+ message: {
1305
+ type: 'validate_branch_request',
1306
+ cwd: options.cwd,
1307
+ branchName: options.branchName,
1308
+ },
1309
+ responseType: 'validate_branch_response',
1310
+ timeout: 10000,
1311
+ });
1312
+ }
1313
+ async getBranchSuggestions(options, requestId) {
1314
+ return this.sendCorrelatedSessionRequest({
1315
+ requestId,
1316
+ message: {
1317
+ type: 'branch_suggestions_request',
1318
+ cwd: options.cwd,
1319
+ query: options.query,
1320
+ limit: options.limit,
1321
+ },
1322
+ responseType: 'branch_suggestions_response',
1323
+ timeout: 10000,
1324
+ });
1325
+ }
1326
+ async getDirectorySuggestions(options, requestId) {
1327
+ return this.sendCorrelatedSessionRequest({
1328
+ requestId,
1329
+ message: {
1330
+ type: 'directory_suggestions_request',
1331
+ query: options.query,
1332
+ cwd: options.cwd,
1333
+ includeFiles: options.includeFiles,
1334
+ includeDirectories: options.includeDirectories,
1335
+ onlyGitRepos: options.onlyGitRepos,
1336
+ limit: options.limit,
1337
+ },
1338
+ responseType: 'directory_suggestions_response',
1339
+ timeout: 10000,
1340
+ });
1341
+ }
1342
+ // ============================================================================
1343
+ // Git Operations
1344
+ // ============================================================================
1345
+ async gitClone(options, requestId) {
1346
+ return this.sendCorrelatedSessionRequest({
1347
+ requestId,
1348
+ message: {
1349
+ type: 'git_clone_request',
1350
+ url: options.url,
1351
+ targetDirectory: options.targetDirectory,
1352
+ },
1353
+ responseType: 'git_clone_response',
1354
+ timeout: 180000,
1355
+ });
1356
+ }
1357
+ async gitInit(options, requestId) {
1358
+ return this.sendCorrelatedSessionRequest({
1359
+ requestId,
1360
+ message: {
1361
+ type: 'git_init_request',
1362
+ targetDirectory: options.targetDirectory,
1363
+ projectName: options.projectName,
1364
+ },
1365
+ responseType: 'git_init_response',
1366
+ timeout: 15000,
1367
+ });
1368
+ }
1369
+ // ============================================================================
1370
+ // File Explorer
1371
+ // ============================================================================
1372
+ async exploreFileSystem(agentId, path, mode = 'list', requestId) {
1373
+ return this.sendCorrelatedSessionRequest({
1374
+ requestId,
1375
+ message: {
1376
+ type: 'file_explorer_request',
1377
+ agentId,
1378
+ path,
1379
+ mode,
1380
+ },
1381
+ responseType: 'file_explorer_response',
1382
+ timeout: 10000,
1383
+ });
1384
+ }
1385
+ async exploreWorkspaceFileSystem(options, requestId) {
1386
+ return this.sendCorrelatedSessionRequest({
1387
+ requestId,
1388
+ message: {
1389
+ type: 'workspace_file_explorer_request',
1390
+ cwd: options.cwd,
1391
+ path: options.path,
1392
+ mode: options.mode ?? 'list',
1393
+ },
1394
+ responseType: 'workspace_file_explorer_response',
1395
+ timeout: 10000,
1396
+ });
1397
+ }
1398
+ async requestDownloadToken(agentId, path, requestId) {
1399
+ return this.sendCorrelatedSessionRequest({
1400
+ requestId,
1401
+ message: {
1402
+ type: 'file_download_token_request',
1403
+ agentId,
1404
+ path,
1405
+ },
1406
+ responseType: 'file_download_token_response',
1407
+ timeout: 10000,
1408
+ });
1409
+ }
1410
+ async requestProjectIcon(cwd, requestId) {
1411
+ return this.sendCorrelatedSessionRequest({
1412
+ requestId,
1413
+ message: {
1414
+ type: 'project_icon_request',
1415
+ cwd,
1416
+ },
1417
+ responseType: 'project_icon_response',
1418
+ timeout: 10000,
1419
+ });
1420
+ }
1421
+ // ============================================================================
1422
+ // Provider Models / Commands
1423
+ // ============================================================================
1424
+ async listProviderModels(provider, options) {
1425
+ return this.sendCorrelatedSessionRequest({
1426
+ requestId: options?.requestId,
1427
+ message: {
1428
+ type: 'list_provider_models_request',
1429
+ provider,
1430
+ cwd: options?.cwd,
1431
+ },
1432
+ responseType: 'list_provider_models_response',
1433
+ // Provider SDK cold starts (especially model discovery) can exceed 30s.
1434
+ timeout: 45000,
1435
+ });
1436
+ }
1437
+ async listAvailableProviders(options) {
1438
+ return this.sendCorrelatedSessionRequest({
1439
+ requestId: options?.requestId,
1440
+ message: {
1441
+ type: 'list_available_providers_request',
1442
+ },
1443
+ responseType: 'list_available_providers_response',
1444
+ timeout: 30000,
1445
+ });
1446
+ }
1447
+ async listCommands(agentId, requestIdOrOptions) {
1448
+ const requestId = typeof requestIdOrOptions === 'string' ? requestIdOrOptions : requestIdOrOptions?.requestId;
1449
+ const draftConfig = typeof requestIdOrOptions === 'string' ? undefined : requestIdOrOptions?.draftConfig;
1450
+ return this.sendCorrelatedSessionRequest({
1451
+ requestId,
1452
+ message: {
1453
+ type: 'list_commands_request',
1454
+ agentId,
1455
+ ...(draftConfig ? { draftConfig } : {}),
1456
+ },
1457
+ responseType: 'list_commands_response',
1458
+ timeout: 30000,
1459
+ });
1460
+ }
1461
+ // ============================================================================
1462
+ // Permissions
1463
+ // ============================================================================
1464
+ async respondToPermission(agentId, requestId, response) {
1465
+ this.sendSessionMessage({
1466
+ type: 'agent_permission_response',
1467
+ agentId,
1468
+ requestId,
1469
+ response,
1470
+ });
1471
+ }
1472
+ async respondToPermissionAndWait(agentId, requestId, response, timeout = 15000) {
1473
+ const message = SessionInboundMessageSchema.parse({
1474
+ type: 'agent_permission_response',
1475
+ agentId,
1476
+ requestId,
1477
+ response,
1478
+ });
1479
+ return this.sendRequest({
1480
+ requestId,
1481
+ message,
1482
+ timeout,
1483
+ options: { skipQueue: true },
1484
+ select: (msg) => {
1485
+ if (msg.type !== 'agent_permission_resolved') {
1486
+ return null;
1487
+ }
1488
+ if (msg.payload.requestId !== requestId) {
1489
+ return null;
1490
+ }
1491
+ if (msg.payload.agentId !== agentId) {
1492
+ return null;
1493
+ }
1494
+ return msg.payload;
1495
+ },
1496
+ });
1497
+ }
1498
+ // ============================================================================
1499
+ // Waiting / Streaming Helpers
1500
+ // ============================================================================
1501
+ async waitForAgentUpsert(agentId, predicate, timeout = 60000) {
1502
+ const initialResult = await this.fetchAgent(agentId).catch(() => null);
1503
+ if (initialResult && predicate(initialResult.agent)) {
1504
+ return initialResult.agent;
1505
+ }
1506
+ const deadline = Date.now() + timeout;
1507
+ return await new Promise((resolve, reject) => {
1508
+ let settled = false;
1509
+ let pollInFlight = false;
1510
+ let pollTimer = null;
1511
+ let timeoutTimer = null;
1512
+ let unsubscribe = null;
1513
+ const finish = (result) => {
1514
+ if (settled) {
1515
+ return;
1516
+ }
1517
+ settled = true;
1518
+ if (timeoutTimer) {
1519
+ clearTimeout(timeoutTimer);
1520
+ timeoutTimer = null;
1521
+ }
1522
+ if (pollTimer) {
1523
+ clearInterval(pollTimer);
1524
+ pollTimer = null;
1525
+ }
1526
+ if (unsubscribe) {
1527
+ unsubscribe();
1528
+ unsubscribe = null;
1529
+ }
1530
+ if (result.kind === 'ok') {
1531
+ resolve(result.snapshot);
1532
+ return;
1533
+ }
1534
+ reject(result.error);
1535
+ };
1536
+ const maybeResolve = (snapshot) => {
1537
+ if (!snapshot) {
1538
+ return false;
1539
+ }
1540
+ if (!predicate(snapshot)) {
1541
+ return false;
1542
+ }
1543
+ finish({ kind: 'ok', snapshot });
1544
+ return true;
1545
+ };
1546
+ const poll = async () => {
1547
+ if (settled || pollInFlight) {
1548
+ return;
1549
+ }
1550
+ pollInFlight = true;
1551
+ try {
1552
+ const result = await this.fetchAgent(agentId).catch(() => null);
1553
+ maybeResolve(result?.agent ?? null);
1554
+ }
1555
+ finally {
1556
+ pollInFlight = false;
1557
+ }
1558
+ };
1559
+ unsubscribe = this.on('agent_update', (message) => {
1560
+ if (settled) {
1561
+ return;
1562
+ }
1563
+ if (message.type !== 'agent_update') {
1564
+ return;
1565
+ }
1566
+ if (message.payload.kind !== 'upsert') {
1567
+ return;
1568
+ }
1569
+ const snapshot = message.payload.agent;
1570
+ if (snapshot.id !== agentId) {
1571
+ return;
1572
+ }
1573
+ maybeResolve(snapshot);
1574
+ });
1575
+ const remaining = Math.max(1, deadline - Date.now());
1576
+ timeoutTimer = setTimeout(() => {
1577
+ finish({
1578
+ kind: 'error',
1579
+ error: new Error(`Timed out waiting for agent ${agentId}`),
1580
+ });
1581
+ }, remaining);
1582
+ pollTimer = setInterval(() => {
1583
+ void poll();
1584
+ }, 250);
1585
+ void poll();
1586
+ });
1587
+ }
1588
+ async waitForFinish(agentId, timeout = 60000) {
1589
+ const requestId = this.createRequestId();
1590
+ const hasTimeout = Number.isFinite(timeout) && timeout > 0;
1591
+ const message = SessionInboundMessageSchema.parse({
1592
+ type: 'wait_for_finish_request',
1593
+ requestId,
1594
+ agentId,
1595
+ ...(hasTimeout ? { timeoutMs: timeout } : {}),
1596
+ });
1597
+ const payload = await this.sendCorrelatedRequest({
1598
+ requestId,
1599
+ message,
1600
+ responseType: 'wait_for_finish_response',
1601
+ timeout: hasTimeout ? timeout + 5000 : 0,
1602
+ options: { skipQueue: true },
1603
+ });
1604
+ return {
1605
+ status: payload.status,
1606
+ final: payload.final,
1607
+ error: payload.error,
1608
+ lastMessage: payload.lastMessage,
1609
+ };
1610
+ }
1611
+ // ============================================================================
1612
+ // Terminals
1613
+ // ============================================================================
1614
+ subscribeTerminals(input) {
1615
+ this.terminalDirectorySubscriptions.add(input.cwd);
1616
+ if (!this.transport || this.connectionState.status !== 'connected') {
1617
+ return;
1618
+ }
1619
+ this.sendSessionMessage({
1620
+ type: 'subscribe_terminals_request',
1621
+ cwd: input.cwd,
1622
+ });
1623
+ }
1624
+ unsubscribeTerminals(input) {
1625
+ this.terminalDirectorySubscriptions.delete(input.cwd);
1626
+ if (!this.transport || this.connectionState.status !== 'connected') {
1627
+ return;
1628
+ }
1629
+ this.sendSessionMessage({
1630
+ type: 'unsubscribe_terminals_request',
1631
+ cwd: input.cwd,
1632
+ });
1633
+ }
1634
+ async listTerminals(cwd, requestId) {
1635
+ const resolvedRequestId = this.createRequestId(requestId);
1636
+ const message = SessionInboundMessageSchema.parse({
1637
+ type: 'list_terminals_request',
1638
+ cwd,
1639
+ requestId: resolvedRequestId,
1640
+ });
1641
+ return this.sendCorrelatedRequest({
1642
+ requestId: resolvedRequestId,
1643
+ message,
1644
+ responseType: 'list_terminals_response',
1645
+ timeout: 10000,
1646
+ options: { skipQueue: true },
1647
+ });
1648
+ }
1649
+ async createTerminal(cwd, name, requestId) {
1650
+ const resolvedRequestId = this.createRequestId(requestId);
1651
+ const message = SessionInboundMessageSchema.parse({
1652
+ type: 'create_terminal_request',
1653
+ cwd,
1654
+ name,
1655
+ requestId: resolvedRequestId,
1656
+ });
1657
+ return this.sendCorrelatedRequest({
1658
+ requestId: resolvedRequestId,
1659
+ message,
1660
+ responseType: 'create_terminal_response',
1661
+ timeout: 10000,
1662
+ options: { skipQueue: true },
1663
+ });
1664
+ }
1665
+ async subscribeTerminal(terminalId, requestId) {
1666
+ const resolvedRequestId = this.createRequestId(requestId);
1667
+ const message = SessionInboundMessageSchema.parse({
1668
+ type: 'subscribe_terminal_request',
1669
+ terminalId,
1670
+ requestId: resolvedRequestId,
1671
+ });
1672
+ return this.sendCorrelatedRequest({
1673
+ requestId: resolvedRequestId,
1674
+ message,
1675
+ responseType: 'subscribe_terminal_response',
1676
+ timeout: 10000,
1677
+ options: { skipQueue: true },
1678
+ });
1679
+ }
1680
+ unsubscribeTerminal(terminalId) {
1681
+ this.sendSessionMessage({
1682
+ type: 'unsubscribe_terminal_request',
1683
+ terminalId,
1684
+ });
1685
+ }
1686
+ sendTerminalInput(terminalId, message) {
1687
+ this.sendSessionMessage({
1688
+ type: 'terminal_input',
1689
+ terminalId,
1690
+ message,
1691
+ });
1692
+ }
1693
+ async killTerminal(terminalId, requestId) {
1694
+ const resolvedRequestId = this.createRequestId(requestId);
1695
+ const message = SessionInboundMessageSchema.parse({
1696
+ type: 'kill_terminal_request',
1697
+ terminalId,
1698
+ requestId: resolvedRequestId,
1699
+ });
1700
+ return this.sendCorrelatedRequest({
1701
+ requestId: resolvedRequestId,
1702
+ message,
1703
+ responseType: 'kill_terminal_response',
1704
+ timeout: 10000,
1705
+ options: { skipQueue: true },
1706
+ });
1707
+ }
1708
+ async attachTerminalStream(terminalId, options, requestId) {
1709
+ const resolvedRequestId = this.createRequestId(requestId);
1710
+ const message = SessionInboundMessageSchema.parse({
1711
+ type: 'attach_terminal_stream_request',
1712
+ terminalId,
1713
+ requestId: resolvedRequestId,
1714
+ ...(options?.resumeOffset !== undefined ? { resumeOffset: options.resumeOffset } : {}),
1715
+ ...(options?.rows !== undefined ? { rows: options.rows } : {}),
1716
+ ...(options?.cols !== undefined ? { cols: options.cols } : {}),
1717
+ });
1718
+ return this.sendCorrelatedRequest({
1719
+ requestId: resolvedRequestId,
1720
+ message,
1721
+ responseType: 'attach_terminal_stream_response',
1722
+ timeout: 10000,
1723
+ options: { skipQueue: true },
1724
+ });
1725
+ }
1726
+ async detachTerminalStream(streamId, requestId) {
1727
+ const resolvedRequestId = this.createRequestId(requestId);
1728
+ const message = SessionInboundMessageSchema.parse({
1729
+ type: 'detach_terminal_stream_request',
1730
+ streamId,
1731
+ requestId: resolvedRequestId,
1732
+ });
1733
+ const payload = await this.sendCorrelatedRequest({
1734
+ requestId: resolvedRequestId,
1735
+ message,
1736
+ responseType: 'detach_terminal_stream_response',
1737
+ timeout: 10000,
1738
+ options: { skipQueue: true },
1739
+ });
1740
+ this.terminalStreams.clearStream({ streamId });
1741
+ return payload;
1742
+ }
1743
+ onTerminalStreamData(streamId, handler) {
1744
+ return this.terminalStreams.subscribe({ streamId, handler });
1745
+ }
1746
+ async waitForTerminalStreamData(streamId, predicate, timeout = 5000) {
1747
+ return new Promise((resolve, reject) => {
1748
+ const timeoutHandle = setTimeout(() => {
1749
+ unsubscribe();
1750
+ reject(new Error(`Timeout waiting for terminal stream data (${timeout}ms)`));
1751
+ }, timeout);
1752
+ const unsubscribe = this.onTerminalStreamData(streamId, (chunk) => {
1753
+ if (!predicate(chunk)) {
1754
+ return;
1755
+ }
1756
+ clearTimeout(timeoutHandle);
1757
+ unsubscribe();
1758
+ resolve(chunk);
1759
+ });
1760
+ });
1761
+ }
1762
+ sendTerminalStreamInput(streamId, data) {
1763
+ const payload = typeof data === 'string' ? encodeUtf8String(data) : data;
1764
+ this.sendBinaryFrame({
1765
+ channel: BinaryMuxChannel.Terminal,
1766
+ messageType: TerminalBinaryMessageType.InputUtf8,
1767
+ streamId,
1768
+ offset: 0,
1769
+ payload,
1770
+ });
1771
+ }
1772
+ sendTerminalStreamKey(streamId, input) {
1773
+ const encoded = encodeTerminalKeyInput(input);
1774
+ if (!encoded) {
1775
+ return;
1776
+ }
1777
+ this.sendTerminalStreamInput(streamId, encoded);
1778
+ }
1779
+ sendTerminalStreamAck(streamId, offset) {
1780
+ const normalizedOffset = Math.max(0, Math.floor(offset));
1781
+ this.terminalStreams.noteAck({ streamId, offset: normalizedOffset });
1782
+ this.sendBinaryFrame({
1783
+ channel: BinaryMuxChannel.Terminal,
1784
+ messageType: TerminalBinaryMessageType.Ack,
1785
+ streamId,
1786
+ offset: normalizedOffset,
1787
+ payload: new Uint8Array(0),
1788
+ });
1789
+ }
1790
+ async waitForTerminalOutput(terminalId, timeout = 5000) {
1791
+ return this.waitFor((msg) => {
1792
+ if (msg.type !== 'terminal_output') {
1793
+ return null;
1794
+ }
1795
+ if (msg.payload.terminalId !== terminalId) {
1796
+ return null;
1797
+ }
1798
+ return msg.payload;
1799
+ }, timeout, { skipQueue: true });
1800
+ }
1801
+ // ============================================================================
1802
+ // Internals
1803
+ // ============================================================================
1804
+ createRequestId(requestId) {
1805
+ return requestId ?? crypto.randomUUID();
1806
+ }
1807
+ getLastWelcomeMessage() {
1808
+ return this.lastWelcomeMessage;
1809
+ }
1810
+ resolveTransportUrlForAttempt() {
1811
+ return this.config.url;
1812
+ }
1813
+ sendHelloMessage() {
1814
+ if (!this.transport) {
1815
+ this.scheduleReconnect({
1816
+ reason: 'Transport unavailable before hello',
1817
+ event: 'HELLO_TRANSPORT_MISSING',
1818
+ reasonCode: 'transport_error',
1819
+ });
1820
+ return;
1821
+ }
1822
+ try {
1823
+ const hello = {
1824
+ type: 'hello',
1825
+ clientId: this.config.clientId,
1826
+ clientType: this.config.clientType ?? 'cli',
1827
+ protocolVersion: 1,
1828
+ };
1829
+ if (this.config.token) {
1830
+ hello.token = this.config.token;
1831
+ }
1832
+ this.transport.send(JSON.stringify(hello));
1833
+ }
1834
+ catch (error) {
1835
+ const message = error instanceof Error ? error.message : 'Failed to send hello message';
1836
+ this.lastErrorValue = message;
1837
+ this.scheduleReconnect({
1838
+ reason: message,
1839
+ event: 'HELLO_SEND_FAILED',
1840
+ reasonCode: 'transport_error',
1841
+ });
1842
+ }
1843
+ }
1844
+ disposeTransport(code = 1001, reason = 'Reconnecting') {
1845
+ this.cleanupTransport();
1846
+ if (this.transport) {
1847
+ try {
1848
+ this.transport.close(code, reason);
1849
+ }
1850
+ catch {
1851
+ // no-op
1852
+ }
1853
+ this.transport = null;
1854
+ }
1855
+ }
1856
+ cleanupTransport() {
1857
+ this.resetConnectTimeout();
1858
+ if (this.pendingGenericTransportErrorTimeout) {
1859
+ clearTimeout(this.pendingGenericTransportErrorTimeout);
1860
+ this.pendingGenericTransportErrorTimeout = null;
1861
+ }
1862
+ for (const cleanup of this.transportCleanup) {
1863
+ try {
1864
+ cleanup();
1865
+ }
1866
+ catch {
1867
+ // no-op
1868
+ }
1869
+ }
1870
+ this.transportCleanup = [];
1871
+ }
1872
+ resetConnectTimeout() {
1873
+ if (!this.connectTimeout) {
1874
+ return;
1875
+ }
1876
+ clearTimeout(this.connectTimeout);
1877
+ this.connectTimeout = null;
1878
+ }
1879
+ handleTransportMessage(data) {
1880
+ const startedAtMs = getNowMs();
1881
+ const rawData = data && typeof data === 'object' && 'data' in data ? data.data : data;
1882
+ const rawBytes = asUint8Array(rawData);
1883
+ if (rawBytes) {
1884
+ const frame = decodeBinaryMuxFrame(rawBytes);
1885
+ if (frame) {
1886
+ this.emitDiagnosticsEvent({
1887
+ type: 'transport_binary_frame',
1888
+ channel: frame.channel,
1889
+ messageType: frame.messageType,
1890
+ payloadBytes: frame.payload?.byteLength ?? 0,
1891
+ });
1892
+ this.handleBinaryFrame(frame);
1893
+ return;
1894
+ }
1895
+ }
1896
+ const payload = decodeMessageData(rawData);
1897
+ if (!payload) {
1898
+ return;
1899
+ }
1900
+ let parsedJson;
1901
+ let parseMs = 0;
1902
+ try {
1903
+ const parseStartedAtMs = getNowMs();
1904
+ parsedJson = JSON.parse(payload);
1905
+ parseMs = getNowMs() - parseStartedAtMs;
1906
+ }
1907
+ catch {
1908
+ this.emitDiagnosticsEvent({
1909
+ type: 'transport_message_timing',
1910
+ messageType: 'unknown',
1911
+ payloadBytes: payload.length,
1912
+ parseMs: 0,
1913
+ validateMs: 0,
1914
+ totalMs: getNowMs() - startedAtMs,
1915
+ outcome: 'parse_error',
1916
+ });
1917
+ return;
1918
+ }
1919
+ const validateStartedAtMs = getNowMs();
1920
+ const parsed = WSOutboundMessageSchema.safeParse(parsedJson);
1921
+ const validateMs = getNowMs() - validateStartedAtMs;
1922
+ if (!parsed.success) {
1923
+ const msgType = parsedJson?.type ?? 'unknown';
1924
+ this.emitDiagnosticsEvent({
1925
+ type: 'transport_message_timing',
1926
+ messageType: msgType,
1927
+ payloadBytes: payload.length,
1928
+ parseMs,
1929
+ validateMs,
1930
+ totalMs: getNowMs() - startedAtMs,
1931
+ outcome: 'validation_error',
1932
+ });
1933
+ this.logger.warn({ msgType, error: parsed.error.message }, 'Message validation failed');
1934
+ return;
1935
+ }
1936
+ this.emitDiagnosticsEvent({
1937
+ type: 'transport_message_timing',
1938
+ messageType: parsed.data.type,
1939
+ payloadBytes: payload.length,
1940
+ parseMs,
1941
+ validateMs,
1942
+ totalMs: getNowMs() - startedAtMs,
1943
+ outcome: 'ok',
1944
+ });
1945
+ if (parsed.data.type === 'pong') {
1946
+ return;
1947
+ }
1948
+ if (parsed.data.type === 'welcome') {
1949
+ this.lastWelcomeMessage = parsed.data;
1950
+ this.resetConnectTimeout();
1951
+ this.reconnectAttempt = 0;
1952
+ this.updateConnectionState({ status: 'connected' }, { event: 'HELLO_WELCOME' });
1953
+ this.resubscribeCheckoutDiffSubscriptions();
1954
+ this.resubscribeTerminalDirectorySubscriptions();
1955
+ this.flushPendingSendQueue();
1956
+ this.resolveConnect();
1957
+ return;
1958
+ }
1959
+ this.handleSessionMessage(parsed.data.message);
1960
+ }
1961
+ handleBinaryFrame(frame) {
1962
+ if (frame.channel === BinaryMuxChannel.Terminal &&
1963
+ frame.messageType === TerminalBinaryMessageType.OutputUtf8) {
1964
+ const chunk = {
1965
+ streamId: frame.streamId,
1966
+ offset: frame.offset,
1967
+ endOffset: frame.offset + (frame.payload?.byteLength ?? 0),
1968
+ replay: Boolean((frame.flags ?? 0) & TerminalBinaryFlags.Replay),
1969
+ data: frame.payload ?? new Uint8Array(0),
1970
+ };
1971
+ this.terminalStreams.receiveChunk({ chunk });
1972
+ return;
1973
+ }
1974
+ }
1975
+ updateConnectionState(next, metadata) {
1976
+ const previous = this.connectionState;
1977
+ this.connectionState = next;
1978
+ const reasonFromNext = next.status === 'disconnected' && typeof next.reason === 'string'
1979
+ ? next.reason
1980
+ : null;
1981
+ const reason = metadata?.reason ?? reasonFromNext;
1982
+ const reasonCode = metadata?.reasonCode ?? toReasonCode(reason);
1983
+ this.logger.debug({
1984
+ serverId: this.logServerId,
1985
+ clientIdHash: this.logClientIdHash,
1986
+ from: previous.status,
1987
+ to: next.status,
1988
+ event: metadata?.event ?? 'STATE_UPDATE',
1989
+ connectionPath: this.logConnectionPath,
1990
+ generation: this.logGeneration,
1991
+ reasonCode,
1992
+ reason,
1993
+ }, 'DaemonClientTransition');
1994
+ for (const listener of this.connectionListeners) {
1995
+ try {
1996
+ listener(next);
1997
+ }
1998
+ catch {
1999
+ // no-op
2000
+ }
2001
+ }
2002
+ }
2003
+ scheduleReconnect(input) {
2004
+ if (this.reconnectTimeout) {
2005
+ clearTimeout(this.reconnectTimeout);
2006
+ this.reconnectTimeout = null;
2007
+ }
2008
+ const wasDisposed = this.connectionState.status === 'disposed';
2009
+ const reason = input?.reason;
2010
+ if (typeof reason === 'string' && reason.trim().length > 0) {
2011
+ this.lastErrorValue = reason.trim();
2012
+ }
2013
+ // Clear all pending waiters and queued sends since the connection was lost
2014
+ // and responses from the previous connection will never arrive.
2015
+ this.clearWaiters(new Error(reason ?? 'Connection lost'));
2016
+ this.rejectPendingSendQueue(new Error(reason ?? 'Connection lost'));
2017
+ this.terminalStreams.clearAll();
2018
+ this.lastWelcomeMessage = null;
2019
+ if (wasDisposed) {
2020
+ this.rejectConnect(new Error(reason ?? 'Daemon client is disposed'));
2021
+ return;
2022
+ }
2023
+ this.updateConnectionState({
2024
+ status: 'disconnected',
2025
+ ...(reason ? { reason } : {}),
2026
+ }, {
2027
+ event: input?.event ?? 'TRANSPORT_CLOSE',
2028
+ ...(reason ? { reason } : {}),
2029
+ ...(input?.reasonCode ? { reasonCode: input.reasonCode } : {}),
2030
+ });
2031
+ if (!this.shouldReconnect || this.config.reconnect?.enabled === false) {
2032
+ this.rejectConnect(new Error(reason ?? 'Transport disconnected before connect'));
2033
+ return;
2034
+ }
2035
+ const attempt = this.reconnectAttempt;
2036
+ const baseDelay = this.config.reconnect?.baseDelayMs ?? DEFAULT_RECONNECT_BASE_DELAY_MS;
2037
+ const maxDelay = this.config.reconnect?.maxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
2038
+ const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
2039
+ this.reconnectAttempt = attempt + 1;
2040
+ this.reconnectTimeout = setTimeout(() => {
2041
+ this.reconnectTimeout = null;
2042
+ if (!this.shouldReconnect) {
2043
+ return;
2044
+ }
2045
+ this.attemptConnect();
2046
+ }, delay);
2047
+ }
2048
+ handleSessionMessage(msg) {
2049
+ if (msg.type === 'terminal_stream_exit') {
2050
+ this.terminalStreams.clearStream({ streamId: msg.payload.streamId });
2051
+ }
2052
+ if (this.rawMessageListeners.size > 0) {
2053
+ for (const handler of this.rawMessageListeners) {
2054
+ try {
2055
+ handler(msg);
2056
+ }
2057
+ catch {
2058
+ // no-op
2059
+ }
2060
+ }
2061
+ }
2062
+ const handlers = this.messageHandlers.get(msg.type);
2063
+ if (handlers) {
2064
+ for (const handler of handlers) {
2065
+ try {
2066
+ handler(msg);
2067
+ }
2068
+ catch {
2069
+ // no-op
2070
+ }
2071
+ }
2072
+ }
2073
+ const event = this.toEvent(msg);
2074
+ if (event) {
2075
+ for (const handler of this.eventListeners) {
2076
+ handler(event);
2077
+ }
2078
+ }
2079
+ this.resolveWaiters(msg);
2080
+ }
2081
+ resolveWaiters(msg) {
2082
+ for (const waiter of Array.from(this.waiters)) {
2083
+ const result = waiter.predicate(msg);
2084
+ if (result !== null) {
2085
+ this.waiters.delete(waiter);
2086
+ if (waiter.timeoutHandle) {
2087
+ clearTimeout(waiter.timeoutHandle);
2088
+ }
2089
+ waiter.resolve(result);
2090
+ }
2091
+ }
2092
+ }
2093
+ clearWaiters(error) {
2094
+ for (const waiter of Array.from(this.waiters)) {
2095
+ if (waiter.timeoutHandle) {
2096
+ clearTimeout(waiter.timeoutHandle);
2097
+ }
2098
+ waiter.reject(error);
2099
+ }
2100
+ this.waiters.clear();
2101
+ }
2102
+ toEvent(msg) {
2103
+ switch (msg.type) {
2104
+ case 'agent_update':
2105
+ return {
2106
+ type: 'agent_update',
2107
+ agentId: msg.payload.kind === 'upsert' ? msg.payload.agent.id : msg.payload.agentId,
2108
+ payload: msg.payload,
2109
+ };
2110
+ case 'agent_stream':
2111
+ return {
2112
+ type: 'agent_stream',
2113
+ agentId: msg.payload.agentId,
2114
+ event: msg.payload.event,
2115
+ timestamp: msg.payload.timestamp,
2116
+ ...(typeof msg.payload.seq === 'number' ? { seq: msg.payload.seq } : {}),
2117
+ ...(typeof msg.payload.epoch === 'string' ? { epoch: msg.payload.epoch } : {}),
2118
+ };
2119
+ case 'status':
2120
+ return { type: 'status', payload: msg.payload };
2121
+ case 'agent_deleted':
2122
+ return { type: 'agent_deleted', agentId: msg.payload.agentId };
2123
+ case 'agent_permission_request':
2124
+ return {
2125
+ type: 'agent_permission_request',
2126
+ agentId: msg.payload.agentId,
2127
+ request: msg.payload.request,
2128
+ };
2129
+ case 'agent_permission_resolved':
2130
+ return {
2131
+ type: 'agent_permission_resolved',
2132
+ agentId: msg.payload.agentId,
2133
+ requestId: msg.payload.requestId,
2134
+ resolution: msg.payload.resolution,
2135
+ };
2136
+ default:
2137
+ return null;
2138
+ }
2139
+ }
2140
+ async waitFor(predicate, timeout = 30000, _options) {
2141
+ return this.waitForWithCancel(predicate, timeout, _options).promise;
2142
+ }
2143
+ waitForWithCancel(predicate, timeout = 30000, _options) {
2144
+ // Capture stack trace at call site, not inside setTimeout
2145
+ const timeoutError = new Error(`Timeout waiting for message (${timeout}ms)`);
2146
+ let waiter = null;
2147
+ let settled = false;
2148
+ let rejectFn = null;
2149
+ const promise = new Promise((resolve, reject) => {
2150
+ const wrappedResolve = (value) => {
2151
+ if (settled)
2152
+ return;
2153
+ settled = true;
2154
+ resolve(value);
2155
+ };
2156
+ const wrappedReject = (error) => {
2157
+ if (settled)
2158
+ return;
2159
+ settled = true;
2160
+ reject(error);
2161
+ };
2162
+ rejectFn = wrappedReject;
2163
+ const timeoutHandle = timeout > 0
2164
+ ? setTimeout(() => {
2165
+ if (waiter) {
2166
+ this.waiters.delete(waiter);
2167
+ }
2168
+ wrappedReject(timeoutError);
2169
+ }, timeout)
2170
+ : null;
2171
+ waiter = {
2172
+ predicate,
2173
+ resolve: wrappedResolve,
2174
+ reject: wrappedReject,
2175
+ timeoutHandle,
2176
+ };
2177
+ this.waiters.add(waiter);
2178
+ });
2179
+ const cancel = (error) => {
2180
+ if (settled) {
2181
+ return;
2182
+ }
2183
+ if (waiter) {
2184
+ this.waiters.delete(waiter);
2185
+ if (waiter.timeoutHandle) {
2186
+ clearTimeout(waiter.timeoutHandle);
2187
+ }
2188
+ }
2189
+ if (rejectFn) {
2190
+ rejectFn(error);
2191
+ return;
2192
+ }
2193
+ // Extremely unlikely: cancel called before the Promise executor ran.
2194
+ queueMicrotask(() => {
2195
+ if (!settled && rejectFn) {
2196
+ rejectFn(error);
2197
+ }
2198
+ });
2199
+ };
2200
+ return { promise, cancel };
2201
+ }
2202
+ }
2203
+ function resolveAgentConfig(options) {
2204
+ const { config, provider, cwd, initialPrompt: _initialPrompt, images: _images, git: _git, worktreeName: _worktreeName, requestId: _requestId, labels: _labels, ...overrides } = options;
2205
+ const baseConfig = {
2206
+ ...(provider ? { provider } : {}),
2207
+ ...(cwd ? { cwd } : {}),
2208
+ ...overrides,
2209
+ };
2210
+ const merged = config ? { ...baseConfig, ...config } : baseConfig;
2211
+ if (!merged.provider || !merged.cwd) {
2212
+ throw new Error('createAgent requires provider and cwd');
2213
+ }
2214
+ if (!merged.modeId) {
2215
+ merged.modeId = getAgentProviderDefinition(merged.provider).defaultModeId ?? undefined;
2216
+ }
2217
+ return {
2218
+ ...merged,
2219
+ provider: merged.provider,
2220
+ cwd: merged.cwd,
2221
+ };
2222
+ }
2223
+ //# sourceMappingURL=daemon-client.js.map