@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
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-runner",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.21",
4
4
  "description": "Pi bash tool replacement with background process supervision",
5
+ "keywords": [
6
+ "ai",
7
+ "coding-agent",
8
+ "developer-tools",
9
+ "doompi",
10
+ "pi-package"
11
+ ],
5
12
  "homepage": "https://agimon.ai",
6
13
  "license": "MIT",
7
14
  "author": "Vuong Ngo",
@@ -285,14 +292,14 @@
285
292
  "node-pty": "1.1.0",
286
293
  "reflect-metadata": "0.2.2",
287
294
  "typebox": "1.1.38",
288
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.2",
289
- "@agimon-ai/doompi-ui": "0.0.1-alpha.2",
290
- "@agimon-ai/foundation-process-registry": "0.28.11",
291
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.2"
295
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.21",
296
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.21",
297
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.21",
298
+ "@agimon-ai/foundation-process-registry": "0.29.11"
292
299
  },
293
300
  "devDependencies": {
294
- "@earendil-works/pi-coding-agent": "0.84.1",
295
- "@earendil-works/pi-tui": "0.84.1",
301
+ "@earendil-works/pi-coding-agent": "0.84.2",
302
+ "@earendil-works/pi-tui": "0.84.2",
296
303
  "@types/node": "26.1.1",
297
304
  "@vitest/coverage-v8": "4.1.10",
298
305
  "tsdown": "0.22.14",
@@ -300,8 +307,8 @@
300
307
  "vitest": "4.1.10"
301
308
  },
302
309
  "peerDependencies": {
303
- "@earendil-works/pi-coding-agent": "0.84.1",
304
- "@earendil-works/pi-tui": "0.84.1"
310
+ "@earendil-works/pi-coding-agent": "0.84.2",
311
+ "@earendil-works/pi-tui": "0.84.2"
305
312
  },
306
313
  "peerDependenciesMeta": {
307
314
  "@earendil-works/pi-coding-agent": {
@@ -312,10 +319,10 @@
312
319
  }
313
320
  },
314
321
  "optionalDependencies": {
315
- "@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.2",
316
- "@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.2",
317
- "@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.2",
318
- "@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.2"
322
+ "@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.21",
323
+ "@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.21",
324
+ "@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.21",
325
+ "@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.21"
319
326
  },
