@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":"PtyHost.mjs","names":[],"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.mjs","names":[],"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,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");let s=require("inversify");require("reflect-metadata/lite");let c=require("node:crypto"),l=require("node:fs");l=e.__toESM(l,1);let u=require("node:path");u=e.__toESM(u,1);let d=require("node:module"),f=require("@rmux/sdk");const p=1e3,m=`doom-runner-`,h=/^doom-runner-[A-Za-z0-9_-]+$/,g={NO_COLOR:`1`,CI:`1`},_={"darwin-arm64":`@agimon-ai/doompi-runner-rmux-darwin-arm64`,"darwin-x64":`@agimon-ai/doompi-runner-rmux-darwin-x64`,"linux-arm64":`@agimon-ai/doompi-runner-rmux-linux-arm64`,"linux-x64":`@agimon-ai/doompi-runner-rmux-linux-x64`};let v=class{paths;clientPromise;runs=new Map;completions=new Map;constructor(e){this.paths=e}async launch(e){let n=await this.client();if(!n)return;this.paths.ensureDirectories();let r=`${m}${e.id}`,i=this.paths.logPathFor(e.id),a=this.auxPaths(e.id,e.sessionId),s=!1,c=!1;try{this.prepareFiles(e,i,a),await n.cmd(`new-session`,`-d`,`-s`,r,`-c`,e.cwd,process.execPath,o.runtimeEntry(`runnerHost`),a.spec,a.gate,a.exit,{check:!0}),s=!0,await n.cmd(`pipe-pane`,`-t`,r,this.logPipeCommand(i,a.logDone),{check:!0});let u=n.session(r).pane(0,0),d=await C(n,r);l.default.writeFileSync(a.gate,``,{mode:384}),c=!0;let f=!0,p=this.monitor(n,u,r,a);this.trackCompletion(r,p);let m={id:e.id,name:e.name,pid:d,logPath:i,backend:`rmux`,backendTarget:r,output:()=>f?j(i,t.getResultMaxBytes()*2):``,completion:()=>p,detach:()=>{f=!1},stop:()=>this.stop(r,d)};return e.interactive&&this.runs.set(e.name,this.interactiveRun(m,u,p)),p.finally(()=>this.runs.delete(e.name)).catch(e=>process.emitWarning(`RMUX completion failed for ${r}: ${N(e)}`)),m}catch(e){if(c)throw e;s&&await n.session(r).kill().catch(e=>process.emitWarning(`Could not clean up failed RMUX launch ${r}: ${N(e)}`)),o.cleanupSupervisorFiles(a),process.emitWarning(`RMUX launch unavailable, using native runner: ${N(e)}`);return}}async stop(e,t){if(!h.test(e)||!Number.isInteger(t)||t<=0)return!1;let n=await this.client();if(!n)return!1;try{if(await k(n,e))return!0;let r=await C(n,e);if(r!==t)return!1;let i=A(r,e);return i===`gone`?!0:i===`failed`?!1:await D(n,e,3e3)||await k(n,e)?!0:await C(n,e)===t&&E(n,e)}catch(t){return process.emitWarning(`Could not stop RMUX target ${e}: ${N(t)}`),!1}}async watch(e,t,n){let r=await this.client();if(!r)return;let i=r.session(t).pane(0,0);try{await r.displayMessage(`#{pane_id}`,{target:t})}catch(e){process.emitWarning(`Could not watch RMUX target ${t}: ${N(e)}`);return}let a=this.monitor(r,i,t,this.auxPaths(e,n));return this.trackCompletion(t,a),a}async input(e,t){let n=await this.client();return n?await n.session(e).pane(0,0).sendText(t).catch(t=>{process.emitWarning(`Could not send input to RMUX target ${e}: ${N(t)}`)})!==void 0:!1}get(e){return this.runs.get(e)}readOutcome(e,t){return o.readExitMetadata(this.auxPaths(e,t).exit)}trackCompletion(e,t){this.completions.set(e,t),t.finally(()=>this.completions.delete(e)).catch(t=>process.emitWarning(`RMUX monitor failed for ${e}: ${N(t)}`))}client(){return this.clientPromise??=this.resolveClient(),this.clientPromise}async resolveClient(){let e=[b(),`rmux`].filter(e=>e!==void 0);for(let t of e)try{t!==`rmux`&&l.default.chmodSync(t,493);let e=new f.RMUX({binary:t,socketName:x(this.paths.repositoryPath())});return await e.capabilities(),e}catch(e){process.emitWarning(`RMUX candidate ${t} is unavailable: ${N(e)}`)}}prepareFiles(e,t,n){o.cleanupSupervisorFiles(n),l.default.writeFileSync(t,``,{mode:384});let r=S(e.sessionId,e.interactive),i={command:e.command,cwd:e.cwd,env:r};o.writeCommandSpec(n.spec,i)}auxPaths(e,t){return o.supervisorPaths(this.paths.stateDirectory(t),e)}logPipeCommand(e,n){return o.shellJoin([process.execPath,o.runtimeEntry(`logSink`),e,this.paths.rotatedLogPathFor(u.default.basename(e,`.log`)),String(t.getLogMaxBytes()),n])}async monitor(e,t,n,r){try{let t=await w(e,n,r.exit);return await T(e,n,r.logDone),o.readExitMetadata(r.exit)??{code:t,signal:null}}finally{await k(e,n).catch(()=>!1)||await t.server.session(n).kill().catch(e=>process.emitWarning(`Could not clean up completed RMUX target ${n}: ${N(e)}`)),o.cleanupSupervisorFiles(r)}}interactiveRun(e,t,n){let r=``,i=!0,a=new Set;return(async()=>{for(;i;){let n=await t.captureText().catch(t=>(process.emitWarning(`Could not capture RMUX screen for ${e.name}: ${N(t)}`),r));if(n!==r){r=n;for(let e of a)e(n)}await M(100)}})(),n.finally(()=>{i=!1}).catch(t=>process.emitWarning(`RMUX interactive completion failed for ${e.name}: ${N(t)}`)),{...e,write:e=>{t.sendText(e).catch(e=>process.emitWarning(N(e)))},screen:()=>r,onData:e=>(a.add(e),()=>a.delete(e)),resize:(e,n)=>{t.resize({width:e,height:n}).catch(e=>process.emitWarning(N(e)))}}}};v=a.default([(0,s.injectable)(),i.default(0,(0,s.inject)(n.TYPES.RunnerPaths)),r.default(`design:paramtypes`,[Object])],v);function y(e,t){let n=`${e}-${t}`,r=_[n];return r||process.emitWarning(`Bundled RMUX binary is unavailable for unsupported target ${n}`),r}function b(){let e=y(process.platform,process.arch);if(e)try{let t=(0,d.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`${e}/package.json`);return u.default.join(u.default.dirname(t),`vendor`,`bin`,`rmux`)}catch(t){process.emitWarning(`Bundled RMUX binary is unavailable for ${e}: ${N(t)}`);return}}function x(e){let t=(0,c.createHash)(`sha256`).update(e).digest(`hex`).slice(0,12);return`${m}${t}`}function S(e,t){let n={...process.env,...t?{}:g,...o.NO_TERMINAL_INPUT_ENV,PI_SESSION_ID:e};return t||delete n.FORCE_COLOR,Object.fromEntries(Object.entries(n).filter(e=>e[1]!==void 0))}async function C(e,t){let n=await e.displayMessage(`#{pane_pid}`,{target:t}),r=Number(n.message);if(!Number.isInteger(r)||r<=0)throw Error(`RMUX did not report a pane pid for ${t}`);return r}async function w(e,t,n){for(;;){if(l.default.existsSync(n))return null;let r=await O(e,t);if(r.exited)return r.code;await M(100)}}async function T(e,t,n){let r=Date.now()+2e3;for(;Date.now()<r;){if(l.default.existsSync(n)||await k(e,t))return;await M(25)}}async function E(e,t){return new Promise(n=>{let r=!1,i=e=>{r||(r=!0,clearTimeout(a),n(e))},a=setTimeout(()=>{process.emitWarning(`Timed out closing RMUX target ${t} after ${p}ms`),i(!1)},p);e.session(t).kill().then(()=>i(!0),e=>{process.emitWarning(`Could not close RMUX target ${t}: ${N(e)}`),i(!1)})})}async function D(e,t,n){let r=Date.now()+n;for(;Date.now()<r;){try{if((await O(e,t)).exited)return!0}catch(e){return process.emitWarning(`Could not verify RMUX target ${t} stopped: ${N(e)}`),!1}await M(100)}return!1}async function O(e,t){let n;try{let r=await e.displayMessage(`#{pane_dead}:#{pane_dead_status}:#{session_name}`,{target:t});n=typeof r.message==`string`?r.message:``}catch{return{exited:!0,code:null}}let[r,i,...a]=n.trim().split(`:`);if(a.join(`:`)===``)return{exited:!0,code:null};if(r===`1`||r===`true`){let e=Number(i);return{exited:!0,code:Number.isInteger(e)?e:null}}return{exited:!1,code:null}}async function k(e,t){try{return(await e.cmd(`has-session`,`-t`,t)).returnCode!==0}catch{return!0}}function A(e,t){try{return process.kill(e,`SIGTERM`),`signalled`}catch(e){return e.code===`ESRCH`?`gone`:(process.emitWarning(`Could not signal RMUX target ${t}: ${N(e)}`),`failed`)}}function j(e,t){try{let n=l.default.readFileSync(e,`utf8`);return n.length>t?n.slice(-t):n}catch(t){return t.code!==`ENOENT`&&process.emitWarning(`Could not read RMUX log ${e}: ${N(t)}`),``}}function M(e){return new Promise(t=>{setTimeout(t,e).unref?.()})}function N(e){return e instanceof Error?e.message:String(e)}Object.defineProperty(exports,"RmuxBackend",{enumerable:!0,get:function(){return v}}),exports.rmuxPackageForTarget=y;
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");let s=require("inversify");require("reflect-metadata/lite");let c=require("node:crypto"),l=require("node:fs");l=e.__toESM(l,1);let u=require("node:path");u=e.__toESM(u,1);let d=require("node:module"),f=require("@rmux/sdk");const p=1e3,m=`doom-runner-`,h=/^doom-runner-[A-Za-z0-9_-]+$/,g={NO_COLOR:`1`,CI:`1`},_={"darwin-arm64":`@agimon-ai/doompi-runner-rmux-darwin-arm64`,"darwin-x64":`@agimon-ai/doompi-runner-rmux-darwin-x64`,"linux-arm64":`@agimon-ai/doompi-runner-rmux-linux-arm64`,"linux-x64":`@agimon-ai/doompi-runner-rmux-linux-x64`};let v=class{paths;clientPromise;runs=new Map;completions=new Map;constructor(e){this.paths=e}async launch(e){let n=await this.client();if(!n)return;this.paths.ensureDirectories();let r=`${m}${e.id}`,i=this.paths.logPathFor(e.id),a=this.auxPaths(e.id,e.sessionId),s=!1,c=!1;try{this.prepareFiles(e,i,a),await n.cmd(`new-session`,`-d`,`-s`,r,`-c`,e.cwd,process.execPath,o.runtimeEntry(`runnerHost`),a.spec,a.gate,a.exit,{check:!0}),s=!0,await n.cmd(`pipe-pane`,`-t`,r,this.logPipeCommand(i,a.logDone),{check:!0});let u=n.session(r).pane(0,0),d=await C(n,r);l.default.writeFileSync(a.gate,``,{mode:384}),c=!0;let f=!0,p=this.monitor(n,u,r,a);this.trackCompletion(r,p);let m={id:e.id,name:e.name,pid:d,logPath:i,backend:`rmux`,backendTarget:r,output:()=>f?j(i,t.getResultMaxBytes()*2):``,completion:()=>p,detach:()=>{f=!1},stop:()=>this.stop(r,d)};return e.interactive&&this.runs.set(e.name,this.interactiveRun(m,u,p)),p.finally(()=>this.runs.delete(e.name)).catch(e=>process.emitWarning(`RMUX completion failed for ${r}: ${N(e)}`)),m}catch(e){if(c)throw e;s&&await n.session(r).kill().catch(e=>process.emitWarning(`Could not clean up failed RMUX launch ${r}: ${N(e)}`)),o.cleanupSupervisorFiles(a),process.emitWarning(`RMUX launch unavailable, using native runner: ${N(e)}`);return}}async stop(e,t){if(!h.test(e)||!Number.isInteger(t)||t<=0)return!1;let n=await this.client();if(!n)return!1;try{if(await k(n,e))return!0;let r=await C(n,e);if(r!==t)return!1;let i=A(r,e);return i===`gone`?!0:i===`failed`?!1:await D(n,e,3e3)||await k(n,e)?!0:await C(n,e)===t&&E(n,e)}catch(t){return process.emitWarning(`Could not stop RMUX target ${e}: ${N(t)}`),!1}}async watch(e,t,n){let r=await this.client();if(!r)return;let i=r.session(t).pane(0,0);try{await r.displayMessage(`#{pane_id}`,{target:t})}catch(e){process.emitWarning(`Could not watch RMUX target ${t}: ${N(e)}`);return}let a=this.monitor(r,i,t,this.auxPaths(e,n));return this.trackCompletion(t,a),a}async input(e,t){let n=await this.client();return n?await n.session(e).pane(0,0).sendText(t).catch(t=>{process.emitWarning(`Could not send input to RMUX target ${e}: ${N(t)}`)})!==void 0:!1}get(e){return this.runs.get(e)}readOutcome(e,t){return o.readExitMetadata(this.auxPaths(e,t).exit)}trackCompletion(e,t){this.completions.set(e,t),t.finally(()=>this.completions.delete(e)).catch(t=>process.emitWarning(`RMUX monitor failed for ${e}: ${N(t)}`))}client(){return this.clientPromise??=this.resolveClient(),this.clientPromise}async resolveClient(){let e=[b(),`rmux`].filter(e=>e!==void 0);for(let t of e)try{t!==`rmux`&&l.default.chmodSync(t,493);let e=new f.RMUX({binary:t,socketName:x(this.paths.repositoryPath())});return await e.capabilities(),e}catch(e){process.emitWarning(`RMUX candidate ${t} is unavailable: ${N(e)}`)}}prepareFiles(e,t,n){o.cleanupSupervisorFiles(n),l.default.writeFileSync(t,``,{mode:384});let r=S(e.sessionId,e.interactive),i={command:e.command,cwd:e.cwd,env:r};o.writeCommandSpec(n.spec,i)}auxPaths(e,t){return o.supervisorPaths(this.paths.stateDirectory(t),e)}logPipeCommand(e,n){return o.shellJoin([process.execPath,o.runtimeEntry(`logSink`),e,this.paths.rotatedLogPathFor(u.default.basename(e,`.log`)),String(t.getLogMaxBytes()),n])}async monitor(e,t,n,r){try{let t=await w(e,n,r.exit);return await T(e,n,r.logDone),o.readExitMetadata(r.exit)??{code:t,signal:null}}finally{await k(e,n).catch(()=>!1)||await t.server.session(n).kill().catch(e=>process.emitWarning(`Could not clean up completed RMUX target ${n}: ${N(e)}`)),o.cleanupSupervisorFiles(r)}}interactiveRun(e,t,n){let r=``,i=!0,a=new Set;return(async()=>{for(;i;){let n=await t.captureText().catch(t=>(process.emitWarning(`Could not capture RMUX screen for ${e.name}: ${N(t)}`),r));if(n!==r){r=n;for(let e of a)e(n)}await M(100)}})(),n.finally(()=>{i=!1}).catch(t=>process.emitWarning(`RMUX interactive completion failed for ${e.name}: ${N(t)}`)),{...e,write:e=>{t.sendText(e).catch(e=>process.emitWarning(N(e)))},screen:()=>r,onData:e=>(a.add(e),()=>a.delete(e)),resize:(e,n)=>{t.resize({width:e,height:n}).catch(e=>process.emitWarning(N(e)))}}}};v=a.default([(0,s.injectable)(),i.default(0,(0,s.inject)(n.TYPES.RunnerPaths)),r.default(`design:paramtypes`,[Object])],v);function y(e,t){let n=`${e}-${t}`,r=_[n];return r||process.emitWarning(`Bundled RMUX binary is unavailable for unsupported target ${n}`),r}function b(){let e=y(process.platform,process.arch);if(e)try{let t=(0,d.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`${e}/package.json`);return u.default.join(u.default.dirname(t),`vendor`,`bin`,`rmux`)}catch(t){process.emitWarning(`Bundled RMUX binary is unavailable for ${e}: ${N(t)}`);return}}function x(e){let t=(0,c.createHash)(`sha256`).update(e).digest(`hex`).slice(0,12);return`${m}${t}`}function S(e,t){let n={...process.env,...t?{}:g,...o.NO_TERMINAL_INPUT_ENV,PI_SESSION_ID:e};return t||delete n.FORCE_COLOR,Object.fromEntries(Object.entries(n).filter(e=>e[1]!==void 0))}async function C(e,t){let n=await e.displayMessage(`#{pane_pid}`,{target:t}),r=Number(n.message);if(!Number.isInteger(r)||r<=0)throw Error(`RMUX did not report a pane pid for ${t}`);return r}async function w(e,t,n){for(;;){if(l.default.existsSync(n))return null;let r=await O(e,t);if(r.exited)return r.code;await M(100)}}async function T(e,t,n){let r=Date.now()+2e3;for(;Date.now()<r;){if(l.default.existsSync(n)||await k(e,t))return;await M(25)}}async function E(e,t){return new Promise(n=>{let r=!1,i=e=>{r||(r=!0,clearTimeout(a),n(e))},a=setTimeout(()=>{process.emitWarning(`Timed out closing RMUX target ${t} after ${p}ms`),i(!1)},p);e.session(t).kill().then(()=>i(!0),e=>{process.emitWarning(`Could not close RMUX target ${t}: ${N(e)}`),i(!1)})})}async function D(e,t,n){let r=Date.now()+n;for(;Date.now()<r;){try{if((await O(e,t)).exited)return!0}catch(e){return process.emitWarning(`Could not verify RMUX target ${t} stopped: ${N(e)}`),!1}await M(100)}return!1}async function O(e,t){let n;try{let r=await e.displayMessage(`#{pane_dead}:#{pane_dead_status}:#{session_name}`,{target:t});n=typeof r.message==`string`?r.message:``}catch{return{exited:!0,code:null}}let[r,i,...a]=n.trim().split(`:`);if(a.join(`:`)===``)return{exited:!0,code:null};if(r===`1`||r===`true`){let e=Number(i);return{exited:!0,code:Number.isInteger(e)?e:null}}return{exited:!1,code:null}}async function k(e,t){try{return(await e.cmd(`has-session`,`-t`,t)).returnCode!==0}catch{return!0}}function A(e,t){try{return process.kill(e,`SIGTERM`),`signalled`}catch(e){return e.code===`ESRCH`?`gone`:(process.emitWarning(`Could not signal RMUX target ${t}: ${N(e)}`),`failed`)}}function j(e,t){try{let n=l.default.readFileSync(e,`utf8`);return n.length>t?n.slice(-t):n}catch(t){return t.code!==`ENOENT`&&process.emitWarning(`Could not read RMUX log ${e}: ${N(t)}`),``}}function M(e){return new Promise(t=>{setTimeout(t,e).unref?.()})}function N(e){return e instanceof Error?e.message:String(e)}Object.defineProperty(exports,"RmuxBackend",{enumerable:!0,get:function(){return v}}),exports.rmuxPackageForTarget=y;
2
2
  //# sourceMappingURL=RmuxBackend.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RmuxBackend.cjs","names":["runtimeEntry","getResultMaxBytes","readExitMetadata","RMUX","supervisorPaths","shellJoin","path","getLogMaxBytes","injectable","inject","TYPES","createRequire","createHash","NO_TERMINAL_INPUT_ENV","fs"],"sources":["../../../src/services/RmuxBackend/RmuxBackend.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport { RMUX, type Pane, type Rmux } from '@rmux/sdk';\nimport 'reflect-metadata/lite';\nimport { getLogMaxBytes, getResultMaxBytes } from '../../config.ts';\nimport {\n type CommandSpec,\n NO_TERMINAL_INPUT_ENV,\n type SupervisorPaths,\n cleanupSupervisorFiles,\n readExitMetadata,\n runtimeEntry,\n shellJoin,\n supervisorPaths,\n writeCommandSpec,\n} from '../../runnerSpec.ts';\nimport { TYPES } from '../../types.ts';\nimport type { ExitResult } from '../Spawner/index.ts';\nimport type { RunHandle } from '../Launcher/index.ts';\nimport type { PtyRun } from '../PtyHost/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { IRmuxBackend, RmuxLaunchRequest } from './types.ts';\n\nconst POLL_MS = 100;\nconst LOG_DRAIN_TIMEOUT_MS = 2_000;\nconst LOG_DRAIN_POLL_MS = 25;\n/** Pane liveness, its exit status, and whether the session still exists, in one read. */\nconst PANE_STATE_FORMAT = '#{pane_dead}:#{pane_dead_status}:#{session_name}';\nconst STOP_GRACE_MS = 3_000;\nconst STOP_CLOSE_TIMEOUT_MS = 1_000;\nconst EXECUTABLE_MODE = 0o755;\nconst SESSION_PREFIX = 'doom-runner-';\nconst OWNED_TARGET_PATTERN = /^doom-runner-[A-Za-z0-9_-]+$/;\nconst SOCKET_HASH_LENGTH = 12;\nconst NON_INTERACTIVE_ENV = { NO_COLOR: '1', CI: '1' };\nconst PACKAGE_BY_PLATFORM: Readonly<Record<string, string>> = {\n 'darwin-arm64': '@agimon-ai/doompi-runner-rmux-darwin-arm64',\n 'darwin-x64': '@agimon-ai/doompi-runner-rmux-darwin-x64',\n 'linux-arm64': '@agimon-ai/doompi-runner-rmux-linux-arm64',\n 'linux-x64': '@agimon-ai/doompi-runner-rmux-linux-x64',\n};\n\n@injectable()\nexport class RmuxBackend implements IRmuxBackend {\n private clientPromise: Promise<Rmux | undefined> | undefined;\n private readonly runs = new Map<string, PtyRun>();\n private readonly completions = new Map<string, Promise<ExitResult>>();\n\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n async launch(request: RmuxLaunchRequest): Promise<RunHandle | undefined> {\n const rmux = await this.client();\n if (!rmux) return undefined;\n\n this.paths.ensureDirectories();\n const target = `${SESSION_PREFIX}${request.id}`;\n const logPath = this.paths.logPathFor(request.id);\n const aux = this.auxPaths(request.id, request.sessionId);\n let created = false;\n let released = false;\n\n try {\n this.prepareFiles(request, logPath, aux);\n await rmux.cmd(\n 'new-session',\n '-d',\n '-s',\n target,\n '-c',\n request.cwd,\n process.execPath,\n runtimeEntry('runnerHost'),\n aux.spec,\n aux.gate,\n aux.exit,\n { check: true },\n );\n created = true;\n // No remain-on-exit: a finished pane ends its session, and the last session\n // ending stops the server, so a completed run leaves no process behind.\n await rmux.cmd('pipe-pane', '-t', target, this.logPipeCommand(logPath, aux.logDone), { check: true });\n\n const pane = rmux.session(target).pane(0, 0);\n const pid = await panePid(rmux, target);\n fs.writeFileSync(aux.gate, '', { mode: 0o600 });\n released = true;\n\n let buffering = true;\n const completion = this.monitor(rmux, pane, target, aux);\n this.trackCompletion(target, completion);\n const stop = (): Promise<boolean> => this.stop(target, pid);\n const handle: RunHandle = {\n id: request.id,\n name: request.name,\n pid,\n logPath,\n backend: 'rmux',\n backendTarget: target,\n output: () => (buffering ? readTail(logPath, getResultMaxBytes() * 2) : ''),\n completion: () => completion,\n detach: () => {\n buffering = false;\n },\n stop,\n };\n\n if (request.interactive) this.runs.set(request.name, this.interactiveRun(handle, pane, completion));\n void completion\n .finally(() => this.runs.delete(request.name))\n .catch((error: unknown) => process.emitWarning(`RMUX completion failed for ${target}: ${errorMessage(error)}`));\n return handle;\n } catch (error) {\n if (released) throw error;\n if (created) {\n await rmux\n .session(target)\n .kill()\n .catch((cleanupError: unknown) =>\n process.emitWarning(`Could not clean up failed RMUX launch ${target}: ${errorMessage(cleanupError)}`),\n );\n }\n cleanupSupervisorFiles(aux);\n process.emitWarning(`RMUX launch unavailable, using native runner: ${errorMessage(error)}`);\n return undefined;\n }\n }\n\n async stop(target: string, expectedPid: number): Promise<boolean> {\n if (!OWNED_TARGET_PATTERN.test(target) || !Number.isInteger(expectedPid) || expectedPid <= 0) return false;\n\n const rmux = await this.client();\n if (!rmux) return false;\n try {\n // A session that already ended took its process group with it.\n if (await sessionMissing(rmux, target)) return true;\n\n const pid = await panePid(rmux, target);\n if (pid !== expectedPid) return false;\n const signalled = signalPane(pid, target);\n if (signalled === 'gone') return true;\n if (signalled === 'failed') return false;\n\n if (await waitForPaneStop(rmux, target, STOP_GRACE_MS)) return true;\n if (await sessionMissing(rmux, target)) return true;\n\n // Revalidate after the grace period so a replacement pane is never closed.\n const currentPid = await panePid(rmux, target);\n if (currentPid !== expectedPid) return false;\n\n return closeOwnedSession(rmux, target);\n } catch (error) {\n process.emitWarning(`Could not stop RMUX target ${target}: ${errorMessage(error)}`);\n return false;\n }\n }\n\n async watch(id: string, target: string, sessionId?: string): Promise<ExitResult | undefined> {\n const rmux = await this.client();\n if (!rmux) return undefined;\n const pane = rmux.session(target).pane(0, 0);\n try {\n await rmux.displayMessage('#{pane_id}', { target });\n } catch (error) {\n process.emitWarning(`Could not watch RMUX target ${target}: ${errorMessage(error)}`);\n return undefined;\n }\n const completion = this.monitor(rmux, pane, target, this.auxPaths(id, sessionId));\n this.trackCompletion(target, completion);\n return completion;\n }\n\n async input(target: string, text: string): Promise<boolean> {\n const rmux = await this.client();\n if (!rmux) return false;\n const result = await rmux\n .session(target)\n .pane(0, 0)\n .sendText(text)\n .catch((error: unknown) => {\n process.emitWarning(`Could not send input to RMUX target ${target}: ${errorMessage(error)}`);\n return undefined;\n });\n return result !== undefined;\n }\n\n get(name: string): PtyRun | undefined {\n return this.runs.get(name);\n }\n\n readOutcome(id: string, sessionId: string): ExitResult | undefined {\n return readExitMetadata(this.auxPaths(id, sessionId).exit);\n }\n\n private trackCompletion(target: string, completion: Promise<ExitResult>): void {\n this.completions.set(target, completion);\n void completion\n .finally(() => this.completions.delete(target))\n .catch((error: unknown) => process.emitWarning(`RMUX monitor failed for ${target}: ${errorMessage(error)}`));\n }\n\n private client(): Promise<Rmux | undefined> {\n this.clientPromise ??= this.resolveClient();\n return this.clientPromise;\n }\n\n private async resolveClient(): Promise<Rmux | undefined> {\n const binaries = [bundledBinary(), 'rmux'].filter((value): value is string => value !== undefined);\n for (const binary of binaries) {\n try {\n if (binary !== 'rmux') fs.chmodSync(binary, EXECUTABLE_MODE);\n const client = new RMUX({ binary, socketName: socketName(this.paths.repositoryPath()) });\n await client.capabilities();\n return client;\n } catch (error) {\n process.emitWarning(`RMUX candidate ${binary} is unavailable: ${errorMessage(error)}`);\n // Try the next compatible candidate, then let the native launcher take over.\n }\n }\n return undefined;\n }\n\n private prepareFiles(request: RmuxLaunchRequest, logPath: string, aux: SupervisorPaths): void {\n cleanupSupervisorFiles(aux);\n fs.writeFileSync(logPath, '', { mode: 0o600 });\n const env = environment(request.sessionId, request.interactive);\n // runnerHost supervises the command and watches the lifeline itself, so an\n // owner that dies mid-run is handled inside the pane rather than around it.\n const spec: CommandSpec = { command: request.command, cwd: request.cwd, env };\n writeCommandSpec(aux.spec, spec);\n }\n\n private auxPaths(id: string, sessionId?: string): SupervisorPaths {\n return supervisorPaths(this.paths.stateDirectory(sessionId), id);\n }\n\n private logPipeCommand(logPath: string, donePath: string): string {\n return shellJoin([\n process.execPath,\n runtimeEntry('logSink'),\n logPath,\n this.paths.rotatedLogPathFor(path.basename(logPath, '.log')),\n String(getLogMaxBytes()),\n donePath,\n ]);\n }\n\n private async monitor(rmux: Rmux, pane: Pane, target: string, aux: SupervisorPaths): Promise<ExitResult> {\n try {\n const paneCode = await waitForRunEnd(rmux, target, aux.exit);\n await waitForLogDrain(rmux, target, aux.logDone);\n return readExitMetadata(aux.exit) ?? { code: paneCode, signal: null };\n } finally {\n // The pane ends its own session, so this only covers a session that somehow\n // outlived its pane. Killing unconditionally would warn on every clean run.\n if (!(await sessionMissing(rmux, target).catch(() => false))) {\n await pane.server\n .session(target)\n .kill()\n .catch((error: unknown) =>\n process.emitWarning(`Could not clean up completed RMUX target ${target}: ${errorMessage(error)}`),\n );\n }\n cleanupSupervisorFiles(aux);\n }\n }\n\n private interactiveRun(handle: RunHandle, pane: Pane, completion: Promise<ExitResult>): PtyRun {\n let screen = '';\n let active = true;\n const handlers = new Set<(data: string) => void>();\n const refresh = async (): Promise<void> => {\n while (active) {\n const next = await pane.captureText().catch((error: unknown) => {\n process.emitWarning(`Could not capture RMUX screen for ${handle.name}: ${errorMessage(error)}`);\n return screen;\n });\n if (next !== screen) {\n screen = next;\n for (const handler of handlers) handler(next);\n }\n await delay(POLL_MS);\n }\n };\n void refresh();\n void completion\n .finally(() => {\n active = false;\n })\n .catch((error: unknown) =>\n process.emitWarning(`RMUX interactive completion failed for ${handle.name}: ${errorMessage(error)}`),\n );\n return {\n ...handle,\n write: (text) => {\n void pane.sendText(text).catch((error: unknown) => process.emitWarning(errorMessage(error)));\n },\n screen: () => screen,\n onData: (handler) => {\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n resize: (cols, rows) => {\n void pane\n .resize({ width: cols, height: rows })\n .catch((error: unknown) => process.emitWarning(errorMessage(error)));\n },\n };\n }\n}\n\nexport function rmuxPackageForTarget(platform: string, architecture: string): string | undefined {\n const target = `${platform}-${architecture}`;\n const packageName = PACKAGE_BY_PLATFORM[target];\n if (!packageName) process.emitWarning(`Bundled RMUX binary is unavailable for unsupported target ${target}`);\n return packageName;\n}\n\nfunction bundledBinary(): string | undefined {\n const packageName = rmuxPackageForTarget(process.platform, process.arch);\n if (!packageName) return undefined;\n try {\n const require = createRequire(import.meta.url);\n const manifest = require.resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), 'vendor', 'bin', 'rmux');\n } catch (error) {\n process.emitWarning(`Bundled RMUX binary is unavailable for ${packageName}: ${errorMessage(error)}`);\n return undefined;\n }\n}\n\nfunction socketName(repositoryPath: string): string {\n const hash = createHash('sha256').update(repositoryPath).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return `${SESSION_PREFIX}${hash}`;\n}\n\nfunction environment(sessionId: string, interactive: boolean): Record<string, string> {\n const values: NodeJS.ProcessEnv = {\n ...process.env,\n ...(interactive ? {} : NON_INTERACTIVE_ENV),\n // Applied to interactive runs as well: a pane is a tty either way, and an\n // agent sending input to a prompt still has no way past a pager.\n ...NO_TERMINAL_INPUT_ENV,\n PI_SESSION_ID: sessionId,\n };\n if (!interactive) delete values['FORCE_COLOR'];\n return Object.fromEntries(\n Object.entries(values).filter((entry): entry is [string, string] => entry[1] !== undefined),\n );\n}\n\nasync function panePid(rmux: Rmux, target: string): Promise<number> {\n const response = await rmux.displayMessage('#{pane_pid}', { target });\n const pid = Number(response['message']);\n if (!Number.isInteger(pid) || pid <= 0) throw new Error(`RMUX did not report a pane pid for ${target}`);\n return pid;\n}\n\n/**\n * Waits for the run to end, preferring the supervisor's own record of it.\n *\n * The sidecar is written before the command's pane can end, and it outlives the\n * server that the pane's exit takes down with it. Asking the server is the\n * fallback for a supervisor that died without recording anything.\n */\nasync function waitForRunEnd(rmux: Rmux, target: string, exitPath: string): Promise<number | null> {\n for (;;) {\n if (fs.existsSync(exitPath)) return null;\n const state = await readPaneExit(rmux, target);\n if (state.exited) return state.code;\n await delay(POLL_MS);\n }\n}\n\n/**\n * Gives the log sink a moment to mark the log complete.\n *\n * The sink dies with the session it was piped from, so once the session is gone\n * the marker is never coming and there is nothing left to wait for. Its writes\n * are synchronous, so the log itself is already on disk either way.\n */\nasync function waitForLogDrain(rmux: Rmux, target: string, donePath: string): Promise<void> {\n const deadline = Date.now() + LOG_DRAIN_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (fs.existsSync(donePath)) return;\n if (await sessionMissing(rmux, target)) return;\n await delay(LOG_DRAIN_POLL_MS);\n }\n}\n\nasync function closeOwnedSession(rmux: Rmux, target: string): Promise<boolean> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (result: boolean): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(result);\n };\n const timer = setTimeout(() => {\n process.emitWarning(`Timed out closing RMUX target ${target} after ${STOP_CLOSE_TIMEOUT_MS}ms`);\n finish(false);\n }, STOP_CLOSE_TIMEOUT_MS);\n void rmux\n .session(target)\n .kill()\n .then(\n () => finish(true),\n (error: unknown) => {\n process.emitWarning(`Could not close RMUX target ${target}: ${errorMessage(error)}`);\n finish(false);\n },\n );\n });\n}\n\nasync function waitForPaneStop(rmux: Rmux, target: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n if ((await readPaneExit(rmux, target)).exited) return true;\n } catch (error) {\n process.emitWarning(`Could not verify RMUX target ${target} stopped: ${errorMessage(error)}`);\n return false;\n }\n await delay(POLL_MS);\n }\n return false;\n}\n\n/**\n * One formatted read answers both questions this poll asks, so watching a run\n * costs the same single rmux call it always did.\n */\nasync function readPaneExit(rmux: Rmux, target: string): Promise<{ exited: boolean; code: number | null }> {\n let message: string;\n try {\n const response = await rmux.displayMessage(PANE_STATE_FORMAT, { target });\n message = typeof response['message'] === 'string' ? response['message'] : '';\n } catch {\n // The last session ending stops the server, so a server that cannot be\n // reached is the ordinary end of the last run rather than a fault.\n return { exited: true, code: null };\n }\n const [dead, status, ...rest] = message.trim().split(':');\n // A missing session formats as a live pane with no name rather than failing,\n // so the name is what says whether there is still anything to watch.\n if (rest.join(':') === '') return { exited: true, code: null };\n if (dead === '1' || dead === 'true') {\n const code = Number(status);\n return { exited: true, code: Number.isInteger(code) ? code : null };\n }\n return { exited: false, code: null };\n}\n\n/** A session that cannot be found, on a server that may itself be gone. */\nasync function sessionMissing(rmux: Rmux, target: string): Promise<boolean> {\n try {\n const probe = await rmux.cmd('has-session', '-t', target);\n return probe.returnCode !== 0;\n } catch {\n // No server to ask means no session to find.\n return true;\n }\n}\n\n/**\n * A pane that was already gone and a signal that could not be delivered are\n * opposite answers to \"is it stopped\", so they are reported apart.\n */\nfunction signalPane(pid: number, target: string): 'signalled' | 'gone' | 'failed' {\n try {\n process.kill(pid, 'SIGTERM');\n return 'signalled';\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ESRCH') return 'gone';\n process.emitWarning(`Could not signal RMUX target ${target}: ${errorMessage(error)}`);\n return 'failed';\n }\n}\n\nfunction readTail(target: string, maxBytes: number): string {\n try {\n const text = fs.readFileSync(target, 'utf8');\n return text.length > maxBytes ? text.slice(-maxBytes) : text;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n process.emitWarning(`Could not read RMUX log ${target}: ${errorMessage(error)}`);\n }\n return '';\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RmuxBackend.cjs","names":["runtimeEntry","getResultMaxBytes","readExitMetadata","RMUX","supervisorPaths","shellJoin","path","getLogMaxBytes","injectable","inject","TYPES","createRequire","createHash","NO_TERMINAL_INPUT_ENV","fs"],"sources":["../../../src/services/RmuxBackend/RmuxBackend.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { type Pane, RMUX, type Rmux } from '@rmux/sdk';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport {\n type CommandSpec,\n cleanupSupervisorFiles,\n NO_TERMINAL_INPUT_ENV,\n readExitMetadata,\n runtimeEntry,\n type SupervisorPaths,\n shellJoin,\n supervisorPaths,\n writeCommandSpec,\n} from '../../schemas/runnerSpec.ts';\nimport { getLogMaxBytes, getResultMaxBytes } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport type { RunHandle } from '../Launcher/index.ts';\nimport type { PtyRun } from '../PtyHost/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { ExitResult } from '../Spawner/index.ts';\nimport type { IRmuxBackend, RmuxLaunchRequest } from './types.ts';\n\nconst POLL_MS = 100;\nconst LOG_DRAIN_TIMEOUT_MS = 2_000;\nconst LOG_DRAIN_POLL_MS = 25;\n/** Pane liveness, its exit status, and whether the session still exists, in one read. */\nconst PANE_STATE_FORMAT = '#{pane_dead}:#{pane_dead_status}:#{session_name}';\nconst STOP_GRACE_MS = 3_000;\nconst STOP_CLOSE_TIMEOUT_MS = 1_000;\nconst EXECUTABLE_MODE = 0o755;\nconst SESSION_PREFIX = 'doom-runner-';\nconst OWNED_TARGET_PATTERN = /^doom-runner-[A-Za-z0-9_-]+$/;\nconst SOCKET_HASH_LENGTH = 12;\nconst NON_INTERACTIVE_ENV = { NO_COLOR: '1', CI: '1' };\nconst PACKAGE_BY_PLATFORM: Readonly<Record<string, string>> = {\n 'darwin-arm64': '@agimon-ai/doompi-runner-rmux-darwin-arm64',\n 'darwin-x64': '@agimon-ai/doompi-runner-rmux-darwin-x64',\n 'linux-arm64': '@agimon-ai/doompi-runner-rmux-linux-arm64',\n 'linux-x64': '@agimon-ai/doompi-runner-rmux-linux-x64',\n};\n\n@injectable()\nexport class RmuxBackend implements IRmuxBackend {\n private clientPromise: Promise<Rmux | undefined> | undefined;\n private readonly runs = new Map<string, PtyRun>();\n private readonly completions = new Map<string, Promise<ExitResult>>();\n\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n async launch(request: RmuxLaunchRequest): Promise<RunHandle | undefined> {\n const rmux = await this.client();\n if (!rmux) return undefined;\n\n this.paths.ensureDirectories();\n const target = `${SESSION_PREFIX}${request.id}`;\n const logPath = this.paths.logPathFor(request.id);\n const aux = this.auxPaths(request.id, request.sessionId);\n let created = false;\n let released = false;\n\n try {\n this.prepareFiles(request, logPath, aux);\n await rmux.cmd(\n 'new-session',\n '-d',\n '-s',\n target,\n '-c',\n request.cwd,\n process.execPath,\n runtimeEntry('runnerHost'),\n aux.spec,\n aux.gate,\n aux.exit,\n { check: true },\n );\n created = true;\n // No remain-on-exit: a finished pane ends its session, and the last session\n // ending stops the server, so a completed run leaves no process behind.\n await rmux.cmd('pipe-pane', '-t', target, this.logPipeCommand(logPath, aux.logDone), { check: true });\n\n const pane = rmux.session(target).pane(0, 0);\n const pid = await panePid(rmux, target);\n fs.writeFileSync(aux.gate, '', { mode: 0o600 });\n released = true;\n\n let buffering = true;\n const completion = this.monitor(rmux, pane, target, aux);\n this.trackCompletion(target, completion);\n const stop = (): Promise<boolean> => this.stop(target, pid);\n const handle: RunHandle = {\n id: request.id,\n name: request.name,\n pid,\n logPath,\n backend: 'rmux',\n backendTarget: target,\n output: () => (buffering ? readTail(logPath, getResultMaxBytes() * 2) : ''),\n completion: () => completion,\n detach: () => {\n buffering = false;\n },\n stop,\n };\n\n if (request.interactive) this.runs.set(request.name, this.interactiveRun(handle, pane, completion));\n void completion\n .finally(() => this.runs.delete(request.name))\n .catch((error: unknown) => process.emitWarning(`RMUX completion failed for ${target}: ${errorMessage(error)}`));\n return handle;\n } catch (error) {\n if (released) throw error;\n if (created) {\n await rmux\n .session(target)\n .kill()\n .catch((cleanupError: unknown) =>\n process.emitWarning(`Could not clean up failed RMUX launch ${target}: ${errorMessage(cleanupError)}`),\n );\n }\n cleanupSupervisorFiles(aux);\n process.emitWarning(`RMUX launch unavailable, using native runner: ${errorMessage(error)}`);\n return undefined;\n }\n }\n\n async stop(target: string, expectedPid: number): Promise<boolean> {\n if (!OWNED_TARGET_PATTERN.test(target) || !Number.isInteger(expectedPid) || expectedPid <= 0) return false;\n\n const rmux = await this.client();\n if (!rmux) return false;\n try {\n // A session that already ended took its process group with it.\n if (await sessionMissing(rmux, target)) return true;\n\n const pid = await panePid(rmux, target);\n if (pid !== expectedPid) return false;\n const signalled = signalPane(pid, target);\n if (signalled === 'gone') return true;\n if (signalled === 'failed') return false;\n\n if (await waitForPaneStop(rmux, target, STOP_GRACE_MS)) return true;\n if (await sessionMissing(rmux, target)) return true;\n\n // Revalidate after the grace period so a replacement pane is never closed.\n const currentPid = await panePid(rmux, target);\n if (currentPid !== expectedPid) return false;\n\n return closeOwnedSession(rmux, target);\n } catch (error) {\n process.emitWarning(`Could not stop RMUX target ${target}: ${errorMessage(error)}`);\n return false;\n }\n }\n\n async watch(id: string, target: string, sessionId?: string): Promise<ExitResult | undefined> {\n const rmux = await this.client();\n if (!rmux) return undefined;\n const pane = rmux.session(target).pane(0, 0);\n try {\n await rmux.displayMessage('#{pane_id}', { target });\n } catch (error) {\n process.emitWarning(`Could not watch RMUX target ${target}: ${errorMessage(error)}`);\n return undefined;\n }\n const completion = this.monitor(rmux, pane, target, this.auxPaths(id, sessionId));\n this.trackCompletion(target, completion);\n return completion;\n }\n\n async input(target: string, text: string): Promise<boolean> {\n const rmux = await this.client();\n if (!rmux) return false;\n const result = await rmux\n .session(target)\n .pane(0, 0)\n .sendText(text)\n .catch((error: unknown) => {\n process.emitWarning(`Could not send input to RMUX target ${target}: ${errorMessage(error)}`);\n return undefined;\n });\n return result !== undefined;\n }\n\n get(name: string): PtyRun | undefined {\n return this.runs.get(name);\n }\n\n readOutcome(id: string, sessionId: string): ExitResult | undefined {\n return readExitMetadata(this.auxPaths(id, sessionId).exit);\n }\n\n private trackCompletion(target: string, completion: Promise<ExitResult>): void {\n this.completions.set(target, completion);\n void completion\n .finally(() => this.completions.delete(target))\n .catch((error: unknown) => process.emitWarning(`RMUX monitor failed for ${target}: ${errorMessage(error)}`));\n }\n\n private client(): Promise<Rmux | undefined> {\n this.clientPromise ??= this.resolveClient();\n return this.clientPromise;\n }\n\n private async resolveClient(): Promise<Rmux | undefined> {\n const binaries = [bundledBinary(), 'rmux'].filter((value): value is string => value !== undefined);\n for (const binary of binaries) {\n try {\n if (binary !== 'rmux') fs.chmodSync(binary, EXECUTABLE_MODE);\n const client = new RMUX({ binary, socketName: socketName(this.paths.repositoryPath()) });\n await client.capabilities();\n return client;\n } catch (error) {\n process.emitWarning(`RMUX candidate ${binary} is unavailable: ${errorMessage(error)}`);\n // Try the next compatible candidate, then let the native launcher take over.\n }\n }\n return undefined;\n }\n\n private prepareFiles(request: RmuxLaunchRequest, logPath: string, aux: SupervisorPaths): void {\n cleanupSupervisorFiles(aux);\n fs.writeFileSync(logPath, '', { mode: 0o600 });\n const env = environment(request.sessionId, request.interactive);\n // runnerHost supervises the command and watches the lifeline itself, so an\n // owner that dies mid-run is handled inside the pane rather than around it.\n const spec: CommandSpec = { command: request.command, cwd: request.cwd, env };\n writeCommandSpec(aux.spec, spec);\n }\n\n private auxPaths(id: string, sessionId?: string): SupervisorPaths {\n return supervisorPaths(this.paths.stateDirectory(sessionId), id);\n }\n\n private logPipeCommand(logPath: string, donePath: string): string {\n return shellJoin([\n process.execPath,\n runtimeEntry('logSink'),\n logPath,\n this.paths.rotatedLogPathFor(path.basename(logPath, '.log')),\n String(getLogMaxBytes()),\n donePath,\n ]);\n }\n\n private async monitor(rmux: Rmux, pane: Pane, target: string, aux: SupervisorPaths): Promise<ExitResult> {\n try {\n const paneCode = await waitForRunEnd(rmux, target, aux.exit);\n await waitForLogDrain(rmux, target, aux.logDone);\n return readExitMetadata(aux.exit) ?? { code: paneCode, signal: null };\n } finally {\n // The pane ends its own session, so this only covers a session that somehow\n // outlived its pane. Killing unconditionally would warn on every clean run.\n if (!(await sessionMissing(rmux, target).catch(() => false))) {\n await pane.server\n .session(target)\n .kill()\n .catch((error: unknown) =>\n process.emitWarning(`Could not clean up completed RMUX target ${target}: ${errorMessage(error)}`),\n );\n }\n cleanupSupervisorFiles(aux);\n }\n }\n\n private interactiveRun(handle: RunHandle, pane: Pane, completion: Promise<ExitResult>): PtyRun {\n let screen = '';\n let active = true;\n const handlers = new Set<(data: string) => void>();\n const refresh = async (): Promise<void> => {\n while (active) {\n const next = await pane.captureText().catch((error: unknown) => {\n process.emitWarning(`Could not capture RMUX screen for ${handle.name}: ${errorMessage(error)}`);\n return screen;\n });\n if (next !== screen) {\n screen = next;\n for (const handler of handlers) handler(next);\n }\n await delay(POLL_MS);\n }\n };\n void refresh();\n void completion\n .finally(() => {\n active = false;\n })\n .catch((error: unknown) =>\n process.emitWarning(`RMUX interactive completion failed for ${handle.name}: ${errorMessage(error)}`),\n );\n return {\n ...handle,\n write: (text) => {\n void pane.sendText(text).catch((error: unknown) => process.emitWarning(errorMessage(error)));\n },\n screen: () => screen,\n onData: (handler) => {\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n resize: (cols, rows) => {\n void pane\n .resize({ width: cols, height: rows })\n .catch((error: unknown) => process.emitWarning(errorMessage(error)));\n },\n };\n }\n}\n\nexport function rmuxPackageForTarget(platform: string, architecture: string): string | undefined {\n const target = `${platform}-${architecture}`;\n const packageName = PACKAGE_BY_PLATFORM[target];\n if (!packageName) process.emitWarning(`Bundled RMUX binary is unavailable for unsupported target ${target}`);\n return packageName;\n}\n\nfunction bundledBinary(): string | undefined {\n const packageName = rmuxPackageForTarget(process.platform, process.arch);\n if (!packageName) return undefined;\n try {\n const require = createRequire(import.meta.url);\n const manifest = require.resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), 'vendor', 'bin', 'rmux');\n } catch (error) {\n process.emitWarning(`Bundled RMUX binary is unavailable for ${packageName}: ${errorMessage(error)}`);\n return undefined;\n }\n}\n\nfunction socketName(repositoryPath: string): string {\n const hash = createHash('sha256').update(repositoryPath).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return `${SESSION_PREFIX}${hash}`;\n}\n\nfunction environment(sessionId: string, interactive: boolean): Record<string, string> {\n const values: NodeJS.ProcessEnv = {\n ...process.env,\n ...(interactive ? {} : NON_INTERACTIVE_ENV),\n // Applied to interactive runs as well: a pane is a tty either way, and an\n // agent sending input to a prompt still has no way past a pager.\n ...NO_TERMINAL_INPUT_ENV,\n PI_SESSION_ID: sessionId,\n };\n if (!interactive) delete values['FORCE_COLOR'];\n return Object.fromEntries(\n Object.entries(values).filter((entry): entry is [string, string] => entry[1] !== undefined),\n );\n}\n\nasync function panePid(rmux: Rmux, target: string): Promise<number> {\n const response = await rmux.displayMessage('#{pane_pid}', { target });\n const pid = Number(response['message']);\n if (!Number.isInteger(pid) || pid <= 0) throw new Error(`RMUX did not report a pane pid for ${target}`);\n return pid;\n}\n\n/**\n * Waits for the run to end, preferring the supervisor's own record of it.\n *\n * The sidecar is written before the command's pane can end, and it outlives the\n * server that the pane's exit takes down with it. Asking the server is the\n * fallback for a supervisor that died without recording anything.\n */\nasync function waitForRunEnd(rmux: Rmux, target: string, exitPath: string): Promise<number | null> {\n for (;;) {\n if (fs.existsSync(exitPath)) return null;\n const state = await readPaneExit(rmux, target);\n if (state.exited) return state.code;\n await delay(POLL_MS);\n }\n}\n\n/**\n * Gives the log sink a moment to mark the log complete.\n *\n * The sink dies with the session it was piped from, so once the session is gone\n * the marker is never coming and there is nothing left to wait for. Its writes\n * are synchronous, so the log itself is already on disk either way.\n */\nasync function waitForLogDrain(rmux: Rmux, target: string, donePath: string): Promise<void> {\n const deadline = Date.now() + LOG_DRAIN_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (fs.existsSync(donePath)) return;\n if (await sessionMissing(rmux, target)) return;\n await delay(LOG_DRAIN_POLL_MS);\n }\n}\n\nasync function closeOwnedSession(rmux: Rmux, target: string): Promise<boolean> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (result: boolean): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(result);\n };\n const timer = setTimeout(() => {\n process.emitWarning(`Timed out closing RMUX target ${target} after ${STOP_CLOSE_TIMEOUT_MS}ms`);\n finish(false);\n }, STOP_CLOSE_TIMEOUT_MS);\n void rmux\n .session(target)\n .kill()\n .then(\n () => finish(true),\n (error: unknown) => {\n process.emitWarning(`Could not close RMUX target ${target}: ${errorMessage(error)}`);\n finish(false);\n },\n );\n });\n}\n\nasync function waitForPaneStop(rmux: Rmux, target: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n if ((await readPaneExit(rmux, target)).exited) return true;\n } catch (error) {\n process.emitWarning(`Could not verify RMUX target ${target} stopped: ${errorMessage(error)}`);\n return false;\n }\n await delay(POLL_MS);\n }\n return false;\n}\n\n/**\n * One formatted read answers both questions this poll asks, so watching a run\n * costs the same single rmux call it always did.\n */\nasync function readPaneExit(rmux: Rmux, target: string): Promise<{ exited: boolean; code: number | null }> {\n let message: string;\n try {\n const response = await rmux.displayMessage(PANE_STATE_FORMAT, { target });\n message = typeof response['message'] === 'string' ? response['message'] : '';\n } catch {\n // The last session ending stops the server, so a server that cannot be\n // reached is the ordinary end of the last run rather than a fault.\n return { exited: true, code: null };\n }\n const [dead, status, ...rest] = message.trim().split(':');\n // A missing session formats as a live pane with no name rather than failing,\n // so the name is what says whether there is still anything to watch.\n if (rest.join(':') === '') return { exited: true, code: null };\n if (dead === '1' || dead === 'true') {\n const code = Number(status);\n return { exited: true, code: Number.isInteger(code) ? code : null };\n }\n return { exited: false, code: null };\n}\n\n/** A session that cannot be found, on a server that may itself be gone. */\nasync function sessionMissing(rmux: Rmux, target: string): Promise<boolean> {\n try {\n const probe = await rmux.cmd('has-session', '-t', target);\n return probe.returnCode !== 0;\n } catch {\n // No server to ask means no session to find.\n return true;\n }\n}\n\n/**\n * A pane that was already gone and a signal that could not be delivered are\n * opposite answers to \"is it stopped\", so they are reported apart.\n */\nfunction signalPane(pid: number, target: string): 'signalled' | 'gone' | 'failed' {\n try {\n process.kill(pid, 'SIGTERM');\n return 'signalled';\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ESRCH') return 'gone';\n process.emitWarning(`Could not signal RMUX target ${target}: ${errorMessage(error)}`);\n return 'failed';\n }\n}\n\nfunction readTail(target: string, maxBytes: number): string {\n try {\n const text = fs.readFileSync(target, 'utf8');\n return text.length > maxBytes ? text.slice(-maxBytes) : text;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n process.emitWarning(`Could not read RMUX log ${target}: ${errorMessage(error)}`);\n }\n return '';\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{getLogMaxBytes as e,getResultMaxBytes as t}from"../../config.mjs";import{TYPES as n}from"../../types.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import a from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{NO_TERMINAL_INPUT_ENV as o,cleanupSupervisorFiles as s,readExitMetadata as c,runtimeEntry as l,shellJoin as u,supervisorPaths as d,writeCommandSpec as f}from"../../runnerSpec.mjs";import{createRequire as p}from"node:module";import{inject as m,injectable as h}from"inversify";import"reflect-metadata/lite";import{createHash as g}from"node:crypto";import _ from"node:fs";import v from"node:path";import{RMUX as y}from"@rmux/sdk";const b=1e3,x=`doom-runner-`,S=/^doom-runner-[A-Za-z0-9_-]+$/,C={NO_COLOR:`1`,CI:`1`},w={"darwin-arm64":`@agimon-ai/doompi-runner-rmux-darwin-arm64`,"darwin-x64":`@agimon-ai/doompi-runner-rmux-darwin-x64`,"linux-arm64":`@agimon-ai/doompi-runner-rmux-linux-arm64`,"linux-x64":`@agimon-ai/doompi-runner-rmux-linux-x64`};let T=class{paths;clientPromise;runs=new Map;completions=new Map;constructor(e){this.paths=e}async launch(e){let n=await this.client();if(!n)return;this.paths.ensureDirectories();let r=`${x}${e.id}`,i=this.paths.logPathFor(e.id),a=this.auxPaths(e.id,e.sessionId),o=!1,c=!1;try{this.prepareFiles(e,i,a),await n.cmd(`new-session`,`-d`,`-s`,r,`-c`,e.cwd,process.execPath,l(`runnerHost`),a.spec,a.gate,a.exit,{check:!0}),o=!0,await n.cmd(`pipe-pane`,`-t`,r,this.logPipeCommand(i,a.logDone),{check:!0});let s=n.session(r).pane(0,0),u=await A(n,r);_.writeFileSync(a.gate,``,{mode:384}),c=!0;let d=!0,f=this.monitor(n,s,r,a);this.trackCompletion(r,f);let p={id:e.id,name:e.name,pid:u,logPath:i,backend:`rmux`,backendTarget:r,output:()=>d?R(i,t()*2):``,completion:()=>f,detach:()=>{d=!1},stop:()=>this.stop(r,u)};return e.interactive&&this.runs.set(e.name,this.interactiveRun(p,s,f)),f.finally(()=>this.runs.delete(e.name)).catch(e=>process.emitWarning(`RMUX completion failed for ${r}: ${B(e)}`)),p}catch(e){if(c)throw e;o&&await n.session(r).kill().catch(e=>process.emitWarning(`Could not clean up failed RMUX launch ${r}: ${B(e)}`)),s(a),process.emitWarning(`RMUX launch unavailable, using native runner: ${B(e)}`);return}}async stop(e,t){if(!S.test(e)||!Number.isInteger(t)||t<=0)return!1;let n=await this.client();if(!n)return!1;try{if(await I(n,e))return!0;let r=await A(n,e);if(r!==t)return!1;let i=L(r,e);return i===`gone`?!0:i===`failed`?!1:await P(n,e,3e3)||await I(n,e)?!0:await A(n,e)===t&&N(n,e)}catch(t){return process.emitWarning(`Could not stop RMUX target ${e}: ${B(t)}`),!1}}async watch(e,t,n){let r=await this.client();if(!r)return;let i=r.session(t).pane(0,0);try{await r.displayMessage(`#{pane_id}`,{target:t})}catch(e){process.emitWarning(`Could not watch RMUX target ${t}: ${B(e)}`);return}let a=this.monitor(r,i,t,this.auxPaths(e,n));return this.trackCompletion(t,a),a}async input(e,t){let n=await this.client();return n?await n.session(e).pane(0,0).sendText(t).catch(t=>{process.emitWarning(`Could not send input to RMUX target ${e}: ${B(t)}`)})!==void 0:!1}get(e){return this.runs.get(e)}readOutcome(e,t){return c(this.auxPaths(e,t).exit)}trackCompletion(e,t){this.completions.set(e,t),t.finally(()=>this.completions.delete(e)).catch(t=>process.emitWarning(`RMUX monitor failed for ${e}: ${B(t)}`))}client(){return this.clientPromise??=this.resolveClient(),this.clientPromise}async resolveClient(){let e=[D(),`rmux`].filter(e=>e!==void 0);for(let t of e)try{t!==`rmux`&&_.chmodSync(t,493);let e=new y({binary:t,socketName:O(this.paths.repositoryPath())});return await e.capabilities(),e}catch(e){process.emitWarning(`RMUX candidate ${t} is unavailable: ${B(e)}`)}}prepareFiles(e,t,n){s(n),_.writeFileSync(t,``,{mode:384});let r=k(e.sessionId,e.interactive),i={command:e.command,cwd:e.cwd,env:r};f(n.spec,i)}auxPaths(e,t){return d(this.paths.stateDirectory(t),e)}logPipeCommand(t,n){return u([process.execPath,l(`logSink`),t,this.paths.rotatedLogPathFor(v.basename(t,`.log`)),String(e()),n])}async monitor(e,t,n,r){try{let t=await j(e,n,r.exit);return await M(e,n,r.logDone),c(r.exit)??{code:t,signal:null}}finally{await I(e,n).catch(()=>!1)||await t.server.session(n).kill().catch(e=>process.emitWarning(`Could not clean up completed RMUX target ${n}: ${B(e)}`)),s(r)}}interactiveRun(e,t,n){let r=``,i=!0,a=new Set;return(async()=>{for(;i;){let n=await t.captureText().catch(t=>(process.emitWarning(`Could not capture RMUX screen for ${e.name}: ${B(t)}`),r));if(n!==r){r=n;for(let e of a)e(n)}await z(100)}})(),n.finally(()=>{i=!1}).catch(t=>process.emitWarning(`RMUX interactive completion failed for ${e.name}: ${B(t)}`)),{...e,write:e=>{t.sendText(e).catch(e=>process.emitWarning(B(e)))},screen:()=>r,onData:e=>(a.add(e),()=>a.delete(e)),resize:(e,n)=>{t.resize({width:e,height:n}).catch(e=>process.emitWarning(B(e)))}}}};T=a([h(),i(0,m(n.RunnerPaths)),r(`design:paramtypes`,[Object])],T);function E(e,t){let n=`${e}-${t}`,r=w[n];return r||process.emitWarning(`Bundled RMUX binary is unavailable for unsupported target ${n}`),r}function D(){let e=E(process.platform,process.arch);if(e)try{let t=p(import.meta.url).resolve(`${e}/package.json`);return v.join(v.dirname(t),`vendor`,`bin`,`rmux`)}catch(t){process.emitWarning(`Bundled RMUX binary is unavailable for ${e}: ${B(t)}`);return}}function O(e){let t=g(`sha256`).update(e).digest(`hex`).slice(0,12);return`${x}${t}`}function k(e,t){let n={...process.env,...t?{}:C,...o,PI_SESSION_ID:e};return t||delete n.FORCE_COLOR,Object.fromEntries(Object.entries(n).filter(e=>e[1]!==void 0))}async function A(e,t){let n=await e.displayMessage(`#{pane_pid}`,{target:t}),r=Number(n.message);if(!Number.isInteger(r)||r<=0)throw Error(`RMUX did not report a pane pid for ${t}`);return r}async function j(e,t,n){for(;;){if(_.existsSync(n))return null;let r=await F(e,t);if(r.exited)return r.code;await z(100)}}async function M(e,t,n){let r=Date.now()+2e3;for(;Date.now()<r;){if(_.existsSync(n)||await I(e,t))return;await z(25)}}async function N(e,t){return new Promise(n=>{let r=!1,i=e=>{r||(r=!0,clearTimeout(a),n(e))},a=setTimeout(()=>{process.emitWarning(`Timed out closing RMUX target ${t} after ${b}ms`),i(!1)},b);e.session(t).kill().then(()=>i(!0),e=>{process.emitWarning(`Could not close RMUX target ${t}: ${B(e)}`),i(!1)})})}async function P(e,t,n){let r=Date.now()+n;for(;Date.now()<r;){try{if((await F(e,t)).exited)return!0}catch(e){return process.emitWarning(`Could not verify RMUX target ${t} stopped: ${B(e)}`),!1}await z(100)}return!1}async function F(e,t){let n;try{let r=await e.displayMessage(`#{pane_dead}:#{pane_dead_status}:#{session_name}`,{target:t});n=typeof r.message==`string`?r.message:``}catch{return{exited:!0,code:null}}let[r,i,...a]=n.trim().split(`:`);if(a.join(`:`)===``)return{exited:!0,code:null};if(r===`1`||r===`true`){let e=Number(i);return{exited:!0,code:Number.isInteger(e)?e:null}}return{exited:!1,code:null}}async function I(e,t){try{return(await e.cmd(`has-session`,`-t`,t)).returnCode!==0}catch{return!0}}function L(e,t){try{return process.kill(e,`SIGTERM`),`signalled`}catch(e){return e.code===`ESRCH`?`gone`:(process.emitWarning(`Could not signal RMUX target ${t}: ${B(e)}`),`failed`)}}function R(e,t){try{let n=_.readFileSync(e,`utf8`);return n.length>t?n.slice(-t):n}catch(t){return t.code!==`ENOENT`&&process.emitWarning(`Could not read RMUX log ${e}: ${B(t)}`),``}}function z(e){return new Promise(t=>{setTimeout(t,e).unref?.()})}function B(e){return e instanceof Error?e.message:String(e)}export{T as RmuxBackend,E as rmuxPackageForTarget};
