mstro-app 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/bin/mstro.js +119 -40
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  3. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  5. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  9. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  13. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  17. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  18. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  19. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  20. package/dist/server/cli/headless/claude-invoker.js +10 -804
  21. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  22. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  23. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  24. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  25. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  26. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  27. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  28. package/dist/server/cli/headless/headless-logger.js +28 -5
  29. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  31. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  33. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  34. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  35. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  36. package/dist/server/cli/headless/stall-assessor.js +65 -457
  37. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  38. package/dist/server/cli/headless/types.d.ts +4 -1
  39. package/dist/server/cli/headless/types.d.ts.map +1 -1
  40. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  41. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  42. package/dist/server/cli/improvisation-attachments.js +116 -0
  43. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  44. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  45. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  46. package/dist/server/cli/improvisation-retry.js +434 -0
  47. package/dist/server/cli/improvisation-retry.js.map +1 -0
  48. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  49. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  50. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  51. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  52. package/dist/server/cli/improvisation-types.d.ts +86 -0
  53. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  54. package/dist/server/cli/improvisation-types.js +10 -0
  55. package/dist/server/cli/improvisation-types.js.map +1 -0
  56. package/dist/server/cli/prompt-builders.d.ts +68 -0
  57. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  58. package/dist/server/cli/prompt-builders.js +312 -0
  59. package/dist/server/cli/prompt-builders.js.map +1 -0
  60. package/dist/server/index.js +33 -212
  61. package/dist/server/index.js.map +1 -1
  62. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  63. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  64. package/dist/server/mcp/bouncer-haiku.js +152 -0
  65. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  66. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  67. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  68. package/dist/server/mcp/bouncer-integration.js +50 -196
  69. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  70. package/dist/server/mcp/security-analysis.d.ts +38 -0
  71. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  72. package/dist/server/mcp/security-analysis.js +183 -0
  73. package/dist/server/mcp/security-analysis.js.map +1 -0
  74. package/dist/server/mcp/security-audit.d.ts +1 -1
  75. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  76. package/dist/server/mcp/security-patterns.d.ts +1 -25
  77. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  78. package/dist/server/mcp/security-patterns.js +55 -260
  79. package/dist/server/mcp/security-patterns.js.map +1 -1
  80. package/dist/server/server-setup.d.ts +22 -0
  81. package/dist/server/server-setup.d.ts.map +1 -0
  82. package/dist/server/server-setup.js +101 -0
  83. package/dist/server/server-setup.js.map +1 -0
  84. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  85. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  86. package/dist/server/services/file-explorer-ops.js +211 -0
  87. package/dist/server/services/file-explorer-ops.js.map +1 -0
  88. package/dist/server/services/files.d.ts +2 -85
  89. package/dist/server/services/files.d.ts.map +1 -1
  90. package/dist/server/services/files.js +7 -427
  91. package/dist/server/services/files.js.map +1 -1
  92. package/dist/server/services/plan/composer.d.ts +1 -1
  93. package/dist/server/services/plan/composer.d.ts.map +1 -1
  94. package/dist/server/services/plan/composer.js +118 -32
  95. package/dist/server/services/plan/composer.js.map +1 -1
  96. package/dist/server/services/plan/config-installer.d.ts +25 -0
  97. package/dist/server/services/plan/config-installer.d.ts.map +1 -0
  98. package/dist/server/services/plan/config-installer.js +182 -0
  99. package/dist/server/services/plan/config-installer.js.map +1 -0
  100. package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
  101. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  102. package/dist/server/services/plan/dependency-resolver.js +4 -1
  103. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  104. package/dist/server/services/plan/executor.d.ts +38 -74
  105. package/dist/server/services/plan/executor.d.ts.map +1 -1
  106. package/dist/server/services/plan/executor.js +274 -460
  107. package/dist/server/services/plan/executor.js.map +1 -1
  108. package/dist/server/services/plan/front-matter.d.ts +18 -0
  109. package/dist/server/services/plan/front-matter.d.ts.map +1 -0
  110. package/dist/server/services/plan/front-matter.js +44 -0
  111. package/dist/server/services/plan/front-matter.js.map +1 -0
  112. package/dist/server/services/plan/output-manager.d.ts +22 -0
  113. package/dist/server/services/plan/output-manager.d.ts.map +1 -0
  114. package/dist/server/services/plan/output-manager.js +97 -0
  115. package/dist/server/services/plan/output-manager.js.map +1 -0
  116. package/dist/server/services/plan/parser-core.d.ts +20 -0
  117. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  118. package/dist/server/services/plan/parser-core.js +350 -0
  119. package/dist/server/services/plan/parser-core.js.map +1 -0
  120. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  121. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  122. package/dist/server/services/plan/parser-migration.js +124 -0
  123. package/dist/server/services/plan/parser-migration.js.map +1 -0
  124. package/dist/server/services/plan/parser.d.ts +11 -3
  125. package/dist/server/services/plan/parser.d.ts.map +1 -1
  126. package/dist/server/services/plan/parser.js +184 -369
  127. package/dist/server/services/plan/parser.js.map +1 -1
  128. package/dist/server/services/plan/prompt-builder.d.ts +17 -0
  129. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
  130. package/dist/server/services/plan/prompt-builder.js +137 -0
  131. package/dist/server/services/plan/prompt-builder.js.map +1 -0
  132. package/dist/server/services/plan/review-gate.d.ts +28 -0
  133. package/dist/server/services/plan/review-gate.d.ts.map +1 -0
  134. package/dist/server/services/plan/review-gate.js +191 -0
  135. package/dist/server/services/plan/review-gate.js.map +1 -0
  136. package/dist/server/services/plan/state-reconciler.d.ts +1 -1
  137. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  138. package/dist/server/services/plan/state-reconciler.js +59 -7
  139. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  140. package/dist/server/services/plan/types.d.ts +68 -0
  141. package/dist/server/services/plan/types.d.ts.map +1 -1
  142. package/dist/server/services/platform-credentials.d.ts +24 -0
  143. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  144. package/dist/server/services/platform-credentials.js +68 -0
  145. package/dist/server/services/platform-credentials.js.map +1 -0
  146. package/dist/server/services/platform.d.ts +1 -31
  147. package/dist/server/services/platform.d.ts.map +1 -1
  148. package/dist/server/services/platform.js +11 -109
  149. package/dist/server/services/platform.js.map +1 -1
  150. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  151. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  152. package/dist/server/services/terminal/pty-manager.js +53 -266
  153. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  154. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  155. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  156. package/dist/server/services/terminal/pty-utils.js +141 -0
  157. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  158. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  159. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  160. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  161. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  162. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  163. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  164. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  165. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  166. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  167. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  168. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  169. package/dist/server/services/websocket/file-utils.js +3 -3
  170. package/dist/server/services/websocket/file-utils.js.map +1 -1
  171. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  172. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  173. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  174. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  175. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  176. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  177. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  178. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  179. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  180. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  181. package/dist/server/services/websocket/git-handlers.js +35 -541
  182. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  183. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  184. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  185. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  186. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  187. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  188. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  189. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  190. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  191. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  192. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  193. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  194. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  195. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  196. package/dist/server/services/websocket/git-utils.js +201 -0
  197. package/dist/server/services/websocket/git-utils.js.map +1 -0
  198. package/dist/server/services/websocket/handler.d.ts +2 -0
  199. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  200. package/dist/server/services/websocket/handler.js +37 -112
  201. package/dist/server/services/websocket/handler.js.map +1 -1
  202. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  203. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  204. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  205. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  206. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  207. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  208. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  209. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  210. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  211. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  212. package/dist/server/services/websocket/plan-handlers.js +21 -462
  213. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  214. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  215. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  216. package/dist/server/services/websocket/plan-helpers.js +199 -0
  217. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  218. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  219. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  220. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  221. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  222. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  223. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  224. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  225. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  226. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  227. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  228. package/dist/server/services/websocket/quality-complexity.js +262 -0
  229. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  230. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  231. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  232. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  233. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  234. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  235. package/dist/server/services/websocket/quality-handlers.js +34 -346
  236. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  237. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  238. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  239. package/dist/server/services/websocket/quality-linting.js +178 -0
  240. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  241. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  242. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  243. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  244. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  245. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  246. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  247. package/dist/server/services/websocket/quality-service.js +9 -651
  248. package/dist/server/services/websocket/quality-service.js.map +1 -1
  249. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  250. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  251. package/dist/server/services/websocket/quality-tools.js +208 -0
  252. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  253. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  254. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  255. package/dist/server/services/websocket/quality-types.js +101 -0
  256. package/dist/server/services/websocket/quality-types.js.map +1 -0
  257. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  258. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  259. package/dist/server/services/websocket/session-handlers.js +3 -378
  260. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  261. package/dist/server/services/websocket/session-history.d.ts +4 -0
  262. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  263. package/dist/server/services/websocket/session-history.js +208 -0
  264. package/dist/server/services/websocket/session-history.js.map +1 -0
  265. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  266. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  267. package/dist/server/services/websocket/session-initialization.js +163 -0
  268. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  269. package/dist/server/services/websocket/types.d.ts +12 -2
  270. package/dist/server/services/websocket/types.d.ts.map +1 -1
  271. package/package.json +1 -2
  272. package/server/cli/headless/claude-invoker-process.ts +204 -0
  273. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  274. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  275. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  276. package/server/cli/headless/claude-invoker.ts +15 -1092
  277. package/server/cli/headless/haiku-assessments.ts +365 -0
  278. package/server/cli/headless/headless-logger.ts +26 -5
  279. package/server/cli/headless/native-timeout-detector.ts +117 -0
  280. package/server/cli/headless/stall-assessor.ts +65 -618
  281. package/server/cli/headless/types.ts +4 -1
  282. package/server/cli/improvisation-attachments.ts +148 -0
  283. package/server/cli/improvisation-retry.ts +602 -0
  284. package/server/cli/improvisation-session-manager.ts +140 -1349
  285. package/server/cli/improvisation-types.ts +98 -0
  286. package/server/cli/prompt-builders.ts +370 -0
  287. package/server/index.ts +35 -246
  288. package/server/mcp/bouncer-haiku.ts +182 -0
  289. package/server/mcp/bouncer-integration.ts +87 -248
  290. package/server/mcp/security-analysis.ts +217 -0
  291. package/server/mcp/security-audit.ts +1 -1
  292. package/server/mcp/security-patterns.ts +60 -283
  293. package/server/server-setup.ts +114 -0
  294. package/server/services/file-explorer-ops.ts +293 -0
  295. package/server/services/files.ts +20 -532
  296. package/server/services/plan/composer.ts +140 -35
  297. package/server/services/plan/config-installer.ts +187 -0
  298. package/server/services/plan/dependency-resolver.ts +4 -1
  299. package/server/services/plan/executor.ts +281 -488
  300. package/server/services/plan/front-matter.ts +48 -0
  301. package/server/services/plan/output-manager.ts +113 -0
  302. package/server/services/plan/parser-core.ts +406 -0
  303. package/server/services/plan/parser-migration.ts +128 -0
  304. package/server/services/plan/parser.ts +188 -394
  305. package/server/services/plan/prompt-builder.ts +161 -0
  306. package/server/services/plan/review-gate.ts +212 -0
  307. package/server/services/plan/state-reconciler.ts +68 -7
  308. package/server/services/plan/types.ts +101 -1
  309. package/server/services/platform-credentials.ts +83 -0
  310. package/server/services/platform.ts +16 -131
  311. package/server/services/terminal/pty-manager.ts +66 -313
  312. package/server/services/terminal/pty-utils.ts +176 -0
  313. package/server/services/websocket/file-definition-handlers.ts +165 -0
  314. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  315. package/server/services/websocket/file-search-handlers.ts +291 -0
  316. package/server/services/websocket/file-utils.ts +3 -3
  317. package/server/services/websocket/git-branch-handlers.ts +130 -0
  318. package/server/services/websocket/git-diff-handlers.ts +140 -0
  319. package/server/services/websocket/git-handlers.ts +40 -625
  320. package/server/services/websocket/git-log-handlers.ts +149 -0
  321. package/server/services/websocket/git-pr-handlers.ts +17 -62
  322. package/server/services/websocket/git-tag-handlers.ts +91 -0
  323. package/server/services/websocket/git-utils.ts +230 -0
  324. package/server/services/websocket/handler.ts +39 -112
  325. package/server/services/websocket/plan-board-handlers.ts +277 -0
  326. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  327. package/server/services/websocket/plan-handlers.ts +23 -544
  328. package/server/services/websocket/plan-helpers.ts +215 -0
  329. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  330. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  331. package/server/services/websocket/quality-complexity.ts +294 -0
  332. package/server/services/websocket/quality-fix-agent.ts +181 -0
  333. package/server/services/websocket/quality-handlers.ts +36 -404
  334. package/server/services/websocket/quality-linting.ts +187 -0
  335. package/server/services/websocket/quality-review-agent.ts +246 -0
  336. package/server/services/websocket/quality-service.ts +11 -762
  337. package/server/services/websocket/quality-tools.ts +209 -0
  338. package/server/services/websocket/quality-types.ts +169 -0
  339. package/server/services/websocket/session-handlers.ts +5 -437
  340. package/server/services/websocket/session-history.ts +222 -0
  341. package/server/services/websocket/session-initialization.ts +209 -0
  342. package/server/services/websocket/types.ts +46 -2