320
327
  "engines": {
321
328
  "node": ">=22.19.0"
@@ -1 +0,0 @@
1
- {"version":3,"file":"cliApp.cjs","names":["inheritedSessionId","requiredSessionId","formatRunnerLine","fs"],"sources":["../../src/cli/cliApp.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { ILauncher } from '../services/Launcher/index.ts';\nimport type { ILogReader } from '../services/LogReader/index.ts';\nimport type { IRmuxBackend } from '../services/RmuxBackend/index.ts';\nimport type { IRunnerRegistry, RunnerRecord } from '../services/RunnerRegistry/index.ts';\nimport { inheritedSessionId, requiredSessionId } from '../session.ts';\nimport { formatRunnerLine } from '../tool/responseEnvelope.ts';\n\nconst DEFAULT_LOG_LINES = 200;\nconst FOLLOW_INTERVAL_MS = 250;\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\nconst STOPPED_REASON = 'stopped';\nconst TERMINATION_SIGNAL = 'SIGTERM';\n\nexport interface CliDependencies {\n registry: IRunnerRegistry;\n launcher: ILauncher;\n rmuxBackend: IRmuxBackend;\n logReader: ILogReader;\n env?: NodeJS.ProcessEnv;\n stdout(text: string): void;\n stderr(text: string): void;\n readStdin(): Promise<string>;\n}\n\nexport async function runCli(argv: readonly string[], dependencies: CliDependencies): Promise<number> {\n const [command, ...args] = argv;\n if (command !== undefined && command !== '--help' && command !== '-h' && !inheritedSessionId(dependencies.env)) {\n dependencies.stderr('PI_SESSION_ID is required for doom-runner commands');\n return 1;\n }\n switch (command) {\n case 'list':\n return list(args, dependencies);\n case 'status':\n return status(args, dependencies);\n case 'logs':\n return logs(args, dependencies);\n case 'stop':\n return stop(args, dependencies);\n case 'stop-all':\n return stopAll(args, dependencies);\n case 'input':\n return input(args, dependencies);\n case '--help':\n case '-h':\n case undefined:\n dependencies.stdout(help());\n return 0;\n default:\n dependencies.stderr(`Unknown command: ${command}\\n\\n${help()}`);\n return 1;\n }\n}\n\nasync function list(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const sessionId = requiredSessionId(dependencies.env);\n const records = args.includes('--all')\n ? await dependencies.registry.listAll(sessionId)\n : await dependencies.registry.listBySession(sessionId);\n dependencies.stdout(records.length > 0 ? records.map(formatRecord).join('\\n') : 'No runners');\n return 0;\n}\n\nasync function status(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n dependencies.stdout(\n [\n `ID: ${record.id}`,\n `Name: ${record.name}`,\n `State: ${record.state}`,\n `Backend: ${record.backend}`,\n ...(record.backendTarget ? [`Backend target: ${record.backendTarget}`] : []),\n `PID: ${record.pid}`,\n `Command: ${record.command}`,\n `CWD: ${record.cwd}`,\n `Session: ${record.sessionId}`,\n `Log: ${record.logPath}`,\n ...(record.exit ? [`Exit: ${formatExit(record)}`] : []),\n ].join('\\n'),\n );\n return 0;\n}\n\nasync function logs(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n const lines = positiveInteger(option(args, '--lines')) ?? DEFAULT_LOG_LINES;\n const result = dependencies.logReader.read(record.logPath, { lines });\n if (!result.exists) {\n dependencies.stderr(`Runner ${record.id} has not written a log yet`);\n return 1;\n }\n if (result.text) dependencies.stdout(result.text);\n if (!args.includes('--follow')) return 0;\n return follow(record, result.fileSize, dependencies);\n}\n\nasync function stop(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n if (record.state !== 'running') {\n dependencies.stderr(`Runner ${record.id} is already ${record.state}`);\n return 1;\n }\n await stopRecord(record, dependencies);\n const reason = option(args, '--reason');\n await dependencies.registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: TERMINATION_SIGNAL,\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n dependencies.stdout(`Stopped ${record.id}\\nLog: ${record.logPath}`);\n return 0;\n}\n\nasync function stopAll(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const sessionId = requiredSessionId(dependencies.env);\n const records = await dependencies.registry.listBySession(sessionId);\n const reason = option(args, '--reason');\n for (const record of records) {\n await stopRecord(record, dependencies);\n await dependencies.registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: TERMINATION_SIGNAL,\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n }\n dependencies.stdout(\n records.length > 0\n ? `Stopped ${records.length} runner(s) for session ${sessionId}: ${records.map((record) => record.id).join(', ')}`\n : `No active runners for session ${sessionId}`,\n );\n return 0;\n}\n\nasync function input(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n if (record.state === COMPLETED_STATE) {\n dependencies.stderr(`Runner ${record.id} is already completed; use doom-runner logs ${record.id}`);\n return 1;\n }\n if (!record.interactive) {\n dependencies.stderr(`Runner ${record.id} was not started with interactive: true`);\n return 1;\n }\n const supplied = option(args, '--text');\n const text = supplied ?? (await dependencies.readStdin());\n const payload = args.includes('--enter') && !text.endsWith('\\n') ? `${text}\\n` : text;\n if (record.backend !== RMUX_BACKEND || !record.backendTarget) {\n dependencies.stderr(`Runner ${record.id} uses the ${record.backend} backend, which does not expose CLI input`);\n return 1;\n }\n if (!(await dependencies.rmuxBackend.input(record.backendTarget, payload))) {\n dependencies.stderr(`Runner ${record.id} is no longer accepting RMUX input`);\n return 1;\n }\n dependencies.stdout(`Sent input to ${record.id}`);\n return 0;\n}\n\nasync function requiredRecord(\n id: string | undefined,\n dependencies: CliDependencies,\n): Promise<RunnerRecord | undefined> {\n if (!id) {\n dependencies.stderr('A runner ID is required');\n return undefined;\n }\n const sessionId = requiredSessionId(dependencies.env);\n const scopedRecord = await dependencies.registry.get(id, sessionId);\n if (!scopedRecord) {\n dependencies.stderr(`No runner with id ${id}`);\n return undefined;\n }\n return scopedRecord;\n}\n\nfunction formatRecord(record: RunnerRecord): string {\n const active = formatRunnerLine(record);\n return `${record.id} ${record.state} ${record.backend} ${active}`;\n}\n\nfunction formatExit(record: RunnerRecord): string {\n const exit = record.exit;\n if (!exit) return 'unknown';\n const detail = exit.code !== null ? ` code ${exit.code}` : exit.signal ? ` ${exit.signal}` : '';\n const note = exit.stopReason ? ` (${exit.stopReason})` : '';\n return `${exit.reason}${detail}${note}`;\n}\n\nfunction option(args: readonly string[], name: string): string | undefined {\n const index = args.indexOf(name);\n return index >= 0 ? args[index + 1] : undefined;\n}\n\nfunction positiveInteger(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nasync function follow(record: RunnerRecord, initialSize: number, dependencies: CliDependencies): Promise<number> {\n let offset = initialSize;\n return new Promise<number>((resolve) => {\n let stopped = false;\n let stateTimer: NodeJS.Timeout;\n const listener = (current: fs.Stats): void => {\n if (current.size < offset) offset = 0;\n if (current.size === offset) return;\n const length = current.size - offset;\n const buffer = Buffer.alloc(length);\n const descriptor = fs.openSync(record.logPath, 'r');\n try {\n const bytesRead = fs.readSync(descriptor, buffer, 0, length, offset);\n if (bytesRead > 0) dependencies.stdout(buffer.subarray(0, bytesRead).toString('utf8'));\n } finally {\n fs.closeSync(descriptor);\n }\n offset = current.size;\n };\n const stopFollowing = (): void => {\n if (stopped) return;\n stopped = true;\n try {\n listener(fs.statSync(record.logPath));\n } catch (error) {\n // A missing log file was already reported by the initial read.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n dependencies.stderr(`Runner ${record.id} failed to flush its final log output: ${(error as Error).message}`);\n }\n }\n fs.unwatchFile(record.logPath, listener);\n clearInterval(stateTimer);\n process.off('SIGINT', stopFollowing);\n resolve(0);\n };\n process.once('SIGINT', stopFollowing);\n fs.watchFile(record.logPath, { interval: FOLLOW_INTERVAL_MS }, listener);\n stateTimer = setInterval(() => {\n void dependencies.registry.get(record.id).then((current) => {\n if (current?.state === COMPLETED_STATE) stopFollowing();\n });\n }, FOLLOW_INTERVAL_MS);\n });\n}\n\nasync function stopRecord(record: RunnerRecord, dependencies: CliDependencies): Promise<boolean> {\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n return dependencies.rmuxBackend.stop(record.backendTarget, record.pid);\n }\n return dependencies.launcher.stop(record.pid);\n}\n\nfunction help(): string {\n return [\n 'Usage: doom-runner <command>',\n '',\n 'Commands:',\n ' list [--all]',\n ' status <id>',\n ' logs <id> [--lines N] [--follow]',\n ' stop <id> [--reason <text>]',\n ' stop-all [--reason <text>]',\n ' input <id> [--text <text>] [--enter]',\n ].join('\\n');\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cliApp.d.cts","names":[],"sources":["../../src/cli/cliApp.ts"],"mappings":";;;;;;;;;UAeiB;EACf,UAAU;EACV,UAAU;EACV,aAAa;EACb,WAAW;EACX,MAAM,OAAO;EACb,OAAO;EACP,OAAO;EACP,aAAa;;iBAGO,OAAO,yBAAyB,cAAc,kBAAkB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cliApp.d.mts","names":[],"sources":["../../src/cli/cliApp.ts"],"mappings":";;;;;;;;;UAeiB;EACf,UAAU;EACV,UAAU;EACV,aAAa;EACb,WAAW;EACX,MAAM,OAAO;EACb,OAAO;EACP,OAAO;EACP,aAAa;;iBAGO,OAAO,yBAAyB,cAAc,kBAAkB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cliApp.mjs","names":[],"sources":["../../src/cli/cliApp.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { ILauncher } from '../services/Launcher/index.ts';\nimport type { ILogReader } from '../services/LogReader/index.ts';\nimport type { IRmuxBackend } from '../services/RmuxBackend/index.ts';\nimport type { IRunnerRegistry, RunnerRecord } from '../services/RunnerRegistry/index.ts';\nimport { inheritedSessionId, requiredSessionId } from '../session.ts';\nimport { formatRunnerLine } from '../tool/responseEnvelope.ts';\n\nconst DEFAULT_LOG_LINES = 200;\nconst FOLLOW_INTERVAL_MS = 250;\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\nconst STOPPED_REASON = 'stopped';\nconst TERMINATION_SIGNAL = 'SIGTERM';\n\nexport interface CliDependencies {\n registry: IRunnerRegistry;\n launcher: ILauncher;\n rmuxBackend: IRmuxBackend;\n logReader: ILogReader;\n env?: NodeJS.ProcessEnv;\n stdout(text: string): void;\n stderr(text: string): void;\n readStdin(): Promise<string>;\n}\n\nexport async function runCli(argv: readonly string[], dependencies: CliDependencies): Promise<number> {\n const [command, ...args] = argv;\n if (command !== undefined && command !== '--help' && command !== '-h' && !inheritedSessionId(dependencies.env)) {\n dependencies.stderr('PI_SESSION_ID is required for doom-runner commands');\n return 1;\n }\n switch (command) {\n case 'list':\n return list(args, dependencies);\n case 'status':\n return status(args, dependencies);\n case 'logs':\n return logs(args, dependencies);\n case 'stop':\n return stop(args, dependencies);\n case 'stop-all':\n return stopAll(args, dependencies);\n case 'input':\n return input(args, dependencies);\n case '--help':\n case '-h':\n case undefined:\n dependencies.stdout(help());\n return 0;\n default:\n dependencies.stderr(`Unknown command: ${command}\\n\\n${help()}`);\n return 1;\n }\n}\n\nasync function list(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const sessionId = requiredSessionId(dependencies.env);\n const records = args.includes('--all')\n ? await dependencies.registry.listAll(sessionId)\n : await dependencies.registry.listBySession(sessionId);\n dependencies.stdout(records.length > 0 ? records.map(formatRecord).join('\\n') : 'No runners');\n return 0;\n}\n\nasync function status(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n dependencies.stdout(\n [\n `ID: ${record.id}`,\n `Name: ${record.name}`,\n `State: ${record.state}`,\n `Backend: ${record.backend}`,\n ...(record.backendTarget ? [`Backend target: ${record.backendTarget}`] : []),\n `PID: ${record.pid}`,\n `Command: ${record.command}`,\n `CWD: ${record.cwd}`,\n `Session: ${record.sessionId}`,\n `Log: ${record.logPath}`,\n ...(record.exit ? [`Exit: ${formatExit(record)}`] : []),\n ].join('\\n'),\n );\n return 0;\n}\n\nasync function logs(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n const lines = positiveInteger(option(args, '--lines')) ?? DEFAULT_LOG_LINES;\n const result = dependencies.logReader.read(record.logPath, { lines });\n if (!result.exists) {\n dependencies.stderr(`Runner ${record.id} has not written a log yet`);\n return 1;\n }\n if (result.text) dependencies.stdout(result.text);\n if (!args.includes('--follow')) return 0;\n return follow(record, result.fileSize, dependencies);\n}\n\nasync function stop(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n if (record.state !== 'running') {\n dependencies.stderr(`Runner ${record.id} is already ${record.state}`);\n return 1;\n }\n await stopRecord(record, dependencies);\n const reason = option(args, '--reason');\n await dependencies.registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: TERMINATION_SIGNAL,\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n dependencies.stdout(`Stopped ${record.id}\\nLog: ${record.logPath}`);\n return 0;\n}\n\nasync function stopAll(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const sessionId = requiredSessionId(dependencies.env);\n const records = await dependencies.registry.listBySession(sessionId);\n const reason = option(args, '--reason');\n for (const record of records) {\n await stopRecord(record, dependencies);\n await dependencies.registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: TERMINATION_SIGNAL,\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n }\n dependencies.stdout(\n records.length > 0\n ? `Stopped ${records.length} runner(s) for session ${sessionId}: ${records.map((record) => record.id).join(', ')}`\n : `No active runners for session ${sessionId}`,\n );\n return 0;\n}\n\nasync function input(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n if (record.state === COMPLETED_STATE) {\n dependencies.stderr(`Runner ${record.id} is already completed; use doom-runner logs ${record.id}`);\n return 1;\n }\n if (!record.interactive) {\n dependencies.stderr(`Runner ${record.id} was not started with interactive: true`);\n return 1;\n }\n const supplied = option(args, '--text');\n const text = supplied ?? (await dependencies.readStdin());\n const payload = args.includes('--enter') && !text.endsWith('\\n') ? `${text}\\n` : text;\n if (record.backend !== RMUX_BACKEND || !record.backendTarget) {\n dependencies.stderr(`Runner ${record.id} uses the ${record.backend} backend, which does not expose CLI input`);\n return 1;\n }\n if (!(await dependencies.rmuxBackend.input(record.backendTarget, payload))) {\n dependencies.stderr(`Runner ${record.id} is no longer accepting RMUX input`);\n return 1;\n }\n dependencies.stdout(`Sent input to ${record.id}`);\n return 0;\n}\n\nasync function requiredRecord(\n id: string | undefined,\n dependencies: CliDependencies,\n): Promise<RunnerRecord | undefined> {\n if (!id) {\n dependencies.stderr('A runner ID is required');\n return undefined;\n }\n const sessionId = requiredSessionId(dependencies.env);\n const scopedRecord = await dependencies.registry.get(id, sessionId);\n if (!scopedRecord) {\n dependencies.stderr(`No runner with id ${id}`);\n return undefined;\n }\n return scopedRecord;\n}\n\nfunction formatRecord(record: RunnerRecord): string {\n const active = formatRunnerLine(record);\n return `${record.id} ${record.state} ${record.backend} ${active}`;\n}\n\nfunction formatExit(record: RunnerRecord): string {\n const exit = record.exit;\n if (!exit) return 'unknown';\n const detail = exit.code !== null ? ` code ${exit.code}` : exit.signal ? ` ${exit.signal}` : '';\n const note = exit.stopReason ? ` (${exit.stopReason})` : '';\n return `${exit.reason}${detail}${note}`;\n}\n\nfunction option(args: readonly string[], name: string): string | undefined {\n const index = args.indexOf(name);\n return index >= 0 ? args[index + 1] : undefined;\n}\n\nfunction positiveInteger(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nasync function follow(record: RunnerRecord, initialSize: number, dependencies: CliDependencies): Promise<number> {\n let offset = initialSize;\n return new Promise<number>((resolve) => {\n let stopped = false;\n let stateTimer: NodeJS.Timeout;\n const listener = (current: fs.Stats): void => {\n if (current.size < offset) offset = 0;\n if (current.size === offset) return;\n const length = current.size - offset;\n const buffer = Buffer.alloc(length);\n const descriptor = fs.openSync(record.logPath, 'r');\n try {\n const bytesRead = fs.readSync(descriptor, buffer, 0, length, offset);\n if (bytesRead > 0) dependencies.stdout(buffer.subarray(0, bytesRead).toString('utf8'));\n } finally {\n fs.closeSync(descriptor);\n }\n offset = current.size;\n };\n const stopFollowing = (): void => {\n if (stopped) return;\n stopped = true;\n try {\n listener(fs.statSync(record.logPath));\n } catch (error) {\n // A missing log file was already reported by the initial read.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n dependencies.stderr(`Runner ${record.id} failed to flush its final log output: ${(error as Error).message}`);\n }\n }\n fs.unwatchFile(record.logPath, listener);\n clearInterval(stateTimer);\n process.off('SIGINT', stopFollowing);\n resolve(0);\n };\n process.once('SIGINT', stopFollowing);\n fs.watchFile(record.logPath, { interval: FOLLOW_INTERVAL_MS }, listener);\n stateTimer = setInterval(() => {\n void dependencies.registry.get(record.id).then((current) => {\n if (current?.state === COMPLETED_STATE) stopFollowing();\n });\n }, FOLLOW_INTERVAL_MS);\n });\n}\n\nasync function stopRecord(record: RunnerRecord, dependencies: CliDependencies): Promise<boolean> {\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n return dependencies.rmuxBackend.stop(record.backendTarget, record.pid);\n }\n return dependencies.launcher.stop(record.pid);\n}\n\nfunction help(): string {\n return [\n 'Usage: doom-runner <command>',\n '',\n 'Commands:',\n ' list [--all]',\n ' status <id>',\n ' logs <id> [--lines N] [--follow]',\n ' stop <id> [--reason <text>]',\n ' stop-all [--reason <text>]',\n ' input <id> [--text <text>] [--enter]',\n ].join('\\n');\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compaction.cjs","names":[],"sources":["../src/compaction.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { RunnerRecord } from './services/RunnerRegistry/index.ts';\n\nconst COMPACTION_RUNNERS_MARKER = 'Active background runners survived compaction:';\nconst RUNNER_COMPACTION_MESSAGE = 'doom-runner-compaction';\n\nexport interface RunnerCompactionDependencies {\n getSessionId(): string | undefined;\n listBySession(sessionId: string): Promise<RunnerRecord[]>;\n}\n\nexport function activeRunnerRecovery(active: readonly RunnerRecord[]): string {\n if (active.length === 0) return '';\n const runners = active.flatMap((record) => [\n `- ${record.name} (${record.id}), log: ${record.logPath}`,\n ` Check: doom-runner status ${record.id}`,\n ` Stream: doom-runner logs ${record.id} --follow`,\n ]);\n return [\n COMPACTION_RUNNERS_MARKER,\n ...runners,\n 'Use these CLI commands to inspect existing runners before launching another command.',\n ].join('\\n');\n}\n\nexport function appendActiveRunnersToSummary(summary: string, active: readonly RunnerRecord[]): string {\n if (active.length === 0 || summary.includes(COMPACTION_RUNNERS_MARKER)) return summary;\n return [summary, '', activeRunnerRecovery(active)].join('\\n');\n}\n\nexport function registerRunnerCompactionRecovery(pi: ExtensionAPI, dependencies: RunnerCompactionDependencies): void {\n pi.on('session_compact', async () => {\n const sessionId = dependencies.getSessionId();\n if (!sessionId) return;\n let active: RunnerRecord[];\n try {\n active = await dependencies.listBySession(sessionId);\n } catch {\n // Recovery is advisory. Omit stale runner hints rather than failing a completed compaction.\n return;\n }\n const content = activeRunnerRecovery(active);\n if (!content) return;\n pi.sendMessage(\n {\n customType: RUNNER_COMPACTION_MESSAGE,\n content,\n display: false,\n details: { sessionId, runnerIds: active.map((record) => record.id) },\n },\n { triggerTurn: false, deliverAs: 'steer' },\n );\n });\n}\n"],"mappings":"mEAGA,MAAM,EAA4B,iDAQlC,SAAgB,EAAqB,EAAyC,CAC5E,GAAI,EAAO,SAAW,EAAG,MAAO,GAChC,IAAM,EAAU,EAAO,QAAS,GAAW,CACzC,KAAK,EAAO,KAAK,IAAI,EAAO,GAAG,UAAU,EAAO,UAChD,+BAA+B,EAAO,KACtC,8BAA8B,EAAO,GAAG,UAC1C,CAAC,EACD,MAAO,CACL,EACA,GAAG,EACH,sFACF,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAA6B,EAAiB,EAAyC,CAErG,OADI,EAAO,SAAW,GAAK,EAAQ,SAAS,CAAyB,EAAU,EACxE,CAAC,EAAS,GAAI,EAAqB,CAAM,CAAC,CAAC,CAAC,KAAK;CAAI,CAC9D,CAEA,SAAgB,EAAiC,EAAkB,EAAkD,CACnH,EAAG,GAAG,kBAAmB,SAAY,CACnC,IAAM,EAAY,EAAa,aAAa,EAC5C,GAAI,CAAC,EAAW,OAChB,IAAI,EACJ,GAAI,CACF,EAAS,MAAM,EAAa,cAAc,CAAS,CACrD,MAAQ,CAEN,MACF,CACA,IAAM,EAAU,EAAqB,CAAM,EACtC,GACL,EAAG,YACD,CACE,WAAY,yBACZ,UACA,QAAS,GACT,QAAS,CAAE,YAAW,UAAW,EAAO,IAAK,GAAW,EAAO,EAAE,CAAE,CACrE,EACA,CAAE,YAAa,GAAO,UAAW,OAAQ,CAC3C,CACF,CAAC,CACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compaction.d.cts","names":[],"sources":["../src/compaction.ts"],"mappings":";;;;UAMiB;EACf;EACA,cAAc,oBAAoB,QAAQ;;iBAG5B,qBAAqB,iBAAiB;iBActC,6BAA6B,iBAAiB,iBAAiB;iBAK/D,iCAAiC,IAAI,cAAc,cAAc"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compaction.d.mts","names":[],"sources":["../src/compaction.ts"],"mappings":";;;;UAMiB;EACf;EACA,cAAc,oBAAoB,QAAQ;;iBAG5B,qBAAqB,iBAAiB;iBActC,6BAA6B,iBAAiB,iBAAiB;iBAK/D,iCAAiC,IAAI,cAAc,cAAc"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compaction.mjs","names":[],"sources":["../src/compaction.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { RunnerRecord } from './services/RunnerRegistry/index.ts';\n\nconst COMPACTION_RUNNERS_MARKER = 'Active background runners survived compaction:';\nconst RUNNER_COMPACTION_MESSAGE = 'doom-runner-compaction';\n\nexport interface RunnerCompactionDependencies {\n getSessionId(): string | undefined;\n listBySession(sessionId: string): Promise<RunnerRecord[]>;\n}\n\nexport function activeRunnerRecovery(active: readonly RunnerRecord[]): string {\n if (active.length === 0) return '';\n const runners = active.flatMap((record) => [\n `- ${record.name} (${record.id}), log: ${record.logPath}`,\n ` Check: doom-runner status ${record.id}`,\n ` Stream: doom-runner logs ${record.id} --follow`,\n ]);\n return [\n COMPACTION_RUNNERS_MARKER,\n ...runners,\n 'Use these CLI commands to inspect existing runners before launching another command.',\n ].join('\\n');\n}\n\nexport function appendActiveRunnersToSummary(summary: string, active: readonly RunnerRecord[]): string {\n if (active.length === 0 || summary.includes(COMPACTION_RUNNERS_MARKER)) return summary;\n return [summary, '', activeRunnerRecovery(active)].join('\\n');\n}\n\nexport function registerRunnerCompactionRecovery(pi: ExtensionAPI, dependencies: RunnerCompactionDependencies): void {\n pi.on('session_compact', async () => {\n const sessionId = dependencies.getSessionId();\n if (!sessionId) return;\n let active: RunnerRecord[];\n try {\n active = await dependencies.listBySession(sessionId);\n } catch {\n // Recovery is advisory. Omit stale runner hints rather than failing a completed compaction.\n return;\n }\n const content = activeRunnerRecovery(active);\n if (!content) return;\n pi.sendMessage(\n {\n customType: RUNNER_COMPACTION_MESSAGE,\n content,\n display: false,\n details: { sessionId, runnerIds: active.map((record) => record.id) },\n },\n { triggerTurn: false, deliverAs: 'steer' },\n );\n });\n}\n"],"mappings":"AAGA,MAAM,EAA4B,iDAQlC,SAAgB,EAAqB,EAAyC,CAC5E,GAAI,EAAO,SAAW,EAAG,MAAO,GAChC,IAAM,EAAU,EAAO,QAAS,GAAW,CACzC,KAAK,EAAO,KAAK,IAAI,EAAO,GAAG,UAAU,EAAO,UAChD,+BAA+B,EAAO,KACtC,8BAA8B,EAAO,GAAG,UAC1C,CAAC,EACD,MAAO,CACL,EACA,GAAG,EACH,sFACF,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAA6B,EAAiB,EAAyC,CAErG,OADI,EAAO,SAAW,GAAK,EAAQ,SAAS,CAAyB,EAAU,EACxE,CAAC,EAAS,GAAI,EAAqB,CAAM,CAAC,CAAC,CAAC,KAAK;CAAI,CAC9D,CAEA,SAAgB,EAAiC,EAAkB,EAAkD,CACnH,EAAG,GAAG,kBAAmB,SAAY,CACnC,IAAM,EAAY,EAAa,aAAa,EAC5C,GAAI,CAAC,EAAW,OAChB,IAAI,EACJ,GAAI,CACF,EAAS,MAAM,EAAa,cAAc,CAAS,CACrD,MAAQ,CAEN,MACF,CACA,IAAM,EAAU,EAAqB,CAAM,EACtC,GACL,EAAG,YACD,CACE,WAAY,yBACZ,UACA,QAAS,GACT,QAAS,CAAE,YAAW,UAAW,EAAO,IAAK,GAAW,EAAO,EAAE,CAAE,CACrE,EACA,CAAE,YAAa,GAAO,UAAW,OAAQ,CAC3C,CACF,CAAC,CACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.cjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["/**\n * Extension configuration, read from the environment.\n *\n * Env rather than a config file, matching doom-task: doom-pi already carries\n * per-launch state to Pi through env vars.\n */\n\nexport const BG_THRESHOLD_MS_ENV = 'DOOM_RUNNER_BG_THRESHOLD_MS';\nexport const RESULT_MAX_BYTES_ENV = 'DOOM_RUNNER_RESULT_MAX_BYTES';\nexport const LOG_MAX_BYTES_ENV = 'DOOM_RUNNER_LOG_MAX_BYTES';\nexport const LOG_TTL_MS_ENV = 'DOOM_RUNNER_LOG_TTL_MS';\n/** Unscoped override for the runner log directory. */\nexport const LOG_DIR_ENV = 'DOOM_RUNNER_LOG_DIR';\n\n/** A bash call still running at this point is promoted to a named runner. */\nexport const DEFAULT_BG_THRESHOLD_MS = 60_000;\n/** Tool results past this size are tail-truncated and spilled to the log file. */\nexport const DEFAULT_RESULT_MAX_BYTES = 51_200;\n/** A runner log rotates once to `.log.1` at this size. */\nexport const DEFAULT_LOG_MAX_BYTES = 5 * 1024 * 1024;\n/** Retention window for logs with no live registry record. */\nexport const DEFAULT_LOG_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\nfunction positiveNumber(raw: string | undefined, fallback: number): number {\n const value = Number(raw);\n return Number.isFinite(value) && value > 0 ? value : fallback;\n}\n\n/** Age at which a bash call becomes a background runner. */\nexport function getBackgroundThresholdMs(env: NodeJS.ProcessEnv = process.env): number {\n return positiveNumber(env[BG_THRESHOLD_MS_ENV], DEFAULT_BG_THRESHOLD_MS);\n}\n\n/** Byte ceiling for a single tool result before tail truncation kicks in. */\nexport function getResultMaxBytes(env: NodeJS.ProcessEnv = process.env): number {\n return positiveNumber(env[RESULT_MAX_BYTES_ENV], DEFAULT_RESULT_MAX_BYTES);\n}\n\n/** Byte ceiling for a runner log before it rotates. */\nexport function getLogMaxBytes(env: NodeJS.ProcessEnv = process.env): number {\n return positiveNumber(env[LOG_MAX_BYTES_ENV], DEFAULT_LOG_MAX_BYTES);\n}\n\n/** Retention window for orphaned runner logs. */\nexport function getLogTtlMs(env: NodeJS.ProcessEnv = process.env): number {\n return positiveNumber(env[LOG_TTL_MS_ENV], DEFAULT_LOG_TTL_MS);\n}\n"],"mappings":"mEAOA,MAAa,EAAsB,8BACtB,EAAuB,+BACvB,EAAoB,4BACpB,EAAiB,yBAKjB,EAA0B,IAE1B,EAA2B,MAE3B,EAAwB,QAExB,EAAqB,OAElC,SAAS,EAAe,EAAyB,EAA0B,CACzE,IAAM,EAAQ,OAAO,CAAG,EACxB,OAAO,OAAO,SAAS,CAAK,GAAK,EAAQ,EAAI,EAAQ,CACvD,CAGA,SAAgB,EAAyB,EAAyB,QAAQ,IAAa,CACrF,OAAO,EAAe,EAAI,GAAsB,CAAuB,CACzE,CAGA,SAAgB,EAAkB,EAAyB,QAAQ,IAAa,CAC9E,OAAO,EAAe,EAAI,GAAuB,CAAwB,CAC3E,CAGA,SAAgB,EAAe,EAAyB,QAAQ,IAAa,CAC3E,OAAO,EAAe,EAAI,GAAoB,CAAqB,CACrE,CAGA,SAAgB,EAAY,EAAyB,QAAQ,IAAa,CACxE,OAAO,EAAe,EAAI,GAAiB,CAAkB,CAC/D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;cAOa;cACA;cACA;cACA;;cAEA;;cAGA;;cAEA;;cAEA;;cAEA;;iBAQG,yBAAyB,MAAK,OAAO;;iBAKrC,kBAAkB,MAAK,OAAO;;iBAK9B,eAAe,MAAK,OAAO;;iBAK3B,YAAY,MAAK,OAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;cAOa;cACA;cACA;cACA;;cAEA;;cAGA;;cAEA;;cAEA;;cAEA;;iBAQG,yBAAyB,MAAK,OAAO;;iBAKrC,kBAAkB,MAAK,OAAO;;iBAK9B,eAAe,MAAK,OAAO;;iBAK3B,YAAY,MAAK,OAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["/**\n * Extension configuration, read from the environment.\n *\n * Env rather than a config file, matching doom-task: doom-pi already carries\n * per-launch state to Pi through env vars.\n */\n\nexport const BG_THRESHOLD_MS_ENV = 'DOOM_RUNNER_BG_THRESHOLD_MS';\nexport const RESULT_MAX_BYTES_ENV = 'DOOM_RUNNER_RESULT_MAX_BYTES';\nexport const LOG_MAX_BYTES_ENV = 'DOOM_RUNNER_LOG_MAX_BYTES';\nexport const LOG_TTL_MS_ENV = 'DOOM_RUNNER_LOG_TTL_MS';\n/** Unscoped override for the runner log directory. */\nexport const LOG_DIR_ENV = 'DOOM_RUNNER_LOG_DIR';\n\n/** A bash call still running at this point is promoted to a named runner. */\nexport const DEFAULT_BG_THRESHOLD_MS = 60_000;\n/** Tool results past this size are tail-truncated and spilled to the log file. */\nexport const DEFAULT_RESULT_MAX_BYTES = 51_200;\n/** A runner log rotates once to `.log.1` at this size. */\nexport const DEFAULT_LOG_MAX_BYTES = 5 * 1024 * 1024;\n/** Retention window for logs with no live registry record. */\nexport const DEFAULT_LOG_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\nfunction positiveNumber(raw: string | undefined, fallback: number): number {\n const value = Number(raw);\n return Number.isFinite(value) && value > 0 ? value : fallback;\n}\n\n/** Age at which a bash call becomes a background runner. */\nexport function getBackgroundThresholdMs(env: NodeJS.ProcessEnv = process.env): number {\n return positiveNumber(env[BG_THRESHOLD_MS_ENV], DEFAULT_BG_THRESHOLD_MS);\n}\n\n/** Byte ceiling for a single tool result before tail truncation kicks in. */\nexport function getResultMaxBytes(env: NodeJS.ProcessEnv = process.env): number {\n return positiveNumber(env[RESULT_MAX_BYTES_ENV], DEFAULT_RESULT_MAX_BYTES);\n}\n\n/** Byte ceiling for a runner log before it rotates. */\nexport function getLogMaxBytes(env: NodeJS.ProcessEnv = process.env): number {\n return positiveNumber(env[LOG_MAX_BYTES_ENV], DEFAULT_LOG_MAX_BYTES);\n}\n\n/** Retention window for orphaned runner logs. */\nexport function getLogTtlMs(env: NodeJS.ProcessEnv = process.env): number {\n return positiveNumber(env[LOG_TTL_MS_ENV], DEFAULT_LOG_TTL_MS);\n}\n"],"mappings":"AAOA,MAAa,EAAsB,8BACtB,EAAuB,+BACvB,EAAoB,4BACpB,EAAiB,yBAEjB,EAAc,sBAGd,EAA0B,IAE1B,EAA2B,MAE3B,EAAwB,QAExB,EAAqB,OAElC,SAAS,EAAe,EAAyB,EAA0B,CACzE,IAAM,EAAQ,OAAO,CAAG,EACxB,OAAO,OAAO,SAAS,CAAK,GAAK,EAAQ,EAAI,EAAQ,CACvD,CAGA,SAAgB,EAAyB,EAAyB,QAAQ,IAAa,CACrF,OAAO,EAAe,EAAI,GAAsB,CAAuB,CACzE,CAGA,SAAgB,EAAkB,EAAyB,QAAQ,IAAa,CAC9E,OAAO,EAAe,EAAI,GAAuB,CAAwB,CAC3E,CAGA,SAAgB,EAAe,EAAyB,QAAQ,IAAa,CAC3E,OAAO,EAAe,EAAI,GAAoB,CAAqB,CACrE,CAGA,SAAgB,EAAY,EAAyB,QAAQ,IAAa,CACxE,OAAO,EAAe,EAAI,GAAiB,CAAkB,CAC/D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.cjs","names":["createRunnerContainer","TYPES","createDoomTelemetry","registerDoomFooterContribution","reconcileActiveRunners","formatRunnerFooterContribution","formatRunnerStatus","openRunnerSpace","resolveRootSessionId","provideRunnerPty","createProtocolRuntime","cleanupLegacyRunnerStore","getLogTtlMs","stopRunnerProcess"],"sources":["../src/extension.ts"],"sourcesContent":["import { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { registerDoomFooterContribution } from '@agimon-ai/doompi-ui/footer';\nimport { registerDoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { getLogTtlMs } from './config.ts';\nimport { createRunnerContainer } from './container/index.ts';\nimport { provideRunnerPty } from './ptyProvider.ts';\nimport { cleanupLegacyRunnerStore, reconcileActiveRunners, stopRunnerProcess } from './reconcile.ts';\nimport type { IBashRunService } from './services/BashRunService/index.ts';\nimport type { ILauncher } from './services/Launcher/index.ts';\nimport type { ILifeline } from './services/Lifeline/index.ts';\nimport type { ILogReader } from './services/LogReader/index.ts';\nimport type { IProcessControl } from './services/ProcessControl/index.ts';\nimport type { IPtyHost } from './services/PtyHost/index.ts';\nimport type { IRunnerPaths } from './services/RunnerPaths/index.ts';\nimport type { IRmuxBackend } from './services/RmuxBackend/index.ts';\nimport type { IRunnerRegistry, RunnerRecord } from './services/RunnerRegistry/index.ts';\nimport { registerBashTool } from './tool/bashTool.ts';\nimport { registerRunnerCompactionRecovery } from './compaction.ts';\nimport { formatRunnerFooterContribution, formatRunnerStatus } from './tui/format.ts';\nimport { openRunnerSpace } from './tui/runnerSpace.ts';\nimport { TYPES } from './types.ts';\n\nconst LEADER_SOURCE = '@agimon-ai/doompi-runner';\n/** After doom-task's `t` (65) and before the core help group (70). */\nconst LEADER_GROUP_ORDER = 67;\nconst COMMAND_NAME = 'runners';\nconst ERR_REQUIRES_INTERACTIVE = '/runners requires interactive mode';\n\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst RUNNER_FINISHED_MESSAGE = 'doom-runner-finished';\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\nconst STOPPED_REASON = 'stopped';\nconst RUNNER_STATUS_KEY = 'doom-runner-runners';\nconst RUNNER_FOOTER_ORDER = 10;\nconst RUNNER_STATUS_POLL_MS = 500;\n\n/**\n * doom-runner: replaces pi's `bash` tool with a supervised one and provides a\n * CLI for anything it leaves running.\n */\nexport function runnerExtension(pi: ExtensionAPI): void {\n const container = createRunnerContainer();\n const registry = container.get<IRunnerRegistry>(TYPES.RunnerRegistry);\n const launcher = container.get<ILauncher>(TYPES.Launcher);\n const rmuxBackend = container.get<IRmuxBackend>(TYPES.RmuxBackend);\n const logReader = container.get<ILogReader>(TYPES.LogReader);\n const ptyHost = container.get<IPtyHost>(TYPES.PtyHost);\n const bashRunService = container.get<IBashRunService>(TYPES.BashRunService);\n const paths = container.get<IRunnerPaths>(TYPES.RunnerPaths);\n const processControl = container.get<IProcessControl>(TYPES.ProcessControl);\n const lifeline = container.get<ILifeline>(TYPES.Lifeline);\n\n let sessionId: string | undefined;\n let rootSessionId: string | undefined;\n let runners: RunnerRecord[] = [];\n let sessionContext: ExtensionContext | undefined;\n let disposePtyProvider: (() => void) | undefined;\n let telemetry: DoomTelemetry | undefined;\n let sessionReady = false;\n let disposed = false;\n let shutdownStarted = false;\n let refreshInFlight: Promise<void> | undefined;\n let refreshQueued = false;\n let queuedReconciliation = false;\n let lastRunnerCount: number | undefined;\n const notifiedRunnerIds = new Set<string>();\n const pendingPromotedRunnerIds = new Set<string>();\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-runner',\n packageName: '@agimon-ai/doompi-runner',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n const footerContribution = registerDoomFooterContribution(pi, {\n source: LEADER_SOURCE,\n id: 'runner-count',\n order: RUNNER_FOOTER_ORDER,\n });\n\n /** Performs one bounded pass over active state and explicitly monitored runners. */\n const refreshNow = async (shouldReconcile: boolean): Promise<void> => {\n const activeSessionId = sessionId;\n if (!activeSessionId || disposed) return;\n\n const active = await registry.list();\n if (disposed) return;\n let visibleActive = active;\n if (shouldReconcile) {\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: false,\n active,\n });\n for (const error of reconciled.errors) process.emitWarning(error);\n if (reconciled.reclaimed.length > 0) {\n const reclaimedIds = new Set(reconciled.reclaimed);\n visibleActive = active.filter((record) => !reclaimedIds.has(record.id));\n }\n }\n if (disposed) return;\n\n runners = visibleActive.filter((record) => record.sessionId === activeSessionId);\n const rootRunners = rootSessionId\n ? visibleActive.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId)\n : [];\n const runnerCount = rootRunners.length;\n if (lastRunnerCount !== runnerCount) {\n lastRunnerCount = runnerCount;\n footerContribution.update(formatRunnerFooterContribution(runnerCount));\n if (sessionContext?.hasUI) sessionContext.ui.setStatus(RUNNER_STATUS_KEY, formatRunnerStatus(runnerCount));\n }\n\n const pendingIds = [...pendingPromotedRunnerIds];\n const monitored = await Promise.all(pendingIds.map((id) => registry.get(id, activeSessionId)));\n if (disposed) return;\n for (const [index, record] of monitored.entries()) {\n const id = pendingIds[index];\n if (!id) continue;\n if (!record || record.sessionId !== activeSessionId) {\n pendingPromotedRunnerIds.delete(id);\n continue;\n }\n if (record.state !== COMPLETED_STATE) continue;\n pendingPromotedRunnerIds.delete(record.id);\n if (notifiedRunnerIds.has(record.id)) continue;\n notifiedRunnerIds.add(record.id);\n const outcome = record.exit?.reason ?? COMPLETED_STATE;\n const code =\n record.exit?.code === null || record.exit?.code === undefined ? '' : `, exit code ${record.exit.code}`;\n const content = [\n `Background runner ${record.name} exited: ${outcome}${code}.`,\n `Runner ID: ${record.id}`,\n `Log: ${record.logPath}`,\n `Inspect: doom-runner logs ${record.id}`,\n ].join('\\n');\n pi.sendMessage(\n { customType: RUNNER_FINISHED_MESSAGE, content, display: true },\n { triggerTurn: true, deliverAs: 'steer' },\n );\n if (sessionContext) {\n void getTelemetry(sessionContext).recordEvent('doom_runner.process_finished', {\n outcome: 'completed',\n 'runner.exit_code': record.exit?.code ?? 0,\n 'runner.backend': record.backend,\n ...(Number.isFinite(Date.parse(record.startedAt))\n ? { duration_ms: Math.max(0, Date.now() - Date.parse(record.startedAt)) }\n : {}),\n });\n }\n }\n };\n\n /** Coalesces timer and event triggers without losing a requested reconciliation pass. */\n const refresh = (shouldReconcile = true): Promise<void> => {\n if (!sessionId || !sessionReady || disposed) return Promise.resolve();\n if (refreshInFlight) {\n refreshQueued = true;\n queuedReconciliation ||= shouldReconcile;\n return refreshInFlight;\n }\n\n const execution = (async () => {\n let reconcileNext = shouldReconcile;\n do {\n refreshQueued = false;\n queuedReconciliation = false;\n await refreshNow(reconcileNext);\n reconcileNext = queuedReconciliation;\n } while (refreshQueued && !disposed);\n })().finally(() => {\n if (refreshInFlight === execution) refreshInFlight = undefined;\n });\n refreshInFlight = execution;\n return execution;\n };\n\n const reportRefreshError = (error: unknown): void => {\n if (disposed) return;\n if (sessionContext) void getTelemetry(sessionContext).recordError('doom_runner.refresh_failed', error);\n process.emitWarning(`Could not refresh doom-runner: ${String(error)}`);\n };\n const scheduleRefresh = (shouldReconcile: boolean): void => {\n void refresh(shouldReconcile).catch(reportRefreshError);\n };\n const unsubscribeRegistry = registry.subscribe(() => scheduleRefresh(false));\n const statusPoll = setInterval(() => scheduleRefresh(true), RUNNER_STATUS_POLL_MS);\n statusPoll.unref?.();\n\n registerBashTool(pi, {\n bashRunService,\n getSessionId: () => {\n if (!sessionId) throw new Error('doom-runner requires an active Pi session');\n return sessionId;\n },\n onRunnerStarted: (id) => {\n pendingPromotedRunnerIds.add(id);\n scheduleRefresh(false);\n },\n });\n\n registerRunnerCompactionRecovery(pi, {\n getSessionId: () => sessionId,\n listBySession: (activeSessionId) => registry.listBySession(activeSessionId),\n });\n\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Runner Space: background processes started by bash',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n await refresh();\n await openRunnerSpace(ctx, {\n getRunners: () => runners,\n getPtyRun: (name) => rmuxBackend.get(name) ?? ptyHost.get(name),\n readLog: (logPath) => logReader.read(logPath, { lines: 1_000 }).text,\n stopRunner: async (id, reason) => {\n const record = runners.find((candidate) => candidate.id === id);\n if (!record) return;\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n await rmuxBackend.stop(record.backendTarget, record.pid);\n } else await launcher.stop(record.pid);\n await registry.complete(\n record.id,\n { reason: STOPPED_REASON, code: null, signal: null, stopReason: reason },\n record.sessionId,\n );\n await refresh();\n },\n });\n },\n });\n\n registerDoomLeaderContribution(pi, {\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'runners.open',\n path: [\n { key: 'r', label: 'runners', order: LEADER_GROUP_ORDER },\n { key: 'r', label: 'open', detail: 'background processes' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n\n pi.on(SESSION_START_EVENT, async (_event, ctx) => {\n const context = ctx as ExtensionContext;\n const activeTelemetry = getTelemetry(context);\n void activeTelemetry.recordEvent('doom_runner.session_started', { outcome: 'started' });\n sessionId = context.sessionManager.getSessionId();\n paths.setSessionId(sessionId);\n rootSessionId = resolveRootSessionId(sessionId);\n sessionContext = context;\n sessionReady = false;\n lastRunnerCount = undefined;\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n // Awaited before anything can launch, so every runner this session starts\n // finds a lifeline it can connect to rather than a socket that is not\n // listening yet, which would read as an owner that is already gone.\n await lifeline.arm(sessionId);\n\n // This package owns the only pty in the workspace, so it lends one to any\n // extension that needs a command the user can watch and answer.\n disposePtyProvider?.();\n disposePtyProvider = provideRunnerPty(createProtocolRuntime({ bus: pi.events, source: LEADER_SOURCE, sessionId }), {\n ptyHost,\n sessionId: () => sessionId,\n onError: (error, id) =>\n void activeTelemetry.recordError('doom_runner.pty_launch_failed', error, { 'run.id': id }),\n });\n\n const retained = await registry.listAll(sessionId);\n for (const record of retained) {\n if (record.state === COMPLETED_STATE) notifiedRunnerIds.add(record.id);\n else if (record.promoted) pendingPromotedRunnerIds.add(record.id);\n }\n const legacy = await cleanupLegacyRunnerStore({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n paths,\n });\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: true,\n });\n sessionReady = true;\n await refresh(false);\n const sweep = paths.sweepHistory(getLogTtlMs());\n const errors = [...legacy.errors, ...reconciled.errors, ...sweep.errors];\n for (const error of errors) process.emitWarning(error);\n const reclaimedCount = legacy.reclaimed.length + reconciled.reclaimed.length;\n if (reclaimedCount > 0 && context.hasUI) {\n context.ui.notify(`Reclaimed ${reclaimedCount} stale runner record(s)`, 'warning');\n }\n void activeTelemetry.recordEvent('doom_runner.reconciled', {\n 'runner.reclaimed_count': reclaimedCount,\n 'runner.error_count': errors.length,\n outcome: errors.length === 0 ? 'completed' : 'degraded',\n });\n });\n\n pi.on(SESSION_SHUTDOWN_EVENT, async () => {\n if (shutdownStarted) return;\n shutdownStarted = true;\n sessionReady = false;\n disposed = true;\n clearInterval(statusPoll);\n unsubscribeRegistry();\n const activeRefresh = refreshInFlight;\n if (activeRefresh) {\n try {\n await activeRefresh;\n } catch (error) {\n process.emitWarning(`Could not settle doom-runner refresh during shutdown: ${String(error)}`);\n }\n }\n try {\n try {\n disposePtyProvider?.();\n disposePtyProvider = undefined;\n await ptyHost.disposeAll();\n } catch (error) {\n process.emitWarning(`Could not dispose doom-runner PTYs: ${String(error)}`);\n }\n const owned = sessionId ? await registry.listBySession(sessionId) : [];\n for (const record of owned) {\n try {\n const stopped = await stopRunnerProcess(record, launcher, rmuxBackend);\n if (!stopped && processControl.isAlive(record.pid)) {\n process.emitWarning(`Could not stop runner ${record.id} during session shutdown`);\n continue;\n }\n await registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: stopped ? 'SIGTERM' : null,\n stopReason: 'session ended',\n },\n record.sessionId,\n );\n } catch (error) {\n process.emitWarning(`Could not clean up runner ${record.id}: ${String(error)}`);\n }\n }\n } finally {\n // Last, and in `finally`: closing the lifeline reaps anything the loop\n // above failed to stop, so a partial cleanup still leaves nothing behind.\n lifeline.dispose();\n footerContribution.dispose();\n if (sessionContext?.hasUI) sessionContext.ui.setStatus(RUNNER_STATUS_KEY, undefined);\n registry.close();\n await telemetry?.recordEvent('doom_runner.session_finished', { outcome: 'stopped' });\n await telemetry?.shutdown();\n telemetry = undefined;\n }\n });\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../src/extension.ts"],"mappings":";;;;;;iBA6CgB,gBAAgB,IAAI"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"mappings":";;;;;;iBA6CgB,gBAAgB,IAAI"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../src/extension.ts"],"sourcesContent":["import { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { registerDoomFooterContribution } from '@agimon-ai/doompi-ui/footer';\nimport { registerDoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { getLogTtlMs } from './config.ts';\nimport { createRunnerContainer } from './container/index.ts';\nimport { provideRunnerPty } from './ptyProvider.ts';\nimport { cleanupLegacyRunnerStore, reconcileActiveRunners, stopRunnerProcess } from './reconcile.ts';\nimport type { IBashRunService } from './services/BashRunService/index.ts';\nimport type { ILauncher } from './services/Launcher/index.ts';\nimport type { ILifeline } from './services/Lifeline/index.ts';\nimport type { ILogReader } from './services/LogReader/index.ts';\nimport type { IProcessControl } from './services/ProcessControl/index.ts';\nimport type { IPtyHost } from './services/PtyHost/index.ts';\nimport type { IRunnerPaths } from './services/RunnerPaths/index.ts';\nimport type { IRmuxBackend } from './services/RmuxBackend/index.ts';\nimport type { IRunnerRegistry, RunnerRecord } from './services/RunnerRegistry/index.ts';\nimport { registerBashTool } from './tool/bashTool.ts';\nimport { registerRunnerCompactionRecovery } from './compaction.ts';\nimport { formatRunnerFooterContribution, formatRunnerStatus } from './tui/format.ts';\nimport { openRunnerSpace } from './tui/runnerSpace.ts';\nimport { TYPES } from './types.ts';\n\nconst LEADER_SOURCE = '@agimon-ai/doompi-runner';\n/** After doom-task's `t` (65) and before the core help group (70). */\nconst LEADER_GROUP_ORDER = 67;\nconst COMMAND_NAME = 'runners';\nconst ERR_REQUIRES_INTERACTIVE = '/runners requires interactive mode';\n\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst RUNNER_FINISHED_MESSAGE = 'doom-runner-finished';\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\nconst STOPPED_REASON = 'stopped';\nconst RUNNER_STATUS_KEY = 'doom-runner-runners';\nconst RUNNER_FOOTER_ORDER = 10;\nconst RUNNER_STATUS_POLL_MS = 500;\n\n/**\n * doom-runner: replaces pi's `bash` tool with a supervised one and provides a\n * CLI for anything it leaves running.\n */\nexport function runnerExtension(pi: ExtensionAPI): void {\n const container = createRunnerContainer();\n const registry = container.get<IRunnerRegistry>(TYPES.RunnerRegistry);\n const launcher = container.get<ILauncher>(TYPES.Launcher);\n const rmuxBackend = container.get<IRmuxBackend>(TYPES.RmuxBackend);\n const logReader = container.get<ILogReader>(TYPES.LogReader);\n const ptyHost = container.get<IPtyHost>(TYPES.PtyHost);\n const bashRunService = container.get<IBashRunService>(TYPES.BashRunService);\n const paths = container.get<IRunnerPaths>(TYPES.RunnerPaths);\n const processControl = container.get<IProcessControl>(TYPES.ProcessControl);\n const lifeline = container.get<ILifeline>(TYPES.Lifeline);\n\n let sessionId: string | undefined;\n let rootSessionId: string | undefined;\n let runners: RunnerRecord[] = [];\n let sessionContext: ExtensionContext | undefined;\n let disposePtyProvider: (() => void) | undefined;\n let telemetry: DoomTelemetry | undefined;\n let sessionReady = false;\n let disposed = false;\n let shutdownStarted = false;\n let refreshInFlight: Promise<void> | undefined;\n let refreshQueued = false;\n let queuedReconciliation = false;\n let lastRunnerCount: number | undefined;\n const notifiedRunnerIds = new Set<string>();\n const pendingPromotedRunnerIds = new Set<string>();\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-runner',\n packageName: '@agimon-ai/doompi-runner',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n const footerContribution = registerDoomFooterContribution(pi, {\n source: LEADER_SOURCE,\n id: 'runner-count',\n order: RUNNER_FOOTER_ORDER,\n });\n\n /** Performs one bounded pass over active state and explicitly monitored runners. */\n const refreshNow = async (shouldReconcile: boolean): Promise<void> => {\n const activeSessionId = sessionId;\n if (!activeSessionId || disposed) return;\n\n const active = await registry.list();\n if (disposed) return;\n let visibleActive = active;\n if (shouldReconcile) {\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: false,\n active,\n });\n for (const error of reconciled.errors) process.emitWarning(error);\n if (reconciled.reclaimed.length > 0) {\n const reclaimedIds = new Set(reconciled.reclaimed);\n visibleActive = active.filter((record) => !reclaimedIds.has(record.id));\n }\n }\n if (disposed) return;\n\n runners = visibleActive.filter((record) => record.sessionId === activeSessionId);\n const rootRunners = rootSessionId\n ? visibleActive.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId)\n : [];\n const runnerCount = rootRunners.length;\n if (lastRunnerCount !== runnerCount) {\n lastRunnerCount = runnerCount;\n footerContribution.update(formatRunnerFooterContribution(runnerCount));\n if (sessionContext?.hasUI) sessionContext.ui.setStatus(RUNNER_STATUS_KEY, formatRunnerStatus(runnerCount));\n }\n\n const pendingIds = [...pendingPromotedRunnerIds];\n const monitored = await Promise.all(pendingIds.map((id) => registry.get(id, activeSessionId)));\n if (disposed) return;\n for (const [index, record] of monitored.entries()) {\n const id = pendingIds[index];\n if (!id) continue;\n if (!record || record.sessionId !== activeSessionId) {\n pendingPromotedRunnerIds.delete(id);\n continue;\n }\n if (record.state !== COMPLETED_STATE) continue;\n pendingPromotedRunnerIds.delete(record.id);\n if (notifiedRunnerIds.has(record.id)) continue;\n notifiedRunnerIds.add(record.id);\n const outcome = record.exit?.reason ?? COMPLETED_STATE;\n const code =\n record.exit?.code === null || record.exit?.code === undefined ? '' : `, exit code ${record.exit.code}`;\n const content = [\n `Background runner ${record.name} exited: ${outcome}${code}.`,\n `Runner ID: ${record.id}`,\n `Log: ${record.logPath}`,\n `Inspect: doom-runner logs ${record.id}`,\n ].join('\\n');\n pi.sendMessage(\n { customType: RUNNER_FINISHED_MESSAGE, content, display: true },\n { triggerTurn: true, deliverAs: 'steer' },\n );\n if (sessionContext) {\n void getTelemetry(sessionContext).recordEvent('doom_runner.process_finished', {\n outcome: 'completed',\n 'runner.exit_code': record.exit?.code ?? 0,\n 'runner.backend': record.backend,\n ...(Number.isFinite(Date.parse(record.startedAt))\n ? { duration_ms: Math.max(0, Date.now() - Date.parse(record.startedAt)) }\n : {}),\n });\n }\n }\n };\n\n /** Coalesces timer and event triggers without losing a requested reconciliation pass. */\n const refresh = (shouldReconcile = true): Promise<void> => {\n if (!sessionId || !sessionReady || disposed) return Promise.resolve();\n if (refreshInFlight) {\n refreshQueued = true;\n queuedReconciliation ||= shouldReconcile;\n return refreshInFlight;\n }\n\n const execution = (async () => {\n let reconcileNext = shouldReconcile;\n do {\n refreshQueued = false;\n queuedReconciliation = false;\n await refreshNow(reconcileNext);\n reconcileNext = queuedReconciliation;\n } while (refreshQueued && !disposed);\n })().finally(() => {\n if (refreshInFlight === execution) refreshInFlight = undefined;\n });\n refreshInFlight = execution;\n return execution;\n };\n\n const reportRefreshError = (error: unknown): void => {\n if (disposed) return;\n if (sessionContext) void getTelemetry(sessionContext).recordError('doom_runner.refresh_failed', error);\n process.emitWarning(`Could not refresh doom-runner: ${String(error)}`);\n };\n const scheduleRefresh = (shouldReconcile: boolean): void => {\n void refresh(shouldReconcile).catch(reportRefreshError);\n };\n const unsubscribeRegistry = registry.subscribe(() => scheduleRefresh(false));\n const statusPoll = setInterval(() => scheduleRefresh(true), RUNNER_STATUS_POLL_MS);\n statusPoll.unref?.();\n\n registerBashTool(pi, {\n bashRunService,\n getSessionId: () => {\n if (!sessionId) throw new Error('doom-runner requires an active Pi session');\n return sessionId;\n },\n onRunnerStarted: (id) => {\n pendingPromotedRunnerIds.add(id);\n scheduleRefresh(false);\n },\n });\n\n registerRunnerCompactionRecovery(pi, {\n getSessionId: () => sessionId,\n listBySession: (activeSessionId) => registry.listBySession(activeSessionId),\n });\n\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Runner Space: background processes started by bash',\n handler: async (_args, ctx) => {\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n await refresh();\n await openRunnerSpace(ctx, {\n getRunners: () => runners,\n getPtyRun: (name) => rmuxBackend.get(name) ?? ptyHost.get(name),\n readLog: (logPath) => logReader.read(logPath, { lines: 1_000 }).text,\n stopRunner: async (id, reason) => {\n const record = runners.find((candidate) => candidate.id === id);\n if (!record) return;\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n await rmuxBackend.stop(record.backendTarget, record.pid);\n } else await launcher.stop(record.pid);\n await registry.complete(\n record.id,\n { reason: STOPPED_REASON, code: null, signal: null, stopReason: reason },\n record.sessionId,\n );\n await refresh();\n },\n });\n },\n });\n\n registerDoomLeaderContribution(pi, {\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'runners.open',\n path: [\n { key: 'r', label: 'runners', order: LEADER_GROUP_ORDER },\n { key: 'r', label: 'open', detail: 'background processes' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n\n pi.on(SESSION_START_EVENT, async (_event, ctx) => {\n const context = ctx as ExtensionContext;\n const activeTelemetry = getTelemetry(context);\n void activeTelemetry.recordEvent('doom_runner.session_started', { outcome: 'started' });\n sessionId = context.sessionManager.getSessionId();\n paths.setSessionId(sessionId);\n rootSessionId = resolveRootSessionId(sessionId);\n sessionContext = context;\n sessionReady = false;\n lastRunnerCount = undefined;\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n // Awaited before anything can launch, so every runner this session starts\n // finds a lifeline it can connect to rather than a socket that is not\n // listening yet, which would read as an owner that is already gone.\n await lifeline.arm(sessionId);\n\n // This package owns the only pty in the workspace, so it lends one to any\n // extension that needs a command the user can watch and answer.\n disposePtyProvider?.();\n disposePtyProvider = provideRunnerPty(createProtocolRuntime({ bus: pi.events, source: LEADER_SOURCE, sessionId }), {\n ptyHost,\n sessionId: () => sessionId,\n onError: (error, id) =>\n void activeTelemetry.recordError('doom_runner.pty_launch_failed', error, { 'run.id': id }),\n });\n\n const retained = await registry.listAll(sessionId);\n for (const record of retained) {\n if (record.state === COMPLETED_STATE) notifiedRunnerIds.add(record.id);\n else if (record.promoted) pendingPromotedRunnerIds.add(record.id);\n }\n const legacy = await cleanupLegacyRunnerStore({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n paths,\n });\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: true,\n });\n sessionReady = true;\n await refresh(false);\n const sweep = paths.sweepHistory(getLogTtlMs());\n const errors = [...legacy.errors, ...reconciled.errors, ...sweep.errors];\n for (const error of errors) process.emitWarning(error);\n const reclaimedCount = legacy.reclaimed.length + reconciled.reclaimed.length;\n if (reclaimedCount > 0 && context.hasUI) {\n context.ui.notify(`Reclaimed ${reclaimedCount} stale runner record(s)`, 'warning');\n }\n void activeTelemetry.recordEvent('doom_runner.reconciled', {\n 'runner.reclaimed_count': reclaimedCount,\n 'runner.error_count': errors.length,\n outcome: errors.length === 0 ? 'completed' : 'degraded',\n });\n });\n\n pi.on(SESSION_SHUTDOWN_EVENT, async () => {\n if (shutdownStarted) return;\n shutdownStarted = true;\n sessionReady = false;\n disposed = true;\n clearInterval(statusPoll);\n unsubscribeRegistry();\n const activeRefresh = refreshInFlight;\n if (activeRefresh) {\n try {\n await activeRefresh;\n } catch (error) {\n process.emitWarning(`Could not settle doom-runner refresh during shutdown: ${String(error)}`);\n }\n }\n try {\n try {\n disposePtyProvider?.();\n disposePtyProvider = undefined;\n await ptyHost.disposeAll();\n } catch (error) {\n process.emitWarning(`Could not dispose doom-runner PTYs: ${String(error)}`);\n }\n const owned = sessionId ? await registry.listBySession(sessionId) : [];\n for (const record of owned) {\n try {\n const stopped = await stopRunnerProcess(record, launcher, rmuxBackend);\n if (!stopped && processControl.isAlive(record.pid)) {\n process.emitWarning(`Could not stop runner ${record.id} during session shutdown`);\n continue;\n }\n await registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: stopped ? 'SIGTERM' : null,\n stopReason: 'session ended',\n },\n record.sessionId,\n );\n } catch (error) {\n process.emitWarning(`Could not clean up runner ${record.id}: ${String(error)}`);\n }\n }\n } finally {\n // Last, and in `finally`: closing the lifeline reaps anything the loop\n // above failed to stop, so a partial cleanup still leaves nothing behind.\n lifeline.dispose();\n footerContribution.dispose();\n if (sessionContext?.hasUI) sessionContext.ui.setStatus(RUNNER_STATUS_KEY, undefined);\n registry.close();\n await telemetry?.recordEvent('doom_runner.session_finished', { outcome: 'stopped' });\n await telemetry?.shutdown();\n telemetry = undefined;\n }\n });\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ptyProvider.cjs","names":["RunnerPtyLaunchProtocol","RunnerPtyData","RunnerPtyExit","RunnerPtyInput","RunnerPtyStop"],"sources":["../src/ptyProvider.ts"],"sourcesContent":["/**\n * Lends this package's pseudo terminals to other extensions.\n *\n * doom-runner is the only package that carries `node-pty`, and the only one that\n * knows how to keep a terminal alive for a session and tear it down with one.\n * Rather than have every extension that needs an answerable command grow its own\n * copy of that, they ask over the bus and the terminal stays here.\n *\n * Output is forwarded raw, chunk by chunk, because the caller is usually\n * rendering a tail and needs the partial line a prompt sits on. Reassembling\n * lines is the caller's problem, not the terminal's.\n *\n * AVOID:\n * - Keeping a run past the session that asked for it\n * - Swallowing an exit; a caller waiting on one would hang forever\n */\n\nimport {\n RunnerPtyData,\n RunnerPtyExit,\n RunnerPtyInput,\n RunnerPtyLaunchProtocol,\n RunnerPtyStop,\n} from '@agimon-ai/doompi-extension-contracts/runner-pty';\nimport type { ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { IPtyHost, PtyRun } from './services/PtyHost/index.ts';\n\n/** Namespaced so a borrowed terminal is distinguishable in the runner list. */\nconst RUN_NAME_PREFIX = 'ext';\n\n/**\n * Collapses a caller and its run id into one safe path segment.\n *\n * The id becomes a log file name, and package-scoped sources carry a `/`, which\n * `path.join` would turn into a directory nobody created.\n */\nfunction runKey(source: string, id: string): string {\n return `${RUN_NAME_PREFIX}:${source}:${id}`.replaceAll(/[^A-Za-z0-9._-]/gu, '-');\n}\n\nexport interface PtyProviderOptions {\n ptyHost: IPtyHost;\n sessionId: () => string | undefined;\n onError?: (error: unknown, id: string) => void;\n}\n\nexport function provideRunnerPty(runtime: ProtocolRuntime, options: PtyProviderOptions): () => void {\n const runs = new Map<string, { run: PtyRun; dispose: () => void }>();\n\n const release = (id: string): void => {\n const entry = runs.get(id);\n if (!entry) return;\n runs.delete(id);\n entry.dispose();\n };\n\n const disposers = [\n runtime.provide(RunnerPtyLaunchProtocol, async (request, identity) => {\n const sessionId = options.sessionId();\n if (!sessionId) return { launched: false, reason: 'No runner session is active.' };\n // Keyed by the caller too, so two extensions choosing the same id cannot\n // collide or read each other's output.\n const key = runKey(identity.source, request.id);\n if (runs.has(key)) return { launched: false, reason: `A run named ${request.id} is already active.` };\n try {\n const run = await options.ptyHost.launch({\n id: key,\n // The host keys its runs by name, so this is the unique one rather\n // than the caller's label; two callers naming a run `install` would\n // otherwise clobber each other.\n name: key,\n command: request.command,\n cwd: request.cwd,\n sessionId,\n ...(request.cols === undefined ? {} : { cols: request.cols }),\n ...(request.rows === undefined ? {} : { rows: request.rows }),\n });\n const unsubscribe = run.onData((chunk) => runtime.notify(RunnerPtyData, { id: request.id, chunk }));\n runs.set(key, { run, dispose: unsubscribe });\n void run\n .completion()\n .then((result) =>\n runtime.notify(RunnerPtyExit, {\n id: request.id,\n ...(typeof result.code === 'number' ? { code: result.code } : {}),\n ...(result.signal ? { signal: String(result.signal) } : {}),\n }),\n )\n // A caller blocked on the exit would wait forever otherwise, so a\n // failure to observe one is still reported as an exit.\n .catch(() => runtime.notify(RunnerPtyExit, { id: request.id, code: -1 }))\n .finally(() => release(key));\n return { launched: true };\n } catch (error) {\n options.onError?.(error, request.id);\n return { launched: false, reason: error instanceof Error ? error.message : String(error) };\n }\n }),\n runtime.onNotification(RunnerPtyInput, ({ id, text }, identity) => {\n runs.get(runKey(identity.source, id))?.run.write(`${text}\\n`);\n }),\n runtime.onNotification(RunnerPtyStop, async ({ id }, identity) => {\n const key = runKey(identity.source, id);\n const entry = runs.get(key);\n if (!entry) return;\n release(key);\n await entry.run.stop();\n }),\n ];\n\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n for (const dispose of disposers) dispose();\n for (const entry of runs.values()) entry.dispose();\n runs.clear();\n };\n}\n"],"mappings":"qIAoCA,SAAS,EAAO,EAAgB,EAAoB,CAClD,MAAO,OAAsB,EAAO,GAAG,IAAK,WAAW,oBAAqB,GAAG,CACjF,CAQA,SAAgB,EAAiB,EAA0B,EAAyC,CAClG,IAAM,EAAO,IAAI,IAEX,EAAW,GAAqB,CACpC,IAAM,EAAQ,EAAK,IAAI,CAAE,EACpB,IACL,EAAK,OAAO,CAAE,EACd,EAAM,QAAQ,EAChB,EAEM,EAAY,CAChB,EAAQ,QAAQA,EAAAA,wBAAyB,MAAO,EAAS,IAAa,CACpE,IAAM,EAAY,EAAQ,UAAU,EACpC,GAAI,CAAC,EAAW,MAAO,CAAE,SAAU,GAAO,OAAQ,8BAA+B,EAGjF,IAAM,EAAM,EAAO,EAAS,OAAQ,EAAQ,EAAE,EAC9C,GAAI,EAAK,IAAI,CAAG,EAAG,MAAO,CAAE,SAAU,GAAO,OAAQ,eAAe,EAAQ,GAAG,oBAAqB,EACpG,GAAI,CACF,IAAM,EAAM,MAAM,EAAQ,QAAQ,OAAO,CACvC,GAAI,EAIJ,KAAM,EACN,QAAS,EAAQ,QACjB,IAAK,EAAQ,IACb,YACA,GAAI,EAAQ,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAQ,IAAK,EAC3D,GAAI,EAAQ,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAQ,IAAK,CAC7D,CAAC,EACK,EAAc,EAAI,OAAQ,GAAU,EAAQ,OAAOC,EAAAA,cAAe,CAAE,GAAI,EAAQ,GAAI,OAAM,CAAC,CAAC,EAelG,OAdA,EAAK,IAAI,EAAK,CAAE,MAAK,QAAS,CAAY,CAAC,EAC3C,EACG,WAAW,CAAC,CACZ,KAAM,GACL,EAAQ,OAAOC,EAAAA,cAAe,CAC5B,GAAI,EAAQ,GACZ,GAAI,OAAO,EAAO,MAAS,SAAW,CAAE,KAAM,EAAO,IAAK,EAAI,CAAC,EAC/D,GAAI,EAAO,OAAS,CAAE,OAAQ,OAAO,EAAO,MAAM,CAAE,EAAI,CAAC,CAC3D,CAAC,CACH,CAAC,CAGA,UAAY,EAAQ,OAAOA,EAAAA,cAAe,CAAE,GAAI,EAAQ,GAAI,KAAM,EAAG,CAAC,CAAC,CAAC,CACxE,YAAc,EAAQ,CAAG,CAAC,EACtB,CAAE,SAAU,EAAK,CAC1B,OAAS,EAAO,CAEd,OADA,EAAQ,UAAU,EAAO,EAAQ,EAAE,EAC5B,CAAE,SAAU,GAAO,OAAQ,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAAE,CAC3F,CACF,CAAC,EACD,EAAQ,eAAeC,EAAAA,gBAAiB,CAAE,KAAI,QAAQ,IAAa,CACjE,EAAK,IAAI,EAAO,EAAS,OAAQ,CAAE,CAAC,CAAC,EAAE,IAAI,MAAM,GAAG,EAAK,GAAG,CAC9D,CAAC,EACD,EAAQ,eAAeC,EAAAA,cAAe,MAAO,CAAE,MAAM,IAAa,CAChE,IAAM,EAAM,EAAO,EAAS,OAAQ,CAAE,EAChC,EAAQ,EAAK,IAAI,CAAG,EACrB,IACL,EAAQ,CAAG,EACX,MAAM,EAAM,IAAI,KAAK,EACvB,CAAC,CACH,EAEI,EAAW,GACf,UAAa,CACP,MACJ,GAAW,GACX,IAAK,IAAM,KAAW,EAAW,EAAQ,EACzC,IAAK,IAAM,KAAS,EAAK,OAAO,EAAG,EAAM,QAAQ,EACjD,EAAK,MAAM,CAHA,CAIb,CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ptyProvider.d.cts","names":[],"sources":["../src/ptyProvider.ts"],"mappings":";;;;UAwCiB;EACf,SAAS;EACT;EACA,WAAW,gBAAgB;;iBAGb,iBAAiB,SAAS,iBAAiB,SAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ptyProvider.d.mts","names":[],"sources":["../src/ptyProvider.ts"],"mappings":";;;;UAwCiB;EACf,SAAS;EACT;EACA,WAAW,gBAAgB;;iBAGb,iBAAiB,SAAS,iBAAiB,SAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ptyProvider.mjs","names":[],"sources":["../src/ptyProvider.ts"],"sourcesContent":["/**\n * Lends this package's pseudo terminals to other extensions.\n *\n * doom-runner is the only package that carries `node-pty`, and the only one that\n * knows how to keep a terminal alive for a session and tear it down with one.\n * Rather than have every extension that needs an answerable command grow its own\n * copy of that, they ask over the bus and the terminal stays here.\n *\n * Output is forwarded raw, chunk by chunk, because the caller is usually\n * rendering a tail and needs the partial line a prompt sits on. Reassembling\n * lines is the caller's problem, not the terminal's.\n *\n * AVOID:\n * - Keeping a run past the session that asked for it\n * - Swallowing an exit; a caller waiting on one would hang forever\n */\n\nimport {\n RunnerPtyData,\n RunnerPtyExit,\n RunnerPtyInput,\n RunnerPtyLaunchProtocol,\n RunnerPtyStop,\n} from '@agimon-ai/doompi-extension-contracts/runner-pty';\nimport type { ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { IPtyHost, PtyRun } from './services/PtyHost/index.ts';\n\n/** Namespaced so a borrowed terminal is distinguishable in the runner list. */\nconst RUN_NAME_PREFIX = 'ext';\n\n/**\n * Collapses a caller and its run id into one safe path segment.\n *\n * The id becomes a log file name, and package-scoped sources carry a `/`, which\n * `path.join` would turn into a directory nobody created.\n */\nfunction runKey(source: string, id: string): string {\n return `${RUN_NAME_PREFIX}:${source}:${id}`.replaceAll(/[^A-Za-z0-9._-]/gu, '-');\n}\n\nexport interface PtyProviderOptions {\n ptyHost: IPtyHost;\n sessionId: () => string | undefined;\n onError?: (error: unknown, id: string) => void;\n}\n\nexport function provideRunnerPty(runtime: ProtocolRuntime, options: PtyProviderOptions): () => void {\n const runs = new Map<string, { run: PtyRun; dispose: () => void }>();\n\n const release = (id: string): void => {\n const entry = runs.get(id);\n if (!entry) return;\n runs.delete(id);\n entry.dispose();\n };\n\n const disposers = [\n runtime.provide(RunnerPtyLaunchProtocol, async (request, identity) => {\n const sessionId = options.sessionId();\n if (!sessionId) return { launched: false, reason: 'No runner session is active.' };\n // Keyed by the caller too, so two extensions choosing the same id cannot\n // collide or read each other's output.\n const key = runKey(identity.source, request.id);\n if (runs.has(key)) return { launched: false, reason: `A run named ${request.id} is already active.` };\n try {\n const run = await options.ptyHost.launch({\n id: key,\n // The host keys its runs by name, so this is the unique one rather\n // than the caller's label; two callers naming a run `install` would\n // otherwise clobber each other.\n name: key,\n command: request.command,\n cwd: request.cwd,\n sessionId,\n ...(request.cols === undefined ? {} : { cols: request.cols }),\n ...(request.rows === undefined ? {} : { rows: request.rows }),\n });\n const unsubscribe = run.onData((chunk) => runtime.notify(RunnerPtyData, { id: request.id, chunk }));\n runs.set(key, { run, dispose: unsubscribe });\n void run\n .completion()\n .then((result) =>\n runtime.notify(RunnerPtyExit, {\n id: request.id,\n ...(typeof result.code === 'number' ? { code: result.code } : {}),\n ...(result.signal ? { signal: String(result.signal) } : {}),\n }),\n )\n // A caller blocked on the exit would wait forever otherwise, so a\n // failure to observe one is still reported as an exit.\n .catch(() => runtime.notify(RunnerPtyExit, { id: request.id, code: -1 }))\n .finally(() => release(key));\n return { launched: true };\n } catch (error) {\n options.onError?.(error, request.id);\n return { launched: false, reason: error instanceof Error ? error.message : String(error) };\n }\n }),\n runtime.onNotification(RunnerPtyInput, ({ id, text }, identity) => {\n runs.get(runKey(identity.source, id))?.run.write(`${text}\\n`);\n }),\n runtime.onNotification(RunnerPtyStop, async ({ id }, identity) => {\n const key = runKey(identity.source, id);\n const entry = runs.get(key);\n if (!entry) return;\n release(key);\n await entry.run.stop();\n }),\n ];\n\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n for (const dispose of disposers) dispose();\n for (const entry of runs.values()) entry.dispose();\n runs.clear();\n };\n}\n"],"mappings":"wKAoCA,SAAS,EAAO,EAAgB,EAAoB,CAClD,MAAO,OAAsB,EAAO,GAAG,IAAK,WAAW,oBAAqB,GAAG,CACjF,CAQA,SAAgB,EAAiB,EAA0B,EAAyC,CAClG,IAAM,EAAO,IAAI,IAEX,EAAW,GAAqB,CACpC,IAAM,EAAQ,EAAK,IAAI,CAAE,EACpB,IACL,EAAK,OAAO,CAAE,EACd,EAAM,QAAQ,EAChB,EAEM,EAAY,CAChB,EAAQ,QAAQ,EAAyB,MAAO,EAAS,IAAa,CACpE,IAAM,EAAY,EAAQ,UAAU,EACpC,GAAI,CAAC,EAAW,MAAO,CAAE,SAAU,GAAO,OAAQ,8BAA+B,EAGjF,IAAM,EAAM,EAAO,EAAS,OAAQ,EAAQ,EAAE,EAC9C,GAAI,EAAK,IAAI,CAAG,EAAG,MAAO,CAAE,SAAU,GAAO,OAAQ,eAAe,EAAQ,GAAG,oBAAqB,EACpG,GAAI,CACF,IAAM,EAAM,MAAM,EAAQ,QAAQ,OAAO,CACvC,GAAI,EAIJ,KAAM,EACN,QAAS,EAAQ,QACjB,IAAK,EAAQ,IACb,YACA,GAAI,EAAQ,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAQ,IAAK,EAC3D,GAAI,EAAQ,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAQ,IAAK,CAC7D,CAAC,EACK,EAAc,EAAI,OAAQ,GAAU,EAAQ,OAAO,EAAe,CAAE,GAAI,EAAQ,GAAI,OAAM,CAAC,CAAC,EAelG,OAdA,EAAK,IAAI,EAAK,CAAE,MAAK,QAAS,CAAY,CAAC,EAC3C,EACG,WAAW,CAAC,CACZ,KAAM,GACL,EAAQ,OAAO,EAAe,CAC5B,GAAI,EAAQ,GACZ,GAAI,OAAO,EAAO,MAAS,SAAW,CAAE,KAAM,EAAO,IAAK,EAAI,CAAC,EAC/D,GAAI,EAAO,OAAS,CAAE,OAAQ,OAAO,EAAO,MAAM,CAAE,EAAI,CAAC,CAC3D,CAAC,CACH,CAAC,CAGA,UAAY,EAAQ,OAAO,EAAe,CAAE,GAAI,EAAQ,GAAI,KAAM,EAAG,CAAC,CAAC,CAAC,CACxE,YAAc,EAAQ,CAAG,CAAC,EACtB,CAAE,SAAU,EAAK,CAC1B,OAAS,EAAO,CAEd,OADA,EAAQ,UAAU,EAAO,EAAQ,EAAE,EAC5B,CAAE,SAAU,GAAO,OAAQ,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAAE,CAC3F,CACF,CAAC,EACD,EAAQ,eAAe,GAAiB,CAAE,KAAI,QAAQ,IAAa,CACjE,EAAK,IAAI,EAAO,EAAS,OAAQ,CAAE,CAAC,CAAC,EAAE,IAAI,MAAM,GAAG,EAAK,GAAG,CAC9D,CAAC,EACD,EAAQ,eAAe,EAAe,MAAO,CAAE,MAAM,IAAa,CAChE,IAAM,EAAM,EAAO,EAAS,OAAQ,CAAE,EAChC,EAAQ,EAAK,IAAI,CAAG,EACrB,IACL,EAAQ,CAAG,EACX,MAAM,EAAM,IAAI,KAAK,EACvB,CAAC,CACH,EAEI,EAAW,GACf,UAAa,CACP,MACJ,GAAW,GACX,IAAK,IAAM,KAAW,EAAW,EAAQ,EACzC,IAAK,IAAM,KAAS,EAAK,OAAO,EAAG,EAAM,QAAQ,EACjD,EAAK,MAAM,CAHA,CAIb,CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reconcile.cjs","names":["path"],"sources":["../src/reconcile.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ILauncher } from './services/Launcher/index.ts';\nimport type { IProcessControl } from './services/ProcessControl/index.ts';\nimport type { IRunnerPaths } from './services/RunnerPaths/index.ts';\nimport type { IRmuxBackend } from './services/RmuxBackend/index.ts';\nimport type { IRunnerRegistry, RunnerRecord } from './services/RunnerRegistry/index.ts';\n\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\n\ninterface ReconcileDependencies {\n registry: IRunnerRegistry;\n launcher: ILauncher;\n rmuxBackend: IRmuxBackend;\n processControl: IProcessControl;\n currentHostPid: number;\n startup: boolean;\n active?: readonly RunnerRecord[];\n}\n\nexport interface RunnerReconcileResult {\n reclaimed: string[];\n errors: string[];\n}\n\nexport async function stopRunnerProcess(\n record: RunnerRecord,\n launcher: ILauncher,\n rmuxBackend: IRmuxBackend,\n): Promise<boolean> {\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n const stopped = await rmuxBackend.stop(record.backendTarget, record.pid);\n if (stopped) return true;\n }\n return launcher.stop(record.pid);\n}\n\n/** Repairs active registry entries that lost their original completion observer. */\nexport async function reconcileActiveRunners(dependencies: ReconcileDependencies): Promise<RunnerReconcileResult> {\n const result: RunnerReconcileResult = { reclaimed: [], errors: [] };\n const active = dependencies.active ?? (await dependencies.registry.list());\n for (const record of active) {\n try {\n const persisted = await dependencies.registry.get(record.id, record.sessionId);\n if (persisted?.state === COMPLETED_STATE) {\n await dependencies.registry.release(record.id);\n result.reclaimed.push(record.id);\n continue;\n }\n\n const rmuxOutcome =\n record.backend === RMUX_BACKEND ? dependencies.rmuxBackend.readOutcome(record.id, record.sessionId) : undefined;\n if (rmuxOutcome) {\n await dependencies.registry.complete(\n record.id,\n {\n reason: rmuxOutcome.signal ? 'signaled' : rmuxOutcome.code === 0 ? COMPLETED_STATE : 'failed',\n code: rmuxOutcome.code,\n signal: rmuxOutcome.signal,\n },\n record.sessionId,\n );\n result.reclaimed.push(record.id);\n continue;\n }\n\n if (!dependencies.processControl.isAlive(record.pid)) {\n await dependencies.registry.complete(\n record.id,\n { reason: 'backend_lost', code: null, signal: null },\n record.sessionId,\n );\n result.reclaimed.push(record.id);\n continue;\n }\n\n const previousRuntime = dependencies.startup && record.hostPid === dependencies.currentHostPid;\n const ownerLost = !dependencies.processControl.isAlive(record.hostPid);\n if (!previousRuntime && !ownerLost) continue;\n\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend);\n if (!stopped && dependencies.processControl.isAlive(record.pid)) {\n result.errors.push(`Could not stop orphaned runner ${record.id}`);\n continue;\n }\n await dependencies.registry.complete(\n record.id,\n {\n reason: 'stopped',\n code: null,\n signal: 'SIGTERM',\n stopReason: 'owner session ended',\n },\n record.sessionId,\n );\n result.reclaimed.push(record.id);\n } catch (error) {\n result.errors.push(`Could not reconcile runner ${record.id}: ${String(error)}`);\n }\n }\n return result;\n}\n\nfunction isWithin(root: string, candidate: string): boolean {\n const relative = path.relative(path.resolve(root), path.resolve(candidate));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\ninterface LegacyCleanupDependencies extends Omit<ReconcileDependencies, 'startup'> {\n paths: IRunnerPaths;\n}\n\n/** Deletes the legacy Git store only after no live foreign runner uses it. */\nexport async function cleanupLegacyRunnerStore(\n dependencies: LegacyCleanupDependencies,\n): Promise<RunnerReconcileResult & { removed?: string }> {\n const result: RunnerReconcileResult & { removed?: string } = { reclaimed: [], errors: [] };\n const legacyDirectory = dependencies.paths.legacyDirectory();\n if (!legacyDirectory) return result;\n\n const legacyRecords = (await dependencies.registry.list()).filter((record) =>\n isWithin(legacyDirectory, record.logPath),\n );\n for (const record of legacyRecords) {\n const childAlive = dependencies.processControl.isAlive(record.pid);\n const ownerAlive = dependencies.processControl.isAlive(record.hostPid);\n const ownedByPreviousRuntime = record.hostPid === dependencies.currentHostPid;\n if (childAlive && ownerAlive && !ownedByPreviousRuntime) continue;\n try {\n if (childAlive) {\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend);\n if (!stopped && dependencies.processControl.isAlive(record.pid)) {\n result.errors.push(`Could not stop legacy runner ${record.id}`);\n continue;\n }\n }\n await dependencies.registry.release(record.id);\n result.reclaimed.push(record.id);\n } catch (error) {\n result.errors.push(`Could not reclaim legacy runner ${record.id}: ${String(error)}`);\n }\n }\n\n let liveDependencies: RunnerRecord[];\n try {\n liveDependencies = (await dependencies.registry.listAcrossRepositories()).filter(\n (record) => isWithin(legacyDirectory, record.logPath) && dependencies.processControl.isAlive(record.pid),\n );\n } catch (error) {\n result.errors.push(`Could not verify legacy runner dependencies: ${String(error)}`);\n return result;\n }\n if (liveDependencies.length === 0) result.removed = dependencies.paths.removeLegacyStore();\n return result;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reconcile.d.cts","names":[],"sources":["../src/reconcile.ts"],"mappings":";;;;;;;;;;;UAUU;EACR,UAAU;EACV,UAAU;EACV,aAAa;EACb,gBAAgB;EAChB;EACA;EACA,kBAAkB;;UAGH;EACf;EACA;;iBAGoB,kBACpB,QAAQ,cACR,UAAU,WACV,aAAa,eACZ;;iBASmB,uBAAuB,cAAc,wBAAwB,QAAQ;UAsEjF,kCAAkC,KAAK;EAC/C,OAAO;;;iBAIa,yBACpB,cAAc,4BACb,QAAQ;EAA0B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reconcile.d.mts","names":[],"sources":["../src/reconcile.ts"],"mappings":";;;;;;;;;;;UAUU;EACR,UAAU;EACV,UAAU;EACV,aAAa;EACb,gBAAgB;EAChB;EACA;EACA,kBAAkB;;UAGH;EACf;EACA;;iBAGoB,kBACpB,QAAQ,cACR,UAAU,WACV,aAAa,eACZ;;iBASmB,uBAAuB,cAAc,wBAAwB,QAAQ;UAsEjF,kCAAkC,KAAK;EAC/C,OAAO;;;iBAIa,yBACpB,cAAc,4BACb,QAAQ;EAA0B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reconcile.mjs","names":[],"sources":["../src/reconcile.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ILauncher } from './services/Launcher/index.ts';\nimport type { IProcessControl } from './services/ProcessControl/index.ts';\nimport type { IRunnerPaths } from './services/RunnerPaths/index.ts';\nimport type { IRmuxBackend } from './services/RmuxBackend/index.ts';\nimport type { IRunnerRegistry, RunnerRecord } from './services/RunnerRegistry/index.ts';\n\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\n\ninterface ReconcileDependencies {\n registry: IRunnerRegistry;\n launcher: ILauncher;\n rmuxBackend: IRmuxBackend;\n processControl: IProcessControl;\n currentHostPid: number;\n startup: boolean;\n active?: readonly RunnerRecord[];\n}\n\nexport interface RunnerReconcileResult {\n reclaimed: string[];\n errors: string[];\n}\n\nexport async function stopRunnerProcess(\n record: RunnerRecord,\n launcher: ILauncher,\n rmuxBackend: IRmuxBackend,\n): Promise<boolean> {\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n const stopped = await rmuxBackend.stop(record.backendTarget, record.pid);\n if (stopped) return true;\n }\n return launcher.stop(record.pid);\n}\n\n/** Repairs active registry entries that lost their original completion observer. */\nexport async function reconcileActiveRunners(dependencies: ReconcileDependencies): Promise<RunnerReconcileResult> {\n const result: RunnerReconcileResult = { reclaimed: [], errors: [] };\n const active = dependencies.active ?? (await dependencies.registry.list());\n for (const record of active) {\n try {\n const persisted = await dependencies.registry.get(record.id, record.sessionId);\n if (persisted?.state === COMPLETED_STATE) {\n await dependencies.registry.release(record.id);\n result.reclaimed.push(record.id);\n continue;\n }\n\n const rmuxOutcome =\n record.backend === RMUX_BACKEND ? dependencies.rmuxBackend.readOutcome(record.id, record.sessionId) : undefined;\n if (rmuxOutcome) {\n await dependencies.registry.complete(\n record.id,\n {\n reason: rmuxOutcome.signal ? 'signaled' : rmuxOutcome.code === 0 ? COMPLETED_STATE : 'failed',\n code: rmuxOutcome.code,\n signal: rmuxOutcome.signal,\n },\n record.sessionId,\n );\n result.reclaimed.push(record.id);\n continue;\n }\n\n if (!dependencies.processControl.isAlive(record.pid)) {\n await dependencies.registry.complete(\n record.id,\n { reason: 'backend_lost', code: null, signal: null },\n record.sessionId,\n );\n result.reclaimed.push(record.id);\n continue;\n }\n\n const previousRuntime = dependencies.startup && record.hostPid === dependencies.currentHostPid;\n const ownerLost = !dependencies.processControl.isAlive(record.hostPid);\n if (!previousRuntime && !ownerLost) continue;\n\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend);\n if (!stopped && dependencies.processControl.isAlive(record.pid)) {\n result.errors.push(`Could not stop orphaned runner ${record.id}`);\n continue;\n }\n await dependencies.registry.complete(\n record.id,\n {\n reason: 'stopped',\n code: null,\n signal: 'SIGTERM',\n stopReason: 'owner session ended',\n },\n record.sessionId,\n );\n result.reclaimed.push(record.id);\n } catch (error) {\n result.errors.push(`Could not reconcile runner ${record.id}: ${String(error)}`);\n }\n }\n return result;\n}\n\nfunction isWithin(root: string, candidate: string): boolean {\n const relative = path.relative(path.resolve(root), path.resolve(candidate));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\ninterface LegacyCleanupDependencies extends Omit<ReconcileDependencies, 'startup'> {\n paths: IRunnerPaths;\n}\n\n/** Deletes the legacy Git store only after no live foreign runner uses it. */\nexport async function cleanupLegacyRunnerStore(\n dependencies: LegacyCleanupDependencies,\n): Promise<RunnerReconcileResult & { removed?: string }> {\n const result: RunnerReconcileResult & { removed?: string } = { reclaimed: [], errors: [] };\n const legacyDirectory = dependencies.paths.legacyDirectory();\n if (!legacyDirectory) return result;\n\n const legacyRecords = (await dependencies.registry.list()).filter((record) =>\n isWithin(legacyDirectory, record.logPath),\n );\n for (const record of legacyRecords) {\n const childAlive = dependencies.processControl.isAlive(record.pid);\n const ownerAlive = dependencies.processControl.isAlive(record.hostPid);\n const ownedByPreviousRuntime = record.hostPid === dependencies.currentHostPid;\n if (childAlive && ownerAlive && !ownedByPreviousRuntime) continue;\n try {\n if (childAlive) {\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend);\n if (!stopped && dependencies.processControl.isAlive(record.pid)) {\n result.errors.push(`Could not stop legacy runner ${record.id}`);\n continue;\n }\n }\n await dependencies.registry.release(record.id);\n result.reclaimed.push(record.id);\n } catch (error) {\n result.errors.push(`Could not reclaim legacy runner ${record.id}: ${String(error)}`);\n }\n }\n\n let liveDependencies: RunnerRecord[];\n try {\n liveDependencies = (await dependencies.registry.listAcrossRepositories()).filter(\n (record) => isWithin(legacyDirectory, record.logPath) && dependencies.processControl.isAlive(record.pid),\n );\n } catch (error) {\n result.errors.push(`Could not verify legacy runner dependencies: ${String(error)}`);\n return result;\n }\n if (liveDependencies.length === 0) result.removed = dependencies.paths.removeLegacyStore();\n return result;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"runnerSpec.cjs","names":["path","fs","fileURLToPath"],"sources":["../src/runnerSpec.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/** What a supervisor needs to run one command. */\nexport interface CommandSpec {\n command: string;\n cwd: string;\n env: Record<string, string>;\n}\n\n/** Exit status the supervisor records once its command is gone. */\nexport interface ExitMetadata {\n code: number | null;\n signal: NodeJS.Signals | null;\n}\n\n/** Sidecars a supervised run reads and writes beside its log. */\nexport interface SupervisorPaths {\n spec: string;\n gate: string;\n exit: string;\n logDone: string;\n}\n\n/**\n * Settings that make a terminal safe for a caller with no keyboard.\n *\n * Commands run inside an rmux pane, so their stdout is a real tty. Git and\n * friends take that as permission to open a pager, and `less` then waits at\n * `(END)` for a keypress nobody is there to send, so the run hangs until its\n * timeout. The same tty invites credential prompts, which hang the same way.\n * A conventional piped stdout disables both automatically; a pane has to be\n * told. A command that genuinely wants a pager can still ask for one, as in\n * `git -c core.pager=less log`.\n */\nexport const NO_TERMINAL_INPUT_ENV = {\n GIT_PAGER: 'cat',\n PAGER: 'cat',\n GIT_TERMINAL_PROMPT: '0',\n};\n\nconst SPEC_MODE = 0o600;\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\nexport function supervisorPaths(directory: string, id: string): SupervisorPaths {\n return {\n spec: path.join(directory, `${id}.command.json`),\n gate: path.join(directory, `${id}.gate`),\n exit: path.join(directory, `${id}.exit.json`),\n logDone: path.join(directory, `${id}.log.done`),\n };\n}\n\nexport function writeCommandSpec(target: string, spec: CommandSpec): void {\n fs.writeFileSync(target, `${JSON.stringify(spec)}\\n`, { mode: SPEC_MODE });\n}\n\nexport function readExitMetadata(target: string): ExitMetadata | undefined {\n try {\n const value: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (typeof value !== 'object' || value === null) return undefined;\n const metadata = value as Partial<ExitMetadata>;\n const code = typeof metadata.code === 'number' ? metadata.code : null;\n const signal = typeof metadata.signal === 'string' ? (metadata.signal as NodeJS.Signals) : null;\n return { code, signal };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n process.emitWarning(`Could not read runner exit metadata ${target}: ${String(error)}`);\n }\n return undefined;\n }\n}\n\nexport function cleanupSupervisorFiles(paths: SupervisorPaths): void {\n for (const target of Object.values(paths)) {\n try {\n fs.unlinkSync(target);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) process.emitWarning(String(error));\n }\n }\n}\n\n/** Resolves a bundled entry point in whichever form the package is running from. */\nexport function runtimeEntry(name: 'runnerHost' | 'logSink'): string {\n const extension = import.meta.url.endsWith('.ts') ? 'ts' : 'mjs';\n return fileURLToPath(new URL(`./bin/${name}.${extension}`, import.meta.url));\n}\n\n/**\n * Replaces the shell rather than nesting under it, so the pid handed back is the\n * supervisor's and a group signal reaches it directly.\n */\nexport function supervisorCommand(paths: SupervisorPaths): string {\n return `exec ${shellJoin([process.execPath, runtimeEntry('runnerHost'), paths.spec, paths.gate, paths.exit])}`;\n}\n\nexport function shellJoin(values: readonly string[]): string {\n return values.map((value) => `'${value.replaceAll(\"'\", `'\"'\"'`)}'`).join(' ');\n}\n"],"mappings":"uOAoCA,MAAa,EAAwB,CACnC,UAAW,MACX,MAAO,MACP,oBAAqB,GACvB,EAGM,EAAuB,SAE7B,SAAgB,EAAgB,EAAmB,EAA6B,CAC9E,MAAO,CACL,KAAMA,EAAAA,QAAK,KAAK,EAAW,GAAG,EAAG,cAAc,EAC/C,KAAMA,EAAAA,QAAK,KAAK,EAAW,GAAG,EAAG,MAAM,EACvC,KAAMA,EAAAA,QAAK,KAAK,EAAW,GAAG,EAAG,WAAW,EAC5C,QAASA,EAAAA,QAAK,KAAK,EAAW,GAAG,EAAG,UAAU,CAChD,CACF,CAEA,SAAgB,EAAiB,EAAgB,EAAyB,CACxE,EAAA,QAAG,cAAc,EAAQ,GAAG,KAAK,UAAU,CAAI,EAAE,IAAK,CAAE,KAAM,GAAU,CAAC,CAC3E,CAEA,SAAgB,EAAiB,EAA0C,CACzE,GAAI,CACF,IAAM,EAAiB,KAAK,MAAMC,EAAAA,QAAG,aAAa,EAAQ,MAAM,CAAC,EACjE,GAAI,OAAO,GAAU,WAAY,EAAgB,OACjD,IAAM,EAAW,EAGjB,MAAO,CAAE,KAFI,OAAO,EAAS,MAAS,SAAW,EAAS,KAAO,KAElD,OADA,OAAO,EAAS,QAAW,SAAY,EAAS,OAA4B,IACrE,CACxB,OAAS,EAAO,CACT,EAAgC,OAAS,GAC5C,QAAQ,YAAY,uCAAuC,EAAO,IAAI,OAAO,CAAK,GAAG,EAEvF,MACF,CACF,CAEA,SAAgB,EAAuB,EAA8B,CACnE,IAAK,IAAM,KAAU,OAAO,OAAO,CAAK,EACtC,GAAI,CACF,EAAA,QAAG,WAAW,CAAM,CACtB,OAAS,EAAO,CACT,EAAgC,OAAS,GAAsB,QAAQ,YAAY,OAAO,CAAK,CAAC,CACvG,CAEJ,CAGA,SAAgB,EAAa,EAAwC,CACnE,IAAM,EAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,KAA4B,SAAS,KAAK,EAAI,KAAO,MAC3D,OAAA,EAAOC,EAAAA,cAAAA,CAAc,IAAI,IAAI,SAAS,EAAK,GAAG,IAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,IAA4B,CAAC,CAC7E,CAMA,SAAgB,EAAkB,EAAgC,CAChE,MAAO,QAAQ,EAAU,CAAC,QAAQ,SAAU,EAAa,YAAY,EAAG,EAAM,KAAM,EAAM,KAAM,EAAM,IAAI,CAAC,GAC7G,CAEA,SAAgB,EAAU,EAAmC,CAC3D,OAAO,EAAO,IAAK,GAAU,IAAI,EAAM,WAAW,IAAK,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAC9E"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"runnerSpec.d.cts","names":[],"sources":["../src/runnerSpec.ts"],"mappings":";;UAKiB;EACf;EACA;EACA,KAAK;;;UAIU;EACf;EACA,QAAQ,OAAO;;;UAIA;EACf;EACA;EACA;EACA;;;;;;;;;;;;;cAcW;;;;;iBASG,gBAAgB,mBAAmB,aAAa;iBAShD,iBAAiB,gBAAgB,MAAM;iBAIvC,iBAAiB,iBAAiB;iBAgBlC,uBAAuB,OAAO;;iBAW9B,aAAa;;;;;iBASb,kBAAkB,OAAO;iBAIzB,UAAU"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"runnerSpec.d.mts","names":[],"sources":["../src/runnerSpec.ts"],"mappings":";;UAKiB;EACf;EACA;EACA,KAAK;;;UAIU;EACf;EACA,QAAQ,OAAO;;;UAIA;EACf;EACA;EACA;EACA;;;;;;;;;;;;;cAcW;;;;;iBASG,gBAAgB,mBAAmB,aAAa;iBAShD,iBAAiB,gBAAgB,MAAM;iBAIvC,iBAAiB,iBAAiB;iBAgBlC,uBAAuB,OAAO;;iBAW9B,aAAa;;;;;iBASb,kBAAkB,OAAO;iBAIzB,UAAU"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"runnerSpec.mjs","names":[],"sources":["../src/runnerSpec.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/** What a supervisor needs to run one command. */\nexport interface CommandSpec {\n command: string;\n cwd: string;\n env: Record<string, string>;\n}\n\n/** Exit status the supervisor records once its command is gone. */\nexport interface ExitMetadata {\n code: number | null;\n signal: NodeJS.Signals | null;\n}\n\n/** Sidecars a supervised run reads and writes beside its log. */\nexport interface SupervisorPaths {\n spec: string;\n gate: string;\n exit: string;\n logDone: string;\n}\n\n/**\n * Settings that make a terminal safe for a caller with no keyboard.\n *\n * Commands run inside an rmux pane, so their stdout is a real tty. Git and\n * friends take that as permission to open a pager, and `less` then waits at\n * `(END)` for a keypress nobody is there to send, so the run hangs until its\n * timeout. The same tty invites credential prompts, which hang the same way.\n * A conventional piped stdout disables both automatically; a pane has to be\n * told. A command that genuinely wants a pager can still ask for one, as in\n * `git -c core.pager=less log`.\n */\nexport const NO_TERMINAL_INPUT_ENV = {\n GIT_PAGER: 'cat',\n PAGER: 'cat',\n GIT_TERMINAL_PROMPT: '0',\n};\n\nconst SPEC_MODE = 0o600;\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\nexport function supervisorPaths(directory: string, id: string): SupervisorPaths {\n return {\n spec: path.join(directory, `${id}.command.json`),\n gate: path.join(directory, `${id}.gate`),\n exit: path.join(directory, `${id}.exit.json`),\n logDone: path.join(directory, `${id}.log.done`),\n };\n}\n\nexport function writeCommandSpec(target: string, spec: CommandSpec): void {\n fs.writeFileSync(target, `${JSON.stringify(spec)}\\n`, { mode: SPEC_MODE });\n}\n\nexport function readExitMetadata(target: string): ExitMetadata | undefined {\n try {\n const value: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (typeof value !== 'object' || value === null) return undefined;\n const metadata = value as Partial<ExitMetadata>;\n const code = typeof metadata.code === 'number' ? metadata.code : null;\n const signal = typeof metadata.signal === 'string' ? (metadata.signal as NodeJS.Signals) : null;\n return { code, signal };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n process.emitWarning(`Could not read runner exit metadata ${target}: ${String(error)}`);\n }\n return undefined;\n }\n}\n\nexport function cleanupSupervisorFiles(paths: SupervisorPaths): void {\n for (const target of Object.values(paths)) {\n try {\n fs.unlinkSync(target);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) process.emitWarning(String(error));\n }\n }\n}\n\n/** Resolves a bundled entry point in whichever form the package is running from. */\nexport function runtimeEntry(name: 'runnerHost' | 'logSink'): string {\n const extension = import.meta.url.endsWith('.ts') ? 'ts' : 'mjs';\n return fileURLToPath(new URL(`./bin/${name}.${extension}`, import.meta.url));\n}\n\n/**\n * Replaces the shell rather than nesting under it, so the pid handed back is the\n * supervisor's and a group signal reaches it directly.\n */\nexport function supervisorCommand(paths: SupervisorPaths): string {\n return `exec ${shellJoin([process.execPath, runtimeEntry('runnerHost'), paths.spec, paths.gate, paths.exit])}`;\n}\n\nexport function shellJoin(values: readonly string[]): string {\n return values.map((value) => `'${value.replaceAll(\"'\", `'\"'\"'`)}'`).join(' ');\n}\n"],"mappings":"yFAoCA,MAAa,EAAwB,CACnC,UAAW,MACX,MAAO,MACP,oBAAqB,GACvB,EAGM,EAAuB,SAE7B,SAAgB,EAAgB,EAAmB,EAA6B,CAC9E,MAAO,CACL,KAAM,EAAK,KAAK,EAAW,GAAG,EAAG,cAAc,EAC/C,KAAM,EAAK,KAAK,EAAW,GAAG,EAAG,MAAM,EACvC,KAAM,EAAK,KAAK,EAAW,GAAG,EAAG,WAAW,EAC5C,QAAS,EAAK,KAAK,EAAW,GAAG,EAAG,UAAU,CAChD,CACF,CAEA,SAAgB,EAAiB,EAAgB,EAAyB,CACxE,EAAG,cAAc,EAAQ,GAAG,KAAK,UAAU,CAAI,EAAE,IAAK,CAAE,KAAM,GAAU,CAAC,CAC3E,CAEA,SAAgB,EAAiB,EAA0C,CACzE,GAAI,CACF,IAAM,EAAiB,KAAK,MAAM,EAAG,aAAa,EAAQ,MAAM,CAAC,EACjE,GAAI,OAAO,GAAU,WAAY,EAAgB,OACjD,IAAM,EAAW,EAGjB,MAAO,CAAE,KAFI,OAAO,EAAS,MAAS,SAAW,EAAS,KAAO,KAElD,OADA,OAAO,EAAS,QAAW,SAAY,EAAS,OAA4B,IACrE,CACxB,OAAS,EAAO,CACT,EAAgC,OAAS,GAC5C,QAAQ,YAAY,uCAAuC,EAAO,IAAI,OAAO,CAAK,GAAG,EAEvF,MACF,CACF,CAEA,SAAgB,EAAuB,EAA8B,CACnE,IAAK,IAAM,KAAU,OAAO,OAAO,CAAK,EACtC,GAAI,CACF,EAAG,WAAW,CAAM,CACtB,OAAS,EAAO,CACT,EAAgC,OAAS,GAAsB,QAAQ,YAAY,OAAO,CAAK,CAAC,CACvG,CAEJ,CAGA,SAAgB,EAAa,EAAwC,CACnE,IAAM,EAAY,YAAY,IAAI,SAAS,KAAK,EAAI,KAAO,MAC3D,OAAO,EAAc,IAAI,IAAI,SAAS,EAAK,GAAG,IAAa,YAAY,GAAG,CAAC,CAC7E,CAMA,SAAgB,EAAkB,EAAgC,CAChE,MAAO,QAAQ,EAAU,CAAC,QAAQ,SAAU,EAAa,YAAY,EAAG,EAAM,KAAM,EAAM,KAAM,EAAM,IAAI,CAAC,GAC7G,CAEA,SAAgB,EAAU,EAAmC,CAC3D,OAAO,EAAO,IAAK,GAAU,IAAI,EAAM,WAAW,IAAK,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAC9E"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"RunnerPaths.cjs","names":["execFileSync","fs","os","path","LOG_DIR_ENV","inheritedSessionId","injectable"],"sources":["../../../src/services/RunnerPaths/RunnerPaths.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { LOG_DIR_ENV } from '../../config.ts';\nimport { inheritedSessionId } from '../../session.ts';\nimport type { IRunnerPaths, LogSweepResult } from './types.ts';\n\nconst STORE_DIR_NAME = 'doom-runner';\nconst DEFAULT_CONFIG_DIR_NAME = '.pi';\nconst AGENT_DIR_NAME = 'agent';\nconst LOG_DIR_NAME = 'logs';\nconst STATE_DIR_NAME = 'runs';\nconst LOG_EXTENSION = '.log';\nconst STATE_EXTENSION = '.json';\nconst ROTATED_SUFFIX = '.1';\nconst PI_CODING_AGENT_DIR_ENV = 'PI_CODING_AGENT_DIR';\nconst HOME_ALIAS = '~';\nconst HOME_ALIAS_PREFIX = '~/';\nconst FILE_ENCODING = 'utf8';\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\nfunction git(args: string[], cwd: string): string | undefined {\n try {\n const output = execFileSync('git', args, { cwd, encoding: FILE_ENCODING, stdio: ['ignore', 'pipe', 'ignore'] });\n const trimmed = output.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return target;\n }\n}\n\nfunction resolveAgentDirectory(env: NodeJS.ProcessEnv): string {\n const configured = env[PI_CODING_AGENT_DIR_ENV]?.trim();\n if (configured === HOME_ALIAS) return os.homedir();\n if (configured?.startsWith(HOME_ALIAS_PREFIX)) {\n return path.join(os.homedir(), configured.slice(HOME_ALIAS_PREFIX.length));\n }\n return configured ? path.resolve(configured) : path.join(os.homedir(), DEFAULT_CONFIG_DIR_NAME, AGENT_DIR_NAME);\n}\n\n/**\n * Newest mtime anywhere one level inside the session, falling back to the\n * directory itself. A session still being written to therefore always looks\n * recent, whatever state its individual records are in.\n */\nfunction newestEntryTime(directory: string): number {\n let newest = fs.statSync(directory).mtimeMs;\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\n const target = path.join(directory, entry.name);\n newest = Math.max(newest, fs.statSync(target).mtimeMs);\n if (!entry.isDirectory()) continue;\n for (const child of fs.readdirSync(target)) {\n newest = Math.max(newest, fs.statSync(path.join(target, child)).mtimeMs);\n }\n }\n return newest;\n}\n\n/**\n * Whether any run in this session names a pi process that is still running.\n *\n * One live record is enough: records are only ever written by their owner, so a\n * live host pid means the session is still in use however old its files look.\n */\nfunction ownerAlive(stateDirectory: string): boolean {\n let entries: string[];\n try {\n entries = fs.readdirSync(stateDirectory);\n } catch {\n // No records left to consult, so nothing claims the directory.\n return false;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith(STATE_EXTENSION)) continue;\n try {\n const value: unknown = JSON.parse(fs.readFileSync(path.join(stateDirectory, entry), FILE_ENCODING));\n const hostPid = (value as { hostPid?: unknown }).hostPid;\n if (typeof hostPid !== 'number') continue;\n process.kill(hostPid, 0);\n return true;\n } catch {\n // An unreadable record and a dead pid mean the same thing here: no claim.\n }\n }\n return false;\n}\n\nfunction completedAt(value: unknown): number | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const record = value as { state?: unknown; exit?: { finishedAt?: unknown } };\n if (record.state !== 'completed' || typeof record.exit?.finishedAt !== 'string') return undefined;\n const timestamp = Date.parse(record.exit.finishedAt);\n return Number.isFinite(timestamp) ? timestamp : undefined;\n}\n\n/** Session-scoped runner storage under the Pi agent directory. */\n@injectable()\nexport class RunnerPaths implements IRunnerPaths {\n private adoptedSessionId: string | undefined;\n private repository: string | undefined;\n\n repositoryPath(): string {\n this.repository ??= realPath(git(['rev-parse', '--show-toplevel'], process.cwd()) ?? process.cwd());\n return this.repository;\n }\n\n setSessionId(sessionId: string): void {\n const trimmed = sessionId.trim();\n if (!trimmed) throw new Error('doom-runner session id cannot be blank');\n this.adoptedSessionId = trimmed;\n }\n\n logDirectory(sessionId?: string): string {\n const override = process.env[LOG_DIR_ENV]?.trim();\n if (override) return path.resolve(override);\n return path.join(this.sessionDirectory(sessionId), LOG_DIR_NAME);\n }\n\n stateDirectory(sessionId?: string): string {\n const override = process.env[LOG_DIR_ENV]?.trim();\n if (override) return path.join(path.resolve(override), '..', STATE_DIR_NAME);\n return path.join(this.sessionDirectory(sessionId), STATE_DIR_NAME);\n }\n\n logPathFor(id: string, sessionId?: string): string {\n return path.join(this.logDirectory(sessionId), `${id}${LOG_EXTENSION}`);\n }\n\n rotatedLogPathFor(id: string, sessionId?: string): string {\n return `${this.logPathFor(id, sessionId)}${ROTATED_SUFFIX}`;\n }\n\n statePathFor(id: string, sessionId?: string): string {\n return path.join(this.stateDirectory(sessionId), `${id}${STATE_EXTENSION}`);\n }\n\n ensureDirectories(sessionId?: string): void {\n fs.mkdirSync(this.logDirectory(sessionId), { recursive: true });\n fs.mkdirSync(this.stateDirectory(sessionId), { recursive: true });\n }\n\n sweepHistory(ttlMs: number, now: number = Date.now()): LogSweepResult {\n const result: LogSweepResult = { removed: [], errors: [] };\n // A pinned log directory belongs to whoever pinned it, so that branch only\n // ever removes the files this package put there.\n if (process.env[LOG_DIR_ENV]?.trim()) {\n this.sweepStateDirectory(this.stateDirectory(), this.sessionId(), ttlMs, now, result);\n return result;\n }\n const root = this.storeDirectory();\n let sessions: string[];\n try {\n sessions = fs.readdirSync(root);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === NOT_FOUND_ERROR_CODE) return result;\n result.errors.push(`Could not read runner history directory ${root}: ${String(error)}`);\n return result;\n }\n\n // Sweeping whole session directories rather than the three files a completed\n // record names is what keeps sockets, sidecars, runs that never reached a\n // terminal state, and the emptied directories themselves from accumulating.\n const active = this.currentSessionId();\n for (const sessionId of sessions) {\n if (sessionId === active) continue;\n this.sweepSessionDirectory(path.join(root, sessionId), ttlMs, now, result);\n }\n return result;\n }\n\n legacyDirectory(): string | undefined {\n const commonDirectory = git(['rev-parse', '--path-format=absolute', '--git-common-dir'], process.cwd());\n return commonDirectory ? path.join(commonDirectory, STORE_DIR_NAME) : undefined;\n }\n\n removeLegacyStore(): string | undefined {\n const target = this.legacyDirectory();\n if (!target || path.basename(target) !== STORE_DIR_NAME || !fs.existsSync(target)) return undefined;\n fs.rmSync(target, { recursive: true, force: true });\n return target;\n }\n\n /** The live session, if one is known, so a sweep never deletes underneath itself. */\n private currentSessionId(): string | undefined {\n try {\n return this.sessionId();\n } catch {\n // A CLI invocation outside a session still sweeps; it just has nothing to spare.\n return undefined;\n }\n }\n\n /** Removes a session's whole directory once its newest entry is past the TTL. */\n private sweepSessionDirectory(directory: string, ttlMs: number, now: number, result: LogSweepResult): void {\n try {\n if (now - newestEntryTime(directory) <= ttlMs) return;\n // An idle session can go quiet for longer than the TTL, and deleting one\n // takes its lifeline socket with it, which would read to its own runners\n // as an owner that had died. Its records name the owner, so ask.\n if (ownerAlive(path.join(directory, STATE_DIR_NAME))) return;\n fs.rmSync(directory, { recursive: true, force: true });\n result.removed.push(directory);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n result.errors.push(`Could not sweep runner session ${directory}: ${String(error)}`);\n }\n }\n }\n\n private sessionId(explicit?: string): string {\n const sessionId = explicit?.trim() || this.adoptedSessionId || inheritedSessionId();\n if (!sessionId) throw new Error('PI_SESSION_ID is required for doom-runner storage');\n return sessionId;\n }\n\n private storeDirectory(): string {\n const override = process.env[LOG_DIR_ENV]?.trim();\n if (override) return path.dirname(path.resolve(override));\n return path.join(resolveAgentDirectory(process.env), STORE_DIR_NAME);\n }\n\n private sessionDirectory(sessionId?: string): string {\n return path.join(this.storeDirectory(), this.sessionId(sessionId));\n }\n\n private sweepStateDirectory(\n directory: string,\n sessionId: string,\n ttlMs: number,\n now: number,\n result: LogSweepResult,\n ): void {\n let entries: string[];\n try {\n entries = fs.readdirSync(directory);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n result.errors.push(`Could not read runner state directory ${directory}: ${String(error)}`);\n }\n return;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith(STATE_EXTENSION)) continue;\n const statePath = path.join(directory, entry);\n try {\n const value: unknown = JSON.parse(fs.readFileSync(statePath, FILE_ENCODING));\n const finishedAt = completedAt(value);\n if (finishedAt === undefined || now - finishedAt <= ttlMs) continue;\n const id = entry.slice(0, -STATE_EXTENSION.length);\n this.removeHistoryFiles(sessionId, id, statePath, result);\n } catch (error) {\n result.errors.push(`Could not sweep runner metadata ${statePath}: ${String(error)}`);\n }\n }\n }\n\n private removeHistoryFiles(sessionId: string, id: string, statePath: string, result: LogSweepResult): void {\n const candidates = [statePath, this.logPathFor(id, sessionId), this.rotatedLogPathFor(id, sessionId)];\n for (const candidate of candidates) {\n try {\n fs.unlinkSync(candidate);\n result.removed.push(candidate);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n result.errors.push(`Could not remove runner history ${candidate}: ${String(error)}`);\n }\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"RunnerPaths.d.cts","names":[],"sources":["../../../src/services/RunnerPaths/RunnerPaths.ts"],"mappings":";;;;cA6Ga,uBAAuB;UAC1B;UACA;EAER;EAKA,aAAa;EAMb,aAAa;EAMb,eAAe;EAMf,WAAW,YAAY;EAIvB,kBAAkB,YAAY;EAI9B,aAAa,YAAY;EAIzB,kBAAkB;EAKlB,aAAa,eAAe,eAA2B;EA6BvD;EAKA;;UAQQ;;UAUA;UAgBA;UAMA;UAMA;UAIA;UAgCA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"RunnerPaths.d.mts","names":[],"sources":["../../../src/services/RunnerPaths/RunnerPaths.ts"],"mappings":";;;;cA6Ga,uBAAuB;UAC1B;UACA;EAER;EAKA,aAAa;EAMb,aAAa;EAMb,eAAe;EAMf,WAAW,YAAY;EAIvB,kBAAkB,YAAY;EAI9B,aAAa,YAAY;EAIzB,kBAAkB;EAKlB,aAAa,eAAe,eAA2B;EA6BvD;EAKA;;UAQQ;;UAUA;UAgBA;UAMA;UAMA;UAIA;UAgCA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"RunnerPaths.mjs","names":[],"sources":["../../../src/services/RunnerPaths/RunnerPaths.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { LOG_DIR_ENV } from '../../config.ts';\nimport { inheritedSessionId } from '../../session.ts';\nimport type { IRunnerPaths, LogSweepResult } from './types.ts';\n\nconst STORE_DIR_NAME = 'doom-runner';\nconst DEFAULT_CONFIG_DIR_NAME = '.pi';\nconst AGENT_DIR_NAME = 'agent';\nconst LOG_DIR_NAME = 'logs';\nconst STATE_DIR_NAME = 'runs';\nconst LOG_EXTENSION = '.log';\nconst STATE_EXTENSION = '.json';\nconst ROTATED_SUFFIX = '.1';\nconst PI_CODING_AGENT_DIR_ENV = 'PI_CODING_AGENT_DIR';\nconst HOME_ALIAS = '~';\nconst HOME_ALIAS_PREFIX = '~/';\nconst FILE_ENCODING = 'utf8';\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\nfunction git(args: string[], cwd: string): string | undefined {\n try {\n const output = execFileSync('git', args, { cwd, encoding: FILE_ENCODING, stdio: ['ignore', 'pipe', 'ignore'] });\n const trimmed = output.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return target;\n }\n}\n\nfunction resolveAgentDirectory(env: NodeJS.ProcessEnv): string {\n const configured = env[PI_CODING_AGENT_DIR_ENV]?.trim();\n if (configured === HOME_ALIAS) return os.homedir();\n if (configured?.startsWith(HOME_ALIAS_PREFIX)) {\n return path.join(os.homedir(), configured.slice(HOME_ALIAS_PREFIX.length));\n }\n return configured ? path.resolve(configured) : path.join(os.homedir(), DEFAULT_CONFIG_DIR_NAME, AGENT_DIR_NAME);\n}\n\n/**\n * Newest mtime anywhere one level inside the session, falling back to the\n * directory itself. A session still being written to therefore always looks\n * recent, whatever state its individual records are in.\n */\nfunction newestEntryTime(directory: string): number {\n let newest = fs.statSync(directory).mtimeMs;\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\n const target = path.join(directory, entry.name);\n newest = Math.max(newest, fs.statSync(target).mtimeMs);\n if (!entry.isDirectory()) continue;\n for (const child of fs.readdirSync(target)) {\n newest = Math.max(newest, fs.statSync(path.join(target, child)).mtimeMs);\n }\n }\n return newest;\n}\n\n/**\n * Whether any run in this session names a pi process that is still running.\n *\n * One live record is enough: records are only ever written by their owner, so a\n * live host pid means the session is still in use however old its files look.\n */\nfunction ownerAlive(stateDirectory: string): boolean {\n let entries: string[];\n try {\n entries = fs.readdirSync(stateDirectory);\n } catch {\n // No records left to consult, so nothing claims the directory.\n return false;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith(STATE_EXTENSION)) continue;\n try {\n const value: unknown = JSON.parse(fs.readFileSync(path.join(stateDirectory, entry), FILE_ENCODING));\n const hostPid = (value as { hostPid?: unknown }).hostPid;\n if (typeof hostPid !== 'number') continue;\n process.kill(hostPid, 0);\n return true;\n } catch {\n // An unreadable record and a dead pid mean the same thing here: no claim.\n }\n }\n return false;\n}\n\nfunction completedAt(value: unknown): number | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const record = value as { state?: unknown; exit?: { finishedAt?: unknown } };\n if (record.state !== 'completed' || typeof record.exit?.finishedAt !== 'string') return undefined;\n const timestamp = Date.parse(record.exit.finishedAt);\n return Number.isFinite(timestamp) ? timestamp : undefined;\n}\n\n/** Session-scoped runner storage under the Pi agent directory. */\n@injectable()\nexport class RunnerPaths implements IRunnerPaths {\n private adoptedSessionId: string | undefined;\n private repository: string | undefined;\n\n repositoryPath(): string {\n this.repository ??= realPath(git(['rev-parse', '--show-toplevel'], process.cwd()) ?? process.cwd());\n return this.repository;\n }\n\n setSessionId(sessionId: string): void {\n const trimmed = sessionId.trim();\n if (!trimmed) throw new Error('doom-runner session id cannot be blank');\n this.adoptedSessionId = trimmed;\n }\n\n logDirectory(sessionId?: string): string {\n const override = process.env[LOG_DIR_ENV]?.trim();\n if (override) return path.resolve(override);\n return path.join(this.sessionDirectory(sessionId), LOG_DIR_NAME);\n }\n\n stateDirectory(sessionId?: string): string {\n const override = process.env[LOG_DIR_ENV]?.trim();\n if (override) return path.join(path.resolve(override), '..', STATE_DIR_NAME);\n return path.join(this.sessionDirectory(sessionId), STATE_DIR_NAME);\n }\n\n logPathFor(id: string, sessionId?: string): string {\n return path.join(this.logDirectory(sessionId), `${id}${LOG_EXTENSION}`);\n }\n\n rotatedLogPathFor(id: string, sessionId?: string): string {\n return `${this.logPathFor(id, sessionId)}${ROTATED_SUFFIX}`;\n }\n\n statePathFor(id: string, sessionId?: string): string {\n return path.join(this.stateDirectory(sessionId), `${id}${STATE_EXTENSION}`);\n }\n\n ensureDirectories(sessionId?: string): void {\n fs.mkdirSync(this.logDirectory(sessionId), { recursive: true });\n fs.mkdirSync(this.stateDirectory(sessionId), { recursive: true });\n }\n\n sweepHistory(ttlMs: number, now: number = Date.now()): LogSweepResult {\n const result: LogSweepResult = { removed: [], errors: [] };\n // A pinned log directory belongs to whoever pinned it, so that branch only\n // ever removes the files this package put there.\n if (process.env[LOG_DIR_ENV]?.trim()) {\n this.sweepStateDirectory(this.stateDirectory(), this.sessionId(), ttlMs, now, result);\n return result;\n }\n const root = this.storeDirectory();\n let sessions: string[];\n try {\n sessions = fs.readdirSync(root);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === NOT_FOUND_ERROR_CODE) return result;\n result.errors.push(`Could not read runner history directory ${root}: ${String(error)}`);\n return result;\n }\n\n // Sweeping whole session directories rather than the three files a completed\n // record names is what keeps sockets, sidecars, runs that never reached a\n // terminal state, and the emptied directories themselves from accumulating.\n const active = this.currentSessionId();\n for (const sessionId of sessions) {\n if (sessionId === active) continue;\n this.sweepSessionDirectory(path.join(root, sessionId), ttlMs, now, result);\n }\n return result;\n }\n\n legacyDirectory(): string | undefined {\n const commonDirectory = git(['rev-parse', '--path-format=absolute', '--git-common-dir'], process.cwd());\n return commonDirectory ? path.join(commonDirectory, STORE_DIR_NAME) : undefined;\n }\n\n removeLegacyStore(): string | undefined {\n const target = this.legacyDirectory();\n if (!target || path.basename(target) !== STORE_DIR_NAME || !fs.existsSync(target)) return undefined;\n fs.rmSync(target, { recursive: true, force: true });\n return target;\n }\n\n /** The live session, if one is known, so a sweep never deletes underneath itself. */\n private currentSessionId(): string | undefined {\n try {\n return this.sessionId();\n } catch {\n // A CLI invocation outside a session still sweeps; it just has nothing to spare.\n return undefined;\n }\n }\n\n /** Removes a session's whole directory once its newest entry is past the TTL. */\n private sweepSessionDirectory(directory: string, ttlMs: number, now: number, result: LogSweepResult): void {\n try {\n if (now - newestEntryTime(directory) <= ttlMs) return;\n // An idle session can go quiet for longer than the TTL, and deleting one\n // takes its lifeline socket with it, which would read to its own runners\n // as an owner that had died. Its records name the owner, so ask.\n if (ownerAlive(path.join(directory, STATE_DIR_NAME))) return;\n fs.rmSync(directory, { recursive: true, force: true });\n result.removed.push(directory);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n result.errors.push(`Could not sweep runner session ${directory}: ${String(error)}`);\n }\n }\n }\n\n private sessionId(explicit?: string): string {\n const sessionId = explicit?.trim() || this.adoptedSessionId || inheritedSessionId();\n if (!sessionId) throw new Error('PI_SESSION_ID is required for doom-runner storage');\n return sessionId;\n }\n\n private storeDirectory(): string {\n const override = process.env[LOG_DIR_ENV]?.trim();\n if (override) return path.dirname(path.resolve(override));\n return path.join(resolveAgentDirectory(process.env), STORE_DIR_NAME);\n }\n\n private sessionDirectory(sessionId?: string): string {\n return path.join(this.storeDirectory(), this.sessionId(sessionId));\n }\n\n private sweepStateDirectory(\n directory: string,\n sessionId: string,\n ttlMs: number,\n now: number,\n result: LogSweepResult,\n ): void {\n let entries: string[];\n try {\n entries = fs.readdirSync(directory);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n result.errors.push(`Could not read runner state directory ${directory}: ${String(error)}`);\n }\n return;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith(STATE_EXTENSION)) continue;\n const statePath = path.join(directory, entry);\n try {\n const value: unknown = JSON.parse(fs.readFileSync(statePath, FILE_ENCODING));\n const finishedAt = completedAt(value);\n if (finishedAt === undefined || now - finishedAt <= ttlMs) continue;\n const id = entry.slice(0, -STATE_EXTENSION.length);\n this.removeHistoryFiles(sessionId, id, statePath, result);\n } catch (error) {\n result.errors.push(`Could not sweep runner metadata ${statePath}: ${String(error)}`);\n }\n }\n }\n\n private removeHistoryFiles(sessionId: string, id: string, statePath: string, result: LogSweepResult): void {\n const candidates = [statePath, this.logPathFor(id, sessionId), this.rotatedLogPathFor(id, sessionId)];\n for (const candidate of candidates) {\n try {\n fs.unlinkSync(candidate);\n result.removed.push(candidate);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n result.errors.push(`Could not remove runner history ${candidate}: ${String(error)}`);\n }\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.cjs","names":[],"sources":["../src/session.ts"],"sourcesContent":["/** Session identity inherited by commands launched from a Pi session. */\nexport const PI_SESSION_ID_ENV = 'PI_SESSION_ID';\n\n/** Returns a usable inherited session id, failing closed on blank values. */\nexport function inheritedSessionId(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const value = env[PI_SESSION_ID_ENV]?.trim();\n return value ? value : undefined;\n}\n\n/** Returns the inherited Pi session id or fails closed outside a session. */\nexport function requiredSessionId(env: NodeJS.ProcessEnv = process.env): string {\n const sessionId = inheritedSessionId(env);\n if (!sessionId) throw new Error(`${PI_SESSION_ID_ENV} is required for doom-runner`);\n return sessionId;\n}\n"],"mappings":"mEACA,MAAa,EAAoB,gBAGjC,SAAgB,EAAmB,EAAyB,QAAQ,IAAyB,CAE3F,OADc,EAAI,EAAkB,EAAE,KAAK,GACpB,IAAA,EACzB,CAGA,SAAgB,EAAkB,EAAyB,QAAQ,IAAa,CAC9E,IAAM,EAAY,EAAmB,CAAG,EACxC,GAAI,CAAC,EAAW,MAAU,MAAM,GAAG,EAAkB,6BAA6B,EAClF,OAAO,CACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.d.cts","names":[],"sources":["../src/session.ts"],"mappings":";;cACa;;iBAGG,mBAAmB,MAAK,OAAO;;iBAM/B,kBAAkB,MAAK,OAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.d.mts","names":[],"sources":["../src/session.ts"],"mappings":";;cACa;;iBAGG,mBAAmB,MAAK,OAAO;;iBAM/B,kBAAkB,MAAK,OAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.mjs","names":[],"sources":["../src/session.ts"],"sourcesContent":["/** Session identity inherited by commands launched from a Pi session. */\nexport const PI_SESSION_ID_ENV = 'PI_SESSION_ID';\n\n/** Returns a usable inherited session id, failing closed on blank values. */\nexport function inheritedSessionId(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const value = env[PI_SESSION_ID_ENV]?.trim();\n return value ? value : undefined;\n}\n\n/** Returns the inherited Pi session id or fails closed outside a session. */\nexport function requiredSessionId(env: NodeJS.ProcessEnv = process.env): string {\n const sessionId = inheritedSessionId(env);\n if (!sessionId) throw new Error(`${PI_SESSION_ID_ENV} is required for doom-runner`);\n return sessionId;\n}\n"],"mappings":"AACA,MAAa,EAAoB,gBAGjC,SAAgB,EAAmB,EAAyB,QAAQ,IAAyB,CAE3F,OADc,EAAI,EAAkB,EAAE,KAAK,GACpB,IAAA,EACzB,CAGA,SAAgB,EAAkB,EAAyB,QAAQ,IAAa,CAC9E,IAAM,EAAY,EAAmB,CAAG,EACxC,GAAI,CAAC,EAAW,MAAU,MAAM,GAAG,EAAkB,6BAA6B,EAClF,OAAO,CACT"}