mstro-app 0.4.3 → 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 (306) hide show
  1. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  3. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  5. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  9. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  13. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  17. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  18. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  19. package/dist/server/cli/headless/claude-invoker.js +10 -807
  20. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  21. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  22. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  23. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  24. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  25. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  26. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  27. package/dist/server/cli/headless/headless-logger.js +28 -5
  28. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  29. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  31. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  33. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  34. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  35. package/dist/server/cli/headless/stall-assessor.js +65 -457
  36. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  37. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  38. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  39. package/dist/server/cli/improvisation-attachments.js +116 -0
  40. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  41. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  42. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  43. package/dist/server/cli/improvisation-retry.js +434 -0
  44. package/dist/server/cli/improvisation-retry.js.map +1 -0
  45. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  46. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  47. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  48. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  49. package/dist/server/cli/improvisation-types.d.ts +86 -0
  50. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  51. package/dist/server/cli/improvisation-types.js +10 -0
  52. package/dist/server/cli/improvisation-types.js.map +1 -0
  53. package/dist/server/cli/prompt-builders.d.ts +68 -0
  54. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  55. package/dist/server/cli/prompt-builders.js +312 -0
  56. package/dist/server/cli/prompt-builders.js.map +1 -0
  57. package/dist/server/index.js +33 -212
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  60. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  61. package/dist/server/mcp/bouncer-haiku.js +152 -0
  62. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  63. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  64. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  65. package/dist/server/mcp/bouncer-integration.js +50 -196
  66. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  67. package/dist/server/mcp/security-analysis.d.ts +38 -0
  68. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  69. package/dist/server/mcp/security-analysis.js +183 -0
  70. package/dist/server/mcp/security-analysis.js.map +1 -0
  71. package/dist/server/mcp/security-audit.d.ts +1 -1
  72. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  73. package/dist/server/mcp/security-patterns.d.ts +1 -25
  74. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  75. package/dist/server/mcp/security-patterns.js +55 -260
  76. package/dist/server/mcp/security-patterns.js.map +1 -1
  77. package/dist/server/server-setup.d.ts +22 -0
  78. package/dist/server/server-setup.d.ts.map +1 -0
  79. package/dist/server/server-setup.js +101 -0
  80. package/dist/server/server-setup.js.map +1 -0
  81. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  82. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  83. package/dist/server/services/file-explorer-ops.js +211 -0
  84. package/dist/server/services/file-explorer-ops.js.map +1 -0
  85. package/dist/server/services/files.d.ts +2 -85
  86. package/dist/server/services/files.d.ts.map +1 -1
  87. package/dist/server/services/files.js +7 -427
  88. package/dist/server/services/files.js.map +1 -1
  89. package/dist/server/services/plan/composer.d.ts.map +1 -1
  90. package/dist/server/services/plan/composer.js +2 -1
  91. package/dist/server/services/plan/composer.js.map +1 -1
  92. package/dist/server/services/plan/executor.d.ts.map +1 -1
  93. package/dist/server/services/plan/executor.js +3 -1
  94. package/dist/server/services/plan/executor.js.map +1 -1
  95. package/dist/server/services/plan/parser-core.d.ts +20 -0
  96. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  97. package/dist/server/services/plan/parser-core.js +350 -0
  98. package/dist/server/services/plan/parser-core.js.map +1 -0
  99. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  100. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  101. package/dist/server/services/plan/parser-migration.js +124 -0
  102. package/dist/server/services/plan/parser-migration.js.map +1 -0
  103. package/dist/server/services/plan/parser.d.ts +0 -8
  104. package/dist/server/services/plan/parser.d.ts.map +1 -1
  105. package/dist/server/services/plan/parser.js +50 -569
  106. package/dist/server/services/plan/parser.js.map +1 -1
  107. package/dist/server/services/plan/review-gate.d.ts +2 -0
  108. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  109. package/dist/server/services/plan/review-gate.js +2 -2
  110. package/dist/server/services/plan/review-gate.js.map +1 -1
  111. package/dist/server/services/plan/types.d.ts +2 -0
  112. package/dist/server/services/plan/types.d.ts.map +1 -1
  113. package/dist/server/services/platform-credentials.d.ts +24 -0
  114. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  115. package/dist/server/services/platform-credentials.js +68 -0
  116. package/dist/server/services/platform-credentials.js.map +1 -0
  117. package/dist/server/services/platform.d.ts +1 -31
  118. package/dist/server/services/platform.d.ts.map +1 -1
  119. package/dist/server/services/platform.js +10 -119
  120. package/dist/server/services/platform.js.map +1 -1
  121. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  122. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  123. package/dist/server/services/terminal/pty-manager.js +53 -266
  124. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  125. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  126. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  127. package/dist/server/services/terminal/pty-utils.js +141 -0
  128. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  129. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  130. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  131. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  132. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  133. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  134. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  135. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  136. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  137. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  138. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  139. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  140. package/dist/server/services/websocket/file-utils.js +3 -3
  141. package/dist/server/services/websocket/file-utils.js.map +1 -1
  142. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  143. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  144. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  145. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  146. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  147. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  148. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  149. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  150. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  151. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  152. package/dist/server/services/websocket/git-handlers.js +35 -541
  153. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  154. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  155. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  156. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  157. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  158. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  159. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  160. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  161. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  162. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  163. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  164. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  165. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  166. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  167. package/dist/server/services/websocket/git-utils.js +201 -0
  168. package/dist/server/services/websocket/git-utils.js.map +1 -0
  169. package/dist/server/services/websocket/handler.d.ts +2 -0
  170. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  171. package/dist/server/services/websocket/handler.js +37 -126
  172. package/dist/server/services/websocket/handler.js.map +1 -1
  173. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  174. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  175. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  176. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  177. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  178. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  179. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  180. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  181. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  182. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  183. package/dist/server/services/websocket/plan-handlers.js +6 -925
  184. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  185. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  186. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  187. package/dist/server/services/websocket/plan-helpers.js +199 -0
  188. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  189. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  190. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  191. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  192. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  193. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  194. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  195. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  196. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  197. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  198. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  199. package/dist/server/services/websocket/quality-complexity.js +262 -0
  200. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  201. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  202. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  203. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  204. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  205. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  206. package/dist/server/services/websocket/quality-handlers.js +34 -346
  207. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  208. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  209. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  210. package/dist/server/services/websocket/quality-linting.js +178 -0
  211. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  212. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  213. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  214. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  215. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  216. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  217. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  218. package/dist/server/services/websocket/quality-service.js +9 -651
  219. package/dist/server/services/websocket/quality-service.js.map +1 -1
  220. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  221. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  222. package/dist/server/services/websocket/quality-tools.js +208 -0
  223. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  224. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  225. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  226. package/dist/server/services/websocket/quality-types.js +101 -0
  227. package/dist/server/services/websocket/quality-types.js.map +1 -0
  228. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  229. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  230. package/dist/server/services/websocket/session-handlers.js +3 -378
  231. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  232. package/dist/server/services/websocket/session-history.d.ts +4 -0
  233. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  234. package/dist/server/services/websocket/session-history.js +208 -0
  235. package/dist/server/services/websocket/session-history.js.map +1 -0
  236. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  237. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  238. package/dist/server/services/websocket/session-initialization.js +163 -0
  239. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  240. package/dist/server/services/websocket/types.d.ts +12 -2
  241. package/dist/server/services/websocket/types.d.ts.map +1 -1
  242. package/package.json +1 -1
  243. package/server/cli/headless/claude-invoker-process.ts +204 -0
  244. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  245. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  246. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  247. package/server/cli/headless/claude-invoker.ts +15 -1096
  248. package/server/cli/headless/haiku-assessments.ts +365 -0
  249. package/server/cli/headless/headless-logger.ts +26 -5
  250. package/server/cli/headless/native-timeout-detector.ts +117 -0
  251. package/server/cli/headless/stall-assessor.ts +65 -618
  252. package/server/cli/improvisation-attachments.ts +148 -0
  253. package/server/cli/improvisation-retry.ts +602 -0
  254. package/server/cli/improvisation-session-manager.ts +140 -1349
  255. package/server/cli/improvisation-types.ts +98 -0
  256. package/server/cli/prompt-builders.ts +370 -0
  257. package/server/index.ts +35 -246
  258. package/server/mcp/bouncer-haiku.ts +182 -0
  259. package/server/mcp/bouncer-integration.ts +87 -248
  260. package/server/mcp/security-analysis.ts +217 -0
  261. package/server/mcp/security-audit.ts +1 -1
  262. package/server/mcp/security-patterns.ts +60 -283
  263. package/server/server-setup.ts +114 -0
  264. package/server/services/file-explorer-ops.ts +293 -0
  265. package/server/services/files.ts +20 -532
  266. package/server/services/plan/composer.ts +2 -1
  267. package/server/services/plan/executor.ts +3 -1
  268. package/server/services/plan/parser-core.ts +406 -0
  269. package/server/services/plan/parser-migration.ts +128 -0
  270. package/server/services/plan/parser.ts +52 -620
  271. package/server/services/plan/review-gate.ts +4 -2
  272. package/server/services/plan/types.ts +2 -0
  273. package/server/services/platform-credentials.ts +83 -0
  274. package/server/services/platform.ts +15 -141
  275. package/server/services/terminal/pty-manager.ts +66 -313
  276. package/server/services/terminal/pty-utils.ts +176 -0
  277. package/server/services/websocket/file-definition-handlers.ts +165 -0
  278. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  279. package/server/services/websocket/file-search-handlers.ts +291 -0
  280. package/server/services/websocket/file-utils.ts +3 -3
  281. package/server/services/websocket/git-branch-handlers.ts +130 -0
  282. package/server/services/websocket/git-diff-handlers.ts +140 -0
  283. package/server/services/websocket/git-handlers.ts +40 -625
  284. package/server/services/websocket/git-log-handlers.ts +149 -0
  285. package/server/services/websocket/git-pr-handlers.ts +17 -62
  286. package/server/services/websocket/git-tag-handlers.ts +91 -0
  287. package/server/services/websocket/git-utils.ts +230 -0
  288. package/server/services/websocket/handler.ts +39 -126
  289. package/server/services/websocket/plan-board-handlers.ts +277 -0
  290. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  291. package/server/services/websocket/plan-handlers.ts +8 -1114
  292. package/server/services/websocket/plan-helpers.ts +215 -0
  293. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  294. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  295. package/server/services/websocket/quality-complexity.ts +294 -0
  296. package/server/services/websocket/quality-fix-agent.ts +181 -0
  297. package/server/services/websocket/quality-handlers.ts +36 -404
  298. package/server/services/websocket/quality-linting.ts +187 -0
  299. package/server/services/websocket/quality-review-agent.ts +246 -0
  300. package/server/services/websocket/quality-service.ts +11 -762
  301. package/server/services/websocket/quality-tools.ts +209 -0
  302. package/server/services/websocket/quality-types.ts +169 -0
  303. package/server/services/websocket/session-handlers.ts +5 -437
  304. package/server/services/websocket/session-history.ts +222 -0
  305. package/server/services/websocket/session-initialization.ts +209 -0
  306. package/server/services/websocket/types.ts +17 -0
