mstro-app 0.4.52 → 0.5.1

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 (590) hide show
  1. package/LICENSE +129 -190
  2. package/PRIVACY.md +3 -3
  3. package/README.md +15 -6
  4. package/bin/commands/config.js +0 -1
  5. package/bin/mstro.js +1 -2
  6. package/bin/postinstall.js +0 -1
  7. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
  8. package/dist/server/cli/headless/claude-invoker-process.js +0 -1
  9. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
  10. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
  11. package/dist/server/cli/headless/claude-invoker-stall.js +7 -3
  12. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
  13. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
  14. package/dist/server/cli/headless/claude-invoker-stream.js +0 -1
  15. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
  16. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -1
  17. package/dist/server/cli/headless/claude-invoker-tools.js +0 -1
  18. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -1
  19. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  20. package/dist/server/cli/headless/claude-invoker.js +1 -2
  21. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  22. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
  23. package/dist/server/cli/headless/haiku-assessments.js +0 -1
  24. package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
  25. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  26. package/dist/server/cli/headless/headless-logger.js +0 -1
  27. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  28. package/dist/server/cli/headless/index.d.ts.map +1 -1
  29. package/dist/server/cli/headless/index.js +0 -1
  30. package/dist/server/cli/headless/index.js.map +1 -1
  31. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -1
  32. package/dist/server/cli/headless/native-timeout-detector.js +0 -1
  33. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -1
  34. package/dist/server/cli/headless/output-utils.d.ts.map +1 -1
  35. package/dist/server/cli/headless/output-utils.js +0 -1
  36. package/dist/server/cli/headless/output-utils.js.map +1 -1
  37. package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -1
  38. package/dist/server/cli/headless/prompt-utils.js +0 -1
  39. package/dist/server/cli/headless/prompt-utils.js.map +1 -1
  40. package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -1
  41. package/dist/server/cli/headless/resilient-runner.js +0 -1
  42. package/dist/server/cli/headless/resilient-runner.js.map +1 -1
  43. package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -1
  44. package/dist/server/cli/headless/retry-strategies.js +0 -1
  45. package/dist/server/cli/headless/retry-strategies.js.map +1 -1
  46. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  47. package/dist/server/cli/headless/runner.js +63 -68
  48. package/dist/server/cli/headless/runner.js.map +1 -1
  49. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  50. package/dist/server/cli/headless/stall-assessor.js +9 -5
  51. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  52. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  53. package/dist/server/cli/headless/tool-watchdog.js +0 -1
  54. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  55. package/dist/server/cli/headless/types.d.ts.map +1 -1
  56. package/dist/server/cli/headless/types.js +0 -1
  57. package/dist/server/cli/headless/types.js.map +1 -1
  58. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -1
  59. package/dist/server/cli/improvisation-attachments.js +0 -1
  60. package/dist/server/cli/improvisation-attachments.js.map +1 -1
  61. package/dist/server/cli/improvisation-history-store.d.ts +16 -0
  62. package/dist/server/cli/improvisation-history-store.d.ts.map +1 -0
  63. package/dist/server/cli/improvisation-history-store.js +51 -0
  64. package/dist/server/cli/improvisation-history-store.js.map +1 -0
  65. package/dist/server/cli/improvisation-movements.d.ts +31 -0
  66. package/dist/server/cli/improvisation-movements.d.ts.map +1 -0
  67. package/dist/server/cli/improvisation-movements.js +92 -0
  68. package/dist/server/cli/improvisation-movements.js.map +1 -0
  69. package/dist/server/cli/improvisation-output-queue.d.ts +13 -0
  70. package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -0
  71. package/dist/server/cli/improvisation-output-queue.js +39 -0
  72. package/dist/server/cli/improvisation-output-queue.js.map +1 -0
  73. package/dist/server/cli/improvisation-retry.d.ts +21 -51
  74. package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
  75. package/dist/server/cli/improvisation-retry.js +18 -434
  76. package/dist/server/cli/improvisation-retry.js.map +1 -1
  77. package/dist/server/cli/improvisation-session-manager.d.ts +10 -8
  78. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  79. package/dist/server/cli/improvisation-session-manager.js +53 -149
  80. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  81. package/dist/server/cli/improvisation-types.d.ts.map +1 -1
  82. package/dist/server/cli/improvisation-types.js +0 -1
  83. package/dist/server/cli/improvisation-types.js.map +1 -1
  84. package/dist/server/cli/retry/retry-best-result.d.ts +4 -0
  85. package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -0
  86. package/dist/server/cli/retry/retry-best-result.js +60 -0
  87. package/dist/server/cli/retry/retry-best-result.js.map +1 -0
  88. package/dist/server/cli/retry/retry-context-loss.d.ts +6 -0
  89. package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -0
  90. package/dist/server/cli/retry/retry-context-loss.js +67 -0
  91. package/dist/server/cli/retry/retry-context-loss.js.map +1 -0
  92. package/dist/server/cli/retry/retry-premature-completion.d.ts +5 -0
  93. package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -0
  94. package/dist/server/cli/retry/retry-premature-completion.js +80 -0
  95. package/dist/server/cli/retry/retry-premature-completion.js.map +1 -0
  96. package/dist/server/cli/retry/retry-recovery-strategies.d.ts +13 -0
  97. package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -0
  98. package/dist/server/cli/retry/retry-recovery-strategies.js +165 -0
  99. package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -0
  100. package/dist/server/cli/retry/retry-resume-strategy.d.ts +12 -0
  101. package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -0
  102. package/dist/server/cli/retry/retry-resume-strategy.js +21 -0
  103. package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -0
  104. package/dist/server/cli/retry/retry-runner-factory.d.ts +11 -0
  105. package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -0
  106. package/dist/server/cli/retry/retry-runner-factory.js +59 -0
  107. package/dist/server/cli/retry/retry-runner-factory.js.map +1 -0
  108. package/dist/server/cli/retry/retry-tool-results.d.ts +9 -0
  109. package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -0
  110. package/dist/server/cli/retry/retry-tool-results.js +23 -0
  111. package/dist/server/cli/retry/retry-tool-results.js.map +1 -0
  112. package/dist/server/cli/retry/retry-types.d.ts +30 -0
  113. package/dist/server/cli/retry/retry-types.d.ts.map +1 -0
  114. package/dist/server/cli/retry/retry-types.js +3 -0
  115. package/dist/server/cli/retry/retry-types.js.map +1 -0
  116. package/dist/server/index.js +21 -110
  117. package/dist/server/index.js.map +1 -1
  118. package/dist/server/mcp/bouncer-cli.js +0 -1
  119. package/dist/server/mcp/bouncer-cli.js.map +1 -1
  120. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
  121. package/dist/server/mcp/bouncer-haiku.js +0 -1
  122. package/dist/server/mcp/bouncer-haiku.js.map +1 -1
  123. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  124. package/dist/server/mcp/bouncer-integration.js +0 -1
  125. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  126. package/dist/server/mcp/security-analysis.d.ts.map +1 -1
  127. package/dist/server/mcp/security-analysis.js +0 -1
  128. package/dist/server/mcp/security-analysis.js.map +1 -1
  129. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  130. package/dist/server/mcp/security-audit.js +0 -1
  131. package/dist/server/mcp/security-audit.js.map +1 -1
  132. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  133. package/dist/server/mcp/security-patterns.js +0 -1
  134. package/dist/server/mcp/security-patterns.js.map +1 -1
  135. package/dist/server/mcp/server.js +0 -1
  136. package/dist/server/mcp/server.js.map +1 -1
  137. package/dist/server/routes/files.d.ts.map +1 -1
  138. package/dist/server/routes/files.js +0 -1
  139. package/dist/server/routes/files.js.map +1 -1
  140. package/dist/server/routes/improvise.d.ts.map +1 -1
  141. package/dist/server/routes/improvise.js +0 -1
  142. package/dist/server/routes/improvise.js.map +1 -1
  143. package/dist/server/routes/index.d.ts.map +1 -1
  144. package/dist/server/routes/index.js +0 -1
  145. package/dist/server/routes/index.js.map +1 -1
  146. package/dist/server/routes/instances.d.ts.map +1 -1
  147. package/dist/server/routes/instances.js +0 -1
  148. package/dist/server/routes/instances.js.map +1 -1
  149. package/dist/server/routes/notifications.d.ts.map +1 -1
  150. package/dist/server/routes/notifications.js +0 -1
  151. package/dist/server/routes/notifications.js.map +1 -1
  152. package/dist/server/server-setup.d.ts +16 -1
  153. package/dist/server/server-setup.d.ts.map +1 -1
  154. package/dist/server/server-setup.js +107 -1
  155. package/dist/server/server-setup.js.map +1 -1
  156. package/dist/server/services/analytics.d.ts.map +1 -1
  157. package/dist/server/services/analytics.js +0 -1
  158. package/dist/server/services/analytics.js.map +1 -1
  159. package/dist/server/services/auth.d.ts.map +1 -1
  160. package/dist/server/services/auth.js +0 -1
  161. package/dist/server/services/auth.js.map +1 -1
  162. package/dist/server/services/client-id.d.ts.map +1 -1
  163. package/dist/server/services/client-id.js +0 -1
  164. package/dist/server/services/client-id.js.map +1 -1
  165. package/dist/server/services/file-explorer-ops.d.ts.map +1 -1
  166. package/dist/server/services/file-explorer-ops.js +0 -1
  167. package/dist/server/services/file-explorer-ops.js.map +1 -1
  168. package/dist/server/services/files.d.ts.map +1 -1
  169. package/dist/server/services/files.js +0 -1
  170. package/dist/server/services/files.js.map +1 -1
  171. package/dist/server/services/instances.d.ts.map +1 -1
  172. package/dist/server/services/instances.js +0 -1
  173. package/dist/server/services/instances.js.map +1 -1
  174. package/dist/server/services/pathUtils.d.ts.map +1 -1
  175. package/dist/server/services/pathUtils.js +0 -1
  176. package/dist/server/services/pathUtils.js.map +1 -1
  177. package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
  178. package/dist/server/services/plan/agent-loader.js +0 -1
  179. package/dist/server/services/plan/agent-loader.js.map +1 -1
  180. package/dist/server/services/plan/board-config.d.ts +21 -0
  181. package/dist/server/services/plan/board-config.d.ts.map +1 -0
  182. package/dist/server/services/plan/board-config.js +111 -0
  183. package/dist/server/services/plan/board-config.js.map +1 -0
  184. package/dist/server/services/plan/composer.d.ts +1 -1
  185. package/dist/server/services/plan/composer.d.ts.map +1 -1
  186. package/dist/server/services/plan/composer.js +7 -6
  187. package/dist/server/services/plan/composer.js.map +1 -1
  188. package/dist/server/services/plan/config-installer.d.ts.map +1 -1
  189. package/dist/server/services/plan/config-installer.js +0 -1
  190. package/dist/server/services/plan/config-installer.js.map +1 -1
  191. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  192. package/dist/server/services/plan/dependency-resolver.js +0 -1
  193. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  194. package/dist/server/services/plan/executor.d.ts +48 -48
  195. package/dist/server/services/plan/executor.d.ts.map +1 -1
  196. package/dist/server/services/plan/executor.js +202 -458
  197. package/dist/server/services/plan/executor.js.map +1 -1
  198. package/dist/server/services/plan/front-matter.d.ts.map +1 -1
  199. package/dist/server/services/plan/front-matter.js +0 -1
  200. package/dist/server/services/plan/front-matter.js.map +1 -1
  201. package/dist/server/services/plan/issue-classification.d.ts.map +1 -1
  202. package/dist/server/services/plan/issue-classification.js +0 -1
  203. package/dist/server/services/plan/issue-classification.js.map +1 -1
  204. package/dist/server/services/plan/issue-loader.d.ts +16 -0
  205. package/dist/server/services/plan/issue-loader.d.ts.map +1 -0
  206. package/dist/server/services/plan/issue-loader.js +45 -0
  207. package/dist/server/services/plan/issue-loader.js.map +1 -0
  208. package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
  209. package/dist/server/services/plan/issue-prompt-builder.js +0 -1
  210. package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
  211. package/dist/server/services/plan/issue-retry.d.ts +3 -1
  212. package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
  213. package/dist/server/services/plan/issue-retry.js +2 -1
  214. package/dist/server/services/plan/issue-retry.js.map +1 -1
  215. package/dist/server/services/plan/issue-writer.d.ts +34 -0
  216. package/dist/server/services/plan/issue-writer.d.ts.map +1 -0
  217. package/dist/server/services/plan/issue-writer.js +109 -0
  218. package/dist/server/services/plan/issue-writer.js.map +1 -0
  219. package/dist/server/services/plan/output-manager.js +2 -2
  220. package/dist/server/services/plan/output-manager.js.map +1 -1
  221. package/dist/server/services/plan/parser-core.d.ts.map +1 -1
  222. package/dist/server/services/plan/parser-core.js +0 -1
  223. package/dist/server/services/plan/parser-core.js.map +1 -1
  224. package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
  225. package/dist/server/services/plan/parser-migration.js +0 -1
  226. package/dist/server/services/plan/parser-migration.js.map +1 -1
  227. package/dist/server/services/plan/parser.d.ts.map +1 -1
  228. package/dist/server/services/plan/parser.js +0 -1
  229. package/dist/server/services/plan/parser.js.map +1 -1
  230. package/dist/server/services/plan/progress-log.d.ts +11 -0
  231. package/dist/server/services/plan/progress-log.d.ts.map +1 -0
  232. package/dist/server/services/plan/progress-log.js +80 -0
  233. package/dist/server/services/plan/progress-log.js.map +1 -0
  234. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
  235. package/dist/server/services/plan/prompt-builder.js +48 -32
  236. package/dist/server/services/plan/prompt-builder.js.map +1 -1
  237. package/dist/server/services/plan/readiness-planner.d.ts +15 -0
  238. package/dist/server/services/plan/readiness-planner.d.ts.map +1 -0
  239. package/dist/server/services/plan/readiness-planner.js +40 -0
  240. package/dist/server/services/plan/readiness-planner.js.map +1 -0
  241. package/dist/server/services/plan/review-gate.d.ts +31 -0
  242. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  243. package/dist/server/services/plan/review-gate.js +52 -3
  244. package/dist/server/services/plan/review-gate.js.map +1 -1
  245. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  246. package/dist/server/services/plan/state-reconciler.js +0 -1
  247. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  248. package/dist/server/services/plan/types.d.ts.map +1 -1
  249. package/dist/server/services/plan/types.js +0 -1
  250. package/dist/server/services/plan/types.js.map +1 -1
  251. package/dist/server/services/plan/watcher.d.ts.map +1 -1
  252. package/dist/server/services/plan/watcher.js +0 -1
  253. package/dist/server/services/plan/watcher.js.map +1 -1
  254. package/dist/server/services/platform-credentials.d.ts.map +1 -1
  255. package/dist/server/services/platform-credentials.js +0 -1
  256. package/dist/server/services/platform-credentials.js.map +1 -1
  257. package/dist/server/services/platform-token-lifecycle.d.ts +70 -0
  258. package/dist/server/services/platform-token-lifecycle.d.ts.map +1 -0
  259. package/dist/server/services/platform-token-lifecycle.js +156 -0
  260. package/dist/server/services/platform-token-lifecycle.js.map +1 -0
  261. package/dist/server/services/platform.d.ts +25 -4
  262. package/dist/server/services/platform.d.ts.map +1 -1
  263. package/dist/server/services/platform.js +150 -92
  264. package/dist/server/services/platform.js.map +1 -1
  265. package/dist/server/services/sentry.d.ts.map +1 -1
  266. package/dist/server/services/sentry.js +0 -1
  267. package/dist/server/services/sentry.js.map +1 -1
  268. package/dist/server/services/settings.d.ts.map +1 -1
  269. package/dist/server/services/settings.js +0 -1
  270. package/dist/server/services/settings.js.map +1 -1
  271. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  272. package/dist/server/services/terminal/pty-manager.js +0 -1
  273. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  274. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -1
  275. package/dist/server/services/terminal/pty-utils.js +0 -1
  276. package/dist/server/services/terminal/pty-utils.js.map +1 -1
  277. package/dist/server/services/websocket/autocomplete.d.ts.map +1 -1
  278. package/dist/server/services/websocket/autocomplete.js +0 -1
  279. package/dist/server/services/websocket/autocomplete.js.map +1 -1
  280. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -1
  281. package/dist/server/services/websocket/file-definition-handlers.js +0 -1
  282. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -1
  283. package/dist/server/services/websocket/file-download-handler.d.ts +17 -0
  284. package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -0
  285. package/dist/server/services/websocket/file-download-handler.js +164 -0
  286. package/dist/server/services/websocket/file-download-handler.js.map +1 -0
  287. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  288. package/dist/server/services/websocket/file-explorer-handlers.js +0 -1
  289. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  290. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -1
  291. package/dist/server/services/websocket/file-search-handlers.js +0 -1
  292. package/dist/server/services/websocket/file-search-handlers.js.map +1 -1
  293. package/dist/server/services/websocket/file-upload-handler.d.ts +2 -3
  294. package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -1
  295. package/dist/server/services/websocket/file-upload-handler.js +4 -7
  296. package/dist/server/services/websocket/file-upload-handler.js.map +1 -1
  297. package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
  298. package/dist/server/services/websocket/file-utils.js +0 -1
  299. package/dist/server/services/websocket/file-utils.js.map +1 -1
  300. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
  301. package/dist/server/services/websocket/git-branch-handlers.js +0 -1
  302. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
  303. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -1
  304. package/dist/server/services/websocket/git-diff-handlers.js +0 -1
  305. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -1
  306. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  307. package/dist/server/services/websocket/git-handlers.js +58 -6
  308. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  309. package/dist/server/services/websocket/git-head-watcher.d.ts.map +1 -1
  310. package/dist/server/services/websocket/git-head-watcher.js +0 -1
  311. package/dist/server/services/websocket/git-head-watcher.js.map +1 -1
  312. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -1
  313. package/dist/server/services/websocket/git-log-handlers.js +0 -1
  314. package/dist/server/services/websocket/git-log-handlers.js.map +1 -1
  315. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  316. package/dist/server/services/websocket/git-pr-handlers.js +0 -1
  317. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  318. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -1
  319. package/dist/server/services/websocket/git-tag-handlers.js +0 -1
  320. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -1
  321. package/dist/server/services/websocket/git-utils.d.ts +18 -3
  322. package/dist/server/services/websocket/git-utils.d.ts.map +1 -1
  323. package/dist/server/services/websocket/git-utils.js +58 -8
  324. package/dist/server/services/websocket/git-utils.js.map +1 -1
  325. package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
  326. package/dist/server/services/websocket/git-worktree-handlers.js +258 -16
  327. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
  328. package/dist/server/services/websocket/handler-context.d.ts +15 -0
  329. package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
  330. package/dist/server/services/websocket/handler-context.js +0 -1
  331. package/dist/server/services/websocket/handler-context.js.map +1 -1
  332. package/dist/server/services/websocket/handler.d.ts +7 -0
  333. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  334. package/dist/server/services/websocket/handler.js +76 -15
  335. package/dist/server/services/websocket/handler.js.map +1 -1
  336. package/dist/server/services/websocket/index.d.ts.map +1 -1
  337. package/dist/server/services/websocket/index.js +0 -1
  338. package/dist/server/services/websocket/index.js.map +1 -1
  339. package/dist/server/services/websocket/msg-id-tracker.d.ts +21 -0
  340. package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -0
  341. package/dist/server/services/websocket/msg-id-tracker.js +76 -0
  342. package/dist/server/services/websocket/msg-id-tracker.js.map +1 -0
  343. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
  344. package/dist/server/services/websocket/plan-board-handlers.js +0 -1
  345. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
  346. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -1
  347. package/dist/server/services/websocket/plan-execution-handlers.js +6 -2
  348. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
  349. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  350. package/dist/server/services/websocket/plan-handlers.js +0 -1
  351. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  352. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
  353. package/dist/server/services/websocket/plan-helpers.js +0 -1
  354. package/dist/server/services/websocket/plan-helpers.js.map +1 -1
  355. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
  356. package/dist/server/services/websocket/plan-issue-handlers.js +0 -1
  357. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
  358. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
  359. package/dist/server/services/websocket/plan-sprint-handlers.js +0 -1
  360. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
  361. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -1
  362. package/dist/server/services/websocket/quality-complexity.js +0 -1
  363. package/dist/server/services/websocket/quality-complexity.js.map +1 -1
  364. package/dist/server/services/websocket/quality-grading.d.ts +46 -0
  365. package/dist/server/services/websocket/quality-grading.d.ts.map +1 -0
  366. package/dist/server/services/websocket/quality-grading.js +482 -0
  367. package/dist/server/services/websocket/quality-grading.js.map +1 -0
  368. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  369. package/dist/server/services/websocket/quality-handlers.js +15 -4
  370. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  371. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -1
  372. package/dist/server/services/websocket/quality-linting.js +0 -1
  373. package/dist/server/services/websocket/quality-linting.js.map +1 -1
  374. package/dist/server/services/websocket/quality-persistence.d.ts +14 -0
  375. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
  376. package/dist/server/services/websocket/quality-persistence.js +28 -12
  377. package/dist/server/services/websocket/quality-persistence.js.map +1 -1
  378. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
  379. package/dist/server/services/websocket/quality-review-agent.js +2 -3
  380. package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
  381. package/dist/server/services/websocket/quality-service.d.ts +3 -1
  382. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  383. package/dist/server/services/websocket/quality-service.js +53 -58
  384. package/dist/server/services/websocket/quality-service.js.map +1 -1
  385. package/dist/server/services/websocket/quality-tools.d.ts +1 -1
  386. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
  387. package/dist/server/services/websocket/quality-tools.js +6 -3
  388. package/dist/server/services/websocket/quality-tools.js.map +1 -1
  389. package/dist/server/services/websocket/quality-types.d.ts +18 -2
  390. package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
  391. package/dist/server/services/websocket/quality-types.js +0 -1
  392. package/dist/server/services/websocket/quality-types.js.map +1 -1
  393. package/dist/server/services/websocket/session-handlers.d.ts +48 -2
  394. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  395. package/dist/server/services/websocket/session-handlers.js +204 -66
  396. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  397. package/dist/server/services/websocket/session-history.d.ts.map +1 -1
  398. package/dist/server/services/websocket/session-history.js +0 -1
  399. package/dist/server/services/websocket/session-history.js.map +1 -1
  400. package/dist/server/services/websocket/session-initialization.d.ts +2 -2
  401. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
  402. package/dist/server/services/websocket/session-initialization.js +75 -18
  403. package/dist/server/services/websocket/session-initialization.js.map +1 -1
  404. package/dist/server/services/websocket/session-registry.d.ts +29 -1
  405. package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
  406. package/dist/server/services/websocket/session-registry.js +53 -5
  407. package/dist/server/services/websocket/session-registry.js.map +1 -1
  408. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
  409. package/dist/server/services/websocket/settings-handlers.js +0 -1
  410. package/dist/server/services/websocket/settings-handlers.js.map +1 -1
  411. package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
  412. package/dist/server/services/websocket/skill-handlers.js +0 -1
  413. package/dist/server/services/websocket/skill-handlers.js.map +1 -1
  414. package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -1
  415. package/dist/server/services/websocket/skill-watcher.js +0 -1
  416. package/dist/server/services/websocket/skill-watcher.js.map +1 -1
  417. package/dist/server/services/websocket/tab-broadcast.d.ts +24 -0
  418. package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -0
  419. package/dist/server/services/websocket/tab-broadcast.js +12 -0
  420. package/dist/server/services/websocket/tab-broadcast.js.map +1 -0
  421. package/dist/server/services/websocket/tab-event-buffer.d.ts +103 -0
  422. package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -0
  423. package/dist/server/services/websocket/tab-event-buffer.js +106 -0
  424. package/dist/server/services/websocket/tab-event-buffer.js.map +1 -0
  425. package/dist/server/services/websocket/tab-event-replay.d.ts +20 -0
  426. package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -0
  427. package/dist/server/services/websocket/tab-event-replay.js +20 -0
  428. package/dist/server/services/websocket/tab-event-replay.js.map +1 -0
  429. package/dist/server/services/websocket/tab-handlers.d.ts +0 -1
  430. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
  431. package/dist/server/services/websocket/tab-handlers.js +2 -10
  432. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  433. package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
  434. package/dist/server/services/websocket/terminal-handlers.js +39 -4
  435. package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
  436. package/dist/server/services/websocket/types.d.ts +17 -8
  437. package/dist/server/services/websocket/types.d.ts.map +1 -1
  438. package/dist/server/services/websocket/types.js +8 -7
  439. package/dist/server/services/websocket/types.js.map +1 -1
  440. package/dist/server/utils/agent-manager.d.ts.map +1 -1
  441. package/dist/server/utils/agent-manager.js +0 -1
  442. package/dist/server/utils/agent-manager.js.map +1 -1
  443. package/dist/server/utils/paths.d.ts.map +1 -1
  444. package/dist/server/utils/paths.js +0 -1
  445. package/dist/server/utils/paths.js.map +1 -1
  446. package/dist/server/utils/port-manager.d.ts.map +1 -1
  447. package/dist/server/utils/port-manager.js +0 -1
  448. package/dist/server/utils/port-manager.js.map +1 -1
  449. package/dist/server/utils/port.d.ts.map +1 -1
  450. package/dist/server/utils/port.js +0 -1
  451. package/dist/server/utils/port.js.map +1 -1
  452. package/package.json +2 -2
  453. package/server/README.md +1 -1
  454. package/server/cli/headless/claude-invoker-process.ts +0 -1
  455. package/server/cli/headless/claude-invoker-stall.ts +7 -3
  456. package/server/cli/headless/claude-invoker-stream.ts +0 -1
  457. package/server/cli/headless/claude-invoker-tools.ts +0 -1
  458. package/server/cli/headless/claude-invoker.ts +1 -2
  459. package/server/cli/headless/haiku-assessments.ts +0 -1
  460. package/server/cli/headless/headless-logger.ts +0 -1
  461. package/server/cli/headless/index.ts +0 -1
  462. package/server/cli/headless/native-timeout-detector.ts +0 -1
  463. package/server/cli/headless/output-utils.ts +0 -1
  464. package/server/cli/headless/prompt-utils.ts +0 -1
  465. package/server/cli/headless/resilient-runner.ts +0 -1
  466. package/server/cli/headless/retry-strategies.ts +0 -1
  467. package/server/cli/headless/runner.ts +67 -73
  468. package/server/cli/headless/stall-assessor.ts +9 -5
  469. package/server/cli/headless/tool-watchdog.ts +0 -1
  470. package/server/cli/headless/types.ts +1 -2
  471. package/server/cli/improvisation-attachments.ts +0 -1
  472. package/server/cli/improvisation-history-store.ts +61 -0
  473. package/server/cli/improvisation-movements.ts +119 -0
  474. package/server/cli/improvisation-output-queue.ts +41 -0
  475. package/server/cli/improvisation-retry.ts +25 -601
  476. package/server/cli/improvisation-session-manager.ts +74 -161
  477. package/server/cli/improvisation-types.ts +0 -1
  478. package/server/cli/retry/retry-best-result.ts +69 -0
  479. package/server/cli/retry/retry-context-loss.ts +86 -0
  480. package/server/cli/retry/retry-premature-completion.ts +112 -0
  481. package/server/cli/retry/retry-recovery-strategies.ts +246 -0
  482. package/server/cli/retry/retry-resume-strategy.ts +32 -0
  483. package/server/cli/retry/retry-runner-factory.ts +69 -0
  484. package/server/cli/retry/retry-tool-results.ts +30 -0
  485. package/server/cli/retry/retry-types.ts +31 -0
  486. package/server/index.ts +37 -124
  487. package/server/mcp/bouncer-cli.ts +0 -1
  488. package/server/mcp/bouncer-haiku.ts +0 -1
  489. package/server/mcp/bouncer-integration.ts +0 -1
  490. package/server/mcp/security-analysis.ts +0 -1
  491. package/server/mcp/security-audit.ts +0 -1
  492. package/server/mcp/security-patterns.ts +0 -1
  493. package/server/mcp/server.ts +0 -1
  494. package/server/routes/files.ts +0 -1
  495. package/server/routes/improvise.ts +0 -1
  496. package/server/routes/index.ts +0 -1
  497. package/server/routes/instances.ts +0 -1
  498. package/server/routes/notifications.ts +0 -1
  499. package/server/server-setup.ts +126 -2
  500. package/server/services/analytics.ts +0 -1
  501. package/server/services/auth.ts +0 -1
  502. package/server/services/client-id.ts +0 -1
  503. package/server/services/file-explorer-ops.ts +0 -1
  504. package/server/services/files.ts +0 -1
  505. package/server/services/instances.ts +0 -1
  506. package/server/services/pathUtils.ts +0 -1
  507. package/server/services/plan/agent-loader.ts +0 -1
  508. package/server/services/plan/agents/assess-stall.md +11 -4
  509. package/server/services/plan/agents/code-review.md +13 -11
  510. package/server/services/plan/board-config.ts +121 -0
  511. package/server/services/plan/composer.ts +7 -6
  512. package/server/services/plan/config-installer.ts +0 -1
  513. package/server/services/plan/dependency-resolver.ts +0 -1
  514. package/server/services/plan/executor.ts +259 -470
  515. package/server/services/plan/front-matter.ts +0 -1
  516. package/server/services/plan/issue-classification.ts +0 -1
  517. package/server/services/plan/issue-loader.ts +63 -0
  518. package/server/services/plan/issue-prompt-builder.ts +0 -1
  519. package/server/services/plan/issue-retry.ts +5 -2
  520. package/server/services/plan/issue-writer.ts +136 -0
  521. package/server/services/plan/output-manager.ts +2 -2
  522. package/server/services/plan/parser-core.ts +0 -1
  523. package/server/services/plan/parser-migration.ts +0 -1
  524. package/server/services/plan/parser.ts +0 -1
  525. package/server/services/plan/progress-log.ts +91 -0
  526. package/server/services/plan/prompt-builder.ts +73 -36
  527. package/server/services/plan/readiness-planner.ts +49 -0
  528. package/server/services/plan/review-gate.ts +102 -3
  529. package/server/services/plan/state-reconciler.ts +0 -1
  530. package/server/services/plan/types.ts +0 -1
  531. package/server/services/plan/watcher.ts +0 -1
  532. package/server/services/platform-credentials.ts +0 -1
  533. package/server/services/platform-token-lifecycle.ts +171 -0
  534. package/server/services/platform.ts +168 -105
  535. package/server/services/sentry.ts +0 -1
  536. package/server/services/settings.ts +0 -1
  537. package/server/services/terminal/pty-manager.ts +0 -1
  538. package/server/services/terminal/pty-utils.ts +0 -1
  539. package/server/services/websocket/autocomplete.ts +0 -1
  540. package/server/services/websocket/file-definition-handlers.ts +0 -1
  541. package/server/services/websocket/file-download-handler.ts +190 -0
  542. package/server/services/websocket/file-explorer-handlers.ts +0 -1
  543. package/server/services/websocket/file-search-handlers.ts +0 -1
  544. package/server/services/websocket/file-upload-handler.ts +6 -5
  545. package/server/services/websocket/file-utils.ts +0 -1
  546. package/server/services/websocket/git-branch-handlers.ts +0 -1
  547. package/server/services/websocket/git-diff-handlers.ts +0 -1
  548. package/server/services/websocket/git-handlers.ts +66 -10
  549. package/server/services/websocket/git-head-watcher.ts +0 -1
  550. package/server/services/websocket/git-log-handlers.ts +0 -1
  551. package/server/services/websocket/git-pr-handlers.ts +0 -1
  552. package/server/services/websocket/git-tag-handlers.ts +0 -1
  553. package/server/services/websocket/git-utils.ts +69 -9
  554. package/server/services/websocket/git-worktree-handlers.ts +289 -19
  555. package/server/services/websocket/handler-context.ts +15 -1
  556. package/server/services/websocket/handler.ts +79 -16
  557. package/server/services/websocket/index.ts +0 -1
  558. package/server/services/websocket/msg-id-tracker.ts +83 -0
  559. package/server/services/websocket/plan-board-handlers.ts +0 -1
  560. package/server/services/websocket/plan-execution-handlers.ts +6 -2
  561. package/server/services/websocket/plan-handlers.ts +0 -1
  562. package/server/services/websocket/plan-helpers.ts +0 -1
  563. package/server/services/websocket/plan-issue-handlers.ts +0 -1
  564. package/server/services/websocket/plan-sprint-handlers.ts +0 -1
  565. package/server/services/websocket/quality-complexity.ts +0 -1
  566. package/server/services/websocket/quality-grading.ts +611 -0
  567. package/server/services/websocket/quality-handlers.ts +16 -4
  568. package/server/services/websocket/quality-linting.ts +0 -1
  569. package/server/services/websocket/quality-persistence.ts +30 -8
  570. package/server/services/websocket/quality-review-agent.ts +2 -3
  571. package/server/services/websocket/quality-service.ts +54 -55
  572. package/server/services/websocket/quality-tools.ts +11 -3
  573. package/server/services/websocket/quality-types.ts +21 -3
  574. package/server/services/websocket/session-handlers.ts +213 -69
  575. package/server/services/websocket/session-history.ts +0 -1
  576. package/server/services/websocket/session-initialization.ts +83 -20
  577. package/server/services/websocket/session-registry.ts +61 -5
  578. package/server/services/websocket/settings-handlers.ts +0 -1
  579. package/server/services/websocket/skill-handlers.ts +0 -1
  580. package/server/services/websocket/skill-watcher.ts +0 -1
  581. package/server/services/websocket/tab-broadcast.ts +37 -0
  582. package/server/services/websocket/tab-event-buffer.ts +158 -0
  583. package/server/services/websocket/tab-event-replay.ts +41 -0
  584. package/server/services/websocket/tab-handlers.ts +2 -10
  585. package/server/services/websocket/terminal-handlers.ts +39 -3
  586. package/server/services/websocket/types.ts +19 -7
  587. package/server/utils/agent-manager.ts +0 -1
  588. package/server/utils/paths.ts +0 -1
  589. package/server/utils/port-manager.ts +0 -1
  590. package/server/utils/port.ts +0 -1
