mstro-app 0.4.3 → 0.4.4

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 (306) hide show
  1. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  3. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  5. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  9. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  13. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  17. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  18. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  19. package/dist/server/cli/headless/claude-invoker.js +10 -807
  20. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  21. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  22. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  23. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  24. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  25. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  26. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  27. package/dist/server/cli/headless/headless-logger.js +28 -5
  28. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  29. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  31. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  33. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  34. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  35. package/dist/server/cli/headless/stall-assessor.js +65 -457
  36. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  37. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  38. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  39. package/dist/server/cli/improvisation-attachments.js +116 -0
  40. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  41. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  42. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  43. package/dist/server/cli/improvisation-retry.js +434 -0
  44. package/dist/server/cli/improvisation-retry.js.map +1 -0
  45. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  46. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  47. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  48. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  49. package/dist/server/cli/improvisation-types.d.ts +86 -0
  50. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  51. package/dist/server/cli/improvisation-types.js +10 -0
  52. package/dist/server/cli/improvisation-types.js.map +1 -0
  53. package/dist/server/cli/prompt-builders.d.ts +68 -0
  54. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  55. package/dist/server/cli/prompt-builders.js +312 -0
  56. package/dist/server/cli/prompt-builders.js.map +1 -0
  57. package/dist/server/index.js +33 -212
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  60. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  61. package/dist/server/mcp/bouncer-haiku.js +152 -0
  62. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  63. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  64. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  65. package/dist/server/mcp/bouncer-integration.js +50 -196
  66. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  67. package/dist/server/mcp/security-analysis.d.ts +38 -0
  68. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  69. package/dist/server/mcp/security-analysis.js +183 -0
  70. package/dist/server/mcp/security-analysis.js.map +1 -0
  71. package/dist/server/mcp/security-audit.d.ts +1 -1
  72. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  73. package/dist/server/mcp/security-patterns.d.ts +1 -25
  74. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  75. package/dist/server/mcp/security-patterns.js +55 -260
  76. package/dist/server/mcp/security-patterns.js.map +1 -1
  77. package/dist/server/server-setup.d.ts +22 -0
  78. package/dist/server/server-setup.d.ts.map +1 -0
  79. package/dist/server/server-setup.js +101 -0
  80. package/dist/server/server-setup.js.map +1 -0
  81. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  82. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  83. package/dist/server/services/file-explorer-ops.js +211 -0
  84. package/dist/server/services/file-explorer-ops.js.map +1 -0
  85. package/dist/server/services/files.d.ts +2 -85
  86. package/dist/server/services/files.d.ts.map +1 -1
  87. package/dist/server/services/files.js +7 -427
  88. package/dist/server/services/files.js.map +1 -1
  89. package/dist/server/services/plan/composer.d.ts.map +1 -1
  90. package/dist/server/services/plan/composer.js +2 -1
  91. package/dist/server/services/plan/composer.js.map +1 -1
  92. package/dist/server/services/plan/executor.d.ts.map +1 -1
  93. package/dist/server/services/plan/executor.js +3 -1
  94. package/dist/server/services/plan/executor.js.map +1 -1
  95. package/dist/server/services/plan/parser-core.d.ts +20 -0
  96. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  97. package/dist/server/services/plan/parser-core.js +350 -0
  98. package/dist/server/services/plan/parser-core.js.map +1 -0
  99. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  100. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  101. package/dist/server/services/plan/parser-migration.js +124 -0
  102. package/dist/server/services/plan/parser-migration.js.map +1 -0
  103. package/dist/server/services/plan/parser.d.ts +0 -8
  104. package/dist/server/services/plan/parser.d.ts.map +1 -1
  105. package/dist/server/services/plan/parser.js +50 -569
  106. package/dist/server/services/plan/parser.js.map +1 -1
  107. package/dist/server/services/plan/review-gate.d.ts +2 -0
  108. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  109. package/dist/server/services/plan/review-gate.js +2 -2
  110. package/dist/server/services/plan/review-gate.js.map +1 -1
  111. package/dist/server/services/plan/types.d.ts +2 -0
  112. package/dist/server/services/plan/types.d.ts.map +1 -1
  113. package/dist/server/services/platform-credentials.d.ts +24 -0
  114. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  115. package/dist/server/services/platform-credentials.js +68 -0
  116. package/dist/server/services/platform-credentials.js.map +1 -0
  117. package/dist/server/services/platform.d.ts +1 -31
  118. package/dist/server/services/platform.d.ts.map +1 -1
  119. package/dist/server/services/platform.js +10 -119
  120. package/dist/server/services/platform.js.map +1 -1
  121. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  122. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  123. package/dist/server/services/terminal/pty-manager.js +53 -266
  124. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  125. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  126. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  127. package/dist/server/services/terminal/pty-utils.js +141 -0
  128. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  129. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  130. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  131. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  132. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  133. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  134. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  135. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  136. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  137. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  138. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  139. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  140. package/dist/server/services/websocket/file-utils.js +3 -3
  141. package/dist/server/services/websocket/file-utils.js.map +1 -1
  142. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  143. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  144. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  145. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  146. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  147. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  148. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  149. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  150. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  151. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  152. package/dist/server/services/websocket/git-handlers.js +35 -541
  153. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  154. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  155. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  156. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  157. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  158. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  159. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  160. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  161. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  162. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  163. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  164. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  165. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  166. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  167. package/dist/server/services/websocket/git-utils.js +201 -0
  168. package/dist/server/services/websocket/git-utils.js.map +1 -0
  169. package/dist/server/services/websocket/handler.d.ts +2 -0
  170. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  171. package/dist/server/services/websocket/handler.js +37 -126
  172. package/dist/server/services/websocket/handler.js.map +1 -1
  173. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  174. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  175. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  176. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  177. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  178. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  179. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  180. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  181. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  182. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  183. package/dist/server/services/websocket/plan-handlers.js +6 -925
  184. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  185. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  186. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  187. package/dist/server/services/websocket/plan-helpers.js +199 -0
  188. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  189. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  190. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  191. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  192. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  193. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  194. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  195. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  196. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  197. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  198. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  199. package/dist/server/services/websocket/quality-complexity.js +262 -0
  200. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  201. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  202. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  203. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  204. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  205. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  206. package/dist/server/services/websocket/quality-handlers.js +34 -346
  207. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  208. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  209. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  210. package/dist/server/services/websocket/quality-linting.js +178 -0
  211. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  212. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  213. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  214. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  215. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  216. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  217. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  218. package/dist/server/services/websocket/quality-service.js +9 -651
  219. package/dist/server/services/websocket/quality-service.js.map +1 -1
  220. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  221. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  222. package/dist/server/services/websocket/quality-tools.js +208 -0
  223. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  224. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  225. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  226. package/dist/server/services/websocket/quality-types.js +101 -0
  227. package/dist/server/services/websocket/quality-types.js.map +1 -0
  228. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  229. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  230. package/dist/server/services/websocket/session-handlers.js +3 -378
  231. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  232. package/dist/server/services/websocket/session-history.d.ts +4 -0
  233. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  234. package/dist/server/services/websocket/session-history.js +208 -0
  235. package/dist/server/services/websocket/session-history.js.map +1 -0
  236. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  237. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  238. package/dist/server/services/websocket/session-initialization.js +163 -0
  239. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  240. package/dist/server/services/websocket/types.d.ts +12 -2
  241. package/dist/server/services/websocket/types.d.ts.map +1 -1
  242. package/package.json +1 -1
  243. package/server/cli/headless/claude-invoker-process.ts +204 -0
  244. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  245. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  246. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  247. package/server/cli/headless/claude-invoker.ts +15 -1096
  248. package/server/cli/headless/haiku-assessments.ts +365 -0
  249. package/server/cli/headless/headless-logger.ts +26 -5
  250. package/server/cli/headless/native-timeout-detector.ts +117 -0
  251. package/server/cli/headless/stall-assessor.ts +65 -618
  252. package/server/cli/improvisation-attachments.ts +148 -0
  253. package/server/cli/improvisation-retry.ts +602 -0
  254. package/server/cli/improvisation-session-manager.ts +140 -1349
  255. package/server/cli/improvisation-types.ts +98 -0
  256. package/server/cli/prompt-builders.ts +370 -0
  257. package/server/index.ts +35 -246
  258. package/server/mcp/bouncer-haiku.ts +182 -0
  259. package/server/mcp/bouncer-integration.ts +87 -248
  260. package/server/mcp/security-analysis.ts +217 -0
  261. package/server/mcp/security-audit.ts +1 -1
  262. package/server/mcp/security-patterns.ts +60 -283
  263. package/server/server-setup.ts +114 -0
  264. package/server/services/file-explorer-ops.ts +293 -0
  265. package/server/services/files.ts +20 -532
  266. package/server/services/plan/composer.ts +2 -1
  267. package/server/services/plan/executor.ts +3 -1
  268. package/server/services/plan/parser-core.ts +406 -0
  269. package/server/services/plan/parser-migration.ts +128 -0
  270. package/server/services/plan/parser.ts +52 -620
  271. package/server/services/plan/review-gate.ts +4 -2
  272. package/server/services/plan/types.ts +2 -0
  273. package/server/services/platform-credentials.ts +83 -0
  274. package/server/services/platform.ts +15 -141
  275. package/server/services/terminal/pty-manager.ts +66 -313
  276. package/server/services/terminal/pty-utils.ts +176 -0
  277. package/server/services/websocket/file-definition-handlers.ts +165 -0
  278. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  279. package/server/services/websocket/file-search-handlers.ts +291 -0
  280. package/server/services/websocket/file-utils.ts +3 -3
  281. package/server/services/websocket/git-branch-handlers.ts +130 -0
  282. package/server/services/websocket/git-diff-handlers.ts +140 -0
  283. package/server/services/websocket/git-handlers.ts +40 -625
  284. package/server/services/websocket/git-log-handlers.ts +149 -0
  285. package/server/services/websocket/git-pr-handlers.ts +17 -62
  286. package/server/services/websocket/git-tag-handlers.ts +91 -0
  287. package/server/services/websocket/git-utils.ts +230 -0
  288. package/server/services/websocket/handler.ts +39 -126
  289. package/server/services/websocket/plan-board-handlers.ts +277 -0
  290. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  291. package/server/services/websocket/plan-handlers.ts +8 -1114
  292. package/server/services/websocket/plan-helpers.ts +215 -0
  293. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  294. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  295. package/server/services/websocket/quality-complexity.ts +294 -0
  296. package/server/services/websocket/quality-fix-agent.ts +181 -0
  297. package/server/services/websocket/quality-handlers.ts +36 -404
  298. package/server/services/websocket/quality-linting.ts +187 -0
  299. package/server/services/websocket/quality-review-agent.ts +246 -0
  300. package/server/services/websocket/quality-service.ts +11 -762
  301. package/server/services/websocket/quality-tools.ts +209 -0
  302. package/server/services/websocket/quality-types.ts +169 -0
  303. package/server/services/websocket/session-handlers.ts +5 -437
  304. package/server/services/websocket/session-history.ts +222 -0
  305. package/server/services/websocket/session-initialization.ts +209 -0
  306. package/server/services/websocket/types.ts +17 -0
