mstro-app 0.5.0 → 0.5.5

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 (649) hide show
  1. package/LICENSE +129 -190
  2. package/PRIVACY.md +11 -11
  3. package/README.md +75 -28
  4. package/bin/commands/config.js +1 -2
  5. package/bin/mstro.js +55 -5
  6. package/bin/postinstall.js +0 -1
  7. package/dist/server/cli/eta-estimator.d.ts +55 -0
  8. package/dist/server/cli/eta-estimator.d.ts.map +1 -0
  9. package/dist/server/cli/eta-estimator.js +222 -0
  10. package/dist/server/cli/eta-estimator.js.map +1 -0
  11. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
  12. package/dist/server/cli/headless/claude-invoker-process.js +0 -1
  13. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
  14. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
  15. package/dist/server/cli/headless/claude-invoker-stall.js +0 -1
  16. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
  17. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
  18. package/dist/server/cli/headless/claude-invoker-stream.js +0 -1
  19. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
  20. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -1
  21. package/dist/server/cli/headless/claude-invoker-tools.js +0 -1
  22. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -1
  23. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  24. package/dist/server/cli/headless/claude-invoker.js +0 -1
  25. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  26. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
  27. package/dist/server/cli/headless/haiku-assessments.js +0 -1
  28. package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
  29. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  30. package/dist/server/cli/headless/headless-logger.js +0 -1
  31. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  32. package/dist/server/cli/headless/index.d.ts.map +1 -1
  33. package/dist/server/cli/headless/index.js +0 -1
  34. package/dist/server/cli/headless/index.js.map +1 -1
  35. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -1
  36. package/dist/server/cli/headless/native-timeout-detector.js +0 -1
  37. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -1
  38. package/dist/server/cli/headless/output-utils.d.ts.map +1 -1
  39. package/dist/server/cli/headless/output-utils.js +0 -1
  40. package/dist/server/cli/headless/output-utils.js.map +1 -1
  41. package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -1
  42. package/dist/server/cli/headless/prompt-utils.js +0 -1
  43. package/dist/server/cli/headless/prompt-utils.js.map +1 -1
  44. package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -1
  45. package/dist/server/cli/headless/resilient-runner.js +0 -1
  46. package/dist/server/cli/headless/resilient-runner.js.map +1 -1
  47. package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -1
  48. package/dist/server/cli/headless/retry-strategies.js +0 -1
  49. package/dist/server/cli/headless/retry-strategies.js.map +1 -1
  50. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  51. package/dist/server/cli/headless/runner.js +0 -1
  52. package/dist/server/cli/headless/runner.js.map +1 -1
  53. package/dist/server/cli/headless/stall-assessor.d.ts +50 -0
  54. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  55. package/dist/server/cli/headless/stall-assessor.js +64 -10
  56. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  57. package/dist/server/cli/headless/tool-watchdog.d.ts +21 -0
  58. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  59. package/dist/server/cli/headless/tool-watchdog.js +19 -13
  60. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  61. package/dist/server/cli/headless/types.d.ts.map +1 -1
  62. package/dist/server/cli/headless/types.js +0 -1
  63. package/dist/server/cli/headless/types.js.map +1 -1
  64. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -1
  65. package/dist/server/cli/improvisation-attachments.js +0 -1
  66. package/dist/server/cli/improvisation-attachments.js.map +1 -1
  67. package/dist/server/cli/improvisation-history-store.d.ts.map +1 -1
  68. package/dist/server/cli/improvisation-history-store.js +5 -2
  69. package/dist/server/cli/improvisation-history-store.js.map +1 -1
  70. package/dist/server/cli/improvisation-movements.d.ts.map +1 -1
  71. package/dist/server/cli/improvisation-movements.js +0 -1
  72. package/dist/server/cli/improvisation-movements.js.map +1 -1
  73. package/dist/server/cli/improvisation-output-queue.d.ts +5 -1
  74. package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -1
  75. package/dist/server/cli/improvisation-output-queue.js +30 -8
  76. package/dist/server/cli/improvisation-output-queue.js.map +1 -1
  77. package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
  78. package/dist/server/cli/improvisation-retry.js +0 -1
  79. package/dist/server/cli/improvisation-retry.js.map +1 -1
  80. package/dist/server/cli/improvisation-session-manager.d.ts +29 -0
  81. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  82. package/dist/server/cli/improvisation-session-manager.js +50 -2
  83. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  84. package/dist/server/cli/improvisation-types.d.ts +2 -0
  85. package/dist/server/cli/improvisation-types.d.ts.map +1 -1
  86. package/dist/server/cli/improvisation-types.js +0 -1
  87. package/dist/server/cli/improvisation-types.js.map +1 -1
  88. package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -1
  89. package/dist/server/cli/retry/retry-best-result.js +0 -1
  90. package/dist/server/cli/retry/retry-best-result.js.map +1 -1
  91. package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -1
  92. package/dist/server/cli/retry/retry-context-loss.js +0 -1
  93. package/dist/server/cli/retry/retry-context-loss.js.map +1 -1
  94. package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -1
  95. package/dist/server/cli/retry/retry-premature-completion.js +1 -2
  96. package/dist/server/cli/retry/retry-premature-completion.js.map +1 -1
  97. package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -1
  98. package/dist/server/cli/retry/retry-recovery-strategies.js +0 -1
  99. package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -1
  100. package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -1
  101. package/dist/server/cli/retry/retry-resume-strategy.js +0 -1
  102. package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -1
  103. package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -1
  104. package/dist/server/cli/retry/retry-runner-factory.js +0 -1
  105. package/dist/server/cli/retry/retry-runner-factory.js.map +1 -1
  106. package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -1
  107. package/dist/server/cli/retry/retry-tool-results.js +0 -1
  108. package/dist/server/cli/retry/retry-tool-results.js.map +1 -1
  109. package/dist/server/cli/retry/retry-types.d.ts.map +1 -1
  110. package/dist/server/cli/retry/retry-types.js +0 -1
  111. package/dist/server/cli/retry/retry-types.js.map +1 -1
  112. package/dist/server/engines/EngineEvent.d.ts +126 -0
  113. package/dist/server/engines/EngineEvent.d.ts.map +1 -0
  114. package/dist/server/engines/EngineEvent.js +11 -0
  115. package/dist/server/engines/EngineEvent.js.map +1 -0
  116. package/dist/server/engines/claude/ClaudeCodeEngine.d.ts +47 -0
  117. package/dist/server/engines/claude/ClaudeCodeEngine.d.ts.map +1 -0
  118. package/dist/server/engines/claude/ClaudeCodeEngine.js +338 -0
  119. package/dist/server/engines/claude/ClaudeCodeEngine.js.map +1 -0
  120. package/dist/server/engines/factory.d.ts +21 -0
  121. package/dist/server/engines/factory.d.ts.map +1 -0
  122. package/dist/server/engines/factory.js +152 -0
  123. package/dist/server/engines/factory.js.map +1 -0
  124. package/dist/server/engines/opencode/OpenCodeEngine.d.ts +148 -0
  125. package/dist/server/engines/opencode/OpenCodeEngine.d.ts.map +1 -0
  126. package/dist/server/engines/opencode/OpenCodeEngine.js +630 -0
  127. package/dist/server/engines/opencode/OpenCodeEngine.js.map +1 -0
  128. package/dist/server/engines/opencode/OpenCodeServerManager.d.ts +172 -0
  129. package/dist/server/engines/opencode/OpenCodeServerManager.d.ts.map +1 -0
  130. package/dist/server/engines/opencode/OpenCodeServerManager.js +390 -0
  131. package/dist/server/engines/opencode/OpenCodeServerManager.js.map +1 -0
  132. package/dist/server/engines/opencode/model-catalog.d.ts +94 -0
  133. package/dist/server/engines/opencode/model-catalog.d.ts.map +1 -0
  134. package/dist/server/engines/opencode/model-catalog.js +141 -0
  135. package/dist/server/engines/opencode/model-catalog.js.map +1 -0
  136. package/dist/server/engines/types.d.ts +146 -0
  137. package/dist/server/engines/types.d.ts.map +1 -0
  138. package/dist/server/engines/types.js +4 -0
  139. package/dist/server/engines/types.js.map +1 -0
  140. package/dist/server/index.js +1 -2
  141. package/dist/server/index.js.map +1 -1
  142. package/dist/server/mcp/bouncer-cli.js +0 -1
  143. package/dist/server/mcp/bouncer-cli.js.map +1 -1
  144. package/dist/server/mcp/bouncer-haiku.d.ts +17 -4
  145. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
  146. package/dist/server/mcp/bouncer-haiku.js +8 -125
  147. package/dist/server/mcp/bouncer-haiku.js.map +1 -1
  148. package/dist/server/mcp/bouncer-integration.d.ts +45 -0
  149. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  150. package/dist/server/mcp/bouncer-integration.js +69 -6
  151. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  152. package/dist/server/mcp/classifier/BouncerClassifier.d.ts +34 -0
  153. package/dist/server/mcp/classifier/BouncerClassifier.d.ts.map +1 -0
  154. package/dist/server/mcp/classifier/BouncerClassifier.js +4 -0
  155. package/dist/server/mcp/classifier/BouncerClassifier.js.map +1 -0
  156. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.d.ts +17 -0
  157. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.d.ts.map +1 -0
  158. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +142 -0
  159. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js.map +1 -0
  160. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.d.ts +68 -0
  161. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.d.ts.map +1 -0
  162. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.js +182 -0
  163. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.js.map +1 -0
  164. package/dist/server/mcp/classifier/factory.d.ts +70 -0
  165. package/dist/server/mcp/classifier/factory.d.ts.map +1 -0
  166. package/dist/server/mcp/classifier/factory.js +155 -0
  167. package/dist/server/mcp/classifier/factory.js.map +1 -0
  168. package/dist/server/mcp/security-analysis.d.ts.map +1 -1
  169. package/dist/server/mcp/security-analysis.js +0 -1
  170. package/dist/server/mcp/security-analysis.js.map +1 -1
  171. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  172. package/dist/server/mcp/security-audit.js +0 -1
  173. package/dist/server/mcp/security-audit.js.map +1 -1
  174. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  175. package/dist/server/mcp/security-patterns.js +0 -1
  176. package/dist/server/mcp/security-patterns.js.map +1 -1
  177. package/dist/server/mcp/server.js +0 -1
  178. package/dist/server/mcp/server.js.map +1 -1
  179. package/dist/server/routes/files.d.ts.map +1 -1
  180. package/dist/server/routes/files.js +0 -1
  181. package/dist/server/routes/files.js.map +1 -1
  182. package/dist/server/routes/improvise.d.ts.map +1 -1
  183. package/dist/server/routes/improvise.js +0 -1
  184. package/dist/server/routes/improvise.js.map +1 -1
  185. package/dist/server/routes/index.d.ts.map +1 -1
  186. package/dist/server/routes/index.js +0 -1
  187. package/dist/server/routes/index.js.map +1 -1
  188. package/dist/server/routes/instances.d.ts.map +1 -1
  189. package/dist/server/routes/instances.js +0 -1
  190. package/dist/server/routes/instances.js.map +1 -1
  191. package/dist/server/routes/notifications.d.ts.map +1 -1
  192. package/dist/server/routes/notifications.js +0 -1
  193. package/dist/server/routes/notifications.js.map +1 -1
  194. package/dist/server/server-setup.d.ts.map +1 -1
  195. package/dist/server/server-setup.js +0 -1
  196. package/dist/server/server-setup.js.map +1 -1
  197. package/dist/server/services/analytics.d.ts.map +1 -1
  198. package/dist/server/services/analytics.js +0 -1
  199. package/dist/server/services/analytics.js.map +1 -1
  200. package/dist/server/services/auth.d.ts.map +1 -1
  201. package/dist/server/services/auth.js +0 -1
  202. package/dist/server/services/auth.js.map +1 -1
  203. package/dist/server/services/client-id.d.ts.map +1 -1
  204. package/dist/server/services/client-id.js +0 -1
  205. package/dist/server/services/client-id.js.map +1 -1
  206. package/dist/server/services/file-explorer-ops.d.ts.map +1 -1
  207. package/dist/server/services/file-explorer-ops.js +0 -1
  208. package/dist/server/services/file-explorer-ops.js.map +1 -1
  209. package/dist/server/services/files.d.ts.map +1 -1
  210. package/dist/server/services/files.js +0 -1
  211. package/dist/server/services/files.js.map +1 -1
  212. package/dist/server/services/instances.d.ts.map +1 -1
  213. package/dist/server/services/instances.js +0 -1
  214. package/dist/server/services/instances.js.map +1 -1
  215. package/dist/server/services/pathUtils.d.ts.map +1 -1
  216. package/dist/server/services/pathUtils.js +0 -1
  217. package/dist/server/services/pathUtils.js.map +1 -1
  218. package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
  219. package/dist/server/services/plan/agent-loader.js +0 -1
  220. package/dist/server/services/plan/agent-loader.js.map +1 -1
  221. package/dist/server/services/plan/agent-resolver.d.ts +26 -0
  222. package/dist/server/services/plan/agent-resolver.d.ts.map +1 -0
  223. package/dist/server/services/plan/agent-resolver.js +102 -0
  224. package/dist/server/services/plan/agent-resolver.js.map +1 -0
  225. package/dist/server/services/plan/board-config.d.ts.map +1 -1
  226. package/dist/server/services/plan/board-config.js +0 -1
  227. package/dist/server/services/plan/board-config.js.map +1 -1
  228. package/dist/server/services/plan/composer.d.ts.map +1 -1
  229. package/dist/server/services/plan/composer.js +59 -12
  230. package/dist/server/services/plan/composer.js.map +1 -1
  231. package/dist/server/services/plan/config-installer.d.ts.map +1 -1
  232. package/dist/server/services/plan/config-installer.js +0 -1
  233. package/dist/server/services/plan/config-installer.js.map +1 -1
  234. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  235. package/dist/server/services/plan/dependency-resolver.js +0 -1
  236. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  237. package/dist/server/services/plan/executor.d.ts.map +1 -1
  238. package/dist/server/services/plan/executor.js +48 -4
  239. package/dist/server/services/plan/executor.js.map +1 -1
  240. package/dist/server/services/plan/front-matter.d.ts.map +1 -1
  241. package/dist/server/services/plan/front-matter.js +0 -1
  242. package/dist/server/services/plan/front-matter.js.map +1 -1
  243. package/dist/server/services/plan/issue-classification.d.ts.map +1 -1
  244. package/dist/server/services/plan/issue-classification.js +0 -1
  245. package/dist/server/services/plan/issue-classification.js.map +1 -1
  246. package/dist/server/services/plan/issue-loader.d.ts.map +1 -1
  247. package/dist/server/services/plan/issue-loader.js +0 -1
  248. package/dist/server/services/plan/issue-loader.js.map +1 -1
  249. package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
  250. package/dist/server/services/plan/issue-prompt-builder.js +33 -2
  251. package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
  252. package/dist/server/services/plan/issue-retry.d.ts +3 -1
  253. package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
  254. package/dist/server/services/plan/issue-retry.js +2 -1
  255. package/dist/server/services/plan/issue-retry.js.map +1 -1
  256. package/dist/server/services/plan/issue-writer.d.ts.map +1 -1
  257. package/dist/server/services/plan/issue-writer.js +0 -1
  258. package/dist/server/services/plan/issue-writer.js.map +1 -1
  259. package/dist/server/services/plan/output-manager.d.ts.map +1 -1
  260. package/dist/server/services/plan/output-manager.js +0 -1
  261. package/dist/server/services/plan/output-manager.js.map +1 -1
  262. package/dist/server/services/plan/parser-core.d.ts.map +1 -1
  263. package/dist/server/services/plan/parser-core.js +1 -1
  264. package/dist/server/services/plan/parser-core.js.map +1 -1
  265. package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
  266. package/dist/server/services/plan/parser-migration.js +0 -1
  267. package/dist/server/services/plan/parser-migration.js.map +1 -1
  268. package/dist/server/services/plan/parser.d.ts.map +1 -1
  269. package/dist/server/services/plan/parser.js +0 -1
  270. package/dist/server/services/plan/parser.js.map +1 -1
  271. package/dist/server/services/plan/progress-log.d.ts.map +1 -1
  272. package/dist/server/services/plan/progress-log.js +0 -1
  273. package/dist/server/services/plan/progress-log.js.map +1 -1
  274. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
  275. package/dist/server/services/plan/prompt-builder.js +0 -1
  276. package/dist/server/services/plan/prompt-builder.js.map +1 -1
  277. package/dist/server/services/plan/readiness-planner.d.ts.map +1 -1
  278. package/dist/server/services/plan/readiness-planner.js +0 -1
  279. package/dist/server/services/plan/readiness-planner.js.map +1 -1
  280. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  281. package/dist/server/services/plan/review-gate.js +0 -1
  282. package/dist/server/services/plan/review-gate.js.map +1 -1
  283. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  284. package/dist/server/services/plan/state-reconciler.js +0 -1
  285. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  286. package/dist/server/services/plan/types.d.ts +1 -0
  287. package/dist/server/services/plan/types.d.ts.map +1 -1
  288. package/dist/server/services/plan/types.js +0 -1
  289. package/dist/server/services/plan/types.js.map +1 -1
  290. package/dist/server/services/plan/watcher.d.ts.map +1 -1
  291. package/dist/server/services/plan/watcher.js +0 -1
  292. package/dist/server/services/plan/watcher.js.map +1 -1
  293. package/dist/server/services/platform-credentials.d.ts.map +1 -1
  294. package/dist/server/services/platform-credentials.js +0 -1
  295. package/dist/server/services/platform-credentials.js.map +1 -1
  296. package/dist/server/services/platform-token-lifecycle.d.ts +70 -0
  297. package/dist/server/services/platform-token-lifecycle.d.ts.map +1 -0
  298. package/dist/server/services/platform-token-lifecycle.js +156 -0
  299. package/dist/server/services/platform-token-lifecycle.js.map +1 -0
  300. package/dist/server/services/platform.d.ts +21 -56
  301. package/dist/server/services/platform.d.ts.map +1 -1
  302. package/dist/server/services/platform.js +98 -142
  303. package/dist/server/services/platform.js.map +1 -1
  304. package/dist/server/services/sentry.d.ts.map +1 -1
  305. package/dist/server/services/sentry.js +0 -1
  306. package/dist/server/services/sentry.js.map +1 -1
  307. package/dist/server/services/settings.d.ts +76 -2
  308. package/dist/server/services/settings.d.ts.map +1 -1
  309. package/dist/server/services/settings.js +127 -5
  310. package/dist/server/services/settings.js.map +1 -1
  311. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  312. package/dist/server/services/terminal/pty-manager.js +0 -1
  313. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  314. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -1
  315. package/dist/server/services/terminal/pty-utils.js +0 -1
  316. package/dist/server/services/terminal/pty-utils.js.map +1 -1
  317. package/dist/server/services/websocket/autocomplete.d.ts.map +1 -1
  318. package/dist/server/services/websocket/autocomplete.js +0 -1
  319. package/dist/server/services/websocket/autocomplete.js.map +1 -1
  320. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -1
  321. package/dist/server/services/websocket/file-definition-handlers.js +0 -1
  322. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -1
  323. package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -1
  324. package/dist/server/services/websocket/file-download-handler.js +0 -1
  325. package/dist/server/services/websocket/file-download-handler.js.map +1 -1
  326. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  327. package/dist/server/services/websocket/file-explorer-handlers.js +0 -1
  328. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  329. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -1
  330. package/dist/server/services/websocket/file-search-handlers.js +0 -1
  331. package/dist/server/services/websocket/file-search-handlers.js.map +1 -1
  332. package/dist/server/services/websocket/file-upload-handler.d.ts +2 -3
  333. package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -1
  334. package/dist/server/services/websocket/file-upload-handler.js +4 -7
  335. package/dist/server/services/websocket/file-upload-handler.js.map +1 -1
  336. package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
  337. package/dist/server/services/websocket/file-utils.js +0 -1
  338. package/dist/server/services/websocket/file-utils.js.map +1 -1
  339. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
  340. package/dist/server/services/websocket/git-branch-handlers.js +19 -7
  341. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
  342. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -1
  343. package/dist/server/services/websocket/git-diff-handlers.js +0 -1
  344. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -1
  345. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  346. package/dist/server/services/websocket/git-handlers.js +58 -6
  347. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  348. package/dist/server/services/websocket/git-head-watcher.d.ts.map +1 -1
  349. package/dist/server/services/websocket/git-head-watcher.js +0 -1
  350. package/dist/server/services/websocket/git-head-watcher.js.map +1 -1
  351. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -1
  352. package/dist/server/services/websocket/git-log-handlers.js +0 -1
  353. package/dist/server/services/websocket/git-log-handlers.js.map +1 -1
  354. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  355. package/dist/server/services/websocket/git-pr-handlers.js +0 -1
  356. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  357. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -1
  358. package/dist/server/services/websocket/git-tag-handlers.js +0 -1
  359. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -1
  360. package/dist/server/services/websocket/git-utils.d.ts +18 -3
  361. package/dist/server/services/websocket/git-utils.d.ts.map +1 -1
  362. package/dist/server/services/websocket/git-utils.js +58 -8
  363. package/dist/server/services/websocket/git-utils.js.map +1 -1
  364. package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
  365. package/dist/server/services/websocket/git-worktree-handlers.js +230 -14
  366. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
  367. package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
  368. package/dist/server/services/websocket/handler-context.js +0 -1
  369. package/dist/server/services/websocket/handler-context.js.map +1 -1
  370. package/dist/server/services/websocket/handler.d.ts +17 -1
  371. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  372. package/dist/server/services/websocket/handler.js +57 -6
  373. package/dist/server/services/websocket/handler.js.map +1 -1
  374. package/dist/server/services/websocket/index.d.ts.map +1 -1
  375. package/dist/server/services/websocket/index.js +0 -1
  376. package/dist/server/services/websocket/index.js.map +1 -1
  377. package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -1
  378. package/dist/server/services/websocket/msg-id-tracker.js +0 -1
  379. package/dist/server/services/websocket/msg-id-tracker.js.map +1 -1
  380. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
  381. package/dist/server/services/websocket/plan-board-handlers.js +0 -1
  382. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
  383. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -1
  384. package/dist/server/services/websocket/plan-execution-handlers.js +6 -2
  385. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
  386. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  387. package/dist/server/services/websocket/plan-handlers.js +0 -1
  388. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  389. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
  390. package/dist/server/services/websocket/plan-helpers.js +0 -1
  391. package/dist/server/services/websocket/plan-helpers.js.map +1 -1
  392. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
  393. package/dist/server/services/websocket/plan-issue-handlers.js +0 -1
  394. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
  395. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
  396. package/dist/server/services/websocket/plan-sprint-handlers.js +0 -1
  397. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
  398. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -1
  399. package/dist/server/services/websocket/quality-complexity.js +78 -27
  400. package/dist/server/services/websocket/quality-complexity.js.map +1 -1
  401. package/dist/server/services/websocket/quality-eta.d.ts +47 -0
  402. package/dist/server/services/websocket/quality-eta.d.ts.map +1 -0
  403. package/dist/server/services/websocket/quality-eta.js +110 -0
  404. package/dist/server/services/websocket/quality-eta.js.map +1 -0
  405. package/dist/server/services/websocket/quality-grading.d.ts +69 -0
  406. package/dist/server/services/websocket/quality-grading.d.ts.map +1 -0
  407. package/dist/server/services/websocket/quality-grading.js +650 -0
  408. package/dist/server/services/websocket/quality-grading.js.map +1 -0
  409. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  410. package/dist/server/services/websocket/quality-handlers.js +145 -8
  411. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  412. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -1
  413. package/dist/server/services/websocket/quality-linting.js +0 -1
  414. package/dist/server/services/websocket/quality-linting.js.map +1 -1
  415. package/dist/server/services/websocket/quality-operations.d.ts +34 -0
  416. package/dist/server/services/websocket/quality-operations.d.ts.map +1 -0
  417. package/dist/server/services/websocket/quality-operations.js +47 -0
  418. package/dist/server/services/websocket/quality-operations.js.map +1 -0
  419. package/dist/server/services/websocket/quality-persistence.d.ts +23 -0
  420. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
  421. package/dist/server/services/websocket/quality-persistence.js +38 -12
  422. package/dist/server/services/websocket/quality-persistence.js.map +1 -1
  423. package/dist/server/services/websocket/quality-review-agent.d.ts +1 -1
  424. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
  425. package/dist/server/services/websocket/quality-review-agent.js +105 -57
  426. package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
  427. package/dist/server/services/websocket/quality-service.d.ts +12 -2
  428. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  429. package/dist/server/services/websocket/quality-service.js +387 -72
  430. package/dist/server/services/websocket/quality-service.js.map +1 -1
  431. package/dist/server/services/websocket/quality-tools.d.ts +22 -1
  432. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
  433. package/dist/server/services/websocket/quality-tools.js +55 -3
  434. package/dist/server/services/websocket/quality-tools.js.map +1 -1
  435. package/dist/server/services/websocket/quality-types.d.ts +52 -3
  436. package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
  437. package/dist/server/services/websocket/quality-types.js +1 -2
  438. package/dist/server/services/websocket/quality-types.js.map +1 -1
  439. package/dist/server/services/websocket/session-handlers.d.ts +3 -1
  440. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  441. package/dist/server/services/websocket/session-handlers.js +57 -10
  442. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  443. package/dist/server/services/websocket/session-history.d.ts.map +1 -1
  444. package/dist/server/services/websocket/session-history.js +3 -1
  445. package/dist/server/services/websocket/session-history.js.map +1 -1
  446. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
  447. package/dist/server/services/websocket/session-initialization.js +158 -43
  448. package/dist/server/services/websocket/session-initialization.js.map +1 -1
  449. package/dist/server/services/websocket/session-registry.d.ts +25 -0
  450. package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
  451. package/dist/server/services/websocket/session-registry.js +19 -1
  452. package/dist/server/services/websocket/session-registry.js.map +1 -1
  453. package/dist/server/services/websocket/settings-handlers.d.ts +1 -1
  454. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
  455. package/dist/server/services/websocket/settings-handlers.js +35 -5
  456. package/dist/server/services/websocket/settings-handlers.js.map +1 -1
  457. package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
  458. package/dist/server/services/websocket/skill-handlers.js +0 -1
  459. package/dist/server/services/websocket/skill-handlers.js.map +1 -1
  460. package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -1
  461. package/dist/server/services/websocket/skill-watcher.js +0 -1
  462. package/dist/server/services/websocket/skill-watcher.js.map +1 -1
  463. package/dist/server/services/websocket/tab-broadcast.d.ts +7 -2
  464. package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -1
  465. package/dist/server/services/websocket/tab-broadcast.js +10 -3
  466. package/dist/server/services/websocket/tab-broadcast.js.map +1 -1
  467. package/dist/server/services/websocket/tab-event-buffer.d.ts +97 -8
  468. package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -1
  469. package/dist/server/services/websocket/tab-event-buffer.js +138 -13
  470. package/dist/server/services/websocket/tab-event-buffer.js.map +1 -1
  471. package/dist/server/services/websocket/tab-event-replay.d.ts +29 -13
  472. package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -1
  473. package/dist/server/services/websocket/tab-event-replay.js +55 -3
  474. package/dist/server/services/websocket/tab-event-replay.js.map +1 -1
  475. package/dist/server/services/websocket/tab-handlers.d.ts +9 -1
  476. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
  477. package/dist/server/services/websocket/tab-handlers.js +47 -3
  478. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  479. package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
  480. package/dist/server/services/websocket/terminal-handlers.js +39 -4
  481. package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
  482. package/dist/server/services/websocket/types.d.ts +30 -7
  483. package/dist/server/services/websocket/types.d.ts.map +1 -1
  484. package/dist/server/services/websocket/types.js +12 -7
  485. package/dist/server/services/websocket/types.js.map +1 -1
  486. package/dist/server/utils/agent-manager.d.ts.map +1 -1
  487. package/dist/server/utils/agent-manager.js +0 -1
  488. package/dist/server/utils/agent-manager.js.map +1 -1
  489. package/dist/server/utils/paths.d.ts.map +1 -1
  490. package/dist/server/utils/paths.js +0 -1
  491. package/dist/server/utils/paths.js.map +1 -1
  492. package/dist/server/utils/port-manager.d.ts.map +1 -1
  493. package/dist/server/utils/port-manager.js +0 -1
  494. package/dist/server/utils/port-manager.js.map +1 -1
  495. package/dist/server/utils/port.d.ts.map +1 -1
  496. package/dist/server/utils/port.js +0 -1
  497. package/dist/server/utils/port.js.map +1 -1
  498. package/package.json +6 -4
  499. package/server/cli/eta-estimator.ts +249 -0
  500. package/server/cli/headless/claude-invoker-process.ts +0 -1
  501. package/server/cli/headless/claude-invoker-stall.ts +0 -1
  502. package/server/cli/headless/claude-invoker-stream.ts +0 -1
  503. package/server/cli/headless/claude-invoker-tools.ts +0 -1
  504. package/server/cli/headless/claude-invoker.ts +0 -1
  505. package/server/cli/headless/haiku-assessments.ts +0 -1
  506. package/server/cli/headless/headless-logger.ts +0 -1
  507. package/server/cli/headless/index.ts +0 -1
  508. package/server/cli/headless/native-timeout-detector.ts +0 -1
  509. package/server/cli/headless/output-utils.ts +0 -1
  510. package/server/cli/headless/prompt-utils.ts +0 -1
  511. package/server/cli/headless/resilient-runner.ts +0 -1
  512. package/server/cli/headless/retry-strategies.ts +0 -1
  513. package/server/cli/headless/runner.ts +0 -1
  514. package/server/cli/headless/stall-assessor.ts +93 -1
  515. package/server/cli/headless/tool-watchdog.ts +21 -1
  516. package/server/cli/headless/types.ts +0 -1
  517. package/server/cli/improvisation-attachments.ts +0 -1
  518. package/server/cli/improvisation-history-store.ts +4 -2
  519. package/server/cli/improvisation-movements.ts +0 -1
  520. package/server/cli/improvisation-output-queue.ts +29 -8
  521. package/server/cli/improvisation-retry.ts +0 -1
  522. package/server/cli/improvisation-session-manager.ts +54 -2
  523. package/server/cli/improvisation-types.ts +2 -1
  524. package/server/cli/retry/retry-best-result.ts +0 -1
  525. package/server/cli/retry/retry-context-loss.ts +0 -1
  526. package/server/cli/retry/retry-premature-completion.ts +1 -2
  527. package/server/cli/retry/retry-recovery-strategies.ts +0 -1
  528. package/server/cli/retry/retry-resume-strategy.ts +0 -1
  529. package/server/cli/retry/retry-runner-factory.ts +0 -1
  530. package/server/cli/retry/retry-tool-results.ts +0 -1
  531. package/server/cli/retry/retry-types.ts +0 -1
  532. package/server/engines/EngineEvent.ts +156 -0
  533. package/server/engines/claude/ClaudeCodeEngine.ts +404 -0
  534. package/server/engines/factory.ts +176 -0
  535. package/server/engines/opencode/OpenCodeEngine.ts +786 -0
  536. package/server/engines/opencode/OpenCodeServerManager.ts +577 -0
  537. package/server/engines/opencode/model-catalog.ts +217 -0
  538. package/server/engines/types.ts +173 -0
  539. package/server/index.ts +1 -2
  540. package/server/mcp/bouncer-cli.ts +0 -1
  541. package/server/mcp/bouncer-haiku.ts +21 -146
  542. package/server/mcp/bouncer-integration.ts +107 -6
  543. package/server/mcp/classifier/BouncerClassifier.ts +40 -0
  544. package/server/mcp/classifier/ClaudeBouncerClassifier.ts +189 -0
  545. package/server/mcp/classifier/OpenCodeBouncerClassifier.ts +305 -0
  546. package/server/mcp/classifier/factory.ts +195 -0
  547. package/server/mcp/security-analysis.ts +0 -1
  548. package/server/mcp/security-audit.ts +0 -1
  549. package/server/mcp/security-patterns.ts +0 -1
  550. package/server/mcp/server.ts +0 -1
  551. package/server/routes/files.ts +0 -1
  552. package/server/routes/improvise.ts +0 -1
  553. package/server/routes/index.ts +0 -1
  554. package/server/routes/instances.ts +0 -1
  555. package/server/routes/notifications.ts +0 -1
  556. package/server/server-setup.ts +0 -1
  557. package/server/services/analytics.ts +0 -1
  558. package/server/services/auth.ts +0 -1
  559. package/server/services/client-id.ts +0 -1
  560. package/server/services/file-explorer-ops.ts +0 -1
  561. package/server/services/files.ts +0 -1
  562. package/server/services/instances.ts +0 -1
  563. package/server/services/pathUtils.ts +0 -1
  564. package/server/services/plan/agent-loader.ts +0 -1
  565. package/server/services/plan/agent-resolver.ts +115 -0
  566. package/server/services/plan/agents/code-review.md +43 -11
  567. package/server/services/plan/board-config.ts +0 -1
  568. package/server/services/plan/composer.ts +63 -12
  569. package/server/services/plan/config-installer.ts +0 -1
  570. package/server/services/plan/dependency-resolver.ts +0 -1
  571. package/server/services/plan/executor.ts +48 -4
  572. package/server/services/plan/front-matter.ts +0 -1
  573. package/server/services/plan/issue-classification.ts +0 -1
  574. package/server/services/plan/issue-loader.ts +0 -1
  575. package/server/services/plan/issue-prompt-builder.ts +39 -2
  576. package/server/services/plan/issue-retry.ts +5 -2
  577. package/server/services/plan/issue-writer.ts +0 -1
  578. package/server/services/plan/output-manager.ts +0 -1
  579. package/server/services/plan/parser-core.ts +1 -1
  580. package/server/services/plan/parser-migration.ts +0 -1
  581. package/server/services/plan/parser.ts +0 -1
  582. package/server/services/plan/progress-log.ts +0 -1
  583. package/server/services/plan/prompt-builder.ts +0 -1
  584. package/server/services/plan/readiness-planner.ts +0 -1
  585. package/server/services/plan/review-gate.ts +0 -1
  586. package/server/services/plan/state-reconciler.ts +0 -1
  587. package/server/services/plan/types.ts +4 -1
  588. package/server/services/plan/watcher.ts +0 -1
  589. package/server/services/platform-credentials.ts +0 -1
  590. package/server/services/platform-token-lifecycle.ts +171 -0
  591. package/server/services/platform.ts +106 -148
  592. package/server/services/sentry.ts +0 -1
  593. package/server/services/settings.ts +161 -5
  594. package/server/services/terminal/pty-manager.ts +0 -1
  595. package/server/services/terminal/pty-utils.ts +0 -1
  596. package/server/services/websocket/autocomplete.ts +0 -1
  597. package/server/services/websocket/file-definition-handlers.ts +0 -1
  598. package/server/services/websocket/file-download-handler.ts +0 -1
  599. package/server/services/websocket/file-explorer-handlers.ts +0 -1
  600. package/server/services/websocket/file-search-handlers.ts +0 -1
  601. package/server/services/websocket/file-upload-handler.ts +6 -5
  602. package/server/services/websocket/file-utils.ts +0 -1
  603. package/server/services/websocket/git-branch-handlers.ts +20 -7
  604. package/server/services/websocket/git-diff-handlers.ts +0 -1
  605. package/server/services/websocket/git-handlers.ts +66 -10
  606. package/server/services/websocket/git-head-watcher.ts +0 -1
  607. package/server/services/websocket/git-log-handlers.ts +0 -1
  608. package/server/services/websocket/git-pr-handlers.ts +0 -1
  609. package/server/services/websocket/git-tag-handlers.ts +0 -1
  610. package/server/services/websocket/git-utils.ts +69 -9
  611. package/server/services/websocket/git-worktree-handlers.ts +260 -17
  612. package/server/services/websocket/handler-context.ts +0 -1
  613. package/server/services/websocket/handler.ts +62 -6
  614. package/server/services/websocket/index.ts +0 -1
  615. package/server/services/websocket/msg-id-tracker.ts +0 -1
  616. package/server/services/websocket/plan-board-handlers.ts +0 -1
  617. package/server/services/websocket/plan-execution-handlers.ts +6 -2
  618. package/server/services/websocket/plan-handlers.ts +0 -1
  619. package/server/services/websocket/plan-helpers.ts +0 -1
  620. package/server/services/websocket/plan-issue-handlers.ts +0 -1
  621. package/server/services/websocket/plan-sprint-handlers.ts +0 -1
  622. package/server/services/websocket/quality-complexity.ts +80 -27
  623. package/server/services/websocket/quality-eta.ts +155 -0
  624. package/server/services/websocket/quality-grading.ts +834 -0
  625. package/server/services/websocket/quality-handlers.ts +153 -8
  626. package/server/services/websocket/quality-linting.ts +0 -1
  627. package/server/services/websocket/quality-operations.ts +72 -0
  628. package/server/services/websocket/quality-persistence.ts +47 -8
  629. package/server/services/websocket/quality-review-agent.ts +154 -65
  630. package/server/services/websocket/quality-service.ts +415 -68
  631. package/server/services/websocket/quality-tools.ts +62 -3
  632. package/server/services/websocket/quality-types.ts +61 -4
  633. package/server/services/websocket/session-handlers.ts +64 -11
  634. package/server/services/websocket/session-history.ts +3 -1
  635. package/server/services/websocket/session-initialization.ts +189 -47
  636. package/server/services/websocket/session-registry.ts +37 -1
  637. package/server/services/websocket/settings-handlers.ts +41 -5
  638. package/server/services/websocket/skill-handlers.ts +0 -1
  639. package/server/services/websocket/skill-watcher.ts +0 -1
  640. package/server/services/websocket/tab-broadcast.ts +10 -3
  641. package/server/services/websocket/tab-event-buffer.ts +143 -12
  642. package/server/services/websocket/tab-event-replay.ts +70 -4
  643. package/server/services/websocket/tab-handlers.ts +53 -6
  644. package/server/services/websocket/terminal-handlers.ts +39 -3
  645. package/server/services/websocket/types.ts +39 -8
  646. package/server/utils/agent-manager.ts +0 -1
  647. package/server/utils/paths.ts +0 -1
  648. package/server/utils/port-manager.ts +0 -1
  649. package/server/utils/port.ts +0 -1