@@ -1,13 +1,26 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
- // Licensed under the MIT License. See LICENSE file for details.
3
2
 
4
3
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
4
  import { dirname, join } from 'node:path';
6
5
  import { resolvePmDir } from '../plan/parser.js';
7
6
  import type { Workspace } from '../plan/types.js';
8
7
  import { executeGitCommand, handleGitStatus, spawnWithOutput } from './git-handlers.js';
8
+ import { handleGitLog } from './git-log-handlers.js';
9
+ import { parseGitStatus } from './git-utils.js';
9
10
  import type { HandlerContext } from './handler-context.js';
10
- import type { WebSocketMessage, WorktreeInfo, WSContext } from './types.js';
11
+ import type { GitFileStatus, WebSocketMessage, WorktreeInfo, WSContext } from './types.js';
12
+
13
+ /**
14
+ * A file in the merge target's working tree whose uncommitted state would be
15
+ * lost if the merge proceeds. Used to drive the merge dialog's blocker state
16
+ * before the merge attempt and to translate the post-flight git error if a
17
+ * race lets one slip through.
18
+ */
19
+ interface MergeBlocker {
20
+ path: string;
21
+ status: GitFileStatus['status'];
22
+ staged: boolean;
23
+ }
11
24
 
12
25
  function persistBoardWorktree(workingDir: string, boardId: string, worktreePath: string | null, branch: string | null): void {
13
26
  const pmDir = resolvePmDir(workingDir);
@@ -43,6 +56,8 @@ export async function handleGitWorktreeMessage(ctx: HandlerContext, ws: WSContex
43
56
  gitWorktreeMerge: () => handleGitWorktreeMerge(ctx, ws, msg, tabId, gitDir),
44
57
  gitMergeAbort: () => handleGitMergeAbort(ctx, ws, tabId, gitDir),
45
58
  gitMergeComplete: () => handleGitMergeComplete(ctx, ws, msg, tabId, gitDir),
59
+ gitMergeStashPop: () => handleGitMergeStashPop(ctx, ws, msg, tabId, gitDir),
60
+ gitMergeDiscardBlockers: () => handleGitMergeDiscardBlockers(ctx, ws, msg, tabId, gitDir),
46
61
  };
47
62
  await handlers[msg.type]?.();
48
63
  }
