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
@@ -2,241 +2,56 @@
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
4
  /**
5
- * PTY Manager - Manages pseudo-terminal sessions for shell access
5
+ * PTY Manager - Manages pseudo-terminal sessions for shell access.
6
6
  *
7
- * Provides terminal emulation for running shell commands on the local machine.
8
- * Each terminal session is managed independently with its own PTY process.
9
- *
10
- * Supports session persistence:
11
- * - Sessions survive WebSocket disconnections
12
- * - Scrollback buffer is maintained for replay on reconnect
13
- * - Sessions can be reattached without losing running processes
14
- *
15
- * NOTE: node-pty is an optional dependency requiring native compilation.
16
- * Terminal features gracefully degrade when node-pty is not available.
7
+ * Utilities (node-pty loading, shell detection, ScrollbackBuffer, types)
8
+ * live in pty-utils.ts. This file owns session lifecycle orchestration.
17
9
  */
18
10
 
19
11
  import { EventEmitter } from 'node:events';
20
- import { createRequire } from 'node:module';
21
12
  import { homedir, platform } from 'node:os';
22
13
  import { sanitizeEnvForSandbox } from '../sandbox-utils.js';
23
-
24
- // Try to load node-pty (optional native dependency)
25
- let pty: typeof import('node-pty') | null = null;
26
- let _ptyLoadError: string | null = null;
27
-
28
- try {
29
- pty = await import('node-pty');
30
- } catch (error: unknown) {
31
- _ptyLoadError = error instanceof Error ? error.message : 'Failed to load node-pty';
32
- console.warn('[PTYManager] node-pty not available - terminal features disabled');
33
- console.warn('[PTYManager] To enable terminals, run: mstro setup-terminal');
34
- }
35
-
36
- /**
37
- * Check if node-pty is available
38
- */
39
- export function isPtyAvailable(): boolean {
40
- return pty !== null;
41
- }
42
-
43
- /**
44
- * Re-attempt loading node-pty at runtime.
45
- * Called after `mstro setup-terminal` compiles the native module
46
- * so the running server can pick it up without a restart.
47
- *
48
- * Uses createRequire (CJS) to bypass ESM's module cache — a failed
49
- * ESM import is permanently cached, but CJS require cache entries
50
- * can be deleted and re-required.
51
- */
52
- export async function reloadPty(): Promise<boolean> {
53
- if (pty) return true;
54
- try {
55
- const require = createRequire(import.meta.url);
56
- // Clear any cached failure so require() retries the native load
57
- const resolved = require.resolve('node-pty');
58
- delete require.cache[resolved];
59
- pty = require('node-pty');
60
- _ptyLoadError = null;
61
- console.log('[PTYManager] node-pty loaded successfully after reload');
62
- return true;
63
- } catch (error: unknown) {
64
- _ptyLoadError = error instanceof Error ? error.message : 'Failed to load node-pty';
65
- return false;
66
- }
67
- }
68
-
69
- /**
70
- * Get installation instructions for node-pty based on platform
71
- */
72
- export function getPtyInstallInstructions(): string {
73
- const os = platform();
74
-
75
- let instructions = `Terminal feature requires native compilation of node-pty.\n\n`;
76
- instructions += `To enable this feature:\n\n`;
77
-
78
- if (os === 'darwin') {
79
- instructions += `1. Install Xcode Command Line Tools:\n`;
80
- instructions += ` xcode-select --install\n\n`;
81
- } else if (os === 'win32') {
82
- instructions += `1. Install Windows Build Tools:\n`;
83
- instructions += ` npm install -g windows-build-tools\n\n`;
84
- } else {
85
- // Linux
86
- instructions += `1. Install build tools:\n`;
87
- instructions += ` # Debian/Ubuntu:\n`;
88
- instructions += ` sudo apt install build-essential python3\n\n`;
89
- instructions += ` # Fedora/RHEL:\n`;
90
- instructions += ` sudo dnf install gcc-c++ make python3\n\n`;
91
- instructions += ` # Arch:\n`;
92
- instructions += ` sudo pacman -S base-devel python\n\n`;
93
- }
94
-
95
- instructions += `2. Rebuild native modules:\n`;
96
- instructions += ` npm rebuild node-pty\n\n`;
97
- instructions += `3. Restart mstro\n`;
98
-
99
- return instructions;
100
- }
101
-
102
- // Import type separately for type-checking (doesn't require the module to load)
103
- type IPty = import('node-pty').IPty;
104
-
105
- /**
106
- * Fixed-size buffer that retains the most recent PTY output for replay on reconnect.
107
- * Stores raw string chunks and evicts oldest data when the total exceeds maxBytes.
108
- */
109
- class ScrollbackBuffer {
110
- private chunks: string[] = [];
111
- private totalLength = 0;
112
- private maxBytes: number;
113
-
114
- constructor(maxBytes: number) {
115
- this.maxBytes = maxBytes;
116
- }
117
-
118
- append(data: string): void {
119
- this.chunks.push(data);
120
- this.totalLength += data.length;
121
- // Evict oldest chunks until under budget
122
- while (this.totalLength > this.maxBytes && this.chunks.length > 1) {
123
- const removed = this.chunks.shift()!;
124
- this.totalLength -= removed.length;
125
- }
126
- // If a single chunk exceeds max, truncate from the front
127
- if (this.totalLength > this.maxBytes && this.chunks.length === 1) {
128
- const excess = this.totalLength - this.maxBytes;
129
- this.chunks[0] = this.chunks[0].slice(excess);
130
- this.totalLength = this.chunks[0].length;
131
- }
132
- }
133
-
134
- getContents(): string {
135
- return this.chunks.join('');
136
- }
137
-
138
- clear(): void {
139
- this.chunks = [];
140
- this.totalLength = 0;
141
- }
142
- }
143
-
144
- const SCROLLBACK_MAX_BYTES = 256 * 1024; // 256KB
145
-
146
- export interface PTYSession {
147
- id: string;
148
- pty: IPty;
149
- shell: string;
150
- cwd: string;
151
- // Timestamp when session was created
152
- createdAt: number;
153
- // Last activity timestamp
154
- lastActivityAt: number;
155
- // Current dimensions
156
- cols: number;
157
- rows: number;
158
- // Output coalescing: buffer small chunks into fewer WS messages
159
- _outputBuffer: string;
160
- _outputTimer: ReturnType<typeof setTimeout> | null;
161
- // Scrollback ring buffer for replay on reconnect
162
- scrollback: ScrollbackBuffer;
163
- }
164
-
165
- /**
166
- * Detect the user's default shell
167
- */
168
- function detectShell(): string {
169
- const shell = process.env.SHELL;
170
-
171
- if (shell) {
172
- return shell;
173
- }
174
-
175
- // Platform-specific defaults
176
- if (platform() === 'win32') {
177
- return process.env.COMSPEC || 'powershell.exe';
178
- }
179
-
180
- return '/bin/bash';
181
- }
182
-
183
- /**
184
- * Get shell name from path
185
- */
186
- function getShellName(shellPath: string): string {
187
- const parts = shellPath.split(/[/\\]/);
188
- return parts[parts.length - 1] || 'shell';
189
- }
14
+ import type { PTYSession } from './pty-utils.js';
15
+ import {
16
+ detectShell,
17
+ getPty,
18
+ getPtyInstallInstructions,
19
+ getShellName,
20
+ isPtyAvailable,
21
+ SCROLLBACK_MAX_BYTES,
22
+ ScrollbackBuffer,
23
+ } from './pty-utils.js';
24
+
25
+ export type { PTYSession } from './pty-utils.js';
26
+ // Re-export public API for backward compatibility
27
+ export { isPtyAvailable, reloadPty } from './pty-utils.js';
190
28
 