@@ -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
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
4
  import { dirname, join } from 'node:path';
@@ -7,8 +6,21 @@ 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';
9
8
  import { handleGitLog } from './git-log-handlers.js';
9
+ import { parseGitStatus } from './git-utils.js';
10
10
  import type { HandlerContext } from './handler-context.js';
11
- 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
+ }
12
24
 
13
25
  function persistBoardWorktree(workingDir: string, boardId: string, worktreePath: string | null, branch: string | null): void {
14
26
  const pmDir = resolvePmDir(workingDir);
@@ -44,6 +56,8 @@ export async function handleGitWorktreeMessage(ctx: HandlerContext, ws: WSContex
44
56
  gitWorktreeMerge: () => handleGitWorktreeMerge(ctx, ws, msg, tabId, gitDir),
45
57
  gitMergeAbort: () => handleGitMergeAbort(ctx, ws, tabId, gitDir),
46
58
  gitMergeComplete: () => handleGitMergeComplete(ctx, ws, msg, tabId, gitDir),
59
+ gitMergeStashPop: () => handleGitMergeStashPop(ctx, ws, msg, tabId, gitDir),
60
+ gitMergeDiscardBlockers: () => handleGitMergeDiscardBlockers(ctx, ws, msg, tabId, gitDir),
47
61
  };
48
62
  await handlers[msg.type]?.();
49
63
  }