@@ -118,7 +118,30 @@ export class WebSocketImproviseHandler implements HandlerContext {
118
118
  }
119
119
  }
120
120
 
121
+ /** Dispatch table mapping message types to domain handlers. Built once, looked up per message. */
122
+ private static readonly DISPATCH: Record<string, 'session' | 'history' | 'file' | 'terminal' | 'fileExplorer' | 'git' | 'quality' | 'plan' | 'fileUpload'> = {
123
+ // Session
124
+ execute: 'session', cancel: 'session', getHistory: 'session', new: 'session', approve: 'session', reject: 'session',
125
+ // History
126
+ getSessions: 'history', getSessionsCount: 'history', getSessionById: 'history', deleteSession: 'history', clearHistory: 'history', searchHistory: 'history',
127
+ // File autocomplete/read
128
+ autocomplete: 'file', readFile: 'file', recordSelection: 'file',
129
+ // Terminal
130
+ terminalInit: 'terminal', terminalReconnect: 'terminal', terminalList: 'terminal', terminalInput: 'terminal', terminalResize: 'terminal', terminalClose: 'terminal',
131
+ // File explorer
132
+ listDirectory: 'fileExplorer', writeFile: 'fileExplorer', createFile: 'fileExplorer', createDirectory: 'fileExplorer', deleteFile: 'fileExplorer', renameFile: 'fileExplorer', notifyFileOpened: 'fileExplorer', searchFileContents: 'fileExplorer', cancelSearch: 'fileExplorer', findDefinition: 'fileExplorer',
133
+ // Git
134
+ gitStatus: 'git', gitStage: 'git', gitUnstage: 'git', gitCommit: 'git', gitCommitWithAI: 'git', gitPush: 'git', gitPull: 'git', gitLog: 'git', gitDiscoverRepos: 'git', gitSetDirectory: 'git', gitGetRemoteInfo: 'git', gitCreatePR: 'git', gitGeneratePRDescription: 'git', gitListBranches: 'git', gitCheckout: 'git', gitCreateBranch: 'git', gitDeleteBranch: 'git', gitDiff: 'git', gitShowCommit: 'git', gitCommitDiff: 'git', gitListTags: 'git', gitCreateTag: 'git', gitPushTag: 'git', gitWorktreeList: 'git', gitWorktreeCreate: 'git', gitWorktreeCreateAndAssign: 'git', gitWorktreeRemove: 'git', tabWorktreeSwitch: 'git', gitWorktreePush: 'git', gitWorktreeCreatePR: 'git', gitMergePreview: 'git', gitWorktreeMerge: 'git', gitMergeAbort: 'git', gitMergeComplete: 'git',
135
+ // Quality
136
+ qualityDetectTools: 'quality', qualityScan: 'quality', qualityInstallTools: 'quality', qualityCodeReview: 'quality', qualityFixIssues: 'quality', qualityLoadState: 'quality', qualitySaveDirectories: 'quality',
137
+ // Plan + boards + sprints
138
+ planInit: 'plan', planGetState: 'plan', planListIssues: 'plan', planGetIssue: 'plan', planGetSprint: 'plan', planGetMilestone: 'plan', planCreateIssue: 'plan', planUpdateIssue: 'plan', planDeleteIssue: 'plan', planScaffold: 'plan', planPrompt: 'plan', planExecute: 'plan', planExecuteEpic: 'plan', planPause: 'plan', planStop: 'plan', planResume: 'plan', planCreateBoard: 'plan', planUpdateBoard: 'plan', planArchiveBoard: 'plan', planGetBoard: 'plan', planGetBoardState: 'plan', planReorderBoards: 'plan', planSetActiveBoard: 'plan', planGetBoardArtifacts: 'plan', planCreateSprint: 'plan', planActivateSprint: 'plan', planCompleteSprint: 'plan', planGetSprintArtifacts: 'plan',
139
+ // File upload
140
+ fileUploadStart: 'fileUpload', fileUploadChunk: 'fileUpload', fileUploadComplete: 'fileUpload', fileUploadCancel: 'fileUpload',
141
+ };
142
+
121
143
  private async dispatchMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string, permission?: 'control' | 'view'): Promise<void> {
144
+ // Handle messages with custom inline logic first
122
145
  switch (msg.type) {
123
146
  case 'ping':
124
147
  this.send(ws, { type: 'pong', tabId });
@@ -128,86 +151,9 @@ export class WebSocketImproviseHandler implements HandlerContext {
128
151
  case 'resumeSession':
129
152
  if (!msg.data?.historicalSessionId) throw new Error('Historical session ID is required');
130
153
  return void await resumeHistoricalSession(this, ws, tabId, workingDir, msg.data.historicalSessionId);
131
- // Session messages
132
- case 'execute':
133
- case 'cancel':
134
- case 'getHistory':
135
- case 'new':
136
- case 'approve':
137
- case 'reject':
138
- return handleSessionMessage(this, ws, msg, tabId, permission);
139
- // History messages
140
- case 'getSessions':
141
- case 'getSessionsCount':
142
- case 'getSessionById':
143
- case 'deleteSession':
144
- case 'clearHistory':
145
- case 'searchHistory':
146
- return handleHistoryMessage(this, ws, msg, tabId, workingDir);
147
- // File autocomplete/read
148
- case 'autocomplete':
149
- case 'readFile':
150
- case 'recordSelection':
151
- return handleFileMessage(this, ws, msg, tabId, workingDir, permission);
152
- // Notification summary
153
154
  case 'requestNotificationSummary':
154
155
  if (!msg.data?.prompt || !msg.data?.output) throw new Error('Prompt and output are required for notification summary');
155
156
  return void await generateNotificationSummary(this, ws, tabId, msg.data.prompt, msg.data.output, workingDir);
156
- // Terminal messages
157
- case 'terminalInit':
158
- case 'terminalReconnect':
159
- case 'terminalList':
160
- case 'terminalInput':
161
- case 'terminalResize':
162
- case 'terminalClose':
163
- return handleTerminalMessage(this, ws, msg, tabId, workingDir, permission);
164
- // File explorer messages
165
- case 'listDirectory':
166
- case 'writeFile':
167
- case 'createFile':
168
- case 'createDirectory':
169
- case 'deleteFile':
170
- case 'renameFile':
171
- case 'notifyFileOpened':
172
- case 'searchFileContents':
173
- case 'cancelSearch':
174
- case 'findDefinition':
175
- return handleFileExplorerMessage(this, ws, msg, tabId, workingDir, permission);
176
- // Git messages
177
- case 'gitStatus':
178
- case 'gitStage':
179
- case 'gitUnstage':
180
- case 'gitCommit':
181
- case 'gitCommitWithAI':
182
- case 'gitPush':
183
- case 'gitPull':
184
- case 'gitLog':
185
- case 'gitDiscoverRepos':
186
- case 'gitSetDirectory':
187
- case 'gitGetRemoteInfo':
188
- case 'gitCreatePR':
189
- case 'gitGeneratePRDescription':
190
- case 'gitListBranches':
191
- case 'gitCheckout':
192
- case 'gitCreateBranch':
193
- case 'gitDeleteBranch':
194
- case 'gitDiff':
195
- case 'gitListTags':
196
- case 'gitCreateTag':
197
- case 'gitPushTag':
198
- case 'gitWorktreeList':
199
- case 'gitWorktreeCreate':
200
- case 'gitWorktreeCreateAndAssign':
201
- case 'gitWorktreeRemove':
202
- case 'tabWorktreeSwitch':
203
- case 'gitWorktreePush':
204
- case 'gitWorktreeCreatePR':
205
- case 'gitMergePreview':
206
- case 'gitWorktreeMerge':
207
- case 'gitMergeAbort':
208
- case 'gitMergeComplete':
209
- return handleGitMessage(this, ws, msg, tabId, workingDir);
210
- // Tab sync messages
211
157
  case 'getActiveTabs':
212
158
  return handleGetActiveTabs(this, ws, workingDir);
213
159
  case 'createTab':
@@ -222,59 +168,26 @@ export class WebSocketImproviseHandler implements HandlerContext {
222
168
  return handleRemoveTab(this, ws, tabId, workingDir);
223
169
  case 'markTabViewed':
224
170
  return handleMarkTabViewed(this, ws, tabId, workingDir);
225
- // Quality messages
226
- case 'qualityDetectTools':
227
- case 'qualityScan':
228
- case 'qualityInstallTools':
229
- case 'qualityCodeReview':
230
- case 'qualityFixIssues':
231
- case 'qualityLoadState':
232
- case 'qualitySaveDirectories':
233
- return handleQualityMessage(this, ws, msg, tabId, workingDir);
234
- // Plan messages
235
- case 'planInit':
236
- case 'planGetState':
237
- case 'planListIssues':
238
- case 'planGetIssue':
239
- case 'planGetSprint':
240
- case 'planGetMilestone':
241
- case 'planCreateIssue':
242
- case 'planUpdateIssue':
243
- case 'planDeleteIssue':
244
- case 'planScaffold':
245
- case 'planPrompt':
246
- case 'planExecute':
247
- case 'planPause':
248
- case 'planStop':
249
- case 'planResume':
250
- // Board lifecycle messages
251
- case 'planCreateBoard':
252
- case 'planUpdateBoard':
253
- case 'planArchiveBoard':
254
- case 'planGetBoard':
255
- case 'planGetBoardState':
256
- case 'planReorderBoards':
257
- case 'planSetActiveBoard':
258
- case 'planGetBoardArtifacts':
259
- // Sprint lifecycle messages (legacy)
260
- case 'planCreateSprint':
261
- case 'planActivateSprint':
262
- case 'planCompleteSprint':
263
- case 'planGetSprintArtifacts':
264
- return handlePlanMessage(this, ws, msg, tabId, workingDir, permission);
265
- // Settings messages
266
171
  case 'getSettings':
267
172
  return handleGetSettings(this, ws);
268
173
  case 'updateSettings':
269
174
  return handleUpdateSettings(this, ws, msg);
270
- // File upload messages (chunked remote uploads)
271
- case 'fileUploadStart':
272
- case 'fileUploadChunk':
273
- case 'fileUploadComplete':
274
- case 'fileUploadCancel':
275
- return this.handleFileUploadMessage(ws, msg, tabId, workingDir);
276
- default:
277
- throw new Error(`Unknown message type: ${msg.type}`);
175
+ }
176
+
177
+ // Dispatch table lookup for domain handlers
178
+ const domain = WebSocketImproviseHandler.DISPATCH[msg.type];
179
+ if (!domain) throw new Error(`Unknown message type: ${msg.type}`);
180
+
181
+ switch (domain) {
182
+ case 'session': return handleSessionMessage(this, ws, msg, tabId, permission);
183
+ case 'history': return handleHistoryMessage(this, ws, msg, tabId, workingDir);
184
+ case 'file': return handleFileMessage(this, ws, msg, tabId, workingDir, permission);
185
+ case 'terminal': return handleTerminalMessage(this, ws, msg, tabId, workingDir, permission);
186
+ case 'fileExplorer': return handleFileExplorerMessage(this, ws, msg, tabId, workingDir, permission);
187
+ case 'git': return handleGitMessage(this, ws, msg, tabId, workingDir);
188
+ case 'quality': return handleQualityMessage(this, ws, msg, tabId, workingDir);
189
+ case 'plan': return handlePlanMessage(this, ws, msg, tabId, workingDir, permission);
190
+ case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId, workingDir);
278
191
  }
279
192
  }
280
193
 
@@ -0,0 +1,277 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { replaceFrontMatterField } from '../plan/front-matter.js';
7
+ import { getNextBoardId, getNextBoardNumber, parseBoardArtifacts, parseBoardDirectory, parsePlanDirectory, resolvePmDir } from '../plan/parser.js';
8
+ import type { Workspace } from '../plan/types.js';
9
+ import type { HandlerContext } from './handler-context.js';
10
+ import { denyIfViewOnly, formatYamlValue } from './plan-helpers.js';
11
+ import type { WebSocketMessage, WSContext } from './types.js';
12
+
13
+ // ============================================================================
14
+ // Board lifecycle handlers
15
+ // ============================================================================
16
+
17
+ export function handleCreateBoard(
18
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
19
+ workingDir: string, permission?: 'control' | 'view',
20
+ ): void {
21
+ if (denyIfViewOnly(ctx, ws, permission)) return;
22
+
23
+ const pmDir = resolvePmDir(workingDir);
24
+ if (!pmDir) {
25
+ ctx.send(ws, { type: 'planError', data: { error: 'No PM directory found' } });
26
+ return;
27
+ }
28
+
29
+ const fullState = parsePlanDirectory(workingDir);
30
+ if (!fullState) return;
31
+
32
+ const boardId = getNextBoardId(fullState.boards);
33
+ const boardNum = getNextBoardNumber(fullState.boards);
34
+ const title = msg.data?.title || `Board ${boardNum}`;
35
+ const goal = msg.data?.goal || '';
36
+ const boardDir = join(pmDir, 'boards', boardId);
37
+
38
+ for (const dir of ['backlog', 'out', 'reviews', 'logs']) {
39
+ mkdirSync(join(boardDir, dir), { recursive: true });
40
+ }
41
+
42
+ const today = new Date().toISOString().split('T')[0];
43
+ writeFileSync(join(boardDir, 'board.md'), `---
44
+ id: ${boardId}
45
+ title: "${title.replace(/"/g, '\\"')}"
46
+ status: draft
47
+ created: "${today}"
48
+ completed_at: null
49
+ goal: "${goal.replace(/"/g, '\\"')}"
50
+ ---
51
+
52
+ # ${title}
53
+
54
+ ## Goal
55
+ ${goal}
56
+
57
+ ## Notes
58
+ `, 'utf-8');
59
+
60
+ writeFileSync(join(boardDir, 'STATE.md'), `---
61
+ project: ../../project.md
62
+ board: board.md
63
+ paused: false
64
+ ---
65
+
66
+ # Board State
67
+
68
+ ## Ready to Work
69
+
70
+ ## In Progress
71
+
72
+ ## Blocked
73
+
74
+ ## Recently Completed
75
+
76
+ ## Warnings
77
+ `, 'utf-8');
78
+
79
+ writeFileSync(join(boardDir, 'progress.md'), '# Board Progress\n', 'utf-8');
80
+
81
+ const wsPath = join(pmDir, 'workspace.json');
82
+ if (!existsSync(wsPath)) {
83
+ writeFileSync(wsPath, JSON.stringify({ activeBoardId: null, boardOrder: [] }, null, 2), 'utf-8');
84
+ }
85
+ const workspaceContent = readFileSync(wsPath, 'utf-8');
86
+ const workspace: Workspace = JSON.parse(workspaceContent);
87
+ workspace.boardOrder.push(boardId);
88
+ if (!workspace.activeBoardId) {
89
+ workspace.activeBoardId = boardId;
90
+ }
91
+ writeFileSync(join(pmDir, 'workspace.json'), JSON.stringify(workspace, null, 2), 'utf-8');
92
+
93
+ const boardState = parseBoardDirectory(pmDir, boardId);
94
+ if (boardState) {
95
+ ctx.broadcastToAll({ type: 'planBoardCreated', data: boardState.board });
96
+ ctx.broadcastToAll({ type: 'planWorkspaceUpdated', data: workspace });
97
+ }
98
+ }
99
+
100
+ export function handleUpdateBoard(
101
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
102
+ workingDir: string, permission?: 'control' | 'view',
103
+ ): void {
104
+ if (denyIfViewOnly(ctx, ws, permission)) return;
105
+
106
+ const { boardId, fields } = msg.data || {};
107
+ if (!boardId || !fields) {
108
+ ctx.send(ws, { type: 'planError', data: { error: 'Board ID and fields required' } });
109
+ return;
110
+ }
111
+
112
+ const pmDir = resolvePmDir(workingDir);
113
+ if (!pmDir) return;
114
+
115
+ const boardMdPath = join(pmDir, 'boards', boardId, 'board.md');
116
+ if (!existsSync(boardMdPath)) {
117
+ ctx.send(ws, { type: 'planError', data: { error: `Board not found: ${boardId}` } });
118
+ return;
119
+ }
120
+
121
+ let content = readFileSync(boardMdPath, 'utf-8');
122
+ for (const [key, value] of Object.entries(fields as Record<string, unknown>)) {
123
+ const yamlKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
124
+ content = replaceFrontMatterField(content, yamlKey, formatYamlValue(value));
125
+ }
126
+ writeFileSync(boardMdPath, content, 'utf-8');
127
+
128
+ const boardState = parseBoardDirectory(pmDir, boardId);
129
+ if (boardState) {
130
+ ctx.broadcastToAll({ type: 'planBoardUpdated', data: boardState.board });
131
+ }
132
+ }
133
+
134
+ export function handleArchiveBoard(
135
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
136
+ workingDir: string, permission?: 'control' | 'view',
137
+ ): void {
138
+ if (denyIfViewOnly(ctx, ws, permission)) return;
139
+
140
+ const boardId = msg.data?.boardId;
141
+ if (!boardId) {
142
+ ctx.send(ws, { type: 'planError', data: { error: 'Board ID required' } });
143
+ return;
144
+ }
145
+
146
+ const pmDir = resolvePmDir(workingDir);
147
+ if (!pmDir) return;
148
+
149
+ const boardMdPath = join(pmDir, 'boards', boardId, 'board.md');
150
+ if (!existsSync(boardMdPath)) {
151
+ ctx.send(ws, { type: 'planError', data: { error: `Board not found: ${boardId}` } });
152
+ return;
153
+ }
154
+
155
+ let content = readFileSync(boardMdPath, 'utf-8');
156
+ content = replaceFrontMatterField(content, 'status', 'archived');
157
+ writeFileSync(boardMdPath, content, 'utf-8');
158
+
159
+ const workspacePath = join(pmDir, 'workspace.json');
160
+ if (existsSync(workspacePath)) {
161
+ const workspace: Workspace = JSON.parse(readFileSync(workspacePath, 'utf-8'));
162
+ workspace.boardOrder = workspace.boardOrder.filter(id => id !== boardId);
163
+ if (workspace.activeBoardId === boardId) {
164
+ workspace.activeBoardId = workspace.boardOrder[0] || null;
165
+ }
166
+ writeFileSync(workspacePath, JSON.stringify(workspace, null, 2), 'utf-8');
167
+ ctx.broadcastToAll({ type: 'planWorkspaceUpdated', data: workspace });
168
+ }
169
+
170
+ const boardState = parseBoardDirectory(pmDir, boardId);
171
+ if (boardState) {
172
+ ctx.broadcastToAll({ type: 'planBoardArchived', data: boardState.board });
173
+ }
174
+ }
175
+
176
+ export function handleGetBoard(
177
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
178
+ workingDir: string,
179
+ ): void {
180
+ const boardId = msg.data?.boardId;
181
+ if (!boardId) {
182
+ ctx.send(ws, { type: 'planError', data: { error: 'Board ID required' } });
183
+ return;
184
+ }
185
+
186
+ const pmDir = resolvePmDir(workingDir);
187
+ if (!pmDir) return;
188
+
189
+ const boardState = parseBoardDirectory(pmDir, boardId);
190
+ if (!boardState) {
191
+ ctx.send(ws, { type: 'planError', data: { error: `Board not found: ${boardId}` } });
192
+ return;
193
+ }
194
+
195
+ ctx.send(ws, { type: 'planBoardState', data: boardState });
196
+ }
197
+
198
+ export function handleGetBoardState(
199
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
200
+ workingDir: string,
201
+ ): void {
202
+ handleGetBoard(ctx, ws, msg, workingDir);
203
+ }
204
+
205
+ export function handleReorderBoards(
206
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
207
+ workingDir: string, permission?: 'control' | 'view',
208
+ ): void {
209
+ if (denyIfViewOnly(ctx, ws, permission)) return;
210
+
211
+ const boardOrder = msg.data?.boardOrder;
212
+ if (!Array.isArray(boardOrder)) {
213
+ ctx.send(ws, { type: 'planError', data: { error: 'boardOrder array required' } });
214
+ return;
215
+ }
216
+
217
+ const pmDir = resolvePmDir(workingDir);
218
+ if (!pmDir) return;
219
+
220
+ const workspacePath = join(pmDir, 'workspace.json');
221
+ if (!existsSync(workspacePath)) return;
222
+
223
+ const workspace: Workspace = JSON.parse(readFileSync(workspacePath, 'utf-8'));
224
+ workspace.boardOrder = boardOrder;
225
+ writeFileSync(workspacePath, JSON.stringify(workspace, null, 2), 'utf-8');
226
+
227
+ ctx.broadcastToAll({ type: 'planWorkspaceUpdated', data: workspace });
228
+ }
229
+
230
+ export function handleSetActiveBoard(
231
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
232
+ workingDir: string, permission?: 'control' | 'view',
233
+ ): void {
234
+ if (denyIfViewOnly(ctx, ws, permission)) return;
235
+
236
+ const boardId = msg.data?.boardId;
237
+ if (!boardId) {
238
+ ctx.send(ws, { type: 'planError', data: { error: 'Board ID required' } });
239
+ return;
240
+ }
241
+
242
+ const pmDir = resolvePmDir(workingDir);
243
+ if (!pmDir) return;
244
+
245
+ const workspacePath = join(pmDir, 'workspace.json');
246
+ if (!existsSync(workspacePath)) return;
247
+
248
+ const workspace: Workspace = JSON.parse(readFileSync(workspacePath, 'utf-8'));
249
+ workspace.activeBoardId = boardId;
250
+ writeFileSync(workspacePath, JSON.stringify(workspace, null, 2), 'utf-8');
251
+
252
+ ctx.broadcastToAll({ type: 'planWorkspaceUpdated', data: workspace });
253
+
254
+ const boardState = parseBoardDirectory(pmDir, boardId);
255
+ if (boardState) {
256
+ ctx.send(ws, { type: 'planBoardState', data: boardState });
257
+ }
258
+ }
259
+
260
+ export function handleGetBoardArtifacts(
261
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
262
+ workingDir: string,
263
+ ): void {
264
+ const boardId = msg.data?.boardId;
265
+ if (!boardId) {
266
+ ctx.send(ws, { type: 'planError', data: { error: 'Board ID required' } });
267
+ return;
268
+ }
269
+
270
+ const artifacts = parseBoardArtifacts(workingDir, boardId);
271
+ if (!artifacts) {
272
+ ctx.send(ws, { type: 'planBoardArtifacts', data: { boardId, progressLog: '', outputFiles: [], reviewResults: [], executionLogs: [] } });
273
+ return;
274
+ }
275
+
276
+ ctx.send(ws, { type: 'planBoardArtifacts', data: artifacts });
277
+ }
@@ -0,0 +1,184 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import { handlePlanPrompt } from '../plan/composer.js';
5
+ import type { PlanExecutor } from '../plan/executor.js';
6
+ import { parsePlanDirectory } from '../plan/parser.js';
7
+ import type { HandlerContext } from './handler-context.js';
8
+ import { denyIfViewOnly, executorCache, getExecutor } from './plan-helpers.js';
9
+ import type { WebSocketMessage, WSContext } from './types.js';
10
+
11
+ // ============================================================================
12
+ // Composer + Execution handlers
13
+ // ============================================================================
14
+
15
+ export function handlePrompt(
16
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
17
+ workingDir: string, permission?: 'control' | 'view',
18
+ ): void {
19
+ if (denyIfViewOnly(ctx, ws, permission)) return;
20
+
21
+ const prompt = msg.data?.prompt;
22
+ const boardId = msg.data?.boardId as string | undefined;
23
+ if (!prompt) {
24
+ ctx.send(ws, { type: 'planError', data: { error: 'Prompt required' } });
25
+ return;
26
+ }
27
+ handlePlanPrompt(ctx, ws, prompt, workingDir, boardId).catch(error => {
28
+ ctx.send(ws, {
29
+ type: 'planError',
30
+ data: { error: error instanceof Error ? error.message : String(error) },
31
+ });
32
+ });
33
+ }
34
+
35
+ function wireExecutorEvents(executor: PlanExecutor, ctx: HandlerContext, workingDir: string): void {
36
+ executor.removeAllListeners();
37
+
38
+ executor.on('statusChanged', (status: string) => {
39
+ ctx.broadcastToAll({ type: 'planExecutionProgress', data: { status } });
40
+ });
41
+
42
+ executor.on('issueStarted', (issue: { id: string; title: string }) => {
43
+ ctx.broadcastToAll({
44
+ type: 'planExecutionProgress',
45
+ data: { issueId: issue.id, status: 'executing', title: issue.title },
46
+ });
47
+ });
48
+
49
+ executor.on('output', (data: { issueId: string; text: string }) => {
50
+ ctx.broadcastToAll({ type: 'planExecutionOutput', data });
51
+ });
52
+
53
+ executor.on('issueCompleted', () => {
54
+ ctx.broadcastToAll({ type: 'planExecutionMetrics', data: executor.getMetrics() });
55
+ const fullState = parsePlanDirectory(workingDir);
56
+ if (fullState) {
57
+ ctx.broadcastToAll({ type: 'planStateUpdated', data: fullState });
58
+ }
59
+ });
60
+
61
+ executor.on('issueError', (data: { issueId: string; error: string }) => {
62
+ ctx.broadcastToAll({ type: 'planExecutionError', data });
63
+ });
64
+
65
+ executor.on('waveStarted', (data: { issueIds: string[] }) => {
66
+ ctx.broadcastToAll({
67
+ type: 'planExecutionProgress',
68
+ data: { status: 'wave', issueIds: data.issueIds },
69
+ });
70
+ });
71
+
72
+ executor.on('waveError', (data: { issueIds: string[]; error: string }) => {
73
+ ctx.broadcastToAll({ type: 'planExecutionError', data });
74
+ });
75
+
76
+ executor.on('stateUpdated', () => {
77
+ const fullState = parsePlanDirectory(workingDir);
78
+ if (fullState) {
79
+ ctx.broadcastToAll({ type: 'planStateUpdated', data: fullState });
80
+ }
81
+ });
82
+
83
+ executor.on('reviewProgress', (data: { issueId: string; status: string }) => {
84
+ ctx.broadcastToAll({ type: 'planReviewProgress', data });
85
+ });
86
+
87
+ executor.on('complete', (reason: string) => {
88
+ ctx.broadcastToAll({ type: 'planExecutionComplete', data: { reason, metrics: executor.getMetrics() } });
89
+ });
90
+
91
+ executor.on('error', (error: string) => {
92
+ ctx.broadcastToAll({ type: 'planExecutionError', data: { error } });
93
+ });
94
+ }
95
+
96
+ export function handleExecute(
97
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
98
+ workingDir: string, permission?: 'control' | 'view',
99
+ ): void {
100
+ if (denyIfViewOnly(ctx, ws, permission)) return;
101
+
102
+ const executor = getExecutor(workingDir);
103
+
104
+ if (executor.getStatus() === 'executing' || executor.getStatus() === 'starting') {
105
+ ctx.send(ws, { type: 'planError', data: { error: 'Execution already in progress' } });
106
+ return;
107
+ }
108
+
109
+ wireExecutorEvents(executor, ctx, workingDir);
110
+
111
+ const boardId = msg.data?.boardId as string | undefined;
112
+ ctx.send(ws, { type: 'planExecutionStarted', data: { status: 'executing', boardId } });
113
+ const startPromise = boardId ? executor.startBoard(boardId) : executor.start();
114
+ startPromise.catch(error => {
115
+ ctx.send(ws, {
116
+ type: 'planExecutionError',
117
+ data: { error: error instanceof Error ? error.message : String(error) },
118
+ });
119
+ });
120
+ }
121
+
122
+ export function handleExecuteEpic(
123
+ ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage,
124
+ workingDir: string, permission?: 'control' | 'view',
125
+ ): void {
126
+ if (denyIfViewOnly(ctx, ws, permission)) return;
127
+
128
+ const epicPath = msg.data?.epicPath;
129
+ if (!epicPath) {
130
+ ctx.send(ws, { type: 'planError', data: { error: 'Epic path required' } });
131
+ return;
132
+ }
133
+
134
+ const executor = getExecutor(workingDir);
135
+
136
+ if (executor.getStatus() === 'executing' || executor.getStatus() === 'starting') {
137
+ ctx.send(ws, { type: 'planError', data: { error: 'Execution already in progress' } });
138
+ return;
139
+ }
140
+
141
+ wireExecutorEvents(executor, ctx, workingDir);
142
+
143
+ ctx.send(ws, { type: 'planExecutionStarted', data: { status: 'executing', epicPath } });
144
+ executor.startEpic(epicPath).catch(error => {
145
+ ctx.send(ws, {
146
+ type: 'planExecutionError',
147
+ data: { error: error instanceof Error ? error.message : String(error) },
148
+ });
149
+ });
150
+ }
151
+
152
+ export function handlePause(
153
+ ctx: HandlerContext, ws: WSContext,
154
+ workingDir: string, permission?: 'control' | 'view',
155
+ ): void {
156
+ if (denyIfViewOnly(ctx, ws, permission)) return;
157
+ const executor = executorCache.get(workingDir);
158
+ if (executor) executor.pause();
159
+ }
160
+
161
+ export function handleStop(
162
+ ctx: HandlerContext, ws: WSContext,
163
+ workingDir: string, permission?: 'control' | 'view',
164
+ ): void {
165
+ if (denyIfViewOnly(ctx, ws, permission)) return;
166
+ const executor = executorCache.get(workingDir);
167
+ if (executor) executor.stop();
168
+ }
169
+
170
+ export function handleResume(
171
+ ctx: HandlerContext, ws: WSContext,
172
+ workingDir: string, permission?: 'control' | 'view',
173
+ ): void {
174
+ if (denyIfViewOnly(ctx, ws, permission)) return;
175
+ const executor = executorCache.get(workingDir);
176
+ if (executor) {
177
+ executor.resume().catch(error => {
178
+ ctx.send(ws, {
179
+ type: 'planExecutionError',
180
+ data: { error: error instanceof Error ? error.message : String(error) },
181
+ });
182
+ });
183
+ }
184
+ }