@@ -0,0 +1,204 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import { type ChildProcess, spawn } from 'node:child_process';
5
+ import { sanitizeEnvForSandbox } from '../../services/sandbox-utils.js';
6
+ import type { StreamHandlerContext } from './claude-invoker-stream.js';
7
+ import { flushNativeTimeoutBuffers, verboseLog } from './claude-invoker-stream.js';
8
+ import { herror } from './headless-logger.js';
9
+ import { generateMcpConfig } from './mcp-config.js';
10
+ import { buildMultimodalMessage } from './prompt-utils.js';
11
+ import type { ExecutionResult, ResolvedHeadlessConfig } from './types.js';
12
+
13
+ // ========== Signal Helpers ==========
14
+
15
+ /** Map a Node.js signal name to its numeric value for exit code computation */
16
+ function signalToNumber(signal: string): number | undefined {
17
+ const map: Record<string, number> = {
18
+ SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGABRT: 6,
19
+ SIGKILL: 9, SIGTERM: 15, SIGUSR1: 10, SIGUSR2: 12,
20
+ };
21
+ return map[signal];
22
+ }
23
+
24
+ // ========== Error Handling ==========
25
+
26
+ const SPAWN_ERROR_MAP: Record<string, { code: string; message: string }> = {
27
+ ENOENT: {
28
+ code: 'CLAUDE_NOT_INSTALLED',
29
+ message: 'Claude Code is not installed or not in PATH. Please install Claude Code: npm install -g @anthropic-ai/claude-code'
30
+ },
31
+ EACCES: {
32
+ code: 'PERMISSION_DENIED',
33
+ message: 'Permission denied when running Claude Code. Please check file permissions.'
34
+ }
35
+ };
36
+
37
+ export function handleSpawnError(
38
+ error: NodeJS.ErrnoException,
39
+ config: ResolvedHeadlessConfig,
40
+ reject: (reason: Error) => void
41
+ ): void {
42
+ const mapped = error.code ? SPAWN_ERROR_MAP[error.code] : undefined;
43
+ if (!mapped) {
44
+ reject(error);
45
+ return;
46
+ }
47
+
48
+ const formatted = `[[MSTRO_ERROR:${mapped.code}]] ${mapped.message}`;
49
+ if (config.outputCallback) {
50
+ config.outputCallback(`\n${formatted}\n`);
51
+ }
52
+ reject(new Error(formatted));
53
+ }
54
+
55
+ // ========== Argument Building ==========
56
+
57
+ export function buildClaudeArgs(
58
+ config: ResolvedHeadlessConfig,
59
+ prompt: string,
60
+ hasImageAttachments: boolean,
61
+ useStreamJson: boolean,
62
+ mcpConfigPath: string | null
63
+ ): string[] {
64
+ const args = ['--print'];
65
+
66
+ if (config.model && config.model !== 'default') {
67
+ args.push('--model', config.model);
68
+ }
69
+
70
+ if (useStreamJson) {
71
+ args.push('--output-format', 'stream-json', '--include-partial-messages', '--verbose');
72
+ }
73
+
74
+ if (hasImageAttachments) {
75
+ args.push('--input-format', 'stream-json');
76
+ }
77
+
78
+ if (config.claudeSessionId) {
79
+ args.push('--resume', config.claudeSessionId);
80
+ } else if (config.continueSession) {
81
+ args.push('--continue');
82
+ }
83
+
84
+ if (config.disallowedTools && config.disallowedTools.length > 0) {
85
+ args.push('--disallowedTools', config.disallowedTools.join(','));
86
+ }
87
+
88
+ if (mcpConfigPath) {
89
+ args.push('--mcp-config', mcpConfigPath);
90
+ args.push('--permission-prompt-tool', 'mcp__mstro-bouncer__approval_prompt');
91
+ } else {
92
+ args.push('--permission-mode', 'acceptEdits');
93
+ }
94
+
95
+ // Reduce Edit-without-Read errors by reminding the model
96
+ args.push('--append-system-prompt', 'IMPORTANT: Always use the Read tool to read a file before using Edit or Write on it. Never edit a file you have not read in this session.');
97
+
98
+ if (!hasImageAttachments) {
99
+ args.push(prompt);
100
+ }
101
+
102
+ return args;
103
+ }
104
+
105
+ /** Write image attachments to the Claude process stdin as stream-json */
106
+ function writeImageAttachmentsToStdin(
107
+ claudeProcess: ChildProcess,
108
+ prompt: string,
109
+ config: ResolvedHeadlessConfig,
110
+ ): void {
111
+ claudeProcess.stdin!.on('error', (err) => {
112
+ if (config.verbose) {
113
+ herror('[STDIN] Write error:', err.message);
114
+ }
115
+ config.outputCallback?.(`\n[[MSTRO_ERROR:STDIN_WRITE_FAILED]] Failed to send image data to Claude: ${err.message}\n`);
116
+ });
117
+ const multimodalMessage = buildMultimodalMessage(prompt, config.imageAttachments!);
118
+ claudeProcess.stdin!.write(multimodalMessage);
119
+ claudeProcess.stdin!.end();
120
+ }
121
+
122
+ // ========== Process Spawning ==========
123
+
124
+ /** Spawn the Claude CLI process and register it */
125
+ export function spawnAndRegister(
126
+ config: ResolvedHeadlessConfig,
127
+ prompt: string,
128
+ hasImageAttachments: boolean,
129
+ useStreamJson: boolean,
130
+ runningProcesses: Map<number, ChildProcess>,
131
+ perfStart: number,
132
+ ): ChildProcess {
133
+ const mcpConfigPath = generateMcpConfig(config.workingDir, config.verbose);
134
+
135
+ if (!mcpConfigPath && config.outputCallback) {
136
+ config.outputCallback(
137
+ '\n[[MSTRO_ERROR:BOUNCER_UNAVAILABLE]] Security bouncer not available. Running with limited permissions — file edits allowed, but shell commands may be restricted.\n'
138
+ );
139
+ }
140
+
141
+ const args = buildClaudeArgs(config, prompt, hasImageAttachments, useStreamJson, mcpConfigPath);
142
+
143
+ verboseLog(config.verbose,
144
+ `[PERF] About to spawn: ${Date.now() - perfStart}ms`,
145
+ `[PERF] Command: ${config.claudeCommand} ${args.join(' ')}`,
146
+ );
147
+
148
+ const baseEnv = config.sandboxed
149
+ ? sanitizeEnvForSandbox(process.env, config.workingDir)
150
+ : { ...process.env };
151
+ const spawnEnv = config.extraEnv
152
+ ? { ...baseEnv, ...config.extraEnv }
153
+ : baseEnv;
154
+
155
+ const claudeProcess = spawn(config.claudeCommand, args, {
156
+ cwd: config.workingDir,
157
+ detached: true,
158
+ env: spawnEnv,
159
+ stdio: [hasImageAttachments ? 'pipe' : 'ignore', 'pipe', 'pipe']
160
+ });
161
+
162
+ if (hasImageAttachments && claudeProcess.stdin) {
163
+ writeImageAttachmentsToStdin(claudeProcess, prompt, config);
164
+ }
165
+
166
+ if (claudeProcess.pid) {
167
+ runningProcesses.set(claudeProcess.pid, claudeProcess);
168
+ }
169
+
170
+ verboseLog(config.verbose, `[PERF] Spawned: ${Date.now() - perfStart}ms`);
171
+
172
+ return claudeProcess;
173
+ }
174
+
175
+ // ========== Result Building ==========
176
+
177
+ export function buildCloseResult(
178
+ ctx: StreamHandlerContext,
179
+ stdout: string,
180
+ stderr: string,
181
+ code: number | null,
182
+ signal: NodeJS.Signals | null,
183
+ sessionCapture: { claudeSessionId?: string },
184
+ ): ExecutionResult {
185
+ const postTimeout = flushNativeTimeoutBuffers(ctx);
186
+ const resumeBuffered = ctx.resumeAssessmentActive ? (ctx.resumeAssessmentBuffer || undefined) : undefined;
187
+ const exitCode = code ?? (signal ? 128 + (signalToNumber(signal) ?? 0) : 0);
188
+ const hasTokenUsage = ctx.apiTokenUsage.inputTokens > 0 || ctx.apiTokenUsage.outputTokens > 0;
189
+ return {
190
+ output: stdout,
191
+ error: stderr || undefined,
192
+ exitCode,
193
+ signalName: signal || undefined,
194
+ assistantResponse: ctx.accumulatedAssistantResponse || undefined,
195
+ thinkingOutput: ctx.accumulatedThinking || undefined,
196
+ toolUseHistory: ctx.accumulatedToolUse.length > 0 ? ctx.accumulatedToolUse : undefined,
197
+ claudeSessionId: sessionCapture.claudeSessionId,
198
+ nativeTimeoutCount: ctx.nativeTimeoutDetector.timeoutCount || undefined,
199
+ postTimeoutOutput: postTimeout,
200
+ resumeBufferedOutput: resumeBuffered,
201
+ apiTokenUsage: hasTokenUsage ? { ...ctx.apiTokenUsage } : undefined,
202
+ stopReason: ctx.stopReason,
203
+ };
204
+ }
@@ -0,0 +1,164 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import type { ChildProcess } from 'node:child_process';
5
+ import { hlog } from './headless-logger.js';
6
+ import { killProcessGroup } from './runner.js';
7
+ import { assessStall, type StallContext } from './stall-assessor.js';
8
+ import type { ResolvedHeadlessConfig } from './types.js';
9
+
10
+ export interface StallAssessmentParams {
11
+ stallCtx: StallContext;
12
+ config: ResolvedHeadlessConfig;
13
+ now: number;
14
+ extensionsGranted: number;
15
+ maxExtensions: number;
16
+ toolWatchdogActive?: boolean;
17
+ }
18
+
19
+ /** Mutable state for stall detection, shared between the interval callback and the outer function */
20
+ export interface StallState {
21
+ lastActivityTime: number;
22
+ stallWarningEmitted: boolean;
23
+ assessmentInProgress: boolean;
24
+ extensionsGranted: number;
25
+ currentKillDeadline: number;
26
+ nextWarningAfter: number;
27
+ }
28
+
29
+ /** Terminate a stalled process: SIGTERM then SIGKILL after 5s */
30
+ export function terminateStallProcess(
31
+ claudeProcess: ChildProcess,
32
+ interval: ReturnType<typeof setInterval>,
33
+ config: ResolvedHeadlessConfig,
34
+ message: string,
35
+ ): void {
36
+ clearInterval(interval);
37
+ config.outputCallback?.(message);
38
+ if (claudeProcess.pid) killProcessGroup(claudeProcess.pid, 'SIGTERM');
39
+ setTimeout(() => {
40
+ if (!claudeProcess.killed && claudeProcess.pid) {
41
+ killProcessGroup(claudeProcess.pid, 'SIGKILL');
42
+ }
43
+ }, 5000);
44
+ }
45
+
46
+ /** Run stall assessment and return updated state if extended, null otherwise */
47
+ async function runStallAssessment(
48
+ params: StallAssessmentParams,
49
+ ): Promise<{ extensionsGranted: number; currentKillDeadline: number } | null> {
50
+ const { stallCtx, config, now, extensionsGranted, maxExtensions, toolWatchdogActive } = params;
51
+ try {
52
+ const verdict = await assessStall(stallCtx, config.claudeCommand, config.verbose, toolWatchdogActive);
53
+ if (verdict.action === 'extend') {
54
+ const newExtensions = extensionsGranted + 1;
55
+ const elapsedMin = Math.round(stallCtx.elapsedTotalMs / 60_000);
56
+ const pendingNames = stallCtx.pendingToolNames ?? new Set<string>();
57
+
58
+ const isAgentTeamsLead = verdict.reason.includes('Agent Teams lead');
59
+ if (pendingNames.has('Task') || isAgentTeamsLead) {
60
+ config.outputCallback?.(
61
+ `\n[[MSTRO_STALL_EXTENDED]] ${isAgentTeamsLead ? 'Teammates still working' : 'Task subagent still running'} (${elapsedMin} min elapsed). ${verdict.reason}.\n`
62
+ );
63
+ } else {
64
+ config.outputCallback?.(
65
+ `\n[[MSTRO_STALL_EXTENDED]] Process still working (${elapsedMin} min elapsed). ${verdict.reason}. Extension ${newExtensions}/${maxExtensions}.\n`
66
+ );
67
+ }
68
+ if (config.verbose) {
69
+ hlog(`[STALL] Extended by ${Math.round(verdict.extensionMs / 60_000)} min: ${verdict.reason}`);
70
+ }
71
+ return { extensionsGranted: newExtensions, currentKillDeadline: now + verdict.extensionMs };
72
+ }
73
+ config.outputCallback?.(
74
+ `\n[[MSTRO_STALL_CONFIRMED]] Assessment: process likely stalled. ${verdict.reason}.\n`
75
+ );
76
+ if (config.verbose) {
77
+ hlog(`[STALL] Assessment says stalled: ${verdict.reason}`);
78
+ }
79
+ } catch (err) {
80
+ if (config.verbose) {
81
+ hlog(`[STALL] Assessment error: ${err}`);
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /** Run a single stall-check tick */
88
+ export async function runStallCheckTick(
89
+ state: StallState,
90
+ opts: {
91
+ perfStart: number;
92
+ stallWarningMs: number;
93
+ stallHardCapMs: number;
94
+ maxExtensions: number;
95
+ stallAssessEnabled: boolean;
96
+ toolWatchdogActive: boolean;
97
+ prompt: string;
98
+ pendingTools: Map<string, string>;
99
+ lastToolInputSummary: string | undefined;
100
+ totalToolCalls: number;
101
+ claudeProcess: ChildProcess;
102
+ stallCheckInterval: ReturnType<typeof setInterval>;
103
+ config: ResolvedHeadlessConfig;
104
+ lastTokenActivityTime: number;
105
+ },
106
+ ): Promise<void> {
107
+ const now = Date.now();
108
+ const silenceMs = now - state.lastActivityTime;
109
+ const totalElapsed = now - opts.perfStart;
110
+ const tokenSilenceMs = now - opts.lastTokenActivityTime;
111
+
112
+ if (totalElapsed >= opts.stallHardCapMs) {
113
+ terminateStallProcess(opts.claudeProcess, opts.stallCheckInterval, opts.config,
114
+ `\n[[MSTRO_ERROR:EXECUTION_STALLED]] Hard time limit reached (${Math.round(opts.stallHardCapMs / 60000)} min total). Terminating process.\n`
115
+ );
116
+ return;
117
+ }
118
+
119
+ // Token activity pushes the kill deadline forward
120
+ if (tokenSilenceMs < 60_000 && now < state.currentKillDeadline) {
121
+ const killMs = opts.config.stallKillMs ?? 1_800_000;
122
+ state.currentKillDeadline = Math.max(state.currentKillDeadline, now + killMs);
123
+ }
124
+
125
+ if (now >= state.currentKillDeadline) {
126
+ terminateStallProcess(opts.claudeProcess, opts.stallCheckInterval, opts.config,
127
+ `\n[[MSTRO_ERROR:EXECUTION_STALLED]] No output for ${Math.round(silenceMs / 60_000)} minutes. Terminating process.\n`
128
+ );
129
+ return;
130
+ }
131
+
132
+ if (silenceMs < opts.stallWarningMs || state.stallWarningEmitted || now < state.nextWarningAfter || state.assessmentInProgress) return;
133
+
134
+ const stallCtx: StallContext = {
135
+ originalPrompt: opts.prompt,
136
+ silenceMs,
137
+ lastToolName: opts.pendingTools.size > 0 ? Array.from(opts.pendingTools.values()).pop() : undefined,
138
+ lastToolInputSummary: opts.lastToolInputSummary,
139
+ pendingToolCount: opts.pendingTools.size,
140
+ pendingToolNames: new Set(opts.pendingTools.values()),
141
+ totalToolCalls: opts.totalToolCalls,
142
+ elapsedTotalMs: totalElapsed,
143
+ tokenSilenceMs,
144
+ };
145
+
146
+ if (opts.stallAssessEnabled && state.extensionsGranted < opts.maxExtensions) {
147
+ state.assessmentInProgress = true;
148
+ const result = await runStallAssessment({ stallCtx, config: opts.config, now, extensionsGranted: state.extensionsGranted, maxExtensions: opts.maxExtensions, toolWatchdogActive: opts.toolWatchdogActive });
149
+ state.assessmentInProgress = false;
150
+
151
+ if (result) {
152
+ state.extensionsGranted = result.extensionsGranted;
153
+ state.currentKillDeadline = result.currentKillDeadline;
154
+ state.nextWarningAfter = now + opts.stallWarningMs;
155
+ return;
156
+ }
157
+ }
158
+
159
+ state.stallWarningEmitted = true;
160
+ const killIn = Math.round((state.currentKillDeadline - now) / 60_000);
161
+ opts.config.outputCallback?.(
162
+ `\n[[MSTRO_ERROR:EXECUTION_STALLED]] No output for ${Math.round(silenceMs / 60_000)} minutes. Will terminate in ${killIn} minutes if no activity.\n`
163
+ );
164
+ }