@agimon-ai/doompi-runner 0.0.1-alpha.2 → 0.0.1-alpha.21

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 (293) hide show
  1. package/README.md +25 -16
  2. package/dist/adapters/RunnerPaths.cjs +2 -0
  3. package/dist/adapters/RunnerPaths.cjs.map +1 -0
  4. package/dist/adapters/RunnerPaths.d.cts +35 -0
  5. package/dist/adapters/RunnerPaths.d.cts.map +1 -0
  6. package/dist/adapters/RunnerPaths.d.mts +35 -0
  7. package/dist/adapters/RunnerPaths.d.mts.map +1 -0
  8. package/dist/adapters/RunnerPaths.mjs +2 -0
  9. package/dist/adapters/RunnerPaths.mjs.map +1 -0
  10. package/dist/adapters/pi/extension.cjs +3 -0
  11. package/dist/adapters/pi/extension.cjs.map +1 -0
  12. package/dist/adapters/pi/extension.d.cts +10 -0
  13. package/dist/adapters/pi/extension.d.cts.map +1 -0
  14. package/dist/adapters/pi/extension.d.mts +10 -0
  15. package/dist/adapters/pi/extension.d.mts.map +1 -0
  16. package/dist/adapters/pi/extension.mjs +3 -0
  17. package/dist/adapters/pi/extension.mjs.map +1 -0
  18. package/dist/adapters/pty/ptyProvider.cjs +2 -0
  19. package/dist/adapters/pty/ptyProvider.cjs.map +1 -0
  20. package/dist/adapters/pty/ptyProvider.d.cts +13 -0
  21. package/dist/adapters/pty/ptyProvider.d.cts.map +1 -0
  22. package/dist/adapters/pty/ptyProvider.d.mts +13 -0
  23. package/dist/adapters/pty/ptyProvider.d.mts.map +1 -0
  24. package/dist/adapters/pty/ptyProvider.mjs +2 -0
  25. package/dist/adapters/pty/ptyProvider.mjs.map +1 -0
  26. package/dist/bin/cli.cjs +1 -1
  27. package/dist/bin/cli.cjs.map +1 -1
  28. package/dist/bin/cli.mjs +1 -1
  29. package/dist/bin/cli.mjs.map +1 -1
  30. package/dist/bin/logSink.cjs.map +1 -1
  31. package/dist/bin/logSink.mjs.map +1 -1
  32. package/dist/bin/runnerHost.cjs.map +1 -1
  33. package/dist/bin/runnerHost.mjs.map +1 -1
  34. package/dist/cli/cliApp.cjs +1 -6
  35. package/dist/cli/cliApp.d.cts +2 -23
  36. package/dist/cli/cliApp.d.mts +2 -23
  37. package/dist/cli/cliApp.mjs +1 -6
  38. package/dist/commands/bash/bashTool.cjs +7 -0
  39. package/dist/commands/bash/bashTool.cjs.map +1 -0
  40. package/dist/commands/bash/bashTool.d.cts +25 -0
  41. package/dist/commands/bash/bashTool.d.cts.map +1 -0
  42. package/dist/commands/bash/bashTool.d.mts +25 -0
  43. package/dist/commands/bash/bashTool.d.mts.map +1 -0
  44. package/dist/commands/bash/bashTool.mjs +7 -0
  45. package/dist/commands/bash/bashTool.mjs.map +1 -0
  46. package/dist/commands/bash/responseEnvelope.cjs +10 -0
  47. package/dist/commands/bash/responseEnvelope.cjs.map +1 -0
  48. package/dist/commands/bash/responseEnvelope.d.cts +45 -0
  49. package/dist/commands/bash/responseEnvelope.d.cts.map +1 -0
  50. package/dist/commands/bash/responseEnvelope.d.mts +45 -0
  51. package/dist/commands/bash/responseEnvelope.d.mts.map +1 -0
  52. package/dist/commands/bash/responseEnvelope.mjs +10 -0
  53. package/dist/{tool/responseEnvelope.cjs.map → commands/bash/responseEnvelope.mjs.map} +1 -1
  54. package/dist/commands/cli/cliApp.cjs +6 -0
  55. package/dist/commands/cli/cliApp.cjs.map +1 -0
  56. package/dist/commands/cli/cliApp.d.cts +23 -0
  57. package/dist/commands/cli/cliApp.d.cts.map +1 -0
  58. package/dist/commands/cli/cliApp.d.mts +23 -0
  59. package/dist/commands/cli/cliApp.d.mts.map +1 -0
  60. package/dist/commands/cli/cliApp.mjs +6 -0
  61. package/dist/commands/cli/cliApp.mjs.map +1 -0
  62. package/dist/compaction.cjs +1 -4
  63. package/dist/compaction.d.cts +2 -14
  64. package/dist/compaction.d.mts +2 -14
  65. package/dist/compaction.mjs +1 -4
  66. package/dist/config.cjs +1 -2
  67. package/dist/config.d.cts +2 -32
  68. package/dist/config.d.mts +2 -32
  69. package/dist/config.mjs +1 -2
  70. package/dist/container/index.cjs +1 -1
  71. package/dist/container/index.cjs.map +1 -1
  72. package/dist/container/index.mjs +1 -1
  73. package/dist/container/index.mjs.map +1 -1
  74. package/dist/extension.cjs +1 -3
  75. package/dist/extension.d.cts +2 -10
  76. package/dist/extension.d.mts +2 -10
  77. package/dist/extension.mjs +1 -3
  78. package/dist/extensions/pi.cjs +1 -1
  79. package/dist/extensions/pi.cjs.map +1 -1
  80. package/dist/extensions/pi.d.cts +1 -1
  81. package/dist/extensions/pi.d.mts +1 -1
  82. package/dist/extensions/pi.mjs +1 -1
  83. package/dist/extensions/pi.mjs.map +1 -1
  84. package/dist/index.cjs +1 -1
  85. package/dist/index.d.cts +2 -2
  86. package/dist/index.d.mts +2 -2
  87. package/dist/index.mjs +1 -1
  88. package/dist/ptyProvider.cjs +1 -2
  89. package/dist/ptyProvider.d.cts +2 -13
  90. package/dist/ptyProvider.d.mts +2 -13
  91. package/dist/ptyProvider.mjs +1 -2
  92. package/dist/reconcile.cjs +1 -2
  93. package/dist/reconcile.d.cts +2 -37
  94. package/dist/reconcile.d.mts +2 -37
  95. package/dist/reconcile.mjs +1 -2
  96. package/dist/runnerSpec.cjs +1 -2
  97. package/dist/runnerSpec.d.cts +2 -50
  98. package/dist/runnerSpec.d.mts +2 -50
  99. package/dist/runnerSpec.mjs +1 -2
  100. package/dist/schemas/bashTool.cjs +2 -0
  101. package/dist/schemas/bashTool.cjs.map +1 -0
  102. package/dist/schemas/bashTool.d.cts +25 -0
  103. package/dist/schemas/bashTool.d.cts.map +1 -0
  104. package/dist/schemas/bashTool.d.mts +25 -0
  105. package/dist/schemas/bashTool.d.mts.map +1 -0
  106. package/dist/schemas/bashTool.mjs +2 -0
  107. package/dist/schemas/bashTool.mjs.map +1 -0
  108. package/dist/schemas/runnerSpec.cjs +2 -0
  109. package/dist/schemas/runnerSpec.cjs.map +1 -0
  110. package/dist/schemas/runnerSpec.d.cts +50 -0
  111. package/dist/schemas/runnerSpec.d.cts.map +1 -0
  112. package/dist/schemas/runnerSpec.d.mts +50 -0
  113. package/dist/schemas/runnerSpec.d.mts.map +1 -0
  114. package/dist/schemas/runnerSpec.mjs +2 -0
  115. package/dist/schemas/runnerSpec.mjs.map +1 -0
  116. package/dist/services/AnsiScrub/ansiScrub.cjs +1 -1
  117. package/dist/services/AnsiScrub/ansiScrub.cjs.map +1 -1
  118. package/dist/services/AnsiScrub/ansiScrub.mjs +1 -1
  119. package/dist/services/AnsiScrub/ansiScrub.mjs.map +1 -1
  120. package/dist/services/BashRunService/BashRunService.cjs +1 -1
  121. package/dist/services/BashRunService/BashRunService.cjs.map +1 -1
  122. package/dist/services/BashRunService/BashRunService.mjs +1 -1
  123. package/dist/services/BashRunService/BashRunService.mjs.map +1 -1
  124. package/dist/services/Launcher/Launcher.cjs +1 -1
  125. package/dist/services/Launcher/Launcher.cjs.map +1 -1
  126. package/dist/services/Launcher/Launcher.mjs +1 -1
  127. package/dist/services/Launcher/Launcher.mjs.map +1 -1
  128. package/dist/services/Lifeline/NodeLifeline.cjs +1 -1
  129. package/dist/services/Lifeline/NodeLifeline.cjs.map +1 -1
  130. package/dist/services/Lifeline/NodeLifeline.mjs +1 -1
  131. package/dist/services/Lifeline/NodeLifeline.mjs.map +1 -1
  132. package/dist/services/LogFile/LogFile.cjs +1 -1
  133. package/dist/services/LogFile/LogFile.cjs.map +1 -1
  134. package/dist/services/LogFile/LogFile.mjs +1 -1
  135. package/dist/services/LogFile/LogFile.mjs.map +1 -1
  136. package/dist/services/PtyHost/PtyHost.cjs +1 -1
  137. package/dist/services/PtyHost/PtyHost.cjs.map +1 -1
  138. package/dist/services/PtyHost/PtyHost.mjs +1 -1
  139. package/dist/services/PtyHost/PtyHost.mjs.map +1 -1
  140. package/dist/services/RmuxBackend/RmuxBackend.cjs +1 -1
  141. package/dist/services/RmuxBackend/RmuxBackend.cjs.map +1 -1
  142. package/dist/services/RmuxBackend/RmuxBackend.mjs +1 -1
  143. package/dist/services/RmuxBackend/RmuxBackend.mjs.map +1 -1
  144. package/dist/services/RunnerNamer/RunnerNamer.cjs +1 -1
  145. package/dist/services/RunnerNamer/RunnerNamer.cjs.map +1 -1
  146. package/dist/services/RunnerNamer/RunnerNamer.mjs +1 -1
  147. package/dist/services/RunnerNamer/RunnerNamer.mjs.map +1 -1
  148. package/dist/services/RunnerPaths/RunnerPaths.cjs +1 -2
  149. package/dist/services/RunnerPaths/RunnerPaths.d.cts +2 -31
  150. package/dist/services/RunnerPaths/RunnerPaths.d.mts +2 -31
  151. package/dist/services/RunnerPaths/RunnerPaths.mjs +1 -2
  152. package/dist/services/RunnerPaths/index.cjs +1 -1
  153. package/dist/services/RunnerPaths/index.d.cts +2 -1
  154. package/dist/services/RunnerPaths/index.d.mts +2 -1
  155. package/dist/services/RunnerPaths/index.mjs +1 -1
  156. package/dist/services/RunnerPaths/types.d.cts +2 -0
  157. package/dist/services/RunnerPaths/types.d.cts.map +1 -1
  158. package/dist/services/RunnerPaths/types.d.mts +2 -0
  159. package/dist/services/RunnerPaths/types.d.mts.map +1 -1
  160. package/dist/services/RunnerRegistry/RunnerRegistry.cjs +1 -1
  161. package/dist/services/RunnerRegistry/RunnerRegistry.cjs.map +1 -1
  162. package/dist/services/RunnerRegistry/RunnerRegistry.mjs +1 -1
  163. package/dist/services/RunnerRegistry/RunnerRegistry.mjs.map +1 -1
  164. package/dist/services/runs/compaction.cjs +4 -0
  165. package/dist/services/runs/compaction.cjs.map +1 -0
  166. package/dist/services/runs/compaction.d.cts +14 -0
  167. package/dist/services/runs/compaction.d.cts.map +1 -0
  168. package/dist/services/runs/compaction.d.mts +14 -0
  169. package/dist/services/runs/compaction.d.mts.map +1 -0
  170. package/dist/services/runs/compaction.mjs +4 -0
  171. package/dist/services/runs/compaction.mjs.map +1 -0
  172. package/dist/services/runs/reconcile.cjs +2 -0
  173. package/dist/services/runs/reconcile.cjs.map +1 -0
  174. package/dist/services/runs/reconcile.d.cts +37 -0
  175. package/dist/services/runs/reconcile.d.cts.map +1 -0
  176. package/dist/services/runs/reconcile.d.mts +37 -0
  177. package/dist/services/runs/reconcile.d.mts.map +1 -0
  178. package/dist/services/runs/reconcile.mjs +2 -0
  179. package/dist/services/runs/reconcile.mjs.map +1 -0
  180. package/dist/services/runs/session.cjs +2 -0
  181. package/dist/services/runs/session.cjs.map +1 -0
  182. package/dist/services/runs/session.d.cts +10 -0
  183. package/dist/services/runs/session.d.cts.map +1 -0
  184. package/dist/services/runs/session.d.mts +10 -0
  185. package/dist/services/runs/session.d.mts.map +1 -0
  186. package/dist/services/runs/session.mjs +2 -0
  187. package/dist/services/runs/session.mjs.map +1 -0
  188. package/dist/session.cjs +1 -2
  189. package/dist/session.d.cts +2 -10
  190. package/dist/session.d.mts +2 -10
  191. package/dist/session.mjs +1 -2
  192. package/dist/tool/bashTool.cjs +1 -7
  193. package/dist/tool/bashTool.d.cts +2 -25
  194. package/dist/tool/bashTool.d.mts +2 -25
  195. package/dist/tool/bashTool.mjs +1 -7
  196. package/dist/tool/responseEnvelope.cjs +1 -10
  197. package/dist/tool/responseEnvelope.d.cts +2 -45
  198. package/dist/tool/responseEnvelope.d.mts +2 -45
  199. package/dist/tool/responseEnvelope.mjs +1 -10
  200. package/dist/tool/schema.cjs +1 -2
  201. package/dist/tool/schema.d.cts +2 -25
  202. package/dist/tool/schema.d.mts +2 -25
  203. package/dist/tool/schema.mjs +1 -2
  204. package/dist/tui/bashRender.cjs +4 -7
  205. package/dist/tui/bashRender.cjs.map +1 -1
  206. package/dist/tui/bashRender.d.cts +5 -5
  207. package/dist/tui/bashRender.d.cts.map +1 -1
  208. package/dist/tui/bashRender.d.mts +5 -5
  209. package/dist/tui/bashRender.d.mts.map +1 -1
  210. package/dist/tui/bashRender.mjs +4 -7
  211. package/dist/tui/bashRender.mjs.map +1 -1
  212. package/dist/tui/format.cjs +1 -1
  213. package/dist/tui/format.cjs.map +1 -1
  214. package/dist/tui/format.mjs +1 -1
  215. package/dist/tui/format.mjs.map +1 -1
  216. package/dist/tui/runnerSpace.cjs +4 -4
  217. package/dist/tui/runnerSpace.cjs.map +1 -1
  218. package/dist/tui/runnerSpace.d.cts.map +1 -1
  219. package/dist/tui/runnerSpace.d.mts.map +1 -1
  220. package/dist/tui/runnerSpace.mjs +4 -4
  221. package/dist/tui/runnerSpace.mjs.map +1 -1
  222. package/dist/types/config.cjs +2 -0
  223. package/dist/types/config.cjs.map +1 -0
  224. package/dist/types/config.d.cts +32 -0
  225. package/dist/types/config.d.cts.map +1 -0
  226. package/dist/types/config.d.mts +32 -0
  227. package/dist/types/config.d.mts.map +1 -0
  228. package/dist/types/config.mjs +2 -0
  229. package/dist/types/config.mjs.map +1 -0
  230. package/dist/types/index.cjs +2 -0
  231. package/dist/types/index.cjs.map +1 -0
  232. package/dist/types/index.d.cts +28 -0
  233. package/dist/types/index.d.cts.map +1 -0
  234. package/dist/types/index.d.mts +28 -0
  235. package/dist/types/index.d.mts.map +1 -0
  236. package/dist/types/index.mjs +2 -0
  237. package/dist/types/index.mjs.map +1 -0
  238. package/dist/types.cjs +1 -2
  239. package/dist/types.d.cts +2 -28
  240. package/dist/types.d.mts +2 -28
  241. package/dist/types.mjs +1 -2
  242. package/package.json +20 -13
  243. package/dist/cli/cliApp.cjs.map +0 -1
  244. package/dist/cli/cliApp.d.cts.map +0 -1
  245. package/dist/cli/cliApp.d.mts.map +0 -1
  246. package/dist/cli/cliApp.mjs.map +0 -1
  247. package/dist/compaction.cjs.map +0 -1
  248. package/dist/compaction.d.cts.map +0 -1
  249. package/dist/compaction.d.mts.map +0 -1
  250. package/dist/compaction.mjs.map +0 -1
  251. package/dist/config.cjs.map +0 -1
  252. package/dist/config.d.cts.map +0 -1
  253. package/dist/config.d.mts.map +0 -1
  254. package/dist/config.mjs.map +0 -1
  255. package/dist/extension.cjs.map +0 -1
  256. package/dist/extension.d.cts.map +0 -1
  257. package/dist/extension.d.mts.map +0 -1
  258. package/dist/extension.mjs.map +0 -1
  259. package/dist/ptyProvider.cjs.map +0 -1
  260. package/dist/ptyProvider.d.cts.map +0 -1
  261. package/dist/ptyProvider.d.mts.map +0 -1
  262. package/dist/ptyProvider.mjs.map +0 -1
  263. package/dist/reconcile.cjs.map +0 -1
  264. package/dist/reconcile.d.cts.map +0 -1
  265. package/dist/reconcile.d.mts.map +0 -1
  266. package/dist/reconcile.mjs.map +0 -1
  267. package/dist/runnerSpec.cjs.map +0 -1
  268. package/dist/runnerSpec.d.cts.map +0 -1
  269. package/dist/runnerSpec.d.mts.map +0 -1
  270. package/dist/runnerSpec.mjs.map +0 -1
  271. package/dist/services/RunnerPaths/RunnerPaths.cjs.map +0 -1
  272. package/dist/services/RunnerPaths/RunnerPaths.d.cts.map +0 -1
  273. package/dist/services/RunnerPaths/RunnerPaths.d.mts.map +0 -1
  274. package/dist/services/RunnerPaths/RunnerPaths.mjs.map +0 -1
  275. package/dist/session.cjs.map +0 -1
  276. package/dist/session.d.cts.map +0 -1
  277. package/dist/session.d.mts.map +0 -1
  278. package/dist/session.mjs.map +0 -1
  279. package/dist/tool/bashTool.cjs.map +0 -1
  280. package/dist/tool/bashTool.d.cts.map +0 -1
  281. package/dist/tool/bashTool.d.mts.map +0 -1
  282. package/dist/tool/bashTool.mjs.map +0 -1
  283. package/dist/tool/responseEnvelope.d.cts.map +0 -1
  284. package/dist/tool/responseEnvelope.d.mts.map +0 -1
  285. package/dist/tool/responseEnvelope.mjs.map +0 -1
  286. package/dist/tool/schema.cjs.map +0 -1
  287. package/dist/tool/schema.d.cts.map +0 -1
  288. package/dist/tool/schema.d.mts.map +0 -1
  289. package/dist/tool/schema.mjs.map +0 -1
  290. package/dist/types.cjs.map +0 -1
  291. package/dist/types.d.cts.map +0 -1
  292. package/dist/types.d.mts.map +0 -1
  293. package/dist/types.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ansiScrub.mjs","names":[],"sources":["../../../src/services/AnsiScrub/ansiScrub.ts"],"sourcesContent":["/**\n * Turns terminal output into text worth grepping.\n *\n * PTY output is written for a screen, not a file: colours, cursor moves and\n * carriage-return redraws all end up in the byte stream. Without this a single\n * progress bar becomes thousands of near-identical log lines.\n */\n\n/**\n * CSI sequences (colours, cursor moves), OSC strings, the two-byte Fe escapes,\n * and the keypad-mode escapes shells emit around every prompt.\n */\nconst ANSI_PATTERN = new RegExp(\n [\n '\\\\u001B\\\\[[0-9;?]*[ -/]*[@-~]',\n '\\\\u001B\\\\][^\\\\u0007\\\\u001B]*(?:\\\\u0007|\\\\u001B\\\\\\\\)',\n '\\\\u001B[@-Z\\\\\\\\-_]',\n '\\\\u001B[=><]',\n ].join('|'),\n 'g',\n);\n\n/** Bell and backspace, which a terminal consumes rather than displays. */\n// oxlint-disable-next-line no-control-regex -- matching terminal control bytes is the whole job\nconst CONTROL_PATTERN = new RegExp('[\\\\u0007\\\\u0008]', 'g');\n\n/**\n * The same set minus SGR, the sequence that only sets colour and weight.\n *\n * The final byte class omits `m`, so `ESC[32m` survives while cursor moves,\n * OSC strings and keypad escapes are still removed.\n */\nconst ANSI_PATTERN_KEEPING_COLOUR = new RegExp(\n [\n '\\\\u001B\\\\[[0-9;?]*[ -/]*[@-ln-~]',\n '\\\\u001B\\\\][^\\\\u0007\\\\u001B]*(?:\\\\u0007|\\\\u001B\\\\\\\\)',\n '\\\\u001B[@-Z\\\\\\\\-_]',\n '\\\\u001B[=><]',\n ].join('|'),\n 'g',\n);\n\n/** Removes escape sequences, leaving the characters a user would have seen. */\nexport function stripAnsi(text: string): string {\n return text.replace(ANSI_PATTERN, '').replace(CONTROL_PATTERN, '');\n}\n\n/**\n * Removes everything `stripAnsi` does except colour.\n *\n * Logs are read back by a terminal renderer as well as by grep, and a command\n * that colours its own output has already said how it should look.\n */\nexport function stripAnsiKeepingColour(text: string): string {\n return text.replace(ANSI_PATTERN_KEEPING_COLOUR, '').replace(CONTROL_PATTERN, '');\n}\n\n/**\n * Collapses carriage-return redraws.\n *\n * A bare `\\r` returns the cursor to the start of the current line, so whatever\n * follows overwrites it. Only the final state of each line is kept. The `\\r` of\n * a CRLF pair is a line terminator rather than a redraw, so it is normalised\n * away first: a pseudo terminal ends every line that way, and treating it as a\n * redraw would blank the whole log.\n */\nexport function collapseCarriageReturns(text: string): string {\n return text\n .replace(/\\r\\n/g, '\\n')\n .split('\\n')\n .map((line) => (line.includes('\\r') ? (line.split('\\r').at(-1) ?? '') : line))\n .join('\\n');\n}\n\n/** Full scrub applied to PTY output before it reaches the log file. */\nexport function scrubTerminalOutput(text: string): string {\n return collapseCarriageReturns(stripAnsiKeepingColour(text));\n}\n\n/** Plain-text scrub, for the copy the model reads. */\nexport function scrubTerminalOutputToPlainText(text: string): string {\n return collapseCarriageReturns(stripAnsi(text));\n}\n"],"mappings":"AAYA,MAAM,EAAe,IAAI,OACvB,CACE,gCACA,sDACA,qBACA,cACF,CAAC,CAAC,KAAK,GAAG,EACV,GACF,EAIM,EAAsB,OAAO,mBAAoB,GAAG,EAQpD,EAA8B,IAAI,OACtC,CACE,mCACA,sDACA,qBACA,cACF,CAAC,CAAC,KAAK,GAAG,EACV,GACF,EAGA,SAAgB,EAAU,EAAsB,CAC9C,OAAO,EAAK,QAAQ,EAAc,EAAE,CAAC,CAAC,QAAQ,EAAiB,EAAE,CACnE,CAQA,SAAgB,EAAuB,EAAsB,CAC3D,OAAO,EAAK,QAAQ,EAA6B,EAAE,CAAC,CAAC,QAAQ,EAAiB,EAAE,CAClF,CAWA,SAAgB,EAAwB,EAAsB,CAC5D,OAAO,EACJ,QAAQ,QAAS;CAAI,CAAC,CACtB,MAAM;CAAI,CAAC,CACX,IAAK,GAAU,EAAK,SAAS,IAAI,EAAK,EAAK,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,GAAK,GAAM,CAAK,CAAC,CAC7E,KAAK;CAAI,CACd,CAGA,SAAgB,EAAoB,EAAsB,CACxD,OAAO,EAAwB,EAAuB,CAAI,CAAC,CAC7D,CAGA,SAAgB,EAA+B,EAAsB,CACnE,OAAO,EAAwB,EAAU,CAAI,CAAC,CAChD"}
