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
  /**
5
4
  * Platform Connection Service
@@ -14,12 +13,8 @@ import { arch, hostname, type } from 'node:os'
14
13
  import { basename } from 'node:path'
15
14
  import { AnalyticsEvents, trackEvent } from './analytics.js'
16
15
  import { getClientId } from './client-id.js'
17
- import {
18
- CLI_VERSION,
19
- getCredentials,
20
- shouldRefreshToken,
21
- updateCredentials,
22
- } from './platform-credentials.js'
16
+ import { CLI_VERSION, getCredentials } from './platform-credentials.js'
17
+ import { TokenLifecycle } from './platform-token-lifecycle.js'
23
18
  import { captureException } from './sentry.js'
24
19
 
25
20
  /**
@@ -50,6 +45,27 @@ if (typeof WebSocket !== 'undefined') {
50
45
  // PLATFORM_URL is set via --server / --dev flag in mstro.js
51
46
  const DEFAULT_PLATFORM_URL = process.env.PLATFORM_URL || 'https://api.mstro.app'
52
47
 
48
+ /**
49
+ * Relay wire-format version this CLI speaks. Bumped by the server when the
50
+ * relay protocol changes incompatibly. Must match (or exceed) the server's
51
+ * `minProtocolVersion` in `server/src/relay/version-policy.ts` — when the
52
+ * server's floor moves above this value, the upgrade handshake returns 426
53
+ * and the CLI surfaces the upgrade message via `handleSocketClose`.
54
+ *
55
+ * This is a build-time constant, not user-configurable: a stale CLI must be
56
+ * told to upgrade rather than allowed to opt itself into compatibility.
57
+ */
58
+ const PROTOCOL_VERSION = 1
59
+
60
+ /**
61
+ * Long sentinel delay used after a 426 protocol-too-old close. The CLI
62
+ * auto-upgrades on the next `mstro` invocation, so a tight reconnect loop
63
+ * would just hammer the server with rejections that policy already decided
64
+ * to refuse. One hour is long enough to count as "stop", short enough that
65
+ * a forgotten foreground process eventually retries on its own.
66
+ */
67
+ const PROTOCOL_UPGRADE_RECONNECT_DELAY_MS = 60 * 60 * 1000
68
+
53
69
  interface ConnectionCallbacks {
54
70
  onConnected?: (connectionId: string) => void
55
71
  onDisconnected?: () => void
@@ -73,11 +89,12 @@ export class PlatformConnection {
73
89
  private callbacks: ConnectionCallbacks
74
90
  private connectionId: string | null = null
75
91
  private isConnected = false
76
- private tokenRefreshInterval: ReturnType<typeof setInterval> | null = null
77
92
  private heartbeatInterval: ReturnType<typeof setInterval> | null = null
78
93
  private missedPongs = 0
79
94
  private everConnected = false
95
+ private protocolUpgradeRequired = false
80
96
  private readonly startedAt: string
97
+ private readonly tokens: TokenLifecycle
81
98
 
82
99
  constructor(
83
100
  workingDirectory: string,
@@ -88,120 +105,13 @@ export class PlatformConnection {
88
105
  this.platformUrl = platformUrl || DEFAULT_PLATFORM_URL
89
106
  this.callbacks = callbacks
90
107
  this.startedAt = new Date().toISOString()
91
- }
92
-
93
- /**
94
- * Refresh the device token if it's older than the refresh interval.
95
- * Returns `true` if the token is (still) valid after this call, `false`
96
- * if refresh was attempted and rejected with an auth error — in which
97
- * case the caller should surface an auth-expired signal to the web
98
- * rather than silently reusing a dead token.
99
- */
100
- private async maybeRefreshToken(): Promise<boolean> {
101
- const creds = getCredentials()
102
- if (!creds) return false
103
- if (!shouldRefreshToken(creds)) return true
104
-
105
- try {
106
- const response = await fetch(`${this.platformUrl}/api/auth/device/refresh`, {
107
- method: 'POST',
108
- headers: {
109
- 'Authorization': `Bearer ${creds.token}`,
110
- 'Content-Type': 'application/json'
111
- }
112
- })
113
-
114
- if (response.ok) {
115
- const data = await response.json() as { accessToken: string }
116
- updateCredentials({
117
- token: data.accessToken,
118
- lastRefreshedAt: new Date().toISOString()
119
- })
120
- return true
121
- }
122
- if (response.status === 401 || response.status === 403) {
123
- console.warn(`[Platform] Token refresh failed — auth is expired (${response.status}). Run \`mstro login --force\`.`)
124
- this.notifyAuthExpired()
125
- return false
126
- }
127
- console.warn(`[Platform] Token refresh failed with status ${response.status}, will retry later`)
128
- return true
129
- } catch (err) {
130
- console.warn('[Platform] Token refresh error:', err)
131
- return true
132
- }
133
- }
134
-
135
- /**
136
- * Verify the current token against the platform. A rejection (401/403)
137
- * means the token is permanently invalid (revoked, signing-key rotation,
138
- * account deleted); the caller should stop looping reconnects and tell
139
- * the user to run `mstro login --force`.
140
- *
141
- * Returns `true` when the token is valid or the verification endpoint
142
- * is unreachable (we prefer false negatives to false positives — a
143
- * network blip shouldn't force a re-login).
144
- */
145
- private async verifyToken(): Promise<boolean> {
146
- const creds = getCredentials()
147
- if (!creds?.token) return false
148
- try {
149
- const response = await fetch(`${this.platformUrl}/api/auth/device/verify`, {
150
- method: 'POST',
151
- headers: { 'Authorization': `Bearer ${creds.token}` },
152
- })
153
- if (response.status === 401 || response.status === 403) {
154
- console.warn(`[Platform] Token verify rejected (${response.status}) — auth is expired.`)
155
- return false
156
- }
157
- return true
158
- } catch {
159
- // Network error: treat as "probably valid" so a flaky connection
160
- // doesn't force users to re-login. The WebSocket open itself will
161
- // catch a truly bad token via the 4001 path.
162
- return true
163
- }
164
- }
165
-
166
- /**
167
- * Surface an auth-expired condition to any paired web clients.
168
- *
169
- * Two cooperating paths deliver this signal — either alone is enough,
170
- * both together cover every timing edge:
171
- *
172
- * 1. **CLI-initiated (this method):** we detected a 401 from the
173
- * `/refresh` or `/verify` endpoint *while the relay socket is
174
- * still open*. `this.send` pushes the message upstream so the
175
- * server relays it to paired webs before we intentionally close.
176
- * A no-op if the socket is already closed.
177
- *
178
- * 2. **Server-initiated:** when the platform closes a CLI socket
179
- * with 4001 or 4008, `handleAuthClose` in `clientHandlers.ts`
180
- * broadcasts the same `clientAuthExpired` to paired webs. This
181
- * covers the cases where the CLI never had a chance to detect
182
- * the rejection itself (e.g. token revoked while the socket was
183
- * idle, server-side token rotation).
184
- *
185
- * IMPORTANT: never call `this.callbacks.onRelayedMessage` here —
186
- * that callback feeds INCOMING web→CLI requests into the local handler,
187
- * which would treat `clientAuthExpired` as an unknown inbound request.
188
- */
189
- private notifyAuthExpired(): void {
190
- this.send({
191
- type: 'clientAuthExpired',
192
- data: {
193
- connectionId: this.connectionId,
194
- message: 'The CLI\'s device token is invalid — run `mstro login --force` on the machine.',
195
- },
108
+ this.tokens = new TokenLifecycle({
109
+ platformUrl: this.platformUrl,
110
+ send: (msg) => this.send(msg),
111
+ getConnectionId: () => this.connectionId,
196
112
  })
197
113
  }
198
114
 
199
- private startTokenRefreshCheck(): void {
200
- this.tokenRefreshInterval = setInterval(() => {
201
- this.maybeRefreshToken()
202
- }, 24 * 60 * 60 * 1000)
203
- }
204
-
205
115
  private startHeartbeat(): void {
206
116
  this.missedPongs = 0
207
117
  this.heartbeatInterval = setInterval(() => this.heartbeatTick(), 25_000)
@@ -232,15 +142,9 @@ export class PlatformConnection {
232
142
  }
233
143
  }
234
144
 
235
- private stopTokenRefreshCheck(): void {
236
- if (this.tokenRefreshInterval) {
237
- clearInterval(this.tokenRefreshInterval)
238
- this.tokenRefreshInterval = null
239
- }
240
- }
241
-
242
145
  connect(): void {
243
146
  this.isIntentionallyClosed = false
147
+ this.protocolUpgradeRequired = false
244
148
 
245
149
  const authToken = getCredentials()?.token
246
150
  if (!authToken) {
@@ -262,24 +166,7 @@ export class PlatformConnection {
262
166
 
263
167
  const connectionTimeout = this.startConnectionTimeout()
264
168
  this.attachSocketHandlers(this.ws, authToken, connectionTimeout)
265
- this.maybeVerifyTokenInParallel()
266
- }
267
-
268
- /**
269
- * Best-effort token verification, fired in parallel with the socket
270
- * open so a slow verify endpoint never delays reconnect.
271
- *
272
- * Only runs when the token is stale enough that we'd be about to
273
- * refresh anyway — keeps the hot path free of an extra network call.
274
- * A truly-revoked token that slips past this check still hits 4001
275
- * on the WebSocket, which also triggers `notifyAuthExpired`.
276
- */
277
- private maybeVerifyTokenInParallel(): void {
278
- const creds = getCredentials()
279
- if (!creds || !shouldRefreshToken(creds)) return
280
- this.verifyToken().then((valid) => {
281
- if (!valid) this.notifyAuthExpired()
282
- }).catch(() => { /* network error — rely on 4001 close path */ })
169
+ this.tokens.maybeVerifyInParallel()
283
170
  }
284
171
 
285
172
  private buildConnectionUrl(): string {
@@ -295,6 +182,7 @@ export class PlatformConnection {
295
182
  cliVersion: CLI_VERSION,
296
183
  capabilities: JSON.stringify({}),
297
184
  startedAt: this.startedAt,
185
+ protocolVersion: String(PROTOCOL_VERSION),
298
186
  })
299
187
  return `${this.platformUrl.replace(/^http/, 'ws')}/ws/client?${params}`
300
188
  }
@@ -319,8 +207,8 @@ export class PlatformConnection {
319
207
  ws.onopen = () => {
320
208
  clearTimeout(connectionTimeout)
321
209
  ws.send(JSON.stringify({ type: 'auth', token: authToken }))
322
- this.maybeRefreshToken()
323
- this.startTokenRefreshCheck()
210
+ this.tokens.maybeRefresh()
211
+ this.tokens.startPeriodicCheck()
324
212
  this.reconnectAttempts = 0
325
213
  trackEvent(AnalyticsEvents.PLATFORM_CONNECTED)
326
214
  }
@@ -342,6 +230,24 @@ export class PlatformConnection {
342
230
  clearTimeout(connectionTimeout)
343
231
  // onclose will be called after this
344
232
  }
233
+
234
+ // The Node `ws` library exposes the raw HTTP upgrade response as an
235
+ // `unexpected-response` event when the server returns a non-101 status
236
+ // (for us: 426 Upgrade Required from the protocol-version gate). The
237
+ // global WebSocket in Bun / Node 21+ doesn't expose this — for those
238
+ // runtimes we rely on the close-code path below. Both paths feed into
239
+ // `protocolUpgradeRequired`, so `handleSocketClose` can decide once.
240
+ const wsWithEmitter = ws as unknown as {
241
+ on?: (event: string, listener: (...args: unknown[]) => void) => void
242
+ }
243
+ if (typeof wsWithEmitter.on === 'function') {
244
+ wsWithEmitter.on('unexpected-response', (...args: unknown[]) => {
245
+ const response = args[1] as { statusCode?: number } | undefined
246
+ if (response?.statusCode === 426) {
247
+ this.protocolUpgradeRequired = true
248
+ }
249
+ })
250
+ }
345
251
  }
346
252
 
347
253
  private handleSocketClose(event: CloseEvent): void {
@@ -350,6 +256,11 @@ export class PlatformConnection {
350
256
 
351
257
  if (this.isIntentionallyClosed) return
352
258
 
259
+ if (this.isProtocolTooOldClose(event)) {
260
+ this.handleProtocolUpgradeRequired()
261
+ return
262
+ }
263
+
353
264
  const isAuthFailure = event.code === 4001 ||
354
265
  event.reason?.includes('Unauthorized') ||
355
266
  (event.code === 1006 && !this.everConnected)
@@ -357,7 +268,7 @@ export class PlatformConnection {
357
268
  if (isAuthFailure) {
358
269
  console.error('\n❌ Authentication failed. Your device token may be invalid or expired.')
359
270
  console.error(' Run `mstro login --force` to re-authenticate.\n')
360
- this.notifyAuthExpired()
271
+ this.tokens.notifyAuthExpired()
361
272
  this.callbacks.onError?.('Authentication failed - run `mstro login --force`')
362
273
  return
363
274
  }
@@ -368,6 +279,53 @@ export class PlatformConnection {
368
279
  this.scheduleReconnect()
369
280
  }
370
281
 
282
+ /**
283
+ * The relay can refuse a CLI for being too old in two shapes:
284
+ *
285
+ * 1. **HTTP 426 during upgrade** — the `ws` library surfaces this via
286
+ * its `unexpected-response` event, which sets
287
+ * `protocolUpgradeRequired` before `onclose` fires. This is the path
288
+ * the current server takes (`checkProtocolVersionGate`).
289
+ *
290
+ * 2. **WS close 1002/1008 with `protocol-too-old` reason** — reserved
291
+ * for a future server variant that completes the upgrade and then
292
+ * closes (e.g. when the policy check moves into a post-handshake
293
+ * stage). The reason string is part of the contract with the server.
294
+ *
295
+ * Anything else (1006 race after a successful run, 1001 going-away on
296
+ * deploy) must fall through to the regular reconnect path so transient
297
+ * failures keep healing on their own.
298
+ */
299
+ private isProtocolTooOldClose(event: CloseEvent): boolean {
300
+ if (this.protocolUpgradeRequired) return true
301
+ const code = event.code
302
+ if ((code === 1002 || code === 1008) && event.reason?.includes('protocol-too-old')) {
303
+ return true
304
+ }
305
+ return false
306
+ }
307
+
308
+ private handleProtocolUpgradeRequired(): void {
309
+ const message = 'Mstro CLI is out of date for this orchestra. Run `mstro` again to upgrade and reconnect.'
310
+ console.error(`\n❌ ${message}\n`)
311
+ this.callbacks.onError?.(message)
312
+ this.callbacks.onDisconnected?.()
313
+
314
+ // Don't exit the process — terminal sessions and other local state stay
315
+ // alive while the user re-runs `mstro`. Schedule a single, far-future
316
+ // reconnect as a sentinel so we silently retry exactly once if the
317
+ // process is somehow still around an hour later. The existing
318
+ // exponential-backoff path is bypassed deliberately: hammering a server
319
+ // that has already decided to refuse on policy is pointless.
320
+ if (this.reconnectTimeout) {
321
+ clearTimeout(this.reconnectTimeout)
322
+ }
323
+ this.reconnectTimeout = setTimeout(() => {
324
+ this.reconnectTimeout = null
325
+ this.connect()
326
+ }, PROTOCOL_UPGRADE_RECONNECT_DELAY_MS)
327
+ }
328
+
371
329
  private handleMessage(message: Record<string, unknown>): void {
372
330
  switch (message.type) {
373
331
  case 'paired':
@@ -430,7 +388,7 @@ export class PlatformConnection {
430
388
  disconnect(): void {
431
389
  this.isIntentionallyClosed = true
432
390
  this.stopHeartbeat()
433
- this.stopTokenRefreshCheck()
391
+ this.tokens.stopPeriodicCheck()
434
392
 
435
393
  if (this.reconnectTimeout) {
436
394
  clearTimeout(this.reconnectTimeout)
@@ -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 } from 'node:fs'
5
4
  import { homedir } from 'node:os'
@@ -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
  * Settings Service
@@ -16,10 +15,75 @@
16
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
17
16
  import { homedir } from 'node:os'
18
17
  import { join } from 'node:path'
18
+ import type { EngineId } from '../engines/types.js'
19
19
 
20
20
  const MSTRO_DIR = join(homedir(), '.mstro')
21
21
  const SETTINGS_FILE = join(MSTRO_DIR, 'settings.json')
22
22
 
23
+ /**
24
+ * Configuration for the Layer-2 Bouncer classifier (the AI model that runs
25
+ * for every ambiguous tool call). The model MUST be flagged
26
+ * `bouncerEligible` in the engine's model catalogue — frontier models
27
+ * (Opus, GPT-4o, …) are deliberately disallowed because they slow the
28
+ * classifier path and degrade the whole security layer.
29
+ */
30
+ export interface BouncerClassifierConfig {
31
+ engine: EngineId
32
+ /** Engine-specific model id, e.g. 'haiku', 'sonnet', 'openai/gpt-5-mini'. */
33
+ model: string
34
+ }
35
+
36
+ /**
37
+ * Canonical list of bouncer-eligible models per engine. Mirrors
38
+ * `web/src/components/views/SettingsView/constants.ts` — keep the two in
39
+ * sync. Only cheap/fast models appear here; if you need to add a model,
40
+ * check p50 latency < ~1s and JSON-mode capability first.
41
+ */
42
+ export const BOUNCER_ELIGIBLE_MODELS: Record<EngineId, readonly string[]> = {
43
+ 'claude-code': ['haiku', 'sonnet'],
44
+ opencode: [
45
+ 'openai/gpt-5-mini',
46
+ 'openai/gpt-5-nano',
47
+ 'google/gemini-2.5-flash',
48
+ 'ollama/llama3.1:8b',
49
+ ],
50
+ }
51
+
52
+ /** Default classifier — Claude Haiku. Matches the pre-feature-flag behavior. */
53
+ export const DEFAULT_BOUNCER_CLASSIFIER: BouncerClassifierConfig = {
54
+ engine: 'claude-code',
55
+ model: 'haiku',
56
+ }
57
+
58
+ /**
59
+ * Validate a `BouncerClassifierConfig`. Rejects with a thrown `Error` when
60
+ * the model is not flagged `bouncerEligible` under the requested engine —
61
+ * e.g. attempting to use Opus as a classifier, or a frontier OpenCode
62
+ * model. The WebSocket settings handler uses this to reject crafted
63
+ * payloads from the web client.
64
+ */
65
+ export function validateBouncerClassifier(config: unknown): BouncerClassifierConfig {
66
+ if (config === null || typeof config !== 'object') {
67
+ throw new Error('bouncerClassifier must be an object with { engine, model }')
68
+ }
69
+ const { engine, model } = config as { engine?: unknown; model?: unknown }
70
+ if (engine !== 'claude-code' && engine !== 'opencode') {
71
+ throw new Error(`bouncerClassifier.engine must be 'claude-code' or 'opencode' (got ${String(engine)})`)
72
+ }
73
+ if (typeof model !== 'string' || model.length === 0) {
74
+ throw new Error('bouncerClassifier.model must be a non-empty string')
75
+ }
76
+ const eligible = BOUNCER_ELIGIBLE_MODELS[engine]
77
+ if (!eligible.includes(model)) {
78
+ throw new Error(
79
+ `Model '${model}' is not bouncer-eligible for engine '${engine}'. ` +
80
+ `Eligible models: ${eligible.join(', ')}. ` +
81
+ `Frontier models (Opus, GPT-4o, etc.) are deliberately excluded to keep the classifier fast.`,
82
+ )
83
+ }
84
+ return { engine, model }
85
+ }
86
+
23
87
  export interface MstroSettings {
24
88
  /**
25
89
  * Claude model to use for main execution.
@@ -38,11 +102,26 @@ export interface MstroSettings {
38
102
  effortLevel: string
39
103
  /** Per-repo preferred PR base branch, keyed by normalized remote URL */
40
104
  prBaseBranches?: Record<string, string>
105
+ /**
106
+ * Feature flag gating all OpenCode code paths (engine factory, classifier
107
+ * factory, and UI). When `false`, the system behaves byte-identically to
108
+ * pre-OpenCode main: no `opencode serve` subprocess, no classifier picker,
109
+ * no EngineSection/EnginePicker in the web UI. Resolution order in
110
+ * `isEngineSwapEnabled()`: env var → stored setting → NODE_ENV default.
111
+ */
112
+ engineSwap?: boolean
113
+ /**
114
+ * Which engine + model backs the Layer-2 Bouncer classifier. Defaults to
115
+ * `{ engine: 'claude-code', model: 'haiku' }`. Only models flagged
116
+ * `bouncerEligible` are accepted — see {@link validateBouncerClassifier}.
117
+ */
118
+ bouncerClassifier?: BouncerClassifierConfig
41
119
  }
42
120
 
43
121
  const DEFAULT_SETTINGS: MstroSettings = {
44
122
  model: 'opus',
45
- effortLevel: 'auto'
123
+ effortLevel: 'auto',
124
+ bouncerClassifier: { ...DEFAULT_BOUNCER_CLASSIFIER },
46
125
  }
47
126
 
48
127
  /**
@@ -55,7 +134,11 @@ function ensureMstroDir(): void {
55
134
  }
56
135
 
57
136
  /**
58
- * Get current settings, merged with defaults for any missing fields
137
+ * Get current settings, merged with defaults for any missing fields. A
138
+ * persisted `bouncerClassifier` that is no longer bouncer-eligible (e.g. a
139
+ * catalogue change removed the model) is dropped in favor of the default
140
+ * and a warning is logged — the Bouncer must never silently run a
141
+ * non-eligible model just because someone edited settings.json by hand.
59
142
  */
60
143
  export function getSettings(): MstroSettings {
61
144
  if (!existsSync(SETTINGS_FILE)) {
@@ -65,10 +148,22 @@ export function getSettings(): MstroSettings {
65
148
  try {
66
149
  const content = readFileSync(SETTINGS_FILE, 'utf-8')
67
150
  const stored = JSON.parse(content)
68
- return {
151
+ const merged: MstroSettings = {
69
152
  ...DEFAULT_SETTINGS,
70
153
  ...stored,
71
154
  }
155
+ if (stored && typeof stored === 'object' && 'bouncerClassifier' in stored) {
156
+ try {
157
+ merged.bouncerClassifier = validateBouncerClassifier(stored.bouncerClassifier)
158
+ } catch (err) {
159
+ console.warn(
160
+ '[settings] Stored bouncerClassifier is not bouncer-eligible, falling back to default:',
161
+ err instanceof Error ? err.message : String(err),
162
+ )
163
+ merged.bouncerClassifier = { ...DEFAULT_BOUNCER_CLASSIFIER }
164
+ }
165
+ }
166
+ return merged
72
167
  } catch (err) {
73
168
  console.warn('Failed to read settings file, using defaults:', err)
74
169
  return { ...DEFAULT_SETTINGS }
@@ -76,9 +171,18 @@ export function getSettings(): MstroSettings {
76
171
  }
77
172
 
78
173
  /**
79
- * Save full settings to disk
174
+ * Save full settings to disk. Rejects if `bouncerClassifier` is present but
175
+ * its model is not flagged `bouncerEligible` — this is the save-time half
176
+ * of the guard; `getSettings` enforces the read-time half. Together they
177
+ * ensure the Bouncer is never configured with a frontier model (Opus,
178
+ * GPT-4o, …) regardless of whether the mutation came from the web UI or a
179
+ * direct edit of settings.json.
80
180
  */
81
181
  export function saveSettings(settings: MstroSettings): void {
182
+ if (settings.bouncerClassifier !== undefined) {
183
+ // Throws on non-eligible model — callers must surface the error.
184
+ validateBouncerClassifier(settings.bouncerClassifier)
185
+ }
82
186
  ensureMstroDir()
83
187
  writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), {
84
188
  mode: 0o600
@@ -117,6 +221,58 @@ export function setEffortLevel(effortLevel: string): void {
117
221
  saveSettings(settings)
118
222
  }
119
223
 
224
+ /**
225
+ * Get the current Bouncer classifier configuration. Returns the default
226
+ * `{ engine: 'claude-code', model: 'haiku' }` when nothing is persisted.
227
+ */
228
+ export function getBouncerClassifier(): BouncerClassifierConfig {
229
+ const settings = getSettings()
230
+ if (settings.bouncerClassifier) {
231
+ try {
232
+ return validateBouncerClassifier(settings.bouncerClassifier)
233
+ } catch {
234
+ // Stored config is no longer eligible (e.g. model removed from the
235
+ // catalogue). Fall back to the safe default rather than crashing.
236
+ return { ...DEFAULT_BOUNCER_CLASSIFIER }
237
+ }
238
+ }
239
+ return { ...DEFAULT_BOUNCER_CLASSIFIER }
240
+ }
241
+
242
+ /**
243
+ * Persist a new Bouncer classifier config. Throws if the model is not
244
+ * flagged `bouncerEligible` under the requested engine — callers should
245
+ * surface the error to the UI so the user sees a clear rejection reason.
246
+ */
247
+ export function setBouncerClassifier(config: unknown): BouncerClassifierConfig {
248
+ const validated = validateBouncerClassifier(config)
249
+ const settings = getSettings()
250
+ settings.bouncerClassifier = validated
251
+ saveSettings(settings)
252
+ return validated
253
+ }
254
+
255
+ /**
256
+ * Resolve the engineSwap feature flag. Precedence:
257
+ * 1. `MSTRO_ENABLE_ENGINE_SWAP` env var ('true'|'1' → on, 'false'|'0' → off).
258
+ * 2. `engineSwap` field in `~/.mstro/settings.json`.
259
+ * 3. NODE_ENV default — off in production, on otherwise (dev/staging/test).
260
+ *
261
+ * Callers who need a single boolean should use this helper rather than
262
+ * reading the field directly, so the precedence stays in one place.
263
+ */
264
+ export function isEngineSwapEnabled(): boolean {
265
+ const envFlag = process.env.MSTRO_ENABLE_ENGINE_SWAP
266
+ if (envFlag !== undefined) {
267
+ const normalized = envFlag.trim().toLowerCase()
268
+ if (normalized === 'true' || normalized === '1') return true
269
+ if (normalized === 'false' || normalized === '0') return false
270
+ }
271
+ const stored = getSettings().engineSwap
272
+ if (typeof stored === 'boolean') return stored
273
+ return process.env.NODE_ENV !== 'production'
274
+ }
275
+
120
276
  /** Normalize a remote URL into a stable key (e.g. "github.com/owner/repo") */
121
277
  function normalizeRemoteUrl(remoteUrl: string): string {
122
278
  return remoteUrl
@@ -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
  * PTY Manager - Manages pseudo-terminal sessions for shell access.
@@ -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
  * PTY Utilities — node-pty loading, shell detection, scrollback buffer, and types.
@@ -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
  * Autocomplete Service
@@ -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 { spawn } from 'node:child_process';
5
4
  import { join, relative } 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
  /**
5
4
  * Chunked File Download Handler
@@ -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 {
5
4
  createDirectory,
@@ -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 { spawn } from 'node:child_process';
5
4
  import { relative } from 'node:path';
@@ -1,12 +1,13 @@
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
  * Chunked File Upload Handler
6
5
  *
7
6
  * Receives files in chunks over WebSocket from remote web clients.
7
+ * The destination working directory is passed per-call (effective dir for the tab,
8
+ * which resolves to the worktree path when the tab is on a worktree).
8
9
  * - When `targetPath` is provided: streams to <workingDir>/<targetPath> (drag-drop into file tree).
9
- * - Otherwise: streams to .mstro/tmp/attachments/{tabId}/ (prompt attachments).
10
+ * - Otherwise: streams to <workingDir>/.mstro/tmp/attachments/{tabId}/ (prompt attachments).
10
11
  */
11
12
 
12
13
  import type { WriteStream } from 'node:fs';
@@ -50,7 +51,6 @@ export class FileUploadHandler {
50
51
  private cleanupInterval: ReturnType<typeof setInterval>;
51
52
 
52
53
  constructor(
53
- private workingDir: string,
54
54
  private ctx?: HandlerContext,
55
55
  ) {
56
56
  // Periodically clean up stale uploads
@@ -74,6 +74,7 @@ export class FileUploadHandler {
74
74
  send: (ws: WSContext, response: WebSocketResponse) => void,
75
75
  tabId: string,
76
76
  data: { uploadId: string; fileName: string; fileSize: number; mimeType: string; isImage: boolean; totalChunks: number; targetPath?: string; overwrite?: boolean },
77
+ workingDir: string,
77
78
  permission?: 'view',
78
79
  ): void {
79
80
  const { uploadId, fileName, fileSize, mimeType, isImage, totalChunks, targetPath, overwrite } = data;
@@ -89,8 +90,8 @@ export class FileUploadHandler {
89
90
  }
90
91
 
91
92
  const resolved = targetPath !== undefined
92
- ? resolveFileTreeTarget({ targetPath, fileName, workingDir: this.workingDir, permission, overwrite })
93
- : resolveAttachmentTarget({ workingDir: this.workingDir, tabId, fileName });
93
+ ? resolveFileTreeTarget({ targetPath, fileName, workingDir, permission, overwrite })
94
+ : resolveAttachmentTarget({ workingDir, tabId, fileName });
94
95
 
95
96
  if (!resolved.ok) {
96
97
  sendError(resolved.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
  * File Utilities