@@ -339,6 +353,64 @@ async function handleGitWorktreeCreatePR(ctx: HandlerContext, ws: WSContext, msg
339
353
  }
340
354
  }
341
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
+
342
414
  async function handleGitMergePreview(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
343
415
  try {
344
416
  const { sourceBranch, targetBranch } = msg.data || {};
@@ -374,10 +446,30 @@ async function handleGitMergePreview(ctx: HandlerContext, ws: WSContext, msg: We
374
446
  return { hash: hash.trim(), message: rest.join('|').trim() };
375
447
  });
376
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
+
377
461
  ctx.send(ws, {
378
462
  type: 'gitMergePreviewResult',
379
463
  tabId,
380
- 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
+ },
381
473
  });
382
474
  } catch (error: unknown) {
383
475
  ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
@@ -482,9 +574,103 @@ export function findWorktreePathForBranch(porcelainOutput: string, branchName: s
482
574
  return null;
483
575
  }
484
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
+
485
671
  async function handleGitWorktreeMerge(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
486
672
  try {
487
- const { sourceBranch, targetBranch, strategy, commitMessage, deleteWorktree, deleteBranch } = msg.data || {};
673
+ const { sourceBranch, targetBranch, strategy, commitMessage, deleteWorktree, deleteBranch, stashFirst } = msg.data || {};
488
674
  if (!sourceBranch || !targetBranch || !strategy) {
489
675
  ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Source branch, target branch, and strategy are required' } });
490
676
  return;
@@ -494,40 +680,97 @@ async function handleGitWorktreeMerge(ctx: HandlerContext, ws: WSContext, msg: W
494
680
 
495
681
  const mainBranchResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], mainPath);
496
682
  if (mainBranchResult.stdout.trim() !== targetBranch) {
497
- 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 } });
498
684
  return;
499
685
  }