@@ -0,0 +1,602 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Retry and recovery logic for improvisation sessions.
6
+ * Handles context loss, tool timeouts, signal crashes, and premature completion.
7
+ */
8
+
9
+ import { AnalyticsEvents, trackEvent } from '../services/analytics.js';
10
+ import { hlog } from './headless/headless-logger.js';
11
+ import { HeadlessRunner } from './headless/index.js';
12
+ import { assessBestResult, assessContextLoss, assessPrematureCompletion, type ContextLossContext } from './headless/stall-assessor.js';
13
+ import type { ExecutionCheckpoint } from './headless/types.js';
14
+ import type { FileAttachment, HeadlessRunResult, ImprovisationOptions, MovementRecord, RetryLoopState, SessionHistory } from './improvisation-types.js';
15
+ import { scoreRunResult } from './improvisation-types.js';
16
+ import {
17
+ buildContextRecoveryPrompt,
18
+ buildFreshRecoveryPrompt,
19
+ buildHistoricalContext,
20
+ buildInterMovementRecoveryPrompt,
21
+ buildResumeRetryPrompt,
22
+ buildRetryPrompt,
23
+ buildSignalCrashRecoveryPrompt,
24
+ extractHistoricalToolResults,
25
+ } from './prompt-builders.js';
26
+
27
+ /** Callbacks the retry logic needs from the session manager */
28
+ export interface RetryCallbacks {
29
+ isCancelled: () => boolean;
30
+ queueOutput: (text: string) => void;
31
+ flushOutputQueue: () => void;
32
+ emit: (event: string, ...args: unknown[]) => void;
33
+ addEventLog: (entry: { type: string; data: unknown; timestamp: number }) => void;
34
+ setRunner: (runner: HeadlessRunner | null) => void;
35
+ }
36
+
37
+ /** Session state the retry logic reads/writes */
38
+ export interface RetrySessionState {
39
+ options: ImprovisationOptions;
40
+ claudeSessionId: string | undefined;
41
+ isFirstPrompt: boolean;
42
+ isResumedSession: boolean;
43
+ history: SessionHistory;
44
+ executionStartTimestamp: number | undefined;
45
+ }
46
+
47
+ // ========== Resume Strategy ==========
48
+
49
+ /** Determine whether to use --resume and which session ID */
50
+ export function determineResumeStrategy(
51
+ state: RetryLoopState,
52
+ session: RetrySessionState,
53
+ ): { useResume: boolean; resumeSessionId: string | undefined } {
54
+ if (state.freshRecoveryMode) {
55
+ state.freshRecoveryMode = false;
56
+ return { useResume: false, resumeSessionId: undefined };
57
+ }
58
+ if (state.contextRecoverySessionId) {
59
+ const id = state.contextRecoverySessionId;
60
+ state.contextRecoverySessionId = undefined;
61
+ return { useResume: true, resumeSessionId: id };
62
+ }
63
+ if (state.retryNumber === 0) {
64
+ return { useResume: !session.isFirstPrompt, resumeSessionId: session.claudeSessionId };
65
+ }
66
+ if (state.lastWatchdogCheckpoint?.inProgressTools.length === 0 && state.lastWatchdogCheckpoint.claudeSessionId) {
67
+ return { useResume: true, resumeSessionId: state.lastWatchdogCheckpoint.claudeSessionId };
68
+ }
69
+ return { useResume: false, resumeSessionId: undefined };
70
+ }
71
+
72
+ // ========== Runner Creation ==========
73
+
74
+ /** Create HeadlessRunner for one retry iteration */
75
+ export function createExecutionRunner(
76
+ state: RetryLoopState,
77
+ session: RetrySessionState,
78
+ callbacks: RetryCallbacks,
79
+ sequenceNumber: number,
80
+ useResume: boolean,
81
+ resumeSessionId: string | undefined,
82
+ imageAttachments: FileAttachment[] | undefined,
83
+ sandboxed: boolean | undefined,
84
+ workingDirOverride?: string,
85
+ ): HeadlessRunner {
86
+ return new HeadlessRunner({
87
+ workingDir: workingDirOverride || session.options.workingDir,
88
+ tokenBudgetThreshold: session.options.tokenBudgetThreshold,
89
+ maxSessions: session.options.maxSessions,
90
+ verbose: session.options.verbose,
91
+ noColor: session.options.noColor,
92
+ model: session.options.model,
93
+ improvisationMode: true,
94
+ movementNumber: sequenceNumber,
95
+ continueSession: useResume,
96
+ claudeSessionId: resumeSessionId,
97
+ outputCallback: (text: string) => {
98
+ if (callbacks.isCancelled()) return;
99
+ callbacks.addEventLog({ type: 'output', data: { text, timestamp: Date.now() }, timestamp: Date.now() });
100
+ callbacks.queueOutput(text);
101
+ callbacks.flushOutputQueue();
102
+ },
103
+ thinkingCallback: (text: string) => {
104
+ if (callbacks.isCancelled()) return;
105
+ callbacks.addEventLog({ type: 'thinking', data: { text }, timestamp: Date.now() });
106
+ callbacks.emit('onThinking', text);
107
+ callbacks.flushOutputQueue();
108
+ },
109
+ toolUseCallback: (event) => {
110
+ if (callbacks.isCancelled()) return;
111
+ callbacks.addEventLog({ type: 'toolUse', data: { ...event, timestamp: Date.now() }, timestamp: Date.now() });
112
+ callbacks.emit('onToolUse', event);
113
+ callbacks.flushOutputQueue();
114
+ },
115
+ tokenUsageCallback: (usage) => {
116
+ if (callbacks.isCancelled()) return;
117
+ callbacks.emit('onTokenUsage', usage);
118
+ },
119
+ directPrompt: state.currentPrompt,
120
+ imageAttachments,
121
+ promptContext: (state.retryNumber === 0 && session.isResumedSession && session.isFirstPrompt)
122
+ ? { accumulatedKnowledge: buildHistoricalContext(session.history.movements), filesModified: [] }
123
+ : undefined,
124
+ onToolTimeout: (checkpoint: ExecutionCheckpoint) => {
125
+ state.checkpointRef.value = checkpoint;
126
+ },
127
+ sandboxed,
128
+ });
129
+ }
130
+
131
+ // ========== Context Loss Detection ==========
132
+
133
+ /** Detect resume context loss (Path 1): session expired on --resume */
134
+ export function detectResumeContextLoss(
135
+ result: HeadlessRunResult,
136
+ state: RetryLoopState,
137
+ useResume: boolean,
138
+ maxRetries: number,
139
+ nativeTimeouts: number,
140
+ verbose: boolean,
141
+ ): void {
142
+ if (!useResume || state.checkpointRef.value || state.retryNumber >= maxRetries || nativeTimeouts > 0) {
143
+ return;
144
+ }
145
+ if (!result.assistantResponse || result.assistantResponse.trim().length === 0) {
146
+ state.contextLost = true;
147
+ if (verbose) hlog('[CONTEXT-RECOVERY] Resume context loss: null/empty response');
148
+ } else if (result.resumeBufferedOutput !== undefined) {
149
+ state.contextLost = true;
150
+ if (verbose) hlog('[CONTEXT-RECOVERY] Resume context loss: buffer never flushed (no thinking/tools)');
151
+ } else if (
152
+ (!result.toolUseHistory || result.toolUseHistory.length === 0) &&
153
+ !result.thinkingOutput &&
154
+ result.assistantResponse.length < 500
155
+ ) {
156
+ state.contextLost = true;
157
+ if (verbose) hlog('[CONTEXT-RECOVERY] Resume context loss: no tools, no thinking, short response');
158
+ }
159
+ }
160
+
161
+ /** Detect native timeout context loss (Path 2): tool timeouts caused confusion */
162
+ export async function detectNativeTimeoutContextLoss(
163
+ result: HeadlessRunResult,
164
+ state: RetryLoopState,
165
+ maxRetries: number,
166
+ nativeTimeouts: number,
167
+ verbose: boolean,
168
+ ): Promise<void> {
169
+ if (state.contextLost) return;
170
+
171
+ const succeededIds = new Set<string>();
172
+ const allIds = new Set<string>();
173
+ for (const t of result.toolUseHistory ?? []) {
174
+ allIds.add(t.toolId);
175
+ if (t.result !== undefined) succeededIds.add(t.toolId);
176
+ }
177
+ const toolsWithoutResult = [...allIds].filter(id => !succeededIds.has(id)).length;
178
+ const effectiveTimeouts = Math.max(nativeTimeouts, toolsWithoutResult);
179
+
180
+ if (effectiveTimeouts === 0 || !result.assistantResponse || state.checkpointRef.value || state.retryNumber >= maxRetries) {
181
+ return;
182
+ }
183
+
184
+ const writeToolNames = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
185
+ const contextLossCtx: ContextLossContext = {
186
+ assistantResponse: result.assistantResponse,
187
+ effectiveTimeouts,
188
+ nativeTimeoutCount: nativeTimeouts,
189
+ successfulToolCalls: result.toolUseHistory?.filter(t => t.result !== undefined && !t.isError).length ?? 0,
190
+ thinkingOutputLength: result.thinkingOutput?.length ?? 0,
191
+ hasSuccessfulWrite: result.toolUseHistory?.some(
192
+ t => writeToolNames.has(t.toolName) && t.result !== undefined && !t.isError
193
+ ) ?? false,
194
+ };
195
+
196
+ const claudeCmd = process.env.CLAUDE_COMMAND || 'claude';
197
+ const verdict = await assessContextLoss(contextLossCtx, claudeCmd, verbose);
198
+ state.contextLost = verdict.contextLost;
199
+ if (verbose) {
200
+ hlog(`[CONTEXT-RECOVERY] Haiku verdict: ${state.contextLost ? 'LOST' : 'OK'} — ${verdict.reason}`);
201
+ }
202
+ }
203
+
204
+ // ========== Tool Result Accumulation ==========
205
+
206
+ const MAX_ACCUMULATED_RESULTS = 50;
207
+
208
+ /** Accumulate completed tool results from a run into the retry state */
209
+ export function accumulateToolResults(result: HeadlessRunResult, state: RetryLoopState): void {
210
+ if (!result.toolUseHistory) return;
211
+ for (const t of result.toolUseHistory) {
212
+ if (t.result !== undefined) {
213
+ state.accumulatedToolResults.push({
214
+ toolName: t.toolName,
215
+ toolId: t.toolId,
216
+ toolInput: t.toolInput,
217
+ result: t.result,
218
+ isError: t.isError,
219
+ duration: t.duration,
220
+ });
221
+ }
222
+ }
223
+ if (state.accumulatedToolResults.length > MAX_ACCUMULATED_RESULTS) {
224
+ state.accumulatedToolResults = state.accumulatedToolResults.slice(-MAX_ACCUMULATED_RESULTS);
225
+ }
226
+ }
227
+
228
+ // ========== Recovery Strategies ==========
229
+
230
+ /** Handle inter-movement context loss recovery (resume session expired) */
231
+ export function applyInterMovementRecovery(
232
+ state: RetryLoopState,
233
+ promptWithAttachments: string,
234
+ history: MovementRecord[],
235
+ callbacks: RetryCallbacks,
236
+ ): void {
237
+ const historicalResults = extractHistoricalToolResults(history);
238
+ const allResults = [...historicalResults, ...state.accumulatedToolResults];
239
+
240
+ callbacks.emit('onAutoRetry', {
241
+ retryNumber: state.retryNumber,
242
+ maxRetries: 3,
243
+ toolName: 'InterMovementRecovery',
244
+ completedCount: allResults.length,
245
+ });
246
+ callbacks.queueOutput(
247
+ `\n[[MSTRO_CONTEXT_RECOVERY]] Session context expired — continuing with ${allResults.length} preserved results from prior work (retry ${state.retryNumber}/3).\n`
248
+ );
249
+ callbacks.flushOutputQueue();
250
+
251
+ state.freshRecoveryMode = true;
252
+ state.currentPrompt = buildInterMovementRecoveryPrompt(promptWithAttachments, allResults, history);
253
+ }
254
+
255
+ /** Handle native-timeout context loss recovery (tool timeouts caused confusion) */
256
+ export function applyNativeTimeoutRecovery(
257
+ result: HeadlessRunResult,
258
+ state: RetryLoopState,
259
+ promptWithAttachments: string,
260
+ session: RetrySessionState,
261
+ callbacks: RetryCallbacks,
262
+ ): void {
263
+ const completedCount = state.accumulatedToolResults.length;
264
+
265
+ callbacks.emit('onAutoRetry', {
266
+ retryNumber: state.retryNumber,
267
+ maxRetries: 3,
268
+ toolName: 'ContextRecovery',
269
+ completedCount,
270
+ });
271
+
272
+ if (result.claudeSessionId && state.retryNumber === 1) {
273
+ callbacks.queueOutput(
274
+ `\n[[MSTRO_CONTEXT_RECOVERY]] Context loss detected — resuming session with ${completedCount} preserved results (retry ${state.retryNumber}/3).\n`
275
+ );
276
+ callbacks.flushOutputQueue();
277
+ state.contextRecoverySessionId = result.claudeSessionId;
278
+ session.claudeSessionId = result.claudeSessionId;
279
+ state.currentPrompt = buildContextRecoveryPrompt(promptWithAttachments);
280
+ } else {
281
+ callbacks.queueOutput(
282
+ `\n[[MSTRO_CONTEXT_RECOVERY]] Continuing with fresh context — ${completedCount} preserved results injected (retry ${state.retryNumber}/3).\n`
283
+ );
284
+ callbacks.flushOutputQueue();
285
+ state.freshRecoveryMode = true;
286
+ state.currentPrompt = buildFreshRecoveryPrompt(promptWithAttachments, state.accumulatedToolResults, state.timedOutTools);
287
+ }
288
+ }
289
+
290
+ /** Check if context loss recovery should trigger. Returns true if loop should continue. */
291
+ export function shouldRetryContextLoss(
292
+ result: HeadlessRunResult,
293
+ state: RetryLoopState,
294
+ session: RetrySessionState,
295
+ useResume: boolean,
296
+ nativeTimeouts: number,
297
+ maxRetries: number,
298
+ promptWithAttachments: string,
299
+ callbacks: RetryCallbacks,
300
+ ): boolean {
301
+ if (state.checkpointRef.value || state.retryNumber >= maxRetries || !state.contextLost) {
302
+ return false;
303
+ }
304
+ accumulateToolResults(result, state);
305
+ state.retryNumber++;
306
+ const path = (useResume && nativeTimeouts === 0) ? 'InterMovementRecovery' : 'NativeTimeoutRecovery';
307
+ state.retryLog.push({
308
+ retryNumber: state.retryNumber,
309
+ path,
310
+ reason: `Context lost (${nativeTimeouts} timeouts, ${state.accumulatedToolResults.length} tools preserved)`,
311
+ timestamp: Date.now(),
312
+ });
313
+ if (useResume && nativeTimeouts === 0) {
314
+ applyInterMovementRecovery(state, promptWithAttachments, session.history.movements, callbacks);
315
+ } else {
316
+ applyNativeTimeoutRecovery(result, state, promptWithAttachments, session, callbacks);
317
+ }
318
+ return true;
319
+ }
320
+
321
+ /** Handle tool timeout checkpoint. Returns true if loop should continue. */
322
+ export function applyToolTimeoutRetry(
323
+ state: RetryLoopState,
324
+ maxRetries: number,
325
+ promptWithAttachments: string,
326
+ callbacks: RetryCallbacks,
327
+ model: string | undefined,
328
+ ): boolean {
329
+ if (!state.checkpointRef.value || state.retryNumber >= maxRetries) {
330
+ return false;
331
+ }
332
+
333
+ const cp: ExecutionCheckpoint = state.checkpointRef.value;
334
+ state.retryNumber++;
335
+
336
+ state.timedOutTools.push({
337
+ toolName: cp.hungTool.toolName,
338
+ input: cp.hungTool.input ?? {},
339
+ timeoutMs: cp.hungTool.timeoutMs,
340
+ });
341
+
342
+ const canResumeSession = cp.inProgressTools.length === 0 && !!cp.claudeSessionId;
343
+ state.retryLog.push({
344
+ retryNumber: state.retryNumber,
345
+ path: 'ToolTimeout',
346
+ reason: `${cp.hungTool.toolName} timed out after ${cp.hungTool.timeoutMs}ms, ${cp.completedTools.length} tools completed, ${canResumeSession ? 'resuming' : 'fresh start'}`,
347
+ timestamp: Date.now(),
348
+ });
349
+ callbacks.emit('onAutoRetry', {
350
+ retryNumber: state.retryNumber,
351
+ maxRetries,
352
+ toolName: cp.hungTool.toolName,
353
+ url: cp.hungTool.url,
354
+ completedCount: cp.completedTools.length,
355
+ });
356
+
357
+ trackEvent(AnalyticsEvents.IMPROVISE_AUTO_RETRY, {
358
+ retry_number: state.retryNumber,
359
+ hung_tool: cp.hungTool.toolName,
360
+ hung_url: cp.hungTool.url?.slice(0, 200),
361
+ completed_tools: cp.completedTools.length,
362
+ elapsed_ms: cp.elapsedMs,
363
+ resume_attempted: canResumeSession,
364
+ model: model || 'default',
365
+ });
366
+
367
+ state.currentPrompt = canResumeSession
368
+ ? buildResumeRetryPrompt(cp, state.timedOutTools)
369
+ : buildRetryPrompt(cp, promptWithAttachments, state.timedOutTools);
370
+
371
+ callbacks.queueOutput(
372
+ `\n[[MSTRO_AUTO_RETRY]] Auto-retry ${state.retryNumber}/${maxRetries}: ${canResumeSession ? 'Resuming session' : 'Continuing'} with ${cp.completedTools.length} successful results, skipping failed ${cp.hungTool.toolName}.\n`
373
+ );
374
+ callbacks.flushOutputQueue();
375
+
376
+ return true;
377
+ }
378
+
379
+ /** Detect and retry after a signal crash. Returns true if loop should continue. */
380
+ export function shouldRetrySignalCrash(
381
+ result: HeadlessRunResult,
382
+ state: RetryLoopState,
383
+ session: RetrySessionState,
384
+ maxRetries: number,
385
+ promptWithAttachments: string,
386
+ callbacks: RetryCallbacks,
387
+ ): boolean {
388
+ const isSignalCrash = !!result.signalName;
389
+ const exitCodeSignal = !result.completed && !result.signalName && result.error?.match(/exited with code (1[2-9]\d|[2-9]\d{2})/);
390
+ if ((!isSignalCrash && !exitCodeSignal) || state.retryNumber >= maxRetries) {
391
+ return false;
392
+ }
393
+ if (state.checkpointRef.value) {
394
+ return false;
395
+ }
396
+
397
+ accumulateToolResults(result, state);
398
+ state.retryNumber++;
399
+
400
+ const completedCount = state.accumulatedToolResults.length;
401
+ const signalInfo = result.signalName || 'unknown signal';
402
+ const useResume = !!result.claudeSessionId && state.retryNumber === 1;
403
+
404
+ state.retryLog.push({
405
+ retryNumber: state.retryNumber,
406
+ path: 'SignalCrash',
407
+ reason: `Process killed (${signalInfo}), ${completedCount} tools preserved, ${useResume ? 'resuming' : 'fresh start'}`,
408
+ timestamp: Date.now(),
409
+ });
410
+
411
+ callbacks.emit('onAutoRetry', {
412
+ retryNumber: state.retryNumber,
413
+ maxRetries,
414
+ toolName: `SignalCrash(${signalInfo})`,
415
+ completedCount,
416
+ });
417
+
418
+ trackEvent(AnalyticsEvents.IMPROVISE_AUTO_RETRY, {
419
+ retry_number: state.retryNumber,
420
+ hung_tool: `signal_crash:${signalInfo}`,
421
+ completed_tools: completedCount,
422
+ resume_attempted: useResume,
423
+ });
424
+
425
+ if (useResume) {
426
+ callbacks.queueOutput(
427
+ `\n[[MSTRO_SIGNAL_RECOVERY]] Process killed (${signalInfo}) — resuming session with ${completedCount} preserved results (retry ${state.retryNumber}/${maxRetries}).\n`
428
+ );
429
+ callbacks.flushOutputQueue();
430
+ state.contextRecoverySessionId = result.claudeSessionId;
431
+ session.claudeSessionId = result.claudeSessionId;
432
+ state.currentPrompt = buildSignalCrashRecoveryPrompt(promptWithAttachments, true);
433
+ } else {
434
+ callbacks.queueOutput(
435
+ `\n[[MSTRO_SIGNAL_RECOVERY]] Process killed (${signalInfo}) — restarting with ${completedCount} preserved results (retry ${state.retryNumber}/${maxRetries}).\n`
436
+ );
437
+ callbacks.flushOutputQueue();
438
+ state.freshRecoveryMode = true;
439
+ const allResults = [...extractHistoricalToolResults(session.history.movements), ...state.accumulatedToolResults];
440
+ state.currentPrompt = buildSignalCrashRecoveryPrompt(promptWithAttachments, false, allResults);
441
+ }
442
+
443
+ return true;
444
+ }
445
+
446
+ // ========== Premature Completion ==========
447
+
448
+ /** Guard checks for premature completion */
449
+ function isPrematureCompletionCandidate(
450
+ result: HeadlessRunResult,
451
+ state: RetryLoopState,
452
+ maxRetries: number,
453
+ ): boolean {
454
+ if (!result.completed || result.signalName || state.retryNumber >= maxRetries) return false;
455
+ if (state.checkpointRef.value || state.contextLost) return false;
456
+ if (!result.claudeSessionId || !result.stopReason) return false;
457
+ return result.stopReason === 'max_tokens' || result.stopReason === 'end_turn';
458
+ }
459
+
460
+ /** Use Haiku to assess whether an end_turn response is genuinely complete */
461
+ async function assessEndTurnCompletion(result: HeadlessRunResult, verbose: boolean): Promise<boolean> {
462
+ if (!result.assistantResponse) return false;
463
+
464
+ const claudeCmd = process.env.CLAUDE_COMMAND || 'claude';
465
+ const verdict = await assessPrematureCompletion({
466
+ responseTail: result.assistantResponse.slice(-800),
467
+ successfulToolCalls: result.toolUseHistory?.filter(t => t.result !== undefined && !t.isError).length ?? 0,
468
+ hasThinking: !!result.thinkingOutput,
469
+ responseLength: result.assistantResponse.length,
470
+ }, claudeCmd, verbose);
471
+
472
+ if (verbose) {
473
+ hlog(`[PREMATURE-COMPLETION] Haiku verdict: ${verdict.isIncomplete ? 'INCOMPLETE' : 'COMPLETE'} — ${verdict.reason}`);
474
+ }
475
+ return verdict.isIncomplete;
476
+ }
477
+
478
+ /** Apply premature completion retry */
479
+ function applyPrematureCompletionRetry(
480
+ result: HeadlessRunResult,
481
+ state: RetryLoopState,
482
+ session: RetrySessionState,
483
+ maxRetries: number,
484
+ stopReason: string,
485
+ isMaxTokens: boolean,
486
+ callbacks: RetryCallbacks,
487
+ ): void {
488
+ state.retryNumber++;
489
+ const reason = isMaxTokens ? 'Output limit reached' : 'Task appears unfinished (AI assessment)';
490
+
491
+ state.retryLog.push({
492
+ retryNumber: state.retryNumber,
493
+ path: 'PrematureCompletion',
494
+ reason,
495
+ timestamp: Date.now(),
496
+ });
497
+
498
+ callbacks.emit('onAutoRetry', {
499
+ retryNumber: state.retryNumber,
500
+ maxRetries,
501
+ toolName: `PrematureCompletion(${stopReason})`,
502
+ completedCount: result.toolUseHistory?.length ?? 0,
503
+ });
504
+
505
+ trackEvent(AnalyticsEvents.IMPROVISE_AUTO_RETRY, {
506
+ retry_number: state.retryNumber,
507
+ hung_tool: `premature_completion:${stopReason}`,
508
+ completed_tools: result.toolUseHistory?.length ?? 0,
509
+ resume_attempted: true,
510
+ });
511
+
512
+ callbacks.queueOutput(
513
+ `\n${reason} — resuming session (retry ${state.retryNumber}/${maxRetries}).\n`
514
+ );
515
+ callbacks.flushOutputQueue();
516
+
517
+ state.contextRecoverySessionId = result.claudeSessionId;
518
+ session.claudeSessionId = result.claudeSessionId;
519
+ state.currentPrompt = 'continue';
520
+ }
521
+
522
+ /** Detect and retry premature completion. Returns true if loop should continue. */
523
+ export async function shouldRetryPrematureCompletion(
524
+ result: HeadlessRunResult,
525
+ state: RetryLoopState,
526
+ session: RetrySessionState,
527
+ maxRetries: number,
528
+ callbacks: RetryCallbacks,
529
+ ): Promise<boolean> {
530
+ if (!isPrematureCompletionCandidate(result, state, maxRetries)) {
531
+ return false;
532
+ }
533
+
534
+ const stopReason = result.stopReason!;
535
+ const isMaxTokens = stopReason === 'max_tokens';
536
+ const isIncomplete = isMaxTokens || await assessEndTurnCompletion(result, session.options.verbose);
537
+
538
+ if (!isIncomplete) return false;
539
+
540
+ applyPrematureCompletionRetry(result, state, session, maxRetries, stopReason, isMaxTokens, callbacks);
541
+ return true;
542
+ }
543
+
544
+ // ========== Best Result Selection ==========
545
+
546
+ /** Select the best result across retries using Haiku assessment */
547
+ export async function selectBestResult(
548
+ state: RetryLoopState,
549
+ result: HeadlessRunResult,
550
+ userPrompt: string,
551
+ verbose: boolean,
552
+ ): Promise<HeadlessRunResult> {
553
+ if (!state.bestResult || state.bestResult === result || state.retryNumber === 0) {
554
+ return result;
555
+ }
556
+
557
+ const claudeCmd = process.env.CLAUDE_COMMAND || 'claude';
558
+ const bestToolCount = state.bestResult.toolUseHistory?.filter(t => t.result !== undefined && !t.isError).length ?? 0;
559
+ const currentToolCount = result.toolUseHistory?.filter(t => t.result !== undefined && !t.isError).length ?? 0;
560
+
561
+ try {
562
+ const verdict = await assessBestResult({
563
+ originalPrompt: userPrompt,
564
+ resultA: {
565
+ successfulToolCalls: bestToolCount,
566
+ responseLength: state.bestResult.assistantResponse?.length ?? 0,
567
+ hasThinking: !!state.bestResult.thinkingOutput,
568
+ responseTail: (state.bestResult.assistantResponse ?? '').slice(-500),
569
+ },
570
+ resultB: {
571
+ successfulToolCalls: currentToolCount,
572
+ responseLength: result.assistantResponse?.length ?? 0,
573
+ hasThinking: !!result.thinkingOutput,
574
+ responseTail: (result.assistantResponse ?? '').slice(-500),
575
+ },
576
+ }, claudeCmd, verbose);
577
+
578
+ if (verdict.winner === 'A') {
579
+ if (verbose) hlog(`[BEST-RESULT] Haiku picked earlier attempt: ${verdict.reason}`);
580
+ return mergeResultSessionId(state.bestResult, result.claudeSessionId);
581
+ }
582
+ if (verbose) hlog(`[BEST-RESULT] Haiku picked final attempt: ${verdict.reason}`);
583
+ return result;
584
+ } catch {
585
+ return fallbackBestResult(state.bestResult, result, verbose);
586
+ }
587
+ }
588
+
589
+ function mergeResultSessionId(result: HeadlessRunResult, sessionId: string | undefined): HeadlessRunResult {
590
+ if (sessionId) return { ...result, claudeSessionId: sessionId };
591
+ return result;
592
+ }
593
+
594
+ function fallbackBestResult(bestResult: HeadlessRunResult, result: HeadlessRunResult, verbose: boolean): HeadlessRunResult {
595
+ if (scoreRunResult(bestResult) > scoreRunResult(result)) {
596
+ if (verbose) {
597
+ hlog(`[BEST-RESULT] Haiku unavailable, numeric fallback: earlier attempt (score ${scoreRunResult(bestResult)} vs ${scoreRunResult(result)})`);
598
+ }
599
+ return mergeResultSessionId(bestResult, result.claudeSessionId);
600
+ }
601
+ return result;
602
+ }