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
@@ -1,15 +1,16 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
- import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
5
- import { join } from 'node:path';
6
- import { type FileAttachment, ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
4
+ import type { FileAttachment, ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
7
5
  import { getModel } from '../settings.js';
8
6
  import type { HandlerContext } from './handler-context.js';
9
7
  import { runQualityScan } from './quality-service.js';
10
- import type { SessionRegistry } from './session-registry.js';
11
8
  import type { WebSocketMessage, WSContext } from './types.js';
12
9
 
10
+ // Re-export from extracted modules for backward compatibility
11
+ export { handleHistoryMessage } from './session-history.js';
12
+ export { initializeTab, resumeHistoricalSession } from './session-initialization.js';
13
+
13
14
  const WRITE_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
14
15
 
15
16
  function movementHadWrites(movement: Record<string, unknown>): boolean {
@@ -225,436 +226,3 @@ export function handleSessionMessage(ctx: HandlerContext, ws: WSContext, msg: We
225
226
  }
226
227
  }
227
228
  }
228
-
229
- export function handleHistoryMessage(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
230
- switch (msg.type) {
231
- case 'getSessions': {
232
- const result = getSessionsList(workingDir, msg.data?.limit ?? 20, msg.data?.offset ?? 0);
233
- ctx.send(ws, { type: 'sessions', tabId, data: result });
234
- break;
235
- }
236
- case 'getSessionsCount':
237
- ctx.send(ws, { type: 'sessionsCount', tabId, data: { total: getSessionsCount(workingDir) } });
238
- break;
239
- case 'getSessionById':
240
- if (!msg.data?.sessionId) throw new Error('Session ID is required');
241
- ctx.send(ws, { type: 'sessionData', tabId, data: getSessionById(workingDir, msg.data.sessionId) });
242
- break;
243
- case 'deleteSession':
244
- if (!msg.data?.sessionId) throw new Error('Session ID is required');
245
- ctx.send(ws, { type: 'sessionDeleted', tabId, data: deleteSession(workingDir, msg.data.sessionId) });
246
- break;
247
- case 'clearHistory':
248
- ctx.send(ws, { type: 'historyCleared', tabId, data: clearAllSessions(workingDir) });
249
- break;
250
- case 'searchHistory': {
251
- if (!msg.data?.query) throw new Error('Search query is required');
252
- const result = searchSessions(workingDir, msg.data.query, msg.data?.limit ?? 20, msg.data?.offset ?? 0);
253
- ctx.send(ws, { type: 'searchResults', tabId, data: { ...result, query: msg.data.query } });
254
- break;
255
- }
256
- }
257
- }
258
-
259
- function tryResumeFromDisk(
260
- ctx: HandlerContext,
261
- ws: WSContext,
262
- tabId: string,
263
- workingDir: string,
264
- registrySessionId: string,
265
- tabMap: Map<string, string> | undefined,
266
- registry: SessionRegistry
267
- ): boolean {
268
- try {
269
- const diskSession = ImprovisationSessionManager.resumeFromHistory(workingDir, registrySessionId);
270
- setupSessionListeners(ctx, diskSession, ws, tabId);
271
- const diskSessionId = diskSession.getSessionInfo().sessionId;
272
- ctx.sessions.set(diskSessionId, diskSession);
273
- if (tabMap) tabMap.set(tabId, diskSessionId);
274
- registry.touchTab(tabId);
275
-
276
- // Restore worktree state from registry
277
- const regTab = registry.getTab(tabId);
278
- if (regTab?.worktreePath && !ctx.gitDirectories.has(tabId)) {
279
- ctx.gitDirectories.set(tabId, regTab.worktreePath);
280
- if (regTab.worktreeBranch) ctx.gitBranches.set(tabId, regTab.worktreeBranch);
281
- }
282
- const worktreePath = ctx.gitDirectories.get(tabId);
283
- const worktreeBranch = ctx.gitBranches.get(tabId);
284
-
285
- ctx.send(ws, {
286
- type: 'tabInitialized',
287
- tabId,
288
- data: {
289
- ...diskSession.getSessionInfo(),
290
- outputHistory: buildOutputHistory(diskSession),
291
- ...(worktreePath ? { worktreePath, worktreeBranch } : {}),
292
- }
293
- });
294
- return true;
295
- } catch {
296
- return false;
297
- }
298
- }
299
-
300
- export async function initializeTab(ctx: HandlerContext, ws: WSContext, tabId: string, workingDir: string, tabName?: string): Promise<void> {
301
- const tabMap = ctx.connections.get(ws);
302
- const registry = ctx.getRegistry(workingDir);
303
-
304
- // 1. Check per-connection map (same WS reconnect)
305
- const existingSessionId = tabMap?.get(tabId);
306
- if (existingSessionId) {
307
- const existingSession = ctx.sessions.get(existingSessionId);
308
- if (existingSession) {
309
- reattachSession(ctx, existingSession, ws, tabId, registry);
310
- return;
311
- }
312
- }
313
-
314
- // 2. Check session registry (cross-connection reattach)
315
- const registrySessionId = registry.getTabSession(tabId);
316
- if (registrySessionId) {
317
- const inMemorySession = ctx.sessions.get(registrySessionId);
318
- if (inMemorySession) {
319
- reattachSession(ctx, inMemorySession, ws, tabId, registry);
320
- return;
321
- }
322
-
323
- if (tryResumeFromDisk(ctx, ws, tabId, workingDir, registrySessionId, tabMap, registry)) {
324
- return;
325
- }
326
- }
327
-
328
- // 3. Create new session
329
- const session = new ImprovisationSessionManager({ workingDir, model: getModel() });
330
- setupSessionListeners(ctx, session, ws, tabId);
331
-
332
- const sessionId = session.getSessionInfo().sessionId;
333
- ctx.sessions.set(sessionId, session);
334
-
335
- if (tabMap) {
336
- tabMap.set(tabId, sessionId);
337
- }
338
-
339
- registry.registerTab(tabId, sessionId, tabName);
340
- const registeredTab = registry.getTab(tabId);
341
- ctx.broadcastToAll({
342
- type: 'tabCreated',
343
- data: { tabId, tabName: registeredTab?.tabName || 'Chat', createdAt: registeredTab?.createdAt, order: registeredTab?.order, sessionInfo: session.getSessionInfo() }
344
- });
345
-
346
- ctx.send(ws, {
347
- type: 'tabInitialized',
348
- tabId,
349
- data: session.getSessionInfo()
350
- });
351
- }
352
-
353
- export async function resumeHistoricalSession(
354
- ctx: HandlerContext,
355
- ws: WSContext,
356
- tabId: string,
357
- workingDir: string,
358
- historicalSessionId: string
359
- ): Promise<void> {
360
- const tabMap = ctx.connections.get(ws);
361
- const registry = ctx.getRegistry(workingDir);
362
-
363
- const existingSessionId = tabMap?.get(tabId);
364
- if (existingSessionId) {
365
- const existingSession = ctx.sessions.get(existingSessionId);
366
- if (existingSession) {
367
- reattachSession(ctx, existingSession, ws, tabId, registry);
368
- return;
369
- }
370
- }
371
-
372
- const registrySessionId = registry.getTabSession(tabId);
373
- if (registrySessionId) {
374
- const inMemorySession = ctx.sessions.get(registrySessionId);
375
- if (inMemorySession) {
376
- reattachSession(ctx, inMemorySession, ws, tabId, registry);
377
- return;
378
- }
379
- }
380
-
381
- let session: ImprovisationSessionManager;
382
- let isNewSession = false;
383
-
384
- try {
385
- session = ImprovisationSessionManager.resumeFromHistory(workingDir, historicalSessionId, { model: getModel() });
386
- } catch (error: unknown) {
387
- console.warn(`[WebSocketImproviseHandler] Could not resume session ${historicalSessionId}: ${error instanceof Error ? error.message : String(error)}. Creating new session.`);
388
- session = new ImprovisationSessionManager({ workingDir, model: getModel() });
389
- isNewSession = true;
390
- }
391
-
392
- setupSessionListeners(ctx, session, ws, tabId);
393
-
394
- const sessionId = session.getSessionInfo().sessionId;
395
- ctx.sessions.set(sessionId, session);
396
-
397
- if (tabMap) {
398
- tabMap.set(tabId, sessionId);
399
- }
400
-
401
- registry.registerTab(tabId, sessionId);
402
-
403
- ctx.send(ws, {
404
- type: 'tabInitialized',
405
- tabId,
406
- data: {
407
- ...session.getSessionInfo(),
408
- outputHistory: buildOutputHistory(session),
409
- resumeFailed: isNewSession,
410
- originalSessionId: isNewSession ? historicalSessionId : undefined
411
- }
412
- });
413
- }
414
-
415
- function reattachSession(
416
- ctx: HandlerContext,
417
- session: ImprovisationSessionManager,
418
- ws: WSContext,
419
- tabId: string,
420
- registry: SessionRegistry
421
- ): void {
422
- setupSessionListeners(ctx, session, ws, tabId);
423
-
424
- const tabMap = ctx.connections.get(ws);
425
- const sessionId = session.getSessionInfo().sessionId;
426
- if (tabMap) tabMap.set(tabId, sessionId);
427
- registry.touchTab(tabId);
428
-
429
- // Restore worktree state from registry if not already in memory
430
- const regTab = registry.getTab(tabId);
431
- if (regTab?.worktreePath && !ctx.gitDirectories.has(tabId)) {
432
- ctx.gitDirectories.set(tabId, regTab.worktreePath);
433
- if (regTab.worktreeBranch) ctx.gitBranches.set(tabId, regTab.worktreeBranch);
434
- }
435
-
436
- const outputHistory = buildOutputHistory(session);
437
-
438
- const executionEvents = session.isExecuting
439
- ? session.getExecutionEventLog()
440
- : undefined;
441
-
442
- const worktreePath = ctx.gitDirectories.get(tabId);
443
- const worktreeBranch = ctx.gitBranches.get(tabId);
444
-
445
- ctx.send(ws, {
446
- type: 'tabInitialized',
447
- tabId,
448
- data: {
449
- ...session.getSessionInfo(),
450
- outputHistory,
451
- isExecuting: session.isExecuting,
452
- executionEvents,
453
- ...(session.isExecuting && session.executionStartTimestamp ? { executionStartTimestamp: session.executionStartTimestamp } : {}),
454
- ...(worktreePath ? { worktreePath, worktreeBranch } : {}),
455
- }
456
- });
457
- }
458
-
459
- // ============================================
460
- // History persistence functions
461
- // ============================================
462
-
463
- function getSessionsCount(workingDir: string): number {
464
- const sessionsDir = join(workingDir, '.mstro', 'history');
465
- if (!existsSync(sessionsDir)) return 0;
466
- return readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json')).length;
467
- }
468
-
469
- function getSessionsList(workingDir: string, limit: number = 20, offset: number = 0): { sessions: Array<Record<string, unknown> | null>; total: number; hasMore: boolean } {
470
- const sessionsDir = join(workingDir, '.mstro', 'history');
471
-
472
- if (!existsSync(sessionsDir)) {
473
- return { sessions: [], total: 0, hasMore: false };
474
- }
475
-
476
- const historyFiles = readdirSync(sessionsDir)
477
- .filter((name: string) => name.endsWith('.json'))
478
- .sort((a: string, b: string) => {
479
- const timestampA = parseInt(a.replace('.json', ''), 10);
480
- const timestampB = parseInt(b.replace('.json', ''), 10);
481
- return timestampB - timestampA;
482
- });
483
-
484
- const total = historyFiles.length;
485
- const pageFiles = historyFiles.slice(offset, offset + limit);
486
-
487
- const sessions = pageFiles.map((filename: string) => {
488
- const historyPath = join(sessionsDir, filename);
489
- try {
490
- const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'));
491
- const firstPrompt = historyData.movements?.[0]?.userPrompt || '';
492
-
493
- const movementPreviews = (historyData.movements || []).slice(0, 3).map((m: Record<string, unknown>) => ({
494
- userPrompt: (typeof m.userPrompt === 'string' ? m.userPrompt : '').slice(0, 100) || ''
495
- }));
496
-
497
- return {
498
- sessionId: historyData.sessionId,
499
- startedAt: historyData.startedAt,
500
- lastActivityAt: historyData.lastActivityAt,
501
- totalTokens: historyData.totalTokens,
502
- movementCount: historyData.movements?.length || 0,
503
- title: firstPrompt.slice(0, 80) + (firstPrompt.length > 80 ? '...' : ''),
504
- movements: movementPreviews
505
- };
506
- } catch {
507
- return null;
508
- }
509
- }).filter(Boolean);
510
-
511
- return { sessions, total, hasMore: offset + limit < total };
512
- }
513
-
514
- function getSessionById(workingDir: string, sessionId: string): Record<string, unknown> | null {
515
- const sessionsDir = join(workingDir, '.mstro', 'history');
516
- if (!existsSync(sessionsDir)) return null;
517
-
518
- const historyFiles = readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json'));
519
-
520
- for (const filename of historyFiles) {
521
- const historyPath = join(sessionsDir, filename);
522
- try {
523
- const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'));
524
- if (historyData.sessionId === sessionId) {
525
- const firstPrompt = historyData.movements?.[0]?.userPrompt || '';
526
- return {
527
- sessionId: historyData.sessionId,
528
- startedAt: historyData.startedAt,
529
- lastActivityAt: historyData.lastActivityAt,
530
- totalTokens: historyData.totalTokens,
531
- movementCount: historyData.movements?.length || 0,
532
- title: firstPrompt.slice(0, 80) + (firstPrompt.length > 80 ? '...' : ''),
533
- movements: historyData.movements || [],
534
- };
535
- }
536
- } catch {
537
- // Skip files that can't be parsed
538
- }
539
- }
540
-
541
- return null;
542
- }
543
-
544
- function deleteSession(workingDir: string, sessionId: string): { sessionId: string; success: boolean } {
545
- const sessionsDir = join(workingDir, '.mstro', 'history');
546
- if (!existsSync(sessionsDir)) return { sessionId, success: false };
547
-
548
- try {
549
- const historyFiles = readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json'));
550
-
551
- for (const filename of historyFiles) {
552
- const historyPath = join(sessionsDir, filename);
553
- try {
554
- const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'));
555
- if (historyData.sessionId === sessionId) {
556
- unlinkSync(historyPath);
557
- return { sessionId, success: true };
558
- }
559
- } catch {
560
- // Skip files that can't be parsed
561
- }
562
- }
563
-
564
- return { sessionId, success: false };
565
- } catch (error) {
566
- console.error('[WebSocketImproviseHandler] Error deleting session:', error);
567
- return { sessionId, success: false };
568
- }
569
- }
570
-
571
- function clearAllSessions(workingDir: string): { success: boolean; deletedCount: number } {
572
- const sessionsDir = join(workingDir, '.mstro', 'history');
573
- if (!existsSync(sessionsDir)) return { success: true, deletedCount: 0 };
574
-
575
- try {
576
- const historyFiles = readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json'));
577
-
578
- let deletedCount = 0;
579
- for (const filename of historyFiles) {
580
- const historyPath = join(sessionsDir, filename);
581
- try {
582
- unlinkSync(historyPath);
583
- deletedCount++;
584
- } catch {
585
- // Skip files that can't be deleted
586
- }
587
- }
588
-
589
- return { success: true, deletedCount };
590
- } catch (error) {
591
- console.error('[WebSocketImproviseHandler] Error clearing sessions:', error);
592
- return { success: false, deletedCount: 0 };
593
- }
594
- }
595
-
596
- function movementMatchesQuery(movements: Array<Record<string, unknown>> | undefined, lowerQuery: string): boolean {
597
- if (!movements) return false;
598
- return movements.some((m: Record<string, unknown>) =>
599
- (typeof m.userPrompt === 'string' && m.userPrompt.toLowerCase().includes(lowerQuery)) ||
600
- (typeof m.summary === 'string' && m.summary.toLowerCase().includes(lowerQuery)) ||
601
- (typeof m.assistantResponse === 'string' && m.assistantResponse.toLowerCase().includes(lowerQuery))
602
- );
603
- }
604
-
605
- function buildSessionSummary(historyData: Record<string, unknown>): Record<string, unknown> {
606
- const movements = historyData.movements as Array<Record<string, unknown>> | undefined;
607
- const firstPrompt = (typeof movements?.[0]?.userPrompt === 'string' ? movements[0].userPrompt : '') || '';
608
- const movementPreviews = (movements || []).slice(0, 3).map((m: Record<string, unknown>) => ({
609
- userPrompt: (typeof m.userPrompt === 'string' ? m.userPrompt : '').slice(0, 100) || ''
610
- }));
611
- return {
612
- sessionId: historyData.sessionId,
613
- startedAt: historyData.startedAt,
614
- lastActivityAt: historyData.lastActivityAt,
615
- totalTokens: historyData.totalTokens,
616
- movementCount: movements?.length || 0,
617
- title: firstPrompt.slice(0, 80) + (firstPrompt.length > 80 ? '...' : ''),
618
- movements: movementPreviews
619
- };
620
- }
621
-
622
- function searchSessions(workingDir: string, query: string, limit: number = 20, offset: number = 0): { sessions: Array<Record<string, unknown>>; total: number; hasMore: boolean } {
623
- const sessionsDir = join(workingDir, '.mstro', 'history');
624
- if (!existsSync(sessionsDir)) return { sessions: [], total: 0, hasMore: false };
625
-
626
- const lowerQuery = query.toLowerCase();
627
-
628
- try {
629
- const historyFiles = readdirSync(sessionsDir)
630
- .filter((name: string) => name.endsWith('.json'))
631
- .sort((a: string, b: string) => {
632
- const timestampA = parseInt(a.replace('.json', ''), 10);
633
- const timestampB = parseInt(b.replace('.json', ''), 10);
634
- return timestampB - timestampA;
635
- });
636
-
637
- const allMatches: Array<Record<string, unknown>> = [];
638
- for (const filename of historyFiles) {
639
- try {
640
- const content = readFileSync(join(sessionsDir, filename), 'utf-8');
641
- const historyData = JSON.parse(content);
642
- if (movementMatchesQuery(historyData.movements, lowerQuery)) {
643
- allMatches.push(buildSessionSummary(historyData));
644
- }
645
- } catch {
646
- // Skip files that can't be parsed
647
- }
648
- }
649
-
650
- const total = allMatches.length;
651
- return {
652
- sessions: allMatches.slice(offset, offset + limit),
653
- total,
654
- hasMore: offset + limit < total
655
- };
656
- } catch (error) {
657
- console.error('[WebSocketImproviseHandler] Error searching sessions:', error);
658
- return { sessions: [], total: 0, hasMore: false };
659
- }
660
- }
@@ -0,0 +1,222 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import type { HandlerContext } from './handler-context.js';
7
+ import type { WebSocketMessage, WSContext } from './types.js';
8
+
9
+ export function handleHistoryMessage(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
10
+ switch (msg.type) {
11
+ case 'getSessions': {
12
+ const result = getSessionsList(workingDir, msg.data?.limit ?? 20, msg.data?.offset ?? 0);
13
+ ctx.send(ws, { type: 'sessions', tabId, data: result });
14
+ break;
15
+ }
16
+ case 'getSessionsCount':
17
+ ctx.send(ws, { type: 'sessionsCount', tabId, data: { total: getSessionsCount(workingDir) } });
18
+ break;
19
+ case 'getSessionById':
20
+ if (!msg.data?.sessionId) throw new Error('Session ID is required');
21
+ ctx.send(ws, { type: 'sessionData', tabId, data: getSessionById(workingDir, msg.data.sessionId) });
22
+ break;
23
+ case 'deleteSession':
24
+ if (!msg.data?.sessionId) throw new Error('Session ID is required');
25
+ ctx.send(ws, { type: 'sessionDeleted', tabId, data: deleteSession(workingDir, msg.data.sessionId) });
26
+ break;
27
+ case 'clearHistory':
28
+ ctx.send(ws, { type: 'historyCleared', tabId, data: clearAllSessions(workingDir) });
29
+ break;
30
+ case 'searchHistory': {
31
+ if (!msg.data?.query) throw new Error('Search query is required');
32
+ const result = searchSessions(workingDir, msg.data.query, msg.data?.limit ?? 20, msg.data?.offset ?? 0);
33
+ ctx.send(ws, { type: 'searchResults', tabId, data: { ...result, query: msg.data.query } });
34
+ break;
35
+ }
36
+ }
37
+ }
38
+
39
+ function getSessionsCount(workingDir: string): number {
40
+ const sessionsDir = join(workingDir, '.mstro', 'history');
41
+ if (!existsSync(sessionsDir)) return 0;
42
+ return readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json')).length;
43
+ }
44
+
45
+ function getSessionsList(workingDir: string, limit: number = 20, offset: number = 0): { sessions: Array<Record<string, unknown> | null>; total: number; hasMore: boolean } {
46
+ const sessionsDir = join(workingDir, '.mstro', 'history');
47
+
48
+ if (!existsSync(sessionsDir)) {
49
+ return { sessions: [], total: 0, hasMore: false };
50
+ }
51
+
52
+ const historyFiles = readdirSync(sessionsDir)
53
+ .filter((name: string) => name.endsWith('.json'))
54
+ .sort((a: string, b: string) => {
55
+ const timestampA = parseInt(a.replace('.json', ''), 10);
56
+ const timestampB = parseInt(b.replace('.json', ''), 10);
57
+ return timestampB - timestampA;
58
+ });
59
+
60
+ const total = historyFiles.length;
61
+ const pageFiles = historyFiles.slice(offset, offset + limit);
62
+
63
+ const sessions = pageFiles.map((filename: string) => {
64
+ const historyPath = join(sessionsDir, filename);
65
+ try {
66
+ const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'));
67
+ return buildSessionSummary(historyData);
68
+ } catch {
69
+ return null;
70
+ }
71
+ }).filter(Boolean);
72
+
73
+ return { sessions, total, hasMore: offset + limit < total };
74
+ }
75
+
76
+ function getSessionById(workingDir: string, sessionId: string): Record<string, unknown> | null {
77
+ const sessionsDir = join(workingDir, '.mstro', 'history');
78
+ if (!existsSync(sessionsDir)) return null;
79
+
80
+ const historyFiles = readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json'));
81
+
82
+ for (const filename of historyFiles) {
83
+ const historyPath = join(sessionsDir, filename);
84
+ try {
85
+ const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'));
86
+ if (historyData.sessionId === sessionId) {
87
+ const firstPrompt = historyData.movements?.[0]?.userPrompt || '';
88
+ return {
89
+ sessionId: historyData.sessionId,
90
+ startedAt: historyData.startedAt,
91
+ lastActivityAt: historyData.lastActivityAt,
92
+ totalTokens: historyData.totalTokens,
93
+ movementCount: historyData.movements?.length || 0,
94
+ title: firstPrompt.slice(0, 80) + (firstPrompt.length > 80 ? '...' : ''),
95
+ movements: historyData.movements || [],
96
+ };
97
+ }
98
+ } catch {
99
+ // Skip files that can't be parsed
100
+ }
101
+ }
102
+
103
+ return null;
104
+ }
105
+
106
+ function deleteSession(workingDir: string, sessionId: string): { sessionId: string; success: boolean } {
107
+ const sessionsDir = join(workingDir, '.mstro', 'history');
108
+ if (!existsSync(sessionsDir)) return { sessionId, success: false };
109
+
110
+ try {
111
+ const historyFiles = readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json'));
112
+
113
+ for (const filename of historyFiles) {
114
+ const historyPath = join(sessionsDir, filename);
115
+ try {
116
+ const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'));
117
+ if (historyData.sessionId === sessionId) {
118
+ unlinkSync(historyPath);
119
+ return { sessionId, success: true };
120
+ }
121
+ } catch {
122
+ // Skip files that can't be parsed
123
+ }
124
+ }
125
+
126
+ return { sessionId, success: false };
127
+ } catch (error) {
128
+ console.error('[WebSocketImproviseHandler] Error deleting session:', error);
129
+ return { sessionId, success: false };
130
+ }
131
+ }
132
+
133
+ function clearAllSessions(workingDir: string): { success: boolean; deletedCount: number } {
134
+ const sessionsDir = join(workingDir, '.mstro', 'history');
135
+ if (!existsSync(sessionsDir)) return { success: true, deletedCount: 0 };
136
+
137
+ try {
138
+ const historyFiles = readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json'));
139
+
140
+ let deletedCount = 0;
141
+ for (const filename of historyFiles) {
142
+ const historyPath = join(sessionsDir, filename);
143
+ try {
144
+ unlinkSync(historyPath);
145
+ deletedCount++;
146
+ } catch {
147
+ // Skip files that can't be deleted
148
+ }
149
+ }
150
+
151
+ return { success: true, deletedCount };
152
+ } catch (error) {
153
+ console.error('[WebSocketImproviseHandler] Error clearing sessions:', error);
154
+ return { success: false, deletedCount: 0 };
155
+ }
156
+ }
157
+
158
+ function movementMatchesQuery(movements: Array<Record<string, unknown>> | undefined, lowerQuery: string): boolean {
159
+ if (!movements) return false;
160
+ return movements.some((m: Record<string, unknown>) =>
161
+ (typeof m.userPrompt === 'string' && m.userPrompt.toLowerCase().includes(lowerQuery)) ||
162
+ (typeof m.summary === 'string' && m.summary.toLowerCase().includes(lowerQuery)) ||
163
+ (typeof m.assistantResponse === 'string' && m.assistantResponse.toLowerCase().includes(lowerQuery))
164
+ );
165
+ }
166
+
167
+ function buildSessionSummary(historyData: Record<string, unknown>): Record<string, unknown> {
168
+ const movements = historyData.movements as Array<Record<string, unknown>> | undefined;
169
+ const firstPrompt = (typeof movements?.[0]?.userPrompt === 'string' ? movements[0].userPrompt : '') || '';
170
+ const movementPreviews = (movements || []).slice(0, 3).map((m: Record<string, unknown>) => ({
171
+ userPrompt: (typeof m.userPrompt === 'string' ? m.userPrompt : '').slice(0, 100) || ''
172
+ }));
173
+ return {
174
+ sessionId: historyData.sessionId,
175
+ startedAt: historyData.startedAt,
176
+ lastActivityAt: historyData.lastActivityAt,
177
+ totalTokens: historyData.totalTokens,
178
+ movementCount: movements?.length || 0,
179
+ title: firstPrompt.slice(0, 80) + (firstPrompt.length > 80 ? '...' : ''),
180
+ movements: movementPreviews
181
+ };
182
+ }
183
+
184
+ function searchSessions(workingDir: string, query: string, limit: number = 20, offset: number = 0): { sessions: Array<Record<string, unknown>>; total: number; hasMore: boolean } {
185
+ const sessionsDir = join(workingDir, '.mstro', 'history');
186
+ if (!existsSync(sessionsDir)) return { sessions: [], total: 0, hasMore: false };
187
+
188
+ const lowerQuery = query.toLowerCase();
189
+
190
+ try {
191
+ const historyFiles = readdirSync(sessionsDir)
192
+ .filter((name: string) => name.endsWith('.json'))
193
+ .sort((a: string, b: string) => {
194
+ const timestampA = parseInt(a.replace('.json', ''), 10);
195
+ const timestampB = parseInt(b.replace('.json', ''), 10);
196
+ return timestampB - timestampA;
197
+ });
198
+
199
+ const allMatches: Array<Record<string, unknown>> = [];
200
+ for (const filename of historyFiles) {
201
+ try {
202
+ const content = readFileSync(join(sessionsDir, filename), 'utf-8');
203
+ const historyData = JSON.parse(content);
204
+ if (movementMatchesQuery(historyData.movements, lowerQuery)) {
205
+ allMatches.push(buildSessionSummary(historyData));
206
+ }
207
+ } catch {
208
+ // Skip files that can't be parsed
209
+ }
210
+ }
211
+
212
+ const total = allMatches.length;
213
+ return {
214
+ sessions: allMatches.slice(offset, offset + limit),
215
+ total,
216
+ hasMore: offset + limit < total
217
+ };
218
+ } catch (error) {
219
+ console.error('[WebSocketImproviseHandler] Error searching sessions:', error);
220
+ return { sessions: [], total: 0, hasMore: false };
221
+ }
222
+ }