191
29
  export class PTYManager extends EventEmitter {
192
30
  private terminals: Map<string, PTYSession> = new Map();
193
31
 
194
32
  constructor() {
195
33
  super();
196
- // Each terminal adds 3 listeners (output, exit, error) to this singleton.
197
- // With multiple terminals, the default limit of 10 is easily exceeded.
198
34
  this.setMaxListeners(50);
199
35
  }
200
36
 
201
- /**
202
- * Check if a terminal session exists and is still running
203
- */
204
37
  exists(terminalId: string): boolean {
205
38
  return this.terminals.has(terminalId);
206
39
  }
207
40
 
208
- /**
209
- * Get session info for reconnection
210
- * Returns null if session doesn't exist
211
- */
212
41
  getSessionInfo(terminalId: string): { shell: string; cwd: string; cols: number; rows: number } | null {
213
42
  const session = this.terminals.get(terminalId);
214
43
  if (!session) return null;
215
- return {
216
- shell: session.shell,
217
- cwd: session.cwd,
218
- cols: session.cols,
219
- rows: session.rows,
220
- };
44
+ return { shell: session.shell, cwd: session.cwd, cols: session.cols, rows: session.rows };
221
45
  }
222
46
 
223
- /**
224
- * Check if PTY functionality is available
225
- */
226
47
  isPtyAvailable(): boolean {
227
48
  return isPtyAvailable();
228
49
  }
229
50
 
230
- /**
231
- * Get installation instructions if PTY is not available
232
- */
233
51
  getPtyInstallInstructions(): string {
234
52
  return getPtyInstallInstructions();
235
53
  }
236
54
 
237
- /**
238
- * Create a new terminal session
239
- */
240
55
  create(
241
56
  terminalId: string,
242
57
  workingDir: string,
@@ -245,52 +60,30 @@ export class PTYManager extends EventEmitter {
245
60
  requestedShell?: string,
246
61
  options?: { sandboxed?: boolean }
247
62
  ): { shell: string; cwd: string; isReconnect: boolean; platform: string } {
248
- // Check if node-pty is available
63
+ const pty = getPty();
249
64
  if (!pty) {
250
65
  throw new Error(`PTY_NOT_AVAILABLE:${getPtyInstallInstructions()}`);
251
66
  }
252
67
 
253
- // Check if session already exists - if so, this is a reconnection
68
+ // Reconnect to existing session
254
69
  if (this.terminals.has(terminalId)) {
255
70
  const existingSession = this.terminals.get(terminalId)!;
256
-
257
- // Always resize on reconnect to trigger SIGWINCH, which causes the
258
- // shell to redraw its prompt line for the reconnected client
259
71
  existingSession.pty.resize(cols, rows);
260
72
  existingSession.cols = cols;
261
73
  existingSession.rows = rows;
262
-
263
- return {
264
- shell: existingSession.shell,
265
- cwd: existingSession.cwd,
266
- isReconnect: true,
267
- platform: platform(),
268
- };
74
+ return { shell: existingSession.shell, cwd: existingSession.cwd, isReconnect: true, platform: platform() };
269
75
  }
270
76
 
271
77
  const shell = requestedShell || detectShell();
272
78
  const cwd = workingDir || homedir();
273
79
 
274
-
275
80
  try {
276
- // Build env: sandboxed sessions get stripped secrets and HOME=projectDir
277
81
  const baseEnv = options?.sandboxed
278
82
  ? sanitizeEnvForSandbox(process.env, cwd)
279
83
  : { ...process.env, HOME: homedir() };
280
- const env = {
281
- ...baseEnv,
282
- TERM: 'xterm-256color',
283
- COLORTERM: 'truecolor',
284
- };
84
+ const env = { ...baseEnv, TERM: 'xterm-256color', COLORTERM: 'truecolor' };
285
85
 
286
- // Spawn the PTY process
287
- const ptyProcess = pty.spawn(shell, [], {
288
- name: 'xterm-256color',
289
- cols,
290
- rows,
291
- cwd,
292
- env,
293
- });
86
+ const ptyProcess = pty.spawn(shell, [], { name: 'xterm-256color', cols, rows, cwd, env });
294
87
 
295
88
  const session: PTYSession = {
296
89
  id: terminalId,
@@ -307,56 +100,7 @@ export class PTYManager extends EventEmitter {
307
100
  };
308
101
  this.terminals.set(terminalId, session);
309
102
 
310
- // Handle data output — coalesce small chunks to reduce WebSocket message count.
311
- // On macOS, node-pty emits many tiny chunks (sometimes single bytes) and zsh
312
- // wraps echoed chars in multi-part ANSI sequences (RPROMPT, syntax highlighting).
313
- // A longer window on macOS ensures these multi-part sequences arrive as one chunk,
314
- // which the browser's predictive echo can match correctly.
315
- // 32ms on macOS captures full zsh redraw cycles (RPROMPT + syntax highlighting)
316
- // that 24ms often split across coalesce boundaries.
317
- const OUTPUT_COALESCE_MS = platform() === 'darwin' ? 32 : 8;
318
- // High-water mark: flush immediately when buffer exceeds this size
319
- // to prevent unbounded memory growth during high-output commands (e.g. `yes`)
320
- const OUTPUT_HIGH_WATER = 64 * 1024; // 64KB
321
- // Maximum chunk size per WebSocket message to prevent browser overload
322
- const OUTPUT_CHUNK_SIZE = 64 * 1024;
323
-
324
- const flushOutputBuffer = () => {
325
- if (session._outputTimer) {
326
- clearTimeout(session._outputTimer);
327
- session._outputTimer = null;
328
- }
329
- const buffered = session._outputBuffer;
330
- session._outputBuffer = '';
331
- // Chunk large output to prevent single massive WebSocket frames
332
- for (let i = 0; i < buffered.length; i += OUTPUT_CHUNK_SIZE) {
333
- this.emit('output', terminalId, buffered.slice(i, i + OUTPUT_CHUNK_SIZE));
334
- }
335
- };
336
-
337
- ptyProcess.onData((data: string) => {
338
- session.scrollback.append(data);
339
- session.lastActivityAt = Date.now();
340
- session._outputBuffer += data;
341
- // Flush immediately if buffer exceeds high-water mark
342
- if (session._outputBuffer.length >= OUTPUT_HIGH_WATER) {
343
- flushOutputBuffer();
344
- } else if (!session._outputTimer) {
345
- session._outputTimer = setTimeout(flushOutputBuffer, OUTPUT_COALESCE_MS);
346
- }
347
- });
348
-
349
- // Handle exit — flush any buffered output first
350
- ptyProcess.onExit(({ exitCode }) => {
351
- if (session._outputBuffer) {
352
- flushOutputBuffer();
353
- } else if (session._outputTimer) {
354
- clearTimeout(session._outputTimer);
355
- session._outputTimer = null;
356
- }
357
- this.emit('exit', terminalId, exitCode);
358
- this.terminals.delete(terminalId);
359
- });
103
+ this.attachOutputHandlers(session, terminalId);
360
104
 
361
105
  return { shell: session.shell, cwd, isReconnect: false, platform: platform() };
362
106
  } catch (error: unknown) {
@@ -366,16 +110,53 @@ export class PTYManager extends EventEmitter {
366
110
  }
367
111
  }
368
112
 
369
- /**
370
- * Write input data to terminal
371
- */
113
+ private attachOutputHandlers(session: PTYSession, terminalId: string): void {
114
+ // Output coalescing constants
115
+ const OUTPUT_COALESCE_MS = platform() === 'darwin' ? 32 : 8;
116
+ const OUTPUT_HIGH_WATER = 64 * 1024;
117
+ const OUTPUT_CHUNK_SIZE = 64 * 1024;
118
+
119
+ const flushOutputBuffer = () => {
120
+ if (session._outputTimer) {
121
+ clearTimeout(session._outputTimer);
122
+ session._outputTimer = null;
123
+ }
124
+ const buffered = session._outputBuffer;
125
+ session._outputBuffer = '';
126
+ for (let i = 0; i < buffered.length; i += OUTPUT_CHUNK_SIZE) {
127
+ this.emit('output', terminalId, buffered.slice(i, i + OUTPUT_CHUNK_SIZE));
128
+ }
129
+ };
130
+
131
+ session.pty.onData((data: string) => {
132
+ session.scrollback.append(data);
133
+ session.lastActivityAt = Date.now();
134
+ session._outputBuffer += data;
135
+ if (session._outputBuffer.length >= OUTPUT_HIGH_WATER) {
136
+ flushOutputBuffer();
137
+ } else if (!session._outputTimer) {
138
+ session._outputTimer = setTimeout(flushOutputBuffer, OUTPUT_COALESCE_MS);
139
+ }
140
+ });
141
+
142
+ session.pty.onExit(({ exitCode }) => {
143
+ if (session._outputBuffer) {
144
+ flushOutputBuffer();
145
+ } else if (session._outputTimer) {
146
+ clearTimeout(session._outputTimer);
147
+ session._outputTimer = null;
148
+ }
149
+ this.emit('exit', terminalId, exitCode);
150
+ this.terminals.delete(terminalId);
151
+ });
152
+ }
153
+
372
154
  write(terminalId: string, data: string): boolean {
373
155
  const session = this.terminals.get(terminalId);
374
156
  if (!session) {
375
157
  console.warn(`[PTYManager] Terminal ${terminalId} not found for write`);
376
158
  return false;
377
159
  }
378
-
379
160
  try {
380
161
  session.pty.write(data);
381
162
  return true;
@@ -386,16 +167,12 @@ export class PTYManager extends EventEmitter {
386
167
  }
387
168
  }
388
169
 
389
- /**
390
- * Resize terminal
391
- */
392
170
  resize(terminalId: string, cols: number, rows: number): boolean {
393
171
  const session = this.terminals.get(terminalId);
394
172
  if (!session) {
395
173
  console.warn(`[PTYManager] Terminal ${terminalId} not found for resize`);
396
174
  return false;
397
175
  }
398
-
399
176
  try {
400
177
  session.pty.resize(cols, rows);
401
178
  return true;
@@ -405,18 +182,10 @@ export class PTYManager extends EventEmitter {
405
182
  }
406
183
  }
407
184
 
408
- /**
409
- * Close terminal session
410
- */
411
185
  close(terminalId: string): boolean {
412
186
  const session = this.terminals.get(terminalId);
413
- if (!session) {
414
- return false;
415
- }
416
-
417
-
187
+ if (!session) return false;
418
188
  try {
419
- // Flush any coalesced output before closing
420
189
  if (session._outputTimer) {
421
190
  clearTimeout(session._outputTimer);
422
191
  if (session._outputBuffer) {
@@ -435,45 +204,29 @@ export class PTYManager extends EventEmitter {
435
204
  }
436
205
  }
437
206
 
438
- /**
439
- * Get scrollback buffer contents for replay on reconnect
440
- */
441
207
  getScrollback(terminalId: string): string | null {
442
208
  const session = this.terminals.get(terminalId);
443
209
  if (!session) return null;
444
210
  return session.scrollback.getContents();
445
211
  }
446
212
 
447
- /**
448
- * Get terminal session info
449
- */
450
213
  getSession(terminalId: string): PTYSession | undefined {
451
214
  return this.terminals.get(terminalId);
452
215
  }
453
216
 
454
- /**
455
- * Check if terminal exists
456
- */
457
217
  has(terminalId: string): boolean {
458
218
  return this.terminals.has(terminalId);
459
219
  }
460
220
 
461
- /**
462
- * Get all active terminal IDs
463
- */
464
221
  getActiveTerminals(): string[] {
465
222
  return Array.from(this.terminals.keys());
466
223
  }
467
224
 
468
- /**
469
- * Close all terminals
470
- */
471
225
  closeAll(): void {
472
226
  for (const terminalId of this.terminals.keys()) {
473
227
  this.close(terminalId);
474
228
  }
475
229
  }
476
-
477
230
  }
478
231
 
479
232
  // Singleton instance
@@ -0,0 +1,176 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * PTY Utilities — node-pty loading, shell detection, scrollback buffer, and types.
6
+ *
7
+ * Separated from pty-manager.ts so the PTYManager class stays focused
8
+ * on session lifecycle orchestration.
9
+ */
10
+
11
+ import { createRequire } from 'node:module';
12
+ import { platform } from 'node:os';
13
+
14
+ // ── node-pty loading ──────────────────────────────────────────
15
+
16
+ // Try to load node-pty (optional native dependency)
17
+ let pty: typeof import('node-pty') | null = null;
18
+ let _ptyLoadError: string | null = null;
19
+
20
+ try {
21
+ pty = await import('node-pty');
22
+ } catch (error: unknown) {
23
+ _ptyLoadError = error instanceof Error ? error.message : 'Failed to load node-pty';
24
+ console.warn('[PTYManager] node-pty not available - terminal features disabled');
25
+ console.warn('[PTYManager] To enable terminals, run: mstro setup-terminal');
26
+ }
27
+
28
+ export function getPty(): typeof import('node-pty') | null {
29
+ return pty;
30
+ }
31
+
32
+ /**
33
+ * Check if node-pty is available
34
+ */
35
+ export function isPtyAvailable(): boolean {
36
+ return pty !== null;
37
+ }
38
+
39
+ /**
40
+ * Re-attempt loading node-pty at runtime.
41
+ * Called after `mstro setup-terminal` compiles the native module
42
+ * so the running server can pick it up without a restart.
43
+ *
44
+ * Uses createRequire (CJS) to bypass ESM's module cache — a failed
45
+ * ESM import is permanently cached, but CJS require cache entries
46
+ * can be deleted and re-required.
47
+ */
48
+ export async function reloadPty(): Promise<boolean> {
49
+ if (pty) return true;
50
+ try {
51
+ const require = createRequire(import.meta.url);
52
+ const resolved = require.resolve('node-pty');
53
+ delete require.cache[resolved];
54
+ pty = require('node-pty');
55
+ _ptyLoadError = null;
56
+ console.log('[PTYManager] node-pty loaded successfully after reload');
57
+ return true;
58
+ } catch (error: unknown) {
59
+ _ptyLoadError = error instanceof Error ? error.message : 'Failed to load node-pty';
60
+ return false;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Get installation instructions for node-pty based on platform
66
+ */
67
+ export function getPtyInstallInstructions(): string {
68
+ const os = platform();
69
+
70
+ let instructions = `Terminal feature requires native compilation of node-pty.\n\n`;
71
+ instructions += `To enable this feature:\n\n`;
72
+
73
+ if (os === 'darwin') {
74
+ instructions += `1. Install Xcode Command Line Tools:\n`;
75
+ instructions += ` xcode-select --install\n\n`;
76
+ } else if (os === 'win32') {
77
+ instructions += `1. Install Windows Build Tools:\n`;
78
+ instructions += ` npm install -g windows-build-tools\n\n`;
79
+ } else {
80
+ instructions += `1. Install build tools:\n`;
81
+ instructions += ` # Debian/Ubuntu:\n`;
82
+ instructions += ` sudo apt install build-essential python3\n\n`;
83
+ instructions += ` # Fedora/RHEL:\n`;
84
+ instructions += ` sudo dnf install gcc-c++ make python3\n\n`;
85
+ instructions += ` # Arch:\n`;
86
+ instructions += ` sudo pacman -S base-devel python\n\n`;
87
+ }
88
+
89
+ instructions += `2. Rebuild native modules:\n`;
90
+ instructions += ` npm rebuild node-pty\n\n`;
91
+ instructions += `3. Restart mstro\n`;
92
+
93
+ return instructions;
94
+ }
95
+
96
+ // ── Shell detection ───────────────────────────────────────────
97
+
98
+ /**
99
+ * Detect the user's default shell
100
+ */
101
+ export function detectShell(): string {
102
+ const shell = process.env.SHELL;
103
+ if (shell) return shell;
104
+ if (platform() === 'win32') {
105
+ return process.env.COMSPEC || 'powershell.exe';
106
+ }
107
+ return '/bin/bash';
108
+ }
109
+
110
+ /**
111
+ * Get shell name from path
112
+ */
113
+ export function getShellName(shellPath: string): string {
114
+ const parts = shellPath.split(/[/\\]/);
115
+ return parts[parts.length - 1] || 'shell';
116
+ }
117
+
118
+ // ── Scrollback buffer ─────────────────────────────────────────
119
+
120
+ export const SCROLLBACK_MAX_BYTES = 256 * 1024; // 256KB
121
+
122
+ /**
123
+ * Fixed-size buffer that retains the most recent PTY output for replay on reconnect.
124
+ * Stores raw string chunks and evicts oldest data when the total exceeds maxBytes.
125
+ */
126
+ export class ScrollbackBuffer {
127
+ private chunks: string[] = [];
128
+ private totalLength = 0;
129
+ private maxBytes: number;
130
+
131
+ constructor(maxBytes: number) {
132
+ this.maxBytes = maxBytes;
133
+ }
134
+
135
+ append(data: string): void {
136
+ this.chunks.push(data);
137
+ this.totalLength += data.length;
138
+ while (this.totalLength > this.maxBytes && this.chunks.length > 1) {
139
+ const removed = this.chunks.shift()!;
140
+ this.totalLength -= removed.length;
141
+ }
142
+ if (this.totalLength > this.maxBytes && this.chunks.length === 1) {
143
+ const excess = this.totalLength - this.maxBytes;
144
+ this.chunks[0] = this.chunks[0].slice(excess);
145
+ this.totalLength = this.chunks[0].length;
146
+ }
147
+ }
148
+
149
+ getContents(): string {
150
+ return this.chunks.join('');
151
+ }
152
+
153
+ clear(): void {
154
+ this.chunks = [];
155
+ this.totalLength = 0;
156
+ }
157
+ }
158
+
159
+ // ── Types ─────────────────────────────────────────────────────
160
+
161
+ // Import type separately for type-checking (doesn't require the module to load)
162
+ type IPty = import('node-pty').IPty;
163
+
164
+ export interface PTYSession {
165
+ id: string;
166
+ pty: IPty;
167
+ shell: string;
168
+ cwd: string;
169
+ createdAt: number;
170
+ lastActivityAt: number;
171
+ cols: number;
172
+ rows: number;
173
+ _outputBuffer: string;
174
+ _outputTimer: ReturnType<typeof setTimeout> | null;
175
+ scrollback: ScrollbackBuffer;
176
+ }