1
+ import{getLogMaxBytes as e,getResultMaxBytes as t}from"../../types/config.mjs";import{TYPES as n}from"../../types/index.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import i from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import a from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{NO_TERMINAL_INPUT_ENV as o,cleanupSupervisorFiles as s,readExitMetadata as c,runtimeEntry as l,shellJoin as u,supervisorPaths as d,writeCommandSpec as f}from"../../schemas/runnerSpec.mjs";import{createRequire as p}from"node:module";import{inject as m,injectable as h}from"inversify";import"reflect-metadata/lite";import{createHash as g}from"node:crypto";import _ from"node:fs";import v from"node:path";import{RMUX as y}from"@rmux/sdk";const b=1e3,x=`doom-runner-`,S=/^doom-runner-[A-Za-z0-9_-]+$/,C={NO_COLOR:`1`,CI:`1`},w={"darwin-arm64":`@agimon-ai/doompi-runner-rmux-darwin-arm64`,"darwin-x64":`@agimon-ai/doompi-runner-rmux-darwin-x64`,"linux-arm64":`@agimon-ai/doompi-runner-rmux-linux-arm64`,"linux-x64":`@agimon-ai/doompi-runner-rmux-linux-x64`};let T=class{paths;clientPromise;runs=new Map;completions=new Map;constructor(e){this.paths=e}async launch(e){let n=await this.client();if(!n)return;this.paths.ensureDirectories();let r=`${x}${e.id}`,i=this.paths.logPathFor(e.id),a=this.auxPaths(e.id,e.sessionId),o=!1,c=!1;try{this.prepareFiles(e,i,a),await n.cmd(`new-session`,`-d`,`-s`,r,`-c`,e.cwd,process.execPath,l(`runnerHost`),a.spec,a.gate,a.exit,{check:!0}),o=!0,await n.cmd(`pipe-pane`,`-t`,r,this.logPipeCommand(i,a.logDone),{check:!0});let s=n.session(r).pane(0,0),u=await A(n,r);_.writeFileSync(a.gate,``,{mode:384}),c=!0;let d=!0,f=this.monitor(n,s,r,a);this.trackCompletion(r,f);let p={id:e.id,name:e.name,pid:u,logPath:i,backend:`rmux`,backendTarget:r,output:()=>d?R(i,t()*2):``,completion:()=>f,detach:()=>{d=!1},stop:()=>this.stop(r,u)};return e.interactive&&this.runs.set(e.name,this.interactiveRun(p,s,f)),f.finally(()=>this.runs.delete(e.name)).catch(e=>process.emitWarning(`RMUX completion failed for ${r}: ${B(e)}`)),p}catch(e){if(c)throw e;o&&await n.session(r).kill().catch(e=>process.emitWarning(`Could not clean up failed RMUX launch ${r}: ${B(e)}`)),s(a),process.emitWarning(`RMUX launch unavailable, using native runner: ${B(e)}`);return}}async stop(e,t){if(!S.test(e)||!Number.isInteger(t)||t<=0)return!1;let n=await this.client();if(!n)return!1;try{if(await I(n,e))return!0;let r=await A(n,e);if(r!==t)return!1;let i=L(r,e);return i===`gone`?!0:i===`failed`?!1:await P(n,e,3e3)||await I(n,e)?!0:await A(n,e)===t&&N(n,e)}catch(t){return process.emitWarning(`Could not stop RMUX target ${e}: ${B(t)}`),!1}}async watch(e,t,n){let r=await this.client();if(!r)return;let i=r.session(t).pane(0,0);try{await r.displayMessage(`#{pane_id}`,{target:t})}catch(e){process.emitWarning(`Could not watch RMUX target ${t}: ${B(e)}`);return}let a=this.monitor(r,i,t,this.auxPaths(e,n));return this.trackCompletion(t,a),a}async input(e,t){let n=await this.client();return n?await n.session(e).pane(0,0).sendText(t).catch(t=>{process.emitWarning(`Could not send input to RMUX target ${e}: ${B(t)}`)})!==void 0:!1}get(e){return this.runs.get(e)}readOutcome(e,t){return c(this.auxPaths(e,t).exit)}trackCompletion(e,t){this.completions.set(e,t),t.finally(()=>this.completions.delete(e)).catch(t=>process.emitWarning(`RMUX monitor failed for ${e}: ${B(t)}`))}client(){return this.clientPromise??=this.resolveClient(),this.clientPromise}async resolveClient(){let e=[D(),`rmux`].filter(e=>e!==void 0);for(let t of e)try{t!==`rmux`&&_.chmodSync(t,493);let e=new y({binary:t,socketName:O(this.paths.repositoryPath())});return await e.capabilities(),e}catch(e){process.emitWarning(`RMUX candidate ${t} is unavailable: ${B(e)}`)}}prepareFiles(e,t,n){s(n),_.writeFileSync(t,``,{mode:384});let r=k(e.sessionId,e.interactive),i={command:e.command,cwd:e.cwd,env:r};f(n.spec,i)}auxPaths(e,t){return d(this.paths.stateDirectory(t),e)}logPipeCommand(t,n){return u([process.execPath,l(`logSink`),t,this.paths.rotatedLogPathFor(v.basename(t,`.log`)),String(e()),n])}async monitor(e,t,n,r){try{let t=await j(e,n,r.exit);return await M(e,n,r.logDone),c(r.exit)??{code:t,signal:null}}finally{await I(e,n).catch(()=>!1)||await t.server.session(n).kill().catch(e=>process.emitWarning(`Could not clean up completed RMUX target ${n}: ${B(e)}`)),s(r)}}interactiveRun(e,t,n){let r=``,i=!0,a=new Set;return(async()=>{for(;i;){let n=await t.captureText().catch(t=>(process.emitWarning(`Could not capture RMUX screen for ${e.name}: ${B(t)}`),r));if(n!==r){r=n;for(let e of a)e(n)}await z(100)}})(),n.finally(()=>{i=!1}).catch(t=>process.emitWarning(`RMUX interactive completion failed for ${e.name}: ${B(t)}`)),{...e,write:e=>{t.sendText(e).catch(e=>process.emitWarning(B(e)))},screen:()=>r,onData:e=>(a.add(e),()=>a.delete(e)),resize:(e,n)=>{t.resize({width:e,height:n}).catch(e=>process.emitWarning(B(e)))}}}};T=a([h(),i(0,m(n.RunnerPaths)),r(`design:paramtypes`,[Object])],T);function E(e,t){let n=`${e}-${t}`,r=w[n];return r||process.emitWarning(`Bundled RMUX binary is unavailable for unsupported target ${n}`),r}function D(){let e=E(process.platform,process.arch);if(e)try{let t=p(import.meta.url).resolve(`${e}/package.json`);return v.join(v.dirname(t),`vendor`,`bin`,`rmux`)}catch(t){process.emitWarning(`Bundled RMUX binary is unavailable for ${e}: ${B(t)}`);return}}function O(e){let t=g(`sha256`).update(e).digest(`hex`).slice(0,12);return`${x}${t}`}function k(e,t){let n={...process.env,...t?{}:C,...o,PI_SESSION_ID:e};return t||delete n.FORCE_COLOR,Object.fromEntries(Object.entries(n).filter(e=>e[1]!==void 0))}async function A(e,t){let n=await e.displayMessage(`#{pane_pid}`,{target:t}),r=Number(n.message);if(!Number.isInteger(r)||r<=0)throw Error(`RMUX did not report a pane pid for ${t}`);return r}async function j(e,t,n){for(;;){if(_.existsSync(n))return null;let r=await F(e,t);if(r.exited)return r.code;await z(100)}}async function M(e,t,n){let r=Date.now()+2e3;for(;Date.now()<r;){if(_.existsSync(n)||await I(e,t))return;await z(25)}}async function N(e,t){return new Promise(n=>{let r=!1,i=e=>{r||(r=!0,clearTimeout(a),n(e))},a=setTimeout(()=>{process.emitWarning(`Timed out closing RMUX target ${t} after ${b}ms`),i(!1)},b);e.session(t).kill().then(()=>i(!0),e=>{process.emitWarning(`Could not close RMUX target ${t}: ${B(e)}`),i(!1)})})}async function P(e,t,n){let r=Date.now()+n;for(;Date.now()<r;){try{if((await F(e,t)).exited)return!0}catch(e){return process.emitWarning(`Could not verify RMUX target ${t} stopped: ${B(e)}`),!1}await z(100)}return!1}async function F(e,t){let n;try{let r=await e.displayMessage(`#{pane_dead}:#{pane_dead_status}:#{session_name}`,{target:t});n=typeof r.message==`string`?r.message:``}catch{return{exited:!0,code:null}}let[r,i,...a]=n.trim().split(`:`);if(a.join(`:`)===``)return{exited:!0,code:null};if(r===`1`||r===`true`){let e=Number(i);return{exited:!0,code:Number.isInteger(e)?e:null}}return{exited:!1,code:null}}async function I(e,t){try{return(await e.cmd(`has-session`,`-t`,t)).returnCode!==0}catch{return!0}}function L(e,t){try{return process.kill(e,`SIGTERM`),`signalled`}catch(e){return e.code===`ESRCH`?`gone`:(process.emitWarning(`Could not signal RMUX target ${t}: ${B(e)}`),`failed`)}}function R(e,t){try{let n=_.readFileSync(e,`utf8`);return n.length>t?n.slice(-t):n}catch(t){return t.code!==`ENOENT`&&process.emitWarning(`Could not read RMUX log ${e}: ${B(t)}`),``}}function z(e){return new Promise(t=>{setTimeout(t,e).unref?.()})}function B(e){return e instanceof Error?e.message:String(e)}export{T as RmuxBackend,E as rmuxPackageForTarget};
2
2
  //# sourceMappingURL=RmuxBackend.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RmuxBackend.mjs","names":[],"sources":["../../../src/services/RmuxBackend/RmuxBackend.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport { RMUX, type Pane, type Rmux } from '@rmux/sdk';\nimport 'reflect-metadata/lite';\nimport { getLogMaxBytes, getResultMaxBytes } from '../../config.ts';\nimport {\n type CommandSpec,\n NO_TERMINAL_INPUT_ENV,\n type SupervisorPaths,\n cleanupSupervisorFiles,\n readExitMetadata,\n runtimeEntry,\n shellJoin,\n supervisorPaths,\n writeCommandSpec,\n} from '../../runnerSpec.ts';\nimport { TYPES } from '../../types.ts';\nimport type { ExitResult } from '../Spawner/index.ts';\nimport type { RunHandle } from '../Launcher/index.ts';\nimport type { PtyRun } from '../PtyHost/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { IRmuxBackend, RmuxLaunchRequest } from './types.ts';\n\nconst POLL_MS = 100;\nconst LOG_DRAIN_TIMEOUT_MS = 2_000;\nconst LOG_DRAIN_POLL_MS = 25;\n/** Pane liveness, its exit status, and whether the session still exists, in one read. */\nconst PANE_STATE_FORMAT = '#{pane_dead}:#{pane_dead_status}:#{session_name}';\nconst STOP_GRACE_MS = 3_000;\nconst STOP_CLOSE_TIMEOUT_MS = 1_000;\nconst EXECUTABLE_MODE = 0o755;\nconst SESSION_PREFIX = 'doom-runner-';\nconst OWNED_TARGET_PATTERN = /^doom-runner-[A-Za-z0-9_-]+$/;\nconst SOCKET_HASH_LENGTH = 12;\nconst NON_INTERACTIVE_ENV = { NO_COLOR: '1', CI: '1' };\nconst PACKAGE_BY_PLATFORM: Readonly<Record<string, string>> = {\n 'darwin-arm64': '@agimon-ai/doompi-runner-rmux-darwin-arm64',\n 'darwin-x64': '@agimon-ai/doompi-runner-rmux-darwin-x64',\n 'linux-arm64': '@agimon-ai/doompi-runner-rmux-linux-arm64',\n 'linux-x64': '@agimon-ai/doompi-runner-rmux-linux-x64',\n};\n\n@injectable()\nexport class RmuxBackend implements IRmuxBackend {\n private clientPromise: Promise<Rmux | undefined> | undefined;\n private readonly runs = new Map<string, PtyRun>();\n private readonly completions = new Map<string, Promise<ExitResult>>();\n\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n async launch(request: RmuxLaunchRequest): Promise<RunHandle | undefined> {\n const rmux = await this.client();\n if (!rmux) return undefined;\n\n this.paths.ensureDirectories();\n const target = `${SESSION_PREFIX}${request.id}`;\n const logPath = this.paths.logPathFor(request.id);\n const aux = this.auxPaths(request.id, request.sessionId);\n let created = false;\n let released = false;\n\n try {\n this.prepareFiles(request, logPath, aux);\n await rmux.cmd(\n 'new-session',\n '-d',\n '-s',\n target,\n '-c',\n request.cwd,\n process.execPath,\n runtimeEntry('runnerHost'),\n aux.spec,\n aux.gate,\n aux.exit,\n { check: true },\n );\n created = true;\n // No remain-on-exit: a finished pane ends its session, and the last session\n // ending stops the server, so a completed run leaves no process behind.\n await rmux.cmd('pipe-pane', '-t', target, this.logPipeCommand(logPath, aux.logDone), { check: true });\n\n const pane = rmux.session(target).pane(0, 0);\n const pid = await panePid(rmux, target);\n fs.writeFileSync(aux.gate, '', { mode: 0o600 });\n released = true;\n\n let buffering = true;\n const completion = this.monitor(rmux, pane, target, aux);\n this.trackCompletion(target, completion);\n const stop = (): Promise<boolean> => this.stop(target, pid);\n const handle: RunHandle = {\n id: request.id,\n name: request.name,\n pid,\n logPath,\n backend: 'rmux',\n backendTarget: target,\n output: () => (buffering ? readTail(logPath, getResultMaxBytes() * 2) : ''),\n completion: () => completion,\n detach: () => {\n buffering = false;\n },\n stop,\n };\n\n if (request.interactive) this.runs.set(request.name, this.interactiveRun(handle, pane, completion));\n void completion\n .finally(() => this.runs.delete(request.name))\n .catch((error: unknown) => process.emitWarning(`RMUX completion failed for ${target}: ${errorMessage(error)}`));\n return handle;\n } catch (error) {\n if (released) throw error;\n if (created) {\n await rmux\n .session(target)\n .kill()\n .catch((cleanupError: unknown) =>\n process.emitWarning(`Could not clean up failed RMUX launch ${target}: ${errorMessage(cleanupError)}`),\n );\n }\n cleanupSupervisorFiles(aux);\n process.emitWarning(`RMUX launch unavailable, using native runner: ${errorMessage(error)}`);\n return undefined;\n }\n }\n\n async stop(target: string, expectedPid: number): Promise<boolean> {\n if (!OWNED_TARGET_PATTERN.test(target) || !Number.isInteger(expectedPid) || expectedPid <= 0) return false;\n\n const rmux = await this.client();\n if (!rmux) return false;\n try {\n // A session that already ended took its process group with it.\n if (await sessionMissing(rmux, target)) return true;\n\n const pid = await panePid(rmux, target);\n if (pid !== expectedPid) return false;\n const signalled = signalPane(pid, target);\n if (signalled === 'gone') return true;\n if (signalled === 'failed') return false;\n\n if (await waitForPaneStop(rmux, target, STOP_GRACE_MS)) return true;\n if (await sessionMissing(rmux, target)) return true;\n\n // Revalidate after the grace period so a replacement pane is never closed.\n const currentPid = await panePid(rmux, target);\n if (currentPid !== expectedPid) return false;\n\n return closeOwnedSession(rmux, target);\n } catch (error) {\n process.emitWarning(`Could not stop RMUX target ${target}: ${errorMessage(error)}`);\n return false;\n }\n }\n\n async watch(id: string, target: string, sessionId?: string): Promise<ExitResult | undefined> {\n const rmux = await this.client();\n if (!rmux) return undefined;\n const pane = rmux.session(target).pane(0, 0);\n try {\n await rmux.displayMessage('#{pane_id}', { target });\n } catch (error) {\n process.emitWarning(`Could not watch RMUX target ${target}: ${errorMessage(error)}`);\n return undefined;\n }\n const completion = this.monitor(rmux, pane, target, this.auxPaths(id, sessionId));\n this.trackCompletion(target, completion);\n return completion;\n }\n\n async input(target: string, text: string): Promise<boolean> {\n const rmux = await this.client();\n if (!rmux) return false;\n const result = await rmux\n .session(target)\n .pane(0, 0)\n .sendText(text)\n .catch((error: unknown) => {\n process.emitWarning(`Could not send input to RMUX target ${target}: ${errorMessage(error)}`);\n return undefined;\n });\n return result !== undefined;\n }\n\n get(name: string): PtyRun | undefined {\n return this.runs.get(name);\n }\n\n readOutcome(id: string, sessionId: string): ExitResult | undefined {\n return readExitMetadata(this.auxPaths(id, sessionId).exit);\n }\n\n private trackCompletion(target: string, completion: Promise<ExitResult>): void {\n this.completions.set(target, completion);\n void completion\n .finally(() => this.completions.delete(target))\n .catch((error: unknown) => process.emitWarning(`RMUX monitor failed for ${target}: ${errorMessage(error)}`));\n }\n\n private client(): Promise<Rmux | undefined> {\n this.clientPromise ??= this.resolveClient();\n return this.clientPromise;\n }\n\n private async resolveClient(): Promise<Rmux | undefined> {\n const binaries = [bundledBinary(), 'rmux'].filter((value): value is string => value !== undefined);\n for (const binary of binaries) {\n try {\n if (binary !== 'rmux') fs.chmodSync(binary, EXECUTABLE_MODE);\n const client = new RMUX({ binary, socketName: socketName(this.paths.repositoryPath()) });\n await client.capabilities();\n return client;\n } catch (error) {\n process.emitWarning(`RMUX candidate ${binary} is unavailable: ${errorMessage(error)}`);\n // Try the next compatible candidate, then let the native launcher take over.\n }\n }\n return undefined;\n }\n\n private prepareFiles(request: RmuxLaunchRequest, logPath: string, aux: SupervisorPaths): void {\n cleanupSupervisorFiles(aux);\n fs.writeFileSync(logPath, '', { mode: 0o600 });\n const env = environment(request.sessionId, request.interactive);\n // runnerHost supervises the command and watches the lifeline itself, so an\n // owner that dies mid-run is handled inside the pane rather than around it.\n const spec: CommandSpec = { command: request.command, cwd: request.cwd, env };\n writeCommandSpec(aux.spec, spec);\n }\n\n private auxPaths(id: string, sessionId?: string): SupervisorPaths {\n return supervisorPaths(this.paths.stateDirectory(sessionId), id);\n }\n\n private logPipeCommand(logPath: string, donePath: string): string {\n return shellJoin([\n process.execPath,\n runtimeEntry('logSink'),\n logPath,\n this.paths.rotatedLogPathFor(path.basename(logPath, '.log')),\n String(getLogMaxBytes()),\n donePath,\n ]);\n }\n\n private async monitor(rmux: Rmux, pane: Pane, target: string, aux: SupervisorPaths): Promise<ExitResult> {\n try {\n const paneCode = await waitForRunEnd(rmux, target, aux.exit);\n await waitForLogDrain(rmux, target, aux.logDone);\n return readExitMetadata(aux.exit) ?? { code: paneCode, signal: null };\n } finally {\n // The pane ends its own session, so this only covers a session that somehow\n // outlived its pane. Killing unconditionally would warn on every clean run.\n if (!(await sessionMissing(rmux, target).catch(() => false))) {\n await pane.server\n .session(target)\n .kill()\n .catch((error: unknown) =>\n process.emitWarning(`Could not clean up completed RMUX target ${target}: ${errorMessage(error)}`),\n );\n }\n cleanupSupervisorFiles(aux);\n }\n }\n\n private interactiveRun(handle: RunHandle, pane: Pane, completion: Promise<ExitResult>): PtyRun {\n let screen = '';\n let active = true;\n const handlers = new Set<(data: string) => void>();\n const refresh = async (): Promise<void> => {\n while (active) {\n const next = await pane.captureText().catch((error: unknown) => {\n process.emitWarning(`Could not capture RMUX screen for ${handle.name}: ${errorMessage(error)}`);\n return screen;\n });\n if (next !== screen) {\n screen = next;\n for (const handler of handlers) handler(next);\n }\n await delay(POLL_MS);\n }\n };\n void refresh();\n void completion\n .finally(() => {\n active = false;\n })\n .catch((error: unknown) =>\n process.emitWarning(`RMUX interactive completion failed for ${handle.name}: ${errorMessage(error)}`),\n );\n return {\n ...handle,\n write: (text) => {\n void pane.sendText(text).catch((error: unknown) => process.emitWarning(errorMessage(error)));\n },\n screen: () => screen,\n onData: (handler) => {\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n resize: (cols, rows) => {\n void pane\n .resize({ width: cols, height: rows })\n .catch((error: unknown) => process.emitWarning(errorMessage(error)));\n },\n };\n }\n}\n\nexport function rmuxPackageForTarget(platform: string, architecture: string): string | undefined {\n const target = `${platform}-${architecture}`;\n const packageName = PACKAGE_BY_PLATFORM[target];\n if (!packageName) process.emitWarning(`Bundled RMUX binary is unavailable for unsupported target ${target}`);\n return packageName;\n}\n\nfunction bundledBinary(): string | undefined {\n const packageName = rmuxPackageForTarget(process.platform, process.arch);\n if (!packageName) return undefined;\n try {\n const require = createRequire(import.meta.url);\n const manifest = require.resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), 'vendor', 'bin', 'rmux');\n } catch (error) {\n process.emitWarning(`Bundled RMUX binary is unavailable for ${packageName}: ${errorMessage(error)}`);\n return undefined;\n }\n}\n\nfunction socketName(repositoryPath: string): string {\n const hash = createHash('sha256').update(repositoryPath).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return `${SESSION_PREFIX}${hash}`;\n}\n\nfunction environment(sessionId: string, interactive: boolean): Record<string, string> {\n const values: NodeJS.ProcessEnv = {\n ...process.env,\n ...(interactive ? {} : NON_INTERACTIVE_ENV),\n // Applied to interactive runs as well: a pane is a tty either way, and an\n // agent sending input to a prompt still has no way past a pager.\n ...NO_TERMINAL_INPUT_ENV,\n PI_SESSION_ID: sessionId,\n };\n if (!interactive) delete values['FORCE_COLOR'];\n return Object.fromEntries(\n Object.entries(values).filter((entry): entry is [string, string] => entry[1] !== undefined),\n );\n}\n\nasync function panePid(rmux: Rmux, target: string): Promise<number> {\n const response = await rmux.displayMessage('#{pane_pid}', { target });\n const pid = Number(response['message']);\n if (!Number.isInteger(pid) || pid <= 0) throw new Error(`RMUX did not report a pane pid for ${target}`);\n return pid;\n}\n\n/**\n * Waits for the run to end, preferring the supervisor's own record of it.\n *\n * The sidecar is written before the command's pane can end, and it outlives the\n * server that the pane's exit takes down with it. Asking the server is the\n * fallback for a supervisor that died without recording anything.\n */\nasync function waitForRunEnd(rmux: Rmux, target: string, exitPath: string): Promise<number | null> {\n for (;;) {\n if (fs.existsSync(exitPath)) return null;\n const state = await readPaneExit(rmux, target);\n if (state.exited) return state.code;\n await delay(POLL_MS);\n }\n}\n\n/**\n * Gives the log sink a moment to mark the log complete.\n *\n * The sink dies with the session it was piped from, so once the session is gone\n * the marker is never coming and there is nothing left to wait for. Its writes\n * are synchronous, so the log itself is already on disk either way.\n */\nasync function waitForLogDrain(rmux: Rmux, target: string, donePath: string): Promise<void> {\n const deadline = Date.now() + LOG_DRAIN_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (fs.existsSync(donePath)) return;\n if (await sessionMissing(rmux, target)) return;\n await delay(LOG_DRAIN_POLL_MS);\n }\n}\n\nasync function closeOwnedSession(rmux: Rmux, target: string): Promise<boolean> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (result: boolean): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(result);\n };\n const timer = setTimeout(() => {\n process.emitWarning(`Timed out closing RMUX target ${target} after ${STOP_CLOSE_TIMEOUT_MS}ms`);\n finish(false);\n }, STOP_CLOSE_TIMEOUT_MS);\n void rmux\n .session(target)\n .kill()\n .then(\n () => finish(true),\n (error: unknown) => {\n process.emitWarning(`Could not close RMUX target ${target}: ${errorMessage(error)}`);\n finish(false);\n },\n );\n });\n}\n\nasync function waitForPaneStop(rmux: Rmux, target: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n if ((await readPaneExit(rmux, target)).exited) return true;\n } catch (error) {\n process.emitWarning(`Could not verify RMUX target ${target} stopped: ${errorMessage(error)}`);\n return false;\n }\n await delay(POLL_MS);\n }\n return false;\n}\n\n/**\n * One formatted read answers both questions this poll asks, so watching a run\n * costs the same single rmux call it always did.\n */\nasync function readPaneExit(rmux: Rmux, target: string): Promise<{ exited: boolean; code: number | null }> {\n let message: string;\n try {\n const response = await rmux.displayMessage(PANE_STATE_FORMAT, { target });\n message = typeof response['message'] === 'string' ? response['message'] : '';\n } catch {\n // The last session ending stops the server, so a server that cannot be\n // reached is the ordinary end of the last run rather than a fault.\n return { exited: true, code: null };\n }\n const [dead, status, ...rest] = message.trim().split(':');\n // A missing session formats as a live pane with no name rather than failing,\n // so the name is what says whether there is still anything to watch.\n if (rest.join(':') === '') return { exited: true, code: null };\n if (dead === '1' || dead === 'true') {\n const code = Number(status);\n return { exited: true, code: Number.isInteger(code) ? code : null };\n }\n return { exited: false, code: null };\n}\n\n/** A session that cannot be found, on a server that may itself be gone. */\nasync function sessionMissing(rmux: Rmux, target: string): Promise<boolean> {\n try {\n const probe = await rmux.cmd('has-session', '-t', target);\n return probe.returnCode !== 0;\n } catch {\n // No server to ask means no session to find.\n return true;\n }\n}\n\n/**\n * A pane that was already gone and a signal that could not be delivered are\n * opposite answers to \"is it stopped\", so they are reported apart.\n */\nfunction signalPane(pid: number, target: string): 'signalled' | 'gone' | 'failed' {\n try {\n process.kill(pid, 'SIGTERM');\n return 'signalled';\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ESRCH') return 'gone';\n process.emitWarning(`Could not signal RMUX target ${target}: ${errorMessage(error)}`);\n return 'failed';\n }\n}\n\nfunction readTail(target: string, maxBytes: number): string {\n try {\n const text = fs.readFileSync(target, 'utf8');\n return text.length > maxBytes ? text.slice(-maxBytes) : text;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n process.emitWarning(`Could not read RMUX log ${target}: ${errorMessage(error)}`);\n }\n return '';\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RmuxBackend.mjs","names":[],"sources":["../../../src/services/RmuxBackend/RmuxBackend.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { type Pane, RMUX, type Rmux } from '@rmux/sdk';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport {\n type CommandSpec,\n cleanupSupervisorFiles,\n NO_TERMINAL_INPUT_ENV,\n readExitMetadata,\n runtimeEntry,\n type SupervisorPaths,\n shellJoin,\n supervisorPaths,\n writeCommandSpec,\n} from '../../schemas/runnerSpec.ts';\nimport { getLogMaxBytes, getResultMaxBytes } from '../../types/config.ts';\nimport { TYPES } from '../../types/index.ts';\nimport type { RunHandle } from '../Launcher/index.ts';\nimport type { PtyRun } from '../PtyHost/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { ExitResult } from '../Spawner/index.ts';\nimport type { IRmuxBackend, RmuxLaunchRequest } from './types.ts';\n\nconst POLL_MS = 100;\nconst LOG_DRAIN_TIMEOUT_MS = 2_000;\nconst LOG_DRAIN_POLL_MS = 25;\n/** Pane liveness, its exit status, and whether the session still exists, in one read. */\nconst PANE_STATE_FORMAT = '#{pane_dead}:#{pane_dead_status}:#{session_name}';\nconst STOP_GRACE_MS = 3_000;\nconst STOP_CLOSE_TIMEOUT_MS = 1_000;\nconst EXECUTABLE_MODE = 0o755;\nconst SESSION_PREFIX = 'doom-runner-';\nconst OWNED_TARGET_PATTERN = /^doom-runner-[A-Za-z0-9_-]+$/;\nconst SOCKET_HASH_LENGTH = 12;\nconst NON_INTERACTIVE_ENV = { NO_COLOR: '1', CI: '1' };\nconst PACKAGE_BY_PLATFORM: Readonly<Record<string, string>> = {\n 'darwin-arm64': '@agimon-ai/doompi-runner-rmux-darwin-arm64',\n 'darwin-x64': '@agimon-ai/doompi-runner-rmux-darwin-x64',\n 'linux-arm64': '@agimon-ai/doompi-runner-rmux-linux-arm64',\n 'linux-x64': '@agimon-ai/doompi-runner-rmux-linux-x64',\n};\n\n@injectable()\nexport class RmuxBackend implements IRmuxBackend {\n private clientPromise: Promise<Rmux | undefined> | undefined;\n private readonly runs = new Map<string, PtyRun>();\n private readonly completions = new Map<string, Promise<ExitResult>>();\n\n constructor(@inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths) {}\n\n async launch(request: RmuxLaunchRequest): Promise<RunHandle | undefined> {\n const rmux = await this.client();\n if (!rmux) return undefined;\n\n this.paths.ensureDirectories();\n const target = `${SESSION_PREFIX}${request.id}`;\n const logPath = this.paths.logPathFor(request.id);\n const aux = this.auxPaths(request.id, request.sessionId);\n let created = false;\n let released = false;\n\n try {\n this.prepareFiles(request, logPath, aux);\n await rmux.cmd(\n 'new-session',\n '-d',\n '-s',\n target,\n '-c',\n request.cwd,\n process.execPath,\n runtimeEntry('runnerHost'),\n aux.spec,\n aux.gate,\n aux.exit,\n { check: true },\n );\n created = true;\n // No remain-on-exit: a finished pane ends its session, and the last session\n // ending stops the server, so a completed run leaves no process behind.\n await rmux.cmd('pipe-pane', '-t', target, this.logPipeCommand(logPath, aux.logDone), { check: true });\n\n const pane = rmux.session(target).pane(0, 0);\n const pid = await panePid(rmux, target);\n fs.writeFileSync(aux.gate, '', { mode: 0o600 });\n released = true;\n\n let buffering = true;\n const completion = this.monitor(rmux, pane, target, aux);\n this.trackCompletion(target, completion);\n const stop = (): Promise<boolean> => this.stop(target, pid);\n const handle: RunHandle = {\n id: request.id,\n name: request.name,\n pid,\n logPath,\n backend: 'rmux',\n backendTarget: target,\n output: () => (buffering ? readTail(logPath, getResultMaxBytes() * 2) : ''),\n completion: () => completion,\n detach: () => {\n buffering = false;\n },\n stop,\n };\n\n if (request.interactive) this.runs.set(request.name, this.interactiveRun(handle, pane, completion));\n void completion\n .finally(() => this.runs.delete(request.name))\n .catch((error: unknown) => process.emitWarning(`RMUX completion failed for ${target}: ${errorMessage(error)}`));\n return handle;\n } catch (error) {\n if (released) throw error;\n if (created) {\n await rmux\n .session(target)\n .kill()\n .catch((cleanupError: unknown) =>\n process.emitWarning(`Could not clean up failed RMUX launch ${target}: ${errorMessage(cleanupError)}`),\n );\n }\n cleanupSupervisorFiles(aux);\n process.emitWarning(`RMUX launch unavailable, using native runner: ${errorMessage(error)}`);\n return undefined;\n }\n }\n\n async stop(target: string, expectedPid: number): Promise<boolean> {\n if (!OWNED_TARGET_PATTERN.test(target) || !Number.isInteger(expectedPid) || expectedPid <= 0) return false;\n\n const rmux = await this.client();\n if (!rmux) return false;\n try {\n // A session that already ended took its process group with it.\n if (await sessionMissing(rmux, target)) return true;\n\n const pid = await panePid(rmux, target);\n if (pid !== expectedPid) return false;\n const signalled = signalPane(pid, target);\n if (signalled === 'gone') return true;\n if (signalled === 'failed') return false;\n\n if (await waitForPaneStop(rmux, target, STOP_GRACE_MS)) return true;\n if (await sessionMissing(rmux, target)) return true;\n\n // Revalidate after the grace period so a replacement pane is never closed.\n const currentPid = await panePid(rmux, target);\n if (currentPid !== expectedPid) return false;\n\n return closeOwnedSession(rmux, target);\n } catch (error) {\n process.emitWarning(`Could not stop RMUX target ${target}: ${errorMessage(error)}`);\n return false;\n }\n }\n\n async watch(id: string, target: string, sessionId?: string): Promise<ExitResult | undefined> {\n const rmux = await this.client();\n if (!rmux) return undefined;\n const pane = rmux.session(target).pane(0, 0);\n try {\n await rmux.displayMessage('#{pane_id}', { target });\n } catch (error) {\n process.emitWarning(`Could not watch RMUX target ${target}: ${errorMessage(error)}`);\n return undefined;\n }\n const completion = this.monitor(rmux, pane, target, this.auxPaths(id, sessionId));\n this.trackCompletion(target, completion);\n return completion;\n }\n\n async input(target: string, text: string): Promise<boolean> {\n const rmux = await this.client();\n if (!rmux) return false;\n const result = await rmux\n .session(target)\n .pane(0, 0)\n .sendText(text)\n .catch((error: unknown) => {\n process.emitWarning(`Could not send input to RMUX target ${target}: ${errorMessage(error)}`);\n return undefined;\n });\n return result !== undefined;\n }\n\n get(name: string): PtyRun | undefined {\n return this.runs.get(name);\n }\n\n readOutcome(id: string, sessionId: string): ExitResult | undefined {\n return readExitMetadata(this.auxPaths(id, sessionId).exit);\n }\n\n private trackCompletion(target: string, completion: Promise<ExitResult>): void {\n this.completions.set(target, completion);\n void completion\n .finally(() => this.completions.delete(target))\n .catch((error: unknown) => process.emitWarning(`RMUX monitor failed for ${target}: ${errorMessage(error)}`));\n }\n\n private client(): Promise<Rmux | undefined> {\n this.clientPromise ??= this.resolveClient();\n return this.clientPromise;\n }\n\n private async resolveClient(): Promise<Rmux | undefined> {\n const binaries = [bundledBinary(), 'rmux'].filter((value): value is string => value !== undefined);\n for (const binary of binaries) {\n try {\n if (binary !== 'rmux') fs.chmodSync(binary, EXECUTABLE_MODE);\n const client = new RMUX({ binary, socketName: socketName(this.paths.repositoryPath()) });\n await client.capabilities();\n return client;\n } catch (error) {\n process.emitWarning(`RMUX candidate ${binary} is unavailable: ${errorMessage(error)}`);\n // Try the next compatible candidate, then let the native launcher take over.\n }\n }\n return undefined;\n }\n\n private prepareFiles(request: RmuxLaunchRequest, logPath: string, aux: SupervisorPaths): void {\n cleanupSupervisorFiles(aux);\n fs.writeFileSync(logPath, '', { mode: 0o600 });\n const env = environment(request.sessionId, request.interactive);\n // runnerHost supervises the command and watches the lifeline itself, so an\n // owner that dies mid-run is handled inside the pane rather than around it.\n const spec: CommandSpec = { command: request.command, cwd: request.cwd, env };\n writeCommandSpec(aux.spec, spec);\n }\n\n private auxPaths(id: string, sessionId?: string): SupervisorPaths {\n return supervisorPaths(this.paths.stateDirectory(sessionId), id);\n }\n\n private logPipeCommand(logPath: string, donePath: string): string {\n return shellJoin([\n process.execPath,\n runtimeEntry('logSink'),\n logPath,\n this.paths.rotatedLogPathFor(path.basename(logPath, '.log')),\n String(getLogMaxBytes()),\n donePath,\n ]);\n }\n\n private async monitor(rmux: Rmux, pane: Pane, target: string, aux: SupervisorPaths): Promise<ExitResult> {\n try {\n const paneCode = await waitForRunEnd(rmux, target, aux.exit);\n await waitForLogDrain(rmux, target, aux.logDone);\n return readExitMetadata(aux.exit) ?? { code: paneCode, signal: null };\n } finally {\n // The pane ends its own session, so this only covers a session that somehow\n // outlived its pane. Killing unconditionally would warn on every clean run.\n if (!(await sessionMissing(rmux, target).catch(() => false))) {\n await pane.server\n .session(target)\n .kill()\n .catch((error: unknown) =>\n process.emitWarning(`Could not clean up completed RMUX target ${target}: ${errorMessage(error)}`),\n );\n }\n cleanupSupervisorFiles(aux);\n }\n }\n\n private interactiveRun(handle: RunHandle, pane: Pane, completion: Promise<ExitResult>): PtyRun {\n let screen = '';\n let active = true;\n const handlers = new Set<(data: string) => void>();\n const refresh = async (): Promise<void> => {\n while (active) {\n const next = await pane.captureText().catch((error: unknown) => {\n process.emitWarning(`Could not capture RMUX screen for ${handle.name}: ${errorMessage(error)}`);\n return screen;\n });\n if (next !== screen) {\n screen = next;\n for (const handler of handlers) handler(next);\n }\n await delay(POLL_MS);\n }\n };\n void refresh();\n void completion\n .finally(() => {\n active = false;\n })\n .catch((error: unknown) =>\n process.emitWarning(`RMUX interactive completion failed for ${handle.name}: ${errorMessage(error)}`),\n );\n return {\n ...handle,\n write: (text) => {\n void pane.sendText(text).catch((error: unknown) => process.emitWarning(errorMessage(error)));\n },\n screen: () => screen,\n onData: (handler) => {\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n resize: (cols, rows) => {\n void pane\n .resize({ width: cols, height: rows })\n .catch((error: unknown) => process.emitWarning(errorMessage(error)));\n },\n };\n }\n}\n\nexport function rmuxPackageForTarget(platform: string, architecture: string): string | undefined {\n const target = `${platform}-${architecture}`;\n const packageName = PACKAGE_BY_PLATFORM[target];\n if (!packageName) process.emitWarning(`Bundled RMUX binary is unavailable for unsupported target ${target}`);\n return packageName;\n}\n\nfunction bundledBinary(): string | undefined {\n const packageName = rmuxPackageForTarget(process.platform, process.arch);\n if (!packageName) return undefined;\n try {\n const require = createRequire(import.meta.url);\n const manifest = require.resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), 'vendor', 'bin', 'rmux');\n } catch (error) {\n process.emitWarning(`Bundled RMUX binary is unavailable for ${packageName}: ${errorMessage(error)}`);\n return undefined;\n }\n}\n\nfunction socketName(repositoryPath: string): string {\n const hash = createHash('sha256').update(repositoryPath).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return `${SESSION_PREFIX}${hash}`;\n}\n\nfunction environment(sessionId: string, interactive: boolean): Record<string, string> {\n const values: NodeJS.ProcessEnv = {\n ...process.env,\n ...(interactive ? {} : NON_INTERACTIVE_ENV),\n // Applied to interactive runs as well: a pane is a tty either way, and an\n // agent sending input to a prompt still has no way past a pager.\n ...NO_TERMINAL_INPUT_ENV,\n PI_SESSION_ID: sessionId,\n };\n if (!interactive) delete values['FORCE_COLOR'];\n return Object.fromEntries(\n Object.entries(values).filter((entry): entry is [string, string] => entry[1] !== undefined),\n );\n}\n\nasync function panePid(rmux: Rmux, target: string): Promise<number> {\n const response = await rmux.displayMessage('#{pane_pid}', { target });\n const pid = Number(response['message']);\n if (!Number.isInteger(pid) || pid <= 0) throw new Error(`RMUX did not report a pane pid for ${target}`);\n return pid;\n}\n\n/**\n * Waits for the run to end, preferring the supervisor's own record of it.\n *\n * The sidecar is written before the command's pane can end, and it outlives the\n * server that the pane's exit takes down with it. Asking the server is the\n * fallback for a supervisor that died without recording anything.\n */\nasync function waitForRunEnd(rmux: Rmux, target: string, exitPath: string): Promise<number | null> {\n for (;;) {\n if (fs.existsSync(exitPath)) return null;\n const state = await readPaneExit(rmux, target);\n if (state.exited) return state.code;\n await delay(POLL_MS);\n }\n}\n\n/**\n * Gives the log sink a moment to mark the log complete.\n *\n * The sink dies with the session it was piped from, so once the session is gone\n * the marker is never coming and there is nothing left to wait for. Its writes\n * are synchronous, so the log itself is already on disk either way.\n */\nasync function waitForLogDrain(rmux: Rmux, target: string, donePath: string): Promise<void> {\n const deadline = Date.now() + LOG_DRAIN_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (fs.existsSync(donePath)) return;\n if (await sessionMissing(rmux, target)) return;\n await delay(LOG_DRAIN_POLL_MS);\n }\n}\n\nasync function closeOwnedSession(rmux: Rmux, target: string): Promise<boolean> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (result: boolean): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(result);\n };\n const timer = setTimeout(() => {\n process.emitWarning(`Timed out closing RMUX target ${target} after ${STOP_CLOSE_TIMEOUT_MS}ms`);\n finish(false);\n }, STOP_CLOSE_TIMEOUT_MS);\n void rmux\n .session(target)\n .kill()\n .then(\n () => finish(true),\n (error: unknown) => {\n process.emitWarning(`Could not close RMUX target ${target}: ${errorMessage(error)}`);\n finish(false);\n },\n );\n });\n}\n\nasync function waitForPaneStop(rmux: Rmux, target: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n if ((await readPaneExit(rmux, target)).exited) return true;\n } catch (error) {\n process.emitWarning(`Could not verify RMUX target ${target} stopped: ${errorMessage(error)}`);\n return false;\n }\n await delay(POLL_MS);\n }\n return false;\n}\n\n/**\n * One formatted read answers both questions this poll asks, so watching a run\n * costs the same single rmux call it always did.\n */\nasync function readPaneExit(rmux: Rmux, target: string): Promise<{ exited: boolean; code: number | null }> {\n let message: string;\n try {\n const response = await rmux.displayMessage(PANE_STATE_FORMAT, { target });\n message = typeof response['message'] === 'string' ? response['message'] : '';\n } catch {\n // The last session ending stops the server, so a server that cannot be\n // reached is the ordinary end of the last run rather than a fault.\n return { exited: true, code: null };\n }\n const [dead, status, ...rest] = message.trim().split(':');\n // A missing session formats as a live pane with no name rather than failing,\n // so the name is what says whether there is still anything to watch.\n if (rest.join(':') === '') return { exited: true, code: null };\n if (dead === '1' || dead === 'true') {\n const code = Number(status);\n return { exited: true, code: Number.isInteger(code) ? code : null };\n }\n return { exited: false, code: null };\n}\n\n/** A session that cannot be found, on a server that may itself be gone. */\nasync function sessionMissing(rmux: Rmux, target: string): Promise<boolean> {\n try {\n const probe = await rmux.cmd('has-session', '-t', target);\n return probe.returnCode !== 0;\n } catch {\n // No server to ask means no session to find.\n return true;\n }\n}\n\n/**\n * A pane that was already gone and a signal that could not be delivered are\n * opposite answers to \"is it stopped\", so they are reported apart.\n */\nfunction signalPane(pid: number, target: string): 'signalled' | 'gone' | 'failed' {\n try {\n process.kill(pid, 'SIGTERM');\n return 'signalled';\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ESRCH') return 'gone';\n process.emitWarning(`Could not signal RMUX target ${target}: ${errorMessage(error)}`);\n return 'failed';\n }\n}\n\nfunction readTail(target: string, maxBytes: number): string {\n try {\n const text = fs.readFileSync(target, 'utf8');\n return text.length > maxBytes ? text.slice(-maxBytes) : text;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n process.emitWarning(`Could not read RMUX log ${target}: ${errorMessage(error)}`);\n }\n return '';\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../types.cjs"),t=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs");let i=require("inversify");require("reflect-metadata/lite");const a=new Set([`sudo`,`env`,`time`,`nohup`,`exec`]);let o=class{registry;constructor(e){this.registry=e}async allocate(e,t,n){let r=c(n??s(e)),i=new Set((await this.registry.listBySession(t)).map(e=>e.name));if(!i.has(r))return r;for(let e=2;e<1e3;e+=1){let t=`${r}-${e}`;if(!i.has(t))return t}throw Error(`Could not find a free runner name based on "${r}"`)}};o=r.default([(0,i.injectable)(),n.default(0,(0,i.inject)(e.TYPES.RunnerRegistry)),t.default(`design:paramtypes`,[Object])],o);function s(e){return e.split(/\s+/).filter(e=>e.length>0&&!e.startsWith(`-`)&&!e.includes(`=`)).filter(e=>!a.has(e)).slice(0,4).join(`-`)}function c(e){let t=e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,32).replace(/-+$/,``);return t.length>0?t:`runner`}Object.defineProperty(exports,"RunnerNamer",{enumerable:!0,get:function(){return o}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../types/index.cjs"),t=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs");let i=require("inversify");require("reflect-metadata/lite");const a=new Set([`sudo`,`env`,`time`,`nohup`,`exec`]);let o=class{registry;constructor(e){this.registry=e}async allocate(e,t,n){let r=c(n??s(e)),i=new Set((await this.registry.listBySession(t)).map(e=>e.name));if(!i.has(r))return r;for(let e=2;e<1e3;e+=1){let t=`${r}-${e}`;if(!i.has(t))return t}throw Error(`Could not find a free runner name based on "${r}"`)}};o=r.default([(0,i.injectable)(),n.default(0,(0,i.inject)(e.TYPES.RunnerRegistry)),t.default(`design:paramtypes`,[Object])],o);function s(e){return e.split(/\s+/).filter(e=>e.length>0&&!e.startsWith(`-`)&&!e.includes(`=`)).filter(e=>!a.has(e)).slice(0,4).join(`-`)}function c(e){let t=e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,32).replace(/-+$/,``);return t.length>0?t:`runner`}Object.defineProperty(exports,"RunnerNamer",{enumerable:!0,get:function(){return o}});