@@ -214,7 +229,7 @@ export async function handleTabWorktreeSwitch(ctx: HandlerContext, ws: WSContext
214
229
  persistBoardWorktree(workingDir, resolvedTabId, null, null);
215
230
  }
216
231
  ctx.send(ws, { type: 'tabWorktreeSwitched', tabId: resolvedTabId, data: { tabId: resolvedTabId, worktreePath: null, branch: null } });
217
- handleGitStatus(ctx, ws, resolvedTabId, workingDir);
232
+ refreshScopeAfterWorktreeSwitch(ctx, ws, resolvedTabId, workingDir);
218
233
  return;
219
234
  }
220
235
 
@@ -229,12 +244,38 @@ export async function handleTabWorktreeSwitch(ctx: HandlerContext, ws: WSContext
229
244
  }
230
245
 
231
246
  ctx.send(ws, { type: 'tabWorktreeSwitched', tabId: resolvedTabId, data: { tabId: resolvedTabId, worktreePath, branch } });
232
- handleGitStatus(ctx, ws, resolvedTabId, worktreePath);
247
+ refreshScopeAfterWorktreeSwitch(ctx, ws, resolvedTabId, worktreePath);
233
248
  } catch (error: unknown) {
234
249
  ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
235
250
  }
236
251
  }