1
+ {"version":3,"file":"ansiScrub.mjs","names":[],"sources":["../../../src/services/AnsiScrub/ansiScrub.ts"],"sourcesContent":["/**\n * Turns terminal output into text worth grepping.\n *\n * PTY output is written for a screen, not a file: colours, cursor moves and\n * carriage-return redraws all end up in the byte stream. Without this a single\n * progress bar becomes thousands of near-identical log lines.\n */\n\n/**\n * CSI sequences (colours, cursor moves), OSC strings, the two-byte Fe escapes,\n * and the keypad-mode escapes shells emit around every prompt.\n */\nconst ANSI_PATTERN = new RegExp(\n [\n '\\\\u001B\\\\[[0-9;?]*[ -/]*[@-~]',\n '\\\\u001B\\\\][^\\\\u0007\\\\u001B]*(?:\\\\u0007|\\\\u001B\\\\\\\\)',\n '\\\\u001B[@-Z\\\\\\\\-_]',\n '\\\\u001B[=><]',\n ].join('|'),\n 'g',\n);\n\n/** Bell and backspace, which a terminal consumes rather than displays. */\n// oxlint-disable-next-line no-control-regex -- matching terminal control bytes is the whole job\nconst CONTROL_PATTERN = /[\\u0007\\u0008]/g;\n\n/**\n * The same set minus SGR, the sequence that only sets colour and weight.\n *\n * The final byte class omits `m`, so `ESC[32m` survives while cursor moves,\n * OSC strings and keypad escapes are still removed.\n */\nconst ANSI_PATTERN_KEEPING_COLOUR = new RegExp(\n [\n '\\\\u001B\\\\[[0-9;?]*[ -/]*[@-ln-~]',\n '\\\\u001B\\\\][^\\\\u0007\\\\u001B]*(?:\\\\u0007|\\\\u001B\\\\\\\\)',\n '\\\\u001B[@-Z\\\\\\\\-_]',\n '\\\\u001B[=><]',\n ].join('|'),\n 'g',\n);\n\n/** Removes escape sequences, leaving the characters a user would have seen. */\nexport function stripAnsi(text: string): string {\n return text.replace(ANSI_PATTERN, '').replace(CONTROL_PATTERN, '');\n}\n\n/**\n * Removes everything `stripAnsi` does except colour.\n *\n * Logs are read back by a terminal renderer as well as by grep, and a command\n * that colours its own output has already said how it should look.\n */\nexport function stripAnsiKeepingColour(text: string): string {\n return text.replace(ANSI_PATTERN_KEEPING_COLOUR, '').replace(CONTROL_PATTERN, '');\n}\n\n/**\n * Collapses carriage-return redraws.\n *\n * A bare `\\r` returns the cursor to the start of the current line, so whatever\n * follows overwrites it. Only the final state of each line is kept. The `\\r` of\n * a CRLF pair is a line terminator rather than a redraw, so it is normalised\n * away first: a pseudo terminal ends every line that way, and treating it as a\n * redraw would blank the whole log.\n */\nexport function collapseCarriageReturns(text: string): string {\n return text\n .replace(/\\r\\n/g, '\\n')\n .split('\\n')\n .map((line) => (line.includes('\\r') ? (line.split('\\r').at(-1) ?? '') : line))\n .join('\\n');\n}\n\n/** Full scrub applied to PTY output before it reaches the log file. */\nexport function scrubTerminalOutput(text: string): string {\n return collapseCarriageReturns(stripAnsiKeepingColour(text));\n}\n\n/** Plain-text scrub, for the copy the model reads. */\nexport function scrubTerminalOutputToPlainText(text: string): string {\n return collapseCarriageReturns(stripAnsi(text));\n}\n"],"mappings":"AAYA,MAAM,EAAe,IAAI,OACvB,CACE,gCACA,sDACA,qBACA,cACF,CAAC,CAAC,KAAK,GAAG,EACV,GACF,EAIM,EAAkB,kBAQlB,EAA8B,IAAI,OACtC,CACE,mCACA,sDACA,qBACA,cACF,CAAC,CAAC,KAAK,GAAG,EACV,GACF,EAGA,SAAgB,EAAU,EAAsB,CAC9C,OAAO,EAAK,QAAQ,EAAc,EAAE,CAAC,CAAC,QAAQ,EAAiB,EAAE,CACnE,CAQA,SAAgB,EAAuB,EAAsB,CAC3D,OAAO,EAAK,QAAQ,EAA6B,EAAE,CAAC,CAAC,QAAQ,EAAiB,EAAE,CAClF,CAWA,SAAgB,EAAwB,EAAsB,CAC5D,OAAO,EACJ,QAAQ,QAAS;CAAI,CAAC,CACtB,MAAM;CAAI,CAAC,CACX,IAAK,GAAU,EAAK,SAAS,IAAI,EAAK,EAAK,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,GAAK,GAAM,CAAK,CAAC,CAC7E,KAAK;CAAI,CACd,CAGA,SAAgB,EAAoB,EAAsB,CACxD,OAAO,EAAwB,EAAuB,CAAI,CAAC,CAC7D,CAGA,SAAgB,EAA+B,EAAsB,CACnE,OAAO,EAAwB,EAAU,CAAI,CAAC,CAChD"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../config.cjs"),t=require("../../types.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs");let a=require("inversify");require("reflect-metadata/lite");let o=require("node:crypto");const s=Symbol(`threshold-reached`),c=Symbol(`deadline-reached`),l=`completed`,u=`failed`,d=`launcher_error`,f=`signaled`;function p(){return`${Date.now().toString(36)}-${(0,o.randomBytes)(3).toString(`base64url`)}`}let m=class{launcher;rmuxBackend;ptyHost;namer;registry;clock;constructor(e,t,n,r,i,a){this.launcher=e,this.rmuxBackend=t,this.ptyHost=n,this.namer=r,this.registry=i,this.clock=a}async run(e){let t=p(),n=await this.namer.allocate(e.command,e.sessionId,e.name),r=e.cwd??process.cwd(),i=e.interactive===!0,a;try{a=await this.rmuxBackend.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId,interactive:i})??(i?await this.ptyHost.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId}):this.launcher.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId}))}catch(e){return{kind:u,id:t,name:n,error:e instanceof Error?e.message:String(e)}}if(a.pid===void 0)return{kind:u,id:t,name:n,error:`The command did not start, so it cannot be supervised`};try{await this.registry.register({id:t,name:n,pid:a.pid,command:e.command,cwd:r,logPath:a.logPath,interactive:i,sessionId:e.sessionId,backend:a.backend,...a.backendTarget?{backendTarget:a.backendTarget}:{}})}catch(e){return await a.stop(),{kind:u,id:t,name:n,error:e instanceof Error?e.message:String(e)}}if(e.background===!0||i)return this.promote(a,e.background===!0?`requested`:`interactive`);let o=e.onOutput?this.followOutput(a,e.onOutput):void 0;try{let r=await this.race(a,e.timeoutMs);return o?.flush(),r===s?this.promote(a,`threshold`):r===c?(await a.stop(),o?.flush(),await this.registry.complete(t,{reason:`timed_out`,code:null,signal:`SIGTERM`}),{kind:l,id:t,name:n,output:a.output(),exitCode:null,signal:`SIGTERM`,logPath:a.logPath,backend:a.backend,timedOut:!0}):r instanceof Error?(await this.registry.complete(t,{reason:d,code:null,signal:null}),{kind:u,id:t,name:n,error:r.message}):(await this.registry.complete(t,{reason:r.signal?f:r.code===0?l:u,code:r.code,signal:r.signal}),{kind:l,id:t,name:n,output:a.output(),exitCode:r.code,signal:r.signal,logPath:a.logPath,backend:a.backend})}finally{o?.dispose()}}followOutput(e,t){let n=!0,r=``,i,a=()=>{if(!n)return;let i=e.output();i!==r&&(r=i,t(i))},o=()=>{n&&(a(),n&&(i=this.clock.after(100,o)))};return i=this.clock.after(100,o),{flush:a,dispose:()=>{n=!1,i?.(),i=void 0}}}async race(t,n){let r=[],i=[t.completion().catch(e=>e)];i.push(new Promise(t=>{r.push(this.clock.after(e.getBackgroundThresholdMs(),()=>t(s)))})),n!==void 0&&n>0&&i.push(new Promise(e=>{r.push(this.clock.after(n,()=>e(c)))}));try{return await Promise.race(i)}finally{for(let e of r)e()}}async promote(e,t){return e.pid===void 0?{kind:u,id:e.id,name:e.name,error:`The command did not start`}:(await this.registry.markPromoted(e.id),e.detach(),e.completion().then(t=>this.registry.complete(e.id,{reason:t.signal?f:t.code===0?l:u,code:t.code,signal:t.signal}),()=>this.registry.complete(e.id,{reason:d,code:null,signal:null})).catch(t=>{let n=t instanceof Error?t.message:String(t);process.emitWarning(`Failed to complete runner ${e.id}: ${n}`)}),{kind:`promoted`,id:e.id,name:e.name,pid:e.pid,logPath:e.logPath,backend:e.backend,reason:t})}};m=i.default([(0,a.injectable)(),r.default(0,(0,a.inject)(t.TYPES.Launcher)),r.default(1,(0,a.inject)(t.TYPES.RmuxBackend)),r.default(2,(0,a.inject)(t.TYPES.PtyHost)),r.default(3,(0,a.inject)(t.TYPES.RunnerNamer)),r.default(4,(0,a.inject)(t.TYPES.RunnerRegistry)),r.default(5,(0,a.inject)(t.TYPES.Clock)),n.default(`design:paramtypes`,[Object,Object,Object,Object,Object,Object])],m),Object.defineProperty(exports,"BashRunService",{enumerable:!0,get:function(){return m}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../types/config.cjs"),t=require("../../types/index.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs");let a=require("inversify");require("reflect-metadata/lite");let o=require("node:crypto");const s=Symbol(`threshold-reached`),c=Symbol(`deadline-reached`),l=`completed`,u=`failed`,d=`launcher_error`,f=`signaled`;function p(){return`${Date.now().toString(36)}-${(0,o.randomBytes)(3).toString(`base64url`)}`}let m=class{launcher;rmuxBackend;ptyHost;namer;registry;clock;constructor(e,t,n,r,i,a){this.launcher=e,this.rmuxBackend=t,this.ptyHost=n,this.namer=r,this.registry=i,this.clock=a}async run(e){let t=p(),n=await this.namer.allocate(e.command,e.sessionId,e.name),r=e.cwd??process.cwd(),i=e.interactive===!0,a;try{a=await this.rmuxBackend.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId,interactive:i})??(i?await this.ptyHost.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId}):this.launcher.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId}))}catch(e){return{kind:u,id:t,name:n,error:e instanceof Error?e.message:String(e)}}if(a.pid===void 0)return{kind:u,id:t,name:n,error:`The command did not start, so it cannot be supervised`};try{await this.registry.register({id:t,name:n,pid:a.pid,command:e.command,cwd:r,logPath:a.logPath,interactive:i,sessionId:e.sessionId,backend:a.backend,...a.backendTarget?{backendTarget:a.backendTarget}:{}})}catch(e){return await a.stop(),{kind:u,id:t,name:n,error:e instanceof Error?e.message:String(e)}}if(e.background===!0||i)return this.promote(a,e.background===!0?`requested`:`interactive`);let o=e.onOutput?this.followOutput(a,e.onOutput):void 0;try{let r=await this.race(a,e.timeoutMs);return o?.flush(),r===s?this.promote(a,`threshold`):r===c?(await a.stop(),o?.flush(),await this.registry.complete(t,{reason:`timed_out`,code:null,signal:`SIGTERM`}),{kind:l,id:t,name:n,output:a.output(),exitCode:null,signal:`SIGTERM`,logPath:a.logPath,backend:a.backend,timedOut:!0}):r instanceof Error?(await this.registry.complete(t,{reason:d,code:null,signal:null}),{kind:u,id:t,name:n,error:r.message}):(await this.registry.complete(t,{reason:r.signal?f:r.code===0?l:u,code:r.code,signal:r.signal}),{kind:l,id:t,name:n,output:a.output(),exitCode:r.code,signal:r.signal,logPath:a.logPath,backend:a.backend})}finally{o?.dispose()}}followOutput(e,t){let n=!0,r=``,i,a=()=>{if(!n)return;let i=e.output();i!==r&&(r=i,t(i))},o=()=>{n&&(a(),n&&(i=this.clock.after(100,o)))};return i=this.clock.after(100,o),{flush:a,dispose:()=>{n=!1,i?.(),i=void 0}}}async race(t,n){let r=[],i=[t.completion().catch(e=>e)];i.push(new Promise(t=>{r.push(this.clock.after(e.getBackgroundThresholdMs(),()=>t(s)))})),n!==void 0&&n>0&&i.push(new Promise(e=>{r.push(this.clock.after(n,()=>e(c)))}));try{return await Promise.race(i)}finally{for(let e of r)e()}}async promote(e,t){return e.pid===void 0?{kind:u,id:e.id,name:e.name,error:`The command did not start`}:(await this.registry.markPromoted(e.id),e.detach(),e.completion().then(t=>this.registry.complete(e.id,{reason:t.signal?f:t.code===0?l:u,code:t.code,signal:t.signal}),()=>this.registry.complete(e.id,{reason:d,code:null,signal:null})).catch(t=>{let n=t instanceof Error?t.message:String(t);process.emitWarning(`Failed to complete runner ${e.id}: ${n}`)}),{kind:`promoted`,id:e.id,name:e.name,pid:e.pid,logPath:e.logPath,backend:e.backend,reason:t})}};m=i.default([(0,a.injectable)(),r.default(0,(0,a.inject)(t.TYPES.Launcher)),r.default(1,(0,a.inject)(t.TYPES.RmuxBackend)),r.default(2,(0,a.inject)(t.TYPES.PtyHost)),r.default(3,(0,a.inject)(t.TYPES.RunnerNamer)),r.default(4,(0,a.inject)(t.TYPES.RunnerRegistry)),r.default(5,(0,a.inject)(t.TYPES.Clock)),n.default(`design:paramtypes`,[Object,Object,Object,Object,Object,Object])],m),Object.defineProperty(exports,"BashRunService",{enumerable:!0,get:function(){return m}});
2
2
  //# sourceMappingURL=BashRunService.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"BashRunService.cjs","names":["randomBytes","getBackgroundThresholdMs","injectable","inject","TYPES"],"sources":["../../../src/services/BashRunService/BashRunService.ts"],"sourcesContent":["import { inject, injectable } from 'inversify';\nimport { randomBytes } from 'node:crypto';\nimport 'reflect-metadata/lite';\nimport { getBackgroundThresholdMs } from '../../config.ts';\nimport { TYPES } from '../../types.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILauncher, RunHandle } from '../Launcher/index.ts';\nimport type { IPtyHost } from '../PtyHost/index.ts';\nimport type { IRmuxBackend } from '../RmuxBackend/index.ts';\nimport type { IRunnerNamer } from '../RunnerNamer/index.ts';\nimport type { IRunnerRegistry } from '../RunnerRegistry/index.ts';\nimport type { BashRunRequest, BashRunResult, IBashRunService, PromotedRun } from './types.ts';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\nconst RUNNER_ID_RANDOM_BYTES = 3;\nconst OUTPUT_UPDATE_POLL_MS = 100;\nconst COMPLETED = 'completed';\nconst FAILED = 'failed';\nconst LAUNCHER_ERROR = 'launcher_error';\nconst SIGNALED = 'signaled';\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\n@injectable()\nexport class BashRunService implements IBashRunService {\n constructor(\n @inject(TYPES.Launcher) private readonly launcher: ILauncher,\n @inject(TYPES.RmuxBackend) private readonly rmuxBackend: IRmuxBackend,\n @inject(TYPES.PtyHost) private readonly ptyHost: IPtyHost,\n @inject(TYPES.RunnerNamer) private readonly namer: IRunnerNamer,\n @inject(TYPES.RunnerRegistry) private readonly registry: IRunnerRegistry,\n @inject(TYPES.Clock) private readonly clock: IClock,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? process.cwd();\n const interactive = request.interactive === true;\n\n let handle: RunHandle;\n try {\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n handle =\n rmuxHandle ??\n (interactive\n ? await this.ptyHost.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId })\n : this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId }));\n } catch (error) {\n // Loading node-pty is the one launch step that can fail before a process\n // exists, and it must not take the whole tool call down.\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive');\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold');\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n ): Promise<Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE> {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(handle: RunHandle, reason: PromotedRun['reason']): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n void handle\n .completion()\n .then(\n (outcome) =>\n this.registry.complete(handle.id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n }),\n () => this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null }),\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n });\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BashRunService.cjs","names":["randomBytes","getBackgroundThresholdMs","injectable","inject","TYPES"],"sources":["../../../src/services/BashRunService/BashRunService.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getBackgroundThresholdMs } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILauncher, RunHandle } from '../Launcher/index.ts';\nimport type { IPtyHost } from '../PtyHost/index.ts';\nimport type { IRmuxBackend } from '../RmuxBackend/index.ts';\nimport type { IRunnerNamer } from '../RunnerNamer/index.ts';\nimport type { IRunnerRegistry } from '../RunnerRegistry/index.ts';\nimport type { BashRunRequest, BashRunResult, IBashRunService, PromotedRun } from './types.ts';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\nconst RUNNER_ID_RANDOM_BYTES = 3;\nconst OUTPUT_UPDATE_POLL_MS = 100;\nconst COMPLETED = 'completed';\nconst FAILED = 'failed';\nconst LAUNCHER_ERROR = 'launcher_error';\nconst SIGNALED = 'signaled';\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\n@injectable()\nexport class BashRunService implements IBashRunService {\n constructor(\n @inject(TYPES.Launcher) private readonly launcher: ILauncher,\n @inject(TYPES.RmuxBackend) private readonly rmuxBackend: IRmuxBackend,\n @inject(TYPES.PtyHost) private readonly ptyHost: IPtyHost,\n @inject(TYPES.RunnerNamer) private readonly namer: IRunnerNamer,\n @inject(TYPES.RunnerRegistry) private readonly registry: IRunnerRegistry,\n @inject(TYPES.Clock) private readonly clock: IClock,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? process.cwd();\n const interactive = request.interactive === true;\n\n let handle: RunHandle;\n try {\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n handle =\n rmuxHandle ??\n (interactive\n ? await this.ptyHost.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId })\n : this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId }));\n } catch (error) {\n // Loading node-pty is the one launch step that can fail before a process\n // exists, and it must not take the whole tool call down.\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive');\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold');\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n ): Promise<Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE> {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(handle: RunHandle, reason: PromotedRun['reason']): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n void handle\n .completion()\n .then(\n (outcome) =>\n this.registry.complete(handle.id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n }),\n () => this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null }),\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n });\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{getBackgroundThresholdMs as e}from"../../config.mjs";import{TYPES as t}from"../../types.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{inject as a,injectable as o}from"inversify";import"reflect-metadata/lite";import{randomBytes as s}from"node:crypto";const c=Symbol(`threshold-reached`),l=Symbol(`deadline-reached`),u=`completed`,d=`failed`,f=`launcher_error`,p=`signaled`;function m(){return`${Date.now().toString(36)}-${s(3).toString(`base64url`)}`}let h=class{launcher;rmuxBackend;ptyHost;namer;registry;clock;constructor(e,t,n,r,i,a){this.launcher=e,this.rmuxBackend=t,this.ptyHost=n,this.namer=r,this.registry=i,this.clock=a}async run(e){let t=m(),n=await this.namer.allocate(e.command,e.sessionId,e.name),r=e.cwd??process.cwd(),i=e.interactive===!0,a;try{a=await this.rmuxBackend.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId,interactive:i})??(i?await this.ptyHost.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId}):this.launcher.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId}))}catch(e){return{kind:d,id:t,name:n,error:e instanceof Error?e.message:String(e)}}if(a.pid===void 0)return{kind:d,id:t,name:n,error:`The command did not start, so it cannot be supervised`};try{await this.registry.register({id:t,name:n,pid:a.pid,command:e.command,cwd:r,logPath:a.logPath,interactive:i,sessionId:e.sessionId,backend:a.backend,...a.backendTarget?{backendTarget:a.backendTarget}:{}})}catch(e){return await a.stop(),{kind:d,id:t,name:n,error:e instanceof Error?e.message:String(e)}}if(e.background===!0||i)return this.promote(a,e.background===!0?`requested`:`interactive`);let o=e.onOutput?this.followOutput(a,e.onOutput):void 0;try{let r=await this.race(a,e.timeoutMs);return o?.flush(),r===c?this.promote(a,`threshold`):r===l?(await a.stop(),o?.flush(),await this.registry.complete(t,{reason:`timed_out`,code:null,signal:`SIGTERM`}),{kind:u,id:t,name:n,output:a.output(),exitCode:null,signal:`SIGTERM`,logPath:a.logPath,backend:a.backend,timedOut:!0}):r instanceof Error?(await this.registry.complete(t,{reason:f,code:null,signal:null}),{kind:d,id:t,name:n,error:r.message}):(await this.registry.complete(t,{reason:r.signal?p:r.code===0?u:d,code:r.code,signal:r.signal}),{kind:u,id:t,name:n,output:a.output(),exitCode:r.code,signal:r.signal,logPath:a.logPath,backend:a.backend})}finally{o?.dispose()}}followOutput(e,t){let n=!0,r=``,i,a=()=>{if(!n)return;let i=e.output();i!==r&&(r=i,t(i))},o=()=>{n&&(a(),n&&(i=this.clock.after(100,o)))};return i=this.clock.after(100,o),{flush:a,dispose:()=>{n=!1,i?.(),i=void 0}}}async race(t,n){let r=[],i=[t.completion().catch(e=>e)];i.push(new Promise(t=>{r.push(this.clock.after(e(),()=>t(c)))})),n!==void 0&&n>0&&i.push(new Promise(e=>{r.push(this.clock.after(n,()=>e(l)))}));try{return await Promise.race(i)}finally{for(let e of r)e()}}async promote(e,t){return e.pid===void 0?{kind:d,id:e.id,name:e.name,error:`The command did not start`}:(await this.registry.markPromoted(e.id),e.detach(),e.completion().then(t=>this.registry.complete(e.id,{reason:t.signal?p:t.code===0?u:d,code:t.code,signal:t.signal}),()=>this.registry.complete(e.id,{reason:f,code:null,signal:null})).catch(t=>{let n=t instanceof Error?t.message:String(t);process.emitWarning(`Failed to complete runner ${e.id}: ${n}`)}),{kind:`promoted`,id:e.id,name:e.name,pid:e.pid,logPath:e.logPath,backend:e.backend,reason:t})}};h=i([o(),r(0,a(t.Launcher)),r(1,a(t.RmuxBackend)),r(2,a(t.PtyHost)),r(3,a(t.RunnerNamer)),r(4,a(t.RunnerRegistry)),r(5,a(t.Clock)),n(`design:paramtypes`,[Object,Object,Object,Object,Object,Object])],h);export{h as BashRunService};
