mstro-app 0.4.2 → 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 (342) hide show
  1. package/bin/mstro.js +119 -40
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  3. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  5. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  9. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  13. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  17. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  18. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  19. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  20. package/dist/server/cli/headless/claude-invoker.js +10 -804
  21. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  22. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  23. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  24. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  25. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  26. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  27. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  28. package/dist/server/cli/headless/headless-logger.js +28 -5
  29. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  31. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  33. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  34. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  35. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  36. package/dist/server/cli/headless/stall-assessor.js +65 -457
  37. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  38. package/dist/server/cli/headless/types.d.ts +4 -1
  39. package/dist/server/cli/headless/types.d.ts.map +1 -1
  40. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  41. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  42. package/dist/server/cli/improvisation-attachments.js +116 -0
  43. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  44. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  45. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  46. package/dist/server/cli/improvisation-retry.js +434 -0
  47. package/dist/server/cli/improvisation-retry.js.map +1 -0
  48. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  49. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  50. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  51. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  52. package/dist/server/cli/improvisation-types.d.ts +86 -0
  53. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  54. package/dist/server/cli/improvisation-types.js +10 -0
  55. package/dist/server/cli/improvisation-types.js.map +1 -0
  56. package/dist/server/cli/prompt-builders.d.ts +68 -0
  57. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  58. package/dist/server/cli/prompt-builders.js +312 -0
  59. package/dist/server/cli/prompt-builders.js.map +1 -0
  60. package/dist/server/index.js +33 -212
  61. package/dist/server/index.js.map +1 -1
  62. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  63. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  64. package/dist/server/mcp/bouncer-haiku.js +152 -0
  65. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  66. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  67. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  68. package/dist/server/mcp/bouncer-integration.js +50 -196
  69. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  70. package/dist/server/mcp/security-analysis.d.ts +38 -0
  71. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  72. package/dist/server/mcp/security-analysis.js +183 -0
  73. package/dist/server/mcp/security-analysis.js.map +1 -0
  74. package/dist/server/mcp/security-audit.d.ts +1 -1
  75. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  76. package/dist/server/mcp/security-patterns.d.ts +1 -25
  77. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  78. package/dist/server/mcp/security-patterns.js +55 -260
  79. package/dist/server/mcp/security-patterns.js.map +1 -1
  80. package/dist/server/server-setup.d.ts +22 -0
  81. package/dist/server/server-setup.d.ts.map +1 -0
  82. package/dist/server/server-setup.js +101 -0
  83. package/dist/server/server-setup.js.map +1 -0
  84. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  85. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  86. package/dist/server/services/file-explorer-ops.js +211 -0
  87. package/dist/server/services/file-explorer-ops.js.map +1 -0
  88. package/dist/server/services/files.d.ts +2 -85
  89. package/dist/server/services/files.d.ts.map +1 -1
  90. package/dist/server/services/files.js +7 -427
  91. package/dist/server/services/files.js.map +1 -1
  92. package/dist/server/services/plan/composer.d.ts +1 -1
  93. package/dist/server/services/plan/composer.d.ts.map +1 -1
  94. package/dist/server/services/plan/composer.js +118 -32
  95. package/dist/server/services/plan/composer.js.map +1 -1
  96. package/dist/server/services/plan/config-installer.d.ts +25 -0
  97. package/dist/server/services/plan/config-installer.d.ts.map +1 -0
  98. package/dist/server/services/plan/config-installer.js +182 -0
  99. package/dist/server/services/plan/config-installer.js.map +1 -0
  100. package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
  101. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  102. package/dist/server/services/plan/dependency-resolver.js +4 -1
  103. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  104. package/dist/server/services/plan/executor.d.ts +38 -74
  105. package/dist/server/services/plan/executor.d.ts.map +1 -1
  106. package/dist/server/services/plan/executor.js +274 -460
  107. package/dist/server/services/plan/executor.js.map +1 -1
  108. package/dist/server/services/plan/front-matter.d.ts +18 -0
  109. package/dist/server/services/plan/front-matter.d.ts.map +1 -0
  110. package/dist/server/services/plan/front-matter.js +44 -0
  111. package/dist/server/services/plan/front-matter.js.map +1 -0
  112. package/dist/server/services/plan/output-manager.d.ts +22 -0
  113. package/dist/server/services/plan/output-manager.d.ts.map +1 -0
  114. package/dist/server/services/plan/output-manager.js +97 -0
  115. package/dist/server/services/plan/output-manager.js.map +1 -0
  116. package/dist/server/services/plan/parser-core.d.ts +20 -0
  117. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  118. package/dist/server/services/plan/parser-core.js +350 -0
  119. package/dist/server/services/plan/parser-core.js.map +1 -0
  120. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  121. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  122. package/dist/server/services/plan/parser-migration.js +124 -0
  123. package/dist/server/services/plan/parser-migration.js.map +1 -0
  124. package/dist/server/services/plan/parser.d.ts +11 -3
  125. package/dist/server/services/plan/parser.d.ts.map +1 -1
  126. package/dist/server/services/plan/parser.js +184 -369
  127. package/dist/server/services/plan/parser.js.map +1 -1
  128. package/dist/server/services/plan/prompt-builder.d.ts +17 -0
  129. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
  130. package/dist/server/services/plan/prompt-builder.js +137 -0
  131. package/dist/server/services/plan/prompt-builder.js.map +1 -0
  132. package/dist/server/services/plan/review-gate.d.ts +28 -0
  133. package/dist/server/services/plan/review-gate.d.ts.map +1 -0
  134. package/dist/server/services/plan/review-gate.js +191 -0
  135. package/dist/server/services/plan/review-gate.js.map +1 -0
  136. package/dist/server/services/plan/state-reconciler.d.ts +1 -1
  137. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  138. package/dist/server/services/plan/state-reconciler.js +59 -7
  139. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  140. package/dist/server/services/plan/types.d.ts +68 -0
  141. package/dist/server/services/plan/types.d.ts.map +1 -1
  142. package/dist/server/services/platform-credentials.d.ts +24 -0
  143. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  144. package/dist/server/services/platform-credentials.js +68 -0
  145. package/dist/server/services/platform-credentials.js.map +1 -0
  146. package/dist/server/services/platform.d.ts +1 -31
  147. package/dist/server/services/platform.d.ts.map +1 -1
  148. package/dist/server/services/platform.js +11 -109
  149. package/dist/server/services/platform.js.map +1 -1
  150. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  151. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  152. package/dist/server/services/terminal/pty-manager.js +53 -266
  153. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  154. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  155. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  156. package/dist/server/services/terminal/pty-utils.js +141 -0
  157. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  158. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  159. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  160. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  161. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  162. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  163. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  164. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  165. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  166. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  167. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  168. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  169. package/dist/server/services/websocket/file-utils.js +3 -3
  170. package/dist/server/services/websocket/file-utils.js.map +1 -1
  171. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  172. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  173. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  174. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  175. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  176. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  177. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  178. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  179. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  180. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  181. package/dist/server/services/websocket/git-handlers.js +35 -541
  182. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  183. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  184. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  185. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  186. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  187. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  188. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  189. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  190. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  191. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  192. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  193. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  194. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  195. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  196. package/dist/server/services/websocket/git-utils.js +201 -0
  197. package/dist/server/services/websocket/git-utils.js.map +1 -0
  198. package/dist/server/services/websocket/handler.d.ts +2 -0
  199. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  200. package/dist/server/services/websocket/handler.js +37 -112
  201. package/dist/server/services/websocket/handler.js.map +1 -1
  202. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  203. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  204. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  205. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  206. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  207. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  208. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  209. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  210. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  211. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  212. package/dist/server/services/websocket/plan-handlers.js +21 -462
  213. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  214. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  215. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  216. package/dist/server/services/websocket/plan-helpers.js +199 -0
  217. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  218. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  219. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  220. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  221. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  222. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  223. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  224. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  225. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  226. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  227. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  228. package/dist/server/services/websocket/quality-complexity.js +262 -0
  229. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  230. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  231. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  232. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  233. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  234. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  235. package/dist/server/services/websocket/quality-handlers.js +34 -346
  236. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  237. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  238. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  239. package/dist/server/services/websocket/quality-linting.js +178 -0
  240. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  241. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  242. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  243. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  244. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  245. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  246. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  247. package/dist/server/services/websocket/quality-service.js +9 -651
  248. package/dist/server/services/websocket/quality-service.js.map +1 -1
  249. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  250. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  251. package/dist/server/services/websocket/quality-tools.js +208 -0
  252. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  253. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  254. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  255. package/dist/server/services/websocket/quality-types.js +101 -0
  256. package/dist/server/services/websocket/quality-types.js.map +1 -0
  257. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  258. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  259. package/dist/server/services/websocket/session-handlers.js +3 -378
  260. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  261. package/dist/server/services/websocket/session-history.d.ts +4 -0
  262. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  263. package/dist/server/services/websocket/session-history.js +208 -0
  264. package/dist/server/services/websocket/session-history.js.map +1 -0
  265. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  266. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  267. package/dist/server/services/websocket/session-initialization.js +163 -0
  268. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  269. package/dist/server/services/websocket/types.d.ts +12 -2
  270. package/dist/server/services/websocket/types.d.ts.map +1 -1
  271. package/package.json +1 -2
  272. package/server/cli/headless/claude-invoker-process.ts +204 -0
  273. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  274. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  275. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  276. package/server/cli/headless/claude-invoker.ts +15 -1092
  277. package/server/cli/headless/haiku-assessments.ts +365 -0
  278. package/server/cli/headless/headless-logger.ts +26 -5
  279. package/server/cli/headless/native-timeout-detector.ts +117 -0
  280. package/server/cli/headless/stall-assessor.ts +65 -618
  281. package/server/cli/headless/types.ts +4 -1
  282. package/server/cli/improvisation-attachments.ts +148 -0
  283. package/server/cli/improvisation-retry.ts +602 -0
  284. package/server/cli/improvisation-session-manager.ts +140 -1349
  285. package/server/cli/improvisation-types.ts +98 -0
  286. package/server/cli/prompt-builders.ts +370 -0
  287. package/server/index.ts +35 -246
  288. package/server/mcp/bouncer-haiku.ts +182 -0
  289. package/server/mcp/bouncer-integration.ts +87 -248
  290. package/server/mcp/security-analysis.ts +217 -0
  291. package/server/mcp/security-audit.ts +1 -1
  292. package/server/mcp/security-patterns.ts +60 -283
  293. package/server/server-setup.ts +114 -0
  294. package/server/services/file-explorer-ops.ts +293 -0
  295. package/server/services/files.ts +20 -532
  296. package/server/services/plan/composer.ts +140 -35
  297. package/server/services/plan/config-installer.ts +187 -0
  298. package/server/services/plan/dependency-resolver.ts +4 -1
  299. package/server/services/plan/executor.ts +281 -488
  300. package/server/services/plan/front-matter.ts +48 -0
  301. package/server/services/plan/output-manager.ts +113 -0
  302. package/server/services/plan/parser-core.ts +406 -0
  303. package/server/services/plan/parser-migration.ts +128 -0
  304. package/server/services/plan/parser.ts +188 -394
  305. package/server/services/plan/prompt-builder.ts +161 -0
  306. package/server/services/plan/review-gate.ts +212 -0
  307. package/server/services/plan/state-reconciler.ts +68 -7
  308. package/server/services/plan/types.ts +101 -1
  309. package/server/services/platform-credentials.ts +83 -0
  310. package/server/services/platform.ts +16 -131
  311. package/server/services/terminal/pty-manager.ts +66 -313
  312. package/server/services/terminal/pty-utils.ts +176 -0
  313. package/server/services/websocket/file-definition-handlers.ts +165 -0
  314. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  315. package/server/services/websocket/file-search-handlers.ts +291 -0
  316. package/server/services/websocket/file-utils.ts +3 -3
  317. package/server/services/websocket/git-branch-handlers.ts +130 -0
  318. package/server/services/websocket/git-diff-handlers.ts +140 -0
  319. package/server/services/websocket/git-handlers.ts +40 -625
  320. package/server/services/websocket/git-log-handlers.ts +149 -0
  321. package/server/services/websocket/git-pr-handlers.ts +17 -62
  322. package/server/services/websocket/git-tag-handlers.ts +91 -0
  323. package/server/services/websocket/git-utils.ts +230 -0
  324. package/server/services/websocket/handler.ts +39 -112
  325. package/server/services/websocket/plan-board-handlers.ts +277 -0
  326. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  327. package/server/services/websocket/plan-handlers.ts +23 -544
  328. package/server/services/websocket/plan-helpers.ts +215 -0
  329. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  330. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  331. package/server/services/websocket/quality-complexity.ts +294 -0
  332. package/server/services/websocket/quality-fix-agent.ts +181 -0
  333. package/server/services/websocket/quality-handlers.ts +36 -404
  334. package/server/services/websocket/quality-linting.ts +187 -0
  335. package/server/services/websocket/quality-review-agent.ts +246 -0
  336. package/server/services/websocket/quality-service.ts +11 -762
  337. package/server/services/websocket/quality-tools.ts +209 -0
  338. package/server/services/websocket/quality-types.ts +169 -0
  339. package/server/services/websocket/session-handlers.ts +5 -437
  340. package/server/services/websocket/session-history.ts +222 -0
  341. package/server/services/websocket/session-initialization.ts +209 -0
  342. package/server/services/websocket/types.ts +46 -2
@@ -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,45 +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
- return handlePlanMessage(this, ws, msg, tabId, workingDir, permission);
251
- // Settings messages
252
171
  case 'getSettings':
253
172
  return handleGetSettings(this, ws);
254
173
  case 'updateSettings':
255
174
  return handleUpdateSettings(this, ws, msg);
256
- // File upload messages (chunked remote uploads)
257
- case 'fileUploadStart':
258
- case 'fileUploadChunk':
259
- case 'fileUploadComplete':
260
- case 'fileUploadCancel':
261
- return this.handleFileUploadMessage(ws, msg, tabId, workingDir);
262
- default:
263
- 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);
264
191
  }
265
192
  }
266
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
+ }