2
2
  //# sourceMappingURL=RunnerNamer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RunnerNamer.cjs","names":["injectable","inject","TYPES"],"sources":["../../../src/services/RunnerNamer/RunnerNamer.ts"],"sourcesContent":["import { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types.ts';\nimport type { IRunnerRegistry } from '../RunnerRegistry/index.ts';\nimport type { IRunnerNamer } from './types.ts';\n\nconst MAX_SLUG_LENGTH = 32;\nconst MAX_ATTEMPTS = 1000;\nconst FALLBACK_SLUG = 'runner';\n/** Shell noise that says nothing about what the command actually runs. */\nconst IGNORED_LEADING_WORDS = new Set(['sudo', 'env', 'time', 'nohup', 'exec']);\n\n@injectable()\nexport class RunnerNamer implements IRunnerNamer {\n constructor(@inject(TYPES.RunnerRegistry) private readonly registry: IRunnerRegistry) {}\n\n async allocate(command: string, sessionId: string, requested?: string): Promise<string> {\n const base = slugify(requested ?? deriveSlug(command));\n const taken = new Set((await this.registry.listBySession(sessionId)).map((record) => record.name));\n if (!taken.has(base)) return base;\n for (let suffix = 2; suffix < MAX_ATTEMPTS; suffix += 1) {\n const candidate = `${base}-${suffix}`;\n if (!taken.has(candidate)) return candidate;\n }\n throw new Error(`Could not find a free runner name based on \"${base}\"`);\n }\n}\n\n/**\n * Names come from the first few meaningful words of the command, which is what\n * a reader recognises in a runner list: `nx-dev-start-api`, not `runner-3`.\n */\nfunction deriveSlug(command: string): string {\n const words = command.split(/\\s+/).filter((word) => word.length > 0 && !word.startsWith('-') && !word.includes('='));\n const meaningful = words.filter((word) => !IGNORED_LEADING_WORDS.has(word));\n return meaningful.slice(0, 4).join('-');\n}\n\nfunction slugify(value: string): string {\n const slug = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, MAX_SLUG_LENGTH)\n .replace(/-+$/, '');\n return slug.length > 0 ? slug : FALLBACK_SLUG;\n}\n"],"mappings":"waAMA,MAIM,EAAwB,IAAI,IAAI,CAAC,OAAQ,MAAO,OAAQ,QAAS,MAAM,CAAC,EAGvE,IAAM,EAAN,KAA0C,CACY,SAA3D,YAAY,EAA0E,CAA3B,KAAA,SAAA,CAA4B,CAEvF,MAAM,SAAS,EAAiB,EAAmB,EAAqC,CACtF,IAAM,EAAO,EAAQ,GAAa,EAAW,CAAO,CAAC,EAC/C,EAAQ,IAAI,KAAK,MAAM,KAAK,SAAS,cAAc,CAAS,EAAA,CAAG,IAAK,GAAW,EAAO,IAAI,CAAC,EACjG,GAAI,CAAC,EAAM,IAAI,CAAI,EAAG,OAAO,EAC7B,IAAK,IAAI,EAAS,EAAG,EAAS,IAAc,GAAU,EAAG,CACvD,IAAM,EAAY,GAAG,EAAK,GAAG,IAC7B,GAAI,CAAC,EAAM,IAAI,CAAS,EAAG,OAAO,CACpC,CACA,MAAU,MAAM,+CAA+C,EAAK,EAAE,CACxE,CACF,gBAdCA,EAAAA,EAAAA,WAAAA,CAAW,EAEGC,EAAAA,QAAAA,GAAAA,EAAAA,EAAAA,OAAAA,CAAOC,EAAAA,MAAM,cAAc,CAAA,8CAkB1C,SAAS,EAAW,EAAyB,CAG3C,OAFc,EAAQ,MAAM,KAAK,CAAC,CAAC,OAAQ,GAAS,EAAK,OAAS,GAAK,CAAC,EAAK,WAAW,GAAG,GAAK,CAAC,EAAK,SAAS,GAAG,CAC3F,CAAC,CAAC,OAAQ,GAAS,CAAC,EAAsB,IAAI,CAAI,CACzD,CAAC,CAAC,MAAM,EAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CACxC,CAEA,SAAS,EAAQ,EAAuB,CACtC,IAAM,EAAO,EACV,YAAY,CAAC,CACb,QAAQ,cAAe,GAAG,CAAC,CAC3B,QAAQ,WAAY,EAAE,CAAC,CACvB,MAAM,EAAG,EAAe,CAAC,CACzB,QAAQ,MAAO,EAAE,EACpB,OAAO,EAAK,OAAS,EAAI,EAAO,QAClC"}