1
+ import{getBackgroundThresholdMs as e}from"../../types/config.mjs";import{TYPES as t}from"../../types/index.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{inject as a,injectable as o}from"inversify";import"reflect-metadata/lite";import{randomBytes as s}from"node:crypto";const c=Symbol(`threshold-reached`),l=Symbol(`deadline-reached`),u=`completed`,d=`failed`,f=`launcher_error`,p=`signaled`;function m(){return`${Date.now().toString(36)}-${s(3).toString(`base64url`)}`}let h=class{launcher;rmuxBackend;ptyHost;namer;registry;clock;constructor(e,t,n,r,i,a){this.launcher=e,this.rmuxBackend=t,this.ptyHost=n,this.namer=r,this.registry=i,this.clock=a}async run(e){let t=m(),n=await this.namer.allocate(e.command,e.sessionId,e.name),r=e.cwd??process.cwd(),i=e.interactive===!0,a;try{a=await this.rmuxBackend.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId,interactive:i})??(i?await this.ptyHost.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId}):this.launcher.launch({id:t,name:n,command:e.command,cwd:r,sessionId:e.sessionId}))}catch(e){return{kind:d,id:t,name:n,error:e instanceof Error?e.message:String(e)}}if(a.pid===void 0)return{kind:d,id:t,name:n,error:`The command did not start, so it cannot be supervised`};try{await this.registry.register({id:t,name:n,pid:a.pid,command:e.command,cwd:r,logPath:a.logPath,interactive:i,sessionId:e.sessionId,backend:a.backend,...a.backendTarget?{backendTarget:a.backendTarget}:{}})}catch(e){return await a.stop(),{kind:d,id:t,name:n,error:e instanceof Error?e.message:String(e)}}if(e.background===!0||i)return this.promote(a,e.background===!0?`requested`:`interactive`);let o=e.onOutput?this.followOutput(a,e.onOutput):void 0;try{let r=await this.race(a,e.timeoutMs);return o?.flush(),r===c?this.promote(a,`threshold`):r===l?(await a.stop(),o?.flush(),await this.registry.complete(t,{reason:`timed_out`,code:null,signal:`SIGTERM`}),{kind:u,id:t,name:n,output:a.output(),exitCode:null,signal:`SIGTERM`,logPath:a.logPath,backend:a.backend,timedOut:!0}):r instanceof Error?(await this.registry.complete(t,{reason:f,code:null,signal:null}),{kind:d,id:t,name:n,error:r.message}):(await this.registry.complete(t,{reason:r.signal?p:r.code===0?u:d,code:r.code,signal:r.signal}),{kind:u,id:t,name:n,output:a.output(),exitCode:r.code,signal:r.signal,logPath:a.logPath,backend:a.backend})}finally{o?.dispose()}}followOutput(e,t){let n=!0,r=``,i,a=()=>{if(!n)return;let i=e.output();i!==r&&(r=i,t(i))},o=()=>{n&&(a(),n&&(i=this.clock.after(100,o)))};return i=this.clock.after(100,o),{flush:a,dispose:()=>{n=!1,i?.(),i=void 0}}}async race(t,n){let r=[],i=[t.completion().catch(e=>e)];i.push(new Promise(t=>{r.push(this.clock.after(e(),()=>t(c)))})),n!==void 0&&n>0&&i.push(new Promise(e=>{r.push(this.clock.after(n,()=>e(l)))}));try{return await Promise.race(i)}finally{for(let e of r)e()}}async promote(e,t){return e.pid===void 0?{kind:d,id:e.id,name:e.name,error:`The command did not start`}:(await this.registry.markPromoted(e.id),e.detach(),e.completion().then(t=>this.registry.complete(e.id,{reason:t.signal?p:t.code===0?u:d,code:t.code,signal:t.signal}),()=>this.registry.complete(e.id,{reason:f,code:null,signal:null})).catch(t=>{let n=t instanceof Error?t.message:String(t);process.emitWarning(`Failed to complete runner ${e.id}: ${n}`)}),{kind:`promoted`,id:e.id,name:e.name,pid:e.pid,logPath:e.logPath,backend:e.backend,reason:t})}};h=i([o(),r(0,a(t.Launcher)),r(1,a(t.RmuxBackend)),r(2,a(t.PtyHost)),r(3,a(t.RunnerNamer)),r(4,a(t.RunnerRegistry)),r(5,a(t.Clock)),n(`design:paramtypes`,[Object,Object,Object,Object,Object,Object])],h);export{h as BashRunService};
2
2
  //# sourceMappingURL=BashRunService.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"BashRunService.mjs","names":[],"sources":["../../../src/services/BashRunService/BashRunService.ts"],"sourcesContent":["import { inject, injectable } from 'inversify';\nimport { randomBytes } from 'node:crypto';\nimport 'reflect-metadata/lite';\nimport { getBackgroundThresholdMs } from '../../config.ts';\nimport { TYPES } from '../../types.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILauncher, RunHandle } from '../Launcher/index.ts';\nimport type { IPtyHost } from '../PtyHost/index.ts';\nimport type { IRmuxBackend } from '../RmuxBackend/index.ts';\nimport type { IRunnerNamer } from '../RunnerNamer/index.ts';\nimport type { IRunnerRegistry } from '../RunnerRegistry/index.ts';\nimport type { BashRunRequest, BashRunResult, IBashRunService, PromotedRun } from './types.ts';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\nconst RUNNER_ID_RANDOM_BYTES = 3;\nconst OUTPUT_UPDATE_POLL_MS = 100;\nconst COMPLETED = 'completed';\nconst FAILED = 'failed';\nconst LAUNCHER_ERROR = 'launcher_error';\nconst SIGNALED = 'signaled';\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\n@injectable()\nexport class BashRunService implements IBashRunService {\n constructor(\n @inject(TYPES.Launcher) private readonly launcher: ILauncher,\n @inject(TYPES.RmuxBackend) private readonly rmuxBackend: IRmuxBackend,\n @inject(TYPES.PtyHost) private readonly ptyHost: IPtyHost,\n @inject(TYPES.RunnerNamer) private readonly namer: IRunnerNamer,\n @inject(TYPES.RunnerRegistry) private readonly registry: IRunnerRegistry,\n @inject(TYPES.Clock) private readonly clock: IClock,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? process.cwd();\n const interactive = request.interactive === true;\n\n let handle: RunHandle;\n try {\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n handle =\n rmuxHandle ??\n (interactive\n ? await this.ptyHost.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId })\n : this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId }));\n } catch (error) {\n // Loading node-pty is the one launch step that can fail before a process\n // exists, and it must not take the whole tool call down.\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive');\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold');\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n ): Promise<Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE> {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(handle: RunHandle, reason: PromotedRun['reason']): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n void handle\n .completion()\n .then(\n (outcome) =>\n this.registry.complete(handle.id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n }),\n () => this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null }),\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n });\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BashRunService.mjs","names":[],"sources":["../../../src/services/BashRunService/BashRunService.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getBackgroundThresholdMs } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILauncher, RunHandle } from '../Launcher/index.ts';\nimport type { IPtyHost } from '../PtyHost/index.ts';\nimport type { IRmuxBackend } from '../RmuxBackend/index.ts';\nimport type { IRunnerNamer } from '../RunnerNamer/index.ts';\nimport type { IRunnerRegistry } from '../RunnerRegistry/index.ts';\nimport type { BashRunRequest, BashRunResult, IBashRunService, PromotedRun } from './types.ts';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\nconst RUNNER_ID_RANDOM_BYTES = 3;\nconst OUTPUT_UPDATE_POLL_MS = 100;\nconst COMPLETED = 'completed';\nconst FAILED = 'failed';\nconst LAUNCHER_ERROR = 'launcher_error';\nconst SIGNALED = 'signaled';\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\n@injectable()\nexport class BashRunService implements IBashRunService {\n constructor(\n @inject(TYPES.Launcher) private readonly launcher: ILauncher,\n @inject(TYPES.RmuxBackend) private readonly rmuxBackend: IRmuxBackend,\n @inject(TYPES.PtyHost) private readonly ptyHost: IPtyHost,\n @inject(TYPES.RunnerNamer) private readonly namer: IRunnerNamer,\n @inject(TYPES.RunnerRegistry) private readonly registry: IRunnerRegistry,\n @inject(TYPES.Clock) private readonly clock: IClock,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? process.cwd();\n const interactive = request.interactive === true;\n\n let handle: RunHandle;\n try {\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n handle =\n rmuxHandle ??\n (interactive\n ? await this.ptyHost.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId })\n : this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId }));\n } catch (error) {\n // Loading node-pty is the one launch step that can fail before a process\n // exists, and it must not take the whole tool call down.\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive');\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold');\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n ): Promise<Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE> {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(handle: RunHandle, reason: PromotedRun['reason']): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n void handle\n .completion()\n .then(\n (outcome) =>\n this.registry.complete(handle.id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n }),\n () => this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null }),\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n });\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../config.cjs"),n=require("../../types.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),a=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs"),o=require("../../runnerSpec.cjs"),s=require("../../session.cjs");let c=require("inversify");require("reflect-metadata/lite");let l=require("node:fs");l=e.__toESM(l,1);const u={FORCE_COLOR:`1`,CI:`1`};let d=class{spawner;processControl;logFile;clock;paths;constructor(e,t,n,r,i){this.spawner=e,this.processControl=t,this.logFile=n,this.clock=r,this.paths=i}launch(e){let n=this.logFile.open(e.id),r=t.getResultMaxBytes()*2,i=``,a=!0,c=!1,d=()=>void 0,p=()=>void 0,m=new Promise((e,t)=>{d=e,p=t});this.paths.ensureDirectories(e.sessionId);let h=o.supervisorPaths(this.paths.stateDirectory(e.sessionId),e.id),g={...process.env,...u,...o.NO_TERMINAL_INPUT_ENV,[s.PI_SESSION_ID_ENV]:e.sessionId};o.cleanupSupervisorFiles(h),o.writeCommandSpec(h.spec,{command:e.command,cwd:e.cwd,env:f(g)}),l.default.writeFileSync(h.gate,``,{mode:384});let _=this.spawner.spawn({command:o.supervisorCommand(h),cwd:e.cwd,env:g,detached:!0});return _.onOutput(e=>{n.append(e),a&&(i+=e,i.length>r&&(i=i.slice(-r)))}),_.onError(e=>{c||(c=!0,n.close(),o.cleanupSupervisorFiles(h),p(e))}),_.onExit(e=>{if(c)return;c=!0,n.close();let t=o.readExitMetadata(h.exit)??e;o.cleanupSupervisorFiles(h),d(t)}),_.unref(),{id:e.id,name:e.name,pid:_.pid,logPath:n.path,backend:`native`,output:()=>i,completion:()=>m,detach:()=>{a=!1,i=``},stop:async()=>_.pid!==void 0&&this.stop(_.pid)}}async stop(e){if(!this.processControl.isAlive(e))return!1;this.processControl.signalGroup(e,`SIGTERM`);let t=this.clock.now()+2e3;for(;this.clock.now()<t;){if(!this.processControl.isAlive(e))return!0;await this.sleep(100)}return this.processControl.signalGroup(e,`SIGKILL`),!0}sleep(e){return new Promise(t=>{this.clock.after(e,t)})}};d=a.default([(0,c.injectable)(),i.default(0,(0,c.inject)(n.TYPES.Spawner)),i.default(1,(0,c.inject)(n.TYPES.ProcessControl)),i.default(2,(0,c.inject)(n.TYPES.LogFile)),i.default(3,(0,c.inject)(n.TYPES.Clock)),i.default(4,(0,c.inject)(n.TYPES.RunnerPaths)),r.default(`design:paramtypes`,[Object,Object,Object,Object,Object])],d);function f(e){return Object.fromEntries(Object.entries(e).filter(e=>e[1]!==void 0))}Object.defineProperty(exports,"Launcher",{enumerable:!0,get:function(){return d}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../types/config.cjs"),n=require("../../types/index.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),a=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs"),o=require("../../schemas/runnerSpec.cjs"),s=require("../runs/session.cjs");let c=require("inversify");require("reflect-metadata/lite");let l=require("node:fs");l=e.__toESM(l,1);const u={FORCE_COLOR:`1`,CI:`1`};let d=class{spawner;processControl;logFile;clock;paths;constructor(e,t,n,r,i){this.spawner=e,this.processControl=t,this.logFile=n,this.clock=r,this.paths=i}launch(e){let n=this.logFile.open(e.id),r=t.getResultMaxBytes()*2,i=``,a=!0,c=!1,d=()=>void 0,p=()=>void 0,m=new Promise((e,t)=>{d=e,p=t});this.paths.ensureDirectories(e.sessionId);let h=o.supervisorPaths(this.paths.stateDirectory(e.sessionId),e.id),g={...process.env,...u,...o.NO_TERMINAL_INPUT_ENV,[s.PI_SESSION_ID_ENV]:e.sessionId};o.cleanupSupervisorFiles(h),o.writeCommandSpec(h.spec,{command:e.command,cwd:e.cwd,env:f(g)}),l.default.writeFileSync(h.gate,``,{mode:384});let _=this.spawner.spawn({command:o.supervisorCommand(h),cwd:e.cwd,env:g,detached:!0});return _.onOutput(e=>{n.append(e),a&&(i+=e,i.length>r&&(i=i.slice(-r)))}),_.onError(e=>{c||(c=!0,n.close(),o.cleanupSupervisorFiles(h),p(e))}),_.onExit(e=>{if(c)return;c=!0,n.close();let t=o.readExitMetadata(h.exit)??e;o.cleanupSupervisorFiles(h),d(t)}),_.unref(),{id:e.id,name:e.name,pid:_.pid,logPath:n.path,backend:`native`,output:()=>i,completion:()=>m,detach:()=>{a=!1,i=``},stop:async()=>_.pid!==void 0&&this.stop(_.pid)}}async stop(e){if(!this.processControl.isAlive(e))return!1;this.processControl.signalGroup(e,`SIGTERM`);let t=this.clock.now()+2e3;for(;this.clock.now()<t;){if(!this.processControl.isAlive(e))return!0;await this.sleep(100)}return this.processControl.signalGroup(e,`SIGKILL`),!0}sleep(e){return new Promise(t=>{this.clock.after(e,t)})}};d=a.default([(0,c.injectable)(),i.default(0,(0,c.inject)(n.TYPES.Spawner)),i.default(1,(0,c.inject)(n.TYPES.ProcessControl)),i.default(2,(0,c.inject)(n.TYPES.LogFile)),i.default(3,(0,c.inject)(n.TYPES.Clock)),i.default(4,(0,c.inject)(n.TYPES.RunnerPaths)),r.default(`design:paramtypes`,[Object,Object,Object,Object,Object])],d);function f(e){return Object.fromEntries(Object.entries(e).filter(e=>e[1]!==void 0))}Object.defineProperty(exports,"Launcher",{enumerable:!0,get:function(){return d}});