500
686
 
501
- 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;
502
691
 
692
+ const headBefore = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
503
693
  const mergeResult = await executeMergeStrategy(strategy, sourceBranch, commitMessage, mainPath);
694
+
504
695
  if (mergeResult.exitCode !== 0) {
505
- const conflictFiles = await detectMergeConflicts(mainPath);
506
- const data = conflictFiles.length > 0
507
- ? { success: false, conflictFiles }
508
- : { 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);
509
701
  ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data });
510
702
  return;
511
703
  }
512
704
 
513
705
  const headAfter = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
514
706
  if (headBefore.stdout.trim() === headAfter.stdout.trim()) {
515
- 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 } });
516
709
  return;
517
710
  }
518
711
 
519
712
  const commitHashResult = await executeGitCommand(['rev-parse', '--short', 'HEAD'], mainPath);
520
713
  const { warnings, removedWorktreePath } = await cleanupAfterMerge(mainPath, sourceBranch, strategy, !!deleteWorktree, !!deleteBranch);
714
+ if (removedWorktreePath) cleanupWorktreeReferences(ctx, workingDir, removedWorktreePath);
715
+
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
+ }
521
722
 
522
- if (removedWorktreePath) {
523
- cleanupWorktreeReferences(ctx, workingDir, removedWorktreePath);
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;
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;
524
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
+ }
525
743
 