1
+ {"version":3,"file":"RunnerNamer.cjs","names":["injectable","inject","TYPES"],"sources":["../../../src/services/RunnerNamer/RunnerNamer.ts"],"sourcesContent":["import { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types/index.ts';\nimport type { IRunnerRegistry } from '../RunnerRegistry/index.ts';\nimport type { IRunnerNamer } from './types.ts';\n\nconst MAX_SLUG_LENGTH = 32;\nconst MAX_ATTEMPTS = 1000;\nconst FALLBACK_SLUG = 'runner';\n/** Shell noise that says nothing about what the command actually runs. */\nconst IGNORED_LEADING_WORDS = new Set(['sudo', 'env', 'time', 'nohup', 'exec']);\n\n@injectable()\nexport class RunnerNamer implements IRunnerNamer {\n constructor(@inject(TYPES.RunnerRegistry) private readonly registry: IRunnerRegistry) {}\n\n async allocate(command: string, sessionId: string, requested?: string): Promise<string> {\n const base = slugify(requested ?? deriveSlug(command));\n const taken = new Set((await this.registry.listBySession(sessionId)).map((record) => record.name));\n if (!taken.has(base)) return base;\n for (let suffix = 2; suffix < MAX_ATTEMPTS; suffix += 1) {\n const candidate = `${base}-${suffix}`;\n if (!taken.has(candidate)) return candidate;\n }\n throw new Error(`Could not find a free runner name based on \"${base}\"`);\n }\n}\n\n/**\n * Names come from the first few meaningful words of the command, which is what\n * a reader recognises in a runner list: `nx-dev-start-api`, not `runner-3`.\n */\nfunction deriveSlug(command: string): string {\n const words = command.split(/\\s+/).filter((word) => word.length > 0 && !word.startsWith('-') && !word.includes('='));\n const meaningful = words.filter((word) => !IGNORED_LEADING_WORDS.has(word));\n return meaningful.slice(0, 4).join('-');\n}\n\nfunction slugify(value: string): string {\n const slug = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, MAX_SLUG_LENGTH)\n .replace(/-+$/, '');\n return slug.length > 0 ? slug : FALLBACK_SLUG;\n}\n"],"mappings":"8aAMA,MAIM,EAAwB,IAAI,IAAI,CAAC,OAAQ,MAAO,OAAQ,QAAS,MAAM,CAAC,EAGvE,IAAM,EAAN,KAA0C,CACY,SAA3D,YAAY,EAA0E,CAA3B,KAAA,SAAA,CAA4B,CAEvF,MAAM,SAAS,EAAiB,EAAmB,EAAqC,CACtF,IAAM,EAAO,EAAQ,GAAa,EAAW,CAAO,CAAC,EAC/C,EAAQ,IAAI,KAAK,MAAM,KAAK,SAAS,cAAc,CAAS,EAAA,CAAG,IAAK,GAAW,EAAO,IAAI,CAAC,EACjG,GAAI,CAAC,EAAM,IAAI,CAAI,EAAG,OAAO,EAC7B,IAAK,IAAI,EAAS,EAAG,EAAS,IAAc,GAAU,EAAG,CACvD,IAAM,EAAY,GAAG,EAAK,GAAG,IAC7B,GAAI,CAAC,EAAM,IAAI,CAAS,EAAG,OAAO,CACpC,CACA,MAAU,MAAM,+CAA+C,EAAK,EAAE,CACxE,CACF,gBAdCA,EAAAA,EAAAA,WAAAA,CAAW,EAEGC,EAAAA,QAAAA,GAAAA,EAAAA,EAAAA,OAAAA,CAAOC,EAAAA,MAAM,cAAc,CAAA,8CAkB1C,SAAS,EAAW,EAAyB,CAG3C,OAFc,EAAQ,MAAM,KAAK,CAAC,CAAC,OAAQ,GAAS,EAAK,OAAS,GAAK,CAAC,EAAK,WAAW,GAAG,GAAK,CAAC,EAAK,SAAS,GAAG,CAC3F,CAAC,CAAC,OAAQ,GAAS,CAAC,EAAsB,IAAI,CAAI,CACzD,CAAC,CAAC,MAAM,EAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CACxC,CAEA,SAAS,EAAQ,EAAuB,CACtC,IAAM,EAAO,EACV,YAAY,CAAC,CACb,QAAQ,cAAe,GAAG,CAAC,CAC3B,QAAQ,WAAY,EAAE,CAAC,CACvB,MAAM,EAAG,EAAe,CAAC,CACzB,QAAQ,MAAO,EAAE,EACpB,OAAO,EAAK,OAAS,EAAI,EAAO,QAClC"}
@@ -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{inject as i,injectable as a}from"inversify";import"reflect-metadata/lite";const o=new Set([`sudo`,`env`,`time`,`nohup`,`exec`]);let s=class{registry;constructor(e){this.registry=e}async allocate(e,t,n){let r=l(n??c(e)),i=new Set((await this.registry.listBySession(t)).map(e=>e.name));if(!i.has(r))return r;for(let e=2;e<1e3;e+=1){let t=`${r}-${e}`;if(!i.has(t))return t}throw Error(`Could not find a free runner name based on "${r}"`)}};s=r([a(),n(0,i(e.RunnerRegistry)),t(`design:paramtypes`,[Object])],s);function c(e){return e.split(/\s+/).filter(e=>e.length>0&&!e.startsWith(`-`)&&!e.includes(`=`)).filter(e=>!o.has(e)).slice(0,4).join(`-`)}function l(e){let t=e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,32).replace(/-+$/,``);return t.length>0?t:`runner`}export{s as RunnerNamer};
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{inject as i,injectable as a}from"inversify";import"reflect-metadata/lite";const o=new Set([`sudo`,`env`,`time`,`nohup`,`exec`]);let s=class{registry;constructor(e){this.registry=e}async allocate(e,t,n){let r=l(n??c(e)),i=new Set((await this.registry.listBySession(t)).map(e=>e.name));if(!i.has(r))return r;for(let e=2;e<1e3;e+=1){let t=`${r}-${e}`;if(!i.has(t))return t}throw Error(`Could not find a free runner name based on "${r}"`)}};s=r([a(),n(0,i(e.RunnerRegistry)),t(`design:paramtypes`,[Object])],s);function c(e){return e.split(/\s+/).filter(e=>e.length>0&&!e.startsWith(`-`)&&!e.includes(`=`)).filter(e=>!o.has(e)).slice(0,4).join(`-`)}function l(e){let t=e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,32).replace(/-+$/,``);return t.length>0?t:`runner`}export{s as RunnerNamer};
2
2
  //# sourceMappingURL=RunnerNamer.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RunnerNamer.mjs","names":[],"sources":["../../../src/services/RunnerNamer/RunnerNamer.ts"],"sourcesContent":["import { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types.ts';\nimport type { IRunnerRegistry } from '../RunnerRegistry/index.ts';\nimport type { IRunnerNamer } from './types.ts';\n\nconst MAX_SLUG_LENGTH = 32;\nconst MAX_ATTEMPTS = 1000;\nconst FALLBACK_SLUG = 'runner';\n/** Shell noise that says nothing about what the command actually runs. */\nconst IGNORED_LEADING_WORDS = new Set(['sudo', 'env', 'time', 'nohup', 'exec']);\n\n@injectable()\nexport class RunnerNamer implements IRunnerNamer {\n constructor(@inject(TYPES.RunnerRegistry) private readonly registry: IRunnerRegistry) {}\n\n async allocate(command: string, sessionId: string, requested?: string): Promise<string> {\n const base = slugify(requested ?? deriveSlug(command));\n const taken = new Set((await this.registry.listBySession(sessionId)).map((record) => record.name));\n if (!taken.has(base)) return base;\n for (let suffix = 2; suffix < MAX_ATTEMPTS; suffix += 1) {\n const candidate = `${base}-${suffix}`;\n if (!taken.has(candidate)) return candidate;\n }\n throw new Error(`Could not find a free runner name based on \"${base}\"`);\n }\n}\n\n/**\n * Names come from the first few meaningful words of the command, which is what\n * a reader recognises in a runner list: `nx-dev-start-api`, not `runner-3`.\n */\nfunction deriveSlug(command: string): string {\n const words = command.split(/\\s+/).filter((word) => word.length > 0 && !word.startsWith('-') && !word.includes('='));\n const meaningful = words.filter((word) => !IGNORED_LEADING_WORDS.has(word));\n return meaningful.slice(0, 4).join('-');\n}\n\nfunction slugify(value: string): string {\n const slug = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, MAX_SLUG_LENGTH)\n .replace(/-+$/, '');\n return slug.length > 0 ? slug : FALLBACK_SLUG;\n}\n"],"mappings":"qYAMA,MAIM,EAAwB,IAAI,IAAI,CAAC,OAAQ,MAAO,OAAQ,QAAS,MAAM,CAAC,EAGvE,IAAM,EAAN,KAA0C,CACY,SAA3D,YAAY,EAA0E,CAA3B,KAAA,SAAA,CAA4B,CAEvF,MAAM,SAAS,EAAiB,EAAmB,EAAqC,CACtF,IAAM,EAAO,EAAQ,GAAa,EAAW,CAAO,CAAC,EAC/C,EAAQ,IAAI,KAAK,MAAM,KAAK,SAAS,cAAc,CAAS,EAAA,CAAG,IAAK,GAAW,EAAO,IAAI,CAAC,EACjG,GAAI,CAAC,EAAM,IAAI,CAAI,EAAG,OAAO,EAC7B,IAAK,IAAI,EAAS,EAAG,EAAS,IAAc,GAAU,EAAG,CACvD,IAAM,EAAY,GAAG,EAAK,GAAG,IAC7B,GAAI,CAAC,EAAM,IAAI,CAAS,EAAG,OAAO,CACpC,CACA,MAAU,MAAM,+CAA+C,EAAK,EAAE,CACxE,CACF,OAdC,EAAW,EAEG,EAAA,EAAA,EAAO,EAAM,cAAc,CAAA,sCAkB1C,SAAS,EAAW,EAAyB,CAG3C,OAFc,EAAQ,MAAM,KAAK,CAAC,CAAC,OAAQ,GAAS,EAAK,OAAS,GAAK,CAAC,EAAK,WAAW,GAAG,GAAK,CAAC,EAAK,SAAS,GAAG,CAC3F,CAAC,CAAC,OAAQ,GAAS,CAAC,EAAsB,IAAI,CAAI,CACzD,CAAC,CAAC,MAAM,EAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CACxC,CAEA,SAAS,EAAQ,EAAuB,CACtC,IAAM,EAAO,EACV,YAAY,CAAC,CACb,QAAQ,cAAe,GAAG,CAAC,CAC3B,QAAQ,WAAY,EAAE,CAAC,CACvB,MAAM,EAAG,EAAe,CAAC,CACzB,QAAQ,MAAO,EAAE,EACpB,OAAO,EAAK,OAAS,EAAI,EAAO,QAClC"}