2
2
  //# sourceMappingURL=Launcher.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Launcher.cjs","names":["getResultMaxBytes","supervisorPaths","NO_TERMINAL_INPUT_ENV","PI_SESSION_ID_ENV","supervisorCommand","readExitMetadata","injectable","inject","TYPES"],"sources":["../../../src/services/Launcher/Launcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getResultMaxBytes } from '../../config.ts';\nimport {\n NO_TERMINAL_INPUT_ENV,\n cleanupSupervisorFiles,\n readExitMetadata,\n supervisorCommand,\n supervisorPaths,\n writeCommandSpec,\n} from '../../runnerSpec.ts';\nimport { TYPES } from '../../types.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILogFile } from '../LogFile/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { ExitResult, ISpawner } from '../Spawner/index.ts';\nimport type { ILauncher, LaunchRequest, RunHandle } from './types.ts';\nimport { PI_SESSION_ID_ENV } from '../../session.ts';\n\n/**\n * Prompts belong on a screen, not in a log file, so `CI` stays set.\n *\n * Colour is kept: the log is replayed into the transcript, and the scrub keeps\n * SGR while still removing the cursor moves that made raw output unreadable.\n */\nconst NON_INTERACTIVE_ENV = { FORCE_COLOR: '1', CI: '1' };\nconst TERM_GRACE_MS = 2_000;\nconst LIVENESS_POLL_MS = 100;\n/** In-memory output is a multiple of the result ceiling; the log holds the rest. */\nconst MEMORY_BUFFER_FACTOR = 2;\nconst GATE_MODE = 0o600;\n\n@injectable()\nexport class Launcher implements ILauncher {\n constructor(\n @inject(TYPES.Spawner) private readonly spawner: ISpawner,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.LogFile) private readonly logFile: ILogFile,\n @inject(TYPES.Clock) private readonly clock: IClock,\n @inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths,\n ) {}\n\n launch(request: LaunchRequest): RunHandle {\n const writer = this.logFile.open(request.id);\n const memoryLimit = getResultMaxBytes() * MEMORY_BUFFER_FACTOR;\n\n let buffer = '';\n let buffering = true;\n let settled = false;\n let resolveCompletion: (result: ExitResult) => void = () => undefined;\n let rejectCompletion: (error: Error) => void = () => undefined;\n const completion = new Promise<ExitResult>((resolve, reject) => {\n resolveCompletion = resolve;\n rejectCompletion = reject;\n });\n\n this.paths.ensureDirectories(request.sessionId);\n const supervisor = supervisorPaths(this.paths.stateDirectory(request.sessionId), request.id);\n const env = {\n ...process.env,\n ...NON_INTERACTIVE_ENV,\n ...NO_TERMINAL_INPUT_ENV,\n [PI_SESSION_ID_ENV]: request.sessionId,\n };\n cleanupSupervisorFiles(supervisor);\n writeCommandSpec(supervisor.spec, { command: request.command, cwd: request.cwd, env: defined(env) });\n // Nothing has to be attached before this runner starts, so its gate opens at once.\n fs.writeFileSync(supervisor.gate, '', { mode: GATE_MODE });\n\n const child = this.spawner.spawn({\n // runnerHost supervises the command: it forwards signals and, if the owning\n // pi dies without running its shutdown handler, kills the group itself.\n command: supervisorCommand(supervisor),\n cwd: request.cwd,\n env,\n // Its own process group, so stopping a runner takes the whole tree with it.\n detached: true,\n });\n\n child.onOutput((chunk) => {\n writer.append(chunk);\n if (!buffering) return;\n buffer += chunk;\n if (buffer.length > memoryLimit) buffer = buffer.slice(-memoryLimit);\n });\n\n child.onError((error) => {\n if (settled) return;\n settled = true;\n writer.close();\n cleanupSupervisorFiles(supervisor);\n rejectCompletion(error);\n });\n\n child.onExit((result) => {\n if (settled) return;\n settled = true;\n writer.close();\n // The supervisor collapses a signalled command into an exit code, so its\n // own sidecar is the only place the original signal survives.\n const outcome = readExitMetadata(supervisor.exit) ?? result;\n cleanupSupervisorFiles(supervisor);\n resolveCompletion(outcome);\n });\n\n // The launcher must never hold the pi process open on a runner's behalf.\n child.unref();\n\n const stop = async (): Promise<boolean> => (child.pid === undefined ? false : this.stop(child.pid));\n\n return {\n id: request.id,\n name: request.name,\n pid: child.pid,\n logPath: writer.path,\n backend: 'native',\n output: () => buffer,\n completion: () => completion,\n detach: () => {\n buffering = false;\n buffer = '';\n },\n stop,\n };\n }\n\n async stop(pid: number): Promise<boolean> {\n if (!this.processControl.isAlive(pid)) return false;\n\n this.processControl.signalGroup(pid, 'SIGTERM');\n const deadline = this.clock.now() + TERM_GRACE_MS;\n while (this.clock.now() < deadline) {\n if (!this.processControl.isAlive(pid)) return true;\n await this.sleep(LIVENESS_POLL_MS);\n }\n\n this.processControl.signalGroup(pid, 'SIGKILL');\n return true;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n this.clock.after(ms, resolve);\n });\n }\n}\n\n/** The spec is JSON, which has no way to carry an unset variable. */\nfunction defined(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Launcher.cjs","names":["getResultMaxBytes","supervisorPaths","NO_TERMINAL_INPUT_ENV","PI_SESSION_ID_ENV","supervisorCommand","readExitMetadata","injectable","inject","TYPES"],"sources":["../../../src/services/Launcher/Launcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport {\n cleanupSupervisorFiles,\n NO_TERMINAL_INPUT_ENV,\n readExitMetadata,\n supervisorCommand,\n supervisorPaths,\n writeCommandSpec,\n} from '../../schemas/runnerSpec.ts';\nimport { getResultMaxBytes } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILogFile } from '../LogFile/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport { PI_SESSION_ID_ENV } from '../runs/session.ts';\nimport type { ExitResult, ISpawner } from '../Spawner/index.ts';\nimport type { ILauncher, LaunchRequest, RunHandle } from './types.ts';\n\n/**\n * Prompts belong on a screen, not in a log file, so `CI` stays set.\n *\n * Colour is kept: the log is replayed into the transcript, and the scrub keeps\n * SGR while still removing the cursor moves that made raw output unreadable.\n */\nconst NON_INTERACTIVE_ENV = { FORCE_COLOR: '1', CI: '1' };\nconst TERM_GRACE_MS = 2_000;\nconst LIVENESS_POLL_MS = 100;\n/** In-memory output is a multiple of the result ceiling; the log holds the rest. */\nconst MEMORY_BUFFER_FACTOR = 2;\nconst GATE_MODE = 0o600;\n\n@injectable()\nexport class Launcher implements ILauncher {\n constructor(\n @inject(TYPES.Spawner) private readonly spawner: ISpawner,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.LogFile) private readonly logFile: ILogFile,\n @inject(TYPES.Clock) private readonly clock: IClock,\n @inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths,\n ) {}\n\n launch(request: LaunchRequest): RunHandle {\n const writer = this.logFile.open(request.id);\n const memoryLimit = getResultMaxBytes() * MEMORY_BUFFER_FACTOR;\n\n let buffer = '';\n let buffering = true;\n let settled = false;\n let resolveCompletion: (result: ExitResult) => void = () => undefined;\n let rejectCompletion: (error: Error) => void = () => undefined;\n const completion = new Promise<ExitResult>((resolve, reject) => {\n resolveCompletion = resolve;\n rejectCompletion = reject;\n });\n\n this.paths.ensureDirectories(request.sessionId);\n const supervisor = supervisorPaths(this.paths.stateDirectory(request.sessionId), request.id);\n const env = {\n ...process.env,\n ...NON_INTERACTIVE_ENV,\n ...NO_TERMINAL_INPUT_ENV,\n [PI_SESSION_ID_ENV]: request.sessionId,\n };\n cleanupSupervisorFiles(supervisor);\n writeCommandSpec(supervisor.spec, { command: request.command, cwd: request.cwd, env: defined(env) });\n // Nothing has to be attached before this runner starts, so its gate opens at once.\n fs.writeFileSync(supervisor.gate, '', { mode: GATE_MODE });\n\n const child = this.spawner.spawn({\n // runnerHost supervises the command: it forwards signals and, if the owning\n // pi dies without running its shutdown handler, kills the group itself.\n command: supervisorCommand(supervisor),\n cwd: request.cwd,\n env,\n // Its own process group, so stopping a runner takes the whole tree with it.\n detached: true,\n });\n\n child.onOutput((chunk) => {\n writer.append(chunk);\n if (!buffering) return;\n buffer += chunk;\n if (buffer.length > memoryLimit) buffer = buffer.slice(-memoryLimit);\n });\n\n child.onError((error) => {\n if (settled) return;\n settled = true;\n writer.close();\n cleanupSupervisorFiles(supervisor);\n rejectCompletion(error);\n });\n\n child.onExit((result) => {\n if (settled) return;\n settled = true;\n writer.close();\n // The supervisor collapses a signalled command into an exit code, so its\n // own sidecar is the only place the original signal survives.\n const outcome = readExitMetadata(supervisor.exit) ?? result;\n cleanupSupervisorFiles(supervisor);\n resolveCompletion(outcome);\n });\n\n // The launcher must never hold the pi process open on a runner's behalf.\n child.unref();\n\n const stop = async (): Promise<boolean> => (child.pid === undefined ? false : this.stop(child.pid));\n\n return {\n id: request.id,\n name: request.name,\n pid: child.pid,\n logPath: writer.path,\n backend: 'native',\n output: () => buffer,\n completion: () => completion,\n detach: () => {\n buffering = false;\n buffer = '';\n },\n stop,\n };\n }\n\n async stop(pid: number): Promise<boolean> {\n if (!this.processControl.isAlive(pid)) return false;\n\n this.processControl.signalGroup(pid, 'SIGTERM');\n const deadline = this.clock.now() + TERM_GRACE_MS;\n while (this.clock.now() < deadline) {\n if (!this.processControl.isAlive(pid)) return true;\n await this.sleep(LIVENESS_POLL_MS);\n }\n\n this.processControl.signalGroup(pid, 'SIGKILL');\n return true;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n this.clock.after(ms, resolve);\n });\n }\n}\n\n/** The spec is JSON, which has no way to carry an unset variable. */\nfunction defined(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined));\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{getResultMaxBytes as e}from"../../config.mjs";import{TYPES as t}from"../../types.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{NO_TERMINAL_INPUT_ENV as a,cleanupSupervisorFiles as o,readExitMetadata as s,supervisorCommand as c,supervisorPaths as l,writeCommandSpec as u}from"../../runnerSpec.mjs";import{PI_SESSION_ID_ENV as d}from"../../session.mjs";import{inject as f,injectable as p}from"inversify";import"reflect-metadata/lite";import m from"node:fs";const h={FORCE_COLOR:`1`,CI:`1`};let g=class{spawner;processControl;logFile;clock;paths;constructor(e,t,n,r,i){this.spawner=e,this.processControl=t,this.logFile=n,this.clock=r,this.paths=i}launch(t){let n=this.logFile.open(t.id),r=e()*2,i=``,f=!0,p=!1,g=()=>void 0,v=()=>void 0,y=new Promise((e,t)=>{g=e,v=t});this.paths.ensureDirectories(t.sessionId);let b=l(this.paths.stateDirectory(t.sessionId),t.id),x={...process.env,...h,...a,[d]:t.sessionId};o(b),u(b.spec,{command:t.command,cwd:t.cwd,env:_(x)}),m.writeFileSync(b.gate,``,{mode:384});let S=this.spawner.spawn({command:c(b),cwd:t.cwd,env:x,detached:!0});return S.onOutput(e=>{n.append(e),f&&(i+=e,i.length>r&&(i=i.slice(-r)))}),S.onError(e=>{p||(p=!0,n.close(),o(b),v(e))}),S.onExit(e=>{if(p)return;p=!0,n.close();let t=s(b.exit)??e;o(b),g(t)}),S.unref(),{id:t.id,name:t.name,pid:S.pid,logPath:n.path,backend:`native`,output:()=>i,completion:()=>y,detach:()=>{f=!1,i=``},stop:async()=>S.pid!==void 0&&this.stop(S.pid)}}async stop(e){if(!this.processControl.isAlive(e))return!1;this.processControl.signalGroup(e,`SIGTERM`);let t=this.clock.now()+2e3;for(;this.clock.now()<t;){if(!this.processControl.isAlive(e))return!0;await this.sleep(100)}return this.processControl.signalGroup(e,`SIGKILL`),!0}sleep(e){return new Promise(t=>{this.clock.after(e,t)})}};g=i([p(),r(0,f(t.Spawner)),r(1,f(t.ProcessControl)),r(2,f(t.LogFile)),r(3,f(t.Clock)),r(4,f(t.RunnerPaths)),n(`design:paramtypes`,[Object,Object,Object,Object,Object])],g);function _(e){return Object.fromEntries(Object.entries(e).filter(e=>e[1]!==void 0))}export{g as Launcher};