237
252
 
253
+ /**
254
+ * After a worktree switch, re-fetch everything that's worktree-specific so
255
+ * the client sees a complete, consistent view of the newly-selected workspace
256
+ * from a single `tabWorktreeSwitched` event. Keeping the refresh on the
257
+ * server side means the client has one signal to react to instead of having
258
+ * to orchestrate status/log/... fetches itself.
259
+ *
260
+ * Branches and the worktree list are NOT re-fetched: they're repo-wide, not
261
+ * worktree-specific.
262
+ *
263
+ * Fire-and-forget: the switch itself has already been acknowledged via
264
+ * `tabWorktreeSwitched`. `handleGitStatus` and `handleGitLog` each own their
265
+ * own error handling (they send `gitError` scoped to the correct tabId).
266
+ * Awaiting here would let any unexpected throw escape into the caller's
267
+ * outer try/catch and produce a misleading `gitError` on the original
268
+ * dispatch tabId after success has already been signalled.
269
+ */
270
+ function refreshScopeAfterWorktreeSwitch(ctx: HandlerContext, ws: WSContext, tabId: string, gitDir: string): void {
271
+ (async () => {
272
+ await handleGitStatus(ctx, ws, tabId, gitDir);
273
+ await handleGitLog(ctx, ws, { type: 'gitLog', tabId, data: { limit: 20 } }, tabId, gitDir);
274
+ })().catch((error: unknown) => {
275
+ console.error('[handleTabWorktreeSwitch] scope refresh failed:', error);
276
+ });
277
+ }
278
+
238
279
  async function pushWithUpstreamRetry(
239
280
  worktreePath: string,
240
281
  pushRemote: string,
@@ -312,6 +353,64 @@ async function handleGitWorktreeCreatePR(ctx: HandlerContext, ws: WSContext, msg
312
353
  }
313
354
  }