1
+ {"version":3,"file":"RunnerNamer.mjs","names":[],"sources":["../../../src/services/RunnerNamer/RunnerNamer.ts"],"sourcesContent":["import { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types/index.ts';\nimport type { IRunnerRegistry } from '../RunnerRegistry/index.ts';\nimport type { IRunnerNamer } from './types.ts';\n\nconst MAX_SLUG_LENGTH = 32;\nconst MAX_ATTEMPTS = 1000;\nconst FALLBACK_SLUG = 'runner';\n/** Shell noise that says nothing about what the command actually runs. */\nconst IGNORED_LEADING_WORDS = new Set(['sudo', 'env', 'time', 'nohup', 'exec']);\n\n@injectable()\nexport class RunnerNamer implements IRunnerNamer {\n constructor(@inject(TYPES.RunnerRegistry) private readonly registry: IRunnerRegistry) {}\n\n async allocate(command: string, sessionId: string, requested?: string): Promise<string> {\n const base = slugify(requested ?? deriveSlug(command));\n const taken = new Set((await this.registry.listBySession(sessionId)).map((record) => record.name));\n if (!taken.has(base)) return base;\n for (let suffix = 2; suffix < MAX_ATTEMPTS; suffix += 1) {\n const candidate = `${base}-${suffix}`;\n if (!taken.has(candidate)) return candidate;\n }\n throw new Error(`Could not find a free runner name based on \"${base}\"`);\n }\n}\n\n/**\n * Names come from the first few meaningful words of the command, which is what\n * a reader recognises in a runner list: `nx-dev-start-api`, not `runner-3`.\n */\nfunction deriveSlug(command: string): string {\n const words = command.split(/\\s+/).filter((word) => word.length > 0 && !word.startsWith('-') && !word.includes('='));\n const meaningful = words.filter((word) => !IGNORED_LEADING_WORDS.has(word));\n return meaningful.slice(0, 4).join('-');\n}\n\nfunction slugify(value: string): string {\n const slug = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, MAX_SLUG_LENGTH)\n .replace(/-+$/, '');\n return slug.length > 0 ? slug : FALLBACK_SLUG;\n}\n"],"mappings":"2YAMA,MAIM,EAAwB,IAAI,IAAI,CAAC,OAAQ,MAAO,OAAQ,QAAS,MAAM,CAAC,EAGvE,IAAM,EAAN,KAA0C,CACY,SAA3D,YAAY,EAA0E,CAA3B,KAAA,SAAA,CAA4B,CAEvF,MAAM,SAAS,EAAiB,EAAmB,EAAqC,CACtF,IAAM,EAAO,EAAQ,GAAa,EAAW,CAAO,CAAC,EAC/C,EAAQ,IAAI,KAAK,MAAM,KAAK,SAAS,cAAc,CAAS,EAAA,CAAG,IAAK,GAAW,EAAO,IAAI,CAAC,EACjG,GAAI,CAAC,EAAM,IAAI,CAAI,EAAG,OAAO,EAC7B,IAAK,IAAI,EAAS,EAAG,EAAS,IAAc,GAAU,EAAG,CACvD,IAAM,EAAY,GAAG,EAAK,GAAG,IAC7B,GAAI,CAAC,EAAM,IAAI,CAAS,EAAG,OAAO,CACpC,CACA,MAAU,MAAM,+CAA+C,EAAK,EAAE,CACxE,CACF,OAdC,EAAW,EAEG,EAAA,EAAA,EAAO,EAAM,cAAc,CAAA,sCAkB1C,SAAS,EAAW,EAAyB,CAG3C,OAFc,EAAQ,MAAM,KAAK,CAAC,CAAC,OAAQ,GAAS,EAAK,OAAS,GAAK,CAAC,EAAK,WAAW,GAAG,GAAK,CAAC,EAAK,SAAS,GAAG,CAC3F,CAAC,CAAC,OAAQ,GAAS,CAAC,EAAsB,IAAI,CAAI,CACzD,CAAC,CAAC,MAAM,EAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CACxC,CAEA,SAAS,EAAQ,EAAuB,CACtC,IAAM,EAAO,EACV,YAAY,CAAC,CACb,QAAQ,cAAe,GAAG,CAAC,CAC3B,QAAQ,WAAY,EAAE,CAAC,CACvB,MAAM,EAAG,EAAe,CAAC,CACzB,QAAQ,MAAO,EAAE,EACpB,OAAO,EAAK,OAAS,EAAI,EAAO,QAClC"}
@@ -1,2 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../config.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs"),r=require("../../session.cjs");let i=require("inversify");require("reflect-metadata/lite");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);let s=require("node:os");s=e.__toESM(s,1);let c=require("node:child_process");const l=`doom-runner`,u=`runs`,d=`.json`,f=`utf8`,p=`ENOENT`;function m(e,t){try{let n=(0,c.execFileSync)(`git`,e,{cwd:t,encoding:f,stdio:[`ignore`,`pipe`,`ignore`]}).trim();return n.length>0?n:void 0}catch{return}}function h(e){try{return a.default.realpathSync(e)}catch{return e}}function g(e){let t=e.PI_CODING_AGENT_DIR?.trim();return t===`~`?s.default.homedir():t?.startsWith(`~/`)?o.default.join(s.default.homedir(),t.slice(2)):t?o.default.resolve(t):o.default.join(s.default.homedir(),`.pi`,`agent`)}function _(e){let t=a.default.statSync(e).mtimeMs;for(let n of a.default.readdirSync(e,{withFileTypes:!0})){let r=o.default.join(e,n.name);if(t=Math.max(t,a.default.statSync(r).mtimeMs),n.isDirectory())for(let e of a.default.readdirSync(r))t=Math.max(t,a.default.statSync(o.default.join(r,e)).mtimeMs)}return t}function v(e){let t;try{t=a.default.readdirSync(e)}catch{return!1}for(let n of t)if(n.endsWith(d))try{let t=JSON.parse(a.default.readFileSync(o.default.join(e,n),f)).hostPid;if(typeof t!=`number`)continue;return process.kill(t,0),!0}catch{}return!1}function y(e){if(typeof e!=`object`||!e)return;let t=e;if(t.state!==`completed`||typeof t.exit?.finishedAt!=`string`)return;let n=Date.parse(t.exit.finishedAt);return Number.isFinite(n)?n:void 0}let b=class{adoptedSessionId;repository;repositoryPath(){return this.repository??=h(m([`rev-parse`,`--show-toplevel`],process.cwd())??process.cwd()),this.repository}setSessionId(e){let t=e.trim();if(!t)throw Error(`doom-runner session id cannot be blank`);this.adoptedSessionId=t}logDirectory(e){let n=process.env[t.LOG_DIR_ENV]?.trim();return n?o.default.resolve(n):o.default.join(this.sessionDirectory(e),`logs`)}stateDirectory(e){let n=process.env[t.LOG_DIR_ENV]?.trim();return n?o.default.join(o.default.resolve(n),`..`,u):o.default.join(this.sessionDirectory(e),u)}logPathFor(e,t){return o.default.join(this.logDirectory(t),`${e}.log`)}rotatedLogPathFor(e,t){return`${this.logPathFor(e,t)}.1`}statePathFor(e,t){return o.default.join(this.stateDirectory(t),`${e}${d}`)}ensureDirectories(e){a.default.mkdirSync(this.logDirectory(e),{recursive:!0}),a.default.mkdirSync(this.stateDirectory(e),{recursive:!0})}sweepHistory(e,t=Date.now()){let n={removed:[],errors:[]};if(process.env.DOOM_RUNNER_LOG_DIR?.trim())return this.sweepStateDirectory(this.stateDirectory(),this.sessionId(),e,t,n),n;let r=this.storeDirectory(),i;try{i=a.default.readdirSync(r)}catch(e){return e.code===p||n.errors.push(`Could not read runner history directory ${r}: ${String(e)}`),n}let s=this.currentSessionId();for(let a of i)a!==s&&this.sweepSessionDirectory(o.default.join(r,a),e,t,n);return n}legacyDirectory(){let e=m([`rev-parse`,`--path-format=absolute`,`--git-common-dir`],process.cwd());return e?o.default.join(e,l):void 0}removeLegacyStore(){let e=this.legacyDirectory();if(!(!e||o.default.basename(e)!==l||!a.default.existsSync(e)))return a.default.rmSync(e,{recursive:!0,force:!0}),e}currentSessionId(){try{return this.sessionId()}catch{return}}sweepSessionDirectory(e,t,n,r){try{if(n-_(e)<=t||v(o.default.join(e,u)))return;a.default.rmSync(e,{recursive:!0,force:!0}),r.removed.push(e)}catch(t){t.code!==p&&r.errors.push(`Could not sweep runner session ${e}: ${String(t)}`)}}sessionId(e){let t=e?.trim()||this.adoptedSessionId||r.inheritedSessionId();if(!t)throw Error(`PI_SESSION_ID is required for doom-runner storage`);return t}storeDirectory(){let e=process.env[t.LOG_DIR_ENV]?.trim();return e?o.default.dirname(o.default.resolve(e)):o.default.join(g(process.env),l)}sessionDirectory(e){return o.default.join(this.storeDirectory(),this.sessionId(e))}sweepStateDirectory(e,t,n,r,i){let s;try{s=a.default.readdirSync(e)}catch(t){t.code!==p&&i.errors.push(`Could not read runner state directory ${e}: ${String(t)}`);return}for(let c of s){if(!c.endsWith(d))continue;let s=o.default.join(e,c);try{let e=y(JSON.parse(a.default.readFileSync(s,f)));if(e===void 0||r-e<=n)continue;let o=c.slice(0,-5);this.removeHistoryFiles(t,o,s,i)}catch(e){i.errors.push(`Could not sweep runner metadata ${s}: ${String(e)}`)}}}removeHistoryFiles(e,t,n,r){let i=[n,this.logPathFor(t,e),this.rotatedLogPathFor(t,e)];for(let e of i)try{a.default.unlinkSync(e),r.removed.push(e)}catch(t){t.code!==p&&r.errors.push(`Could not remove runner history ${e}: ${String(t)}`)}}};b=n.default([(0,i.injectable)()],b),Object.defineProperty(exports,"RunnerPaths",{enumerable:!0,get:function(){return b}});
2
- //# sourceMappingURL=RunnerPaths.cjs.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../adapters/RunnerPaths.cjs");Object.defineProperty(exports,"RunnerPaths",{enumerable:!0,get:function(){return e.RunnerPaths}});
@@ -1,31 +1,2 @@
1
- import { IRunnerPaths, LogSweepResult } from "./types.cjs";
2
- import "reflect-metadata/lite";
3
- //#region src/services/RunnerPaths/RunnerPaths.d.ts
4
- /** Session-scoped runner storage under the Pi agent directory. */
5
- declare class RunnerPaths implements IRunnerPaths {
6
- private adoptedSessionId;
7
- private repository;
8
- repositoryPath(): string;
9
- setSessionId(sessionId: string): void;
10
- logDirectory(sessionId?: string): string;
11
- stateDirectory(sessionId?: string): string;
12
- logPathFor(id: string, sessionId?: string): string;
13
- rotatedLogPathFor(id: string, sessionId?: string): string;
14
- statePathFor(id: string, sessionId?: string): string;
15
- ensureDirectories(sessionId?: string): void;
16
- sweepHistory(ttlMs: number, now?: number): LogSweepResult;
17
- legacyDirectory(): string | undefined;
18
- removeLegacyStore(): string | undefined;
19
- /** The live session, if one is known, so a sweep never deletes underneath itself. */
20
- private currentSessionId;
21
- /** Removes a session's whole directory once its newest entry is past the TTL. */
22
- private sweepSessionDirectory;
23
- private sessionId;
24
- private storeDirectory;
25
- private sessionDirectory;
26
- private sweepStateDirectory;
27
- private removeHistoryFiles;
28
- }
29
- //#endregion
30
- export { RunnerPaths };
31
- //# sourceMappingURL=RunnerPaths.d.cts.map
1
+ import { RunnerPaths } from "../../adapters/RunnerPaths.cjs";
2
+ export { RunnerPaths };
@@ -1,31 +1,2 @@
1
- import { IRunnerPaths, LogSweepResult } from "./types.mjs";
2
- import "reflect-metadata/lite";
3
- //#region src/services/RunnerPaths/RunnerPaths.d.ts
4
- /** Session-scoped runner storage under the Pi agent directory. */
5
- declare class RunnerPaths implements IRunnerPaths {
6
- private adoptedSessionId;
7
- private repository;
8
- repositoryPath(): string;
9
- setSessionId(sessionId: string): void;
10
- logDirectory(sessionId?: string): string;
11
- stateDirectory(sessionId?: string): string;
12
- logPathFor(id: string, sessionId?: string): string;
13
- rotatedLogPathFor(id: string, sessionId?: string): string;
14
- statePathFor(id: string, sessionId?: string): string;
15
- ensureDirectories(sessionId?: string): void;
16
- sweepHistory(ttlMs: number, now?: number): LogSweepResult;
17
- legacyDirectory(): string | undefined;
18
- removeLegacyStore(): string | undefined;
19
- /** The live session, if one is known, so a sweep never deletes underneath itself. */
20
- private currentSessionId;
21
- /** Removes a session's whole directory once its newest entry is past the TTL. */
22
- private sweepSessionDirectory;
23
- private sessionId;
24
- private storeDirectory;
25
- private sessionDirectory;
26
- private sweepStateDirectory;
27
- private removeHistoryFiles;
28
- }
29
- //#endregion
30
- export { RunnerPaths };
31
- //# sourceMappingURL=RunnerPaths.d.mts.map
1
+ import { RunnerPaths } from "../../adapters/RunnerPaths.mjs";
2
+ export { RunnerPaths };
@@ -1,2 +1 @@
1
- import{LOG_DIR_ENV as e}from"../../config.mjs";import t from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{inheritedSessionId as n}from"../../session.mjs";import{injectable as r}from"inversify";import"reflect-metadata/lite";import i from"node:fs";import a from"node:path";import o from"node:os";import{execFileSync as s}from"node:child_process";const c=`doom-runner`,l=`runs`,u=`.json`,d=`utf8`,f=`ENOENT`;function p(e,t){try{let n=s(`git`,e,{cwd:t,encoding:d,stdio:[`ignore`,`pipe`,`ignore`]}).trim();return n.length>0?n:void 0}catch{return}}function m(e){try{return i.realpathSync(e)}catch{return e}}function h(e){let t=e.PI_CODING_AGENT_DIR?.trim();return t===`~`?o.homedir():t?.startsWith(`~/`)?a.join(o.homedir(),t.slice(2)):t?a.resolve(t):a.join(o.homedir(),`.pi`,`agent`)}function g(e){let t=i.statSync(e).mtimeMs;for(let n of i.readdirSync(e,{withFileTypes:!0})){let r=a.join(e,n.name);if(t=Math.max(t,i.statSync(r).mtimeMs),n.isDirectory())for(let e of i.readdirSync(r))t=Math.max(t,i.statSync(a.join(r,e)).mtimeMs)}return t}function _(e){let t;try{t=i.readdirSync(e)}catch{return!1}for(let n of t)if(n.endsWith(u))try{let t=JSON.parse(i.readFileSync(a.join(e,n),d)).hostPid;if(typeof t!=`number`)continue;return process.kill(t,0),!0}catch{}return!1}function v(e){if(typeof e!=`object`||!e)return;let t=e;if(t.state!==`completed`||typeof t.exit?.finishedAt!=`string`)return;let n=Date.parse(t.exit.finishedAt);return Number.isFinite(n)?n:void 0}let y=class{adoptedSessionId;repository;repositoryPath(){return this.repository??=m(p([`rev-parse`,`--show-toplevel`],process.cwd())??process.cwd()),this.repository}setSessionId(e){let t=e.trim();if(!t)throw Error(`doom-runner session id cannot be blank`);this.adoptedSessionId=t}logDirectory(t){let n=process.env[e]?.trim();return n?a.resolve(n):a.join(this.sessionDirectory(t),`logs`)}stateDirectory(t){let n=process.env[e]?.trim();return n?a.join(a.resolve(n),`..`,l):a.join(this.sessionDirectory(t),l)}logPathFor(e,t){return a.join(this.logDirectory(t),`${e}.log`)}rotatedLogPathFor(e,t){return`${this.logPathFor(e,t)}.1`}statePathFor(e,t){return a.join(this.stateDirectory(t),`${e}${u}`)}ensureDirectories(e){i.mkdirSync(this.logDirectory(e),{recursive:!0}),i.mkdirSync(this.stateDirectory(e),{recursive:!0})}sweepHistory(e,t=Date.now()){let n={removed:[],errors:[]};if(process.env.DOOM_RUNNER_LOG_DIR?.trim())return this.sweepStateDirectory(this.stateDirectory(),this.sessionId(),e,t,n),n;let r=this.storeDirectory(),o;try{o=i.readdirSync(r)}catch(e){return e.code===f||n.errors.push(`Could not read runner history directory ${r}: ${String(e)}`),n}let s=this.currentSessionId();for(let i of o)i!==s&&this.sweepSessionDirectory(a.join(r,i),e,t,n);return n}legacyDirectory(){let e=p([`rev-parse`,`--path-format=absolute`,`--git-common-dir`],process.cwd());return e?a.join(e,c):void 0}removeLegacyStore(){let e=this.legacyDirectory();if(!(!e||a.basename(e)!==c||!i.existsSync(e)))return i.rmSync(e,{recursive:!0,force:!0}),e}currentSessionId(){try{return this.sessionId()}catch{return}}sweepSessionDirectory(e,t,n,r){try{if(n-g(e)<=t||_(a.join(e,l)))return;i.rmSync(e,{recursive:!0,force:!0}),r.removed.push(e)}catch(t){t.code!==f&&r.errors.push(`Could not sweep runner session ${e}: ${String(t)}`)}}sessionId(e){let t=e?.trim()||this.adoptedSessionId||n();if(!t)throw Error(`PI_SESSION_ID is required for doom-runner storage`);return t}storeDirectory(){let t=process.env[e]?.trim();return t?a.dirname(a.resolve(t)):a.join(h(process.env),c)}sessionDirectory(e){return a.join(this.storeDirectory(),this.sessionId(e))}sweepStateDirectory(e,t,n,r,o){let s;try{s=i.readdirSync(e)}catch(t){t.code!==f&&o.errors.push(`Could not read runner state directory ${e}: ${String(t)}`);return}for(let c of s){if(!c.endsWith(u))continue;let s=a.join(e,c);try{let e=v(JSON.parse(i.readFileSync(s,d)));if(e===void 0||r-e<=n)continue;let a=c.slice(0,-5);this.removeHistoryFiles(t,a,s,o)}catch(e){o.errors.push(`Could not sweep runner metadata ${s}: ${String(e)}`)}}}removeHistoryFiles(e,t,n,r){let a=[n,this.logPathFor(t,e),this.rotatedLogPathFor(t,e)];for(let e of a)try{i.unlinkSync(e),r.removed.push(e)}catch(t){t.code!==f&&r.errors.push(`Could not remove runner history ${e}: ${String(t)}`)}}};y=t([r()],y);export{y as RunnerPaths};
2
- //# sourceMappingURL=RunnerPaths.mjs.map
1
+ import{RunnerPaths as e}from"../../adapters/RunnerPaths.mjs";export{e as RunnerPaths};
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./RunnerPaths.cjs");Object.defineProperty(exports,"RunnerPaths",{enumerable:!0,get:function(){return e.RunnerPaths}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../adapters/RunnerPaths.cjs");require("./RunnerPaths.cjs"),Object.defineProperty(exports,"RunnerPaths",{enumerable:!0,get:function(){return e.RunnerPaths}});
@@ -1,3 +1,4 @@
1
1
  import { IRunnerPaths, LogSweepResult } from "./types.cjs";
2
- import { RunnerPaths } from "./RunnerPaths.cjs";
2
+ import { RunnerPaths } from "../../adapters/RunnerPaths.cjs";
3
+ import "./RunnerPaths.cjs";
3
4
  export { type IRunnerPaths, type LogSweepResult, RunnerPaths };
@@ -1,3 +1,4 @@
1
1
  import { IRunnerPaths, LogSweepResult } from "./types.mjs";
2
- import { RunnerPaths } from "./RunnerPaths.mjs";
2
+ import { RunnerPaths } from "../../adapters/RunnerPaths.mjs";
3
+ import "./RunnerPaths.mjs";
3
4
  export { type IRunnerPaths, type LogSweepResult, RunnerPaths };
@@ -1 +1 @@
1
- import{RunnerPaths as e}from"./RunnerPaths.mjs";export{e as RunnerPaths};
1
+ import{RunnerPaths as e}from"../../adapters/RunnerPaths.mjs";import"./RunnerPaths.mjs";export{e as RunnerPaths};