1
+ import{getResultMaxBytes as e}from"../../types/config.mjs";import{TYPES as t}from"../../types/index.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{NO_TERMINAL_INPUT_ENV as a,cleanupSupervisorFiles as o,readExitMetadata as s,supervisorCommand as c,supervisorPaths as l,writeCommandSpec as u}from"../../schemas/runnerSpec.mjs";import{PI_SESSION_ID_ENV as d}from"../runs/session.mjs";import{inject as f,injectable as p}from"inversify";import"reflect-metadata/lite";import m from"node:fs";const h={FORCE_COLOR:`1`,CI:`1`};let g=class{spawner;processControl;logFile;clock;paths;constructor(e,t,n,r,i){this.spawner=e,this.processControl=t,this.logFile=n,this.clock=r,this.paths=i}launch(t){let n=this.logFile.open(t.id),r=e()*2,i=``,f=!0,p=!1,g=()=>void 0,v=()=>void 0,y=new Promise((e,t)=>{g=e,v=t});this.paths.ensureDirectories(t.sessionId);let b=l(this.paths.stateDirectory(t.sessionId),t.id),x={...process.env,...h,...a,[d]:t.sessionId};o(b),u(b.spec,{command:t.command,cwd:t.cwd,env:_(x)}),m.writeFileSync(b.gate,``,{mode:384});let S=this.spawner.spawn({command:c(b),cwd:t.cwd,env:x,detached:!0});return S.onOutput(e=>{n.append(e),f&&(i+=e,i.length>r&&(i=i.slice(-r)))}),S.onError(e=>{p||(p=!0,n.close(),o(b),v(e))}),S.onExit(e=>{if(p)return;p=!0,n.close();let t=s(b.exit)??e;o(b),g(t)}),S.unref(),{id:t.id,name:t.name,pid:S.pid,logPath:n.path,backend:`native`,output:()=>i,completion:()=>y,detach:()=>{f=!1,i=``},stop:async()=>S.pid!==void 0&&this.stop(S.pid)}}async stop(e){if(!this.processControl.isAlive(e))return!1;this.processControl.signalGroup(e,`SIGTERM`);let t=this.clock.now()+2e3;for(;this.clock.now()<t;){if(!this.processControl.isAlive(e))return!0;await this.sleep(100)}return this.processControl.signalGroup(e,`SIGKILL`),!0}sleep(e){return new Promise(t=>{this.clock.after(e,t)})}};g=i([p(),r(0,f(t.Spawner)),r(1,f(t.ProcessControl)),r(2,f(t.LogFile)),r(3,f(t.Clock)),r(4,f(t.RunnerPaths)),n(`design:paramtypes`,[Object,Object,Object,Object,Object])],g);function _(e){return Object.fromEntries(Object.entries(e).filter(e=>e[1]!==void 0))}export{g as Launcher};
2
2
  //# sourceMappingURL=Launcher.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Launcher.mjs","names":[],"sources":["../../../src/services/Launcher/Launcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getResultMaxBytes } from '../../config.ts';\nimport {\n NO_TERMINAL_INPUT_ENV,\n cleanupSupervisorFiles,\n readExitMetadata,\n supervisorCommand,\n supervisorPaths,\n writeCommandSpec,\n} from '../../runnerSpec.ts';\nimport { TYPES } from '../../types.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILogFile } from '../LogFile/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { ExitResult, ISpawner } from '../Spawner/index.ts';\nimport type { ILauncher, LaunchRequest, RunHandle } from './types.ts';\nimport { PI_SESSION_ID_ENV } from '../../session.ts';\n\n/**\n * Prompts belong on a screen, not in a log file, so `CI` stays set.\n *\n * Colour is kept: the log is replayed into the transcript, and the scrub keeps\n * SGR while still removing the cursor moves that made raw output unreadable.\n */\nconst NON_INTERACTIVE_ENV = { FORCE_COLOR: '1', CI: '1' };\nconst TERM_GRACE_MS = 2_000;\nconst LIVENESS_POLL_MS = 100;\n/** In-memory output is a multiple of the result ceiling; the log holds the rest. */\nconst MEMORY_BUFFER_FACTOR = 2;\nconst GATE_MODE = 0o600;\n\n@injectable()\nexport class Launcher implements ILauncher {\n constructor(\n @inject(TYPES.Spawner) private readonly spawner: ISpawner,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.LogFile) private readonly logFile: ILogFile,\n @inject(TYPES.Clock) private readonly clock: IClock,\n @inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths,\n ) {}\n\n launch(request: LaunchRequest): RunHandle {\n const writer = this.logFile.open(request.id);\n const memoryLimit = getResultMaxBytes() * MEMORY_BUFFER_FACTOR;\n\n let buffer = '';\n let buffering = true;\n let settled = false;\n let resolveCompletion: (result: ExitResult) => void = () => undefined;\n let rejectCompletion: (error: Error) => void = () => undefined;\n const completion = new Promise<ExitResult>((resolve, reject) => {\n resolveCompletion = resolve;\n rejectCompletion = reject;\n });\n\n this.paths.ensureDirectories(request.sessionId);\n const supervisor = supervisorPaths(this.paths.stateDirectory(request.sessionId), request.id);\n const env = {\n ...process.env,\n ...NON_INTERACTIVE_ENV,\n ...NO_TERMINAL_INPUT_ENV,\n [PI_SESSION_ID_ENV]: request.sessionId,\n };\n cleanupSupervisorFiles(supervisor);\n writeCommandSpec(supervisor.spec, { command: request.command, cwd: request.cwd, env: defined(env) });\n // Nothing has to be attached before this runner starts, so its gate opens at once.\n fs.writeFileSync(supervisor.gate, '', { mode: GATE_MODE });\n\n const child = this.spawner.spawn({\n // runnerHost supervises the command: it forwards signals and, if the owning\n // pi dies without running its shutdown handler, kills the group itself.\n command: supervisorCommand(supervisor),\n cwd: request.cwd,\n env,\n // Its own process group, so stopping a runner takes the whole tree with it.\n detached: true,\n });\n\n child.onOutput((chunk) => {\n writer.append(chunk);\n if (!buffering) return;\n buffer += chunk;\n if (buffer.length > memoryLimit) buffer = buffer.slice(-memoryLimit);\n });\n\n child.onError((error) => {\n if (settled) return;\n settled = true;\n writer.close();\n cleanupSupervisorFiles(supervisor);\n rejectCompletion(error);\n });\n\n child.onExit((result) => {\n if (settled) return;\n settled = true;\n writer.close();\n // The supervisor collapses a signalled command into an exit code, so its\n // own sidecar is the only place the original signal survives.\n const outcome = readExitMetadata(supervisor.exit) ?? result;\n cleanupSupervisorFiles(supervisor);\n resolveCompletion(outcome);\n });\n\n // The launcher must never hold the pi process open on a runner's behalf.\n child.unref();\n\n const stop = async (): Promise<boolean> => (child.pid === undefined ? false : this.stop(child.pid));\n\n return {\n id: request.id,\n name: request.name,\n pid: child.pid,\n logPath: writer.path,\n backend: 'native',\n output: () => buffer,\n completion: () => completion,\n detach: () => {\n buffering = false;\n buffer = '';\n },\n stop,\n };\n }\n\n async stop(pid: number): Promise<boolean> {\n if (!this.processControl.isAlive(pid)) return false;\n\n this.processControl.signalGroup(pid, 'SIGTERM');\n const deadline = this.clock.now() + TERM_GRACE_MS;\n while (this.clock.now() < deadline) {\n if (!this.processControl.isAlive(pid)) return true;\n await this.sleep(LIVENESS_POLL_MS);\n }\n\n this.processControl.signalGroup(pid, 'SIGKILL');\n return true;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n this.clock.after(ms, resolve);\n });\n }\n}\n\n/** The spec is JSON, which has no way to carry an unset variable. */\nfunction defined(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Launcher.mjs","names":[],"sources":["../../../src/services/Launcher/Launcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport {\n cleanupSupervisorFiles,\n NO_TERMINAL_INPUT_ENV,\n readExitMetadata,\n supervisorCommand,\n supervisorPaths,\n writeCommandSpec,\n} from '../../schemas/runnerSpec.ts';\nimport { getResultMaxBytes } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILogFile } from '../LogFile/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport { PI_SESSION_ID_ENV } from '../runs/session.ts';\nimport type { ExitResult, ISpawner } from '../Spawner/index.ts';\nimport type { ILauncher, LaunchRequest, RunHandle } from './types.ts';\n\n/**\n * Prompts belong on a screen, not in a log file, so `CI` stays set.\n *\n * Colour is kept: the log is replayed into the transcript, and the scrub keeps\n * SGR while still removing the cursor moves that made raw output unreadable.\n */\nconst NON_INTERACTIVE_ENV = { FORCE_COLOR: '1', CI: '1' };\nconst TERM_GRACE_MS = 2_000;\nconst LIVENESS_POLL_MS = 100;\n/** In-memory output is a multiple of the result ceiling; the log holds the rest. */\nconst MEMORY_BUFFER_FACTOR = 2;\nconst GATE_MODE = 0o600;\n\n@injectable()\nexport class Launcher implements ILauncher {\n constructor(\n @inject(TYPES.Spawner) private readonly spawner: ISpawner,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.LogFile) private readonly logFile: ILogFile,\n @inject(TYPES.Clock) private readonly clock: IClock,\n @inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths,\n ) {}\n\n launch(request: LaunchRequest): RunHandle {\n const writer = this.logFile.open(request.id);\n const memoryLimit = getResultMaxBytes() * MEMORY_BUFFER_FACTOR;\n\n let buffer = '';\n let buffering = true;\n let settled = false;\n let resolveCompletion: (result: ExitResult) => void = () => undefined;\n let rejectCompletion: (error: Error) => void = () => undefined;\n const completion = new Promise<ExitResult>((resolve, reject) => {\n resolveCompletion = resolve;\n rejectCompletion = reject;\n });\n\n this.paths.ensureDirectories(request.sessionId);\n const supervisor = supervisorPaths(this.paths.stateDirectory(request.sessionId), request.id);\n const env = {\n ...process.env,\n ...NON_INTERACTIVE_ENV,\n ...NO_TERMINAL_INPUT_ENV,\n [PI_SESSION_ID_ENV]: request.sessionId,\n };\n cleanupSupervisorFiles(supervisor);\n writeCommandSpec(supervisor.spec, { command: request.command, cwd: request.cwd, env: defined(env) });\n // Nothing has to be attached before this runner starts, so its gate opens at once.\n fs.writeFileSync(supervisor.gate, '', { mode: GATE_MODE });\n\n const child = this.spawner.spawn({\n // runnerHost supervises the command: it forwards signals and, if the owning\n // pi dies without running its shutdown handler, kills the group itself.\n command: supervisorCommand(supervisor),\n cwd: request.cwd,\n env,\n // Its own process group, so stopping a runner takes the whole tree with it.\n detached: true,\n });\n\n child.onOutput((chunk) => {\n writer.append(chunk);\n if (!buffering) return;\n buffer += chunk;\n if (buffer.length > memoryLimit) buffer = buffer.slice(-memoryLimit);\n });\n\n child.onError((error) => {\n if (settled) return;\n settled = true;\n writer.close();\n cleanupSupervisorFiles(supervisor);\n rejectCompletion(error);\n });\n\n child.onExit((result) => {\n if (settled) return;\n settled = true;\n writer.close();\n // The supervisor collapses a signalled command into an exit code, so its\n // own sidecar is the only place the original signal survives.\n const outcome = readExitMetadata(supervisor.exit) ?? result;\n cleanupSupervisorFiles(supervisor);\n resolveCompletion(outcome);\n });\n\n // The launcher must never hold the pi process open on a runner's behalf.\n child.unref();\n\n const stop = async (): Promise<boolean> => (child.pid === undefined ? false : this.stop(child.pid));\n\n return {\n id: request.id,\n name: request.name,\n pid: child.pid,\n logPath: writer.path,\n backend: 'native',\n output: () => buffer,\n completion: () => completion,\n detach: () => {\n buffering = false;\n buffer = '';\n },\n stop,\n };\n }\n\n async stop(pid: number): Promise<boolean> {\n if (!this.processControl.isAlive(pid)) return false;\n\n this.processControl.signalGroup(pid, 'SIGTERM');\n const deadline = this.clock.now() + TERM_GRACE_MS;\n while (this.clock.now() < deadline) {\n if (!this.processControl.isAlive(pid)) return true;\n await this.sleep(LIVENESS_POLL_MS);\n }\n\n this.processControl.signalGroup(pid, 'SIGKILL');\n return true;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n this.clock.after(ms, resolve);\n });\n }\n}\n\n/** The spec is JSON, which has no way to carry an unset variable. */\nfunction defined(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined));\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../types.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs"),a=require("./client.cjs");let o=require("inversify");require("reflect-metadata/lite");let s=require("node:crypto"),c=require("node:fs");c=e.__toESM(c,1);let l=require("node:path");l=e.__toESM(l,1);let u=require("node:net");u=e.__toESM(u,1);let d=require("node:os");d=e.__toESM(d,1);let f=class{paths;server;target;connections=new Set;constructor(e){this.paths=e}async arm(e){if(this.target)return this.target;try{this.paths.ensureDirectories(e);let t=this.socketPath(e);c.default.rmSync(t,{force:!0});let n=u.default.createServer(e=>{e.unref(),e.resume(),this.connections.add(e),e.on(`close`,()=>this.connections.delete(e))});return await p(n,t),c.default.chmodSync(t,384),n.unref(),this.server=n,this.target=t,process.env[a.LIFELINE_ENV]=t,t}catch(e){process.emitWarning(`Could not arm the doom-runner lifeline: ${String(e)}`);return}}path(){return this.target}dispose(){for(let e of this.connections)e.destroy();if(this.connections.clear(),this.server){try{this.server.close()}catch(e){process.emitWarning(`Could not close the doom-runner lifeline: ${String(e)}`)}this.server=void 0}if(this.target){try{c.default.rmSync(this.target,{force:!0})}catch(e){process.emitWarning(`Could not remove the doom-runner lifeline: ${String(e)}`)}this.target=void 0}process.env.DOOM_RUNNER_LIFELINE&&delete process.env[a.LIFELINE_ENV]}socketPath(e){let t=l.default.join(this.paths.stateDirectory(e),`lifeline.sock`);if(Buffer.byteLength(t)<=100)return t;let n=(0,s.createHash)(`sha256`).update(t).digest(`hex`).slice(0,12);return l.default.join(d.default.tmpdir(),`doom-runner-${n}.sock`)}};f=i.default([(0,o.injectable)(),r.default(0,(0,o.inject)(t.TYPES.RunnerPaths)),n.default(`design:paramtypes`,[Object])],f);function p(e,t){return new Promise((n,r)=>{e.once(`error`,r),e.listen(t,()=>{e.removeListener(`error`,r),e.on(`error`,e=>process.emitWarning(`doom-runner lifeline failed: ${e.message}`)),n()})})}Object.defineProperty(exports,"NodeLifeline",{enumerable:!0,get:function(){return f}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../types/index.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs"),a=require("./client.cjs");let o=require("inversify");require("reflect-metadata/lite");let s=require("node:crypto"),c=require("node:fs");c=e.__toESM(c,1);let l=require("node:path");l=e.__toESM(l,1);let u=require("node:net");u=e.__toESM(u,1);let d=require("node:os");d=e.__toESM(d,1);let f=class{paths;server;target;connections=new Set;constructor(e){this.paths=e}async arm(e){if(this.target)return this.target;try{this.paths.ensureDirectories(e);let t=this.socketPath(e);c.default.rmSync(t,{force:!0});let n=u.default.createServer(e=>{e.unref(),e.resume(),this.connections.add(e),e.on(`close`,()=>this.connections.delete(e))});return await p(n,t),c.default.chmodSync(t,384),n.unref(),this.server=n,this.target=t,process.env[a.LIFELINE_ENV]=t,t}catch(e){process.emitWarning(`Could not arm the doom-runner lifeline: ${String(e)}`);return}}path(){return this.target}dispose(){for(let e of this.connections)e.destroy();if(this.connections.clear(),this.server){try{this.server.close()}catch(e){process.emitWarning(`Could not close the doom-runner lifeline: ${String(e)}`)}this.server=void 0}if(this.target){try{c.default.rmSync(this.target,{force:!0})}catch(e){process.emitWarning(`Could not remove the doom-runner lifeline: ${String(e)}`)}this.target=void 0}process.env.DOOM_RUNNER_LIFELINE&&delete process.env[a.LIFELINE_ENV]}socketPath(e){let t=l.default.join(this.paths.stateDirectory(e),`lifeline.sock`);if(Buffer.byteLength(t)<=100)return t;let n=(0,s.createHash)(`sha256`).update(t).digest(`hex`).slice(0,12);return l.default.join(d.default.tmpdir(),`doom-runner-${n}.sock`)}};f=i.default([(0,o.injectable)(),r.default(0,(0,o.inject)(t.TYPES.RunnerPaths)),n.default(`design:paramtypes`,[Object])],f);function p(e,t){return new Promise((n,r)=>{e.once(`error`,r),e.listen(t,()=>{e.removeListener(`error`,r),e.on(`error`,e=>process.emitWarning(`doom-runner lifeline failed: ${e.message}`)),n()})})}Object.defineProperty(exports,"NodeLifeline",{enumerable:!0,get:function(){return f}});
2
2
  //# sourceMappingURL=NodeLifeline.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodeLifeline.cjs","names":["net","LIFELINE_ENV","path","createHash","os","injectable","inject","TYPES"],"sources":["../../../src/services/Lifeline/NodeLifeline.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport { LIFELINE_ENV } from './client.ts';\nimport type { ILifeline } from './types.ts';\n\nconst SOCKET_NAME = 'lifeline.sock';\n/** Unix socket paths are capped at 104 bytes, so a long agent directory falls back to the temp dir. */\nconst SOCKET_PATH_LIMIT = 100;\nconst SOCKET_HASH_LENGTH = 12;\nconst SOCKET_MODE = 0o600;\n\n@injectable()\nexport class NodeLifeline implements ILifeline {\n private server: net.Server | undefined;\n private target: string | undefined;\n /** Closing a server stops it accepting; only the connections carry the death signal. */\n private readonly connections = new Set<net.Socket>();\n\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n async arm(sessionId: string): Promise<string | undefined> {\n if (this.target) return this.target;\n try {\n this.paths.ensureDirectories(sessionId);\n const target = this.socketPath(sessionId);\n // A socket left behind by a SIGKILLed predecessor refuses connections, never accepts them.\n fs.rmSync(target, { force: true });\n const server = net.createServer((connection) => {\n // Runners need only the close edge, so their liveness and their data are equally irrelevant.\n connection.unref();\n connection.resume();\n this.connections.add(connection);\n connection.on('close', () => this.connections.delete(connection));\n });\n await listen(server, target);\n fs.chmodSync(target, SOCKET_MODE);\n // The lifeline must never be the reason pi stays alive.\n server.unref();\n this.server = server;\n this.target = target;\n process.env[LIFELINE_ENV] = target;\n return target;\n } catch (error) {\n // Runners stay unwatched rather than unlaunchable, so a session without a\n // lifeline degrades to running them unsupervised instead of failing.\n process.emitWarning(`Could not arm the doom-runner lifeline: ${String(error)}`);\n return undefined;\n }\n }\n\n path(): string | undefined {\n return this.target;\n }\n\n dispose(): void {\n // Dropping the connections is what a runner sees, and it is the whole point\n // of disposing: closing the server alone would leave every runner waiting.\n for (const connection of this.connections) connection.destroy();\n this.connections.clear();\n if (this.server) {\n try {\n this.server.close();\n } catch (error) {\n process.emitWarning(`Could not close the doom-runner lifeline: ${String(error)}`);\n }\n this.server = undefined;\n }\n if (this.target) {\n try {\n fs.rmSync(this.target, { force: true });\n } catch (error) {\n process.emitWarning(`Could not remove the doom-runner lifeline: ${String(error)}`);\n }\n this.target = undefined;\n }\n if (process.env[LIFELINE_ENV]) delete process.env[LIFELINE_ENV];\n }\n\n /** Keeps the socket beside the session it belongs to unless that path is too long to bind. */\n private socketPath(sessionId: string): string {\n const preferred = path.join(this.paths.stateDirectory(sessionId), SOCKET_NAME);\n if (Buffer.byteLength(preferred) <= SOCKET_PATH_LIMIT) return preferred;\n const hash = createHash('sha256').update(preferred).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return path.join(os.tmpdir(), `doom-runner-${hash}.sock`);\n }\n}\n\n/** Binding must complete before the first runner launches, so arming waits for it. */\nfunction listen(server: net.Server, target: string): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(target, () => {\n server.removeListener('error', reject);\n // Later errors would otherwise reach the process as unhandled.\n server.on('error', (error: Error) => process.emitWarning(`doom-runner lifeline failed: ${error.message}`));\n resolve();\n });\n });\n}\n"],"mappings":"wrBAmBO,IAAM,EAAN,KAAwC,CAMW,MALxD,OACA,OAEA,YAA+B,IAAI,IAEnC,YAAY,EAAiE,CAArB,KAAA,MAAA,CAAsB,CAE9E,MAAM,IAAI,EAAgD,CACxD,GAAI,KAAK,OAAQ,OAAO,KAAK,OAC7B,GAAI,CACF,KAAK,MAAM,kBAAkB,CAAS,EACtC,IAAM,EAAS,KAAK,WAAW,CAAS,EAExC,EAAA,QAAG,OAAO,EAAQ,CAAE,MAAO,EAAK,CAAC,EACjC,IAAM,EAASA,EAAAA,QAAI,aAAc,GAAe,CAE9C,EAAW,MAAM,EACjB,EAAW,OAAO,EAClB,KAAK,YAAY,IAAI,CAAU,EAC/B,EAAW,GAAG,YAAe,KAAK,YAAY,OAAO,CAAU,CAAC,CAClE,CAAC,EAQD,OAPA,MAAM,EAAO,EAAQ,CAAM,EAC3B,EAAA,QAAG,UAAU,EAAQ,GAAW,EAEhC,EAAO,MAAM,EACb,KAAK,OAAS,EACd,KAAK,OAAS,EACd,QAAQ,IAAIC,EAAAA,cAAgB,EACrB,CACT,OAAS,EAAO,CAGd,QAAQ,YAAY,2CAA2C,OAAO,CAAK,GAAG,EAC9E,MACF,CACF,CAEA,MAA2B,CACzB,OAAO,KAAK,MACd,CAEA,SAAgB,CAGd,IAAK,IAAM,KAAc,KAAK,YAAa,EAAW,QAAQ,EAE9D,GADA,KAAK,YAAY,MAAM,EACnB,KAAK,OAAQ,CACf,GAAI,CACF,KAAK,OAAO,MAAM,CACpB,OAAS,EAAO,CACd,QAAQ,YAAY,6CAA6C,OAAO,CAAK,GAAG,CAClF,CACA,KAAK,OAAS,IAAA,EAChB,CACA,GAAI,KAAK,OAAQ,CACf,GAAI,CACF,EAAA,QAAG,OAAO,KAAK,OAAQ,CAAE,MAAO,EAAK,CAAC,CACxC,OAAS,EAAO,CACd,QAAQ,YAAY,8CAA8C,OAAO,CAAK,GAAG,CACnF,CACA,KAAK,OAAS,IAAA,EAChB,CACI,QAAQ,IAAA,sBAAmB,OAAO,QAAQ,IAAIA,EAAAA,aACpD,CAGA,WAAmB,EAA2B,CAC5C,IAAM,EAAYC,EAAAA,QAAK,KAAK,KAAK,MAAM,eAAe,CAAS,EAAG,eAAW,EAC7E,GAAI,OAAO,WAAW,CAAS,GAAK,IAAmB,OAAO,EAC9D,IAAM,GAAA,EAAOC,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAS,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAkB,EAC7F,OAAOD,EAAAA,QAAK,KAAKE,EAAAA,QAAG,OAAO,EAAG,eAAe,EAAK,MAAM,CAC1D,CACF,gBA1ECC,EAAAA,EAAAA,WAAAA,CAAW,EAOGC,EAAAA,QAAAA,GAAAA,EAAAA,EAAAA,OAAAA,CAAOC,EAAAA,MAAM,WAAW,CAAA,8CAsEvC,SAAS,EAAO,EAAoB,EAA+B,CACjE,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,EAAO,KAAK,QAAS,CAAM,EAC3B,EAAO,OAAO,MAAc,CAC1B,EAAO,eAAe,QAAS,CAAM,EAErC,EAAO,GAAG,QAAU,GAAiB,QAAQ,YAAY,gCAAgC,EAAM,SAAS,CAAC,EACzG,EAAQ,CACV,CAAC,CACH,CAAC,CACH"}