314
355
 
356
+ /**
357
+ * Files in the target worktree's working tree whose dirty state intersects
358
+ * the set of files the merge would touch. These are the rows that drive the
359
+ * merge dialog's "uncommitted changes on <target>" blocker UI — surfacing
360
+ * them preflight prevents the user from ever hitting git's raw "would be
361
+ * overwritten" error.
362
+ *
363
+ * Detection strategy:
364
+ * - `git status --porcelain=v1` on the target's worktree → all dirty paths
365
+ * - `git diff --name-only target..source` → all paths the merge would touch
366
+ * - intersection = blockers
367
+ *
368
+ * Untracked files are included via the porcelain output (they collide with
369
+ * incoming additions of the same path). Files that are dirty but outside the
370
+ * merge's diff are NOT blockers — git would carry them through cleanly.
371
+ */
372
+ async function detectMergeBlockers(
373
+ targetWorktreePath: string,
374
+ sourceBranch: string,
375
+ targetBranch: string,
376
+ ): Promise<MergeBlocker[]> {
377
+ const statusResult = await executeGitCommand(['status', '--porcelain=v1'], targetWorktreePath);
378
+ if (statusResult.exitCode !== 0) return [];
379
+ const { staged, unstaged, untracked } = parseGitStatus(statusResult.stdout);
380
+
381
+ const diffResult = await executeGitCommand(
382
+ ['diff', '--name-only', `${targetBranch}..${sourceBranch}`],
383
+ targetWorktreePath,
384
+ );
385
+ if (diffResult.exitCode !== 0) return [];
386
+ const mergeTouches = new Set(
387
+ diffResult.stdout.split('\n').map(s => s.trim()).filter(Boolean),
388
+ );
389
+ if (mergeTouches.size === 0) return [];
390
+
391
+ // Prefer the worktree (unstaged) status when both index and worktree are
392
+ // dirty — that's what the user would lose. Fall back to staged, then
393
+ // untracked. Order in the result list groups deletions last so the UI can
394
+ // surface them where users expect.
395
+ const seen = new Map<string, MergeBlocker>();
396
+ const consider = (entries: GitFileStatus[]) => {
397
+ for (const entry of entries) {
398
+ if (!mergeTouches.has(entry.path)) continue;
399
+ if (seen.has(entry.path)) continue;
400
+ seen.set(entry.path, { path: entry.path, status: entry.status, staged: entry.staged });
401
+ }
402
+ };
403
+ consider(unstaged);
404
+ consider(staged);
405
+ consider(untracked);
406
+
407
+ return Array.from(seen.values()).sort((a, b) => {
408
+ if (a.status === 'D' && b.status !== 'D') return 1;
409
+ if (a.status !== 'D' && b.status === 'D') return -1;
410
+ return a.path.localeCompare(b.path);
411
+ });
412
+ }
413
+
315
414
  async function handleGitMergePreview(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
316
415
  try {
317
416
  const { sourceBranch, targetBranch } = msg.data || {};
@@ -347,10 +446,30 @@ async function handleGitMergePreview(ctx: HandlerContext, ws: WSContext, msg: We
347
446
  return { hash: hash.trim(), message: rest.join('|').trim() };
348
447
  });
349
448
 
449
+ // The merge always lands on the main worktree currently checked out to
450
+ // the target branch. If main isn't on `targetBranch` we can't preflight
451
+ // blockers (the dirty state wouldn't be the relevant one), so skip the
452
+ // check and let the post-flight handler surface the branch-mismatch
453
+ // error like before.
454
+ const mainPath = await resolveMainWorktreePath(workingDir);
455
+ const mainBranchResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], mainPath);
456
+ const mainOnTarget = mainBranchResult.stdout.trim() === targetBranch;
457
+ const blockers = mainOnTarget
458
+ ? await detectMergeBlockers(mainPath, sourceBranch, targetBranch)
459
+ : [];
460
+
350
461
  ctx.send(ws, {
351
462
  type: 'gitMergePreviewResult',
352
463
  tabId,
353
- data: { clean, conflicts, stat, commits, ahead: commits.length },
464
+ data: {
465
+ clean,
466
+ conflicts,
467
+ stat,
468
+ commits,
469
+ ahead: commits.length,
470
+ targetWorktreePath: mainPath,
471
+ targetWorktreeBlockers: blockers,
472
+ },
354
473
  });