526
- const data: Record<string, unknown> = { success: true, mergeCommit: commitHashResult.stdout.trim() };
527
- if (warnings.length > 0) {
528
- 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;
529
750
  }
530
- 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);
531
774
  } catch (error: unknown) {
532
775
  ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
533
776
  }
@@ -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
  import type { ChildProcess } from 'node:child_process';
5
4
  import type { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
@@ -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
@@ -13,6 +12,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
13
12
  import { homedir } from 'node:os';
14
13
  import { dirname, join } from 'node:path';
15
14
  import type { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
15
+ import type { InstanceRegistry } from '../instances.js';
16
16
  import { captureException } from '../sentry.js';
17
17
  import { getPTYManager } from '../terminal/pty-manager.js';
18
18
  import { AutocompleteService } from './autocomplete.js';
@@ -31,7 +31,7 @@ import { generateNotificationSummary, handleGetSettings, handleUpdateSettings }
31
31
  import { handleListSkills } from './skill-handlers.js';
32
32
  import { SkillsWatcher } from './skill-watcher.js';
33
33
  import { TabEventBufferRegistry } from './tab-event-buffer.js';
34
- import { handleCreateTab, handleGetActiveTabs, handleMarkTabViewed, handleRemoveTab, handleReorderTabs, handleSyncTabMeta } from './tab-handlers.js';
34
+ import { handleCreateTab, handleGetActiveTabs, handleMarkTabViewed, handleRemoveTab, handleReorderTabs, handleSetTabEngine, handleSyncTabMeta } from './tab-handlers.js';
35
35
  import { cleanupTerminalSubscribers, handleTerminalMessage } from './terminal-handlers.js';
36
36
  import type { FrecencyData, WebSocketMessage, WebSocketResponse, WSContext } from './types.js';
37
37
 
@@ -56,11 +56,14 @@ export class WebSocketImproviseHandler implements HandlerContext {
56
56
  skillsWatcher: SkillsWatcher | null = null;
57
57
  tabEventBuffers: TabEventBufferRegistry = new TabEventBufferRegistry();
58
58
  msgIdTracker: MsgIdTracker = new MsgIdTracker();
59
+ private instanceRegistry: InstanceRegistry | null;
60
+ private shutdownInProgress = false;
59
61
 
60
- constructor() {
62
+ constructor(instanceRegistry: InstanceRegistry | null = null) {
61
63
  this.frecencyPath = join(homedir(), '.mstro', 'autocomplete-frecency.json');
62
64
  const frecencyData = this.loadFrecencyData();
63
65
  this.autocompleteService = new AutocompleteService(frecencyData);
66
+ this.instanceRegistry = instanceRegistry;
64
67
  process.on('exit', () => {
65
68
  if (this.frecencySaveTimer) {
66
69
  clearTimeout(this.frecencySaveTimer);
@@ -202,6 +205,9 @@ export class WebSocketImproviseHandler implements HandlerContext {
202
205
  return handleRemoveTab(this, ws, tabId, workingDir);
203
206
  case 'markTabViewed':
204
207
  return handleMarkTabViewed(this, ws, tabId, workingDir);
208
+ case 'setTabEngine':
209
+ if (permission === 'view') return;
210
+ return handleSetTabEngine(this, ws, msg, tabId, workingDir);
205
211
  case 'getSettings':
206
212
  return handleGetSettings(this, ws);
207
213
  case 'updateSettings':
@@ -209,6 +215,8 @@ export class WebSocketImproviseHandler implements HandlerContext {
209
215
  return handleUpdateSettings(this, ws, msg);
210
216
  case 'listSkills':
211
217
  return handleListSkills(this, ws, workingDir);
218
+ case 'shutdownInstance':
219
+ return this.handleShutdownInstance(ws, permission);
212
220
  }
213
221
 
214
222
  // Dispatch table lookup for domain handlers
@@ -235,7 +243,7 @@ export class WebSocketImproviseHandler implements HandlerContext {
235
243
  case 'git': return handleGitMessage(this, ws, msg, tabId, workingDir);
236
244
  case 'quality': return handleQualityMessage(this, ws, msg, tabId, workingDir, permission);
237
245
  case 'plan': return handlePlanMessage(this, ws, msg, tabId, workingDir, permission);
238
- case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId, workingDir, permission);
246
+ case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId, effectiveDir, permission);
239
247
  case 'fileDownload': return this.handleFileDownloadMessage(ws, msg, tabId, effectiveDir);
240
248
  }
241
249
  }
@@ -259,14 +267,14 @@ export class WebSocketImproviseHandler implements HandlerContext {
259
267
 
260
268
  private handleFileUploadMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string, permission?: 'view'): void {
261
269
  if (!this.fileUploadHandler) {
262
- this.fileUploadHandler = new FileUploadHandler(workingDir, this);
270
+ this.fileUploadHandler = new FileUploadHandler(this);
263
271
  }
264
272
  const handler = this.fileUploadHandler;
265
273
  const send = this.send.bind(this);
266
274
 
267
275
  switch (msg.type) {
268
276
  case 'fileUploadStart':
269
- handler.handleUploadStart(ws, send, tabId, msg.data, permission);
277
+ handler.handleUploadStart(ws, send, tabId, msg.data, workingDir, permission);
270
278
  break;
271
279
  case 'fileUploadChunk':
272
280
  handler.handleUploadChunk(ws, send, tabId, msg.data);
@@ -383,4 +391,52 @@ export class WebSocketImproviseHandler implements HandlerContext {
383
391
  this.sessions.delete(sessionId);
384
392
  }
385
393
 
394
+ /**
395
+ * Handle a `shutdownInstance` control message from a web client.
396
+ *
397
+ * Authorization: only the orchestra owner may shut down. The relay tags
398
+ * shared (view-only) users with `_permission: 'view'`; absence means the
399
+ * requester is the owner whose CLI this is. View-only requests are
400
+ * rejected with a `forbidden` error rather than silently dropped so the
401
+ * UI can surface "you're not the owner" to non-owners.
402
+ *
403
+ * Idempotency: a shutdown already in progress is acked (broadcast +
404
+ * exit timer were already scheduled) but does not stack a second timer.
405
+ */
406
+ private handleShutdownInstance(ws: WSContext, permission: 'view' | undefined): void {
407
+ if (permission === 'view') {
408
+ console.log('[WebSocketImproviseHandler] Rejecting shutdownInstance from view-only user');
409
+ this.send(ws, {
410
+ type: 'error',
411
+ data: {
412
+ code: 'forbidden',
413
+ message: 'Only the owner can shut down this instance.'
414
+ }
415
+ });
416
+ return;
417
+ }
418
+
419
+ if (this.shutdownInProgress) {
420
+ console.log('[WebSocketImproviseHandler] shutdownInstance already in progress — ignoring duplicate request');
421
+ return;
422
+ }
423
+ this.shutdownInProgress = true;
424
+
425
+ // The CLI knows the request came from the owner (the relay only forwards
426
+ // owner traffic without a `_permission` tag), but does not receive the
427
+ // owner's userId on the wire. Logged as 'owner' for the audit trail.
428
+ console.log('[WebSocketImproviseHandler] shutdownInstance requested by owner — broadcasting shuttingDown and exiting');
429
+
430
+ this.broadcastToAll({ type: 'shuttingDown', data: { reason: 'user-requested' } });
431
+
432
+ // Mirrors the HTTP /api/shutdown route's 100ms delay so the broadcast
433
+ // has a chance to flush before process.exit tears down the socket.
434
+ setTimeout(() => {
435
+ if (this.instanceRegistry) {
436
+ this.instanceRegistry.unregister();
437
+ }
438
+ process.exit(0);
439
+ }, 100);
440
+ }
441
+
386
442
  }
@@ -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
@@ -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
  * Idempotency tracker for client-sent message IDs.
@@ -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
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
5
4
  import { join } from 'node:path';
@@ -1,11 +1,11 @@
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 { handlePlanPrompt } from '../plan/composer.js';
5
4
  import type { PlanExecutor } from '../plan/executor.js';
6
5
  import { parsePlanDirectory } from '../plan/parser.js';
7
6
  import type { HandlerContext } from './handler-context.js';
8
7
  import { denyIfViewOnly, executorCache, getExecutor } from './plan-helpers.js';
8
+ import { mergePreUploadedAttachments } from './session-handlers.js';
9
9
  import type { WebSocketMessage, WSContext } from './types.js';
10
10
 
11
11
  // ============================================================================
@@ -25,7 +25,11 @@ export function handlePrompt(
25
25
  return;
26
26
  }
27
27
  const executionDir = boardId ? ctx.gitDirectories.get(boardId) : undefined;
28
- handlePlanPrompt(ctx, ws, prompt, workingDir, boardId, executionDir).catch(error => {
28
+ // Pull in any chunked uploads stashed under this board's tabId by the
29
+ // composer paperclip / drag-drop path, so they flow into the plan prompt
30
+ // exactly like chat-tab attachments do.
31
+ const attachments = boardId ? mergePreUploadedAttachments(ctx, boardId, undefined) : undefined;
32
+ handlePlanPrompt(ctx, ws, prompt, workingDir, boardId, executionDir, attachments).catch(error => {
29
33
  ctx.send(ws, {
30
34
  type: 'planError',
31
35
  data: { error: error instanceof Error ? error.message : String(error) },
@@ -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
  * Plan Handlers — WebSocket message router for Plan view
@@ -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
  import { mkdirSync, writeFileSync } from 'node:fs';
5
4
  import { join, resolve } from 'node:path';
@@ -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
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
5
4
  import { basename, join } from 'node:path';
@@ -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
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
4
  import { join } from 'node:path';