1
+ {"version":3,"file":"NodeLifeline.cjs","names":["net","LIFELINE_ENV","path","createHash","os","injectable","inject","TYPES"],"sources":["../../../src/services/Lifeline/NodeLifeline.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport { LIFELINE_ENV } from './client.ts';\nimport type { ILifeline } from './types.ts';\n\nconst SOCKET_NAME = 'lifeline.sock';\n/** Unix socket paths are capped at 104 bytes, so a long agent directory falls back to the temp dir. */\nconst SOCKET_PATH_LIMIT = 100;\nconst SOCKET_HASH_LENGTH = 12;\nconst SOCKET_MODE = 0o600;\n\n@injectable()\nexport class NodeLifeline implements ILifeline {\n private server: net.Server | undefined;\n private target: string | undefined;\n /** Closing a server stops it accepting; only the connections carry the death signal. */\n private readonly connections = new Set<net.Socket>();\n\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n async arm(sessionId: string): Promise<string | undefined> {\n if (this.target) return this.target;\n try {\n this.paths.ensureDirectories(sessionId);\n const target = this.socketPath(sessionId);\n // A socket left behind by a SIGKILLed predecessor refuses connections, never accepts them.\n fs.rmSync(target, { force: true });\n const server = net.createServer((connection) => {\n // Runners need only the close edge, so their liveness and their data are equally irrelevant.\n connection.unref();\n connection.resume();\n this.connections.add(connection);\n connection.on('close', () => this.connections.delete(connection));\n });\n await listen(server, target);\n fs.chmodSync(target, SOCKET_MODE);\n // The lifeline must never be the reason pi stays alive.\n server.unref();\n this.server = server;\n this.target = target;\n process.env[LIFELINE_ENV] = target;\n return target;\n } catch (error) {\n // Runners stay unwatched rather than unlaunchable, so a session without a\n // lifeline degrades to running them unsupervised instead of failing.\n process.emitWarning(`Could not arm the doom-runner lifeline: ${String(error)}`);\n return undefined;\n }\n }\n\n path(): string | undefined {\n return this.target;\n }\n\n dispose(): void {\n // Dropping the connections is what a runner sees, and it is the whole point\n // of disposing: closing the server alone would leave every runner waiting.\n for (const connection of this.connections) connection.destroy();\n this.connections.clear();\n if (this.server) {\n try {\n this.server.close();\n } catch (error) {\n process.emitWarning(`Could not close the doom-runner lifeline: ${String(error)}`);\n }\n this.server = undefined;\n }\n if (this.target) {\n try {\n fs.rmSync(this.target, { force: true });\n } catch (error) {\n process.emitWarning(`Could not remove the doom-runner lifeline: ${String(error)}`);\n }\n this.target = undefined;\n }\n if (process.env[LIFELINE_ENV]) delete process.env[LIFELINE_ENV];\n }\n\n /** Keeps the socket beside the session it belongs to unless that path is too long to bind. */\n private socketPath(sessionId: string): string {\n const preferred = path.join(this.paths.stateDirectory(sessionId), SOCKET_NAME);\n if (Buffer.byteLength(preferred) <= SOCKET_PATH_LIMIT) return preferred;\n const hash = createHash('sha256').update(preferred).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return path.join(os.tmpdir(), `doom-runner-${hash}.sock`);\n }\n}\n\n/** Binding must complete before the first runner launches, so arming waits for it. */\nfunction listen(server: net.Server, target: string): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(target, () => {\n server.removeListener('error', reject);\n // Later errors would otherwise reach the process as unhandled.\n server.on('error', (error: Error) => process.emitWarning(`doom-runner lifeline failed: ${error.message}`));\n resolve();\n });\n });\n}\n"],"mappings":"8rBAmBO,IAAM,EAAN,KAAwC,CAMW,MALxD,OACA,OAEA,YAA+B,IAAI,IAEnC,YAAY,EAAiE,CAArB,KAAA,MAAA,CAAsB,CAE9E,MAAM,IAAI,EAAgD,CACxD,GAAI,KAAK,OAAQ,OAAO,KAAK,OAC7B,GAAI,CACF,KAAK,MAAM,kBAAkB,CAAS,EACtC,IAAM,EAAS,KAAK,WAAW,CAAS,EAExC,EAAA,QAAG,OAAO,EAAQ,CAAE,MAAO,EAAK,CAAC,EACjC,IAAM,EAASA,EAAAA,QAAI,aAAc,GAAe,CAE9C,EAAW,MAAM,EACjB,EAAW,OAAO,EAClB,KAAK,YAAY,IAAI,CAAU,EAC/B,EAAW,GAAG,YAAe,KAAK,YAAY,OAAO,CAAU,CAAC,CAClE,CAAC,EAQD,OAPA,MAAM,EAAO,EAAQ,CAAM,EAC3B,EAAA,QAAG,UAAU,EAAQ,GAAW,EAEhC,EAAO,MAAM,EACb,KAAK,OAAS,EACd,KAAK,OAAS,EACd,QAAQ,IAAIC,EAAAA,cAAgB,EACrB,CACT,OAAS,EAAO,CAGd,QAAQ,YAAY,2CAA2C,OAAO,CAAK,GAAG,EAC9E,MACF,CACF,CAEA,MAA2B,CACzB,OAAO,KAAK,MACd,CAEA,SAAgB,CAGd,IAAK,IAAM,KAAc,KAAK,YAAa,EAAW,QAAQ,EAE9D,GADA,KAAK,YAAY,MAAM,EACnB,KAAK,OAAQ,CACf,GAAI,CACF,KAAK,OAAO,MAAM,CACpB,OAAS,EAAO,CACd,QAAQ,YAAY,6CAA6C,OAAO,CAAK,GAAG,CAClF,CACA,KAAK,OAAS,IAAA,EAChB,CACA,GAAI,KAAK,OAAQ,CACf,GAAI,CACF,EAAA,QAAG,OAAO,KAAK,OAAQ,CAAE,MAAO,EAAK,CAAC,CACxC,OAAS,EAAO,CACd,QAAQ,YAAY,8CAA8C,OAAO,CAAK,GAAG,CACnF,CACA,KAAK,OAAS,IAAA,EAChB,CACI,QAAQ,IAAA,sBAAmB,OAAO,QAAQ,IAAIA,EAAAA,aACpD,CAGA,WAAmB,EAA2B,CAC5C,IAAM,EAAYC,EAAAA,QAAK,KAAK,KAAK,MAAM,eAAe,CAAS,EAAG,eAAW,EAC7E,GAAI,OAAO,WAAW,CAAS,GAAK,IAAmB,OAAO,EAC9D,IAAM,GAAA,EAAOC,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAS,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAkB,EAC7F,OAAOD,EAAAA,QAAK,KAAKE,EAAAA,QAAG,OAAO,EAAG,eAAe,EAAK,MAAM,CAC1D,CACF,gBA1ECC,EAAAA,EAAAA,WAAAA,CAAW,EAOGC,EAAAA,QAAAA,GAAAA,EAAAA,EAAAA,OAAAA,CAAOC,EAAAA,MAAM,WAAW,CAAA,8CAsEvC,SAAS,EAAO,EAAoB,EAA+B,CACjE,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,EAAO,KAAK,QAAS,CAAM,EAC3B,EAAO,OAAO,MAAc,CAC1B,EAAO,eAAe,QAAS,CAAM,EAErC,EAAO,GAAG,QAAU,GAAiB,QAAQ,YAAY,gCAAgC,EAAM,SAAS,CAAC,EACzG,EAAQ,CACV,CAAC,CACH,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- import{TYPES as e}from"../../types.mjs";import t from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{LIFELINE_ENV as i}from"./client.mjs";import{inject as a,injectable as o}from"inversify";import"reflect-metadata/lite";import{createHash as s}from"node:crypto";import c from"node:fs";import l from"node:path";import u from"node:net";import d from"node:os";let f=class{paths;server;target;connections=new Set;constructor(e){this.paths=e}async arm(e){if(this.target)return this.target;try{this.paths.ensureDirectories(e);let t=this.socketPath(e);c.rmSync(t,{force:!0});let n=u.createServer(e=>{e.unref(),e.resume(),this.connections.add(e),e.on(`close`,()=>this.connections.delete(e))});return await p(n,t),c.chmodSync(t,384),n.unref(),this.server=n,this.target=t,process.env[i]=t,t}catch(e){process.emitWarning(`Could not arm the doom-runner lifeline: ${String(e)}`);return}}path(){return this.target}dispose(){for(let e of this.connections)e.destroy();if(this.connections.clear(),this.server){try{this.server.close()}catch(e){process.emitWarning(`Could not close the doom-runner lifeline: ${String(e)}`)}this.server=void 0}if(this.target){try{c.rmSync(this.target,{force:!0})}catch(e){process.emitWarning(`Could not remove the doom-runner lifeline: ${String(e)}`)}this.target=void 0}process.env.DOOM_RUNNER_LIFELINE&&delete process.env[i]}socketPath(e){let t=l.join(this.paths.stateDirectory(e),`lifeline.sock`);if(Buffer.byteLength(t)<=100)return t;let n=s(`sha256`).update(t).digest(`hex`).slice(0,12);return l.join(d.tmpdir(),`doom-runner-${n}.sock`)}};f=r([o(),n(0,a(e.RunnerPaths)),t(`design:paramtypes`,[Object])],f);function p(e,t){return new Promise((n,r)=>{e.once(`error`,r),e.listen(t,()=>{e.removeListener(`error`,r),e.on(`error`,e=>process.emitWarning(`doom-runner lifeline failed: ${e.message}`)),n()})})}export{f as NodeLifeline};
1
+ import{TYPES as e}from"../../types/index.mjs";import t from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{LIFELINE_ENV as i}from"./client.mjs";import{inject as a,injectable as o}from"inversify";import"reflect-metadata/lite";import{createHash as s}from"node:crypto";import c from"node:fs";import l from"node:path";import u from"node:net";import d from"node:os";let f=class{paths;server;target;connections=new Set;constructor(e){this.paths=e}async arm(e){if(this.target)return this.target;try{this.paths.ensureDirectories(e);let t=this.socketPath(e);c.rmSync(t,{force:!0});let n=u.createServer(e=>{e.unref(),e.resume(),this.connections.add(e),e.on(`close`,()=>this.connections.delete(e))});return await p(n,t),c.chmodSync(t,384),n.unref(),this.server=n,this.target=t,process.env[i]=t,t}catch(e){process.emitWarning(`Could not arm the doom-runner lifeline: ${String(e)}`);return}}path(){return this.target}dispose(){for(let e of this.connections)e.destroy();if(this.connections.clear(),this.server){try{this.server.close()}catch(e){process.emitWarning(`Could not close the doom-runner lifeline: ${String(e)}`)}this.server=void 0}if(this.target){try{c.rmSync(this.target,{force:!0})}catch(e){process.emitWarning(`Could not remove the doom-runner lifeline: ${String(e)}`)}this.target=void 0}process.env.DOOM_RUNNER_LIFELINE&&delete process.env[i]}socketPath(e){let t=l.join(this.paths.stateDirectory(e),`lifeline.sock`);if(Buffer.byteLength(t)<=100)return t;let n=s(`sha256`).update(t).digest(`hex`).slice(0,12);return l.join(d.tmpdir(),`doom-runner-${n}.sock`)}};f=r([o(),n(0,a(e.RunnerPaths)),t(`design:paramtypes`,[Object])],f);function p(e,t){return new Promise((n,r)=>{e.once(`error`,r),e.listen(t,()=>{e.removeListener(`error`,r),e.on(`error`,e=>process.emitWarning(`doom-runner lifeline failed: ${e.message}`)),n()})})}export{f as NodeLifeline};
2
2
  //# sourceMappingURL=NodeLifeline.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodeLifeline.mjs","names":[],"sources":["../../../src/services/Lifeline/NodeLifeline.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport { LIFELINE_ENV } from './client.ts';\nimport type { ILifeline } from './types.ts';\n\nconst SOCKET_NAME = 'lifeline.sock';\n/** Unix socket paths are capped at 104 bytes, so a long agent directory falls back to the temp dir. */\nconst SOCKET_PATH_LIMIT = 100;\nconst SOCKET_HASH_LENGTH = 12;\nconst SOCKET_MODE = 0o600;\n\n@injectable()\nexport class NodeLifeline implements ILifeline {\n private server: net.Server | undefined;\n private target: string | undefined;\n /** Closing a server stops it accepting; only the connections carry the death signal. */\n private readonly connections = new Set<net.Socket>();\n\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n async arm(sessionId: string): Promise<string | undefined> {\n if (this.target) return this.target;\n try {\n this.paths.ensureDirectories(sessionId);\n const target = this.socketPath(sessionId);\n // A socket left behind by a SIGKILLed predecessor refuses connections, never accepts them.\n fs.rmSync(target, { force: true });\n const server = net.createServer((connection) => {\n // Runners need only the close edge, so their liveness and their data are equally irrelevant.\n connection.unref();\n connection.resume();\n this.connections.add(connection);\n connection.on('close', () => this.connections.delete(connection));\n });\n await listen(server, target);\n fs.chmodSync(target, SOCKET_MODE);\n // The lifeline must never be the reason pi stays alive.\n server.unref();\n this.server = server;\n this.target = target;\n process.env[LIFELINE_ENV] = target;\n return target;\n } catch (error) {\n // Runners stay unwatched rather than unlaunchable, so a session without a\n // lifeline degrades to running them unsupervised instead of failing.\n process.emitWarning(`Could not arm the doom-runner lifeline: ${String(error)}`);\n return undefined;\n }\n }\n\n path(): string | undefined {\n return this.target;\n }\n\n dispose(): void {\n // Dropping the connections is what a runner sees, and it is the whole point\n // of disposing: closing the server alone would leave every runner waiting.\n for (const connection of this.connections) connection.destroy();\n this.connections.clear();\n if (this.server) {\n try {\n this.server.close();\n } catch (error) {\n process.emitWarning(`Could not close the doom-runner lifeline: ${String(error)}`);\n }\n this.server = undefined;\n }\n if (this.target) {\n try {\n fs.rmSync(this.target, { force: true });\n } catch (error) {\n process.emitWarning(`Could not remove the doom-runner lifeline: ${String(error)}`);\n }\n this.target = undefined;\n }\n if (process.env[LIFELINE_ENV]) delete process.env[LIFELINE_ENV];\n }\n\n /** Keeps the socket beside the session it belongs to unless that path is too long to bind. */\n private socketPath(sessionId: string): string {\n const preferred = path.join(this.paths.stateDirectory(sessionId), SOCKET_NAME);\n if (Buffer.byteLength(preferred) <= SOCKET_PATH_LIMIT) return preferred;\n const hash = createHash('sha256').update(preferred).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return path.join(os.tmpdir(), `doom-runner-${hash}.sock`);\n }\n}\n\n/** Binding must complete before the first runner launches, so arming waits for it. */\nfunction listen(server: net.Server, target: string): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(target, () => {\n server.removeListener('error', reject);\n // Later errors would otherwise reach the process as unhandled.\n server.on('error', (error: Error) => process.emitWarning(`doom-runner lifeline failed: ${error.message}`));\n resolve();\n });\n });\n}\n"],"mappings":"yjBAmBO,IAAM,EAAN,KAAwC,CAMW,MALxD,OACA,OAEA,YAA+B,IAAI,IAEnC,YAAY,EAAiE,CAArB,KAAA,MAAA,CAAsB,CAE9E,MAAM,IAAI,EAAgD,CACxD,GAAI,KAAK,OAAQ,OAAO,KAAK,OAC7B,GAAI,CACF,KAAK,MAAM,kBAAkB,CAAS,EACtC,IAAM,EAAS,KAAK,WAAW,CAAS,EAExC,EAAG,OAAO,EAAQ,CAAE,MAAO,EAAK,CAAC,EACjC,IAAM,EAAS,EAAI,aAAc,GAAe,CAE9C,EAAW,MAAM,EACjB,EAAW,OAAO,EAClB,KAAK,YAAY,IAAI,CAAU,EAC/B,EAAW,GAAG,YAAe,KAAK,YAAY,OAAO,CAAU,CAAC,CAClE,CAAC,EAQD,OAPA,MAAM,EAAO,EAAQ,CAAM,EAC3B,EAAG,UAAU,EAAQ,GAAW,EAEhC,EAAO,MAAM,EACb,KAAK,OAAS,EACd,KAAK,OAAS,EACd,QAAQ,IAAI,GAAgB,EACrB,CACT,OAAS,EAAO,CAGd,QAAQ,YAAY,2CAA2C,OAAO,CAAK,GAAG,EAC9E,MACF,CACF,CAEA,MAA2B,CACzB,OAAO,KAAK,MACd,CAEA,SAAgB,CAGd,IAAK,IAAM,KAAc,KAAK,YAAa,EAAW,QAAQ,EAE9D,GADA,KAAK,YAAY,MAAM,EACnB,KAAK,OAAQ,CACf,GAAI,CACF,KAAK,OAAO,MAAM,CACpB,OAAS,EAAO,CACd,QAAQ,YAAY,6CAA6C,OAAO,CAAK,GAAG,CAClF,CACA,KAAK,OAAS,IAAA,EAChB,CACA,GAAI,KAAK,OAAQ,CACf,GAAI,CACF,EAAG,OAAO,KAAK,OAAQ,CAAE,MAAO,EAAK,CAAC,CACxC,OAAS,EAAO,CACd,QAAQ,YAAY,8CAA8C,OAAO,CAAK,GAAG,CACnF,CACA,KAAK,OAAS,IAAA,EAChB,CACI,QAAQ,IAAA,sBAAmB,OAAO,QAAQ,IAAI,EACpD,CAGA,WAAmB,EAA2B,CAC5C,IAAM,EAAY,EAAK,KAAK,KAAK,MAAM,eAAe,CAAS,EAAG,eAAW,EAC7E,GAAI,OAAO,WAAW,CAAS,GAAK,IAAmB,OAAO,EAC9D,IAAM,EAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAS,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAkB,EAC7F,OAAO,EAAK,KAAK,EAAG,OAAO,EAAG,eAAe,EAAK,MAAM,CAC1D,CACF,OA1EC,EAAW,EAOG,EAAA,EAAA,EAAO,EAAM,WAAW,CAAA,sCAsEvC,SAAS,EAAO,EAAoB,EAA+B,CACjE,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,EAAO,KAAK,QAAS,CAAM,EAC3B,EAAO,OAAO,MAAc,CAC1B,EAAO,eAAe,QAAS,CAAM,EAErC,EAAO,GAAG,QAAU,GAAiB,QAAQ,YAAY,gCAAgC,EAAM,SAAS,CAAC,EACzG,EAAQ,CACV,CAAC,CACH,CAAC,CACH"}
1
+ {"version":3,"file":"NodeLifeline.mjs","names":[],"sources":["../../../src/services/Lifeline/NodeLifeline.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport { LIFELINE_ENV } from './client.ts';\nimport type { ILifeline } from './types.ts';\n\nconst SOCKET_NAME = 'lifeline.sock';\n/** Unix socket paths are capped at 104 bytes, so a long agent directory falls back to the temp dir. */\nconst SOCKET_PATH_LIMIT = 100;\nconst SOCKET_HASH_LENGTH = 12;\nconst SOCKET_MODE = 0o600;\n\n@injectable()\nexport class NodeLifeline implements ILifeline {\n private server: net.Server | undefined;\n private target: string | undefined;\n /** Closing a server stops it accepting; only the connections carry the death signal. */\n private readonly connections = new Set<net.Socket>();\n\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n async arm(sessionId: string): Promise<string | undefined> {\n if (this.target) return this.target;\n try {\n this.paths.ensureDirectories(sessionId);\n const target = this.socketPath(sessionId);\n // A socket left behind by a SIGKILLed predecessor refuses connections, never accepts them.\n fs.rmSync(target, { force: true });\n const server = net.createServer((connection) => {\n // Runners need only the close edge, so their liveness and their data are equally irrelevant.\n connection.unref();\n connection.resume();\n this.connections.add(connection);\n connection.on('close', () => this.connections.delete(connection));\n });\n await listen(server, target);\n fs.chmodSync(target, SOCKET_MODE);\n // The lifeline must never be the reason pi stays alive.\n server.unref();\n this.server = server;\n this.target = target;\n process.env[LIFELINE_ENV] = target;\n return target;\n } catch (error) {\n // Runners stay unwatched rather than unlaunchable, so a session without a\n // lifeline degrades to running them unsupervised instead of failing.\n process.emitWarning(`Could not arm the doom-runner lifeline: ${String(error)}`);\n return undefined;\n }\n }\n\n path(): string | undefined {\n return this.target;\n }\n\n dispose(): void {\n // Dropping the connections is what a runner sees, and it is the whole point\n // of disposing: closing the server alone would leave every runner waiting.\n for (const connection of this.connections) connection.destroy();\n this.connections.clear();\n if (this.server) {\n try {\n this.server.close();\n } catch (error) {\n process.emitWarning(`Could not close the doom-runner lifeline: ${String(error)}`);\n }\n this.server = undefined;\n }\n if (this.target) {\n try {\n fs.rmSync(this.target, { force: true });\n } catch (error) {\n process.emitWarning(`Could not remove the doom-runner lifeline: ${String(error)}`);\n }\n this.target = undefined;\n }\n if (process.env[LIFELINE_ENV]) delete process.env[LIFELINE_ENV];\n }\n\n /** Keeps the socket beside the session it belongs to unless that path is too long to bind. */\n private socketPath(sessionId: string): string {\n const preferred = path.join(this.paths.stateDirectory(sessionId), SOCKET_NAME);\n if (Buffer.byteLength(preferred) <= SOCKET_PATH_LIMIT) return preferred;\n const hash = createHash('sha256').update(preferred).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return path.join(os.tmpdir(), `doom-runner-${hash}.sock`);\n }\n}\n\n/** Binding must complete before the first runner launches, so arming waits for it. */\nfunction listen(server: net.Server, target: string): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(target, () => {\n server.removeListener('error', reject);\n // Later errors would otherwise reach the process as unhandled.\n server.on('error', (error: Error) => process.emitWarning(`doom-runner lifeline failed: ${error.message}`));\n resolve();\n });\n });\n}\n"],"mappings":"+jBAmBO,IAAM,EAAN,KAAwC,CAMW,MALxD,OACA,OAEA,YAA+B,IAAI,IAEnC,YAAY,EAAiE,CAArB,KAAA,MAAA,CAAsB,CAE9E,MAAM,IAAI,EAAgD,CACxD,GAAI,KAAK,OAAQ,OAAO,KAAK,OAC7B,GAAI,CACF,KAAK,MAAM,kBAAkB,CAAS,EACtC,IAAM,EAAS,KAAK,WAAW,CAAS,EAExC,EAAG,OAAO,EAAQ,CAAE,MAAO,EAAK,CAAC,EACjC,IAAM,EAAS,EAAI,aAAc,GAAe,CAE9C,EAAW,MAAM,EACjB,EAAW,OAAO,EAClB,KAAK,YAAY,IAAI,CAAU,EAC/B,EAAW,GAAG,YAAe,KAAK,YAAY,OAAO,CAAU,CAAC,CAClE,CAAC,EAQD,OAPA,MAAM,EAAO,EAAQ,CAAM,EAC3B,EAAG,UAAU,EAAQ,GAAW,EAEhC,EAAO,MAAM,EACb,KAAK,OAAS,EACd,KAAK,OAAS,EACd,QAAQ,IAAI,GAAgB,EACrB,CACT,OAAS,EAAO,CAGd,QAAQ,YAAY,2CAA2C,OAAO,CAAK,GAAG,EAC9E,MACF,CACF,CAEA,MAA2B,CACzB,OAAO,KAAK,MACd,CAEA,SAAgB,CAGd,IAAK,IAAM,KAAc,KAAK,YAAa,EAAW,QAAQ,EAE9D,GADA,KAAK,YAAY,MAAM,EACnB,KAAK,OAAQ,CACf,GAAI,CACF,KAAK,OAAO,MAAM,CACpB,OAAS,EAAO,CACd,QAAQ,YAAY,6CAA6C,OAAO,CAAK,GAAG,CAClF,CACA,KAAK,OAAS,IAAA,EAChB,CACA,GAAI,KAAK,OAAQ,CACf,GAAI,CACF,EAAG,OAAO,KAAK,OAAQ,CAAE,MAAO,EAAK,CAAC,CACxC,OAAS,EAAO,CACd,QAAQ,YAAY,8CAA8C,OAAO,CAAK,GAAG,CACnF,CACA,KAAK,OAAS,IAAA,EAChB,CACI,QAAQ,IAAA,sBAAmB,OAAO,QAAQ,IAAI,EACpD,CAGA,WAAmB,EAA2B,CAC5C,IAAM,EAAY,EAAK,KAAK,KAAK,MAAM,eAAe,CAAS,EAAG,eAAW,EAC7E,GAAI,OAAO,WAAW,CAAS,GAAK,IAAmB,OAAO,EAC9D,IAAM,EAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAS,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAkB,EAC7F,OAAO,EAAK,KAAK,EAAG,OAAO,EAAG,eAAe,EAAK,MAAM,CAC1D,CACF,OA1EC,EAAW,EAOG,EAAA,EAAA,EAAO,EAAM,WAAW,CAAA,sCAsEvC,SAAS,EAAO,EAAoB,EAA+B,CACjE,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,EAAO,KAAK,QAAS,CAAM,EAC3B,EAAO,OAAO,MAAc,CAC1B,EAAO,eAAe,QAAS,CAAM,EAErC,EAAO,GAAG,QAAU,GAAiB,QAAQ,YAAY,gCAAgC,EAAM,SAAS,CAAC,EACzG,EAAQ,CACV,CAAC,CACH,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../config.cjs"),n=require("../../types.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),a=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs");let o=require("inversify");require("reflect-metadata/lite");let s=require("node:fs");s=e.__toESM(s,1);let c=class{paths;constructor(e){this.paths=e}open(e){this.paths.ensureDirectories();let n=this.paths.logPathFor(e),r=this.paths.rotatedLogPathFor(e),i=t.getLogMaxBytes(),a=s.default.openSync(n,`w`),o=0;return{path:n,append(e){let t=Buffer.from(e,`utf8`);o+t.byteLength>i&&(s.default.closeSync(a),s.default.renameSync(n,r),a=s.default.openSync(n,`w`),o=0),s.default.writeSync(a,t),o+=t.byteLength},size(){return o},close(){try{s.default.closeSync(a)}catch{}}}}};c=a.default([(0,o.injectable)(),i.default(0,(0,o.inject)(n.TYPES.RunnerPaths)),r.default(`design:paramtypes`,[Object])],c),Object.defineProperty(exports,"LogFile",{enumerable:!0,get:function(){return c}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../types/config.cjs"),n=require("../../types/index.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),a=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs");let o=require("inversify");require("reflect-metadata/lite");let s=require("node:fs");s=e.__toESM(s,1);let c=class{paths;constructor(e){this.paths=e}open(e){this.paths.ensureDirectories();let n=this.paths.logPathFor(e),r=this.paths.rotatedLogPathFor(e),i=t.getLogMaxBytes(),a=s.default.openSync(n,`w`),o=0;return{path:n,append(e){let t=Buffer.from(e,`utf8`);o+t.byteLength>i&&(s.default.closeSync(a),s.default.renameSync(n,r),a=s.default.openSync(n,`w`),o=0),s.default.writeSync(a,t),o+=t.byteLength},size(){return o},close(){try{s.default.closeSync(a)}catch{}}}}};c=a.default([(0,o.injectable)(),i.default(0,(0,o.inject)(n.TYPES.RunnerPaths)),r.default(`design:paramtypes`,[Object])],c),Object.defineProperty(exports,"LogFile",{enumerable:!0,get:function(){return c}});
2
2
  //# sourceMappingURL=LogFile.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"LogFile.cjs","names":["getLogMaxBytes","fs","injectable","inject","TYPES"],"sources":["../../../src/services/LogFile/LogFile.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getLogMaxBytes } from '../../config.ts';\nimport { TYPES } from '../../types.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { ILogFile, LogWriter } from './types.ts';\n\n@injectable()\nexport class LogFile implements ILogFile {\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n open(id: string): LogWriter {\n this.paths.ensureDirectories();\n const logPath = this.paths.logPathFor(id);\n const rotatedPath = this.paths.rotatedLogPathFor(id);\n const maxBytes = getLogMaxBytes();\n\n // A reused name starts clean: the previous runner's output is already\n // available to whoever wanted it, and mixing two runs in one file makes\n // both unreadable.\n let handle = fs.openSync(logPath, 'w');\n let written = 0;\n\n return {\n path: logPath,\n append(text: string): void {\n const chunk = Buffer.from(text, 'utf8');\n if (written + chunk.byteLength > maxBytes) {\n fs.closeSync(handle);\n fs.renameSync(logPath, rotatedPath);\n handle = fs.openSync(logPath, 'w');\n written = 0;\n }\n fs.writeSync(handle, chunk);\n written += chunk.byteLength;\n },\n size(): number {\n return written;\n },\n close(): void {\n try {\n fs.closeSync(handle);\n } catch {\n // Closing twice is not worth reporting: the log is already flushed.\n }\n },\n };\n }\n}\n"],"mappings":"kiBASO,IAAM,EAAN,KAAkC,CACiB,MAAxD,YAAY,EAAiE,CAArB,KAAA,MAAA,CAAsB,CAE9E,KAAK,EAAuB,CAC1B,KAAK,MAAM,kBAAkB,EAC7B,IAAM,EAAU,KAAK,MAAM,WAAW,CAAE,EAClC,EAAc,KAAK,MAAM,kBAAkB,CAAE,EAC7C,EAAWA,EAAAA,eAAe,EAK5B,EAASC,EAAAA,QAAG,SAAS,EAAS,GAAG,EACjC,EAAU,EAEd,MAAO,CACL,KAAM,EACN,OAAO,EAAoB,CACzB,IAAM,EAAQ,OAAO,KAAK,EAAM,MAAM,EAClC,EAAU,EAAM,WAAa,IAC/B,EAAA,QAAG,UAAU,CAAM,EACnB,EAAA,QAAG,WAAW,EAAS,CAAW,EAClC,EAASA,EAAAA,QAAG,SAAS,EAAS,GAAG,EACjC,EAAU,GAEZ,EAAA,QAAG,UAAU,EAAQ,CAAK,EAC1B,GAAW,EAAM,UACnB,EACA,MAAe,CACb,OAAO,CACT,EACA,OAAc,CACZ,GAAI,CACF,EAAA,QAAG,UAAU,CAAM,CACrB,MAAQ,CAER,CACF,CACF,CACF,CACF,gBAzCCC,EAAAA,EAAAA,WAAAA,CAAW,EAEGC,EAAAA,QAAAA,GAAAA,EAAAA,EAAAA,OAAAA,CAAOC,EAAAA,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"LogFile.cjs","names":["getLogMaxBytes","fs","injectable","inject","TYPES"],"sources":["../../../src/services/LogFile/LogFile.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getLogMaxBytes } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { ILogFile, LogWriter } from './types.ts';\n\n@injectable()\nexport class LogFile implements ILogFile {\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n open(id: string): LogWriter {\n this.paths.ensureDirectories();\n const logPath = this.paths.logPathFor(id);\n const rotatedPath = this.paths.rotatedLogPathFor(id);\n const maxBytes = getLogMaxBytes();\n\n // A reused name starts clean: the previous runner's output is already\n // available to whoever wanted it, and mixing two runs in one file makes\n // both unreadable.\n let handle = fs.openSync(logPath, 'w');\n let written = 0;\n\n return {\n path: logPath,\n append(text: string): void {\n const chunk = Buffer.from(text, 'utf8');\n if (written + chunk.byteLength > maxBytes) {\n fs.closeSync(handle);\n fs.renameSync(logPath, rotatedPath);\n handle = fs.openSync(logPath, 'w');\n written = 0;\n }\n fs.writeSync(handle, chunk);\n written += chunk.byteLength;\n },\n size(): number {\n return written;\n },\n close(): void {\n try {\n fs.closeSync(handle);\n } catch {\n // Closing twice is not worth reporting: the log is already flushed.\n }\n },\n };\n }\n}\n"],"mappings":"8iBASO,IAAM,EAAN,KAAkC,CACiB,MAAxD,YAAY,EAAiE,CAArB,KAAA,MAAA,CAAsB,CAE9E,KAAK,EAAuB,CAC1B,KAAK,MAAM,kBAAkB,EAC7B,IAAM,EAAU,KAAK,MAAM,WAAW,CAAE,EAClC,EAAc,KAAK,MAAM,kBAAkB,CAAE,EAC7C,EAAWA,EAAAA,eAAe,EAK5B,EAASC,EAAAA,QAAG,SAAS,EAAS,GAAG,EACjC,EAAU,EAEd,MAAO,CACL,KAAM,EACN,OAAO,EAAoB,CACzB,IAAM,EAAQ,OAAO,KAAK,EAAM,MAAM,EAClC,EAAU,EAAM,WAAa,IAC/B,EAAA,QAAG,UAAU,CAAM,EACnB,EAAA,QAAG,WAAW,EAAS,CAAW,EAClC,EAASA,EAAAA,QAAG,SAAS,EAAS,GAAG,EACjC,EAAU,GAEZ,EAAA,QAAG,UAAU,EAAQ,CAAK,EAC1B,GAAW,EAAM,UACnB,EACA,MAAe,CACb,OAAO,CACT,EACA,OAAc,CACZ,GAAI,CACF,EAAA,QAAG,UAAU,CAAM,CACrB,MAAQ,CAER,CACF,CACF,CACF,CACF,gBAzCCC,EAAAA,EAAAA,WAAAA,CAAW,EAEGC,EAAAA,QAAAA,GAAAA,EAAAA,EAAAA,OAAAA,CAAOC,EAAAA,MAAM,WAAW,CAAA"}
@@ -1,2 +1,2 @@
1
- import{getLogMaxBytes as e}from"../../config.mjs";import{TYPES as t}from"../../types.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{inject as a,injectable as o}from"inversify";import"reflect-metadata/lite";import s from"node:fs";let c=class{paths;constructor(e){this.paths=e}open(t){this.paths.ensureDirectories();let n=this.paths.logPathFor(t),r=this.paths.rotatedLogPathFor(t),i=e(),a=s.openSync(n,`w`),o=0;return{path:n,append(e){let t=Buffer.from(e,`utf8`);o+t.byteLength>i&&(s.closeSync(a),s.renameSync(n,r),a=s.openSync(n,`w`),o=0),s.writeSync(a,t),o+=t.byteLength},size(){return o},close(){try{s.closeSync(a)}catch{}}}}};c=i([o(),r(0,a(t.RunnerPaths)),n(`design:paramtypes`,[Object])],c);export{c as LogFile};
1
+ import{getLogMaxBytes as e}from"../../types/config.mjs";import{TYPES as t}from"../../types/index.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{inject as a,injectable as o}from"inversify";import"reflect-metadata/lite";import s from"node:fs";let c=class{paths;constructor(e){this.paths=e}open(t){this.paths.ensureDirectories();let n=this.paths.logPathFor(t),r=this.paths.rotatedLogPathFor(t),i=e(),a=s.openSync(n,`w`),o=0;return{path:n,append(e){let t=Buffer.from(e,`utf8`);o+t.byteLength>i&&(s.closeSync(a),s.renameSync(n,r),a=s.openSync(n,`w`),o=0),s.writeSync(a,t),o+=t.byteLength},size(){return o},close(){try{s.closeSync(a)}catch{}}}}};c=i([o(),r(0,a(t.RunnerPaths)),n(`design:paramtypes`,[Object])],c);export{c as LogFile};
2
2
  //# sourceMappingURL=LogFile.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"LogFile.mjs","names":[],"sources":["../../../src/services/LogFile/LogFile.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getLogMaxBytes } from '../../config.ts';\nimport { TYPES } from '../../types.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { ILogFile, LogWriter } from './types.ts';\n\n@injectable()\nexport class LogFile implements ILogFile {\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n open(id: string): LogWriter {\n this.paths.ensureDirectories();\n const logPath = this.paths.logPathFor(id);\n const rotatedPath = this.paths.rotatedLogPathFor(id);\n const maxBytes = getLogMaxBytes();\n\n // A reused name starts clean: the previous runner's output is already\n // available to whoever wanted it, and mixing two runs in one file makes\n // both unreadable.\n let handle = fs.openSync(logPath, 'w');\n let written = 0;\n\n return {\n path: logPath,\n append(text: string): void {\n const chunk = Buffer.from(text, 'utf8');\n if (written + chunk.byteLength > maxBytes) {\n fs.closeSync(handle);\n fs.renameSync(logPath, rotatedPath);\n handle = fs.openSync(logPath, 'w');\n written = 0;\n }\n fs.writeSync(handle, chunk);\n written += chunk.byteLength;\n },\n size(): number {\n return written;\n },\n close(): void {\n try {\n fs.closeSync(handle);\n } catch {\n // Closing twice is not worth reporting: the log is already flushed.\n }\n },\n };\n }\n}\n"],"mappings":"8cASO,IAAM,EAAN,KAAkC,CACiB,MAAxD,YAAY,EAAiE,CAArB,KAAA,MAAA,CAAsB,CAE9E,KAAK,EAAuB,CAC1B,KAAK,MAAM,kBAAkB,EAC7B,IAAM,EAAU,KAAK,MAAM,WAAW,CAAE,EAClC,EAAc,KAAK,MAAM,kBAAkB,CAAE,EAC7C,EAAW,EAAe,EAK5B,EAAS,EAAG,SAAS,EAAS,GAAG,EACjC,EAAU,EAEd,MAAO,CACL,KAAM,EACN,OAAO,EAAoB,CACzB,IAAM,EAAQ,OAAO,KAAK,EAAM,MAAM,EAClC,EAAU,EAAM,WAAa,IAC/B,EAAG,UAAU,CAAM,EACnB,EAAG,WAAW,EAAS,CAAW,EAClC,EAAS,EAAG,SAAS,EAAS,GAAG,EACjC,EAAU,GAEZ,EAAG,UAAU,EAAQ,CAAK,EAC1B,GAAW,EAAM,UACnB,EACA,MAAe,CACb,OAAO,CACT,EACA,OAAc,CACZ,GAAI,CACF,EAAG,UAAU,CAAM,CACrB,MAAQ,CAER,CACF,CACF,CACF,CACF,OAzCC,EAAW,EAEG,EAAA,EAAA,EAAO,EAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"LogFile.mjs","names":[],"sources":["../../../src/services/LogFile/LogFile.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getLogMaxBytes } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { ILogFile, LogWriter } from './types.ts';\n\n@injectable()\nexport class LogFile implements ILogFile {\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n open(id: string): LogWriter {\n this.paths.ensureDirectories();\n const logPath = this.paths.logPathFor(id);\n const rotatedPath = this.paths.rotatedLogPathFor(id);\n const maxBytes = getLogMaxBytes();\n\n // A reused name starts clean: the previous runner's output is already\n // available to whoever wanted it, and mixing two runs in one file makes\n // both unreadable.\n let handle = fs.openSync(logPath, 'w');\n let written = 0;\n\n return {\n path: logPath,\n append(text: string): void {\n const chunk = Buffer.from(text, 'utf8');\n if (written + chunk.byteLength > maxBytes) {\n fs.closeSync(handle);\n fs.renameSync(logPath, rotatedPath);\n handle = fs.openSync(logPath, 'w');\n written = 0;\n }\n fs.writeSync(handle, chunk);\n written += chunk.byteLength;\n },\n size(): number {\n return written;\n },\n close(): void {\n try {\n fs.closeSync(handle);\n } catch {\n // Closing twice is not worth reporting: the log is already flushed.\n }\n },\n };\n }\n}\n"],"mappings":"0dASO,IAAM,EAAN,KAAkC,CACiB,MAAxD,YAAY,EAAiE,CAArB,KAAA,MAAA,CAAsB,CAE9E,KAAK,EAAuB,CAC1B,KAAK,MAAM,kBAAkB,EAC7B,IAAM,EAAU,KAAK,MAAM,WAAW,CAAE,EAClC,EAAc,KAAK,MAAM,kBAAkB,CAAE,EAC7C,EAAW,EAAe,EAK5B,EAAS,EAAG,SAAS,EAAS,GAAG,EACjC,EAAU,EAEd,MAAO,CACL,KAAM,EACN,OAAO,EAAoB,CACzB,IAAM,EAAQ,OAAO,KAAK,EAAM,MAAM,EAClC,EAAU,EAAM,WAAa,IAC/B,EAAG,UAAU,CAAM,EACnB,EAAG,WAAW,EAAS,CAAW,EAClC,EAAS,EAAG,SAAS,EAAS,GAAG,EACjC,EAAU,GAEZ,EAAG,UAAU,EAAQ,CAAK,EAC1B,GAAW,EAAM,UACnB,EACA,MAAe,CACb,OAAO,CACT,EACA,OAAc,CACZ,GAAI,CACF,EAAG,UAAU,CAAM,CACrB,MAAQ,CAER,CACF,CACF,CACF,CACF,OAzCC,EAAW,EAEG,EAAA,EAAA,EAAO,EAAM,WAAW,CAAA"}
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../config.cjs"),t=require("../../types.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs"),a=require("../../runnerSpec.cjs"),o=require("../../session.cjs"),s=require("../AnsiScrub/ansiScrub.cjs");require("../AnsiScrub/index.cjs");let c=require("inversify");require("reflect-metadata/lite");const{Terminal:l}=(0,require("node:module").createRequire)(require("url").pathToFileURL(__filename).href)(`@xterm/headless`);let u=class{spawner;logFile;processControl;clock;runs=new Map;constructor(e,t,n,r){this.spawner=e,this.logFile=t,this.processControl=n,this.clock=r}async launch(t){let n=t.cols??120,r=t.rows??30,i=this.logFile.open(t.id),c=new l({cols:n,rows:r,scrollback:2e3,allowProposedApi:!0}),u=e.getResultMaxBytes()*2,f=await this.spawner.spawn({command:t.command,cwd:t.cwd,env:{...process.env,...a.NO_TERMINAL_INPUT_ENV,[o.PI_SESSION_ID_ENV]:t.sessionId},cols:n,rows:r}),p=``,m=!0,h=!1,g=()=>void 0,_=new Promise(e=>{g=e}),v=new Set;f.onData(e=>{c.write(e);let t=s.scrubTerminalOutput(e);i.append(t),m&&(p+=t,p.length>u&&(p=p.slice(-u)));for(let t of v)t(e)}),f.onExit(e=>{h||(h=!0,this.runs.delete(t.name),i.close(),g({code:e.exitCode,signal:null}))});let y={id:t.id,name:t.name,pid:f.pid,logPath:i.path,backend:`native`,output:()=>p,completion:()=>_,detach:()=>{m=!1,p=``},stop:()=>this.stopChild(f,t.name),write:e=>f.write(e),screen:()=>d(c),onData:e=>(v.add(e),()=>v.delete(e)),resize:(e,t)=>{f.resize(e,t),c.resize(e,t)}};return this.runs.set(t.name,y),y}get(e){return this.runs.get(e)}write(e,t){let n=this.runs.get(e);return n?(n.write(t.endsWith(`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../types/config.cjs"),t=require("../../types/index.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs"),a=require("../../schemas/runnerSpec.cjs"),o=require("../runs/session.cjs"),s=require("../AnsiScrub/ansiScrub.cjs");require("../AnsiScrub/index.cjs");let c=require("inversify");require("reflect-metadata/lite");const{Terminal:l}=(0,require("node:module").createRequire)(require("url").pathToFileURL(__filename).href)(`@xterm/headless`);let u=class{spawner;logFile;processControl;clock;runs=new Map;constructor(e,t,n,r){this.spawner=e,this.logFile=t,this.processControl=n,this.clock=r}async launch(t){let n=t.cols??120,r=t.rows??30,i=this.logFile.open(t.id),c=new l({cols:n,rows:r,scrollback:2e3,allowProposedApi:!0}),u=e.getResultMaxBytes()*2,f=await this.spawner.spawn({command:t.command,cwd:t.cwd,env:{...process.env,...a.NO_TERMINAL_INPUT_ENV,[o.PI_SESSION_ID_ENV]:t.sessionId},cols:n,rows:r}),p=``,m=!0,h=!1,g=()=>void 0,_=new Promise(e=>{g=e}),v=new Set;f.onData(e=>{c.write(e);let t=s.scrubTerminalOutput(e);i.append(t),m&&(p+=t,p.length>u&&(p=p.slice(-u)));for(let t of v)t(e)}),f.onExit(e=>{h||(h=!0,this.runs.delete(t.name),i.close(),g({code:e.exitCode,signal:null}))});let y={id:t.id,name:t.name,pid:f.pid,logPath:i.path,backend:`native`,output:()=>p,completion:()=>_,detach:()=>{m=!1,p=``},stop:()=>this.stopChild(f,t.name),write:e=>f.write(e),screen:()=>d(c),onData:e=>(v.add(e),()=>v.delete(e)),resize:(e,t)=>{f.resize(e,t),c.resize(e,t)}};return this.runs.set(t.name,y),y}get(e){return this.runs.get(e)}write(e,t){let n=this.runs.get(e);return n?(n.write(t.endsWith(`
2
2
  `)?t:`${t}
3
3
  `),!0):!1}list(){return[...this.runs.values()]}async disposeAll(){let e=[...this.runs.values()];this.runs.clear();for(let t of e)await t.stop()}async stopChild(e,t){if(this.runs.delete(t),!this.processControl.isAlive(e.pid))return!1;e.kill(`SIGTERM`);let n=this.clock.now()+2e3;for(;this.clock.now()<n;){if(!this.processControl.isAlive(e.pid))return!0;await new Promise(e=>{this.clock.after(100,e)})}return e.kill(`SIGKILL`),!0}};u=i.default([(0,c.injectable)(),r.default(0,(0,c.inject)(t.TYPES.PtySpawner)),r.default(1,(0,c.inject)(t.TYPES.LogFile)),r.default(2,(0,c.inject)(t.TYPES.ProcessControl)),r.default(3,(0,c.inject)(t.TYPES.Clock)),n.default(`design:paramtypes`,[Object,Object,Object,Object])],u);function d(e){let t=e.buffer.active,n=[];for(let r=t.baseY;r<t.baseY+e.rows;r+=1)n.push(t.getLine(r)?.translateToString(!0)??``);for(;n.length>0&&n.at(-1)===``;)n.pop();return n.join(`
4
4
  `)}Object.defineProperty(exports,"PtyHost",{enumerable:!0,get:function(){return u}});
@@ -1 +1 @@
1
- {"version":3,"file":"PtyHost.cjs","names":["createRequire","getResultMaxBytes","NO_TERMINAL_INPUT_ENV","PI_SESSION_ID_ENV","scrubTerminalOutput","injectable","inject","TYPES"],"sources":["../../../src/services/PtyHost/PtyHost.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport type { Terminal } from '@xterm/headless';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { getResultMaxBytes } from '../../config.ts';\nimport { NO_TERMINAL_INPUT_ENV } from '../../runnerSpec.ts';\nimport { TYPES } from '../../types.ts';\nimport { scrubTerminalOutput } from '../AnsiScrub/index.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILogFile } from '../LogFile/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IPtySpawner, PtyProcess } from '../PtySpawner/index.ts';\nimport type { ExitResult } from '../Spawner/index.ts';\nimport type { IPtyHost, PtyLaunchRequest, PtyRun } from './types.ts';\nimport { PI_SESSION_ID_ENV } from '../../session.ts';\n\nconst DEFAULT_COLS = 120;\nconst DEFAULT_ROWS = 30;\n/** Enough history for the overlay to scroll without holding a whole build. */\nconst SCROLLBACK = 2_000;\nconst MEMORY_BUFFER_FACTOR = 2;\nconst TERM_GRACE_MS = 2_000;\nconst LIVENESS_POLL_MS = 100;\nconst NEWLINE = '\\n';\n\n/**\n * `@xterm/headless` publishes CommonJS, and Node's ESM loader cannot detect its\n * named exports, so it is required rather than imported.\n */\nconst { Terminal: HeadlessTerminal } = createRequire(import.meta.url)(\n '@xterm/headless',\n) as typeof import('@xterm/headless');\n\n@injectable()\nexport class PtyHost implements IPtyHost {\n private readonly runs = new Map<string, PtyRun>();\n\n constructor(\n @inject(TYPES.PtySpawner) private readonly spawner: IPtySpawner,\n @inject(TYPES.LogFile) private readonly logFile: ILogFile,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.Clock) private readonly clock: IClock,\n ) {}\n\n async launch(request: PtyLaunchRequest): Promise<PtyRun> {\n const cols = request.cols ?? DEFAULT_COLS;\n const rows = request.rows ?? DEFAULT_ROWS;\n const writer = this.logFile.open(request.id);\n const terminal = new HeadlessTerminal({ cols, rows, scrollback: SCROLLBACK, allowProposedApi: true });\n const memoryLimit = getResultMaxBytes() * MEMORY_BUFFER_FACTOR;\n\n const child = await this.spawner.spawn({\n command: request.command,\n cwd: request.cwd,\n env: { ...process.env, ...NO_TERMINAL_INPUT_ENV, [PI_SESSION_ID_ENV]: request.sessionId },\n cols,\n rows,\n });\n\n let buffer = '';\n let buffering = true;\n let settled = false;\n let resolveCompletion: (result: ExitResult) => void = () => undefined;\n const completion = new Promise<ExitResult>((resolve) => {\n resolveCompletion = resolve;\n });\n const dataHandlers = new Set<(data: string) => void>();\n\n child.onData((data) => {\n // The terminal keeps the screen; the log keeps text worth grepping.\n terminal.write(data);\n const scrubbed = scrubTerminalOutput(data);\n writer.append(scrubbed);\n if (buffering) {\n buffer += scrubbed;\n if (buffer.length > memoryLimit) buffer = buffer.slice(-memoryLimit);\n }\n for (const handler of dataHandlers) handler(data);\n });\n\n child.onExit((result) => {\n if (settled) return;\n settled = true;\n this.runs.delete(request.name);\n writer.close();\n resolveCompletion({ code: result.exitCode, signal: null });\n });\n\n const run: PtyRun = {\n id: request.id,\n name: request.name,\n pid: child.pid,\n logPath: writer.path,\n backend: 'native',\n output: () => buffer,\n completion: () => completion,\n detach: () => {\n buffering = false;\n buffer = '';\n },\n stop: () => this.stopChild(child, request.name),\n write: (text) => child.write(text),\n screen: () => renderScreen(terminal),\n onData: (handler) => {\n dataHandlers.add(handler);\n return () => dataHandlers.delete(handler);\n },\n resize: (nextCols, nextRows) => {\n child.resize(nextCols, nextRows);\n terminal.resize(nextCols, nextRows);\n },\n };\n\n this.runs.set(request.name, run);\n return run;\n }\n\n get(name: string): PtyRun | undefined {\n return this.runs.get(name);\n }\n\n write(name: string, text: string): boolean {\n const run = this.runs.get(name);\n if (!run) return false;\n run.write(text.endsWith(NEWLINE) ? text : `${text}${NEWLINE}`);\n return true;\n }\n\n list(): PtyRun[] {\n return [...this.runs.values()];\n }\n\n async disposeAll(): Promise<void> {\n const runs = [...this.runs.values()];\n this.runs.clear();\n for (const run of runs) await run.stop();\n }\n\n private async stopChild(child: PtyProcess, name: string): Promise<boolean> {\n this.runs.delete(name);\n if (!this.processControl.isAlive(child.pid)) return false;\n\n child.kill('SIGTERM');\n const deadline = this.clock.now() + TERM_GRACE_MS;\n while (this.clock.now() < deadline) {\n if (!this.processControl.isAlive(child.pid)) return true;\n await new Promise<void>((resolve) => {\n this.clock.after(LIVENESS_POLL_MS, resolve);\n });\n }\n child.kill('SIGKILL');\n return true;\n }\n}\n\n/** The visible rows, trimmed of trailing blanks, as plain text. */\nfunction renderScreen(terminal: Terminal): string {\n const buffer = terminal.buffer.active;\n const lines: string[] = [];\n for (let row = buffer.baseY; row < buffer.baseY + terminal.rows; row += 1) {\n lines.push(buffer.getLine(row)?.translateToString(true) ?? '');\n }\n while (lines.length > 0 && lines.at(-1) === '') lines.pop();\n return lines.join(NEWLINE);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PtyHost.cjs","names":["createRequire","getResultMaxBytes","NO_TERMINAL_INPUT_ENV","PI_SESSION_ID_ENV","scrubTerminalOutput","injectable","inject","TYPES"],"sources":["../../../src/services/PtyHost/PtyHost.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport type { Terminal } from '@xterm/headless';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { NO_TERMINAL_INPUT_ENV } from '../../schemas/runnerSpec.ts';\nimport { getResultMaxBytes } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport { scrubTerminalOutput } from '../AnsiScrub/index.ts';\nimport type { IClock } from '../Clock/index.ts';\nimport type { ILogFile } from '../LogFile/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IPtySpawner, PtyProcess } from '../PtySpawner/index.ts';\nimport { PI_SESSION_ID_ENV } from '../runs/session.ts';\nimport type { ExitResult } from '../Spawner/index.ts';\nimport type { IPtyHost, PtyLaunchRequest, PtyRun } from './types.ts';\n\nconst DEFAULT_COLS = 120;\nconst DEFAULT_ROWS = 30;\n/** Enough history for the overlay to scroll without holding a whole build. */\nconst SCROLLBACK = 2_000;\nconst MEMORY_BUFFER_FACTOR = 2;\nconst TERM_GRACE_MS = 2_000;\nconst LIVENESS_POLL_MS = 100;\nconst NEWLINE = '\\n';\n\n/**\n * `@xterm/headless` publishes CommonJS, and Node's ESM loader cannot detect its\n * named exports, so it is required rather than imported.\n */\nconst { Terminal: HeadlessTerminal } = createRequire(import.meta.url)(\n '@xterm/headless',\n) as typeof import('@xterm/headless');\n\n@injectable()\nexport class PtyHost implements IPtyHost {\n private readonly runs = new Map<string, PtyRun>();\n\n constructor(\n @inject(TYPES.PtySpawner) private readonly spawner: IPtySpawner,\n @inject(TYPES.LogFile) private readonly logFile: ILogFile,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.Clock) private readonly clock: IClock,\n ) {}\n\n async launch(request: PtyLaunchRequest): Promise<PtyRun> {\n const cols = request.cols ?? DEFAULT_COLS;\n const rows = request.rows ?? DEFAULT_ROWS;\n const writer = this.logFile.open(request.id);\n const terminal = new HeadlessTerminal({ cols, rows, scrollback: SCROLLBACK, allowProposedApi: true });\n const memoryLimit = getResultMaxBytes() * MEMORY_BUFFER_FACTOR;\n\n const child = await this.spawner.spawn({\n command: request.command,\n cwd: request.cwd,\n env: { ...process.env, ...NO_TERMINAL_INPUT_ENV, [PI_SESSION_ID_ENV]: request.sessionId },\n cols,\n rows,\n });\n\n let buffer = '';\n let buffering = true;\n let settled = false;\n let resolveCompletion: (result: ExitResult) => void = () => undefined;\n const completion = new Promise<ExitResult>((resolve) => {\n resolveCompletion = resolve;\n });\n const dataHandlers = new Set<(data: string) => void>();\n\n child.onData((data) => {\n // The terminal keeps the screen; the log keeps text worth grepping.\n terminal.write(data);\n const scrubbed = scrubTerminalOutput(data);\n writer.append(scrubbed);\n if (buffering) {\n buffer += scrubbed;\n if (buffer.length > memoryLimit) buffer = buffer.slice(-memoryLimit);\n }\n for (const handler of dataHandlers) handler(data);\n });\n\n child.onExit((result) => {\n if (settled) return;\n settled = true;\n this.runs.delete(request.name);\n writer.close();\n resolveCompletion({ code: result.exitCode, signal: null });\n });\n\n const run: PtyRun = {\n id: request.id,\n name: request.name,\n pid: child.pid,\n logPath: writer.path,\n backend: 'native',\n output: () => buffer,\n completion: () => completion,\n detach: () => {\n buffering = false;\n buffer = '';\n },\n stop: () => this.stopChild(child, request.name),\n write: (text) => child.write(text),\n screen: () => renderScreen(terminal),\n onData: (handler) => {\n dataHandlers.add(handler);\n return () => dataHandlers.delete(handler);\n },\n resize: (nextCols, nextRows) => {\n child.resize(nextCols, nextRows);\n terminal.resize(nextCols, nextRows);\n },\n };\n\n this.runs.set(request.name, run);\n return run;\n }\n\n get(name: string): PtyRun | undefined {\n return this.runs.get(name);\n }\n\n write(name: string, text: string): boolean {\n const run = this.runs.get(name);\n if (!run) return false;\n run.write(text.endsWith(NEWLINE) ? text : `${text}${NEWLINE}`);\n return true;\n }\n\n list(): PtyRun[] {\n return [...this.runs.values()];\n }\n\n async disposeAll(): Promise<void> {\n const runs = [...this.runs.values()];\n this.runs.clear();\n for (const run of runs) await run.stop();\n }\n\n private async stopChild(child: PtyProcess, name: string): Promise<boolean> {\n this.runs.delete(name);\n if (!this.processControl.isAlive(child.pid)) return false;\n\n child.kill('SIGTERM');\n const deadline = this.clock.now() + TERM_GRACE_MS;\n while (this.clock.now() < deadline) {\n if (!this.processControl.isAlive(child.pid)) return true;\n await new Promise<void>((resolve) => {\n this.clock.after(LIVENESS_POLL_MS, resolve);\n });\n }\n child.kill('SIGKILL');\n return true;\n }\n}\n\n/** The visible rows, trimmed of trailing blanks, as plain text. */\nfunction renderScreen(terminal: Terminal): string {\n const buffer = terminal.buffer.active;\n const lines: string[] = [];\n for (let row = buffer.baseY; row < buffer.baseY + terminal.rows; row += 1) {\n lines.push(buffer.getLine(row)?.translateToString(true) ?? '');\n }\n while (lines.length > 0 && lines.at(-1) === '') lines.pop();\n return lines.join(NEWLINE);\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{getResultMaxBytes as e}from"../../config.mjs";import{TYPES as t}from"../../types.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{NO_TERMINAL_INPUT_ENV as a}from"../../runnerSpec.mjs";import{PI_SESSION_ID_ENV as o}from"../../session.mjs";import{scrubTerminalOutput as s}from"../AnsiScrub/ansiScrub.mjs";import"../AnsiScrub/index.mjs";import{createRequire as c}from"node:module";import{inject as l,injectable as u}from"inversify";import"reflect-metadata/lite";const{Terminal:d}=c(import.meta.url)(`@xterm/headless`);let f=class{spawner;logFile;processControl;clock;runs=new Map;constructor(e,t,n,r){this.spawner=e,this.logFile=t,this.processControl=n,this.clock=r}async launch(t){let n=t.cols??120,r=t.rows??30,i=this.logFile.open(t.id),c=new d({cols:n,rows:r,scrollback:2e3,allowProposedApi:!0}),l=e()*2,u=await this.spawner.spawn({command:t.command,cwd:t.cwd,env:{...process.env,...a,[o]:t.sessionId},cols:n,rows:r}),f=``,m=!0,h=!1,g=()=>void 0,_=new Promise(e=>{g=e}),v=new Set;u.onData(e=>{c.write(e);let t=s(e);i.append(t),m&&(f+=t,f.length>l&&(f=f.slice(-l)));for(let t of v)t(e)}),u.onExit(e=>{h||(h=!0,this.runs.delete(t.name),i.close(),g({code:e.exitCode,signal:null}))});let y={id:t.id,name:t.name,pid:u.pid,logPath:i.path,backend:`native`,output:()=>f,completion:()=>_,detach:()=>{m=!1,f=``},stop:()=>this.stopChild(u,t.name),write:e=>u.write(e),screen:()=>p(c),onData:e=>(v.add(e),()=>v.delete(e)),resize:(e,t)=>{u.resize(e,t),c.resize(e,t)}};return this.runs.set(t.name,y),y}get(e){return this.runs.get(e)}write(e,t){let n=this.runs.get(e);return n?(n.write(t.endsWith(`
1
+ import{getResultMaxBytes as e}from"../../types/config.mjs";import{TYPES as t}from"../../types/index.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{NO_TERMINAL_INPUT_ENV as a}from"../../schemas/runnerSpec.mjs";import{PI_SESSION_ID_ENV as o}from"../runs/session.mjs";import{scrubTerminalOutput as s}from"../AnsiScrub/ansiScrub.mjs";import"../AnsiScrub/index.mjs";import{createRequire as c}from"node:module";import{inject as l,injectable as u}from"inversify";import"reflect-metadata/lite";const{Terminal:d}=c(import.meta.url)(`@xterm/headless`);let f=class{spawner;logFile;processControl;clock;runs=new Map;constructor(e,t,n,r){this.spawner=e,this.logFile=t,this.processControl=n,this.clock=r}async launch(t){let n=t.cols??120,r=t.rows??30,i=this.logFile.open(t.id),c=new d({cols:n,rows:r,scrollback:2e3,allowProposedApi:!0}),l=e()*2,u=await this.spawner.spawn({command:t.command,cwd:t.cwd,env:{...process.env,...a,[o]:t.sessionId},cols:n,rows:r}),f=``,m=!0,h=!1,g=()=>void 0,_=new Promise(e=>{g=e}),v=new Set;u.onData(e=>{c.write(e);let t=s(e);i.append(t),m&&(f+=t,f.length>l&&(f=f.slice(-l)));for(let t of v)t(e)}),u.onExit(e=>{h||(h=!0,this.runs.delete(t.name),i.close(),g({code:e.exitCode,signal:null}))});let y={id:t.id,name:t.name,pid:u.pid,logPath:i.path,backend:`native`,output:()=>f,completion:()=>_,detach:()=>{m=!1,f=``},stop:()=>this.stopChild(u,t.name),write:e=>u.write(e),screen:()=>p(c),onData:e=>(v.add(e),()=>v.delete(e)),resize:(e,t)=>{u.resize(e,t),c.resize(e,t)}};return this.runs.set(t.name,y),y}get(e){return this.runs.get(e)}write(e,t){let n=this.runs.get(e);return n?(n.write(t.endsWith(`
2
2
  `)?t:`${t}
3
3
  `),!0):!1}list(){return[...this.runs.values()]}async disposeAll(){let e=[...this.runs.values()];this.runs.clear();for(let t of e)await t.stop()}async stopChild(e,t){if(this.runs.delete(t),!this.processControl.isAlive(e.pid))return!1;e.kill(`SIGTERM`);let n=this.clock.now()+2e3;for(;this.clock.now()<n;){if(!this.processControl.isAlive(e.pid))return!0;await new Promise(e=>{this.clock.after(100,e)})}return e.kill(`SIGKILL`),!0}};f=i([u(),r(0,l(t.PtySpawner)),r(1,l(t.LogFile)),r(2,l(t.ProcessControl)),r(3,l(t.Clock)),n(`design:paramtypes`,[Object,Object,Object,Object])],f);function p(e){let t=e.buffer.active,n=[];for(let r=t.baseY;r<t.baseY+e.rows;r+=1)n.push(t.getLine(r)?.translateToString(!0)??``);for(;n.length>0&&n.at(-1)===``;)n.pop();return n.join(`
4
4
  `)}export{f as PtyHost};