355
474
  } catch (error: unknown) {
356
475
  ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
@@ -455,9 +574,103 @@ export function findWorktreePathForBranch(porcelainOutput: string, branchName: s
455
574
  return null;
456
575
  }
457
576
 
577
+ /**
578
+ * Parse a git stderr buffer for the "your local changes / untracked working
579
+ * tree files would be overwritten by merge" message. Returned paths feed the
580
+ * web client's blocker UI when a merge slips through preflight (race against
581
+ * a teammate's auto-formatter, stale status, etc.).
582
+ */
583
+ function parseOverwritePaths(stderr: string): string[] {
584
+ const lines = stderr.split('\n');
585
+ const headerIdx = lines.findIndex(l =>
586
+ /your local changes to the following files would be overwritten/i.test(l) ||
587
+ /untracked working tree files would be overwritten/i.test(l),
588
+ );
589
+ if (headerIdx < 0) return [];
590
+ const paths: string[] = [];
591
+ for (let i = headerIdx + 1; i < lines.length; i++) {
592
+ const line = lines[i];
593
+ const trimmed = line.trim();
594
+ if (!trimmed) continue;
595
+ if (/^(merge with strategy|aborting|please commit)/i.test(trimmed)) break;
596
+ // Git indents each path with a tab; tolerate spaces too.
597
+ if (/^[\t ]/.test(line)) {
598
+ paths.push(trimmed);
599
+ }
600
+ }
601
+ return paths;
602
+ }
603
+
604
+ const STASH_MESSAGE_TAG = 'mstro:pre-merge';
605
+
606
+ async function pushMergeStash(targetWorktreePath: string, sourceBranch: string, targetBranch: string): Promise<{ ref: string; sha: string; message: string } | null> {
607
+ const message = `${STASH_MESSAGE_TAG} ${targetBranch} <- ${sourceBranch} @ ${new Date().toISOString()}`;
608
+ const result = await executeGitCommand(['stash', 'push', '-u', '-m', message], targetWorktreePath);
609
+ if (result.exitCode !== 0) return null;
610
+ // "No local changes to save" exits 0 but does not push a stash; detect by
611
+ // checking whether stash@{0}'s message matches what we just wrote.
612
+ const top = await executeGitCommand(['stash', 'list', '-n', '1', '--format=%H%x09%gs'], targetWorktreePath);
613
+ if (top.exitCode !== 0) return null;
614
+ const [sha, ...rest] = top.stdout.trim().split('\t');
615
+ const stashedMsg = rest.join('\t');
616
+ if (!sha || !stashedMsg.includes(STASH_MESSAGE_TAG)) return null;
617
+ return { ref: 'stash@{0}', sha, message };
618
+ }
619
+
620
+ async function popMergeStashBySha(targetWorktreePath: string, sha: string): Promise<{ exitCode: number; error?: string }> {
621
+ const list = await executeGitCommand(['stash', 'list', '--format=%gd%x09%H'], targetWorktreePath);
622
+ if (list.exitCode !== 0) return { exitCode: list.exitCode, error: list.stderr || 'Failed to read stash list' };
623
+ const ref = list.stdout.split('\n').map(l => l.split('\t')).find(([, h]) => h === sha)?.[0];
624
+ if (!ref) return { exitCode: 1, error: 'Stash no longer exists — it may have already been popped or dropped.' };
625
+ const pop = await executeGitCommand(['stash', 'pop', ref], targetWorktreePath);
626
+ return { exitCode: pop.exitCode, error: pop.exitCode !== 0 ? pop.stderr : undefined };
627
+ }
628
+
629
+ /**
630
+ * Decide what shape of merge-failure payload to send the client. Splits the
631
+ * three error modes — index conflict, target-worktree blocker, generic — out
632
+ * of the main handler so each is a self-contained branch.
633
+ */
634
+ async function buildMergeFailurePayload(
635
+ mainPath: string,
636
+ sourceBranch: string,
637
+ targetBranch: string,
638
+ mergeError: string | undefined,
639
+ ): Promise<Record<string, unknown>> {
640
+ const conflictFiles = await detectMergeConflicts(mainPath);
641
+ if (conflictFiles.length > 0) {
642
+ return { success: false, conflictFiles, targetWorktreePath: mainPath };
643
+ }
644
+ const overwritten = parseOverwritePaths(mergeError || '');
645
+ if (overwritten.length > 0) {
646
+ const blockers = await detectMergeBlockers(mainPath, sourceBranch, targetBranch);
647
+ const targetWorktreeBlockers: MergeBlocker[] = blockers.length > 0
648
+ ? blockers
649
+ : overwritten.map(p => ({ path: p, status: 'M' as const, staged: false }));
650
+ return { success: false, targetWorktreePath: mainPath, targetWorktreeBlockers };
651
+ }
652
+ return { success: false, error: mergeError || 'Merge failed', targetWorktreePath: mainPath };
653
+ }
654
+
655
+ function buildMergeSuccessPayload(
656
+ mainPath: string,
657
+ mergeCommit: string,
658
+ warnings: string[],
659
+ stash: { ref: string; sha: string; message: string } | null,
660
+ ): Record<string, unknown> {
661
+ const data: Record<string, unknown> = { success: true, mergeCommit, targetWorktreePath: mainPath };
662
+ if (warnings.length > 0) data.warnings = warnings;
663
+ if (stash) {
664
+ data.stashRef = stash.ref;
665
+ data.stashSha = stash.sha;
666
+ data.stashMessage = stash.message;
667
+ }
668
+ return data;
669
+ }
670
+
458
671
  async function handleGitWorktreeMerge(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
459
672
  try {
460
- const { sourceBranch, targetBranch, strategy, commitMessage, deleteWorktree, deleteBranch } = msg.data || {};
673
+ const { sourceBranch, targetBranch, strategy, commitMessage, deleteWorktree, deleteBranch, stashFirst } = msg.data || {};
461
674
  if (!sourceBranch || !targetBranch || !strategy) {
462
675
  ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Source branch, target branch, and strategy are required' } });
463
676
  return;
@@ -467,40 +680,97 @@ async function handleGitWorktreeMerge(ctx: HandlerContext, ws: WSContext, msg: W
467
680
 
468
681
  const mainBranchResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], mainPath);
469
682
  if (mainBranchResult.stdout.trim() !== targetBranch) {
470
- ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Switch the main worktree to "${targetBranch}" before merging` } });
683
+ ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Switch the main worktree to "${targetBranch}" before merging`, targetWorktreePath: mainPath } });
471
684
  return;
472
685
  }
473
686
 
474
- const headBefore = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
687
+ // Stash first if requested. A no-op result (nothing to stash, e.g. user
688
+ // already cleaned up between preflight and merge) is fine — we still
689
+ // proceed with the merge.
690
+ const stash = stashFirst ? await pushMergeStash(mainPath, sourceBranch, targetBranch) : null;
475
691
 
692
+ const headBefore = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
476
693
  const mergeResult = await executeMergeStrategy(strategy, sourceBranch, commitMessage, mainPath);
694
+
477
695
  if (mergeResult.exitCode !== 0) {
478
- const conflictFiles = await detectMergeConflicts(mainPath);
479
- const data = conflictFiles.length > 0
480
- ? { success: false, conflictFiles }
481
- : { success: false, error: mergeResult.error || 'Merge failed' };
696
+ // Best-effort restore of the user's working tree before reporting.
697
+ // If pop conflicts, the stash stays around and the user can recover it
698
+ // manually we still surface the original merge error.
699
+ if (stash) await popMergeStashBySha(mainPath, stash.sha).catch(() => undefined);
700
+ const data = await buildMergeFailurePayload(mainPath, sourceBranch, targetBranch, mergeResult.error);
482
701
  ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data });
483
702
  return;
484
703
  }
485
704
 
486
705
  const headAfter = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
487
706
  if (headBefore.stdout.trim() === headAfter.stdout.trim()) {
488
- ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Already up to date — "${sourceBranch}" has no new commits to merge into "${targetBranch}"` } });
707
+ if (stash) await popMergeStashBySha(mainPath, stash.sha).catch(() => undefined);
708
+ ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Already up to date — "${sourceBranch}" has no new commits to merge into "${targetBranch}"`, targetWorktreePath: mainPath } });
489
709
  return;
490
710
  }
491
711
 
492
712
  const commitHashResult = await executeGitCommand(['rev-parse', '--short', 'HEAD'], mainPath);
493
713
  const { warnings, removedWorktreePath } = await cleanupAfterMerge(mainPath, sourceBranch, strategy, !!deleteWorktree, !!deleteBranch);
714
+ if (removedWorktreePath) cleanupWorktreeReferences(ctx, workingDir, removedWorktreePath);
494
715
 
