@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
@@ -23,6 +23,8 @@ interface IRunnerPaths {
23
23
  * Running records are never removed.
24
24
  */
25
25
  sweepHistory(ttlMs: number, now?: number): LogSweepResult;
26
+ /** Async startup-safe form of history retention cleanup. */
27
+ sweepHistoryAsync?(ttlMs: number, now?: number): Promise<LogSweepResult>;
26
28
  /** Exact legacy Git-directory store, if this repository has one. */
27
29
  legacyDirectory(): string | undefined;
28
30
  /** Removes only the validated legacy doom-runner directory. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.cts","names":[],"sources":["../../../src/services/RunnerPaths/types.ts"],"mappings":";UAAiB;EACf;EACA;;;UAIe;;EAEf;;EAEA,aAAa;;EAEb,aAAa;;EAEb,eAAe;EACf,WAAW,YAAY;;EAEvB,kBAAkB,YAAY;EAC9B,aAAa,YAAY;EACzB,kBAAkB;;;;;EAKlB,aAAa,eAAe,eAAe;;EAE3C;;EAEA"}
1
+ {"version":3,"file":"types.d.cts","names":[],"sources":["../../../src/services/RunnerPaths/types.ts"],"mappings":";UAAiB;EACf;EACA;;;UAIe;;EAEf;;EAEA,aAAa;;EAEb,aAAa;;EAEb,eAAe;EACf,WAAW,YAAY;;EAEvB,kBAAkB,YAAY;EAC9B,aAAa,YAAY;EACzB,kBAAkB;;;;;EAKlB,aAAa,eAAe,eAAe;;EAE3C,mBAAmB,eAAe,eAAe,QAAQ;;EAEzD;;EAEA"}
@@ -23,6 +23,8 @@ interface IRunnerPaths {
23
23
  * Running records are never removed.
24
24
  */
25
25
  sweepHistory(ttlMs: number, now?: number): LogSweepResult;
26
+ /** Async startup-safe form of history retention cleanup. */
27
+ sweepHistoryAsync?(ttlMs: number, now?: number): Promise<LogSweepResult>;
26
28
  /** Exact legacy Git-directory store, if this repository has one. */
27
29
  legacyDirectory(): string | undefined;
28
30
  /** Removes only the validated legacy doom-runner directory. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/services/RunnerPaths/types.ts"],"mappings":";UAAiB;EACf;EACA;;;UAIe;;EAEf;;EAEA,aAAa;;EAEb,aAAa;;EAEb,eAAe;EACf,WAAW,YAAY;;EAEvB,kBAAkB,YAAY;EAC9B,aAAa,YAAY;EACzB,kBAAkB;;;;;EAKlB,aAAa,eAAe,eAAe;;EAE3C;;EAEA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/services/RunnerPaths/types.ts"],"mappings":";UAAiB;EACf;EACA;;;UAIe;;EAEf;;EAEA,aAAa;;EAEb,aAAa;;EAEb,eAAe;EACf,WAAW,YAAY;;EAEvB,kBAAkB,YAAY;EAC9B,aAAa,YAAY;EACzB,kBAAkB;;;;;EAKlB,aAAa,eAAe,eAAe;;EAE3C,mBAAmB,eAAe,eAAe,QAAQ;;EAEzD;;EAEA"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../types.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs");let a=require("inversify");require("reflect-metadata/lite");let o=require("node:fs");o=e.__toESM(o,1);let s=require("node:path");s=e.__toESM(s,1);let c=require("@agimon-ai/doompi-extension-contracts/child-process"),l=require("@agimon-ai/foundation-process-registry");const u=`tool`,d=`doom-runner:`,f=`doom-runner`;function p(){return(0,l.createProcessRegistryService)(process.env.PROCESS_REGISTRY_PATH)}function m(e){return`session:${e}`}function h(e,t){let n=e.metadata?.[t];return typeof n==`string`?n:``}function g(e,t){let n=e.metadata?.[t];return typeof n==`number`?n:0}let _=class{paths;processControl;registry;listeners=new Set;constructor(e,t,n){this.paths=e,this.processControl=t,this.registry=n}async register(e){let t=new Date().toISOString(),n=(0,c.resolveRootSessionId)(e.sessionId),r=await this.registry.registerProcess({repositoryPath:this.repositoryPath(),serviceName:`${d}${e.id}`,serviceType:u,pid:e.pid,command:e.command,tags:(0,l.resolveProcessTags)([f,`runner:${e.id}`,m(e.sessionId)]),metadata:{id:e.id,name:e.name,command:e.command,cwd:e.cwd,logPath:e.logPath,interactive:e.interactive,sessionId:e.sessionId,rootSessionId:n,backend:e.backend,...e.backendTarget?{backendTarget:e.backendTarget}:{},startedAt:t,hostPid:process.pid},force:!0});if(!r.success)throw Error(r.error??`Failed to register runner ${e.name}`);let i={id:e.id,name:e.name,pid:e.pid,command:e.command,cwd:e.cwd,logPath:e.logPath,interactive:e.interactive,sessionId:e.sessionId,rootSessionId:n,startedAt:t,state:`running`,promoted:!1,backend:e.backend,...e.backendTarget?{backendTarget:e.backendTarget}:{},...e.alarmMs?{alarm:{intervalMs:e.alarmMs,lastFiredAt:t}}:{},hostPid:process.pid};return this.writeRecord(i),this.notify(),i}async list(){return(await this.registry.listProcesses({repositoryPath:this.repositoryPath(),serviceType:u,tags:(0,l.resolveProcessTags)([f])})).map(v)}async listAcrossRepositories(){return(await this.registry.listProcesses({serviceType:u,tags:(0,l.resolveProcessTags)([f])})).map(v)}async listBySession(e){return(await this.list()).filter(t=>t.sessionId===e)}async listByRootSession(e){return(await this.list()).filter(t=>(t.rootSessionId??t.sessionId)===e)}async listAll(e){this.paths.ensureDirectories(e);let t=[],n=this.paths.stateDirectory(e);for(let e of o.default.readdirSync(n))if(b(e))try{let r=JSON.parse(o.default.readFileSync(`${n}/${e}`,`utf8`));x(r)&&t.push(r)}catch(t){process.emitWarning(`Could not read runner metadata ${e}: ${String(t)}`)}return t.sort((e,t)=>t.startedAt.localeCompare(e.startedAt))}async get(e,t){let n=this.paths.statePathFor(e,t);try{let t=JSON.parse(o.default.readFileSync(n,`utf8`));if(!x(t)||t.id!==e){process.emitWarning(`Could not read runner metadata ${s.default.basename(n)}: invalid runner record`);return}return t}catch(e){if(e.code===`ENOENT`)return;process.emitWarning(`Could not read runner metadata ${s.default.basename(n)}: ${String(e)}`);return}}async markPromoted(e){let t=await this.get(e);if(!t)return;let n={...t,promoted:!0};return this.writeRecord(n),this.notify(),n}async clearAlarm(e,t){let n=await this.get(e,t);if(!n)return;if(!n.alarm)return n;let r={...n};return delete r.alarm,this.writeRecord(r),this.notify(),r}async markAlarmFired(e,t){let n=await this.get(e);if(!n?.alarm||n.state!==`running`)return;let r={...n,alarm:{...n.alarm,lastFiredAt:t}};return this.writeRecord(r),this.notify(),r}async complete(e,t,n){let r=await this.get(e,n);if(r?.state===`completed`)return await this.release(e),r;let i=await this.list(),a=r??i.find(t=>t.id===e&&(n===void 0||t.sessionId===n));if(!a){n!==void 0&&i.some(t=>t.id===e&&t.sessionId!==n)||await this.release(e);return}let o={...a,state:`completed`,exit:{...t,finishedAt:new Date().toISOString()}};return this.writeRecord(o),await this.release(e),o}async release(e){let t=await this.registry.releaseProcess({repositoryPath:this.repositoryPath(),serviceName:`${d}${e}`,serviceType:u,kill:!1,releasePort:!1,force:!0});if(!t.success&&!t.error?.includes(`No matching process entry`))throw Error(t.error??`Failed to release runner ${e}`);this.notify()}async pruneDead(){let e=(await this.list()).filter(e=>!this.processControl.isAlive(e.pid));for(let t of e)await this.complete(t.id,{reason:`backend_lost`,code:null,signal:null},t.sessionId);return e.map(e=>e.id)}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}close(){this.listeners.clear(),this.registry.close()}notify(){for(let e of this.listeners)e()}repositoryPath(){return(0,l.normalizeRepositoryPath)(this.paths.repositoryPath())}writeRecord(e){this.paths.ensureDirectories(e.sessionId);let t=this.paths.statePathFor(e.id,e.sessionId),n=`${t}.${process.pid}.tmp`;o.default.writeFileSync(n,`${JSON.stringify(e,null,2)}\n`,`utf8`),o.default.renameSync(n,t)}};_=i.default([(0,a.injectable)(),r.default(0,(0,a.inject)(t.TYPES.RunnerPaths)),r.default(1,(0,a.inject)(t.TYPES.ProcessControl)),r.default(2,(0,a.inject)(t.TYPES.ProcessRegistry)),n.default(`design:paramtypes`,[Object,Object,Object])],_);function v(e){return{id:h(e,`id`),name:h(e,`name`),pid:e.pid,command:h(e,`command`),cwd:h(e,`cwd`),logPath:h(e,`logPath`),interactive:e.metadata?.interactive===!0,sessionId:h(e,`sessionId`),...h(e,`rootSessionId`)?{rootSessionId:h(e,`rootSessionId`)}:{},startedAt:h(e,`startedAt`),state:`running`,promoted:!1,backend:y(e.metadata?.backend),...h(e,`backendTarget`)?{backendTarget:h(e,`backendTarget`)}:{},hostPid:g(e,`hostPid`)}}function y(e){return e===`rmux`||e===`tmux`?e:`native`}function b(e){return e.endsWith(`.json`)&&!e.endsWith(`.command.json`)&&!e.endsWith(`.exit.json`)}function x(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.id==`string`&&typeof t.name==`string`&&typeof t.pid==`number`&&typeof t.command==`string`&&typeof t.cwd==`string`&&typeof t.logPath==`string`&&typeof t.sessionId==`string`&&typeof t.startedAt==`string`&&(t.state===`running`||t.state===`completed`)&&typeof t.promoted==`boolean`&&(t.backend===`rmux`||t.backend===`tmux`||t.backend===`native`)}Object.defineProperty(exports,"RunnerRegistry",{enumerable:!0,get:function(){return _}}),exports.createDefaultProcessRegistry=p;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../types/index.cjs"),n=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs"),r=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs"),i=require("../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs");let a=require("inversify");require("reflect-metadata/lite");let o=require("node:fs");o=e.__toESM(o,1);let s=require("node:path");s=e.__toESM(s,1);let c=require("@agimon-ai/doompi-extension-contracts/child-process"),l=require("@agimon-ai/foundation-process-registry");const u=`tool`,d=`doom-runner:`,f=`doom-runner`;function p(){return(0,l.createProcessRegistryService)(process.env.PROCESS_REGISTRY_PATH)}function m(e){return`session:${e}`}function h(e,t){let n=e.metadata?.[t];return typeof n==`string`?n:``}function g(e,t){let n=e.metadata?.[t];return typeof n==`number`?n:0}let _=class{paths;processControl;registry;listeners=new Set;constructor(e,t,n){this.paths=e,this.processControl=t,this.registry=n}async register(e){let t=new Date().toISOString(),n=(0,c.resolveRootSessionId)(e.sessionId),r=await this.registry.registerProcess({repositoryPath:this.repositoryPath(),serviceName:`${d}${e.id}`,serviceType:u,pid:e.pid,command:e.command,tags:(0,l.resolveProcessTags)([f,`runner:${e.id}`,m(e.sessionId)]),metadata:{id:e.id,name:e.name,command:e.command,cwd:e.cwd,logPath:e.logPath,interactive:e.interactive,sessionId:e.sessionId,rootSessionId:n,backend:e.backend,...e.backendTarget?{backendTarget:e.backendTarget}:{},startedAt:t,hostPid:process.pid},force:!0});if(!r.success)throw Error(r.error??`Failed to register runner ${e.name}`);let i={id:e.id,name:e.name,pid:e.pid,command:e.command,cwd:e.cwd,logPath:e.logPath,interactive:e.interactive,sessionId:e.sessionId,rootSessionId:n,startedAt:t,state:`running`,promoted:!1,backend:e.backend,...e.backendTarget?{backendTarget:e.backendTarget}:{},...e.alarmMs?{alarm:{intervalMs:e.alarmMs,lastFiredAt:t}}:{},hostPid:process.pid};return this.writeRecord(i),this.notify(),i}async list(){return(await this.registry.listProcesses({repositoryPath:this.repositoryPath(),serviceType:u,tags:(0,l.resolveProcessTags)([f])})).map(v)}async listAcrossRepositories(){return(await this.registry.listProcesses({serviceType:u,tags:(0,l.resolveProcessTags)([f])})).map(v)}async listBySession(e){return(await this.list()).filter(t=>t.sessionId===e)}async listByRootSession(e){return(await this.list()).filter(t=>(t.rootSessionId??t.sessionId)===e)}async listAll(e){this.paths.ensureDirectories(e);let t=[],n=this.paths.stateDirectory(e);for(let e of o.default.readdirSync(n))if(b(e))try{let r=JSON.parse(o.default.readFileSync(`${n}/${e}`,`utf8`));x(r)&&t.push(r)}catch(t){process.emitWarning(`Could not read runner metadata ${e}: ${String(t)}`)}return t.sort((e,t)=>t.startedAt.localeCompare(e.startedAt))}async get(e,t){let n=this.paths.statePathFor(e,t);try{let t=JSON.parse(o.default.readFileSync(n,`utf8`));if(!x(t)||t.id!==e){process.emitWarning(`Could not read runner metadata ${s.default.basename(n)}: invalid runner record`);return}return t}catch(e){if(e.code===`ENOENT`)return;process.emitWarning(`Could not read runner metadata ${s.default.basename(n)}: ${String(e)}`);return}}async markPromoted(e){let t=await this.get(e);if(!t)return;let n={...t,promoted:!0};return this.writeRecord(n),this.notify(),n}async clearAlarm(e,t){let n=await this.get(e,t);if(!n)return;if(!n.alarm)return n;let r={...n};return delete r.alarm,this.writeRecord(r),this.notify(),r}async markAlarmFired(e,t){let n=await this.get(e);if(!n?.alarm||n.state!==`running`)return;let r={...n,alarm:{...n.alarm,lastFiredAt:t}};return this.writeRecord(r),this.notify(),r}async complete(e,t,n){let r=await this.get(e,n);if(r?.state===`completed`)return await this.release(e),r;let i=await this.list(),a=r??i.find(t=>t.id===e&&(n===void 0||t.sessionId===n));if(!a){n!==void 0&&i.some(t=>t.id===e&&t.sessionId!==n)||await this.release(e);return}let o={...a,state:`completed`,exit:{...t,finishedAt:new Date().toISOString()}};return this.writeRecord(o),await this.release(e),o}async release(e){let t=await this.registry.releaseProcess({repositoryPath:this.repositoryPath(),serviceName:`${d}${e}`,serviceType:u,kill:!1,releasePort:!1,force:!0});if(!t.success&&!t.error?.includes(`No matching process entry`))throw Error(t.error??`Failed to release runner ${e}`);this.notify()}async pruneDead(){let e=(await this.list()).filter(e=>!this.processControl.isAlive(e.pid));for(let t of e)await this.complete(t.id,{reason:`backend_lost`,code:null,signal:null},t.sessionId);return e.map(e=>e.id)}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}close(){this.listeners.clear(),this.registry.close()}notify(){for(let e of this.listeners)e()}repositoryPath(){return(0,l.normalizeRepositoryPath)(this.paths.repositoryPath())}writeRecord(e){this.paths.ensureDirectories(e.sessionId);let t=this.paths.statePathFor(e.id,e.sessionId),n=`${t}.${process.pid}.tmp`;o.default.writeFileSync(n,`${JSON.stringify(e,null,2)}\n`,`utf8`),o.default.renameSync(n,t)}};_=i.default([(0,a.injectable)(),r.default(0,(0,a.inject)(t.TYPES.RunnerPaths)),r.default(1,(0,a.inject)(t.TYPES.ProcessControl)),r.default(2,(0,a.inject)(t.TYPES.ProcessRegistry)),n.default(`design:paramtypes`,[Object,Object,Object])],_);function v(e){return{id:h(e,`id`),name:h(e,`name`),pid:e.pid,command:h(e,`command`),cwd:h(e,`cwd`),logPath:h(e,`logPath`),interactive:e.metadata?.interactive===!0,sessionId:h(e,`sessionId`),...h(e,`rootSessionId`)?{rootSessionId:h(e,`rootSessionId`)}:{},startedAt:h(e,`startedAt`),state:`running`,promoted:!1,backend:y(e.metadata?.backend),...h(e,`backendTarget`)?{backendTarget:h(e,`backendTarget`)}:{},hostPid:g(e,`hostPid`)}}function y(e){return e===`rmux`||e===`tmux`?e:`native`}function b(e){return e.endsWith(`.json`)&&!e.endsWith(`.command.json`)&&!e.endsWith(`.exit.json`)}function x(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.id==`string`&&typeof t.name==`string`&&typeof t.pid==`number`&&typeof t.command==`string`&&typeof t.cwd==`string`&&typeof t.logPath==`string`&&typeof t.sessionId==`string`&&typeof t.startedAt==`string`&&(t.state===`running`||t.state===`completed`)&&typeof t.promoted==`boolean`&&(t.backend===`rmux`||t.backend===`tmux`||t.backend===`native`)}Object.defineProperty(exports,"RunnerRegistry",{enumerable:!0,get:function(){return _}}),exports.createDefaultProcessRegistry=p;
2
2
  //# sourceMappingURL=RunnerRegistry.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RunnerRegistry.cjs","names":["createProcessRegistryService","resolveRootSessionId","resolveProcessTags","fs","path","normalizeRepositoryPath","injectable","inject","TYPES"],"sources":["../../../src/services/RunnerRegistry/RunnerRegistry.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n createProcessRegistryService,\n normalizeRepositoryPath,\n type ProcessRegistryRecord,\n type ProcessRegistryService,\n resolveProcessTags,\n} from '@agimon-ai/foundation-process-registry';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport { TYPES } from '../../types.ts';\nimport type { CompleteRunnerInput, IRunnerRegistry, RegisterRunnerInput, RunnerRecord } from './types.ts';\n\nconst SERVICE_TYPE = 'tool';\nconst SERVICE_PREFIX = 'doom-runner:';\nconst RUNNER_TAG = 'doom-runner';\nconst REGISTRY_PATH_ENV = 'PROCESS_REGISTRY_PATH';\nconst JSON_EXTENSION = '.json';\nconst COMMAND_SIDECAR_SUFFIX = '.command.json';\nconst EXIT_SIDECAR_SUFFIX = '.exit.json';\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\n/** The slice of the shared registry service this wrapper depends on. */\nexport type ProcessRegistryPort = Pick<\n ProcessRegistryService,\n 'registerProcess' | 'releaseProcess' | 'listProcesses' | 'close'\n>;\n\n/** Opens the SQLite-backed registry the rest of the monorepo already shares. */\nexport function createDefaultProcessRegistry(): ProcessRegistryPort {\n return createProcessRegistryService(process.env[REGISTRY_PATH_ENV]);\n}\n\nfunction sessionTag(sessionId: string): string {\n return `session:${sessionId}`;\n}\n\nfunction readString(record: ProcessRegistryRecord, key: string): string {\n const value = record.metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nfunction readNumber(record: ProcessRegistryRecord, key: string): number {\n const value = record.metadata?.[key];\n return typeof value === 'number' ? value : 0;\n}\n\n@injectable()\nexport class RunnerRegistry implements IRunnerRegistry {\n private readonly listeners = new Set<() => void>();\n\n constructor(\n @inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.ProcessRegistry) private readonly registry: ProcessRegistryPort,\n ) {}\n\n async register(input: RegisterRunnerInput): Promise<RunnerRecord> {\n const startedAt = new Date().toISOString();\n const rootSessionId = resolveRootSessionId(input.sessionId);\n const response = await this.registry.registerProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${input.id}`,\n serviceType: SERVICE_TYPE,\n pid: input.pid,\n command: input.command,\n tags: resolveProcessTags([RUNNER_TAG, `runner:${input.id}`, sessionTag(input.sessionId)]),\n metadata: {\n id: input.id,\n name: input.name,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n startedAt,\n hostPid: process.pid,\n },\n // The caller has already resolved name collisions against live records,\n // so anything still holding this name is stale.\n force: true,\n });\n if (!response.success) throw new Error(response.error ?? `Failed to register runner ${input.name}`);\n const record: RunnerRecord = {\n id: input.id,\n name: input.name,\n pid: input.pid,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n startedAt,\n state: 'running',\n promoted: false,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n ...(input.alarmMs ? { alarm: { intervalMs: input.alarmMs, lastFiredAt: startedAt } } : {}),\n hostPid: process.pid,\n };\n this.writeRecord(record);\n this.notify();\n return record;\n }\n\n async list(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n repositoryPath: this.repositoryPath(),\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listAcrossRepositories(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listBySession(sessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => record.sessionId === sessionId);\n }\n\n async listByRootSession(rootSessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId);\n }\n\n async listAll(sessionId?: string): Promise<RunnerRecord[]> {\n this.paths.ensureDirectories(sessionId);\n const records: RunnerRecord[] = [];\n const directory = this.paths.stateDirectory(sessionId);\n for (const entry of fs.readdirSync(directory)) {\n if (!isPrimaryMetadataEntry(entry)) continue;\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(`${directory}/${entry}`, 'utf8'));\n if (isRunnerRecord(parsed)) records.push(parsed);\n } catch (error) {\n process.emitWarning(`Could not read runner metadata ${entry}: ${String(error)}`);\n }\n }\n return records.sort((left, right) => right.startedAt.localeCompare(left.startedAt));\n }\n\n async get(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const target = this.paths.statePathFor(id, sessionId);\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (!isRunnerRecord(parsed) || parsed.id !== id) {\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: invalid runner record`);\n return undefined;\n }\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === NOT_FOUND_ERROR_CODE) return undefined;\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: ${String(error)}`);\n return undefined;\n }\n }\n\n async markPromoted(id: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record) return undefined;\n const promoted = { ...record, promoted: true };\n this.writeRecord(promoted);\n this.notify();\n return promoted;\n }\n\n async clearAlarm(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id, sessionId);\n if (!record) return undefined;\n if (!record.alarm) return record;\n const cleared: RunnerRecord = { ...record };\n delete cleared.alarm;\n this.writeRecord(cleared);\n this.notify();\n return cleared;\n }\n\n async markAlarmFired(id: string, firedAt: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record?.alarm || record.state !== 'running') return undefined;\n const fired: RunnerRecord = { ...record, alarm: { ...record.alarm, lastFiredAt: firedAt } };\n this.writeRecord(fired);\n this.notify();\n return fired;\n }\n\n async complete(id: string, outcome: CompleteRunnerInput, sessionId?: string): Promise<RunnerRecord | undefined> {\n const persisted = await this.get(id, sessionId);\n if (persisted?.state === 'completed') {\n await this.release(id);\n return persisted;\n }\n const active = await this.list();\n const record =\n persisted ??\n active.find((candidate) => candidate.id === id && (sessionId === undefined || candidate.sessionId === sessionId));\n if (!record) {\n const belongsToAnotherSession =\n sessionId !== undefined && active.some((candidate) => candidate.id === id && candidate.sessionId !== sessionId);\n if (!belongsToAnotherSession) await this.release(id);\n return undefined;\n }\n const completed: RunnerRecord = {\n ...record,\n state: 'completed',\n exit: { ...outcome, finishedAt: new Date().toISOString() },\n };\n this.writeRecord(completed);\n await this.release(id);\n return completed;\n }\n\n async release(id: string): Promise<void> {\n const response = await this.registry.releaseProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${id}`,\n serviceType: SERVICE_TYPE,\n kill: false,\n releasePort: false,\n force: true,\n });\n if (!response.success && !response.error?.includes('No matching process entry')) {\n throw new Error(response.error ?? `Failed to release runner ${id}`);\n }\n this.notify();\n }\n\n async pruneDead(): Promise<string[]> {\n const dead = (await this.list()).filter((record) => !this.processControl.isAlive(record.pid));\n for (const record of dead) {\n await this.complete(record.id, { reason: 'backend_lost', code: null, signal: null }, record.sessionId);\n }\n return dead.map((record) => record.id);\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n close(): void {\n this.listeners.clear();\n this.registry.close();\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener();\n }\n\n private repositoryPath(): string {\n return normalizeRepositoryPath(this.paths.repositoryPath());\n }\n\n private writeRecord(record: RunnerRecord): void {\n this.paths.ensureDirectories(record.sessionId);\n const target = this.paths.statePathFor(record.id, record.sessionId);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\\n`, 'utf8');\n fs.renameSync(temporary, target);\n }\n}\n\n/** Every field is read back from metadata this class wrote on registration. */\nfunction toRunnerRecord(record: ProcessRegistryRecord): RunnerRecord {\n return {\n id: readString(record, 'id'),\n name: readString(record, 'name'),\n pid: record.pid,\n command: readString(record, 'command'),\n cwd: readString(record, 'cwd'),\n logPath: readString(record, 'logPath'),\n interactive: record.metadata?.interactive === true,\n sessionId: readString(record, 'sessionId'),\n ...(readString(record, 'rootSessionId') ? { rootSessionId: readString(record, 'rootSessionId') } : {}),\n startedAt: readString(record, 'startedAt'),\n state: 'running',\n promoted: false,\n backend: readBackend(record.metadata?.backend),\n ...(readString(record, 'backendTarget') ? { backendTarget: readString(record, 'backendTarget') } : {}),\n hostPid: readNumber(record, 'hostPid'),\n };\n}\n\nfunction readBackend(value: unknown): RunnerRecord['backend'] {\n return value === 'rmux' || value === 'tmux' ? value : 'native';\n}\n\nfunction isPrimaryMetadataEntry(entry: string): boolean {\n return (\n entry.endsWith(JSON_EXTENSION) && !entry.endsWith(COMMAND_SIDECAR_SUFFIX) && !entry.endsWith(EXIT_SIDECAR_SUFFIX)\n );\n}\n\nfunction isRunnerRecord(value: unknown): value is RunnerRecord {\n if (typeof value !== 'object' || value === null) return false;\n const record = value as Partial<RunnerRecord>;\n return (\n typeof record.id === 'string' &&\n typeof record.name === 'string' &&\n typeof record.pid === 'number' &&\n typeof record.command === 'string' &&\n typeof record.cwd === 'string' &&\n typeof record.logPath === 'string' &&\n typeof record.sessionId === 'string' &&\n typeof record.startedAt === 'string' &&\n (record.state === 'running' || record.state === 'completed') &&\n typeof record.promoted === 'boolean' &&\n (record.backend === 'rmux' || record.backend === 'tmux' || record.backend === 'native')\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RunnerRegistry.cjs","names":["createProcessRegistryService","resolveRootSessionId","resolveProcessTags","fs","path","normalizeRepositoryPath","injectable","inject","TYPES"],"sources":["../../../src/services/RunnerRegistry/RunnerRegistry.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n createProcessRegistryService,\n normalizeRepositoryPath,\n type ProcessRegistryRecord,\n type ProcessRegistryService,\n resolveProcessTags,\n} from '@agimon-ai/foundation-process-registry';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { CompleteRunnerInput, IRunnerRegistry, RegisterRunnerInput, RunnerRecord } from './types.ts';\n\nconst SERVICE_TYPE = 'tool';\nconst SERVICE_PREFIX = 'doom-runner:';\nconst RUNNER_TAG = 'doom-runner';\nconst REGISTRY_PATH_ENV = 'PROCESS_REGISTRY_PATH';\nconst JSON_EXTENSION = '.json';\nconst COMMAND_SIDECAR_SUFFIX = '.command.json';\nconst EXIT_SIDECAR_SUFFIX = '.exit.json';\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\n/** The slice of the shared registry service this wrapper depends on. */\nexport type ProcessRegistryPort = Pick<\n ProcessRegistryService,\n 'registerProcess' | 'releaseProcess' | 'listProcesses' | 'close'\n>;\n\n/** Opens the SQLite-backed registry the rest of the monorepo already shares. */\nexport function createDefaultProcessRegistry(): ProcessRegistryPort {\n return createProcessRegistryService(process.env[REGISTRY_PATH_ENV]);\n}\n\nfunction sessionTag(sessionId: string): string {\n return `session:${sessionId}`;\n}\n\nfunction readString(record: ProcessRegistryRecord, key: string): string {\n const value = record.metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nfunction readNumber(record: ProcessRegistryRecord, key: string): number {\n const value = record.metadata?.[key];\n return typeof value === 'number' ? value : 0;\n}\n\n@injectable()\nexport class RunnerRegistry implements IRunnerRegistry {\n private readonly listeners = new Set<() => void>();\n\n constructor(\n @inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.ProcessRegistry) private readonly registry: ProcessRegistryPort,\n ) {}\n\n async register(input: RegisterRunnerInput): Promise<RunnerRecord> {\n const startedAt = new Date().toISOString();\n const rootSessionId = resolveRootSessionId(input.sessionId);\n const response = await this.registry.registerProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${input.id}`,\n serviceType: SERVICE_TYPE,\n pid: input.pid,\n command: input.command,\n tags: resolveProcessTags([RUNNER_TAG, `runner:${input.id}`, sessionTag(input.sessionId)]),\n metadata: {\n id: input.id,\n name: input.name,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n startedAt,\n hostPid: process.pid,\n },\n // The caller has already resolved name collisions against live records,\n // so anything still holding this name is stale.\n force: true,\n });\n if (!response.success) throw new Error(response.error ?? `Failed to register runner ${input.name}`);\n const record: RunnerRecord = {\n id: input.id,\n name: input.name,\n pid: input.pid,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n startedAt,\n state: 'running',\n promoted: false,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n ...(input.alarmMs ? { alarm: { intervalMs: input.alarmMs, lastFiredAt: startedAt } } : {}),\n hostPid: process.pid,\n };\n this.writeRecord(record);\n this.notify();\n return record;\n }\n\n async list(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n repositoryPath: this.repositoryPath(),\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listAcrossRepositories(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listBySession(sessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => record.sessionId === sessionId);\n }\n\n async listByRootSession(rootSessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId);\n }\n\n async listAll(sessionId?: string): Promise<RunnerRecord[]> {\n this.paths.ensureDirectories(sessionId);\n const records: RunnerRecord[] = [];\n const directory = this.paths.stateDirectory(sessionId);\n for (const entry of fs.readdirSync(directory)) {\n if (!isPrimaryMetadataEntry(entry)) continue;\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(`${directory}/${entry}`, 'utf8'));\n if (isRunnerRecord(parsed)) records.push(parsed);\n } catch (error) {\n process.emitWarning(`Could not read runner metadata ${entry}: ${String(error)}`);\n }\n }\n return records.sort((left, right) => right.startedAt.localeCompare(left.startedAt));\n }\n\n async get(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const target = this.paths.statePathFor(id, sessionId);\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (!isRunnerRecord(parsed) || parsed.id !== id) {\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: invalid runner record`);\n return undefined;\n }\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === NOT_FOUND_ERROR_CODE) return undefined;\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: ${String(error)}`);\n return undefined;\n }\n }\n\n async markPromoted(id: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record) return undefined;\n const promoted = { ...record, promoted: true };\n this.writeRecord(promoted);\n this.notify();\n return promoted;\n }\n\n async clearAlarm(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id, sessionId);\n if (!record) return undefined;\n if (!record.alarm) return record;\n const cleared: RunnerRecord = { ...record };\n delete cleared.alarm;\n this.writeRecord(cleared);\n this.notify();\n return cleared;\n }\n\n async markAlarmFired(id: string, firedAt: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record?.alarm || record.state !== 'running') return undefined;\n const fired: RunnerRecord = { ...record, alarm: { ...record.alarm, lastFiredAt: firedAt } };\n this.writeRecord(fired);\n this.notify();\n return fired;\n }\n\n async complete(id: string, outcome: CompleteRunnerInput, sessionId?: string): Promise<RunnerRecord | undefined> {\n const persisted = await this.get(id, sessionId);\n if (persisted?.state === 'completed') {\n await this.release(id);\n return persisted;\n }\n const active = await this.list();\n const record =\n persisted ??\n active.find((candidate) => candidate.id === id && (sessionId === undefined || candidate.sessionId === sessionId));\n if (!record) {\n const belongsToAnotherSession =\n sessionId !== undefined && active.some((candidate) => candidate.id === id && candidate.sessionId !== sessionId);\n if (!belongsToAnotherSession) await this.release(id);\n return undefined;\n }\n const completed: RunnerRecord = {\n ...record,\n state: 'completed',\n exit: { ...outcome, finishedAt: new Date().toISOString() },\n };\n this.writeRecord(completed);\n await this.release(id);\n return completed;\n }\n\n async release(id: string): Promise<void> {\n const response = await this.registry.releaseProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${id}`,\n serviceType: SERVICE_TYPE,\n kill: false,\n releasePort: false,\n force: true,\n });\n if (!response.success && !response.error?.includes('No matching process entry')) {\n throw new Error(response.error ?? `Failed to release runner ${id}`);\n }\n this.notify();\n }\n\n async pruneDead(): Promise<string[]> {\n const dead = (await this.list()).filter((record) => !this.processControl.isAlive(record.pid));\n for (const record of dead) {\n await this.complete(record.id, { reason: 'backend_lost', code: null, signal: null }, record.sessionId);\n }\n return dead.map((record) => record.id);\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n close(): void {\n this.listeners.clear();\n this.registry.close();\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener();\n }\n\n private repositoryPath(): string {\n return normalizeRepositoryPath(this.paths.repositoryPath());\n }\n\n private writeRecord(record: RunnerRecord): void {\n this.paths.ensureDirectories(record.sessionId);\n const target = this.paths.statePathFor(record.id, record.sessionId);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\\n`, 'utf8');\n fs.renameSync(temporary, target);\n }\n}\n\n/** Every field is read back from metadata this class wrote on registration. */\nfunction toRunnerRecord(record: ProcessRegistryRecord): RunnerRecord {\n return {\n id: readString(record, 'id'),\n name: readString(record, 'name'),\n pid: record.pid,\n command: readString(record, 'command'),\n cwd: readString(record, 'cwd'),\n logPath: readString(record, 'logPath'),\n interactive: record.metadata?.interactive === true,\n sessionId: readString(record, 'sessionId'),\n ...(readString(record, 'rootSessionId') ? { rootSessionId: readString(record, 'rootSessionId') } : {}),\n startedAt: readString(record, 'startedAt'),\n state: 'running',\n promoted: false,\n backend: readBackend(record.metadata?.backend),\n ...(readString(record, 'backendTarget') ? { backendTarget: readString(record, 'backendTarget') } : {}),\n hostPid: readNumber(record, 'hostPid'),\n };\n}\n\nfunction readBackend(value: unknown): RunnerRecord['backend'] {\n return value === 'rmux' || value === 'tmux' ? value : 'native';\n}\n\nfunction isPrimaryMetadataEntry(entry: string): boolean {\n return (\n entry.endsWith(JSON_EXTENSION) && !entry.endsWith(COMMAND_SIDECAR_SUFFIX) && !entry.endsWith(EXIT_SIDECAR_SUFFIX)\n );\n}\n\nfunction isRunnerRecord(value: unknown): value is RunnerRecord {\n if (typeof value !== 'object' || value === null) return false;\n const record = value as Partial<RunnerRecord>;\n return (\n typeof record.id === 'string' &&\n typeof record.name === 'string' &&\n typeof record.pid === 'number' &&\n typeof record.command === 'string' &&\n typeof record.cwd === 'string' &&\n typeof record.logPath === 'string' &&\n typeof record.sessionId === 'string' &&\n typeof record.startedAt === 'string' &&\n (record.state === 'running' || record.state === 'completed') &&\n typeof record.promoted === 'boolean' &&\n (record.backend === 'rmux' || record.backend === 'tmux' || record.backend === 'native')\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{TYPES as e}from"../../types.mjs";import t from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{inject as i,injectable as a}from"inversify";import"reflect-metadata/lite";import o from"node:fs";import s from"node:path";import{resolveRootSessionId as c}from"@agimon-ai/doompi-extension-contracts/child-process";import{createProcessRegistryService as l,normalizeRepositoryPath as u,resolveProcessTags as d}from"@agimon-ai/foundation-process-registry";const f=`tool`,p=`doom-runner:`,m=`doom-runner`;function h(){return l(process.env.PROCESS_REGISTRY_PATH)}function g(e){return`session:${e}`}function _(e,t){let n=e.metadata?.[t];return typeof n==`string`?n:``}function v(e,t){let n=e.metadata?.[t];return typeof n==`number`?n:0}let y=class{paths;processControl;registry;listeners=new Set;constructor(e,t,n){this.paths=e,this.processControl=t,this.registry=n}async register(e){let t=new Date().toISOString(),n=c(e.sessionId),r=await this.registry.registerProcess({repositoryPath:this.repositoryPath(),serviceName:`${p}${e.id}`,serviceType:f,pid:e.pid,command:e.command,tags:d([m,`runner:${e.id}`,g(e.sessionId)]),metadata:{id:e.id,name:e.name,command:e.command,cwd:e.cwd,logPath:e.logPath,interactive:e.interactive,sessionId:e.sessionId,rootSessionId:n,backend:e.backend,...e.backendTarget?{backendTarget:e.backendTarget}:{},startedAt:t,hostPid:process.pid},force:!0});if(!r.success)throw Error(r.error??`Failed to register runner ${e.name}`);let i={id:e.id,name:e.name,pid:e.pid,command:e.command,cwd:e.cwd,logPath:e.logPath,interactive:e.interactive,sessionId:e.sessionId,rootSessionId:n,startedAt:t,state:`running`,promoted:!1,backend:e.backend,...e.backendTarget?{backendTarget:e.backendTarget}:{},...e.alarmMs?{alarm:{intervalMs:e.alarmMs,lastFiredAt:t}}:{},hostPid:process.pid};return this.writeRecord(i),this.notify(),i}async list(){return(await this.registry.listProcesses({repositoryPath:this.repositoryPath(),serviceType:f,tags:d([m])})).map(b)}async listAcrossRepositories(){return(await this.registry.listProcesses({serviceType:f,tags:d([m])})).map(b)}async listBySession(e){return(await this.list()).filter(t=>t.sessionId===e)}async listByRootSession(e){return(await this.list()).filter(t=>(t.rootSessionId??t.sessionId)===e)}async listAll(e){this.paths.ensureDirectories(e);let t=[],n=this.paths.stateDirectory(e);for(let e of o.readdirSync(n))if(S(e))try{let r=JSON.parse(o.readFileSync(`${n}/${e}`,`utf8`));C(r)&&t.push(r)}catch(t){process.emitWarning(`Could not read runner metadata ${e}: ${String(t)}`)}return t.sort((e,t)=>t.startedAt.localeCompare(e.startedAt))}async get(e,t){let n=this.paths.statePathFor(e,t);try{let t=JSON.parse(o.readFileSync(n,`utf8`));if(!C(t)||t.id!==e){process.emitWarning(`Could not read runner metadata ${s.basename(n)}: invalid runner record`);return}return t}catch(e){if(e.code===`ENOENT`)return;process.emitWarning(`Could not read runner metadata ${s.basename(n)}: ${String(e)}`);return}}async markPromoted(e){let t=await this.get(e);if(!t)return;let n={...t,promoted:!0};return this.writeRecord(n),this.notify(),n}async clearAlarm(e,t){let n=await this.get(e,t);if(!n)return;if(!n.alarm)return n;let r={...n};return delete r.alarm,this.writeRecord(r),this.notify(),r}async markAlarmFired(e,t){let n=await this.get(e);if(!n?.alarm||n.state!==`running`)return;let r={...n,alarm:{...n.alarm,lastFiredAt:t}};return this.writeRecord(r),this.notify(),r}async complete(e,t,n){let r=await this.get(e,n);if(r?.state===`completed`)return await this.release(e),r;let i=await this.list(),a=r??i.find(t=>t.id===e&&(n===void 0||t.sessionId===n));if(!a){n!==void 0&&i.some(t=>t.id===e&&t.sessionId!==n)||await this.release(e);return}let o={...a,state:`completed`,exit:{...t,finishedAt:new Date().toISOString()}};return this.writeRecord(o),await this.release(e),o}async release(e){let t=await this.registry.releaseProcess({repositoryPath:this.repositoryPath(),serviceName:`${p}${e}`,serviceType:f,kill:!1,releasePort:!1,force:!0});if(!t.success&&!t.error?.includes(`No matching process entry`))throw Error(t.error??`Failed to release runner ${e}`);this.notify()}async pruneDead(){let e=(await this.list()).filter(e=>!this.processControl.isAlive(e.pid));for(let t of e)await this.complete(t.id,{reason:`backend_lost`,code:null,signal:null},t.sessionId);return e.map(e=>e.id)}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}close(){this.listeners.clear(),this.registry.close()}notify(){for(let e of this.listeners)e()}repositoryPath(){return u(this.paths.repositoryPath())}writeRecord(e){this.paths.ensureDirectories(e.sessionId);let t=this.paths.statePathFor(e.id,e.sessionId),n=`${t}.${process.pid}.tmp`;o.writeFileSync(n,`${JSON.stringify(e,null,2)}\n`,`utf8`),o.renameSync(n,t)}};y=r([a(),n(0,i(e.RunnerPaths)),n(1,i(e.ProcessControl)),n(2,i(e.ProcessRegistry)),t(`design:paramtypes`,[Object,Object,Object])],y);function b(e){return{id:_(e,`id`),name:_(e,`name`),pid:e.pid,command:_(e,`command`),cwd:_(e,`cwd`),logPath:_(e,`logPath`),interactive:e.metadata?.interactive===!0,sessionId:_(e,`sessionId`),..._(e,`rootSessionId`)?{rootSessionId:_(e,`rootSessionId`)}:{},startedAt:_(e,`startedAt`),state:`running`,promoted:!1,backend:x(e.metadata?.backend),..._(e,`backendTarget`)?{backendTarget:_(e,`backendTarget`)}:{},hostPid:v(e,`hostPid`)}}function x(e){return e===`rmux`||e===`tmux`?e:`native`}function S(e){return e.endsWith(`.json`)&&!e.endsWith(`.command.json`)&&!e.endsWith(`.exit.json`)}function C(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.id==`string`&&typeof t.name==`string`&&typeof t.pid==`number`&&typeof t.command==`string`&&typeof t.cwd==`string`&&typeof t.logPath==`string`&&typeof t.sessionId==`string`&&typeof t.startedAt==`string`&&(t.state===`running`||t.state===`completed`)&&typeof t.promoted==`boolean`&&(t.backend===`rmux`||t.backend===`tmux`||t.backend===`native`)}export{y as RunnerRegistry,h as createDefaultProcessRegistry};
1
+ import{TYPES as e}from"../../types/index.mjs";import t from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs";import n from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs";import r from"../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs";import{inject as i,injectable as a}from"inversify";import"reflect-metadata/lite";import o from"node:fs";import s from"node:path";import{resolveRootSessionId as c}from"@agimon-ai/doompi-extension-contracts/child-process";import{createProcessRegistryService as l,normalizeRepositoryPath as u,resolveProcessTags as d}from"@agimon-ai/foundation-process-registry";const f=`tool`,p=`doom-runner:`,m=`doom-runner`;function h(){return l(process.env.PROCESS_REGISTRY_PATH)}function g(e){return`session:${e}`}function _(e,t){let n=e.metadata?.[t];return typeof n==`string`?n:``}function v(e,t){let n=e.metadata?.[t];return typeof n==`number`?n:0}let y=class{paths;processControl;registry;listeners=new Set;constructor(e,t,n){this.paths=e,this.processControl=t,this.registry=n}async register(e){let t=new Date().toISOString(),n=c(e.sessionId),r=await this.registry.registerProcess({repositoryPath:this.repositoryPath(),serviceName:`${p}${e.id}`,serviceType:f,pid:e.pid,command:e.command,tags:d([m,`runner:${e.id}`,g(e.sessionId)]),metadata:{id:e.id,name:e.name,command:e.command,cwd:e.cwd,logPath:e.logPath,interactive:e.interactive,sessionId:e.sessionId,rootSessionId:n,backend:e.backend,...e.backendTarget?{backendTarget:e.backendTarget}:{},startedAt:t,hostPid:process.pid},force:!0});if(!r.success)throw Error(r.error??`Failed to register runner ${e.name}`);let i={id:e.id,name:e.name,pid:e.pid,command:e.command,cwd:e.cwd,logPath:e.logPath,interactive:e.interactive,sessionId:e.sessionId,rootSessionId:n,startedAt:t,state:`running`,promoted:!1,backend:e.backend,...e.backendTarget?{backendTarget:e.backendTarget}:{},...e.alarmMs?{alarm:{intervalMs:e.alarmMs,lastFiredAt:t}}:{},hostPid:process.pid};return this.writeRecord(i),this.notify(),i}async list(){return(await this.registry.listProcesses({repositoryPath:this.repositoryPath(),serviceType:f,tags:d([m])})).map(b)}async listAcrossRepositories(){return(await this.registry.listProcesses({serviceType:f,tags:d([m])})).map(b)}async listBySession(e){return(await this.list()).filter(t=>t.sessionId===e)}async listByRootSession(e){return(await this.list()).filter(t=>(t.rootSessionId??t.sessionId)===e)}async listAll(e){this.paths.ensureDirectories(e);let t=[],n=this.paths.stateDirectory(e);for(let e of o.readdirSync(n))if(S(e))try{let r=JSON.parse(o.readFileSync(`${n}/${e}`,`utf8`));C(r)&&t.push(r)}catch(t){process.emitWarning(`Could not read runner metadata ${e}: ${String(t)}`)}return t.sort((e,t)=>t.startedAt.localeCompare(e.startedAt))}async get(e,t){let n=this.paths.statePathFor(e,t);try{let t=JSON.parse(o.readFileSync(n,`utf8`));if(!C(t)||t.id!==e){process.emitWarning(`Could not read runner metadata ${s.basename(n)}: invalid runner record`);return}return t}catch(e){if(e.code===`ENOENT`)return;process.emitWarning(`Could not read runner metadata ${s.basename(n)}: ${String(e)}`);return}}async markPromoted(e){let t=await this.get(e);if(!t)return;let n={...t,promoted:!0};return this.writeRecord(n),this.notify(),n}async clearAlarm(e,t){let n=await this.get(e,t);if(!n)return;if(!n.alarm)return n;let r={...n};return delete r.alarm,this.writeRecord(r),this.notify(),r}async markAlarmFired(e,t){let n=await this.get(e);if(!n?.alarm||n.state!==`running`)return;let r={...n,alarm:{...n.alarm,lastFiredAt:t}};return this.writeRecord(r),this.notify(),r}async complete(e,t,n){let r=await this.get(e,n);if(r?.state===`completed`)return await this.release(e),r;let i=await this.list(),a=r??i.find(t=>t.id===e&&(n===void 0||t.sessionId===n));if(!a){n!==void 0&&i.some(t=>t.id===e&&t.sessionId!==n)||await this.release(e);return}let o={...a,state:`completed`,exit:{...t,finishedAt:new Date().toISOString()}};return this.writeRecord(o),await this.release(e),o}async release(e){let t=await this.registry.releaseProcess({repositoryPath:this.repositoryPath(),serviceName:`${p}${e}`,serviceType:f,kill:!1,releasePort:!1,force:!0});if(!t.success&&!t.error?.includes(`No matching process entry`))throw Error(t.error??`Failed to release runner ${e}`);this.notify()}async pruneDead(){let e=(await this.list()).filter(e=>!this.processControl.isAlive(e.pid));for(let t of e)await this.complete(t.id,{reason:`backend_lost`,code:null,signal:null},t.sessionId);return e.map(e=>e.id)}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}close(){this.listeners.clear(),this.registry.close()}notify(){for(let e of this.listeners)e()}repositoryPath(){return u(this.paths.repositoryPath())}writeRecord(e){this.paths.ensureDirectories(e.sessionId);let t=this.paths.statePathFor(e.id,e.sessionId),n=`${t}.${process.pid}.tmp`;o.writeFileSync(n,`${JSON.stringify(e,null,2)}\n`,`utf8`),o.renameSync(n,t)}};y=r([a(),n(0,i(e.RunnerPaths)),n(1,i(e.ProcessControl)),n(2,i(e.ProcessRegistry)),t(`design:paramtypes`,[Object,Object,Object])],y);function b(e){return{id:_(e,`id`),name:_(e,`name`),pid:e.pid,command:_(e,`command`),cwd:_(e,`cwd`),logPath:_(e,`logPath`),interactive:e.metadata?.interactive===!0,sessionId:_(e,`sessionId`),..._(e,`rootSessionId`)?{rootSessionId:_(e,`rootSessionId`)}:{},startedAt:_(e,`startedAt`),state:`running`,promoted:!1,backend:x(e.metadata?.backend),..._(e,`backendTarget`)?{backendTarget:_(e,`backendTarget`)}:{},hostPid:v(e,`hostPid`)}}function x(e){return e===`rmux`||e===`tmux`?e:`native`}function S(e){return e.endsWith(`.json`)&&!e.endsWith(`.command.json`)&&!e.endsWith(`.exit.json`)}function C(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.id==`string`&&typeof t.name==`string`&&typeof t.pid==`number`&&typeof t.command==`string`&&typeof t.cwd==`string`&&typeof t.logPath==`string`&&typeof t.sessionId==`string`&&typeof t.startedAt==`string`&&(t.state===`running`||t.state===`completed`)&&typeof t.promoted==`boolean`&&(t.backend===`rmux`||t.backend===`tmux`||t.backend===`native`)}export{y as RunnerRegistry,h as createDefaultProcessRegistry};
2
2
  //# sourceMappingURL=RunnerRegistry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RunnerRegistry.mjs","names":[],"sources":["../../../src/services/RunnerRegistry/RunnerRegistry.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n createProcessRegistryService,\n normalizeRepositoryPath,\n type ProcessRegistryRecord,\n type ProcessRegistryService,\n resolveProcessTags,\n} from '@agimon-ai/foundation-process-registry';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport { TYPES } from '../../types.ts';\nimport type { CompleteRunnerInput, IRunnerRegistry, RegisterRunnerInput, RunnerRecord } from './types.ts';\n\nconst SERVICE_TYPE = 'tool';\nconst SERVICE_PREFIX = 'doom-runner:';\nconst RUNNER_TAG = 'doom-runner';\nconst REGISTRY_PATH_ENV = 'PROCESS_REGISTRY_PATH';\nconst JSON_EXTENSION = '.json';\nconst COMMAND_SIDECAR_SUFFIX = '.command.json';\nconst EXIT_SIDECAR_SUFFIX = '.exit.json';\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\n/** The slice of the shared registry service this wrapper depends on. */\nexport type ProcessRegistryPort = Pick<\n ProcessRegistryService,\n 'registerProcess' | 'releaseProcess' | 'listProcesses' | 'close'\n>;\n\n/** Opens the SQLite-backed registry the rest of the monorepo already shares. */\nexport function createDefaultProcessRegistry(): ProcessRegistryPort {\n return createProcessRegistryService(process.env[REGISTRY_PATH_ENV]);\n}\n\nfunction sessionTag(sessionId: string): string {\n return `session:${sessionId}`;\n}\n\nfunction readString(record: ProcessRegistryRecord, key: string): string {\n const value = record.metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nfunction readNumber(record: ProcessRegistryRecord, key: string): number {\n const value = record.metadata?.[key];\n return typeof value === 'number' ? value : 0;\n}\n\n@injectable()\nexport class RunnerRegistry implements IRunnerRegistry {\n private readonly listeners = new Set<() => void>();\n\n constructor(\n @inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.ProcessRegistry) private readonly registry: ProcessRegistryPort,\n ) {}\n\n async register(input: RegisterRunnerInput): Promise<RunnerRecord> {\n const startedAt = new Date().toISOString();\n const rootSessionId = resolveRootSessionId(input.sessionId);\n const response = await this.registry.registerProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${input.id}`,\n serviceType: SERVICE_TYPE,\n pid: input.pid,\n command: input.command,\n tags: resolveProcessTags([RUNNER_TAG, `runner:${input.id}`, sessionTag(input.sessionId)]),\n metadata: {\n id: input.id,\n name: input.name,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n startedAt,\n hostPid: process.pid,\n },\n // The caller has already resolved name collisions against live records,\n // so anything still holding this name is stale.\n force: true,\n });\n if (!response.success) throw new Error(response.error ?? `Failed to register runner ${input.name}`);\n const record: RunnerRecord = {\n id: input.id,\n name: input.name,\n pid: input.pid,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n startedAt,\n state: 'running',\n promoted: false,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n ...(input.alarmMs ? { alarm: { intervalMs: input.alarmMs, lastFiredAt: startedAt } } : {}),\n hostPid: process.pid,\n };\n this.writeRecord(record);\n this.notify();\n return record;\n }\n\n async list(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n repositoryPath: this.repositoryPath(),\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listAcrossRepositories(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listBySession(sessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => record.sessionId === sessionId);\n }\n\n async listByRootSession(rootSessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId);\n }\n\n async listAll(sessionId?: string): Promise<RunnerRecord[]> {\n this.paths.ensureDirectories(sessionId);\n const records: RunnerRecord[] = [];\n const directory = this.paths.stateDirectory(sessionId);\n for (const entry of fs.readdirSync(directory)) {\n if (!isPrimaryMetadataEntry(entry)) continue;\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(`${directory}/${entry}`, 'utf8'));\n if (isRunnerRecord(parsed)) records.push(parsed);\n } catch (error) {\n process.emitWarning(`Could not read runner metadata ${entry}: ${String(error)}`);\n }\n }\n return records.sort((left, right) => right.startedAt.localeCompare(left.startedAt));\n }\n\n async get(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const target = this.paths.statePathFor(id, sessionId);\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (!isRunnerRecord(parsed) || parsed.id !== id) {\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: invalid runner record`);\n return undefined;\n }\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === NOT_FOUND_ERROR_CODE) return undefined;\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: ${String(error)}`);\n return undefined;\n }\n }\n\n async markPromoted(id: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record) return undefined;\n const promoted = { ...record, promoted: true };\n this.writeRecord(promoted);\n this.notify();\n return promoted;\n }\n\n async clearAlarm(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id, sessionId);\n if (!record) return undefined;\n if (!record.alarm) return record;\n const cleared: RunnerRecord = { ...record };\n delete cleared.alarm;\n this.writeRecord(cleared);\n this.notify();\n return cleared;\n }\n\n async markAlarmFired(id: string, firedAt: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record?.alarm || record.state !== 'running') return undefined;\n const fired: RunnerRecord = { ...record, alarm: { ...record.alarm, lastFiredAt: firedAt } };\n this.writeRecord(fired);\n this.notify();\n return fired;\n }\n\n async complete(id: string, outcome: CompleteRunnerInput, sessionId?: string): Promise<RunnerRecord | undefined> {\n const persisted = await this.get(id, sessionId);\n if (persisted?.state === 'completed') {\n await this.release(id);\n return persisted;\n }\n const active = await this.list();\n const record =\n persisted ??\n active.find((candidate) => candidate.id === id && (sessionId === undefined || candidate.sessionId === sessionId));\n if (!record) {\n const belongsToAnotherSession =\n sessionId !== undefined && active.some((candidate) => candidate.id === id && candidate.sessionId !== sessionId);\n if (!belongsToAnotherSession) await this.release(id);\n return undefined;\n }\n const completed: RunnerRecord = {\n ...record,\n state: 'completed',\n exit: { ...outcome, finishedAt: new Date().toISOString() },\n };\n this.writeRecord(completed);\n await this.release(id);\n return completed;\n }\n\n async release(id: string): Promise<void> {\n const response = await this.registry.releaseProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${id}`,\n serviceType: SERVICE_TYPE,\n kill: false,\n releasePort: false,\n force: true,\n });\n if (!response.success && !response.error?.includes('No matching process entry')) {\n throw new Error(response.error ?? `Failed to release runner ${id}`);\n }\n this.notify();\n }\n\n async pruneDead(): Promise<string[]> {\n const dead = (await this.list()).filter((record) => !this.processControl.isAlive(record.pid));\n for (const record of dead) {\n await this.complete(record.id, { reason: 'backend_lost', code: null, signal: null }, record.sessionId);\n }\n return dead.map((record) => record.id);\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n close(): void {\n this.listeners.clear();\n this.registry.close();\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener();\n }\n\n private repositoryPath(): string {\n return normalizeRepositoryPath(this.paths.repositoryPath());\n }\n\n private writeRecord(record: RunnerRecord): void {\n this.paths.ensureDirectories(record.sessionId);\n const target = this.paths.statePathFor(record.id, record.sessionId);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\\n`, 'utf8');\n fs.renameSync(temporary, target);\n }\n}\n\n/** Every field is read back from metadata this class wrote on registration. */\nfunction toRunnerRecord(record: ProcessRegistryRecord): RunnerRecord {\n return {\n id: readString(record, 'id'),\n name: readString(record, 'name'),\n pid: record.pid,\n command: readString(record, 'command'),\n cwd: readString(record, 'cwd'),\n logPath: readString(record, 'logPath'),\n interactive: record.metadata?.interactive === true,\n sessionId: readString(record, 'sessionId'),\n ...(readString(record, 'rootSessionId') ? { rootSessionId: readString(record, 'rootSessionId') } : {}),\n startedAt: readString(record, 'startedAt'),\n state: 'running',\n promoted: false,\n backend: readBackend(record.metadata?.backend),\n ...(readString(record, 'backendTarget') ? { backendTarget: readString(record, 'backendTarget') } : {}),\n hostPid: readNumber(record, 'hostPid'),\n };\n}\n\nfunction readBackend(value: unknown): RunnerRecord['backend'] {\n return value === 'rmux' || value === 'tmux' ? value : 'native';\n}\n\nfunction isPrimaryMetadataEntry(entry: string): boolean {\n return (\n entry.endsWith(JSON_EXTENSION) && !entry.endsWith(COMMAND_SIDECAR_SUFFIX) && !entry.endsWith(EXIT_SIDECAR_SUFFIX)\n );\n}\n\nfunction isRunnerRecord(value: unknown): value is RunnerRecord {\n if (typeof value !== 'object' || value === null) return false;\n const record = value as Partial<RunnerRecord>;\n return (\n typeof record.id === 'string' &&\n typeof record.name === 'string' &&\n typeof record.pid === 'number' &&\n typeof record.command === 'string' &&\n typeof record.cwd === 'string' &&\n typeof record.logPath === 'string' &&\n typeof record.sessionId === 'string' &&\n typeof record.startedAt === 'string' &&\n (record.state === 'running' || record.state === 'completed') &&\n typeof record.promoted === 'boolean' &&\n (record.backend === 'rmux' || record.backend === 'tmux' || record.backend === 'native')\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RunnerRegistry.mjs","names":[],"sources":["../../../src/services/RunnerRegistry/RunnerRegistry.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n createProcessRegistryService,\n normalizeRepositoryPath,\n type ProcessRegistryRecord,\n type ProcessRegistryService,\n resolveProcessTags,\n} from '@agimon-ai/foundation-process-registry';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { TYPES } from '../../types/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { CompleteRunnerInput, IRunnerRegistry, RegisterRunnerInput, RunnerRecord } from './types.ts';\n\nconst SERVICE_TYPE = 'tool';\nconst SERVICE_PREFIX = 'doom-runner:';\nconst RUNNER_TAG = 'doom-runner';\nconst REGISTRY_PATH_ENV = 'PROCESS_REGISTRY_PATH';\nconst JSON_EXTENSION = '.json';\nconst COMMAND_SIDECAR_SUFFIX = '.command.json';\nconst EXIT_SIDECAR_SUFFIX = '.exit.json';\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\n/** The slice of the shared registry service this wrapper depends on. */\nexport type ProcessRegistryPort = Pick<\n ProcessRegistryService,\n 'registerProcess' | 'releaseProcess' | 'listProcesses' | 'close'\n>;\n\n/** Opens the SQLite-backed registry the rest of the monorepo already shares. */\nexport function createDefaultProcessRegistry(): ProcessRegistryPort {\n return createProcessRegistryService(process.env[REGISTRY_PATH_ENV]);\n}\n\nfunction sessionTag(sessionId: string): string {\n return `session:${sessionId}`;\n}\n\nfunction readString(record: ProcessRegistryRecord, key: string): string {\n const value = record.metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nfunction readNumber(record: ProcessRegistryRecord, key: string): number {\n const value = record.metadata?.[key];\n return typeof value === 'number' ? value : 0;\n}\n\n@injectable()\nexport class RunnerRegistry implements IRunnerRegistry {\n private readonly listeners = new Set<() => void>();\n\n constructor(\n @inject(TYPES.RunnerPaths) private readonly paths: IRunnerPaths,\n @inject(TYPES.ProcessControl) private readonly processControl: IProcessControl,\n @inject(TYPES.ProcessRegistry) private readonly registry: ProcessRegistryPort,\n ) {}\n\n async register(input: RegisterRunnerInput): Promise<RunnerRecord> {\n const startedAt = new Date().toISOString();\n const rootSessionId = resolveRootSessionId(input.sessionId);\n const response = await this.registry.registerProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${input.id}`,\n serviceType: SERVICE_TYPE,\n pid: input.pid,\n command: input.command,\n tags: resolveProcessTags([RUNNER_TAG, `runner:${input.id}`, sessionTag(input.sessionId)]),\n metadata: {\n id: input.id,\n name: input.name,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n startedAt,\n hostPid: process.pid,\n },\n // The caller has already resolved name collisions against live records,\n // so anything still holding this name is stale.\n force: true,\n });\n if (!response.success) throw new Error(response.error ?? `Failed to register runner ${input.name}`);\n const record: RunnerRecord = {\n id: input.id,\n name: input.name,\n pid: input.pid,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n startedAt,\n state: 'running',\n promoted: false,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n ...(input.alarmMs ? { alarm: { intervalMs: input.alarmMs, lastFiredAt: startedAt } } : {}),\n hostPid: process.pid,\n };\n this.writeRecord(record);\n this.notify();\n return record;\n }\n\n async list(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n repositoryPath: this.repositoryPath(),\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listAcrossRepositories(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listBySession(sessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => record.sessionId === sessionId);\n }\n\n async listByRootSession(rootSessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId);\n }\n\n async listAll(sessionId?: string): Promise<RunnerRecord[]> {\n this.paths.ensureDirectories(sessionId);\n const records: RunnerRecord[] = [];\n const directory = this.paths.stateDirectory(sessionId);\n for (const entry of fs.readdirSync(directory)) {\n if (!isPrimaryMetadataEntry(entry)) continue;\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(`${directory}/${entry}`, 'utf8'));\n if (isRunnerRecord(parsed)) records.push(parsed);\n } catch (error) {\n process.emitWarning(`Could not read runner metadata ${entry}: ${String(error)}`);\n }\n }\n return records.sort((left, right) => right.startedAt.localeCompare(left.startedAt));\n }\n\n async get(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const target = this.paths.statePathFor(id, sessionId);\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (!isRunnerRecord(parsed) || parsed.id !== id) {\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: invalid runner record`);\n return undefined;\n }\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === NOT_FOUND_ERROR_CODE) return undefined;\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: ${String(error)}`);\n return undefined;\n }\n }\n\n async markPromoted(id: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record) return undefined;\n const promoted = { ...record, promoted: true };\n this.writeRecord(promoted);\n this.notify();\n return promoted;\n }\n\n async clearAlarm(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id, sessionId);\n if (!record) return undefined;\n if (!record.alarm) return record;\n const cleared: RunnerRecord = { ...record };\n delete cleared.alarm;\n this.writeRecord(cleared);\n this.notify();\n return cleared;\n }\n\n async markAlarmFired(id: string, firedAt: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record?.alarm || record.state !== 'running') return undefined;\n const fired: RunnerRecord = { ...record, alarm: { ...record.alarm, lastFiredAt: firedAt } };\n this.writeRecord(fired);\n this.notify();\n return fired;\n }\n\n async complete(id: string, outcome: CompleteRunnerInput, sessionId?: string): Promise<RunnerRecord | undefined> {\n const persisted = await this.get(id, sessionId);\n if (persisted?.state === 'completed') {\n await this.release(id);\n return persisted;\n }\n const active = await this.list();\n const record =\n persisted ??\n active.find((candidate) => candidate.id === id && (sessionId === undefined || candidate.sessionId === sessionId));\n if (!record) {\n const belongsToAnotherSession =\n sessionId !== undefined && active.some((candidate) => candidate.id === id && candidate.sessionId !== sessionId);\n if (!belongsToAnotherSession) await this.release(id);\n return undefined;\n }\n const completed: RunnerRecord = {\n ...record,\n state: 'completed',\n exit: { ...outcome, finishedAt: new Date().toISOString() },\n };\n this.writeRecord(completed);\n await this.release(id);\n return completed;\n }\n\n async release(id: string): Promise<void> {\n const response = await this.registry.releaseProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${id}`,\n serviceType: SERVICE_TYPE,\n kill: false,\n releasePort: false,\n force: true,\n });\n if (!response.success && !response.error?.includes('No matching process entry')) {\n throw new Error(response.error ?? `Failed to release runner ${id}`);\n }\n this.notify();\n }\n\n async pruneDead(): Promise<string[]> {\n const dead = (await this.list()).filter((record) => !this.processControl.isAlive(record.pid));\n for (const record of dead) {\n await this.complete(record.id, { reason: 'backend_lost', code: null, signal: null }, record.sessionId);\n }\n return dead.map((record) => record.id);\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n close(): void {\n this.listeners.clear();\n this.registry.close();\n }\n\n private notify(): void {\n for (const listener of this.listeners) listener();\n }\n\n private repositoryPath(): string {\n return normalizeRepositoryPath(this.paths.repositoryPath());\n }\n\n private writeRecord(record: RunnerRecord): void {\n this.paths.ensureDirectories(record.sessionId);\n const target = this.paths.statePathFor(record.id, record.sessionId);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\\n`, 'utf8');\n fs.renameSync(temporary, target);\n }\n}\n\n/** Every field is read back from metadata this class wrote on registration. */\nfunction toRunnerRecord(record: ProcessRegistryRecord): RunnerRecord {\n return {\n id: readString(record, 'id'),\n name: readString(record, 'name'),\n pid: record.pid,\n command: readString(record, 'command'),\n cwd: readString(record, 'cwd'),\n logPath: readString(record, 'logPath'),\n interactive: record.metadata?.interactive === true,\n sessionId: readString(record, 'sessionId'),\n ...(readString(record, 'rootSessionId') ? { rootSessionId: readString(record, 'rootSessionId') } : {}),\n startedAt: readString(record, 'startedAt'),\n state: 'running',\n promoted: false,\n backend: readBackend(record.metadata?.backend),\n ...(readString(record, 'backendTarget') ? { backendTarget: readString(record, 'backendTarget') } : {}),\n hostPid: readNumber(record, 'hostPid'),\n };\n}\n\nfunction readBackend(value: unknown): RunnerRecord['backend'] {\n return value === 'rmux' || value === 'tmux' ? value : 'native';\n}\n\nfunction isPrimaryMetadataEntry(entry: string): boolean {\n return (\n entry.endsWith(JSON_EXTENSION) && !entry.endsWith(COMMAND_SIDECAR_SUFFIX) && !entry.endsWith(EXIT_SIDECAR_SUFFIX)\n );\n}\n\nfunction isRunnerRecord(value: unknown): value is RunnerRecord {\n if (typeof value !== 'object' || value === null) return false;\n const record = value as Partial<RunnerRecord>;\n return (\n typeof record.id === 'string' &&\n typeof record.name === 'string' &&\n typeof record.pid === 'number' &&\n typeof record.command === 'string' &&\n typeof record.cwd === 'string' &&\n typeof record.logPath === 'string' &&\n typeof record.sessionId === 'string' &&\n typeof record.startedAt === 'string' &&\n (record.state === 'running' || record.state === 'completed') &&\n typeof record.promoted === 'boolean' &&\n (record.backend === 'rmux' || record.backend === 'tmux' || record.backend === 'native')\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ const e=`Active background runners survived compaction:`;function t(t){if(t.length===0)return``;let n=t.flatMap(e=>[`- ${e.name} (${e.id}), log: ${e.logPath}`,` Check: doom-runner status ${e.id}`,` Stream: doom-runner logs ${e.id} --follow`]);return[e,...n,`Use these CLI commands to inspect existing runners before launching another command.`].join(`
2
+ `)}function n(n,r){return r.length===0||n.includes(e)?n:[n,``,t(r)].join(`
3
+ `)}function r(e,n){e.on(`session_compact`,async()=>{let r=n.getSessionId();if(!r)return;let i;try{i=await n.listBySession(r)}catch{return}let a=t(i);a&&e.sendMessage({customType:`doom-runner-compaction`,content:a,display:!1,details:{sessionId:r,runnerIds:i.map(e=>e.id)}},{triggerTurn:!1,deliverAs:`steer`})})}exports.activeRunnerRecovery=t,exports.appendActiveRunnersToSummary=n,exports.registerRunnerCompactionRecovery=r;
4
+ //# sourceMappingURL=compaction.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.cjs","names":[],"sources":["../../../src/services/runs/compaction.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { RunnerRecord } from '../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"}
@@ -0,0 +1,14 @@
1
+ import { RunnerRecord } from "../RunnerRegistry/types.cjs";
2
+ import "../RunnerRegistry/index.cjs";
3
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ //#region src/services/runs/compaction.d.ts
5
+ interface RunnerCompactionDependencies {
6
+ getSessionId(): string | undefined;
7
+ listBySession(sessionId: string): Promise<RunnerRecord[]>;
8
+ }
9
+ declare function activeRunnerRecovery(active: readonly RunnerRecord[]): string;
10
+ declare function appendActiveRunnersToSummary(summary: string, active: readonly RunnerRecord[]): string;
11
+ declare function registerRunnerCompactionRecovery(pi: ExtensionAPI, dependencies: RunnerCompactionDependencies): void;
12
+ //#endregion
13
+ export { RunnerCompactionDependencies, activeRunnerRecovery, appendActiveRunnersToSummary, registerRunnerCompactionRecovery };
14
+ //# sourceMappingURL=compaction.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.d.cts","names":[],"sources":["../../../src/services/runs/compaction.ts"],"mappings":";;;;UAMiB;EACf;EACA,cAAc,oBAAoB,QAAQ;;iBAG5B,qBAAqB,iBAAiB;iBActC,6BAA6B,iBAAiB,iBAAiB;iBAK/D,iCAAiC,IAAI,cAAc,cAAc"}
@@ -0,0 +1,14 @@
1
+ import { RunnerRecord } from "../RunnerRegistry/types.mjs";
2
+ import "../RunnerRegistry/index.mjs";
3
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ //#region src/services/runs/compaction.d.ts
5
+ interface RunnerCompactionDependencies {
6
+ getSessionId(): string | undefined;
7
+ listBySession(sessionId: string): Promise<RunnerRecord[]>;
8
+ }
9
+ declare function activeRunnerRecovery(active: readonly RunnerRecord[]): string;
10
+ declare function appendActiveRunnersToSummary(summary: string, active: readonly RunnerRecord[]): string;
11
+ declare function registerRunnerCompactionRecovery(pi: ExtensionAPI, dependencies: RunnerCompactionDependencies): void;
12
+ //#endregion
13
+ export { RunnerCompactionDependencies, activeRunnerRecovery, appendActiveRunnersToSummary, registerRunnerCompactionRecovery };
14
+ //# sourceMappingURL=compaction.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.d.mts","names":[],"sources":["../../../src/services/runs/compaction.ts"],"mappings":";;;;UAMiB;EACf;EACA,cAAc,oBAAoB,QAAQ;;iBAG5B,qBAAqB,iBAAiB;iBActC,6BAA6B,iBAAiB,iBAAiB;iBAK/D,iCAAiC,IAAI,cAAc,cAAc"}
@@ -0,0 +1,4 @@
1
+ const e=`Active background runners survived compaction:`;function t(t){if(t.length===0)return``;let n=t.flatMap(e=>[`- ${e.name} (${e.id}), log: ${e.logPath}`,` Check: doom-runner status ${e.id}`,` Stream: doom-runner logs ${e.id} --follow`]);return[e,...n,`Use these CLI commands to inspect existing runners before launching another command.`].join(`
2
+ `)}function n(n,r){return r.length===0||n.includes(e)?n:[n,``,t(r)].join(`
3
+ `)}function r(e,n){e.on(`session_compact`,async()=>{let r=n.getSessionId();if(!r)return;let i;try{i=await n.listBySession(r)}catch{return}let a=t(i);a&&e.sendMessage({customType:`doom-runner-compaction`,content:a,display:!1,details:{sessionId:r,runnerIds:i.map(e=>e.id)}},{triggerTurn:!1,deliverAs:`steer`})})}export{t as activeRunnerRecovery,n as appendActiveRunnersToSummary,r as registerRunnerCompactionRecovery};
4
+ //# sourceMappingURL=compaction.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.mjs","names":[],"sources":["../../../src/services/runs/compaction.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { RunnerRecord } from '../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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);const n=`completed`,r=`rmux`;async function i(e,t,n){return e.backend===r&&e.backendTarget&&await n.stop(e.backendTarget,e.pid)?!0:t.stop(e.pid)}async function a(e){let t={reclaimed:[],errors:[]},a=e.active??await e.registry.list();for(let o of a)try{if((await e.registry.get(o.id,o.sessionId))?.state===n){await e.registry.release(o.id),t.reclaimed.push(o.id);continue}let a=o.backend===r?e.rmuxBackend.readOutcome(o.id,o.sessionId):void 0;if(a){await e.registry.complete(o.id,{reason:a.signal?`signaled`:a.code===0?n:`failed`,code:a.code,signal:a.signal},o.sessionId),t.reclaimed.push(o.id);continue}if(!e.processControl.isAlive(o.pid)){await e.registry.complete(o.id,{reason:`backend_lost`,code:null,signal:null},o.sessionId),t.reclaimed.push(o.id);continue}let s=e.startup&&o.hostPid===e.currentHostPid,c=!e.processControl.isAlive(o.hostPid);if(!s&&!c)continue;if(!await i(o,e.launcher,e.rmuxBackend)&&e.processControl.isAlive(o.pid)){t.errors.push(`Could not stop orphaned runner ${o.id}`);continue}await e.registry.complete(o.id,{reason:`stopped`,code:null,signal:`SIGTERM`,stopReason:`owner session ended`},o.sessionId),t.reclaimed.push(o.id)}catch(e){t.errors.push(`Could not reconcile runner ${o.id}: ${String(e)}`)}return t}function o(e,n){let r=t.default.relative(t.default.resolve(e),t.default.resolve(n));return r===``||!r.startsWith(`..`)&&!t.default.isAbsolute(r)}async function s(e){let t={reclaimed:[],errors:[]},n=e.paths.legacyDirectory();if(!n)return t;let r=(await e.registry.list()).filter(e=>o(n,e.logPath));for(let n of r){let r=e.processControl.isAlive(n.pid),a=e.processControl.isAlive(n.hostPid),o=n.hostPid===e.currentHostPid;if(!(r&&a&&!o))try{if(r&&!await i(n,e.launcher,e.rmuxBackend)&&e.processControl.isAlive(n.pid)){t.errors.push(`Could not stop legacy runner ${n.id}`);continue}await e.registry.release(n.id),t.reclaimed.push(n.id)}catch(e){t.errors.push(`Could not reclaim legacy runner ${n.id}: ${String(e)}`)}}let a;try{a=(await e.registry.listAcrossRepositories()).filter(t=>o(n,t.logPath)&&e.processControl.isAlive(t.pid))}catch(e){return t.errors.push(`Could not verify legacy runner dependencies: ${String(e)}`),t}return a.length===0&&(t.removed=e.paths.removeLegacyStore()),t}exports.cleanupLegacyRunnerStore=s,exports.reconcileActiveRunners=a,exports.stopRunnerProcess=i;
2
+ //# sourceMappingURL=reconcile.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.cjs","names":["path"],"sources":["../../../src/services/runs/reconcile.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ILauncher } from '../Launcher/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRmuxBackend } from '../RmuxBackend/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { IRunnerRegistry, RunnerRecord } from '../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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"}
@@ -0,0 +1,37 @@
1
+ import { IRunnerPaths } from "../RunnerPaths/types.cjs";
2
+ import "../RunnerPaths/index.cjs";
3
+ import { IProcessControl } from "../ProcessControl/types.cjs";
4
+ import "../ProcessControl/index.cjs";
5
+ import { ILauncher } from "../Launcher/types.cjs";
6
+ import "../Launcher/index.cjs";
7
+ import { IRmuxBackend } from "../RmuxBackend/types.cjs";
8
+ import "../RmuxBackend/index.cjs";
9
+ import { IRunnerRegistry, RunnerRecord } from "../RunnerRegistry/types.cjs";
10
+ import "../RunnerRegistry/index.cjs";
11
+ //#region src/services/runs/reconcile.d.ts
12
+ interface ReconcileDependencies {
13
+ registry: IRunnerRegistry;
14
+ launcher: ILauncher;
15
+ rmuxBackend: IRmuxBackend;
16
+ processControl: IProcessControl;
17
+ currentHostPid: number;
18
+ startup: boolean;
19
+ active?: readonly RunnerRecord[];
20
+ }
21
+ interface RunnerReconcileResult {
22
+ reclaimed: string[];
23
+ errors: string[];
24
+ }
25
+ declare function stopRunnerProcess(record: RunnerRecord, launcher: ILauncher, rmuxBackend: IRmuxBackend): Promise<boolean>;
26
+ /** Repairs active registry entries that lost their original completion observer. */
27
+ declare function reconcileActiveRunners(dependencies: ReconcileDependencies): Promise<RunnerReconcileResult>;
28
+ interface LegacyCleanupDependencies extends Omit<ReconcileDependencies, 'startup'> {
29
+ paths: IRunnerPaths;
30
+ }
31
+ /** Deletes the legacy Git store only after no live foreign runner uses it. */
32
+ declare function cleanupLegacyRunnerStore(dependencies: LegacyCleanupDependencies): Promise<RunnerReconcileResult & {
33
+ removed?: string;
34
+ }>;
35
+ //#endregion
36
+ export { RunnerReconcileResult, cleanupLegacyRunnerStore, reconcileActiveRunners, stopRunnerProcess };
37
+ //# sourceMappingURL=reconcile.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.cts","names":[],"sources":["../../../src/services/runs/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"}
@@ -0,0 +1,37 @@
1
+ import { IRunnerPaths } from "../RunnerPaths/types.mjs";
2
+ import "../RunnerPaths/index.mjs";
3
+ import { IProcessControl } from "../ProcessControl/types.mjs";
4
+ import "../ProcessControl/index.mjs";
5
+ import { ILauncher } from "../Launcher/types.mjs";
6
+ import "../Launcher/index.mjs";
7
+ import { IRmuxBackend } from "../RmuxBackend/types.mjs";
8
+ import "../RmuxBackend/index.mjs";
9
+ import { IRunnerRegistry, RunnerRecord } from "../RunnerRegistry/types.mjs";
10
+ import "../RunnerRegistry/index.mjs";
11
+ //#region src/services/runs/reconcile.d.ts
12
+ interface ReconcileDependencies {
13
+ registry: IRunnerRegistry;
14
+ launcher: ILauncher;
15
+ rmuxBackend: IRmuxBackend;
16
+ processControl: IProcessControl;
17
+ currentHostPid: number;
18
+ startup: boolean;
19
+ active?: readonly RunnerRecord[];
20
+ }
21
+ interface RunnerReconcileResult {
22
+ reclaimed: string[];
23
+ errors: string[];
24
+ }
25
+ declare function stopRunnerProcess(record: RunnerRecord, launcher: ILauncher, rmuxBackend: IRmuxBackend): Promise<boolean>;
26
+ /** Repairs active registry entries that lost their original completion observer. */
27
+ declare function reconcileActiveRunners(dependencies: ReconcileDependencies): Promise<RunnerReconcileResult>;
28
+ interface LegacyCleanupDependencies extends Omit<ReconcileDependencies, 'startup'> {
29
+ paths: IRunnerPaths;
30
+ }
31
+ /** Deletes the legacy Git store only after no live foreign runner uses it. */
32
+ declare function cleanupLegacyRunnerStore(dependencies: LegacyCleanupDependencies): Promise<RunnerReconcileResult & {
33
+ removed?: string;
34
+ }>;
35
+ //#endregion
36
+ export { RunnerReconcileResult, cleanupLegacyRunnerStore, reconcileActiveRunners, stopRunnerProcess };
37
+ //# sourceMappingURL=reconcile.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.mts","names":[],"sources":["../../../src/services/runs/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"}
@@ -0,0 +1,2 @@
1
+ import e from"node:path";const t=`completed`,n=`rmux`;async function r(e,t,r){return e.backend===n&&e.backendTarget&&await r.stop(e.backendTarget,e.pid)?!0:t.stop(e.pid)}async function i(e){let i={reclaimed:[],errors:[]},a=e.active??await e.registry.list();for(let o of a)try{if((await e.registry.get(o.id,o.sessionId))?.state===t){await e.registry.release(o.id),i.reclaimed.push(o.id);continue}let a=o.backend===n?e.rmuxBackend.readOutcome(o.id,o.sessionId):void 0;if(a){await e.registry.complete(o.id,{reason:a.signal?`signaled`:a.code===0?t:`failed`,code:a.code,signal:a.signal},o.sessionId),i.reclaimed.push(o.id);continue}if(!e.processControl.isAlive(o.pid)){await e.registry.complete(o.id,{reason:`backend_lost`,code:null,signal:null},o.sessionId),i.reclaimed.push(o.id);continue}let s=e.startup&&o.hostPid===e.currentHostPid,c=!e.processControl.isAlive(o.hostPid);if(!s&&!c)continue;if(!await r(o,e.launcher,e.rmuxBackend)&&e.processControl.isAlive(o.pid)){i.errors.push(`Could not stop orphaned runner ${o.id}`);continue}await e.registry.complete(o.id,{reason:`stopped`,code:null,signal:`SIGTERM`,stopReason:`owner session ended`},o.sessionId),i.reclaimed.push(o.id)}catch(e){i.errors.push(`Could not reconcile runner ${o.id}: ${String(e)}`)}return i}function a(t,n){let r=e.relative(e.resolve(t),e.resolve(n));return r===``||!r.startsWith(`..`)&&!e.isAbsolute(r)}async function o(e){let t={reclaimed:[],errors:[]},n=e.paths.legacyDirectory();if(!n)return t;let i=(await e.registry.list()).filter(e=>a(n,e.logPath));for(let n of i){let i=e.processControl.isAlive(n.pid),a=e.processControl.isAlive(n.hostPid),o=n.hostPid===e.currentHostPid;if(!(i&&a&&!o))try{if(i&&!await r(n,e.launcher,e.rmuxBackend)&&e.processControl.isAlive(n.pid)){t.errors.push(`Could not stop legacy runner ${n.id}`);continue}await e.registry.release(n.id),t.reclaimed.push(n.id)}catch(e){t.errors.push(`Could not reclaim legacy runner ${n.id}: ${String(e)}`)}}let o;try{o=(await e.registry.listAcrossRepositories()).filter(t=>a(n,t.logPath)&&e.processControl.isAlive(t.pid))}catch(e){return t.errors.push(`Could not verify legacy runner dependencies: ${String(e)}`),t}return o.length===0&&(t.removed=e.paths.removeLegacyStore()),t}export{o as cleanupLegacyRunnerStore,i as reconcileActiveRunners,r as stopRunnerProcess};
2
+ //# sourceMappingURL=reconcile.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.mjs","names":[],"sources":["../../../src/services/runs/reconcile.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ILauncher } from '../Launcher/index.ts';\nimport type { IProcessControl } from '../ProcessControl/index.ts';\nimport type { IRmuxBackend } from '../RmuxBackend/index.ts';\nimport type { IRunnerPaths } from '../RunnerPaths/index.ts';\nimport type { IRunnerRegistry, RunnerRecord } from '../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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"}
@@ -0,0 +1,2 @@
1
+ const e=`PI_SESSION_ID`;function t(t=process.env){return t[e]?.trim()||void 0}function n(n=process.env){let r=t(n);if(!r)throw Error(`${e} is required for doom-runner`);return r}exports.PI_SESSION_ID_ENV=e,exports.inheritedSessionId=t,exports.requiredSessionId=n;
2
+ //# sourceMappingURL=session.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.cjs","names":[],"sources":["../../../src/services/runs/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"}
@@ -0,0 +1,10 @@
1
+ //#region src/services/runs/session.d.ts
2
+ /** Session identity inherited by commands launched from a Pi session. */
3
+ declare const PI_SESSION_ID_ENV = "PI_SESSION_ID";
4
+ /** Returns a usable inherited session id, failing closed on blank values. */
5
+ declare function inheritedSessionId(env?: NodeJS.ProcessEnv): string | undefined;
6
+ /** Returns the inherited Pi session id or fails closed outside a session. */
7
+ declare function requiredSessionId(env?: NodeJS.ProcessEnv): string;
8
+ //#endregion
9
+ export { PI_SESSION_ID_ENV, inheritedSessionId, requiredSessionId };
10
+ //# sourceMappingURL=session.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.cts","names":[],"sources":["../../../src/services/runs/session.ts"],"mappings":";;cACa;;iBAGG,mBAAmB,MAAK,OAAO;;iBAM/B,kBAAkB,MAAK,OAAO"}
@@ -0,0 +1,10 @@
1
+ //#region src/services/runs/session.d.ts
2
+ /** Session identity inherited by commands launched from a Pi session. */
3
+ declare const PI_SESSION_ID_ENV = "PI_SESSION_ID";
4
+ /** Returns a usable inherited session id, failing closed on blank values. */
5
+ declare function inheritedSessionId(env?: NodeJS.ProcessEnv): string | undefined;
6
+ /** Returns the inherited Pi session id or fails closed outside a session. */
7
+ declare function requiredSessionId(env?: NodeJS.ProcessEnv): string;
8
+ //#endregion
9
+ export { PI_SESSION_ID_ENV, inheritedSessionId, requiredSessionId };
10
+ //# sourceMappingURL=session.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.mts","names":[],"sources":["../../../src/services/runs/session.ts"],"mappings":";;cACa;;iBAGG,mBAAmB,MAAK,OAAO;;iBAM/B,kBAAkB,MAAK,OAAO"}
@@ -0,0 +1,2 @@
1
+ const e=`PI_SESSION_ID`;function t(t=process.env){return t[e]?.trim()||void 0}function n(n=process.env){let r=t(n);if(!r)throw Error(`${e} is required for doom-runner`);return r}export{e as PI_SESSION_ID_ENV,t as inheritedSessionId,n as requiredSessionId};
2
+ //# sourceMappingURL=session.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.mjs","names":[],"sources":["../../../src/services/runs/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"}
package/dist/session.cjs CHANGED
@@ -1,2 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=`PI_SESSION_ID`;function t(t=process.env){return t[e]?.trim()||void 0}function n(n=process.env){let r=t(n);if(!r)throw Error(`${e} is required for doom-runner`);return r}exports.PI_SESSION_ID_ENV=e,exports.inheritedSessionId=t,exports.requiredSessionId=n;
2
- //# sourceMappingURL=session.cjs.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/runs/session.cjs");exports.PI_SESSION_ID_ENV=e.PI_SESSION_ID_ENV,exports.inheritedSessionId=e.inheritedSessionId,exports.requiredSessionId=e.requiredSessionId;
@@ -1,10 +1,2 @@
1
- //#region src/session.d.ts
2
- /** Session identity inherited by commands launched from a Pi session. */
3
- declare const PI_SESSION_ID_ENV = "PI_SESSION_ID";
4
- /** Returns a usable inherited session id, failing closed on blank values. */
5
- declare function inheritedSessionId(env?: NodeJS.ProcessEnv): string | undefined;
6
- /** Returns the inherited Pi session id or fails closed outside a session. */
7
- declare function requiredSessionId(env?: NodeJS.ProcessEnv): string;
8
- //#endregion
9
- export { PI_SESSION_ID_ENV, inheritedSessionId, requiredSessionId };
10
- //# sourceMappingURL=session.d.cts.map
1
+ import { PI_SESSION_ID_ENV, inheritedSessionId, requiredSessionId } from "./services/runs/session.cjs";
2
+ export { PI_SESSION_ID_ENV, inheritedSessionId, requiredSessionId };