495
- if (removedWorktreePath) {
496
- cleanupWorktreeReferences(ctx, workingDir, removedWorktreePath);
716
+ const data = buildMergeSuccessPayload(mainPath, commitHashResult.stdout.trim(), warnings, stash);
717
+ ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data });
718
+ } catch (error: unknown) {
719
+ ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
720
+ }
721
+ }
722
+
723
+ async function handleGitMergeStashPop(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
724
+ try {
725
+ const { stashSha, targetWorktreePath } = msg.data || {};
726
+ if (!stashSha) {
727
+ ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: false, error: 'Missing stash reference' } });
728
+ return;
497
729
  }
730
+ const path = targetWorktreePath || (await resolveMainWorktreePath(workingDir));
731
+ const result = await popMergeStashBySha(path, stashSha);
732
+ if (result.exitCode !== 0) {
733
+ ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: false, error: result.error || 'Failed to pop stash', targetWorktreePath: path } });
734
+ return;
735
+ }
736
+ ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: true, targetWorktreePath: path } });
737
+ // Refresh status so the file explorer / git view reflects the restored changes.
738
+ handleGitStatus(ctx, ws, tabId, path);
739
+ } catch (error: unknown) {
740
+ ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
741
+ }
742
+ }
498
743
 
499
- const data: Record<string, unknown> = { success: true, mergeCommit: commitHashResult.stdout.trim() };
500
- if (warnings.length > 0) {
501
- data.warnings = warnings;
744
+ async function handleGitMergeDiscardBlockers(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
745
+ try {
746
+ const { paths, targetWorktreePath } = msg.data || {};
747
+ if (!Array.isArray(paths) || paths.length === 0) {
748
+ ctx.send(ws, { type: 'gitMergeBlockersDiscarded', tabId, data: { success: false, error: 'No paths to discard' } });
749
+ return;
502
750
  }
503
- ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data });
751
+ const path = targetWorktreePath || (await resolveMainWorktreePath(workingDir));
752
+
753
+ // Restore tracked files (modifications, deletions, stages) from HEAD.
754
+ // `git checkout HEAD -- <paths>` overwrites both index and worktree, which
755
+ // is exactly the "discard" semantics the UI promised the user.
756
+ const checkoutResult = await executeGitCommand(['checkout', 'HEAD', '--', ...paths], path);
757
+ // Untracked files won't be touched by `checkout HEAD --` (git refuses with
758
+ // "did not match any file(s) known to git" when ALL paths are untracked,
759
+ // but mixed lists succeed for the tracked subset). Run `git clean -f` on
760
+ // the originally-supplied list to remove any untracked leftovers — git
761
+ // ignores anything that's already gone, so this is safe to run after.
762
+ const cleanResult = await executeGitCommand(['clean', '-f', '--', ...paths], path);
763
+
764
+ if (checkoutResult.exitCode !== 0 && cleanResult.exitCode !== 0) {
765
+ ctx.send(ws, {
766
+ type: 'gitMergeBlockersDiscarded',
767
+ tabId,
768
+ data: { success: false, error: checkoutResult.stderr || cleanResult.stderr || 'Failed to discard changes', targetWorktreePath: path },
769
+ });
770
+ return;
771
+ }
772
+ ctx.send(ws, { type: 'gitMergeBlockersDiscarded', tabId, data: { success: true, targetWorktreePath: path, paths } });
773
+ handleGitStatus(ctx, ws, tabId, path);
504
774
  } catch (error: unknown) {
505
775
  ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
506
776
  }
@@ -1,13 +1,14 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
- // Licensed under the MIT License. See LICENSE file for details.
3
2
 
4
3
  import type { ChildProcess } from 'node:child_process';
5
4
  import type { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
6
5
  import type { AutocompleteService } from './autocomplete.js';
7
6
  import type { FileUploadHandler } from './file-upload-handler.js';
8
7
  import type { GitHeadWatcher } from './git-head-watcher.js';
8
+ import type { MsgIdTracker } from './msg-id-tracker.js';
9
9
  import type { SessionRegistry } from './session-registry.js';
10
10
  import type { SkillsWatcher } from './skill-watcher.js';
11
+ import type { TabEventBufferRegistry } from './tab-event-buffer.js';
11
12
  import type { WebSocketResponse, WSContext } from './types.js';
12
13
 
13
14
  export interface UsageReport {
@@ -37,6 +38,19 @@ export interface HandlerContext {
37
38
  fileUploadHandler: FileUploadHandler | null;
38
39
  gitHeadWatcher: GitHeadWatcher | null;
39
40
  skillsWatcher: SkillsWatcher | null;
41
+ /**
42
+ * Per-tab replay buffer for tab-scoped broadcasts. Populated by
43
+ * `broadcastTabEvent` (see `tab-broadcast.ts`) so a web client rejoining a
44
+ * tab after a reconnect can request replay of anything it missed during
45
+ * the transport gap. See `tab-event-buffer.ts`.
46
+ */
47
+ tabEventBuffers: TabEventBufferRegistry;
48
+ /**
49
+ * Idempotency tracker for `execute` `msgId`s. Lets the web replay the
50
+ * same prompt across reconnects without causing double execution — the
51
+ * CLI still acks, but skips running the prompt a second time.
52
+ */
53
+ msgIdTracker: MsgIdTracker;
40
54
 
41
55
  // Registry access
42
56
  getRegistry(workingDir: string): SessionRegistry;
@@ -1,5 +1,4 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
- // Licensed under the MIT License. See LICENSE file for details.
3
2
 
4
3
  /**
5
4
  * WebSocket Handler for Improvisation Sessions
@@ -14,20 +13,24 @@ import { homedir } from 'node:os';
14
13
  import { dirname, join } from 'node:path';
15
14
  import type { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
16
15
  import { captureException } from '../sentry.js';
16
+ import { getPTYManager } from '../terminal/pty-manager.js';
17
17
  import { AutocompleteService } from './autocomplete.js';
18
+ import { FileDownloadHandler } from './file-download-handler.js';
18
19
  import { handleFileExplorerMessage, handleFileMessage } from './file-explorer-handlers.js';
19
20
  import { FileUploadHandler } from './file-upload-handler.js';
20
21
  import { handleGitMessage } from './git-handlers.js';
21
22
  import { GitHeadWatcher } from './git-head-watcher.js';
22
23
  import type { HandlerContext, UsageReporter } from './handler-context.js';
24
+ import { MsgIdTracker } from './msg-id-tracker.js';
23
25
  import { handlePlanMessage } from './plan-handlers.js';
24
26
  import { handleQualityMessage } from './quality-handlers.js';
25
- import { handleHistoryMessage, handleSessionMessage, initializeTab, resumeHistoricalSession } from './session-handlers.js';
27
+ import { handleHistoryMessage, handleSessionMessage, initializeTab, restoreWorktreeFromRegistry, resumeHistoricalSession } from './session-handlers.js';
26
28
  import { SessionRegistry } from './session-registry.js';
27
29
  import { generateNotificationSummary, handleGetSettings, handleUpdateSettings } from './settings-handlers.js';
28
30
  import { handleListSkills } from './skill-handlers.js';
29
31
  import { SkillsWatcher } from './skill-watcher.js';
30
- import { handleCreateTab, handleGetActiveTabs, handleMarkTabViewed, handleRemoveTab, handleReorderTabs, handleSyncPromptText, handleSyncTabMeta } from './tab-handlers.js';
32
+ import { TabEventBufferRegistry } from './tab-event-buffer.js';
33
+ import { handleCreateTab, handleGetActiveTabs, handleMarkTabViewed, handleRemoveTab, handleReorderTabs, handleSyncTabMeta } from './tab-handlers.js';
31
34
  import { cleanupTerminalSubscribers, handleTerminalMessage } from './terminal-handlers.js';
32
35
  import type { FrecencyData, WebSocketMessage, WebSocketResponse, WSContext } from './types.js';
33
36
 
@@ -47,8 +50,11 @@ export class WebSocketImproviseHandler implements HandlerContext {
47
50
  terminalListenerCleanups: Map<string, () => void> = new Map();
48
51
  terminalSubscribers: Map<string, Set<WSContext>> = new Map();
49
52
  fileUploadHandler: FileUploadHandler | null = null;
53
+ fileDownloadHandler: FileDownloadHandler | null = null;
50
54
  gitHeadWatcher: GitHeadWatcher | null = null;
51
55
  skillsWatcher: SkillsWatcher | null = null;
56
+ tabEventBuffers: TabEventBufferRegistry = new TabEventBufferRegistry();
57
+ msgIdTracker: MsgIdTracker = new MsgIdTracker();
52
58
 
53
59
  constructor() {
54
60
  this.frecencyPath = join(homedir(), '.mstro', 'autocomplete-frecency.json');
@@ -146,7 +152,7 @@ export class WebSocketImproviseHandler implements HandlerContext {
146
152
  }
147
153
 
148
154
  /** Dispatch table mapping message types to domain handlers. Built once, looked up per message. */
149
- private static readonly DISPATCH: Record<string, 'session' | 'history' | 'file' | 'terminal' | 'fileExplorer' | 'git' | 'quality' | 'plan' | 'fileUpload'> = {
155
+ private static readonly DISPATCH: Record<string, 'session' | 'history' | 'file' | 'terminal' | 'fileExplorer' | 'git' | 'quality' | 'plan' | 'fileUpload' | 'fileDownload'> = {
150
156
  // Session
151
157
  execute: 'session', cancel: 'session', getHistory: 'session', new: 'session', approve: 'session', reject: 'session',
152
158
  // History
@@ -165,6 +171,8 @@ export class WebSocketImproviseHandler implements HandlerContext {
165
171
  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', chatToBoard: 'plan',
166
172
  // File upload
167
173
  fileUploadStart: 'fileUpload', fileUploadChunk: 'fileUpload', fileUploadComplete: 'fileUpload', fileUploadCancel: 'fileUpload',
174
+ // File download (chunked streaming for large binaries)
175
+ fileDownloadStart: 'fileDownload', fileDownloadCancel: 'fileDownload',
168
176
  };
169
177
 
170
178
  private async dispatchMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string, permission?: 'view'): Promise<void> {
@@ -174,10 +182,10 @@ export class WebSocketImproviseHandler implements HandlerContext {
174
182
  this.send(ws, { type: 'pong', tabId });
175
183
  return;
176
184
  case 'initTab':
177
- return void await initializeTab(this, ws, tabId, workingDir, msg.data?.tabName);
185
+ return void await initializeTab(this, ws, tabId, workingDir, msg.data?.tabName, msg.data);
178
186
  case 'resumeSession':
179
187
  if (!msg.data?.historicalSessionId) throw new Error('Historical session ID is required');
180
- return void await resumeHistoricalSession(this, ws, tabId, workingDir, msg.data.historicalSessionId);
188
+ return void await resumeHistoricalSession(this, ws, tabId, workingDir, msg.data.historicalSessionId, msg.data);
181
189
  case 'requestNotificationSummary':
182
190
  if (!msg.data?.prompt || !msg.data?.output) throw new Error('Prompt and output are required for notification summary');
183
191
  return void await generateNotificationSummary(this, ws, tabId, msg.data.prompt, msg.data.output, workingDir);
@@ -189,8 +197,6 @@ export class WebSocketImproviseHandler implements HandlerContext {
189
197
  return handleReorderTabs(this, ws, workingDir, msg.data?.tabOrder);
190
198
  case 'syncTabMeta':
191
199
  return handleSyncTabMeta(this, ws, msg, tabId, workingDir);
192
- case 'syncPromptText':
193
- return handleSyncPromptText(this, ws, msg, tabId);
194
200
  case 'removeTab':
195
201
  return handleRemoveTab(this, ws, tabId, workingDir);
196
202
  case 'markTabViewed':
@@ -208,6 +214,14 @@ export class WebSocketImproviseHandler implements HandlerContext {
208
214
  const domain = WebSocketImproviseHandler.DISPATCH[msg.type];
209
215
  if (!domain) throw new Error(`Unknown message type: ${msg.type}`);
210
216
 
217
+ // Hydrate worktree state from the registry before any domain handler
218
+ // reads it, so git/file/autocomplete ops route to the tab's worktree
219
+ // even when they arrive before the initTab handshake completes. The
220
+ // registry is authoritative; the in-memory Map is just a cache.
221
+ if (msg.tabId && !this.gitDirectories.has(tabId)) {
222
+ restoreWorktreeFromRegistry(this, this.getRegistry(workingDir), tabId);
223
+ }
224
+
211
225
  // Resolve effective working directory: use worktree path if tab is on a worktree
212
226
  const effectiveDir = this.gitDirectories.get(tabId) || workingDir;
213
227
 
@@ -220,20 +234,38 @@ export class WebSocketImproviseHandler implements HandlerContext {
220
234
  case 'git': return handleGitMessage(this, ws, msg, tabId, workingDir);
221
235
  case 'quality': return handleQualityMessage(this, ws, msg, tabId, workingDir, permission);
222
236
  case 'plan': return handlePlanMessage(this, ws, msg, tabId, workingDir, permission);
223
- case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId, workingDir, permission);
237
+ case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId, effectiveDir, permission);
238
+ case 'fileDownload': return this.handleFileDownloadMessage(ws, msg, tabId, effectiveDir);
239
+ }
240
+ }
241
+
242
+ private handleFileDownloadMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
243
+ if (!this.fileDownloadHandler) {
244
+ this.fileDownloadHandler = new FileDownloadHandler(workingDir);
245
+ }
246
+ const handler = this.fileDownloadHandler;
247
+ const send = this.send.bind(this);
248
+
249
+ switch (msg.type) {
250
+ case 'fileDownloadStart':
251
+ handler.handleDownloadStart(ws, send, tabId, msg.data);
252
+ break;
253
+ case 'fileDownloadCancel':
254
+ handler.handleDownloadCancel(ws, send, tabId, msg.data);
255
+ break;
224
256
  }
225
257
  }
226
258
 
227
259
  private handleFileUploadMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string, permission?: 'view'): void {
228
260
  if (!this.fileUploadHandler) {
229
- this.fileUploadHandler = new FileUploadHandler(workingDir, this);
261
+ this.fileUploadHandler = new FileUploadHandler(this);
230
262
  }
231
263
  const handler = this.fileUploadHandler;
232
264
  const send = this.send.bind(this);
233
265
 
234
266
  switch (msg.type) {
235
267
  case 'fileUploadStart':
236
- handler.handleUploadStart(ws, send, tabId, msg.data, permission);
268
+ handler.handleUploadStart(ws, send, tabId, msg.data, workingDir, permission);
237
269
  break;
238
270
  case 'fileUploadChunk':
239
271
  handler.handleUploadChunk(ws, send, tabId, msg.data);
@@ -262,6 +294,10 @@ export class WebSocketImproviseHandler implements HandlerContext {
262
294
  this.fileUploadHandler.destroy();
263
295
  this.fileUploadHandler = null;
264
296
  }
297
+ if (this.fileDownloadHandler) {
298
+ this.fileDownloadHandler.destroy();
299
+ this.fileDownloadHandler = null;
300
+ }
265
301
  if (this.gitHeadWatcher) {
266
302
  this.gitHeadWatcher.stop();
267
303
  this.gitHeadWatcher = null;
@@ -270,18 +306,45 @@ export class WebSocketImproviseHandler implements HandlerContext {
270
306
  this.skillsWatcher.stop();
271
307
  this.skillsWatcher = null;
272
308
  }
309
+
310
+ // Close orphan PTYs when no web client is watching any more.
311
+ //
312
+ // Prior behavior: PTYs survived until the user typed `exit` or the
313
+ // CLI process died. A page refresh / browser tab close / view-switch
314
+ // would leak the pty, and after a few of these the user would have
315
+ // tens of zombie shells competing for I/O bandwidth, which manifests
316
+ // as the app feeling "unresponsive" — interactive operations starve
317
+ // because the relay socket is saturated streaming output for ptys
318
+ // that no UI is rendering.
319
+ //
320
+ // The active-session preservation in `cleanupConnectionResources`
321
+ // above is intentionally separate: improvise sessions can produce
322
+ // useful work while the browser is closed (Claude Code keeps running
323
+ // in the background); a shell process can't, by construction. So we
324
+ // preserve the former and reap the latter.
325
+ const ptyManager = getPTYManager();
326
+ const activeTerminals = ptyManager.getActiveTerminals();
327
+ if (activeTerminals.length > 0) {
328
+ console.log(`[handler] No web subscribers — closing ${activeTerminals.length} orphan PTY${activeTerminals.length === 1 ? '' : 's'}`);
329
+ ptyManager.closeAll();
330
+ }
273
331
  }
274
332
  }
275
333
 
276
334
  private cleanupConnectionResources(tabMap: Map<string, string>): void {
277
- // Destroy sessions owned by this connection
335
+ // Preserve actively-executing sessions across web reconnects. The runner
336
+ // is still producing output, and the new web connection will re-attach
337
+ // via session-handlers.ts::getSession (or initializeTab → reattachSession)
338
+ // which rebinds listeners and replays executionEventLog. Destroying the
339
+ // session here would orphan the runner and silently drop all streamed
340
+ // output for the rest of the prompt.
278
341
  const sessionIds = new Set(tabMap.values());
279
342
  for (const sessionId of sessionIds) {
280
343
  const session = this.sessions.get(sessionId);
281
- if (session) {
282
- session.destroy();
283
- this.sessions.delete(sessionId);
284
- }
344
+ if (!session) continue;
345
+ if (session.isExecuting) continue;
346
+ session.destroy();
347
+ this.sessions.delete(sessionId);
285
348
  }
286
349
  // Kill search processes owned by this connection's tabs
287
350
  for (const tabId of tabMap.keys()) {
@@ -1,5 +1,4 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
- // Licensed under the MIT License. See LICENSE file for details.
3
2
 
4
3
  /**
5
4
  * WebSocket Improvise Module