@agimon-ai/doompi-team 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 (727) hide show
  1. package/README.md +33 -17
  2. package/dist/adapters/asyncJobTracker.cjs +2 -0
  3. package/dist/adapters/asyncJobTracker.cjs.map +1 -0
  4. package/dist/adapters/asyncJobTracker.mjs +2 -0
  5. package/dist/adapters/asyncJobTracker.mjs.map +1 -0
  6. package/dist/adapters/atomicJson.cjs +2 -0
  7. package/dist/adapters/atomicJson.cjs.map +1 -0
  8. package/dist/adapters/atomicJson.mjs +2 -0
  9. package/dist/adapters/atomicJson.mjs.map +1 -0
  10. package/dist/adapters/pi/extension.cjs +2 -0
  11. package/dist/adapters/pi/extension.cjs.map +1 -0
  12. package/dist/adapters/pi/extension.mjs +2 -0
  13. package/dist/adapters/pi/extension.mjs.map +1 -0
  14. package/dist/adapters/pollScheduler.cjs +2 -0
  15. package/dist/adapters/pollScheduler.cjs.map +1 -0
  16. package/dist/adapters/pollScheduler.mjs +2 -0
  17. package/dist/adapters/pollScheduler.mjs.map +1 -0
  18. package/dist/adapters/process/childTranscript.cjs +5 -0
  19. package/dist/adapters/process/childTranscript.cjs.map +1 -0
  20. package/dist/adapters/process/childTranscript.mjs +5 -0
  21. package/dist/adapters/process/childTranscript.mjs.map +1 -0
  22. package/dist/adapters/process/env.cjs +2 -0
  23. package/dist/adapters/process/env.cjs.map +1 -0
  24. package/dist/adapters/process/env.d.cts +69 -0
  25. package/dist/adapters/process/env.d.cts.map +1 -0
  26. package/dist/adapters/process/env.d.mts +69 -0
  27. package/dist/adapters/process/env.d.mts.map +1 -0
  28. package/dist/adapters/process/env.mjs +2 -0
  29. package/dist/adapters/process/env.mjs.map +1 -0
  30. package/dist/adapters/process/sdkRunnerEntry.cjs +2 -0
  31. package/dist/adapters/process/sdkRunnerEntry.cjs.map +1 -0
  32. package/dist/adapters/process/sdkRunnerEntry.d.cts +5 -0
  33. package/dist/adapters/process/sdkRunnerEntry.d.cts.map +1 -0
  34. package/dist/adapters/process/sdkRunnerEntry.d.mts +5 -0
  35. package/dist/adapters/process/sdkRunnerEntry.d.mts.map +1 -0
  36. package/dist/adapters/process/sdkRunnerEntry.mjs +2 -0
  37. package/dist/adapters/process/sdkRunnerEntry.mjs.map +1 -0
  38. package/dist/adapters/processTerminal.cjs +2 -0
  39. package/dist/adapters/processTerminal.cjs.map +1 -0
  40. package/dist/adapters/processTerminal.mjs +2 -0
  41. package/dist/adapters/processTerminal.mjs.map +1 -0
  42. package/dist/adapters/resultWatcher.cjs +2 -0
  43. package/dist/adapters/resultWatcher.cjs.map +1 -0
  44. package/dist/adapters/resultWatcher.mjs +2 -0
  45. package/dist/adapters/resultWatcher.mjs.map +1 -0
  46. package/dist/adapters/runIdResolver.cjs +2 -0
  47. package/dist/adapters/runIdResolver.cjs.map +1 -0
  48. package/dist/adapters/runIdResolver.mjs +2 -0
  49. package/dist/adapters/runIdResolver.mjs.map +1 -0
  50. package/dist/adapters/runRegistry.cjs +2 -0
  51. package/dist/adapters/runRegistry.cjs.map +1 -0
  52. package/dist/adapters/runRegistry.mjs +2 -0
  53. package/dist/adapters/runRegistry.mjs.map +1 -0
  54. package/dist/adapters/scopeOwner.cjs +2 -0
  55. package/dist/adapters/scopeOwner.cjs.map +1 -0
  56. package/dist/adapters/scopeOwner.mjs +2 -0
  57. package/dist/adapters/scopeOwner.mjs.map +1 -0
  58. package/dist/adapters/staleRunReconciler.cjs +2 -0
  59. package/dist/adapters/staleRunReconciler.cjs.map +1 -0
  60. package/dist/adapters/staleRunReconciler.mjs +2 -0
  61. package/dist/adapters/staleRunReconciler.mjs.map +1 -0
  62. package/dist/adapters/statusReader.cjs +2 -0
  63. package/dist/adapters/statusReader.cjs.map +1 -0
  64. package/dist/adapters/statusReader.mjs +2 -0
  65. package/dist/adapters/statusReader.mjs.map +1 -0
  66. package/dist/adapters/suspendedRuns.cjs +6 -0
  67. package/dist/adapters/suspendedRuns.cjs.map +1 -0
  68. package/dist/adapters/suspendedRuns.mjs +6 -0
  69. package/dist/adapters/suspendedRuns.mjs.map +1 -0
  70. package/dist/api/backgroundWork.cjs +1 -1
  71. package/dist/api/backgroundWork.d.cts +2 -2
  72. package/dist/api/backgroundWork.d.mts +2 -2
  73. package/dist/api/backgroundWork.mjs +1 -1
  74. package/dist/api/capabilityCeiling.cjs +1 -1
  75. package/dist/api/capabilityCeiling.d.cts +3 -3
  76. package/dist/api/capabilityCeiling.d.mts +3 -3
  77. package/dist/api/capabilityCeiling.mjs +1 -2
  78. package/dist/api/delegation.cjs +1 -1
  79. package/dist/api/delegation.d.cts +1 -1
  80. package/dist/api/delegation.d.mts +1 -1
  81. package/dist/api/delegation.mjs +1 -1
  82. package/dist/api/teamSnapshot.cjs +1 -1
  83. package/dist/api/teamSnapshot.d.cts +2 -2
  84. package/dist/api/teamSnapshot.d.mts +2 -2
  85. package/dist/api/teamSnapshot.mjs +1 -2
  86. package/dist/commands/slash/chainExpression.cjs.map +1 -0
  87. package/dist/commands/slash/chainExpression.mjs.map +1 -0
  88. package/dist/commands/slash/doctor.cjs +5 -0
  89. package/dist/commands/slash/doctor.cjs.map +1 -0
  90. package/dist/commands/slash/doctor.mjs +5 -0
  91. package/dist/{slash/doctor.cjs.map → commands/slash/doctor.mjs.map} +1 -1
  92. package/dist/{slash → commands/slash}/slashCommands.cjs +1 -1
  93. package/dist/commands/slash/slashCommands.cjs.map +1 -0
  94. package/dist/{slash → commands/slash}/slashCommands.mjs +2 -2
  95. package/dist/commands/slash/slashCommands.mjs.map +1 -0
  96. package/dist/commands/slash/spawnRequestMapping.cjs.map +1 -0
  97. package/dist/commands/slash/spawnRequestMapping.mjs.map +1 -0
  98. package/dist/commands/slash/subagentLaunch.cjs +2 -0
  99. package/dist/commands/slash/subagentLaunch.cjs.map +1 -0
  100. package/dist/commands/slash/subagentLaunch.mjs +2 -0
  101. package/dist/commands/slash/subagentLaunch.mjs.map +1 -0
  102. package/dist/container/globalBridge.cjs.map +1 -1
  103. package/dist/container/globalBridge.d.cts +1 -1
  104. package/dist/container/globalBridge.d.mts +1 -1
  105. package/dist/container/globalBridge.mjs.map +1 -1
  106. package/dist/container/index.cjs +1 -1
  107. package/dist/container/index.cjs.map +1 -1
  108. package/dist/container/index.d.cts +2 -1
  109. package/dist/container/index.d.cts.map +1 -1
  110. package/dist/container/index.d.mts +2 -1
  111. package/dist/container/index.d.mts.map +1 -1
  112. package/dist/container/index.mjs +1 -1
  113. package/dist/container/index.mjs.map +1 -1
  114. package/dist/container/modules/agents.cjs +1 -1
  115. package/dist/container/modules/agents.cjs.map +1 -1
  116. package/dist/container/modules/agents.mjs +1 -1
  117. package/dist/container/modules/agents.mjs.map +1 -1
  118. package/dist/container/modules/core.cjs +1 -1
  119. package/dist/container/modules/core.cjs.map +1 -1
  120. package/dist/container/modules/core.mjs +1 -1
  121. package/dist/container/modules/core.mjs.map +1 -1
  122. package/dist/container/modules/extensions.cjs +1 -1
  123. package/dist/container/modules/extensions.cjs.map +1 -1
  124. package/dist/container/modules/extensions.mjs +1 -1
  125. package/dist/container/modules/extensions.mjs.map +1 -1
  126. package/dist/container/modules/intercom.cjs +1 -1
  127. package/dist/container/modules/intercom.cjs.map +1 -1
  128. package/dist/container/modules/intercom.mjs +1 -1
  129. package/dist/container/modules/intercom.mjs.map +1 -1
  130. package/dist/container/modules/runs.cjs +1 -1
  131. package/dist/container/modules/runs.cjs.map +1 -1
  132. package/dist/container/modules/runs.mjs +1 -1
  133. package/dist/container/modules/runs.mjs.map +1 -1
  134. package/dist/container/types.cjs +1 -1
  135. package/dist/container/types.cjs.map +1 -1
  136. package/dist/container/types.d.cts +1 -0
  137. package/dist/container/types.d.cts.map +1 -1
  138. package/dist/container/types.d.mts +1 -0
  139. package/dist/container/types.d.mts.map +1 -1
  140. package/dist/container/types.mjs +1 -1
  141. package/dist/container/types.mjs.map +1 -1
  142. package/dist/env.cjs +1 -2
  143. package/dist/env.d.cts +2 -65
  144. package/dist/env.d.mts +2 -65
  145. package/dist/env.mjs +1 -1
  146. package/dist/env.mjs.map +1 -1
  147. package/dist/extensions/config.cjs.map +1 -1
  148. package/dist/extensions/config.mjs.map +1 -1
  149. package/dist/extensions/delegationBridge.cjs.map +1 -1
  150. package/dist/extensions/delegationBridge.mjs.map +1 -1
  151. package/dist/extensions/doom.cjs +1 -1
  152. package/dist/extensions/doom.cjs.map +1 -1
  153. package/dist/extensions/doom.mjs +1 -1
  154. package/dist/extensions/doom.mjs.map +1 -1
  155. package/dist/extensions/fablePlanBridge.cjs +1 -1
  156. package/dist/extensions/fablePlanBridge.cjs.map +1 -1
  157. package/dist/extensions/fablePlanBridge.mjs +1 -1
  158. package/dist/extensions/fablePlanBridge.mjs.map +1 -1
  159. package/dist/extensions/managementActions.cjs +1 -1
  160. package/dist/extensions/managementActions.cjs.map +1 -1
  161. package/dist/extensions/managementActions.mjs +1 -1
  162. package/dist/extensions/managementActions.mjs.map +1 -1
  163. package/dist/extensions/operationJournal.cjs +1 -1
  164. package/dist/extensions/operationJournal.cjs.map +1 -1
  165. package/dist/extensions/operationJournal.mjs +1 -1
  166. package/dist/extensions/operationJournal.mjs.map +1 -1
  167. package/dist/extensions/orchestratorPrompt.cjs +5 -6
  168. package/dist/extensions/orchestratorPrompt.cjs.map +1 -1
  169. package/dist/extensions/orchestratorPrompt.mjs +5 -6
  170. package/dist/extensions/orchestratorPrompt.mjs.map +1 -1
  171. package/dist/extensions/schemas.mjs.map +1 -1
  172. package/dist/extensions/spawnPlan.cjs +3 -2
  173. package/dist/extensions/spawnPlan.cjs.map +1 -1
  174. package/dist/extensions/spawnPlan.mjs +3 -2
  175. package/dist/extensions/spawnPlan.mjs.map +1 -1
  176. package/dist/extensions/standard.cjs +1 -1
  177. package/dist/extensions/standard.cjs.map +1 -1
  178. package/dist/extensions/standard.mjs +1 -1
  179. package/dist/extensions/standard.mjs.map +1 -1
  180. package/dist/extensions/statusViews.cjs +1 -1
  181. package/dist/extensions/statusViews.cjs.map +1 -1
  182. package/dist/extensions/statusViews.mjs +2 -2
  183. package/dist/extensions/statusViews.mjs.map +1 -1
  184. package/dist/extensions/subagentPromptRuntime.cjs +11 -11
  185. package/dist/extensions/subagentPromptRuntime.cjs.map +1 -1
  186. package/dist/extensions/subagentPromptRuntime.d.cts.map +1 -1
  187. package/dist/extensions/subagentPromptRuntime.d.mts.map +1 -1
  188. package/dist/extensions/subagentPromptRuntime.mjs +5 -5
  189. package/dist/extensions/subagentPromptRuntime.mjs.map +1 -1
  190. package/dist/extensions/subagentTool.cjs +5 -6
  191. package/dist/extensions/subagentTool.cjs.map +1 -1
  192. package/dist/extensions/subagentTool.mjs +5 -6
  193. package/dist/extensions/subagentTool.mjs.map +1 -1
  194. package/dist/extensions/toolDescription.cjs +1 -1
  195. package/dist/extensions/toolDescription.cjs.map +1 -1
  196. package/dist/extensions/toolDescription.mjs +1 -1
  197. package/dist/extensions/toolDescription.mjs.map +1 -1
  198. package/dist/index.cjs +1 -1
  199. package/dist/index.cjs.map +1 -1
  200. package/dist/index.d.cts +2 -2
  201. package/dist/index.d.cts.map +1 -1
  202. package/dist/index.d.mts +2 -2
  203. package/dist/index.d.mts.map +1 -1
  204. package/dist/index.mjs +1 -1
  205. package/dist/index.mjs.map +1 -1
  206. package/dist/runs/background/cliRunnerEntry.cjs +1 -2
  207. package/dist/runs/background/cliRunnerEntry.mjs +1 -2
  208. package/dist/runs/sdkRunnerEntry.cjs +1 -2
  209. package/dist/runs/sdkRunnerEntry.d.cts +2 -1
  210. package/dist/runs/sdkRunnerEntry.d.mts +2 -1
  211. package/dist/runs/sdkRunnerEntry.mjs +1 -2
  212. package/dist/schemas/team/backgroundWork.cjs +2 -0
  213. package/dist/schemas/team/backgroundWork.cjs.map +1 -0
  214. package/dist/{protocol → schemas/team}/backgroundWork.d.cts +2 -2
  215. package/dist/schemas/team/backgroundWork.d.cts.map +1 -0
  216. package/dist/{protocol → schemas/team}/backgroundWork.d.mts +2 -2
  217. package/dist/schemas/team/backgroundWork.d.mts.map +1 -0
  218. package/dist/schemas/team/backgroundWork.mjs +2 -0
  219. package/dist/{protocol/backgroundWork.cjs.map → schemas/team/backgroundWork.mjs.map} +1 -1
  220. package/dist/schemas/team/capabilityCeiling.cjs +2 -0
  221. package/dist/schemas/team/capabilityCeiling.cjs.map +1 -0
  222. package/dist/{protocol → schemas/team}/capabilityCeiling.d.cts +3 -3
  223. package/dist/schemas/team/capabilityCeiling.d.cts.map +1 -0
  224. package/dist/{protocol → schemas/team}/capabilityCeiling.d.mts +3 -3
  225. package/dist/schemas/team/capabilityCeiling.d.mts.map +1 -0
  226. package/dist/schemas/team/capabilityCeiling.mjs +2 -0
  227. package/dist/schemas/team/capabilityCeiling.mjs.map +1 -0
  228. package/dist/schemas/team/delegation.cjs.map +1 -0
  229. package/dist/{protocol → schemas/team}/delegation.d.cts +1 -1
  230. package/dist/schemas/team/delegation.d.cts.map +1 -0
  231. package/dist/{protocol → schemas/team}/delegation.d.mts +1 -1
  232. package/dist/schemas/team/delegation.d.mts.map +1 -0
  233. package/dist/schemas/team/delegation.mjs.map +1 -0
  234. package/dist/schemas/team/packageConfig.cjs +2 -0
  235. package/dist/schemas/team/packageConfig.cjs.map +1 -0
  236. package/dist/schemas/team/packageConfig.mjs +2 -0
  237. package/dist/schemas/team/packageConfig.mjs.map +1 -0
  238. package/dist/services/agents/discovery.cjs +2 -0
  239. package/dist/services/agents/discovery.cjs.map +1 -0
  240. package/dist/services/agents/discovery.mjs +2 -0
  241. package/dist/services/agents/discovery.mjs.map +1 -0
  242. package/dist/services/agents/frontmatter.cjs.map +1 -0
  243. package/dist/{agents → services/agents}/frontmatter.mjs +1 -1
  244. package/dist/services/agents/frontmatter.mjs.map +1 -0
  245. package/dist/services/agents/loader.cjs +2 -0
  246. package/dist/services/agents/loader.cjs.map +1 -0
  247. package/dist/services/agents/loader.mjs +2 -0
  248. package/dist/services/agents/loader.mjs.map +1 -0
  249. package/dist/services/agents/memory.cjs +3 -0
  250. package/dist/services/agents/memory.cjs.map +1 -0
  251. package/dist/{agents → services/agents}/memory.mjs +1 -1
  252. package/dist/services/agents/memory.mjs.map +1 -0
  253. package/dist/services/agents/projectRoot.cjs +2 -0
  254. package/dist/services/agents/projectRoot.cjs.map +1 -0
  255. package/dist/services/agents/projectRoot.mjs +2 -0
  256. package/dist/services/agents/projectRoot.mjs.map +1 -0
  257. package/dist/services/agents/settings.cjs +2 -0
  258. package/dist/services/agents/settings.cjs.map +1 -0
  259. package/dist/services/agents/settings.mjs +2 -0
  260. package/dist/services/agents/settings.mjs.map +1 -0
  261. package/dist/services/agents/skills.cjs +0 -0
  262. package/dist/services/agents/skills.cjs.map +1 -0
  263. package/dist/services/agents/skills.mjs +5 -0
  264. package/dist/services/agents/skills.mjs.map +1 -0
  265. package/dist/{agents → services/agents}/types.d.cts +4 -4
  266. package/dist/services/agents/types.d.cts.map +1 -0
  267. package/dist/{agents → services/agents}/types.d.mts +4 -4
  268. package/dist/services/agents/types.d.mts.map +1 -0
  269. package/dist/services/api/backgroundWork.cjs +1 -0
  270. package/dist/services/api/backgroundWork.d.cts +1 -0
  271. package/dist/services/api/backgroundWork.d.mts +1 -0
  272. package/dist/services/api/backgroundWork.mjs +1 -0
  273. package/dist/services/api/capabilityCeiling.cjs +1 -0
  274. package/dist/services/api/capabilityCeiling.d.cts +2 -0
  275. package/dist/services/api/capabilityCeiling.d.mts +2 -0
  276. package/dist/services/api/capabilityCeiling.mjs +2 -0
  277. package/dist/services/api/capabilityCeiling.mjs.map +1 -0
  278. package/dist/services/api/teamSnapshot.cjs +1 -0
  279. package/dist/services/api/teamSnapshot.d.cts +1 -0
  280. package/dist/services/api/teamSnapshot.d.mts +1 -0
  281. package/dist/services/api/teamSnapshot.mjs +2 -0
  282. package/dist/services/api/teamSnapshot.mjs.map +1 -0
  283. package/dist/services/intercom/intercomBridge.cjs.map +1 -0
  284. package/dist/services/intercom/intercomBridge.mjs.map +1 -0
  285. package/dist/services/intercom/nativeTeamChannel.cjs +5 -0
  286. package/dist/services/intercom/nativeTeamChannel.cjs.map +1 -0
  287. package/dist/{intercom → services/intercom}/nativeTeamChannel.d.cts +1 -1
  288. package/dist/services/intercom/nativeTeamChannel.d.cts.map +1 -0
  289. package/dist/{intercom → services/intercom}/nativeTeamChannel.d.mts +1 -1
  290. package/dist/services/intercom/nativeTeamChannel.d.mts.map +1 -0
  291. package/dist/services/intercom/nativeTeamChannel.mjs +5 -0
  292. package/dist/services/intercom/nativeTeamChannel.mjs.map +1 -0
  293. package/dist/services/intercom/supervisorControlChannel.cjs +2 -0
  294. package/dist/services/intercom/supervisorControlChannel.cjs.map +1 -0
  295. package/dist/{intercom → services/intercom}/supervisorControlChannel.mjs +1 -1
  296. package/dist/services/intercom/supervisorControlChannel.mjs.map +1 -0
  297. package/dist/services/runs/background/asyncExecution.cjs +2 -0
  298. package/dist/services/runs/background/asyncExecution.cjs.map +1 -0
  299. package/dist/services/runs/background/asyncExecution.mjs +2 -0
  300. package/dist/services/runs/background/asyncExecution.mjs.map +1 -0
  301. package/dist/services/runs/background/asyncJobTracker.cjs +1 -0
  302. package/dist/services/runs/background/asyncJobTracker.mjs +2 -0
  303. package/dist/services/runs/background/asyncJobTracker.mjs.map +1 -0
  304. package/dist/services/runs/background/cliRunnerEntry.cjs +2 -0
  305. package/dist/services/runs/background/cliRunnerEntry.cjs.map +1 -0
  306. package/dist/services/runs/background/cliRunnerEntry.mjs +2 -0
  307. package/dist/services/runs/background/cliRunnerEntry.mjs.map +1 -0
  308. package/dist/services/runs/background/completionBatcher.cjs.map +1 -0
  309. package/dist/services/runs/background/completionBatcher.mjs +2 -0
  310. package/dist/services/runs/background/completionBatcher.mjs.map +1 -0
  311. package/dist/{runs → services/runs}/background/notify.cjs +1 -1
  312. package/dist/services/runs/background/notify.cjs.map +1 -0
  313. package/dist/{runs → services/runs}/background/notify.mjs +1 -1
  314. package/dist/services/runs/background/notify.mjs.map +1 -0
  315. package/dist/services/runs/background/parentWatchdog.cjs.map +1 -0
  316. package/dist/services/runs/background/parentWatchdog.mjs.map +1 -0
  317. package/dist/services/runs/background/processTerminal.cjs +1 -0
  318. package/dist/services/runs/background/processTerminal.mjs +2 -0
  319. package/dist/services/runs/background/processTerminal.mjs.map +1 -0
  320. package/dist/services/runs/background/resultWatcher.cjs +1 -0
  321. package/dist/services/runs/background/resultWatcher.mjs +2 -0
  322. package/dist/services/runs/background/resultWatcher.mjs.map +1 -0
  323. package/dist/services/runs/background/runIdResolver.cjs +1 -0
  324. package/dist/services/runs/background/runIdResolver.mjs +2 -0
  325. package/dist/services/runs/background/runIdResolver.mjs.map +1 -0
  326. package/dist/services/runs/background/runnerBootstrap.cjs +2 -0
  327. package/dist/services/runs/background/runnerBootstrap.cjs.map +1 -0
  328. package/dist/services/runs/background/runnerBootstrap.mjs +2 -0
  329. package/dist/services/runs/background/runnerBootstrap.mjs.map +1 -0
  330. package/dist/services/runs/background/runnerExecution.cjs +2 -0
  331. package/dist/services/runs/background/runnerExecution.cjs.map +1 -0
  332. package/dist/services/runs/background/runnerExecution.mjs +2 -0
  333. package/dist/services/runs/background/runnerExecution.mjs.map +1 -0
  334. package/dist/{runs → services/runs}/background/runnerReporting.cjs +2 -2
  335. package/dist/services/runs/background/runnerReporting.cjs.map +1 -0
  336. package/dist/{runs → services/runs}/background/runnerReporting.mjs +2 -2
  337. package/dist/services/runs/background/runnerReporting.mjs.map +1 -0
  338. package/dist/services/runs/background/runnerTeamMembership.cjs +2 -0
  339. package/dist/services/runs/background/runnerTeamMembership.cjs.map +1 -0
  340. package/dist/services/runs/background/runnerTeamMembership.mjs +2 -0
  341. package/dist/services/runs/background/runnerTeamMembership.mjs.map +1 -0
  342. package/dist/services/runs/background/spawnHandshake.cjs +2 -0
  343. package/dist/services/runs/background/spawnHandshake.cjs.map +1 -0
  344. package/dist/services/runs/background/spawnHandshake.mjs +2 -0
  345. package/dist/services/runs/background/spawnHandshake.mjs.map +1 -0
  346. package/dist/services/runs/background/staleRunReconciler.cjs +1 -0
  347. package/dist/services/runs/background/staleRunReconciler.mjs +2 -0
  348. package/dist/services/runs/background/staleRunReconciler.mjs.map +1 -0
  349. package/dist/services/runs/background/statusReader.cjs +1 -0
  350. package/dist/services/runs/background/statusReader.mjs +2 -0
  351. package/dist/services/runs/background/statusReader.mjs.map +1 -0
  352. package/dist/services/runs/background/statusWriter.cjs +2 -0
  353. package/dist/services/runs/background/statusWriter.cjs.map +1 -0
  354. package/dist/services/runs/background/statusWriter.mjs +2 -0
  355. package/dist/services/runs/background/statusWriter.mjs.map +1 -0
  356. package/dist/services/runs/background/subagentWait.cjs +2 -0
  357. package/dist/services/runs/background/subagentWait.cjs.map +1 -0
  358. package/dist/services/runs/background/subagentWait.mjs +2 -0
  359. package/dist/services/runs/background/subagentWait.mjs.map +1 -0
  360. package/dist/{runs/background/terminalPersistence.mjs → services/runs/background/terminalPersistence.cjs} +2 -2
  361. package/dist/services/runs/background/terminalPersistence.cjs.map +1 -0
  362. package/dist/services/runs/background/terminalPersistence.mjs +2 -0
  363. package/dist/services/runs/background/terminalPersistence.mjs.map +1 -0
  364. package/dist/services/runs/extensionErrorTelemetry.cjs +2 -0
  365. package/dist/services/runs/extensionErrorTelemetry.cjs.map +1 -0
  366. package/dist/services/runs/extensionErrorTelemetry.mjs +2 -0
  367. package/dist/services/runs/extensionErrorTelemetry.mjs.map +1 -0
  368. package/dist/services/runs/registry/processGroup.cjs.map +1 -0
  369. package/dist/services/runs/registry/processGroup.mjs.map +1 -0
  370. package/dist/services/runs/registry/runRegistry.cjs +1 -0
  371. package/dist/services/runs/registry/runRegistry.mjs +2 -0
  372. package/dist/services/runs/registry/runRegistry.mjs.map +1 -0
  373. package/dist/services/runs/registry/sessionLifecycle.cjs +2 -0
  374. package/dist/services/runs/registry/sessionLifecycle.cjs.map +1 -0
  375. package/dist/services/runs/registry/sessionLifecycle.mjs +2 -0
  376. package/dist/services/runs/registry/sessionLifecycle.mjs.map +1 -0
  377. package/dist/services/runs/registry/suspendedRuns.cjs +1 -0
  378. package/dist/services/runs/registry/suspendedRuns.mjs +2 -0
  379. package/dist/services/runs/registry/suspendedRuns.mjs.map +1 -0
  380. package/dist/services/runs/sdkRunnerEntry.cjs +1 -0
  381. package/dist/services/runs/sdkRunnerEntry.mjs +1 -0
  382. package/dist/services/runs/shared/claudeFableProfile.cjs +4 -0
  383. package/dist/services/runs/shared/claudeFableProfile.cjs.map +1 -0
  384. package/dist/services/runs/shared/claudeFableProfile.mjs +4 -0
  385. package/dist/services/runs/shared/claudeFableProfile.mjs.map +1 -0
  386. package/dist/services/runs/shared/depthGuard.cjs +2 -0
  387. package/dist/services/runs/shared/depthGuard.cjs.map +1 -0
  388. package/dist/services/runs/shared/depthGuard.mjs +2 -0
  389. package/dist/services/runs/shared/depthGuard.mjs.map +1 -0
  390. package/dist/services/runs/shared/mcpDirectToolAllowlist.cjs +2 -0
  391. package/dist/services/runs/shared/mcpDirectToolAllowlist.cjs.map +1 -0
  392. package/dist/services/runs/shared/mcpDirectToolAllowlist.mjs +2 -0
  393. package/dist/services/runs/shared/mcpDirectToolAllowlist.mjs.map +1 -0
  394. package/dist/services/runs/shared/modelFallback.cjs +2 -0
  395. package/dist/services/runs/shared/modelFallback.cjs.map +1 -0
  396. package/dist/services/runs/shared/modelFallback.mjs +2 -0
  397. package/dist/services/runs/shared/modelFallback.mjs.map +1 -0
  398. package/dist/services/runs/shared/modelScope.cjs +2 -0
  399. package/dist/services/runs/shared/modelScope.cjs.map +1 -0
  400. package/dist/services/runs/shared/modelScope.mjs +2 -0
  401. package/dist/services/runs/shared/modelScope.mjs.map +1 -0
  402. package/dist/services/runs/shared/nestedPath.cjs +2 -0
  403. package/dist/services/runs/shared/nestedPath.cjs.map +1 -0
  404. package/dist/services/runs/shared/nestedPath.mjs +2 -0
  405. package/dist/services/runs/shared/nestedPath.mjs.map +1 -0
  406. package/dist/services/runs/shared/piArgs.cjs +2 -0
  407. package/dist/services/runs/shared/piArgs.cjs.map +1 -0
  408. package/dist/services/runs/shared/piArgs.mjs +2 -0
  409. package/dist/services/runs/shared/piArgs.mjs.map +1 -0
  410. package/dist/services/runs/shared/piSpawn.cjs +2 -0
  411. package/dist/services/runs/shared/piSpawn.cjs.map +1 -0
  412. package/dist/services/runs/shared/piSpawn.mjs +2 -0
  413. package/dist/services/runs/shared/piSpawn.mjs.map +1 -0
  414. package/dist/services/runs/shared/runWithConcurrency.cjs +2 -0
  415. package/dist/services/runs/shared/runWithConcurrency.cjs.map +1 -0
  416. package/dist/services/runs/shared/runWithConcurrency.d.cts +25 -0
  417. package/dist/services/runs/shared/runWithConcurrency.d.cts.map +1 -0
  418. package/dist/services/runs/shared/runWithConcurrency.d.mts +25 -0
  419. package/dist/services/runs/shared/runWithConcurrency.d.mts.map +1 -0
  420. package/dist/services/runs/shared/runWithConcurrency.mjs +2 -0
  421. package/dist/services/runs/shared/runWithConcurrency.mjs.map +1 -0
  422. package/dist/services/runs/shared/runtimeRegistry.cjs +2 -0
  423. package/dist/services/runs/shared/runtimeRegistry.cjs.map +1 -0
  424. package/dist/services/runs/shared/runtimeRegistry.mjs +2 -0
  425. package/dist/services/runs/shared/runtimeRegistry.mjs.map +1 -0
  426. package/dist/services/runs/shared/structuredOutput.cjs +2 -0
  427. package/dist/{runs → services/runs}/shared/structuredOutput.cjs.map +1 -1
  428. package/dist/services/runs/shared/structuredOutput.mjs +2 -0
  429. package/dist/{runs → services/runs}/shared/structuredOutput.mjs.map +1 -1
  430. package/dist/services/runs/shared/toolAvailability.cjs +2 -0
  431. package/dist/services/runs/shared/toolAvailability.cjs.map +1 -0
  432. package/dist/services/runs/shared/toolAvailability.mjs +2 -0
  433. package/dist/services/runs/shared/toolAvailability.mjs.map +1 -0
  434. package/dist/services/runs/shared/toolBudget.cjs +2 -0
  435. package/dist/services/runs/shared/toolBudget.cjs.map +1 -0
  436. package/dist/services/runs/shared/toolBudget.mjs +2 -0
  437. package/dist/services/runs/shared/toolBudget.mjs.map +1 -0
  438. package/dist/shared/artifacts.cjs.map +1 -1
  439. package/dist/shared/artifacts.mjs.map +1 -1
  440. package/dist/shared/atomicJson.cjs +1 -2
  441. package/dist/shared/atomicJson.mjs +1 -1
  442. package/dist/shared/atomicJson.mjs.map +1 -1
  443. package/dist/shared/childTranscript.cjs +1 -5
  444. package/dist/shared/childTranscript.mjs +1 -4
  445. package/dist/shared/childTranscript.mjs.map +1 -1
  446. package/dist/shared/fileSystemRetry.cjs +1 -1
  447. package/dist/shared/fileSystemRetry.cjs.map +1 -1
  448. package/dist/shared/fileSystemRetry.mjs +1 -1
  449. package/dist/shared/fileSystemRetry.mjs.map +1 -1
  450. package/dist/shared/formatters.cjs.map +1 -1
  451. package/dist/shared/formatters.mjs.map +1 -1
  452. package/dist/shared/paths.cjs +1 -1
  453. package/dist/shared/paths.cjs.map +1 -1
  454. package/dist/shared/paths.mjs +1 -1
  455. package/dist/shared/paths.mjs.map +1 -1
  456. package/dist/shared/pollScheduler.cjs +1 -2
  457. package/dist/shared/pollScheduler.mjs +1 -1
  458. package/dist/shared/pollScheduler.mjs.map +1 -1
  459. package/dist/shared/scopeOwner.cjs +1 -2
  460. package/dist/shared/scopeOwner.mjs +1 -1
  461. package/dist/shared/scopeOwner.mjs.map +1 -1
  462. package/dist/tui/agentCatalog.cjs +1 -1
  463. package/dist/tui/agentCatalog.cjs.map +1 -1
  464. package/dist/tui/agentCatalog.mjs +1 -1
  465. package/dist/tui/agentCatalog.mjs.map +1 -1
  466. package/dist/tui/agentResourceProjection.cjs +1 -1
  467. package/dist/tui/agentResourceProjection.cjs.map +1 -1
  468. package/dist/tui/agentResourceProjection.mjs +1 -1
  469. package/dist/tui/agentResourceProjection.mjs.map +1 -1
  470. package/dist/tui/completionNotice.cjs +1 -1
  471. package/dist/tui/completionNotice.cjs.map +1 -1
  472. package/dist/tui/completionNotice.mjs +1 -1
  473. package/dist/tui/completionNotice.mjs.map +1 -1
  474. package/dist/tui/fleet.cjs +2 -2
  475. package/dist/tui/fleet.cjs.map +1 -1
  476. package/dist/tui/fleet.mjs +2 -2
  477. package/dist/tui/fleet.mjs.map +1 -1
  478. package/dist/tui/fleetAgentView.cjs.map +1 -1
  479. package/dist/tui/fleetAgentView.mjs.map +1 -1
  480. package/dist/tui/fleetStatus.cjs +1 -1
  481. package/dist/tui/fleetStatus.cjs.map +1 -1
  482. package/dist/tui/fleetStatus.mjs +1 -1
  483. package/dist/tui/fleetStatus.mjs.map +1 -1
  484. package/dist/tui/fleetTranscript.cjs +1 -1
  485. package/dist/tui/fleetTranscript.cjs.map +1 -1
  486. package/dist/tui/fleetTranscript.mjs +1 -1
  487. package/dist/tui/fleetTranscript.mjs.map +1 -1
  488. package/dist/tui/register.cjs +1 -1
  489. package/dist/tui/register.cjs.map +1 -1
  490. package/dist/tui/register.mjs +1 -1
  491. package/dist/tui/register.mjs.map +1 -1
  492. package/dist/tui/subagentToolRender.cjs +3 -0
  493. package/dist/tui/subagentToolRender.cjs.map +1 -0
  494. package/dist/tui/subagentToolRender.mjs +3 -0
  495. package/dist/tui/subagentToolRender.mjs.map +1 -0
  496. package/dist/{types.d.cts → types/index.d.cts} +2 -2
  497. package/dist/types/index.d.cts.map +1 -0
  498. package/dist/{types.d.mts → types/index.d.mts} +2 -2
  499. package/dist/types/index.d.mts.map +1 -0
  500. package/package.json +23 -16
  501. package/dist/agents/discovery.cjs +0 -2
  502. package/dist/agents/discovery.cjs.map +0 -1
  503. package/dist/agents/discovery.mjs +0 -2
  504. package/dist/agents/discovery.mjs.map +0 -1
  505. package/dist/agents/frontmatter.cjs.map +0 -1
  506. package/dist/agents/frontmatter.mjs.map +0 -1
  507. package/dist/agents/loader.cjs +0 -2
  508. package/dist/agents/loader.cjs.map +0 -1
  509. package/dist/agents/loader.mjs +0 -2
  510. package/dist/agents/loader.mjs.map +0 -1
  511. package/dist/agents/memory.cjs +0 -3
  512. package/dist/agents/memory.cjs.map +0 -1
  513. package/dist/agents/memory.mjs.map +0 -1
  514. package/dist/agents/projectRoot.cjs +0 -2
  515. package/dist/agents/projectRoot.cjs.map +0 -1
  516. package/dist/agents/projectRoot.mjs +0 -2
  517. package/dist/agents/projectRoot.mjs.map +0 -1
  518. package/dist/agents/settings.cjs +0 -2
  519. package/dist/agents/settings.cjs.map +0 -1
  520. package/dist/agents/settings.mjs +0 -2
  521. package/dist/agents/settings.mjs.map +0 -1
  522. package/dist/agents/skills.cjs +0 -0
  523. package/dist/agents/skills.cjs.map +0 -1
  524. package/dist/agents/skills.mjs +0 -5
  525. package/dist/agents/skills.mjs.map +0 -1
  526. package/dist/agents/types.d.cts.map +0 -1
  527. package/dist/agents/types.d.mts.map +0 -1
  528. package/dist/api/capabilityCeiling.mjs.map +0 -1
  529. package/dist/api/teamSnapshot.mjs.map +0 -1
  530. package/dist/env.cjs.map +0 -1
  531. package/dist/env.d.cts.map +0 -1
  532. package/dist/env.d.mts.map +0 -1
  533. package/dist/extension.cjs +0 -2
  534. package/dist/extension.cjs.map +0 -1
  535. package/dist/extension.mjs +0 -2
  536. package/dist/extension.mjs.map +0 -1
  537. package/dist/intercom/intercomBridge.cjs.map +0 -1
  538. package/dist/intercom/intercomBridge.mjs.map +0 -1
  539. package/dist/intercom/nativeTeamChannel.cjs +0 -5
  540. package/dist/intercom/nativeTeamChannel.cjs.map +0 -1
  541. package/dist/intercom/nativeTeamChannel.d.cts.map +0 -1
  542. package/dist/intercom/nativeTeamChannel.d.mts.map +0 -1
  543. package/dist/intercom/nativeTeamChannel.mjs +0 -5
  544. package/dist/intercom/nativeTeamChannel.mjs.map +0 -1
  545. package/dist/intercom/supervisorControlChannel.cjs +0 -2
  546. package/dist/intercom/supervisorControlChannel.cjs.map +0 -1
  547. package/dist/intercom/supervisorControlChannel.mjs.map +0 -1
  548. package/dist/protocol/backgroundWork.cjs +0 -2
  549. package/dist/protocol/backgroundWork.d.cts.map +0 -1
  550. package/dist/protocol/backgroundWork.d.mts.map +0 -1
  551. package/dist/protocol/backgroundWork.mjs +0 -2
  552. package/dist/protocol/backgroundWork.mjs.map +0 -1
  553. package/dist/protocol/capabilityCeiling.cjs +0 -2
  554. package/dist/protocol/capabilityCeiling.cjs.map +0 -1
  555. package/dist/protocol/capabilityCeiling.d.cts.map +0 -1
  556. package/dist/protocol/capabilityCeiling.d.mts.map +0 -1
  557. package/dist/protocol/capabilityCeiling.mjs +0 -2
  558. package/dist/protocol/capabilityCeiling.mjs.map +0 -1
  559. package/dist/protocol/delegation.cjs.map +0 -1
  560. package/dist/protocol/delegation.d.cts.map +0 -1
  561. package/dist/protocol/delegation.d.mts.map +0 -1
  562. package/dist/protocol/delegation.mjs.map +0 -1
  563. package/dist/runs/background/asyncExecution.cjs +0 -2
  564. package/dist/runs/background/asyncExecution.cjs.map +0 -1
  565. package/dist/runs/background/asyncExecution.mjs +0 -2
  566. package/dist/runs/background/asyncExecution.mjs.map +0 -1
  567. package/dist/runs/background/asyncJobTracker.cjs +0 -2
  568. package/dist/runs/background/asyncJobTracker.cjs.map +0 -1
  569. package/dist/runs/background/asyncJobTracker.mjs +0 -2
  570. package/dist/runs/background/asyncJobTracker.mjs.map +0 -1
  571. package/dist/runs/background/cliRunnerEntry.cjs.map +0 -1
  572. package/dist/runs/background/cliRunnerEntry.mjs.map +0 -1
  573. package/dist/runs/background/completionBatcher.cjs.map +0 -1
  574. package/dist/runs/background/completionBatcher.mjs +0 -2
  575. package/dist/runs/background/completionBatcher.mjs.map +0 -1
  576. package/dist/runs/background/notify.cjs.map +0 -1
  577. package/dist/runs/background/notify.mjs.map +0 -1
  578. package/dist/runs/background/parentWatchdog.cjs.map +0 -1
  579. package/dist/runs/background/parentWatchdog.mjs.map +0 -1
  580. package/dist/runs/background/processTerminal.cjs +0 -2
  581. package/dist/runs/background/processTerminal.cjs.map +0 -1
  582. package/dist/runs/background/processTerminal.mjs +0 -2
  583. package/dist/runs/background/processTerminal.mjs.map +0 -1
  584. package/dist/runs/background/resultWatcher.cjs +0 -2
  585. package/dist/runs/background/resultWatcher.cjs.map +0 -1
  586. package/dist/runs/background/resultWatcher.mjs +0 -2
  587. package/dist/runs/background/resultWatcher.mjs.map +0 -1
  588. package/dist/runs/background/runIdResolver.cjs +0 -2
  589. package/dist/runs/background/runIdResolver.cjs.map +0 -1
  590. package/dist/runs/background/runIdResolver.mjs +0 -2
  591. package/dist/runs/background/runIdResolver.mjs.map +0 -1
  592. package/dist/runs/background/runnerBootstrap.cjs +0 -2
  593. package/dist/runs/background/runnerBootstrap.cjs.map +0 -1
  594. package/dist/runs/background/runnerBootstrap.mjs +0 -2
  595. package/dist/runs/background/runnerBootstrap.mjs.map +0 -1
  596. package/dist/runs/background/runnerExecution.cjs +0 -2
  597. package/dist/runs/background/runnerExecution.cjs.map +0 -1
  598. package/dist/runs/background/runnerExecution.mjs +0 -2
  599. package/dist/runs/background/runnerExecution.mjs.map +0 -1
  600. package/dist/runs/background/runnerReporting.cjs.map +0 -1
  601. package/dist/runs/background/runnerReporting.mjs.map +0 -1
  602. package/dist/runs/background/runnerTeamMembership.cjs +0 -2
  603. package/dist/runs/background/runnerTeamMembership.cjs.map +0 -1
  604. package/dist/runs/background/runnerTeamMembership.mjs +0 -2
  605. package/dist/runs/background/runnerTeamMembership.mjs.map +0 -1
  606. package/dist/runs/background/spawnHandshake.cjs +0 -2
  607. package/dist/runs/background/spawnHandshake.cjs.map +0 -1
  608. package/dist/runs/background/spawnHandshake.mjs +0 -2
  609. package/dist/runs/background/spawnHandshake.mjs.map +0 -1
  610. package/dist/runs/background/staleRunReconciler.cjs +0 -2
  611. package/dist/runs/background/staleRunReconciler.cjs.map +0 -1
  612. package/dist/runs/background/staleRunReconciler.mjs +0 -2
  613. package/dist/runs/background/staleRunReconciler.mjs.map +0 -1
  614. package/dist/runs/background/statusReader.cjs +0 -2
  615. package/dist/runs/background/statusReader.cjs.map +0 -1
  616. package/dist/runs/background/statusReader.mjs +0 -2
  617. package/dist/runs/background/statusReader.mjs.map +0 -1
  618. package/dist/runs/background/statusWriter.cjs +0 -2
  619. package/dist/runs/background/statusWriter.cjs.map +0 -1
  620. package/dist/runs/background/statusWriter.mjs +0 -2
  621. package/dist/runs/background/statusWriter.mjs.map +0 -1
  622. package/dist/runs/background/subagentWait.cjs +0 -2
  623. package/dist/runs/background/subagentWait.cjs.map +0 -1
  624. package/dist/runs/background/subagentWait.mjs +0 -2
  625. package/dist/runs/background/subagentWait.mjs.map +0 -1
  626. package/dist/runs/background/terminalPersistence.cjs +0 -2
  627. package/dist/runs/background/terminalPersistence.cjs.map +0 -1
  628. package/dist/runs/background/terminalPersistence.mjs.map +0 -1
  629. package/dist/runs/registry/processGroup.cjs.map +0 -1
  630. package/dist/runs/registry/processGroup.mjs.map +0 -1
  631. package/dist/runs/registry/runRegistry.cjs +0 -2
  632. package/dist/runs/registry/runRegistry.cjs.map +0 -1
  633. package/dist/runs/registry/runRegistry.mjs +0 -2
  634. package/dist/runs/registry/runRegistry.mjs.map +0 -1
  635. package/dist/runs/registry/sessionLifecycle.cjs +0 -2
  636. package/dist/runs/registry/sessionLifecycle.cjs.map +0 -1
  637. package/dist/runs/registry/sessionLifecycle.mjs +0 -2
  638. package/dist/runs/registry/sessionLifecycle.mjs.map +0 -1
  639. package/dist/runs/registry/suspendedRuns.cjs +0 -4
  640. package/dist/runs/registry/suspendedRuns.cjs.map +0 -1
  641. package/dist/runs/registry/suspendedRuns.mjs +0 -4
  642. package/dist/runs/registry/suspendedRuns.mjs.map +0 -1
  643. package/dist/runs/sdkRunnerEntry.cjs.map +0 -1
  644. package/dist/runs/sdkRunnerEntry.mjs.map +0 -1
  645. package/dist/runs/shared/claudeFableProfile.cjs +0 -4
  646. package/dist/runs/shared/claudeFableProfile.cjs.map +0 -1
  647. package/dist/runs/shared/claudeFableProfile.mjs +0 -4
  648. package/dist/runs/shared/claudeFableProfile.mjs.map +0 -1
  649. package/dist/runs/shared/depthGuard.cjs +0 -2
  650. package/dist/runs/shared/depthGuard.cjs.map +0 -1
  651. package/dist/runs/shared/depthGuard.mjs +0 -2
  652. package/dist/runs/shared/depthGuard.mjs.map +0 -1
  653. package/dist/runs/shared/mcpDirectToolAllowlist.cjs +0 -2
  654. package/dist/runs/shared/mcpDirectToolAllowlist.cjs.map +0 -1
  655. package/dist/runs/shared/mcpDirectToolAllowlist.mjs +0 -2
  656. package/dist/runs/shared/mcpDirectToolAllowlist.mjs.map +0 -1
  657. package/dist/runs/shared/modelFallback.cjs +0 -2
  658. package/dist/runs/shared/modelFallback.cjs.map +0 -1
  659. package/dist/runs/shared/modelFallback.mjs +0 -2
  660. package/dist/runs/shared/modelFallback.mjs.map +0 -1
  661. package/dist/runs/shared/modelScope.cjs +0 -2
  662. package/dist/runs/shared/modelScope.cjs.map +0 -1
  663. package/dist/runs/shared/modelScope.mjs +0 -2
  664. package/dist/runs/shared/modelScope.mjs.map +0 -1
  665. package/dist/runs/shared/nestedPath.cjs +0 -2
  666. package/dist/runs/shared/nestedPath.cjs.map +0 -1
  667. package/dist/runs/shared/nestedPath.mjs +0 -2
  668. package/dist/runs/shared/nestedPath.mjs.map +0 -1
  669. package/dist/runs/shared/piArgs.cjs +0 -2
  670. package/dist/runs/shared/piArgs.cjs.map +0 -1
  671. package/dist/runs/shared/piArgs.mjs +0 -2
  672. package/dist/runs/shared/piArgs.mjs.map +0 -1
  673. package/dist/runs/shared/piSpawn.cjs +0 -2
  674. package/dist/runs/shared/piSpawn.cjs.map +0 -1
  675. package/dist/runs/shared/piSpawn.mjs +0 -2
  676. package/dist/runs/shared/piSpawn.mjs.map +0 -1
  677. package/dist/runs/shared/runWithConcurrency.cjs +0 -2
  678. package/dist/runs/shared/runWithConcurrency.cjs.map +0 -1
  679. package/dist/runs/shared/runWithConcurrency.mjs +0 -2
  680. package/dist/runs/shared/runWithConcurrency.mjs.map +0 -1
  681. package/dist/runs/shared/runtimeRegistry.cjs +0 -2
  682. package/dist/runs/shared/runtimeRegistry.cjs.map +0 -1
  683. package/dist/runs/shared/runtimeRegistry.mjs +0 -2
  684. package/dist/runs/shared/runtimeRegistry.mjs.map +0 -1
  685. package/dist/runs/shared/structuredOutput.cjs +0 -2
  686. package/dist/runs/shared/structuredOutput.mjs +0 -2
  687. package/dist/runs/shared/toolAvailability.cjs +0 -2
  688. package/dist/runs/shared/toolAvailability.cjs.map +0 -1
  689. package/dist/runs/shared/toolAvailability.mjs +0 -2
  690. package/dist/runs/shared/toolAvailability.mjs.map +0 -1
  691. package/dist/runs/shared/toolBudget.cjs +0 -2
  692. package/dist/runs/shared/toolBudget.cjs.map +0 -1
  693. package/dist/runs/shared/toolBudget.mjs +0 -2
  694. package/dist/runs/shared/toolBudget.mjs.map +0 -1
  695. package/dist/shared/atomicJson.cjs.map +0 -1
  696. package/dist/shared/childTranscript.cjs.map +0 -1
  697. package/dist/shared/pollScheduler.cjs.map +0 -1
  698. package/dist/shared/scopeOwner.cjs.map +0 -1
  699. package/dist/slash/chainExpression.cjs.map +0 -1
  700. package/dist/slash/chainExpression.mjs.map +0 -1
  701. package/dist/slash/doctor.cjs +0 -5
  702. package/dist/slash/doctor.mjs +0 -5
  703. package/dist/slash/doctor.mjs.map +0 -1
  704. package/dist/slash/slashCommands.cjs.map +0 -1
  705. package/dist/slash/slashCommands.mjs.map +0 -1
  706. package/dist/slash/spawnRequestMapping.cjs.map +0 -1
  707. package/dist/slash/spawnRequestMapping.mjs.map +0 -1
  708. package/dist/slash/subagentLaunch.cjs +0 -2
  709. package/dist/slash/subagentLaunch.cjs.map +0 -1
  710. package/dist/slash/subagentLaunch.mjs +0 -2
  711. package/dist/slash/subagentLaunch.mjs.map +0 -1
  712. package/dist/types.d.cts.map +0 -1
  713. package/dist/types.d.mts.map +0 -1
  714. /package/dist/{slash → commands/slash}/chainExpression.cjs +0 -0
  715. /package/dist/{slash → commands/slash}/chainExpression.mjs +0 -0
  716. /package/dist/{slash → commands/slash}/spawnRequestMapping.cjs +0 -0
  717. /package/dist/{slash → commands/slash}/spawnRequestMapping.mjs +0 -0
  718. /package/dist/{protocol → schemas/team}/delegation.cjs +0 -0
  719. /package/dist/{protocol → schemas/team}/delegation.mjs +0 -0
  720. /package/dist/{agents → services/agents}/frontmatter.cjs +0 -0
  721. /package/dist/{intercom → services/intercom}/intercomBridge.cjs +0 -0
  722. /package/dist/{intercom → services/intercom}/intercomBridge.mjs +0 -0
  723. /package/dist/{runs → services/runs}/background/completionBatcher.cjs +0 -0
  724. /package/dist/{runs → services/runs}/background/parentWatchdog.cjs +0 -0
  725. /package/dist/{runs → services/runs}/background/parentWatchdog.mjs +0 -0
  726. /package/dist/{runs → services/runs}/registry/processGroup.cjs +0 -0
  727. /package/dist/{runs → services/runs}/registry/processGroup.mjs +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runnerReporting.cjs","names":["path","currentResultsDir","RESULT_FILE_SUFFIX","DOOM_TEAM_TYPES"],"sources":["../../../../src/services/runs/background/runnerReporting.ts"],"sourcesContent":["/**\n * Owns the single, synchronous callback wired into\n * `TerminalPersistenceService.begin()`: it both finalizes the run's status\n * and writes the terminal result file `ResultWatcher` claims by rename.\n *\n * WHY BOTH WRITES LIVE IN ONE CALLBACK:\n * `TerminalPersistenceService`'s idempotency guarantee only covers what\n * happens inside that one callback - a second, independent write site for\n * the result file would be exactly the \"fourth, unguarded terminal write\"\n * shape that made the predecessor's finalization the worst bug in its\n * review (see `asyncExecution.ts`'s header for the full diagnosis). Putting\n * the result-file write here means it inherits the same at-most-once\n * guarantee the status write already has, from the same source, rather than\n * needing a second one invented for it.\n *\n * WRITE ORDER, DECIDED DELIBERATELY (status, then result):\n * - status terminal / result missing: `ResultWatcher` delivers nothing, and\n * `StaleRunReconciler` sees no result plus a dead process, so it repairs.\n * RECOVERABLE\n * - result present / status not terminal: `ResultWatcher` delivers, but\n * `StaleRunReconciler` sees a result exists and correctly leaves the run\n * alone (see that class's header), so the stale status persists forever.\n * NOT RECOVERABLE\n * So status must land on disk first. The subtlety: `mutateTerminalStatus` is\n * the MUTATOR `TerminalPersistenceService` passes to\n * `CoalescedStatusWriter.updateSync(mutator)`, and that writer's own flush\n * only happens AFTER the mutator returns - so mutating `status` in place and\n * then writing the result file, in that order, inside this one function,\n * would put the result file on disk BEFORE the status flush, exactly\n * backwards. The fix: call `this.statusWriter.updateSync(() => {})` - a\n * second, no-op-mutator call to the SAME writer, re-entered from inside the\n * first one - immediately after mutating `status`. `updateSync` increments\n * its dirty counter and flushes unconditionally on every call regardless of\n * what its own mutator did, so this forces the just-mutated `status` onto\n * disk right there, before this function does anything else. The one\n * (harmless) cost is that `TerminalPersistenceService`'s own flush, once\n * this callback returns, then flushes again - a redundant but correct\n * second write of the same, already-correct status.\n *\n * WHY A THROW FROM THE RESULT WRITE CANNOT UNDO THE STATUS FLUSH:\n * The forced flush above happens, completes, and returns before\n * `writeResultFile` is even called - not \"before it succeeds\", before it is\n * INVOKED at all. A throw from that call propagates up through\n * `TerminalPersistenceService.triggerFinalize`'s `try` (skipping\n * `clearCrashMarker()`, per that class's own documented \"leave the marker in\n * place\" behavior on a mutator throw) into its `finally` (which still kills\n * tracked children) - but the status write already happened and cannot be\n * un-happened by anything that runs after it.\n *\n * WHY THE RESULT SHAPE IS MINIMAL, WITH ONE DELIBERATE EXCEPTION:\n * Most of the predecessor's ~40 result fields (structured output, artifacts,\n * workflow graph, capability audit) depend on subsystems not fully composed\n * here yet; shipping a correct minimal contract now beats an unverifiable\n * rich one. The exception: `acceptance` (an `AcceptanceLedger`) IS included\n * when `prepareResult()` was given one, because `acceptance.ts` is fully\n * ported and tested in this package today - an agent that declared\n * acceptance criteria and had them evaluated would otherwise produce a\n * result that silently omits the verdict, which is the same failure shape\n * as `DeliverableGuard`'s policy being unobservable (see\n * `asyncExecution.ts`'s header on that).\n *\n * WHY `summary` IS ALSO WRITTEN TO `status.json`, TRUNCATED:\n * Chain mode (`extensions/spawnPlan.ts`) needs a completed step's summary\n * to resolve the next step's `{previous}`/`{outputs.name}` template, but the\n * only thing that ever reads a delivered `RunResultFile` is whoever is\n * registered as `ResultWatcher`'s ONE consumer slot (`CompletionNotifier`,\n * by design). A second reader competing for that single claim-by-rename\n * would either displace the registered consumer or race it - both wrong.\n * `status.json` has no such problem: it has exactly one writer (this\n * callback, via `CoalescedStatusWriter`), is never claimed or unlinked, and\n * `AsyncJobTracker`/`SubagentWaiter` already read it to learn a run\n * finished - chain mode reads a step's summary through the path it already\n * uses. The copy in `status.json` is TRUNCATED (`MAX_STATUS_SUMMARY_CHARS`):\n * status is a coalesced, rewritten-in-full state file, not a log, and an\n * unbounded field there is a size problem waiting to happen. The full,\n * untruncated text still goes to the result file, for anything that reads\n * that instead. `acceptance` is deliberately NOT duplicated into status:\n * nothing this package builds yet gates chain advancement on an acceptance\n * verdict (only on execution success - see `spawnPlan.ts`'s header), so\n * there is no reader for a second copy of it yet. Add it if and when one\n * exists, the same reasoning as everything else flagged \"not wired yet\" in\n * this package.\n *\n * WHY `sessionFile` IS RECORDED HERE TOO, AND BOUNDED BY OMISSION, NOT\n * TRUNCATION:\n * `action='resume'` needs to know which Pi session transcript a completed\n * run's own child was writing to, and the CHILD is the only party that ever\n * knows that - `childTranscript.ts`'s header says the PARENT does not learn\n * a child's session id until the run ends, which already rules out the\n * parent recording it. `recordSessionFile()` is `prepareResult()`'s sibling:\n * a value set from outside, read back inside this same terminal-write\n * callback, on the SAME `CoalescedStatusWriter`, so it lands in the SAME\n * flush as everything else `mutateTerminalStatus` computes - no second\n * writer, no second flush. Where it is called from is\n * `subagentPromptRuntime.ts`'s `session_start` handler, the one place in\n * the child process that receives `ExtensionContext.sessionManager` at all.\n * `summary`'s bound is a truncation with a marker, because a shortened\n * summary is still a readable, honest summary. A shortened PATH is not a\n * smaller path to the same file - it is a WRONG path to some other file, or\n * no file at all, and `resume` acting on it would silently target the wrong\n * transcript. So this bound is \"drop entirely past `MAX_SESSION_FILE_CHARS`,\n * never truncate\" - the field goes missing rather than becoming a plausible-\n * looking lie an eventual `resume` implementation would trust.\n */\n\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport { writeAtomicJson } from '../../../shared/atomicJson.ts';\nimport { currentResultsDir } from '../../../shared/paths.ts';\nimport type { AcceptanceLedger } from '../types.ts';\nimport type { AsyncRunStatus } from './asyncExecution.ts';\nimport { RESULT_FILE_SUFFIX, type RunResultFile } from './resultWatcher.ts';\nimport { type ICoalescedStatusWriter } from './statusWriter.ts';\nimport type { TerminalTrigger } from './terminalPersistence.ts';\n\nfunction resultPathFor(runId: string): string {\n return path.join(currentResultsDir(), `${runId}${RESULT_FILE_SUFFIX}`);\n}\n\n/** Generous but bounded - see the module header's summary-in-status section. */\nconst MAX_STATUS_SUMMARY_CHARS = 4_000;\nconst TRUNCATION_MARKER = '\\n... [truncated for status.json; see the result file for the full text]';\n\nfunction truncateForStatus(summary: string): string {\n if (summary.length <= MAX_STATUS_SUMMARY_CHARS) return summary;\n return summary.slice(0, MAX_STATUS_SUMMARY_CHARS - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;\n}\n\n/** Generous for any real filesystem path; see the module header's `sessionFile` section for why exceeding this drops the field instead of truncating it. */\nconst MAX_SESSION_FILE_CHARS = 4_096;\n\n/** What the run itself knows about its own outcome, recorded before a trigger fires. */\nexport interface RunnerResultInput {\n success: boolean;\n summary: string;\n /** Explicit terminal state for controlled outcomes such as an operator stop. */\n state?: 'completed' | 'failed' | 'stopped';\n acceptance?: AcceptanceLedger;\n}\n\nexport interface IRunnerReporting {\n /**\n * Record what to report if/when this run reaches a terminal state. Call\n * this before triggering finalize (normal completion, or right before an\n * interrupt/stop/timeout handler calls `terminalPersistence.finalize()`).\n * A trigger that fires with nothing prepared (a signal or exception mid-run)\n * still produces a result - see `mutateTerminalStatus`'s fallback.\n */\n prepareResult(input: RunnerResultInput): void;\n /**\n * Record this run's own Pi session transcript path, if/when the child\n * learns it. Call this as soon as it is known (`subagentPromptRuntime.ts`'s\n * `session_start` handler, today). The accepted value is returned for the\n * bootstrap status write, then retained here for the terminal status write.\n * See the module header's `sessionFile` section for why a too-long value is\n * dropped, not truncated.\n */\n recordSessionFile(sessionFile: string): string | undefined;\n /** The callback to wire into `TerminalPersistenceService.begin()`. See the module header for what it does and why, in this order. */\n mutateTerminalStatus(status: AsyncRunStatus, trigger: TerminalTrigger | undefined): void;\n}\n\nfunction describeReason(reason: unknown): string {\n if (reason instanceof Error) return reason.message;\n return typeof reason === 'string' ? reason : 'Unhandled rejection with no error message.';\n}\n\ninterface TerminalOutcome {\n state: AsyncRunStatus['state'];\n success: boolean;\n error?: string;\n}\n\n@injectable()\nexport class RunnerReporting implements IRunnerReporting {\n constructor(\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter)\n private readonly statusWriter: ICoalescedStatusWriter<AsyncRunStatus>,\n ) {}\n\n protected now(): number {\n return Date.now();\n }\n\n protected writeResultFile(runId: string, result: RunResultFile): void {\n writeAtomicJson(resultPathFor(runId), result);\n }\n\n private prepared: RunnerResultInput | undefined;\n private recordedSessionFile: string | undefined;\n\n prepareResult(input: RunnerResultInput): void {\n this.prepared = input;\n }\n\n recordSessionFile(sessionFile: string): string | undefined {\n // Drop, don't truncate, past the bound - see the module header's\n // `sessionFile` section for why a shortened path is worse than no path.\n this.recordedSessionFile = sessionFile.length <= MAX_SESSION_FILE_CHARS ? sessionFile : undefined;\n return this.recordedSessionFile;\n }\n\n private resolveOutcome(trigger: TerminalTrigger | undefined): TerminalOutcome {\n if (trigger?.kind === 'signal') {\n return { state: 'stopped', success: false, error: `Terminated by signal ${trigger.signal}.` };\n }\n if (trigger?.kind === 'uncaughtException') {\n return { state: 'failed', success: false, error: trigger.error.message };\n }\n if (trigger?.kind === 'unhandledRejection') {\n return { state: 'failed', success: false, error: describeReason(trigger.reason) };\n }\n // trigger === undefined: the run's own explicit finalize() call.\n if (this.prepared) {\n return {\n state: this.prepared.state ?? (this.prepared.success ? 'completed' : 'failed'),\n success: this.prepared.success,\n };\n }\n return { state: 'failed', success: false, error: 'Run finalized without ever calling prepareResult().' };\n }\n\n mutateTerminalStatus(status: AsyncRunStatus, trigger: TerminalTrigger | undefined): void {\n const now = this.now();\n const outcome = this.resolveOutcome(trigger);\n\n const summary = this.prepared?.summary ?? outcome.error ?? '(no summary)';\n\n status.state = outcome.state;\n status.endedAt = now;\n status.lastUpdate = now;\n status.summary = truncateForStatus(summary);\n if (outcome.error) status.error = outcome.error;\n if (this.recordedSessionFile) status.sessionFile = this.recordedSessionFile;\n\n // Forces the mutation above onto disk before anything else in this\n // function runs. See the module header's WRITE ORDER section for why\n // this specific call, at this specific point, is what achieves that.\n this.statusWriter.updateSync(() => {});\n\n const result: RunResultFile = {\n runId: status.runId,\n agent: status.agent,\n success: outcome.success,\n state: outcome.state,\n summary,\n startedAt: status.startedAt,\n endedAt: now,\n ...(outcome.error ? { error: outcome.error } : {}),\n ...(this.prepared?.acceptance ? { acceptance: this.prepared.acceptance } : {}),\n };\n this.writeResultFile(status.runId, result);\n }\n}\n"],"mappings":"ooBAoHA,SAAS,EAAc,EAAuB,CAC5C,OAAOA,EAAK,KAAKC,EAAAA,mBAAmB,CAAE,GAAG,IAAQC,EAAAA,qBAAqB,CAIxE,MAAM,EAA2B,IAGjC,SAAS,EAAkB,EAAyB,CAElD,OADI,EAAQ,QAAU,EAAiC,EAChD,EAAQ,MAAM,EAAG,EAA2B,GAAyB,CAAG;wEAqCjF,SAAS,EAAe,EAAyB,CAE/C,OADI,aAAkB,MAAc,EAAO,QACpC,OAAO,GAAW,SAAW,EAAS,6CAUxC,IAAA,EAAA,KAAkD,CACvD,YACE,EAEA,CADiB,KAAA,aAAA,EAGnB,KAAwB,CACtB,OAAO,KAAK,KAAK,CAGnB,gBAA0B,EAAe,EAA6B,CACpE,EAAA,gBAAgB,EAAc,EAAM,CAAE,EAAO,CAG/C,SACA,oBAEA,cAAc,EAAgC,CAC5C,KAAK,SAAW,EAGlB,kBAAkB,EAAyC,CAIzD,MADA,MAAK,oBAAsB,EAAY,QAAU,KAAyB,EAAc,IAAA,GACjF,KAAK,oBAGd,eAAuB,EAAuD,CAiB5E,OAhBI,GAAS,OAAS,SACb,CAAE,MAAO,UAAW,QAAS,GAAO,MAAO,wBAAwB,EAAQ,OAAO,GAAI,CAE3F,GAAS,OAAS,oBACb,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,EAAQ,MAAM,QAAS,CAEtE,GAAS,OAAS,qBACb,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,EAAe,EAAQ,OAAO,CAAE,CAG/E,KAAK,SACA,CACL,MAAO,KAAK,SAAS,QAAU,KAAK,SAAS,QAAU,YAAc,UACrE,QAAS,KAAK,SAAS,QACxB,CAEI,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,sDAAuD,CAG1G,qBAAqB,EAAwB,EAA4C,CACvF,IAAM,EAAM,KAAK,KAAK,CAChB,EAAU,KAAK,eAAe,EAAQ,CAEtC,EAAU,KAAK,UAAU,SAAW,EAAQ,OAAS,eAE3D,EAAO,MAAQ,EAAQ,MACvB,EAAO,QAAU,EACjB,EAAO,WAAa,EACpB,EAAO,QAAU,EAAkB,EAAQ,CACvC,EAAQ,QAAO,EAAO,MAAQ,EAAQ,OACtC,KAAK,sBAAqB,EAAO,YAAc,KAAK,qBAKxD,KAAK,aAAa,eAAiB,GAAG,CAEtC,IAAM,EAAwB,CAC5B,MAAO,EAAO,MACd,MAAO,EAAO,MACd,QAAS,EAAQ,QACjB,MAAO,EAAQ,MACf,UACA,UAAW,EAAO,UAClB,QAAS,EACT,GAAI,EAAQ,MAAQ,CAAE,MAAO,EAAQ,MAAO,CAAG,EAAE,CACjD,GAAI,KAAK,UAAU,WAAa,CAAE,WAAY,KAAK,SAAS,WAAY,CAAG,EAAE,CAC9E,CACD,KAAK,gBAAgB,EAAO,MAAO,EAAO,qCA9EjC,kCAGDC,EAAAA,gBAAgB,sBAAsB,CAAA"}
@@ -1,3 +1,3 @@
1
- import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{init_atomicJson as t,writeAtomicJson as n}from"../../shared/atomicJson.mjs";import{__decorate as r,init_decorate as i}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{DOOM_TEAM_TYPES as a,init_types as o}from"../../container/types.mjs";import{__decorateMetadata as s,init_decorateMetadata as c}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as l,init_decorateParam as u}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{currentResultsDir as d,init_paths as f}from"../../shared/paths.mjs";import{RESULT_FILE_SUFFIX as p,init_resultWatcher as m}from"./resultWatcher.mjs";import*as h from"node:path";import{inject as g,injectable as _}from"inversify";function v(e){return h.join(d(),`${e}${p}`)}function y(e){return e.length<=x?e:e.slice(0,x-71)+S}function b(e){return e instanceof Error?e.message:typeof e==`string`?e:`Unhandled rejection with no error message.`}var x,S,C,w,T=e((()=>{t(),f(),o(),m(),c(),u(),i(),x=4e3,S=`
2
- ... [truncated for status.json; see the result file for the full text]`,C=4096,w=class{constructor(e){this.statusWriter=e}now(){return Date.now()}writeResultFile(e,t){n(v(e),t)}prepared;recordedSessionFile;prepareResult(e){this.prepared=e}recordSessionFile(e){return this.recordedSessionFile=e.length<=C?e:void 0,this.recordedSessionFile}resolveOutcome(e){return e?.kind===`signal`?{state:`stopped`,success:!1,error:`Terminated by signal ${e.signal}.`}:e?.kind===`uncaughtException`?{state:`failed`,success:!1,error:e.error.message}:e?.kind===`unhandledRejection`?{state:`failed`,success:!1,error:b(e.reason)}:this.prepared?{state:this.prepared.state??(this.prepared.success?`completed`:`failed`),success:this.prepared.success}:{state:`failed`,success:!1,error:`Run finalized without ever calling prepareResult().`}}mutateTerminalStatus(e,t){let n=this.now(),r=this.resolveOutcome(t),i=this.prepared?.summary??r.error??`(no summary)`;e.state=r.state,e.endedAt=n,e.lastUpdate=n,e.summary=y(i),r.error&&(e.error=r.error),this.recordedSessionFile&&(e.sessionFile=this.recordedSessionFile),this.statusWriter.updateSync(()=>{});let a={runId:e.runId,agent:e.agent,success:r.success,state:r.state,summary:i,startedAt:e.startedAt,endedAt:n,...r.error?{error:r.error}:{},...this.prepared?.acceptance?{acceptance:this.prepared.acceptance}:{}};this.writeResultFile(e.runId,a)}},w=r([_(),l(0,g(a.CoalescedStatusWriter)),s(`design:paramtypes`,[Object])],w)}));T();export{w as RunnerReporting,T as init_runnerReporting};
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{DOOM_TEAM_TYPES as r,init_types as i}from"../../../container/types.mjs";import{writeAtomicJson as a}from"../../../adapters/atomicJson.mjs";import{init_atomicJson as o}from"../../../shared/atomicJson.mjs";import{__decorateMetadata as s,init_decorateMetadata as c}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as l,init_decorateParam as u}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{currentResultsDir as d,init_paths as f}from"../../../shared/paths.mjs";import{RESULT_FILE_SUFFIX as p}from"../../../adapters/resultWatcher.mjs";import{init_resultWatcher as m}from"./resultWatcher.mjs";import*as h from"node:path";import{inject as g,injectable as _}from"inversify";function v(e){return h.join(d(),`${e}${p}`)}function y(e){return e.length<=x?e:e.slice(0,x-71)+S}function b(e){return e instanceof Error?e.message:typeof e==`string`?e:`Unhandled rejection with no error message.`}var x,S,C,w,T=e((()=>{i(),o(),f(),m(),c(),u(),n(),x=4e3,S=`
2
+ ... [truncated for status.json; see the result file for the full text]`,C=4096,w=class{constructor(e){this.statusWriter=e}now(){return Date.now()}writeResultFile(e,t){a(v(e),t)}prepared;recordedSessionFile;prepareResult(e){this.prepared=e}recordSessionFile(e){return this.recordedSessionFile=e.length<=C?e:void 0,this.recordedSessionFile}resolveOutcome(e){return e?.kind===`signal`?{state:`stopped`,success:!1,error:`Terminated by signal ${e.signal}.`}:e?.kind===`uncaughtException`?{state:`failed`,success:!1,error:e.error.message}:e?.kind===`unhandledRejection`?{state:`failed`,success:!1,error:b(e.reason)}:this.prepared?{state:this.prepared.state??(this.prepared.success?`completed`:`failed`),success:this.prepared.success}:{state:`failed`,success:!1,error:`Run finalized without ever calling prepareResult().`}}mutateTerminalStatus(e,t){let n=this.now(),r=this.resolveOutcome(t),i=this.prepared?.summary??r.error??`(no summary)`;e.state=r.state,e.endedAt=n,e.lastUpdate=n,e.summary=y(i),r.error&&(e.error=r.error),this.recordedSessionFile&&(e.sessionFile=this.recordedSessionFile),this.statusWriter.updateSync(()=>{});let a={runId:e.runId,agent:e.agent,success:r.success,state:r.state,summary:i,startedAt:e.startedAt,endedAt:n,...r.error?{error:r.error}:{},...this.prepared?.acceptance?{acceptance:this.prepared.acceptance}:{}};this.writeResultFile(e.runId,a)}},w=t([_(),l(0,g(r.CoalescedStatusWriter)),s(`design:paramtypes`,[Object])],w)}));T();export{w as RunnerReporting,T as init_runnerReporting};
3
3
  //# sourceMappingURL=runnerReporting.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runnerReporting.mjs","names":[],"sources":["../../../../src/services/runs/background/runnerReporting.ts"],"sourcesContent":["/**\n * Owns the single, synchronous callback wired into\n * `TerminalPersistenceService.begin()`: it both finalizes the run's status\n * and writes the terminal result file `ResultWatcher` claims by rename.\n *\n * WHY BOTH WRITES LIVE IN ONE CALLBACK:\n * `TerminalPersistenceService`'s idempotency guarantee only covers what\n * happens inside that one callback - a second, independent write site for\n * the result file would be exactly the \"fourth, unguarded terminal write\"\n * shape that made the predecessor's finalization the worst bug in its\n * review (see `asyncExecution.ts`'s header for the full diagnosis). Putting\n * the result-file write here means it inherits the same at-most-once\n * guarantee the status write already has, from the same source, rather than\n * needing a second one invented for it.\n *\n * WRITE ORDER, DECIDED DELIBERATELY (status, then result):\n * - status terminal / result missing: `ResultWatcher` delivers nothing, and\n * `StaleRunReconciler` sees no result plus a dead process, so it repairs.\n * RECOVERABLE\n * - result present / status not terminal: `ResultWatcher` delivers, but\n * `StaleRunReconciler` sees a result exists and correctly leaves the run\n * alone (see that class's header), so the stale status persists forever.\n * NOT RECOVERABLE\n * So status must land on disk first. The subtlety: `mutateTerminalStatus` is\n * the MUTATOR `TerminalPersistenceService` passes to\n * `CoalescedStatusWriter.updateSync(mutator)`, and that writer's own flush\n * only happens AFTER the mutator returns - so mutating `status` in place and\n * then writing the result file, in that order, inside this one function,\n * would put the result file on disk BEFORE the status flush, exactly\n * backwards. The fix: call `this.statusWriter.updateSync(() => {})` - a\n * second, no-op-mutator call to the SAME writer, re-entered from inside the\n * first one - immediately after mutating `status`. `updateSync` increments\n * its dirty counter and flushes unconditionally on every call regardless of\n * what its own mutator did, so this forces the just-mutated `status` onto\n * disk right there, before this function does anything else. The one\n * (harmless) cost is that `TerminalPersistenceService`'s own flush, once\n * this callback returns, then flushes again - a redundant but correct\n * second write of the same, already-correct status.\n *\n * WHY A THROW FROM THE RESULT WRITE CANNOT UNDO THE STATUS FLUSH:\n * The forced flush above happens, completes, and returns before\n * `writeResultFile` is even called - not \"before it succeeds\", before it is\n * INVOKED at all. A throw from that call propagates up through\n * `TerminalPersistenceService.triggerFinalize`'s `try` (skipping\n * `clearCrashMarker()`, per that class's own documented \"leave the marker in\n * place\" behavior on a mutator throw) into its `finally` (which still kills\n * tracked children) - but the status write already happened and cannot be\n * un-happened by anything that runs after it.\n *\n * WHY THE RESULT SHAPE IS MINIMAL, WITH ONE DELIBERATE EXCEPTION:\n * Most of the predecessor's ~40 result fields (structured output, artifacts,\n * workflow graph, capability audit) depend on subsystems not fully composed\n * here yet; shipping a correct minimal contract now beats an unverifiable\n * rich one. The exception: `acceptance` (an `AcceptanceLedger`) IS included\n * when `prepareResult()` was given one, because `acceptance.ts` is fully\n * ported and tested in this package today - an agent that declared\n * acceptance criteria and had them evaluated would otherwise produce a\n * result that silently omits the verdict, which is the same failure shape\n * as `DeliverableGuard`'s policy being unobservable (see\n * `asyncExecution.ts`'s header on that).\n *\n * WHY `summary` IS ALSO WRITTEN TO `status.json`, TRUNCATED:\n * Chain mode (`extensions/spawnPlan.ts`) needs a completed step's summary\n * to resolve the next step's `{previous}`/`{outputs.name}` template, but the\n * only thing that ever reads a delivered `RunResultFile` is whoever is\n * registered as `ResultWatcher`'s ONE consumer slot (`CompletionNotifier`,\n * by design). A second reader competing for that single claim-by-rename\n * would either displace the registered consumer or race it - both wrong.\n * `status.json` has no such problem: it has exactly one writer (this\n * callback, via `CoalescedStatusWriter`), is never claimed or unlinked, and\n * `AsyncJobTracker`/`SubagentWaiter` already read it to learn a run\n * finished - chain mode reads a step's summary through the path it already\n * uses. The copy in `status.json` is TRUNCATED (`MAX_STATUS_SUMMARY_CHARS`):\n * status is a coalesced, rewritten-in-full state file, not a log, and an\n * unbounded field there is a size problem waiting to happen. The full,\n * untruncated text still goes to the result file, for anything that reads\n * that instead. `acceptance` is deliberately NOT duplicated into status:\n * nothing this package builds yet gates chain advancement on an acceptance\n * verdict (only on execution success - see `spawnPlan.ts`'s header), so\n * there is no reader for a second copy of it yet. Add it if and when one\n * exists, the same reasoning as everything else flagged \"not wired yet\" in\n * this package.\n *\n * WHY `sessionFile` IS RECORDED HERE TOO, AND BOUNDED BY OMISSION, NOT\n * TRUNCATION:\n * `action='resume'` needs to know which Pi session transcript a completed\n * run's own child was writing to, and the CHILD is the only party that ever\n * knows that - `childTranscript.ts`'s header says the PARENT does not learn\n * a child's session id until the run ends, which already rules out the\n * parent recording it. `recordSessionFile()` is `prepareResult()`'s sibling:\n * a value set from outside, read back inside this same terminal-write\n * callback, on the SAME `CoalescedStatusWriter`, so it lands in the SAME\n * flush as everything else `mutateTerminalStatus` computes - no second\n * writer, no second flush. Where it is called from is\n * `subagentPromptRuntime.ts`'s `session_start` handler, the one place in\n * the child process that receives `ExtensionContext.sessionManager` at all.\n * `summary`'s bound is a truncation with a marker, because a shortened\n * summary is still a readable, honest summary. A shortened PATH is not a\n * smaller path to the same file - it is a WRONG path to some other file, or\n * no file at all, and `resume` acting on it would silently target the wrong\n * transcript. So this bound is \"drop entirely past `MAX_SESSION_FILE_CHARS`,\n * never truncate\" - the field goes missing rather than becoming a plausible-\n * looking lie an eventual `resume` implementation would trust.\n */\n\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport { writeAtomicJson } from '../../../shared/atomicJson.ts';\nimport { currentResultsDir } from '../../../shared/paths.ts';\nimport type { AcceptanceLedger } from '../types.ts';\nimport type { AsyncRunStatus } from './asyncExecution.ts';\nimport { RESULT_FILE_SUFFIX, type RunResultFile } from './resultWatcher.ts';\nimport { type ICoalescedStatusWriter } from './statusWriter.ts';\nimport type { TerminalTrigger } from './terminalPersistence.ts';\n\nfunction resultPathFor(runId: string): string {\n return path.join(currentResultsDir(), `${runId}${RESULT_FILE_SUFFIX}`);\n}\n\n/** Generous but bounded - see the module header's summary-in-status section. */\nconst MAX_STATUS_SUMMARY_CHARS = 4_000;\nconst TRUNCATION_MARKER = '\\n... [truncated for status.json; see the result file for the full text]';\n\nfunction truncateForStatus(summary: string): string {\n if (summary.length <= MAX_STATUS_SUMMARY_CHARS) return summary;\n return summary.slice(0, MAX_STATUS_SUMMARY_CHARS - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;\n}\n\n/** Generous for any real filesystem path; see the module header's `sessionFile` section for why exceeding this drops the field instead of truncating it. */\nconst MAX_SESSION_FILE_CHARS = 4_096;\n\n/** What the run itself knows about its own outcome, recorded before a trigger fires. */\nexport interface RunnerResultInput {\n success: boolean;\n summary: string;\n /** Explicit terminal state for controlled outcomes such as an operator stop. */\n state?: 'completed' | 'failed' | 'stopped';\n acceptance?: AcceptanceLedger;\n}\n\nexport interface IRunnerReporting {\n /**\n * Record what to report if/when this run reaches a terminal state. Call\n * this before triggering finalize (normal completion, or right before an\n * interrupt/stop/timeout handler calls `terminalPersistence.finalize()`).\n * A trigger that fires with nothing prepared (a signal or exception mid-run)\n * still produces a result - see `mutateTerminalStatus`'s fallback.\n */\n prepareResult(input: RunnerResultInput): void;\n /**\n * Record this run's own Pi session transcript path, if/when the child\n * learns it. Call this as soon as it is known (`subagentPromptRuntime.ts`'s\n * `session_start` handler, today). The accepted value is returned for the\n * bootstrap status write, then retained here for the terminal status write.\n * See the module header's `sessionFile` section for why a too-long value is\n * dropped, not truncated.\n */\n recordSessionFile(sessionFile: string): string | undefined;\n /** The callback to wire into `TerminalPersistenceService.begin()`. See the module header for what it does and why, in this order. */\n mutateTerminalStatus(status: AsyncRunStatus, trigger: TerminalTrigger | undefined): void;\n}\n\nfunction describeReason(reason: unknown): string {\n if (reason instanceof Error) return reason.message;\n return typeof reason === 'string' ? reason : 'Unhandled rejection with no error message.';\n}\n\ninterface TerminalOutcome {\n state: AsyncRunStatus['state'];\n success: boolean;\n error?: string;\n}\n\n@injectable()\nexport class RunnerReporting implements IRunnerReporting {\n constructor(\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter)\n private readonly statusWriter: ICoalescedStatusWriter<AsyncRunStatus>,\n ) {}\n\n protected now(): number {\n return Date.now();\n }\n\n protected writeResultFile(runId: string, result: RunResultFile): void {\n writeAtomicJson(resultPathFor(runId), result);\n }\n\n private prepared: RunnerResultInput | undefined;\n private recordedSessionFile: string | undefined;\n\n prepareResult(input: RunnerResultInput): void {\n this.prepared = input;\n }\n\n recordSessionFile(sessionFile: string): string | undefined {\n // Drop, don't truncate, past the bound - see the module header's\n // `sessionFile` section for why a shortened path is worse than no path.\n this.recordedSessionFile = sessionFile.length <= MAX_SESSION_FILE_CHARS ? sessionFile : undefined;\n return this.recordedSessionFile;\n }\n\n private resolveOutcome(trigger: TerminalTrigger | undefined): TerminalOutcome {\n if (trigger?.kind === 'signal') {\n return { state: 'stopped', success: false, error: `Terminated by signal ${trigger.signal}.` };\n }\n if (trigger?.kind === 'uncaughtException') {\n return { state: 'failed', success: false, error: trigger.error.message };\n }\n if (trigger?.kind === 'unhandledRejection') {\n return { state: 'failed', success: false, error: describeReason(trigger.reason) };\n }\n // trigger === undefined: the run's own explicit finalize() call.\n if (this.prepared) {\n return {\n state: this.prepared.state ?? (this.prepared.success ? 'completed' : 'failed'),\n success: this.prepared.success,\n };\n }\n return { state: 'failed', success: false, error: 'Run finalized without ever calling prepareResult().' };\n }\n\n mutateTerminalStatus(status: AsyncRunStatus, trigger: TerminalTrigger | undefined): void {\n const now = this.now();\n const outcome = this.resolveOutcome(trigger);\n\n const summary = this.prepared?.summary ?? outcome.error ?? '(no summary)';\n\n status.state = outcome.state;\n status.endedAt = now;\n status.lastUpdate = now;\n status.summary = truncateForStatus(summary);\n if (outcome.error) status.error = outcome.error;\n if (this.recordedSessionFile) status.sessionFile = this.recordedSessionFile;\n\n // Forces the mutation above onto disk before anything else in this\n // function runs. See the module header's WRITE ORDER section for why\n // this specific call, at this specific point, is what achieves that.\n this.statusWriter.updateSync(() => {});\n\n const result: RunResultFile = {\n runId: status.runId,\n agent: status.agent,\n success: outcome.success,\n state: outcome.state,\n summary,\n startedAt: status.startedAt,\n endedAt: now,\n ...(outcome.error ? { error: outcome.error } : {}),\n ...(this.prepared?.acceptance ? { acceptance: this.prepared.acceptance } : {}),\n };\n this.writeResultFile(status.runId, result);\n }\n}\n"],"mappings":"27BAoHA,SAAS,EAAc,EAAuB,CAC5C,OAAO,EAAK,KAAK,GAAmB,CAAE,GAAG,IAAQ,IAAqB,CAOxE,SAAS,EAAkB,EAAyB,CAElD,OADI,EAAQ,QAAU,EAAiC,EAChD,EAAQ,MAAM,EAAG,EAA2B,GAAyB,CAAG,EAqCjF,SAAS,EAAe,EAAyB,CAE/C,OADI,aAAkB,MAAc,EAAO,QACpC,OAAO,GAAW,SAAW,EAAS,sEA1De,IACE,IACH,IAGe,aAStE,EAA2B,IAC3B,EAAoB;wEAQpB,EAAyB,OA6CxB,KAAkD,CACvD,YACE,EAEA,CADiB,KAAA,aAAA,EAGnB,KAAwB,CACtB,OAAO,KAAK,KAAK,CAGnB,gBAA0B,EAAe,EAA6B,CACpE,EAAgB,EAAc,EAAM,CAAE,EAAO,CAG/C,SACA,oBAEA,cAAc,EAAgC,CAC5C,KAAK,SAAW,EAGlB,kBAAkB,EAAyC,CAIzD,MADA,MAAK,oBAAsB,EAAY,QAAU,EAAyB,EAAc,IAAA,GACjF,KAAK,oBAGd,eAAuB,EAAuD,CAiB5E,OAhBI,GAAS,OAAS,SACb,CAAE,MAAO,UAAW,QAAS,GAAO,MAAO,wBAAwB,EAAQ,OAAO,GAAI,CAE3F,GAAS,OAAS,oBACb,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,EAAQ,MAAM,QAAS,CAEtE,GAAS,OAAS,qBACb,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,EAAe,EAAQ,OAAO,CAAE,CAG/E,KAAK,SACA,CACL,MAAO,KAAK,SAAS,QAAU,KAAK,SAAS,QAAU,YAAc,UACrE,QAAS,KAAK,SAAS,QACxB,CAEI,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,sDAAuD,CAG1G,qBAAqB,EAAwB,EAA4C,CACvF,IAAM,EAAM,KAAK,KAAK,CAChB,EAAU,KAAK,eAAe,EAAQ,CAEtC,EAAU,KAAK,UAAU,SAAW,EAAQ,OAAS,eAE3D,EAAO,MAAQ,EAAQ,MACvB,EAAO,QAAU,EACjB,EAAO,WAAa,EACpB,EAAO,QAAU,EAAkB,EAAQ,CACvC,EAAQ,QAAO,EAAO,MAAQ,EAAQ,OACtC,KAAK,sBAAqB,EAAO,YAAc,KAAK,qBAKxD,KAAK,aAAa,eAAiB,GAAG,CAEtC,IAAM,EAAwB,CAC5B,MAAO,EAAO,MACd,MAAO,EAAO,MACd,QAAS,EAAQ,QACjB,MAAO,EAAQ,MACf,UACA,UAAW,EAAO,UAClB,QAAS,EACT,GAAI,EAAQ,MAAQ,CAAE,MAAO,EAAQ,MAAO,CAAG,EAAE,CACjD,GAAI,KAAK,UAAU,WAAa,CAAE,WAAY,KAAK,SAAS,WAAY,CAAG,EAAE,CAC9E,CACD,KAAK,gBAAgB,EAAO,MAAO,EAAO,QA9E7C,GAAY,KAGR,EAAO,EAAgB,sBAAsB,CAAA"}
@@ -0,0 +1,2 @@
1
+ require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),t=require(`../../intercom/nativeTeamChannel.cjs`);let n=require(`inversify`),r=class{readRoot(){return t.readNativeTeamRootFromEnvironment()}register(e){return t.registerNativeTeamMember(e)}};r=e.__decorate([(0,n.injectable)()],r),Object.defineProperty(exports,`RunnerTeamMembership`,{enumerable:!0,get:function(){return r}});
2
+ //# sourceMappingURL=runnerTeamMembership.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runnerTeamMembership.cjs","names":["readNativeTeamRootFromEnvironment","registerNativeTeamMember"],"sources":["../../../../src/services/runs/background/runnerTeamMembership.ts"],"sourcesContent":["/**\n * Thin composition seam over `nativeTeamChannel.ts`'s registration\n * functions, the same shape as `RunnerRevivalLease` over `session-lease.ts`:\n * no logic of its own, just an injectable wrapper so `RunnerBootstrap` can be\n * tested without writing real team files to disk.\n *\n * ERROR POLICY LIVES IN THE CALLER, NOT HERE:\n * `register()` throws exactly what `registerNativeTeamMember()` throws;\n * whether that is fatal to the run is `RunnerBootstrap`'s decision (it is\n * not - see that class's header for why team membership is additive), not\n * this wrapper's.\n */\n\nimport { injectable } from 'inversify';\nimport {\n type RegisterNativeTeamMemberInput,\n readNativeTeamRootFromEnvironment,\n registerNativeTeamMember,\n type TeamMemberContext,\n type TeamRootContext,\n} from '../../intercom/nativeTeamChannel.ts';\n\nexport interface RunnerTeamRegistration {\n context: TeamMemberContext;\n dispose: () => void;\n}\n\nexport interface IRunnerTeamMembership {\n /** This process's own team root context, if one was forwarded by whatever spawned it. `undefined` when this run is not part of a team. */\n readRoot(): TeamRootContext | undefined;\n /** Join the team as a new member. Throws on failure - see `RunnerBootstrap` for what it does with that. */\n register(input: RegisterNativeTeamMemberInput): RunnerTeamRegistration;\n}\n\n@injectable()\nexport class RunnerTeamMembership implements IRunnerTeamMembership {\n readRoot(): TeamRootContext | undefined {\n return readNativeTeamRootFromEnvironment();\n }\n\n register(input: RegisterNativeTeamMemberInput): RunnerTeamRegistration {\n return registerNativeTeamMember(input);\n }\n}\n"],"mappings":"wNAmCO,EAAA,KAA4D,CACjE,UAAwC,CACtC,OAAOA,EAAAA,mCAAmC,CAG5C,SAAS,EAA8D,CACrE,OAAOC,EAAAA,yBAAyB,EAAM,qCAP7B,CAAA,CAAA,EAAA"}
@@ -0,0 +1,2 @@
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{init_nativeTeamChannel as r,readNativeTeamRootFromEnvironment as i,registerNativeTeamMember as a}from"../../intercom/nativeTeamChannel.mjs";import{injectable as o}from"inversify";var s,c=e((()=>{r(),n(),s=class{readRoot(){return i()}register(e){return a(e)}},s=t([o()],s)}));c();export{s as RunnerTeamMembership,c as init_runnerTeamMembership};
2
+ //# sourceMappingURL=runnerTeamMembership.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runnerTeamMembership.mjs","names":[],"sources":["../../../../src/services/runs/background/runnerTeamMembership.ts"],"sourcesContent":["/**\n * Thin composition seam over `nativeTeamChannel.ts`'s registration\n * functions, the same shape as `RunnerRevivalLease` over `session-lease.ts`:\n * no logic of its own, just an injectable wrapper so `RunnerBootstrap` can be\n * tested without writing real team files to disk.\n *\n * ERROR POLICY LIVES IN THE CALLER, NOT HERE:\n * `register()` throws exactly what `registerNativeTeamMember()` throws;\n * whether that is fatal to the run is `RunnerBootstrap`'s decision (it is\n * not - see that class's header for why team membership is additive), not\n * this wrapper's.\n */\n\nimport { injectable } from 'inversify';\nimport {\n type RegisterNativeTeamMemberInput,\n readNativeTeamRootFromEnvironment,\n registerNativeTeamMember,\n type TeamMemberContext,\n type TeamRootContext,\n} from '../../intercom/nativeTeamChannel.ts';\n\nexport interface RunnerTeamRegistration {\n context: TeamMemberContext;\n dispose: () => void;\n}\n\nexport interface IRunnerTeamMembership {\n /** This process's own team root context, if one was forwarded by whatever spawned it. `undefined` when this run is not part of a team. */\n readRoot(): TeamRootContext | undefined;\n /** Join the team as a new member. Throws on failure - see `RunnerBootstrap` for what it does with that. */\n register(input: RegisterNativeTeamMemberInput): RunnerTeamRegistration;\n}\n\n@injectable()\nexport class RunnerTeamMembership implements IRunnerTeamMembership {\n readRoot(): TeamRootContext | undefined {\n return readNativeTeamRootFromEnvironment();\n }\n\n register(input: RegisterNativeTeamMemberInput): RunnerTeamRegistration {\n return registerNativeTeamMember(input);\n }\n}\n"],"mappings":"qYAoB6C,OAetC,KAA4D,CACjE,UAAwC,CACtC,OAAO,GAAmC,CAG5C,SAAS,EAA8D,CACrE,OAAO,EAAyB,EAAM,QAPzC,GAAY,CAAA,CAAA,EAAA"}
@@ -0,0 +1,2 @@
1
+ const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`);let n=require(`node:fs`);n=e.__toESM(n,1);let r=require(`node:path`);r=e.__toESM(r,1);let i=require(`inversify`),a=class{pollIntervalMs=20;defaultTimeoutMs=1e4;readFile(e){try{return n.readFileSync(e,`utf-8`)}catch{return}}watchDirectory(e,t){try{let r=n.watch(e,()=>t());return r.on(`error`,()=>t()),r}catch{return}}readOutcome(e){let t=this.readFile(e);if(t===void 0)return;let n;try{n=JSON.parse(t)}catch{return}if(typeof n!=`object`||!n)return;let r=n.state;if(r===`ready`)return{status:`signalled`};if(r===`error`){let e=n.error;return{status:`failed`,error:typeof e==`string`?e:`Async runner reported an error.`}}}waitForHandshake(e){let t=e.timeoutMs??this.defaultTimeoutMs,n=!1,i,a,o,s,c=()=>{i&&clearInterval(i),a&&clearTimeout(a),o?.close(),i=void 0,a=void 0,o=void 0};return{promise:new Promise((l,u)=>{s=u;let d=e=>{n||(n=!0,c(),l(e))},f=()=>{if(n)return;let t=this.readOutcome(e.path);t&&d(t)};a=setTimeout(()=>d({status:`timed-out`}),t),a.unref?.(),o=this.watchDirectory(r.dirname(e.path),f),i=setInterval(f,this.pollIntervalMs),i.unref?.(),f()}),cancel:e=>{n||(n=!0,c(),s?.(Error(e)))}}}};a=t.__decorate([(0,i.injectable)()],a),Object.defineProperty(exports,`SpawnHandshake`,{enumerable:!0,get:function(){return a}});
2
+ //# sourceMappingURL=spawnHandshake.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawnHandshake.cjs","names":["fs","path"],"sources":["../../../../src/services/runs/background/spawnHandshake.ts"],"sourcesContent":["/**\n * Promise-based wait for a spawned child to signal that it has started.\n *\n * WHY THIS EXISTS:\n * The predecessor package waits for this same signal with `Atomics.wait`\n * (`doom-pi-subagents/src/runs/background/asyncExecution.ts:376-380`), which\n * blocks the parent's event loop for up to 10 seconds. That froze the whole TUI.\n * It was survivable when background spawns were opt-in; this package is\n * async-only, so every spawn would pay it. This module reads the same\n * readiness signal (a JSON file the child writes, `{ state: 'ready' }` or\n * `{ state: 'error', error }`) but never blocks the event loop to wait for it.\n *\n * WATCH-FIRST, POLL AS A SAFETY NET:\n * Same pattern as `PollScheduler`'s header, for the same reason: `fs.watch` is\n * the fast path, a ~20ms poll is the safety net for the platforms and mount\n * types where watches are unreliable or do not fire, and neither path is\n * trusted alone.\n *\n * WHY THIS DOES NOT REGISTER WITH `PollScheduler`:\n * `PollScheduler` exists to let background housekeeping (a handful of\n * long-lived subscribers: channel polling, status writing) back off toward\n * multi-second intervals while idle. A handshake wait is the opposite shape: a\n * per-spawn, short-lived (bounded by `timeoutMs`, default 10s), low-latency\n * wait that specifically must NOT inherit the scheduler's idle backoff. Sharing\n * the master tick would force a choice between two bad options: either every\n * concurrent handshake wait keeps the whole scheduler pinned near its floor\n * (so unrelated housekeeping never gets to back off while any spawn is\n * starting), or the handshake is checked only as often as the scheduler's\n * current backed-off interval allows (which defeats the ~20ms safety net this\n * requirement exists for). A dedicated, self-terminating timer avoids coupling\n * spawn concurrency to the cadence of unrelated background work.\n *\n * DESIGN PATTERNS:\n * - `waitForHandshake` returns `{ promise, cancel }` rather than only a promise.\n * `cancel` is a real exit path, not a convenience: an orchestrator that sees\n * the child process itself exit before signalling should be able to reject\n * immediately rather than waiting out the remaining timeout doing nothing\n * - The three terminal outcomes a caller actually wants to distinguish\n * (`signalled`, `failed`, `timed-out`) are all resolutions, not rejections,\n * so a plain timeout does not force every caller into a try/catch. `cancel`\n * is the one path that rejects, because it is the caller abandoning the wait\n * for a reason of its own, not an outcome of the handshake itself\n * - One `cleanup` function is the only place that clears the timers and closes\n * the watcher, and every terminal path (signal, failure, timeout, cancel)\n * goes through it, so there is exactly one place a leak could be introduced\n * - Reading the handshake file behind protected seams rather than importing\n * `node:fs` calls directly into tests: ESM module namespaces are frozen, so\n * `vi.spyOn(fs, ...)` throws, and a seam is the documented way around that\n *\n * AVOID:\n * - Treating a JSON parse failure on the handshake file as a definite outcome;\n * the caller writes it with a rename-based atomic write, so a failure here\n * means this check landed mid-write, not that the child failed. It must be\n * treated as \"still pending\" and retried on the next check\n * - Letting `watchDirectory` throwing propagate; a platform or mount that\n * cannot support `fs.watch` is exactly the case the poll safety net exists\n * for, so watch setup failing must fall through to poll-only, not abort\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { injectable } from 'inversify';\n\n/** Poll safety net cadence. Watches are the fast path; this bounds the worst case. */\nconst DEFAULT_POLL_INTERVAL_MS = 20;\n/** Matches the predecessor's `RUNNER_STARTUP_TIMEOUT_MS`: this is the wire protocol's known-good bound. */\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nexport interface SpawnHandshakeOptions {\n /** Absolute path of the file the child writes when it starts (or fails to). */\n path: string;\n /** Upper bound on how long to wait before resolving `timed-out`. Defaults to 10s. */\n timeoutMs?: number;\n}\n\nexport type SpawnHandshakeOutcome =\n | { status: 'signalled' }\n | { status: 'failed'; error: string }\n | { status: 'timed-out' };\n\nexport interface SpawnHandshakeWait {\n promise: Promise<SpawnHandshakeOutcome>;\n /** Reject early and tear down. For a caller that already knows waiting further is pointless. */\n cancel: (reason: string) => void;\n}\n\nexport interface ISpawnHandshake {\n waitForHandshake(options: SpawnHandshakeOptions): SpawnHandshakeWait;\n}\n\n/** Minimal surface this module needs from a `fs.FSWatcher`, so a test seam does not need the real type. */\ninterface DirectoryWatch {\n close: () => void;\n}\n\n@injectable()\nexport class SpawnHandshake implements ISpawnHandshake {\n /**\n * Seams for tests, as protected members rather than constructor arguments.\n * The container resolves this service by type, and inversify cannot supply a\n * primitive like a poll interval, so a test subclass overrides these instead.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly defaultTimeoutMs: number = DEFAULT_TIMEOUT_MS;\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch {\n // Missing (not yet written) or transiently unreadable: keep waiting.\n return undefined;\n }\n }\n\n /**\n * Watches the handshake file's directory, not the file itself: the file does\n * not exist yet when the wait starts, and `fs.watch` on a path that does not\n * exist throws on most platforms. Returns `undefined` when watching could not\n * be set up at all, which the caller treats as \"poll-only for this wait\".\n */\n protected watchDirectory(dirPath: string, onEvent: () => void): DirectoryWatch | undefined {\n try {\n const watcher = fs.watch(dirPath, () => onEvent());\n // An unhandled 'error' event on an FSWatcher crashes the process; this\n // wait has a poll safety net regardless, so a watch error just means\n // falling back to it rather than losing the wait entirely.\n watcher.on('error', () => onEvent());\n return watcher;\n } catch {\n return undefined;\n }\n }\n\n /** Interpret the handshake file's contents, or `undefined` when the outcome is not decided yet. */\n private readOutcome(filePath: string): SpawnHandshakeOutcome | undefined {\n const raw = this.readFile(filePath);\n if (raw === undefined) return undefined;\n let payload: unknown;\n try {\n payload = JSON.parse(raw);\n } catch {\n // A rename-based atomic write should never be readable mid-write, but a\n // torn read is cheaper to tolerate than to rule out; the next check retries.\n return undefined;\n }\n if (typeof payload !== 'object' || payload === null) return undefined;\n const state = (payload as { state?: unknown }).state;\n if (state === 'ready') return { status: 'signalled' };\n if (state === 'error') {\n const error = (payload as { error?: unknown }).error;\n return { status: 'failed', error: typeof error === 'string' ? error : 'Async runner reported an error.' };\n }\n return undefined;\n }\n\n waitForHandshake(options: SpawnHandshakeOptions): SpawnHandshakeWait {\n const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;\n let settled = false;\n let pollTimer: ReturnType<typeof setInterval> | undefined;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n let watcher: DirectoryWatch | undefined;\n let rejectWait: ((reason: unknown) => void) | undefined;\n\n const cleanup = (): void => {\n if (pollTimer) clearInterval(pollTimer);\n if (timeoutTimer) clearTimeout(timeoutTimer);\n watcher?.close();\n pollTimer = undefined;\n timeoutTimer = undefined;\n watcher = undefined;\n };\n\n const promise = new Promise<SpawnHandshakeOutcome>((resolve, reject) => {\n rejectWait = reject;\n\n const settle = (outcome: SpawnHandshakeOutcome): void => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(outcome);\n };\n\n const checkNow = (): void => {\n if (settled) return;\n const outcome = this.readOutcome(options.path);\n if (outcome) settle(outcome);\n };\n\n timeoutTimer = setTimeout(() => settle({ status: 'timed-out' }), timeoutMs);\n timeoutTimer.unref?.();\n\n watcher = this.watchDirectory(path.dirname(options.path), checkNow);\n\n pollTimer = setInterval(checkNow, this.pollIntervalMs);\n pollTimer.unref?.();\n\n // The file may already exist (a very fast child, or a wait that started\n // late), so the first check happens immediately rather than waiting out\n // either the poll interval or the first watch event.\n checkNow();\n });\n\n const cancel = (reason: string): void => {\n if (settled) return;\n settled = true;\n cleanup();\n rejectWait?.(new Error(reason));\n };\n\n return { promise, cancel };\n }\n}\n"],"mappings":"8PAgGO,EAAA,KAAgD,CAMrD,eAA4C,GAC5C,iBAA8C,IAE9C,SAAmB,EAAsC,CACvD,GAAI,CACF,OAAOA,EAAG,aAAa,EAAU,QAAQ,MACnC,CAEN,QAUJ,eAAyB,EAAiB,EAAiD,CACzF,GAAI,CACF,IAAM,EAAUA,EAAG,MAAM,MAAe,GAAS,CAAC,CAKlD,OADA,EAAQ,GAAG,YAAe,GAAS,CAAC,CAC7B,OACD,CACN,QAKJ,YAAoB,EAAqD,CACvE,IAAM,EAAM,KAAK,SAAS,EAAS,CACnC,GAAI,IAAQ,IAAA,GAAW,OACvB,IAAI,EACJ,GAAI,CACF,EAAU,KAAK,MAAM,EAAI,MACnB,CAGN,OAEF,GAAI,OAAO,GAAY,WAAY,EAAkB,OACrD,IAAM,EAAS,EAAgC,MAC/C,GAAI,IAAU,QAAS,MAAO,CAAE,OAAQ,YAAa,CACrD,GAAI,IAAU,QAAS,CACrB,IAAM,EAAS,EAAgC,MAC/C,MAAO,CAAE,OAAQ,SAAU,MAAO,OAAO,GAAU,SAAW,EAAQ,kCAAmC,EAK7G,iBAAiB,EAAoD,CACnE,IAAM,EAAY,EAAQ,WAAa,KAAK,iBACxC,EAAU,GACV,EACA,EACA,EACA,EAEE,MAAsB,CACtB,GAAW,cAAc,EAAU,CACnC,GAAc,aAAa,EAAa,CAC5C,GAAS,OAAO,CAChB,EAAY,IAAA,GACZ,EAAe,IAAA,GACf,EAAU,IAAA,IAwCZ,MAAO,CAAE,YArCW,SAAgC,EAAS,IAAW,CACtE,EAAa,EAEb,IAAM,EAAU,GAAyC,CACnD,IACJ,EAAU,GACV,GAAS,CACT,EAAQ,EAAQ,GAGZ,MAAuB,CAC3B,GAAI,EAAS,OACb,IAAM,EAAU,KAAK,YAAY,EAAQ,KAAK,CAC1C,GAAS,EAAO,EAAQ,EAG9B,EAAe,eAAiB,EAAO,CAAE,OAAQ,YAAa,CAAC,CAAE,EAAU,CAC3E,EAAa,SAAS,CAEtB,EAAU,KAAK,eAAeC,EAAK,QAAQ,EAAQ,KAAK,CAAE,EAAS,CAEnE,EAAY,YAAY,EAAU,KAAK,eAAe,CACtD,EAAU,SAAS,CAKnB,GAAU,EAUH,CAAS,OAPF,GAAyB,CACnC,IACJ,EAAU,GACV,GAAS,CACT,IAAiB,MAAM,EAAO,CAAC,GAGP,qCAlHjB,CAAA,CAAA,EAAA"}
@@ -0,0 +1,2 @@
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import*as r from"node:fs";import*as i from"node:path";import{injectable as a}from"inversify";var o,s,c,l=e((()=>{n(),o=20,s=1e4,c=class{pollIntervalMs=o;defaultTimeoutMs=s;readFile(e){try{return r.readFileSync(e,`utf-8`)}catch{return}}watchDirectory(e,t){try{let n=r.watch(e,()=>t());return n.on(`error`,()=>t()),n}catch{return}}readOutcome(e){let t=this.readFile(e);if(t===void 0)return;let n;try{n=JSON.parse(t)}catch{return}if(typeof n!=`object`||!n)return;let r=n.state;if(r===`ready`)return{status:`signalled`};if(r===`error`){let e=n.error;return{status:`failed`,error:typeof e==`string`?e:`Async runner reported an error.`}}}waitForHandshake(e){let t=e.timeoutMs??this.defaultTimeoutMs,n=!1,r,a,o,s,c=()=>{r&&clearInterval(r),a&&clearTimeout(a),o?.close(),r=void 0,a=void 0,o=void 0};return{promise:new Promise((l,u)=>{s=u;let d=e=>{n||(n=!0,c(),l(e))},f=()=>{if(n)return;let t=this.readOutcome(e.path);t&&d(t)};a=setTimeout(()=>d({status:`timed-out`}),t),a.unref?.(),o=this.watchDirectory(i.dirname(e.path),f),r=setInterval(f,this.pollIntervalMs),r.unref?.(),f()}),cancel:e=>{n||(n=!0,c(),s?.(Error(e)))}}}},c=t([a()],c)}));l();export{c as SpawnHandshake,l as init_spawnHandshake};
2
+ //# sourceMappingURL=spawnHandshake.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawnHandshake.mjs","names":[],"sources":["../../../../src/services/runs/background/spawnHandshake.ts"],"sourcesContent":["/**\n * Promise-based wait for a spawned child to signal that it has started.\n *\n * WHY THIS EXISTS:\n * The predecessor package waits for this same signal with `Atomics.wait`\n * (`doom-pi-subagents/src/runs/background/asyncExecution.ts:376-380`), which\n * blocks the parent's event loop for up to 10 seconds. That froze the whole TUI.\n * It was survivable when background spawns were opt-in; this package is\n * async-only, so every spawn would pay it. This module reads the same\n * readiness signal (a JSON file the child writes, `{ state: 'ready' }` or\n * `{ state: 'error', error }`) but never blocks the event loop to wait for it.\n *\n * WATCH-FIRST, POLL AS A SAFETY NET:\n * Same pattern as `PollScheduler`'s header, for the same reason: `fs.watch` is\n * the fast path, a ~20ms poll is the safety net for the platforms and mount\n * types where watches are unreliable or do not fire, and neither path is\n * trusted alone.\n *\n * WHY THIS DOES NOT REGISTER WITH `PollScheduler`:\n * `PollScheduler` exists to let background housekeeping (a handful of\n * long-lived subscribers: channel polling, status writing) back off toward\n * multi-second intervals while idle. A handshake wait is the opposite shape: a\n * per-spawn, short-lived (bounded by `timeoutMs`, default 10s), low-latency\n * wait that specifically must NOT inherit the scheduler's idle backoff. Sharing\n * the master tick would force a choice between two bad options: either every\n * concurrent handshake wait keeps the whole scheduler pinned near its floor\n * (so unrelated housekeeping never gets to back off while any spawn is\n * starting), or the handshake is checked only as often as the scheduler's\n * current backed-off interval allows (which defeats the ~20ms safety net this\n * requirement exists for). A dedicated, self-terminating timer avoids coupling\n * spawn concurrency to the cadence of unrelated background work.\n *\n * DESIGN PATTERNS:\n * - `waitForHandshake` returns `{ promise, cancel }` rather than only a promise.\n * `cancel` is a real exit path, not a convenience: an orchestrator that sees\n * the child process itself exit before signalling should be able to reject\n * immediately rather than waiting out the remaining timeout doing nothing\n * - The three terminal outcomes a caller actually wants to distinguish\n * (`signalled`, `failed`, `timed-out`) are all resolutions, not rejections,\n * so a plain timeout does not force every caller into a try/catch. `cancel`\n * is the one path that rejects, because it is the caller abandoning the wait\n * for a reason of its own, not an outcome of the handshake itself\n * - One `cleanup` function is the only place that clears the timers and closes\n * the watcher, and every terminal path (signal, failure, timeout, cancel)\n * goes through it, so there is exactly one place a leak could be introduced\n * - Reading the handshake file behind protected seams rather than importing\n * `node:fs` calls directly into tests: ESM module namespaces are frozen, so\n * `vi.spyOn(fs, ...)` throws, and a seam is the documented way around that\n *\n * AVOID:\n * - Treating a JSON parse failure on the handshake file as a definite outcome;\n * the caller writes it with a rename-based atomic write, so a failure here\n * means this check landed mid-write, not that the child failed. It must be\n * treated as \"still pending\" and retried on the next check\n * - Letting `watchDirectory` throwing propagate; a platform or mount that\n * cannot support `fs.watch` is exactly the case the poll safety net exists\n * for, so watch setup failing must fall through to poll-only, not abort\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { injectable } from 'inversify';\n\n/** Poll safety net cadence. Watches are the fast path; this bounds the worst case. */\nconst DEFAULT_POLL_INTERVAL_MS = 20;\n/** Matches the predecessor's `RUNNER_STARTUP_TIMEOUT_MS`: this is the wire protocol's known-good bound. */\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nexport interface SpawnHandshakeOptions {\n /** Absolute path of the file the child writes when it starts (or fails to). */\n path: string;\n /** Upper bound on how long to wait before resolving `timed-out`. Defaults to 10s. */\n timeoutMs?: number;\n}\n\nexport type SpawnHandshakeOutcome =\n | { status: 'signalled' }\n | { status: 'failed'; error: string }\n | { status: 'timed-out' };\n\nexport interface SpawnHandshakeWait {\n promise: Promise<SpawnHandshakeOutcome>;\n /** Reject early and tear down. For a caller that already knows waiting further is pointless. */\n cancel: (reason: string) => void;\n}\n\nexport interface ISpawnHandshake {\n waitForHandshake(options: SpawnHandshakeOptions): SpawnHandshakeWait;\n}\n\n/** Minimal surface this module needs from a `fs.FSWatcher`, so a test seam does not need the real type. */\ninterface DirectoryWatch {\n close: () => void;\n}\n\n@injectable()\nexport class SpawnHandshake implements ISpawnHandshake {\n /**\n * Seams for tests, as protected members rather than constructor arguments.\n * The container resolves this service by type, and inversify cannot supply a\n * primitive like a poll interval, so a test subclass overrides these instead.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly defaultTimeoutMs: number = DEFAULT_TIMEOUT_MS;\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch {\n // Missing (not yet written) or transiently unreadable: keep waiting.\n return undefined;\n }\n }\n\n /**\n * Watches the handshake file's directory, not the file itself: the file does\n * not exist yet when the wait starts, and `fs.watch` on a path that does not\n * exist throws on most platforms. Returns `undefined` when watching could not\n * be set up at all, which the caller treats as \"poll-only for this wait\".\n */\n protected watchDirectory(dirPath: string, onEvent: () => void): DirectoryWatch | undefined {\n try {\n const watcher = fs.watch(dirPath, () => onEvent());\n // An unhandled 'error' event on an FSWatcher crashes the process; this\n // wait has a poll safety net regardless, so a watch error just means\n // falling back to it rather than losing the wait entirely.\n watcher.on('error', () => onEvent());\n return watcher;\n } catch {\n return undefined;\n }\n }\n\n /** Interpret the handshake file's contents, or `undefined` when the outcome is not decided yet. */\n private readOutcome(filePath: string): SpawnHandshakeOutcome | undefined {\n const raw = this.readFile(filePath);\n if (raw === undefined) return undefined;\n let payload: unknown;\n try {\n payload = JSON.parse(raw);\n } catch {\n // A rename-based atomic write should never be readable mid-write, but a\n // torn read is cheaper to tolerate than to rule out; the next check retries.\n return undefined;\n }\n if (typeof payload !== 'object' || payload === null) return undefined;\n const state = (payload as { state?: unknown }).state;\n if (state === 'ready') return { status: 'signalled' };\n if (state === 'error') {\n const error = (payload as { error?: unknown }).error;\n return { status: 'failed', error: typeof error === 'string' ? error : 'Async runner reported an error.' };\n }\n return undefined;\n }\n\n waitForHandshake(options: SpawnHandshakeOptions): SpawnHandshakeWait {\n const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;\n let settled = false;\n let pollTimer: ReturnType<typeof setInterval> | undefined;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n let watcher: DirectoryWatch | undefined;\n let rejectWait: ((reason: unknown) => void) | undefined;\n\n const cleanup = (): void => {\n if (pollTimer) clearInterval(pollTimer);\n if (timeoutTimer) clearTimeout(timeoutTimer);\n watcher?.close();\n pollTimer = undefined;\n timeoutTimer = undefined;\n watcher = undefined;\n };\n\n const promise = new Promise<SpawnHandshakeOutcome>((resolve, reject) => {\n rejectWait = reject;\n\n const settle = (outcome: SpawnHandshakeOutcome): void => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(outcome);\n };\n\n const checkNow = (): void => {\n if (settled) return;\n const outcome = this.readOutcome(options.path);\n if (outcome) settle(outcome);\n };\n\n timeoutTimer = setTimeout(() => settle({ status: 'timed-out' }), timeoutMs);\n timeoutTimer.unref?.();\n\n watcher = this.watchDirectory(path.dirname(options.path), checkNow);\n\n pollTimer = setInterval(checkNow, this.pollIntervalMs);\n pollTimer.unref?.();\n\n // The file may already exist (a very fast child, or a wait that started\n // late), so the first check happens immediately rather than waiting out\n // either the poll interval or the first watch event.\n checkNow();\n });\n\n const cancel = (reason: string): void => {\n if (settled) return;\n settled = true;\n cleanup();\n rejectWait?.(new Error(reason));\n };\n\n return { promise, cancel };\n }\n}\n"],"mappings":"6SAgEM,EAA2B,GAE3B,EAAqB,MA8BpB,KAAgD,CAMrD,eAA4C,EAC5C,iBAA8C,EAE9C,SAAmB,EAAsC,CACvD,GAAI,CACF,OAAO,EAAG,aAAa,EAAU,QAAQ,MACnC,CAEN,QAUJ,eAAyB,EAAiB,EAAiD,CACzF,GAAI,CACF,IAAM,EAAU,EAAG,MAAM,MAAe,GAAS,CAAC,CAKlD,OADA,EAAQ,GAAG,YAAe,GAAS,CAAC,CAC7B,OACD,CACN,QAKJ,YAAoB,EAAqD,CACvE,IAAM,EAAM,KAAK,SAAS,EAAS,CACnC,GAAI,IAAQ,IAAA,GAAW,OACvB,IAAI,EACJ,GAAI,CACF,EAAU,KAAK,MAAM,EAAI,MACnB,CAGN,OAEF,GAAI,OAAO,GAAY,WAAY,EAAkB,OACrD,IAAM,EAAS,EAAgC,MAC/C,GAAI,IAAU,QAAS,MAAO,CAAE,OAAQ,YAAa,CACrD,GAAI,IAAU,QAAS,CACrB,IAAM,EAAS,EAAgC,MAC/C,MAAO,CAAE,OAAQ,SAAU,MAAO,OAAO,GAAU,SAAW,EAAQ,kCAAmC,EAK7G,iBAAiB,EAAoD,CACnE,IAAM,EAAY,EAAQ,WAAa,KAAK,iBACxC,EAAU,GACV,EACA,EACA,EACA,EAEE,MAAsB,CACtB,GAAW,cAAc,EAAU,CACnC,GAAc,aAAa,EAAa,CAC5C,GAAS,OAAO,CAChB,EAAY,IAAA,GACZ,EAAe,IAAA,GACf,EAAU,IAAA,IAwCZ,MAAO,CAAE,YArCW,SAAgC,EAAS,IAAW,CACtE,EAAa,EAEb,IAAM,EAAU,GAAyC,CACnD,IACJ,EAAU,GACV,GAAS,CACT,EAAQ,EAAQ,GAGZ,MAAuB,CAC3B,GAAI,EAAS,OACb,IAAM,EAAU,KAAK,YAAY,EAAQ,KAAK,CAC1C,GAAS,EAAO,EAAQ,EAG9B,EAAe,eAAiB,EAAO,CAAE,OAAQ,YAAa,CAAC,CAAE,EAAU,CAC3E,EAAa,SAAS,CAEtB,EAAU,KAAK,eAAe,EAAK,QAAQ,EAAQ,KAAK,CAAE,EAAS,CAEnE,EAAY,YAAY,EAAU,KAAK,eAAe,CACtD,EAAU,SAAS,CAKnB,GAAU,EAUH,CAAS,OAPF,GAAyB,CACnC,IACJ,EAAU,GACV,GAAS,CACT,IAAiB,MAAM,EAAO,CAAC,GAGP,QAlH7B,GAAY,CAAA,CAAA,EAAA"}
@@ -0,0 +1 @@
1
+ require(`../../../adapters/staleRunReconciler.cjs`);
@@ -0,0 +1,2 @@
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{init_staleRunReconciler as t}from"../../../adapters/staleRunReconciler.mjs";var n=e((()=>{t()}));n();export{n as init_staleRunReconciler};
2
+ //# sourceMappingURL=staleRunReconciler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staleRunReconciler.mjs","names":[],"sources":["../../../../src/services/runs/background/staleRunReconciler.ts"],"sourcesContent":["export {\n type IStaleRunReconciler,\n type ReconcilableStatus,\n type ReconcileOutcome,\n type ReconcileSweepOutcome,\n type RecoveredClaim,\n StaleRunReconciler,\n type SweepOutcome,\n} from '../../../adapters/staleRunReconciler.ts';\n"],"mappings":"uKAQiD"}
@@ -0,0 +1 @@
1
+ require(`../../../adapters/statusReader.cjs`);
@@ -0,0 +1,2 @@
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{init_statusReader as t}from"../../../adapters/statusReader.mjs";var n=e((()=>{t()}));n();export{n as init_statusReader};
2
+ //# sourceMappingURL=statusReader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusReader.mjs","names":[],"sources":["../../../../src/services/runs/background/statusReader.ts"],"sourcesContent":["export {\n parseAsyncRunStatus,\n readAsyncRunStatus,\n readAsyncRunStatusAt,\n readAsyncRunStatusAtAsync,\n readAsyncRunStatusResult,\n readAsyncRunStatusResultAsync,\n readAsyncRunStatusResultAt,\n readAsyncRunStatusResultAtAsync,\n type StatusReadFailure,\n type StatusReadResult,\n} from '../../../adapters/statusReader.ts';\n"],"mappings":"2JAW2C"}
@@ -0,0 +1,2 @@
1
+ const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),n=require(`../../../adapters/atomicJson.cjs`);require(`../../../shared/atomicJson.cjs`);const r=require(`../../../shared/paths.cjs`);let i=require(`node:path`);i=e.__toESM(i,1);let a=require(`inversify`);const o=`status.json`;let s=class{flushIntervalMs=75;statusPath;status;dirtyCount=0;flushTimer;open(e,t){this.flushTimer&&=(clearTimeout(this.flushTimer),void 0),this.statusPath=i.join(r.currentRunsDir(),e,o),this.status=t,this.dirtyCount=0,n.writeAtomicJson(this.statusPath,this.clone(t))}update(e){e(this.requireStatus()),this.dirtyCount++,this.scheduleFlush()}updateSync(e){e(this.requireStatus()),this.dirtyCount++,this.flush()}appendTool(e){this.update(t=>c(t,`recentTools`,e))}appendOutput(e){this.update(t=>c(t,`recentOutput`,e))}close(){this.flush()}clone(e){return structuredClone(e)}flush(){if(this.flushTimer&&=(clearTimeout(this.flushTimer),void 0),this.dirtyCount===0)return;let e=this.status,t=this.statusPath;if(!e||!t)return;let r=this.clone(e);this.dirtyCount=0,n.writeAtomicJson(t,r)}scheduleFlush(){if(this.flushTimer)return;let e=setTimeout(()=>this.flush(),this.flushIntervalMs);e.unref?.(),this.flushTimer=e}requireStatus(){if(!this.status)throw Error(`CoalescedStatusWriter used before open().`);return this.status}};s=t.__decorate([(0,a.injectable)()],s);function c(e,t,n){let r=e[t]??[];r.push(n),r.length>50&&r.splice(0,r.length-50),e[t]=r}Object.defineProperty(exports,`CoalescedStatusWriter`,{enumerable:!0,get:function(){return s}}),exports.STATUS_FILE_NAME=o;
2
+ //# sourceMappingURL=statusWriter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusWriter.cjs","names":["path","currentRunsDir"],"sources":["../../../../src/services/runs/background/statusWriter.ts"],"sourcesContent":["/**\n * Coalesced writer for a run's `status.json`.\n *\n * WHY THIS EXISTS:\n * In the predecessor (`doom-pi-subagents/src/runs/background/subagent-runner.ts`)\n * `status.json` was rewritten in full on every child event, and every rewrite\n * `structuredClone`d the whole run graph first. On a fan-out run that makes the\n * hot path quadratic: N events cost O(N) clones of an O(N)-sized graph. Its\n * `recentTools` and `recentOutput` arrays were also appended to and never\n * trimmed, so a long run's status file, and the cost of cloning it, grew\n * without bound.\n *\n * DESIGN PATTERNS:\n * - Coalescing: `update()` mutates the in-memory status and arms a trailing\n * ~75ms timer. A burst of events inside that window costs one clone and one\n * write, not one per event\n * - The clone is gated by a dirty counter, not by the timer firing. A flush\n * with nothing dirty since the last one performs no clone and no write at\n * all, which is the specific fix for the quadratic behaviour above\n * - `updateSync()` bypasses the timer for transitions that must not be lost if\n * the process dies right after: terminal run status, and chain step\n * transitions. Everything else can wait out the coalescing window\n * - `recentTools` and `recentOutput` are capped at 50 entries, oldest dropped,\n * on every append, so neither the in-memory status nor the cloned snapshot\n * grows across a long run\n *\n * SINGLE WRITER CONTRACT (read this before adding another write site):\n * `CoalescedStatusWriter` is the ONLY thing in this package that may write\n * `status.json`. Not \"the preferred way\" - the only way. The predecessor had\n * four unsynchronised writers to the same file, which is exactly how a run's\n * displayed status ends up torn, stale, or overwritten by a slower writer\n * racing a faster one; coalescing a single in-memory owner is what makes that\n * class of bug structurally impossible instead of merely unlikely.\n *\n * Anything else that wants to influence a run's displayed status - pause a\n * step, request a steer, ask a run to stop, report progress from a different\n * process than the one that called `open()` - must NOT open or write\n * `status.json` directly. It writes a sidecar/intent file instead, and the\n * process holding this writer observes that file and folds it into its own\n * `update()`. Readers for those intent files do not exist yet; that is a gap\n * to fill, not a license to reach for `writeAtomicJson(statusPath, ...)`\n * somewhere else. If you are about to write `status.json` from outside this\n * class, stop - that is the bug this module exists to remove.\n *\n * AVOID:\n * - Reading back `status.json` from disk to decide what to write next; the\n * in-memory status held by `open()` is the only source of truth this class\n * trusts\n * - Adding a second writer of `status.json` anywhere else in the package,\n * including \"just this once\" debug or recovery code\n */\n\nimport * as path from 'node:path';\nimport { injectable } from 'inversify';\n\nimport { writeAtomicJson } from '../../../shared/atomicJson.ts';\nimport { currentRunsDir } from '../../../shared/paths.ts';\n\n/** How long a burst of updates may be buffered before a trailing flush. */\nconst DEFAULT_FLUSH_INTERVAL_MS = 75;\n\n/** Oldest-dropped cap for `recentTools` and `recentOutput`. */\nconst MAX_RECENT_ENTRIES = 50;\n\n/**\n * The one status filename.\n *\n * Exported because the reconciler and the steering reader both have to find\n * this file without going through the writer. Two modules matching a literal\n * by eye is how a rename silently orphans a reader.\n */\nexport const STATUS_FILE_NAME = 'status.json';\n\n/**\n * The minimum shape this writer needs to know about. Everything else in a\n * concrete run's status is opaque to it: capping `recentTools` and\n * `recentOutput` is the one piece of run semantics this module owns, because\n * it is the specific fix for the unbounded-growth bug above.\n */\nexport interface StatusWithRecentEntries {\n recentTools?: unknown[];\n recentOutput?: unknown[];\n}\n\nexport interface ICoalescedStatusWriter<TStatus extends StatusWithRecentEntries = StatusWithRecentEntries> {\n /**\n * Start tracking a run's status and write it once, synchronously, so the\n * file exists before the caller does anything else.\n *\n * Calling this again, on the same or a different `runId`, is a reset, not\n * an error: it discards whatever was previously tracked, including any\n * mutation buffered by `update()` that had not flushed yet, cancels the\n * pending coalescing timer, and starts a fresh session against the new\n * status. This is deliberate so a runner that retries a bootstrap step can\n * just call `open()` again rather than having to detect and recover from a\n * half-initialized writer.\n */\n open(runId: string, initialStatus: TStatus): void;\n /** Apply an in-memory mutation and let the trailing timer coalesce the write. */\n update(mutator: (status: TStatus) => void): void;\n /** Apply an in-memory mutation and flush synchronously, for transitions that must survive a crash. */\n updateSync(mutator: (status: TStatus) => void): void;\n /** Append one entry to `recentTools`, capped at 50, coalesced like `update()`. */\n appendTool(entry: unknown): void;\n /** Append one entry to `recentOutput`, capped at 50, coalesced like `update()`. */\n appendOutput(entry: unknown): void;\n /** Cancel any pending timer and flush whatever is dirty. Safe to call more than once. */\n close(): void;\n}\n\n@injectable()\nexport class CoalescedStatusWriter<\n TStatus extends StatusWithRecentEntries = StatusWithRecentEntries,\n> implements ICoalescedStatusWriter<TStatus> {\n /**\n * Seam for tests, as a protected member rather than a constructor argument:\n * inversify cannot resolve a primitive, so configuration cannot arrive\n * through the constructor. A test subclass overrides this to avoid waiting\n * out the real window when it does exercise the timer directly instead of\n * using fake timers.\n */\n protected readonly flushIntervalMs: number = DEFAULT_FLUSH_INTERVAL_MS;\n\n private statusPath: string | undefined;\n private status: TStatus | undefined;\n private dirtyCount = 0;\n private flushTimer: NodeJS.Timeout | undefined;\n\n open(runId: string, initialStatus: TStatus): void {\n // A second open() is a reset: cancel whatever this instance was\n // previously tracking (including an unflushed dirty mutation) rather\n // than merging with it. See the doc comment on the interface method.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n this.statusPath = path.join(currentRunsDir(), runId, STATUS_FILE_NAME);\n this.status = initialStatus;\n this.dirtyCount = 0;\n // The initial write is not a coalescing candidate: callers rely on the\n // file existing as soon as `open()` returns, so it always goes straight\n // to disk rather than waiting for a timer.\n writeAtomicJson(this.statusPath, this.clone(initialStatus));\n }\n\n update(mutator: (status: TStatus) => void): void {\n mutator(this.requireStatus());\n this.dirtyCount++;\n this.scheduleFlush();\n }\n\n updateSync(mutator: (status: TStatus) => void): void {\n mutator(this.requireStatus());\n this.dirtyCount++;\n this.flush();\n }\n\n appendTool(entry: unknown): void {\n this.update((status) => appendCapped(status, 'recentTools', entry));\n }\n\n appendOutput(entry: unknown): void {\n this.update((status) => appendCapped(status, 'recentOutput', entry));\n }\n\n close(): void {\n this.flush();\n }\n\n /**\n * The clone-and-write step. Protected so a test can override `clone()`\n * alone and assert it was never called across a run of no-op flushes,\n * which is the direct, observable proof that the dirty-counter gate works.\n */\n protected clone<T>(value: T): T {\n return structuredClone(value);\n }\n\n private flush(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n if (this.dirtyCount === 0) return;\n const status = this.status;\n const statusPath = this.statusPath;\n if (!status || !statusPath) return;\n const snapshot = this.clone(status);\n this.dirtyCount = 0;\n writeAtomicJson(statusPath, snapshot);\n }\n\n private scheduleFlush(): void {\n if (this.flushTimer) return;\n const timer = setTimeout(() => this.flush(), this.flushIntervalMs);\n // A pending flush timer must never be the reason the process stays alive.\n timer.unref?.();\n this.flushTimer = timer;\n }\n\n private requireStatus(): TStatus {\n if (!this.status) throw new Error('CoalescedStatusWriter used before open().');\n return this.status;\n }\n}\n\n/** Push `entry` onto `status[field]`, dropping the oldest until at most 50 remain. */\nfunction appendCapped(status: StatusWithRecentEntries, field: 'recentTools' | 'recentOutput', entry: unknown): void {\n const list = status[field] ?? [];\n list.push(entry);\n if (list.length > MAX_RECENT_ENTRIES) list.splice(0, list.length - MAX_RECENT_ENTRIES);\n status[field] = list;\n}\n"],"mappings":"yVA2DA,MAYa,EAAmB,cAwCzB,IAAA,EAAA,KAEsC,CAQ3C,gBAA6C,GAE7C,WACA,OACA,WAAqB,EACrB,WAEA,KAAK,EAAe,EAA8B,CAIhD,AAEE,KAAK,cADL,aAAa,KAAK,WAAW,CACX,IAAA,IAEpB,KAAK,WAAaA,EAAK,KAAKC,EAAAA,gBAAgB,CAAE,EAAO,EAAiB,CACtE,KAAK,OAAS,EACd,KAAK,WAAa,EAIlB,EAAA,gBAAgB,KAAK,WAAY,KAAK,MAAM,EAAc,CAAC,CAG7D,OAAO,EAA0C,CAC/C,EAAQ,KAAK,eAAe,CAAC,CAC7B,KAAK,aACL,KAAK,eAAe,CAGtB,WAAW,EAA0C,CACnD,EAAQ,KAAK,eAAe,CAAC,CAC7B,KAAK,aACL,KAAK,OAAO,CAGd,WAAW,EAAsB,CAC/B,KAAK,OAAQ,GAAW,EAAa,EAAQ,cAAe,EAAM,CAAC,CAGrE,aAAa,EAAsB,CACjC,KAAK,OAAQ,GAAW,EAAa,EAAQ,eAAgB,EAAM,CAAC,CAGtE,OAAc,CACZ,KAAK,OAAO,CAQd,MAAmB,EAAa,CAC9B,OAAO,gBAAgB,EAAM,CAG/B,OAAsB,CAKpB,GAJA,AAEE,KAAK,cADL,aAAa,KAAK,WAAW,CACX,IAAA,IAEhB,KAAK,aAAe,EAAG,OAC3B,IAAM,EAAS,KAAK,OACd,EAAa,KAAK,WACxB,GAAI,CAAC,GAAU,CAAC,EAAY,OAC5B,IAAM,EAAW,KAAK,MAAM,EAAO,CACnC,KAAK,WAAa,EAClB,EAAA,gBAAgB,EAAY,EAAS,CAGvC,eAA8B,CAC5B,GAAI,KAAK,WAAY,OACrB,IAAM,EAAQ,eAAiB,KAAK,OAAO,CAAE,KAAK,gBAAgB,CAElE,EAAM,SAAS,CACf,KAAK,WAAa,EAGpB,eAAiC,CAC/B,GAAI,CAAC,KAAK,OAAQ,MAAU,MAAM,4CAA4C,CAC9E,OAAO,KAAK,2CA5FH,CAAA,CAAA,EAAA,CAiGb,SAAS,EAAa,EAAiC,EAAuC,EAAsB,CAClH,IAAM,EAAO,EAAO,IAAU,EAAE,CAChC,EAAK,KAAK,EAAM,CACZ,EAAK,OAAS,IAAoB,EAAK,OAAO,EAAG,EAAK,OAAS,GAAmB,CACtF,EAAO,GAAS"}
@@ -0,0 +1,2 @@
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{writeAtomicJson as r}from"../../../adapters/atomicJson.mjs";import{init_atomicJson as i}from"../../../shared/atomicJson.mjs";import{currentRunsDir as a,init_paths as o}from"../../../shared/paths.mjs";import*as s from"node:path";import{injectable as c}from"inversify";function l(e,t,n){let r=e[t]??[];r.push(n),r.length>d&&r.splice(0,r.length-d),e[t]=r}var u,d,f,p,m=e((()=>{i(),o(),n(),u=75,d=50,f=`status.json`,p=class{flushIntervalMs=u;statusPath;status;dirtyCount=0;flushTimer;open(e,t){this.flushTimer&&=(clearTimeout(this.flushTimer),void 0),this.statusPath=s.join(a(),e,f),this.status=t,this.dirtyCount=0,r(this.statusPath,this.clone(t))}update(e){e(this.requireStatus()),this.dirtyCount++,this.scheduleFlush()}updateSync(e){e(this.requireStatus()),this.dirtyCount++,this.flush()}appendTool(e){this.update(t=>l(t,`recentTools`,e))}appendOutput(e){this.update(t=>l(t,`recentOutput`,e))}close(){this.flush()}clone(e){return structuredClone(e)}flush(){if(this.flushTimer&&=(clearTimeout(this.flushTimer),void 0),this.dirtyCount===0)return;let e=this.status,t=this.statusPath;if(!e||!t)return;let n=this.clone(e);this.dirtyCount=0,r(t,n)}scheduleFlush(){if(this.flushTimer)return;let e=setTimeout(()=>this.flush(),this.flushIntervalMs);e.unref?.(),this.flushTimer=e}requireStatus(){if(!this.status)throw Error(`CoalescedStatusWriter used before open().`);return this.status}},p=t([c()],p)}));m();export{p as CoalescedStatusWriter,f as STATUS_FILE_NAME,m as init_statusWriter};
2
+ //# sourceMappingURL=statusWriter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusWriter.mjs","names":[],"sources":["../../../../src/services/runs/background/statusWriter.ts"],"sourcesContent":["/**\n * Coalesced writer for a run's `status.json`.\n *\n * WHY THIS EXISTS:\n * In the predecessor (`doom-pi-subagents/src/runs/background/subagent-runner.ts`)\n * `status.json` was rewritten in full on every child event, and every rewrite\n * `structuredClone`d the whole run graph first. On a fan-out run that makes the\n * hot path quadratic: N events cost O(N) clones of an O(N)-sized graph. Its\n * `recentTools` and `recentOutput` arrays were also appended to and never\n * trimmed, so a long run's status file, and the cost of cloning it, grew\n * without bound.\n *\n * DESIGN PATTERNS:\n * - Coalescing: `update()` mutates the in-memory status and arms a trailing\n * ~75ms timer. A burst of events inside that window costs one clone and one\n * write, not one per event\n * - The clone is gated by a dirty counter, not by the timer firing. A flush\n * with nothing dirty since the last one performs no clone and no write at\n * all, which is the specific fix for the quadratic behaviour above\n * - `updateSync()` bypasses the timer for transitions that must not be lost if\n * the process dies right after: terminal run status, and chain step\n * transitions. Everything else can wait out the coalescing window\n * - `recentTools` and `recentOutput` are capped at 50 entries, oldest dropped,\n * on every append, so neither the in-memory status nor the cloned snapshot\n * grows across a long run\n *\n * SINGLE WRITER CONTRACT (read this before adding another write site):\n * `CoalescedStatusWriter` is the ONLY thing in this package that may write\n * `status.json`. Not \"the preferred way\" - the only way. The predecessor had\n * four unsynchronised writers to the same file, which is exactly how a run's\n * displayed status ends up torn, stale, or overwritten by a slower writer\n * racing a faster one; coalescing a single in-memory owner is what makes that\n * class of bug structurally impossible instead of merely unlikely.\n *\n * Anything else that wants to influence a run's displayed status - pause a\n * step, request a steer, ask a run to stop, report progress from a different\n * process than the one that called `open()` - must NOT open or write\n * `status.json` directly. It writes a sidecar/intent file instead, and the\n * process holding this writer observes that file and folds it into its own\n * `update()`. Readers for those intent files do not exist yet; that is a gap\n * to fill, not a license to reach for `writeAtomicJson(statusPath, ...)`\n * somewhere else. If you are about to write `status.json` from outside this\n * class, stop - that is the bug this module exists to remove.\n *\n * AVOID:\n * - Reading back `status.json` from disk to decide what to write next; the\n * in-memory status held by `open()` is the only source of truth this class\n * trusts\n * - Adding a second writer of `status.json` anywhere else in the package,\n * including \"just this once\" debug or recovery code\n */\n\nimport * as path from 'node:path';\nimport { injectable } from 'inversify';\n\nimport { writeAtomicJson } from '../../../shared/atomicJson.ts';\nimport { currentRunsDir } from '../../../shared/paths.ts';\n\n/** How long a burst of updates may be buffered before a trailing flush. */\nconst DEFAULT_FLUSH_INTERVAL_MS = 75;\n\n/** Oldest-dropped cap for `recentTools` and `recentOutput`. */\nconst MAX_RECENT_ENTRIES = 50;\n\n/**\n * The one status filename.\n *\n * Exported because the reconciler and the steering reader both have to find\n * this file without going through the writer. Two modules matching a literal\n * by eye is how a rename silently orphans a reader.\n */\nexport const STATUS_FILE_NAME = 'status.json';\n\n/**\n * The minimum shape this writer needs to know about. Everything else in a\n * concrete run's status is opaque to it: capping `recentTools` and\n * `recentOutput` is the one piece of run semantics this module owns, because\n * it is the specific fix for the unbounded-growth bug above.\n */\nexport interface StatusWithRecentEntries {\n recentTools?: unknown[];\n recentOutput?: unknown[];\n}\n\nexport interface ICoalescedStatusWriter<TStatus extends StatusWithRecentEntries = StatusWithRecentEntries> {\n /**\n * Start tracking a run's status and write it once, synchronously, so the\n * file exists before the caller does anything else.\n *\n * Calling this again, on the same or a different `runId`, is a reset, not\n * an error: it discards whatever was previously tracked, including any\n * mutation buffered by `update()` that had not flushed yet, cancels the\n * pending coalescing timer, and starts a fresh session against the new\n * status. This is deliberate so a runner that retries a bootstrap step can\n * just call `open()` again rather than having to detect and recover from a\n * half-initialized writer.\n */\n open(runId: string, initialStatus: TStatus): void;\n /** Apply an in-memory mutation and let the trailing timer coalesce the write. */\n update(mutator: (status: TStatus) => void): void;\n /** Apply an in-memory mutation and flush synchronously, for transitions that must survive a crash. */\n updateSync(mutator: (status: TStatus) => void): void;\n /** Append one entry to `recentTools`, capped at 50, coalesced like `update()`. */\n appendTool(entry: unknown): void;\n /** Append one entry to `recentOutput`, capped at 50, coalesced like `update()`. */\n appendOutput(entry: unknown): void;\n /** Cancel any pending timer and flush whatever is dirty. Safe to call more than once. */\n close(): void;\n}\n\n@injectable()\nexport class CoalescedStatusWriter<\n TStatus extends StatusWithRecentEntries = StatusWithRecentEntries,\n> implements ICoalescedStatusWriter<TStatus> {\n /**\n * Seam for tests, as a protected member rather than a constructor argument:\n * inversify cannot resolve a primitive, so configuration cannot arrive\n * through the constructor. A test subclass overrides this to avoid waiting\n * out the real window when it does exercise the timer directly instead of\n * using fake timers.\n */\n protected readonly flushIntervalMs: number = DEFAULT_FLUSH_INTERVAL_MS;\n\n private statusPath: string | undefined;\n private status: TStatus | undefined;\n private dirtyCount = 0;\n private flushTimer: NodeJS.Timeout | undefined;\n\n open(runId: string, initialStatus: TStatus): void {\n // A second open() is a reset: cancel whatever this instance was\n // previously tracking (including an unflushed dirty mutation) rather\n // than merging with it. See the doc comment on the interface method.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n this.statusPath = path.join(currentRunsDir(), runId, STATUS_FILE_NAME);\n this.status = initialStatus;\n this.dirtyCount = 0;\n // The initial write is not a coalescing candidate: callers rely on the\n // file existing as soon as `open()` returns, so it always goes straight\n // to disk rather than waiting for a timer.\n writeAtomicJson(this.statusPath, this.clone(initialStatus));\n }\n\n update(mutator: (status: TStatus) => void): void {\n mutator(this.requireStatus());\n this.dirtyCount++;\n this.scheduleFlush();\n }\n\n updateSync(mutator: (status: TStatus) => void): void {\n mutator(this.requireStatus());\n this.dirtyCount++;\n this.flush();\n }\n\n appendTool(entry: unknown): void {\n this.update((status) => appendCapped(status, 'recentTools', entry));\n }\n\n appendOutput(entry: unknown): void {\n this.update((status) => appendCapped(status, 'recentOutput', entry));\n }\n\n close(): void {\n this.flush();\n }\n\n /**\n * The clone-and-write step. Protected so a test can override `clone()`\n * alone and assert it was never called across a run of no-op flushes,\n * which is the direct, observable proof that the dirty-counter gate works.\n */\n protected clone<T>(value: T): T {\n return structuredClone(value);\n }\n\n private flush(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = undefined;\n }\n if (this.dirtyCount === 0) return;\n const status = this.status;\n const statusPath = this.statusPath;\n if (!status || !statusPath) return;\n const snapshot = this.clone(status);\n this.dirtyCount = 0;\n writeAtomicJson(statusPath, snapshot);\n }\n\n private scheduleFlush(): void {\n if (this.flushTimer) return;\n const timer = setTimeout(() => this.flush(), this.flushIntervalMs);\n // A pending flush timer must never be the reason the process stays alive.\n timer.unref?.();\n this.flushTimer = timer;\n }\n\n private requireStatus(): TStatus {\n if (!this.status) throw new Error('CoalescedStatusWriter used before open().');\n return this.status;\n }\n}\n\n/** Push `entry` onto `status[field]`, dropping the oldest until at most 50 remain. */\nfunction appendCapped(status: StatusWithRecentEntries, field: 'recentTools' | 'recentOutput', entry: unknown): void {\n const list = status[field] ?? [];\n list.push(entry);\n if (list.length > MAX_RECENT_ENTRIES) list.splice(0, list.length - MAX_RECENT_ENTRIES);\n status[field] = list;\n}\n"],"mappings":"0cA+MA,SAAS,EAAa,EAAiC,EAAuC,EAAsB,CAClH,IAAM,EAAO,EAAO,IAAU,EAAE,CAChC,EAAK,KAAK,EAAM,CACZ,EAAK,OAAS,GAAoB,EAAK,OAAO,EAAG,EAAK,OAAS,EAAmB,CACtF,EAAO,GAAS,2BA5J8C,IACN,KAGpD,EAA4B,GAG5B,EAAqB,GASd,EAAmB,gBAwCzB,KAEsC,CAQ3C,gBAA6C,EAE7C,WACA,OACA,WAAqB,EACrB,WAEA,KAAK,EAAe,EAA8B,CAIhD,AAEE,KAAK,cADL,aAAa,KAAK,WAAW,CACX,IAAA,IAEpB,KAAK,WAAa,EAAK,KAAK,GAAgB,CAAE,EAAO,EAAiB,CACtE,KAAK,OAAS,EACd,KAAK,WAAa,EAIlB,EAAgB,KAAK,WAAY,KAAK,MAAM,EAAc,CAAC,CAG7D,OAAO,EAA0C,CAC/C,EAAQ,KAAK,eAAe,CAAC,CAC7B,KAAK,aACL,KAAK,eAAe,CAGtB,WAAW,EAA0C,CACnD,EAAQ,KAAK,eAAe,CAAC,CAC7B,KAAK,aACL,KAAK,OAAO,CAGd,WAAW,EAAsB,CAC/B,KAAK,OAAQ,GAAW,EAAa,EAAQ,cAAe,EAAM,CAAC,CAGrE,aAAa,EAAsB,CACjC,KAAK,OAAQ,GAAW,EAAa,EAAQ,eAAgB,EAAM,CAAC,CAGtE,OAAc,CACZ,KAAK,OAAO,CAQd,MAAmB,EAAa,CAC9B,OAAO,gBAAgB,EAAM,CAG/B,OAAsB,CAKpB,GAJA,AAEE,KAAK,cADL,aAAa,KAAK,WAAW,CACX,IAAA,IAEhB,KAAK,aAAe,EAAG,OAC3B,IAAM,EAAS,KAAK,OACd,EAAa,KAAK,WACxB,GAAI,CAAC,GAAU,CAAC,EAAY,OAC5B,IAAM,EAAW,KAAK,MAAM,EAAO,CACnC,KAAK,WAAa,EAClB,EAAgB,EAAY,EAAS,CAGvC,eAA8B,CAC5B,GAAI,KAAK,WAAY,OACrB,IAAM,EAAQ,eAAiB,KAAK,OAAO,CAAE,KAAK,gBAAgB,CAElE,EAAM,SAAS,CACf,KAAK,WAAa,EAGpB,eAAiC,CAC/B,GAAI,CAAC,KAAK,OAAQ,MAAU,MAAM,4CAA4C,CAC9E,OAAO,KAAK,cA5Ff,GAAY,CAAA,CAAA,EAAA"}
@@ -0,0 +1,2 @@
1
+ require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),t=require(`../../../container/types.cjs`),n=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs`),r=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs`);let i=require(`inversify`);const a=new Set([`complete`,`completed`,`failed`,`paused`,`stopped`]);function o(e,t){return t?{runId:t.runId,status:t.status,activityState:t.activityState,attentionReason:t.attentionReason,error:t.error,startedAt:t.startedAt,updatedAt:t.updatedAt}:{runId:e,status:void 0}}function s(e){return e.status!==void 0&&a.has(e.status)}function c(e){return e.activityState===`needs_attention`}function l(e,t){return t===`attention`?c(e):t===`completion`?s(e):c(e)||s(e)}function u(e){return c(e)?`attention`:`completed`}let d=class{constructor(e){this.tracker=e}pollIntervalMs=250;defaultTimeoutMs=18e5;now(){return Date.now()}sleep(e){return new Promise(t=>{setTimeout(t,e).unref?.()})}async wait(e){let t=this.now(),n=e.timeoutMs!==void 0&&e.timeoutMs>0?e.timeoutMs:this.defaultTimeoutMs,r=e.waitFor??`any`,i=e.sessionId?this.tracker.forSession(e.sessionId):this.tracker,a=this.resolveTargetIds(e.target,i);for(let e of a)i.track(e);let s=()=>this.now()-t,c=()=>a.map(e=>o(e,i.get(e)));if(a.length===0)return{reason:`no-active-runs`,elapsedMs:0,runs:[]};let d=c();if(d.every(e=>e.status===void 0))return{reason:`no-active-runs`,elapsedMs:s(),runs:d};for(;;){let t=c(),i=t.find(e=>l(e,r));if(i)return{reason:r===`completion`?`completed`:r===`attention`?`attention`:u(i),elapsedMs:s(),runs:t};if(e.signal?.aborted)return{reason:`aborted`,elapsedMs:s(),runs:t};if(s()>=n)return{reason:`timeout`,elapsedMs:s(),runs:t};await this.sleep(Math.min(this.pollIntervalMs,Math.max(0,n-s())))}}resolveTargetIds(e,t){return`id`in e?[e.id]:`ids`in e?e.ids:t.list().map(e=>e.runId)}};d=e.__decorate([(0,i.injectable)(),r.__decorateParam(0,(0,i.inject)(t.DOOM_TEAM_TYPES.AsyncJobTracker)),n.__decorateMetadata(`design:paramtypes`,[Object])],d),Object.defineProperty(exports,`SubagentWaiter`,{enumerable:!0,get:function(){return d}});
2
+ //# sourceMappingURL=subagentWait.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagentWait.cjs","names":["DOOM_TEAM_TYPES"],"sources":["../../../../src/services/runs/background/subagentWait.ts"],"sourcesContent":["/**\n * Blocks a turn until one or more tracked async runs finish, need attention,\n * or a timeout elapses - resolving with a status snapshot every time.\n *\n * WHY THIS IS A REDESIGN, NOT A PORT:\n * `doom-pi-subagents/src/runs/background/subagentWait.ts` is 686 lines\n * because it also waits on remembered detached FOREGROUND runs (reading\n * child transcripts byte-range-tailed off disk to render a live preview) and\n * session-scoped background-work PROVIDER items (a separate registration\n * protocol with its own wake channels), on top of async runs. This package is\n * async-only - there is no foreground run mode, and no provider protocol has\n * been ported (see `spawnHandshake.ts`'s header for the same \"async-only\"\n * framing applied elsewhere). Waiting on either would be dead code with\n * nothing to ever populate it. Only the async-run wait survives, rebuilt\n * against what this package actually has.\n *\n * COMPOSITION, NOT REIMPLEMENTATION:\n * `status.json` has one writer (`CoalescedStatusWriter`) and several\n * legitimate readers; this module is not a new one. It reads run state\n * exclusively through `AsyncJobTracker`, which already owns the poll-and-cache\n * loop against `status.json` (`PollScheduler`-driven, per its own header).\n * `wait()` never touches `node:fs` and registers no `PollScheduler`\n * subscription of its own - see \"WHY A PRIVATE POLL LOOP\" below for why it\n * still owns a short-lived timer despite that.\n *\n * TARGET SHAPE - `{ id } | { ids } | { all: true }`:\n * One run, several, or every run `AsyncJobTracker` currently has tracked\n * (`list()` IS that set - whatever the run's own spawn path called `track()`\n * for is what \"outstanding\" means here; this module does not independently\n * decide what counts as outstanding). An `id`/`ids` entry not yet tracked is\n * tracked on the caller's behalf, mirroring `track()`'s own idempotent-refresh\n * contract, so a caller does not have to track before it can wait.\n *\n * RETURN-ON-FIRST, NOT WAIT-FOR-ALL:\n * `wait()` resolves the moment ANY targeted run satisfies `waitFor`, not once\n * every targeted run does. This is a deliberate judgement call, not something\n * spelled out in the brief, and it is the more expressive of the two shapes:\n * wait-for-all composes out of return-on-first (call `wait()` again for\n * whatever remains in `WaitOutcome.runs` that has not yet satisfied\n * `waitFor`), but the reverse does not hold - if wait-for-all were the\n * primitive, a caller that wants the NEXT completion (the predecessor's own\n * rolling-replacement fleet-manager loop: wait for one, spawn its\n * replacement, wait again) could only get it by polling. Choosing the shape\n * that does not foreclose the other is why this is return-on-first rather\n * than a use case that happened to need it; the fleet-manager loop is\n * evidence the choice is usable, not the reason for it. If a real caller\n * turns up needing true wait-for-all-at-once as a single call, that is a\n * reason to add it later, built on top of this primitive.\n *\n * TIMEOUT IS A SNAPSHOT, NOT AN ERROR:\n * `wait()` always resolves; it never rejects for a mere timeout. Waiting\n * longer than asked is a normal outcome of an async system, and forcing every\n * caller into a `try`/`catch` for it is how a real error (a thrown bug) ends\n * up caught and swallowed right alongside it - the same reasoning\n * `SpawnHandshake`'s header gives for its own three-outcome, all-resolved\n * design. `WaitOutcome.reason` is `'timeout'`; the caller decides what to do\n * with `WaitOutcome.runs`, which is populated on every path, including this one.\n *\n * `'attention'` MUST WIN EARLY - THE POINT OF THIS INTERNAL SERVICE:\n * `DeliverableGuard.evaluate()` computes `activityState: 'needs_attention'`\n * and `reason: 'missing-deliverable'` after a failed nudge, but has no\n * standing to write `status.json` itself (see its own header). Once whatever\n * calls `evaluate()` folds that result into `status.json` through\n * `CoalescedStatusWriter` - the run's own runner main loop, not yet built -\n * `AsyncJobTracker` picks the fields up on its next poll (see that module's\n * own header note on this), and THIS module is what lets a caller that is\n * blocked in `wait()` ever find out, instead of only discovering it by\n * polling `subagent({ action: \"status\" })` in a loop. `waitFor: 'attention'`\n * returns the instant any targeted run's `activityState` is\n * `'needs_attention'`, before its timeout, before it would ever go terminal.\n * `waitFor: 'completion'` deliberately does the opposite - it waits THROUGH\n * attention, for a caller (an auto-drain loop) that wants to know about\n * results, not about a mid-flight nudge. `'any'` (the default) returns on\n * whichever comes first, matching the predecessor's default behaviour.\n *\n * WHY A PRIVATE POLL LOOP, NOT A SECOND `PollScheduler` SUBSCRIPTION:\n * `PollScheduler` is for long-lived subscribers that back off toward a shared\n * idle ceiling; `wait()` is the opposite shape, a short-lived, bounded call\n * that needs a TIGHT interval for its whole (short) life, the same reasoning\n * `SpawnHandshake`'s header gives for not registering with it either. The loop\n * here only ever reads `AsyncJobTracker`'s already-cached, in-memory state\n * (no `node:fs`, no disk I/O of its own), so polling it every\n * `pollIntervalMs` costs a handful of `Map` reads, not a filesystem round trip.\n *\n * WHY NO EVENT-BUS WAKE (dropped from the predecessor):\n * The predecessor wakes early on `pi.events` firing (an `ExtensionAPI` event\n * bus this package has not built) and otherwise falls back to a fixed poll.\n * With no bus to wake on, this module is poll-only - which is exactly the\n * fallback path the predecessor already had to support for when its own bus\n * was unavailable, so there is no new failure mode here, only a narrower one.\n *\n * AVOID:\n * - Reading `status.json` directly. If a field `wait()` needs is missing from\n * `TrackedAsyncJob`, that is a reason to extend `AsyncJobTracker` (see its\n * header's note on `activityState`/`attentionReason` for the last time this\n * happened), not to open a second read path around it\n * - Treating a run id that was never tracked and has no readable `status.json`\n * the same as one that is merely slow. `wait()` resolves `'no-active-runs'`\n * immediately when NONE of the targeted ids ever resolve to a known status,\n * rather than waiting out the full timeout on nothing\n */\n\nimport { inject, injectable } from 'inversify';\n\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport type { ActivityState } from '../../../types/index.ts';\nimport type { IAsyncJobTracker, ITrackedAsyncJobs } from './asyncJobTracker.ts';\n\n/** States `AsyncJobTracker` (mirroring `staleRunReconciler.ts`) treats as finished. */\nconst TERMINAL_STATES: ReadonlySet<string> = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\n\nconst DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;\nconst DEFAULT_POLL_INTERVAL_MS = 250;\nexport type WaitTarget = { id: string } | { ids: string[] } | { all: true };\n\nexport type WaitForMode = 'completion' | 'attention' | 'any';\n\nexport interface WaitRequest {\n target: WaitTarget;\n /** Pi context that owns the targeted run collection. */\n sessionId?: string;\n /** Defaults to `'any'`. */\n waitFor?: WaitForMode;\n /** Defaults to 30 minutes. */\n timeoutMs?: number;\n /** Resolves early with `reason: 'aborted'` when this fires. */\n signal?: AbortSignal;\n}\n\nexport type WaitReason = 'completed' | 'attention' | 'timeout' | 'aborted' | 'no-active-runs';\n\n/** One targeted run's state at the moment `wait()` resolved. */\nexport interface WaitRunSnapshot {\n runId: string;\n /** `undefined` when this run has never had a readable `status.json`. */\n status: string | undefined;\n activityState?: ActivityState;\n /** Present alongside `activityState: 'needs_attention'`, e.g. `'missing-deliverable'`. */\n attentionReason?: string;\n error?: string;\n startedAt?: number;\n updatedAt?: number;\n}\n\nexport interface WaitOutcome {\n reason: WaitReason;\n elapsedMs: number;\n /** Every targeted run's snapshot, regardless of `reason` - populated even on `'timeout'`. */\n runs: WaitRunSnapshot[];\n}\n\nexport interface ISubagentWaiter {\n wait(request: WaitRequest): Promise<WaitOutcome>;\n}\n\nfunction snapshotFrom(runId: string, job: ReturnType<ITrackedAsyncJobs['get']>): WaitRunSnapshot {\n if (!job) return { runId, status: undefined };\n return {\n runId: job.runId,\n status: job.status,\n activityState: job.activityState,\n attentionReason: job.attentionReason,\n error: job.error,\n startedAt: job.startedAt,\n updatedAt: job.updatedAt,\n };\n}\n\nfunction isTerminal(snapshot: WaitRunSnapshot): boolean {\n return snapshot.status !== undefined && TERMINAL_STATES.has(snapshot.status);\n}\n\nfunction needsAttention(snapshot: WaitRunSnapshot): boolean {\n return snapshot.activityState === 'needs_attention';\n}\n\nfunction satisfies(snapshot: WaitRunSnapshot, waitFor: WaitForMode): boolean {\n if (waitFor === 'attention') return needsAttention(snapshot);\n if (waitFor === 'completion') return isTerminal(snapshot);\n return needsAttention(snapshot) || isTerminal(snapshot);\n}\n\n/** For `waitFor: 'any'` only: which condition actually satisfied it. Attention wins when both happen to be true, since it is the more actionable signal. */\nfunction reasonFor(snapshot: WaitRunSnapshot): WaitReason {\n return needsAttention(snapshot) ? 'attention' : 'completed';\n}\n\n@injectable()\nexport class SubagentWaiter implements ISubagentWaiter {\n constructor(@inject(DOOM_TEAM_TYPES.AsyncJobTracker) private readonly tracker: IAsyncJobTracker) {}\n\n /**\n * Seams for tests, as protected members rather than constructor arguments:\n * inversify cannot resolve a primitive. See `pollScheduler.ts` and\n * `agents/discovery.ts` for the same pattern.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly defaultTimeoutMs: number = DEFAULT_TIMEOUT_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n /** Sleep seam so a test can drive this with fake timers without a real delay. */\n protected sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n }\n\n async wait(request: WaitRequest): Promise<WaitOutcome> {\n const startedAt = this.now();\n const timeoutMs =\n request.timeoutMs !== undefined && request.timeoutMs > 0 ? request.timeoutMs : this.defaultTimeoutMs;\n const waitFor = request.waitFor ?? 'any';\n\n const jobs = request.sessionId ? this.tracker.forSession(request.sessionId) : this.tracker;\n const runIds = this.resolveTargetIds(request.target, jobs);\n for (const runId of runIds) jobs.track(runId);\n\n const elapsed = (): number => this.now() - startedAt;\n const snapshotAll = (): WaitRunSnapshot[] => runIds.map((runId) => snapshotFrom(runId, jobs.get(runId)));\n\n if (runIds.length === 0) {\n return { reason: 'no-active-runs', elapsedMs: 0, runs: [] };\n }\n\n const initialRuns = snapshotAll();\n if (initialRuns.every((snapshot) => snapshot.status === undefined)) {\n return { reason: 'no-active-runs', elapsedMs: elapsed(), runs: initialRuns };\n }\n\n for (;;) {\n const runs = snapshotAll();\n const satisfied = runs.find((snapshot) => satisfies(snapshot, waitFor));\n if (satisfied) {\n // The label reflects what `waitFor` actually asked for, not merely\n // whatever the snapshot happens to carry: a run that is BOTH terminal\n // and (independently) still flagged needs_attention must still report\n // 'completed' for `waitFor: 'completion'`, since attention was never\n // part of what that mode was watching for.\n const reason: WaitReason =\n waitFor === 'completion' ? 'completed' : waitFor === 'attention' ? 'attention' : reasonFor(satisfied);\n return { reason, elapsedMs: elapsed(), runs };\n }\n if (request.signal?.aborted) {\n return { reason: 'aborted', elapsedMs: elapsed(), runs };\n }\n if (elapsed() >= timeoutMs) {\n return { reason: 'timeout', elapsedMs: elapsed(), runs };\n }\n await this.sleep(Math.min(this.pollIntervalMs, Math.max(0, timeoutMs - elapsed())));\n }\n }\n\n private resolveTargetIds(target: WaitTarget, jobs: ITrackedAsyncJobs): string[] {\n if ('id' in target) return [target.id];\n if ('ids' in target) return target.ids;\n return jobs.list().map((job) => job.runId);\n }\n}\n"],"mappings":"iYA6GA,MAAM,EAAuC,IAAI,IAAI,CAAC,WAAY,YAAa,SAAU,SAAU,UAAU,CAAC,CA8C9G,SAAS,EAAa,EAAe,EAA4D,CAE/F,OADK,EACE,CACL,MAAO,EAAI,MACX,OAAQ,EAAI,OACZ,cAAe,EAAI,cACnB,gBAAiB,EAAI,gBACrB,MAAO,EAAI,MACX,UAAW,EAAI,UACf,UAAW,EAAI,UAChB,CATgB,CAAE,QAAO,OAAQ,IAAA,GAAW,CAY/C,SAAS,EAAW,EAAoC,CACtD,OAAO,EAAS,SAAW,IAAA,IAAa,EAAgB,IAAI,EAAS,OAAO,CAG9E,SAAS,EAAe,EAAoC,CAC1D,OAAO,EAAS,gBAAkB,kBAGpC,SAAS,EAAU,EAA2B,EAA+B,CAG3E,OAFI,IAAY,YAAoB,EAAe,EAAS,CACxD,IAAY,aAAqB,EAAW,EAAS,CAClD,EAAe,EAAS,EAAI,EAAW,EAAS,CAIzD,SAAS,EAAU,EAAuC,CACxD,OAAO,EAAe,EAAS,CAAG,YAAc,YAI3C,IAAA,EAAA,KAAgD,CACrD,YAAY,EAAqF,CAA3B,KAAA,QAAA,EAOtE,eAA4C,IAC5C,iBAA8C,KAE9C,KAAwB,CACtB,OAAO,KAAK,KAAK,CAInB,MAAgB,EAA2B,CACzC,OAAO,IAAI,QAAS,GAAY,CAChB,WAAW,EAAS,EAC7B,CAAC,SAAS,EACf,CAGJ,MAAM,KAAK,EAA4C,CACrD,IAAM,EAAY,KAAK,KAAK,CACtB,EACJ,EAAQ,YAAc,IAAA,IAAa,EAAQ,UAAY,EAAI,EAAQ,UAAY,KAAK,iBAChF,EAAU,EAAQ,SAAW,MAE7B,EAAO,EAAQ,UAAY,KAAK,QAAQ,WAAW,EAAQ,UAAU,CAAG,KAAK,QAC7E,EAAS,KAAK,iBAAiB,EAAQ,OAAQ,EAAK,CAC1D,IAAK,IAAM,KAAS,EAAQ,EAAK,MAAM,EAAM,CAE7C,IAAM,MAAwB,KAAK,KAAK,CAAG,EACrC,MAAuC,EAAO,IAAK,GAAU,EAAa,EAAO,EAAK,IAAI,EAAM,CAAC,CAAC,CAExG,GAAI,EAAO,SAAW,EACpB,MAAO,CAAE,OAAQ,iBAAkB,UAAW,EAAG,KAAM,EAAE,CAAE,CAG7D,IAAM,EAAc,GAAa,CACjC,GAAI,EAAY,MAAO,GAAa,EAAS,SAAW,IAAA,GAAU,CAChE,MAAO,CAAE,OAAQ,iBAAkB,UAAW,GAAS,CAAE,KAAM,EAAa,CAG9E,OAAS,CACP,IAAM,EAAO,GAAa,CACpB,EAAY,EAAK,KAAM,GAAa,EAAU,EAAU,EAAQ,CAAC,CACvE,GAAI,EAQF,MAAO,CAAE,OADP,IAAY,aAAe,YAAc,IAAY,YAAc,YAAc,EAAU,EAAU,CACtF,UAAW,GAAS,CAAE,OAAM,CAE/C,GAAI,EAAQ,QAAQ,QAClB,MAAO,CAAE,OAAQ,UAAW,UAAW,GAAS,CAAE,OAAM,CAE1D,GAAI,GAAS,EAAI,EACf,MAAO,CAAE,OAAQ,UAAW,UAAW,GAAS,CAAE,OAAM,CAE1D,MAAM,KAAK,MAAM,KAAK,IAAI,KAAK,eAAgB,KAAK,IAAI,EAAG,EAAY,GAAS,CAAC,CAAC,CAAC,EAIvF,iBAAyB,EAAoB,EAAmC,CAG9E,MAFI,OAAQ,EAAe,CAAC,EAAO,GAAG,CAClC,QAAS,EAAe,EAAO,IAC5B,EAAK,MAAM,CAAC,IAAK,GAAQ,EAAI,MAAM,qCAxEjC,kCAESA,EAAAA,gBAAgB,gBAAgB,CAAA"}
@@ -0,0 +1,2 @@
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{DOOM_TEAM_TYPES as r,init_types as i}from"../../../container/types.mjs";import{__decorateMetadata as a,init_decorateMetadata as o}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as s,init_decorateParam as c}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{inject as l,injectable as u}from"inversify";function d(e,t){return t?{runId:t.runId,status:t.status,activityState:t.activityState,attentionReason:t.attentionReason,error:t.error,startedAt:t.startedAt,updatedAt:t.updatedAt}:{runId:e,status:void 0}}function f(e){return e.status!==void 0&&g.has(e.status)}function p(e){return e.activityState===`needs_attention`}function m(e,t){return t===`attention`?p(e):t===`completion`?f(e):p(e)||f(e)}function h(e){return p(e)?`attention`:`completed`}var g,_,v,y,b=e((()=>{i(),o(),c(),n(),g=new Set([`complete`,`completed`,`failed`,`paused`,`stopped`]),_=1800*1e3,v=250,y=class{constructor(e){this.tracker=e}pollIntervalMs=v;defaultTimeoutMs=_;now(){return Date.now()}sleep(e){return new Promise(t=>{setTimeout(t,e).unref?.()})}async wait(e){let t=this.now(),n=e.timeoutMs!==void 0&&e.timeoutMs>0?e.timeoutMs:this.defaultTimeoutMs,r=e.waitFor??`any`,i=e.sessionId?this.tracker.forSession(e.sessionId):this.tracker,a=this.resolveTargetIds(e.target,i);for(let e of a)i.track(e);let o=()=>this.now()-t,s=()=>a.map(e=>d(e,i.get(e)));if(a.length===0)return{reason:`no-active-runs`,elapsedMs:0,runs:[]};let c=s();if(c.every(e=>e.status===void 0))return{reason:`no-active-runs`,elapsedMs:o(),runs:c};for(;;){let t=s(),i=t.find(e=>m(e,r));if(i)return{reason:r===`completion`?`completed`:r===`attention`?`attention`:h(i),elapsedMs:o(),runs:t};if(e.signal?.aborted)return{reason:`aborted`,elapsedMs:o(),runs:t};if(o()>=n)return{reason:`timeout`,elapsedMs:o(),runs:t};await this.sleep(Math.min(this.pollIntervalMs,Math.max(0,n-o())))}}resolveTargetIds(e,t){return`id`in e?[e.id]:`ids`in e?e.ids:t.list().map(e=>e.runId)}},y=t([u(),s(0,l(r.AsyncJobTracker)),a(`design:paramtypes`,[Object])],y)}));b();export{y as SubagentWaiter,b as init_subagentWait};
2
+ //# sourceMappingURL=subagentWait.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagentWait.mjs","names":[],"sources":["../../../../src/services/runs/background/subagentWait.ts"],"sourcesContent":["/**\n * Blocks a turn until one or more tracked async runs finish, need attention,\n * or a timeout elapses - resolving with a status snapshot every time.\n *\n * WHY THIS IS A REDESIGN, NOT A PORT:\n * `doom-pi-subagents/src/runs/background/subagentWait.ts` is 686 lines\n * because it also waits on remembered detached FOREGROUND runs (reading\n * child transcripts byte-range-tailed off disk to render a live preview) and\n * session-scoped background-work PROVIDER items (a separate registration\n * protocol with its own wake channels), on top of async runs. This package is\n * async-only - there is no foreground run mode, and no provider protocol has\n * been ported (see `spawnHandshake.ts`'s header for the same \"async-only\"\n * framing applied elsewhere). Waiting on either would be dead code with\n * nothing to ever populate it. Only the async-run wait survives, rebuilt\n * against what this package actually has.\n *\n * COMPOSITION, NOT REIMPLEMENTATION:\n * `status.json` has one writer (`CoalescedStatusWriter`) and several\n * legitimate readers; this module is not a new one. It reads run state\n * exclusively through `AsyncJobTracker`, which already owns the poll-and-cache\n * loop against `status.json` (`PollScheduler`-driven, per its own header).\n * `wait()` never touches `node:fs` and registers no `PollScheduler`\n * subscription of its own - see \"WHY A PRIVATE POLL LOOP\" below for why it\n * still owns a short-lived timer despite that.\n *\n * TARGET SHAPE - `{ id } | { ids } | { all: true }`:\n * One run, several, or every run `AsyncJobTracker` currently has tracked\n * (`list()` IS that set - whatever the run's own spawn path called `track()`\n * for is what \"outstanding\" means here; this module does not independently\n * decide what counts as outstanding). An `id`/`ids` entry not yet tracked is\n * tracked on the caller's behalf, mirroring `track()`'s own idempotent-refresh\n * contract, so a caller does not have to track before it can wait.\n *\n * RETURN-ON-FIRST, NOT WAIT-FOR-ALL:\n * `wait()` resolves the moment ANY targeted run satisfies `waitFor`, not once\n * every targeted run does. This is a deliberate judgement call, not something\n * spelled out in the brief, and it is the more expressive of the two shapes:\n * wait-for-all composes out of return-on-first (call `wait()` again for\n * whatever remains in `WaitOutcome.runs` that has not yet satisfied\n * `waitFor`), but the reverse does not hold - if wait-for-all were the\n * primitive, a caller that wants the NEXT completion (the predecessor's own\n * rolling-replacement fleet-manager loop: wait for one, spawn its\n * replacement, wait again) could only get it by polling. Choosing the shape\n * that does not foreclose the other is why this is return-on-first rather\n * than a use case that happened to need it; the fleet-manager loop is\n * evidence the choice is usable, not the reason for it. If a real caller\n * turns up needing true wait-for-all-at-once as a single call, that is a\n * reason to add it later, built on top of this primitive.\n *\n * TIMEOUT IS A SNAPSHOT, NOT AN ERROR:\n * `wait()` always resolves; it never rejects for a mere timeout. Waiting\n * longer than asked is a normal outcome of an async system, and forcing every\n * caller into a `try`/`catch` for it is how a real error (a thrown bug) ends\n * up caught and swallowed right alongside it - the same reasoning\n * `SpawnHandshake`'s header gives for its own three-outcome, all-resolved\n * design. `WaitOutcome.reason` is `'timeout'`; the caller decides what to do\n * with `WaitOutcome.runs`, which is populated on every path, including this one.\n *\n * `'attention'` MUST WIN EARLY - THE POINT OF THIS INTERNAL SERVICE:\n * `DeliverableGuard.evaluate()` computes `activityState: 'needs_attention'`\n * and `reason: 'missing-deliverable'` after a failed nudge, but has no\n * standing to write `status.json` itself (see its own header). Once whatever\n * calls `evaluate()` folds that result into `status.json` through\n * `CoalescedStatusWriter` - the run's own runner main loop, not yet built -\n * `AsyncJobTracker` picks the fields up on its next poll (see that module's\n * own header note on this), and THIS module is what lets a caller that is\n * blocked in `wait()` ever find out, instead of only discovering it by\n * polling `subagent({ action: \"status\" })` in a loop. `waitFor: 'attention'`\n * returns the instant any targeted run's `activityState` is\n * `'needs_attention'`, before its timeout, before it would ever go terminal.\n * `waitFor: 'completion'` deliberately does the opposite - it waits THROUGH\n * attention, for a caller (an auto-drain loop) that wants to know about\n * results, not about a mid-flight nudge. `'any'` (the default) returns on\n * whichever comes first, matching the predecessor's default behaviour.\n *\n * WHY A PRIVATE POLL LOOP, NOT A SECOND `PollScheduler` SUBSCRIPTION:\n * `PollScheduler` is for long-lived subscribers that back off toward a shared\n * idle ceiling; `wait()` is the opposite shape, a short-lived, bounded call\n * that needs a TIGHT interval for its whole (short) life, the same reasoning\n * `SpawnHandshake`'s header gives for not registering with it either. The loop\n * here only ever reads `AsyncJobTracker`'s already-cached, in-memory state\n * (no `node:fs`, no disk I/O of its own), so polling it every\n * `pollIntervalMs` costs a handful of `Map` reads, not a filesystem round trip.\n *\n * WHY NO EVENT-BUS WAKE (dropped from the predecessor):\n * The predecessor wakes early on `pi.events` firing (an `ExtensionAPI` event\n * bus this package has not built) and otherwise falls back to a fixed poll.\n * With no bus to wake on, this module is poll-only - which is exactly the\n * fallback path the predecessor already had to support for when its own bus\n * was unavailable, so there is no new failure mode here, only a narrower one.\n *\n * AVOID:\n * - Reading `status.json` directly. If a field `wait()` needs is missing from\n * `TrackedAsyncJob`, that is a reason to extend `AsyncJobTracker` (see its\n * header's note on `activityState`/`attentionReason` for the last time this\n * happened), not to open a second read path around it\n * - Treating a run id that was never tracked and has no readable `status.json`\n * the same as one that is merely slow. `wait()` resolves `'no-active-runs'`\n * immediately when NONE of the targeted ids ever resolve to a known status,\n * rather than waiting out the full timeout on nothing\n */\n\nimport { inject, injectable } from 'inversify';\n\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport type { ActivityState } from '../../../types/index.ts';\nimport type { IAsyncJobTracker, ITrackedAsyncJobs } from './asyncJobTracker.ts';\n\n/** States `AsyncJobTracker` (mirroring `staleRunReconciler.ts`) treats as finished. */\nconst TERMINAL_STATES: ReadonlySet<string> = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\n\nconst DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;\nconst DEFAULT_POLL_INTERVAL_MS = 250;\nexport type WaitTarget = { id: string } | { ids: string[] } | { all: true };\n\nexport type WaitForMode = 'completion' | 'attention' | 'any';\n\nexport interface WaitRequest {\n target: WaitTarget;\n /** Pi context that owns the targeted run collection. */\n sessionId?: string;\n /** Defaults to `'any'`. */\n waitFor?: WaitForMode;\n /** Defaults to 30 minutes. */\n timeoutMs?: number;\n /** Resolves early with `reason: 'aborted'` when this fires. */\n signal?: AbortSignal;\n}\n\nexport type WaitReason = 'completed' | 'attention' | 'timeout' | 'aborted' | 'no-active-runs';\n\n/** One targeted run's state at the moment `wait()` resolved. */\nexport interface WaitRunSnapshot {\n runId: string;\n /** `undefined` when this run has never had a readable `status.json`. */\n status: string | undefined;\n activityState?: ActivityState;\n /** Present alongside `activityState: 'needs_attention'`, e.g. `'missing-deliverable'`. */\n attentionReason?: string;\n error?: string;\n startedAt?: number;\n updatedAt?: number;\n}\n\nexport interface WaitOutcome {\n reason: WaitReason;\n elapsedMs: number;\n /** Every targeted run's snapshot, regardless of `reason` - populated even on `'timeout'`. */\n runs: WaitRunSnapshot[];\n}\n\nexport interface ISubagentWaiter {\n wait(request: WaitRequest): Promise<WaitOutcome>;\n}\n\nfunction snapshotFrom(runId: string, job: ReturnType<ITrackedAsyncJobs['get']>): WaitRunSnapshot {\n if (!job) return { runId, status: undefined };\n return {\n runId: job.runId,\n status: job.status,\n activityState: job.activityState,\n attentionReason: job.attentionReason,\n error: job.error,\n startedAt: job.startedAt,\n updatedAt: job.updatedAt,\n };\n}\n\nfunction isTerminal(snapshot: WaitRunSnapshot): boolean {\n return snapshot.status !== undefined && TERMINAL_STATES.has(snapshot.status);\n}\n\nfunction needsAttention(snapshot: WaitRunSnapshot): boolean {\n return snapshot.activityState === 'needs_attention';\n}\n\nfunction satisfies(snapshot: WaitRunSnapshot, waitFor: WaitForMode): boolean {\n if (waitFor === 'attention') return needsAttention(snapshot);\n if (waitFor === 'completion') return isTerminal(snapshot);\n return needsAttention(snapshot) || isTerminal(snapshot);\n}\n\n/** For `waitFor: 'any'` only: which condition actually satisfied it. Attention wins when both happen to be true, since it is the more actionable signal. */\nfunction reasonFor(snapshot: WaitRunSnapshot): WaitReason {\n return needsAttention(snapshot) ? 'attention' : 'completed';\n}\n\n@injectable()\nexport class SubagentWaiter implements ISubagentWaiter {\n constructor(@inject(DOOM_TEAM_TYPES.AsyncJobTracker) private readonly tracker: IAsyncJobTracker) {}\n\n /**\n * Seams for tests, as protected members rather than constructor arguments:\n * inversify cannot resolve a primitive. See `pollScheduler.ts` and\n * `agents/discovery.ts` for the same pattern.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly defaultTimeoutMs: number = DEFAULT_TIMEOUT_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n /** Sleep seam so a test can drive this with fake timers without a real delay. */\n protected sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n }\n\n async wait(request: WaitRequest): Promise<WaitOutcome> {\n const startedAt = this.now();\n const timeoutMs =\n request.timeoutMs !== undefined && request.timeoutMs > 0 ? request.timeoutMs : this.defaultTimeoutMs;\n const waitFor = request.waitFor ?? 'any';\n\n const jobs = request.sessionId ? this.tracker.forSession(request.sessionId) : this.tracker;\n const runIds = this.resolveTargetIds(request.target, jobs);\n for (const runId of runIds) jobs.track(runId);\n\n const elapsed = (): number => this.now() - startedAt;\n const snapshotAll = (): WaitRunSnapshot[] => runIds.map((runId) => snapshotFrom(runId, jobs.get(runId)));\n\n if (runIds.length === 0) {\n return { reason: 'no-active-runs', elapsedMs: 0, runs: [] };\n }\n\n const initialRuns = snapshotAll();\n if (initialRuns.every((snapshot) => snapshot.status === undefined)) {\n return { reason: 'no-active-runs', elapsedMs: elapsed(), runs: initialRuns };\n }\n\n for (;;) {\n const runs = snapshotAll();\n const satisfied = runs.find((snapshot) => satisfies(snapshot, waitFor));\n if (satisfied) {\n // The label reflects what `waitFor` actually asked for, not merely\n // whatever the snapshot happens to carry: a run that is BOTH terminal\n // and (independently) still flagged needs_attention must still report\n // 'completed' for `waitFor: 'completion'`, since attention was never\n // part of what that mode was watching for.\n const reason: WaitReason =\n waitFor === 'completion' ? 'completed' : waitFor === 'attention' ? 'attention' : reasonFor(satisfied);\n return { reason, elapsedMs: elapsed(), runs };\n }\n if (request.signal?.aborted) {\n return { reason: 'aborted', elapsedMs: elapsed(), runs };\n }\n if (elapsed() >= timeoutMs) {\n return { reason: 'timeout', elapsedMs: elapsed(), runs };\n }\n await this.sleep(Math.min(this.pollIntervalMs, Math.max(0, timeoutMs - elapsed())));\n }\n }\n\n private resolveTargetIds(target: WaitTarget, jobs: ITrackedAsyncJobs): string[] {\n if ('id' in target) return [target.id];\n if ('ids' in target) return target.ids;\n return jobs.list().map((job) => job.runId);\n }\n}\n"],"mappings":"2kBA2JA,SAAS,EAAa,EAAe,EAA4D,CAE/F,OADK,EACE,CACL,MAAO,EAAI,MACX,OAAQ,EAAI,OACZ,cAAe,EAAI,cACnB,gBAAiB,EAAI,gBACrB,MAAO,EAAI,MACX,UAAW,EAAI,UACf,UAAW,EAAI,UAChB,CATgB,CAAE,QAAO,OAAQ,IAAA,GAAW,CAY/C,SAAS,EAAW,EAAoC,CACtD,OAAO,EAAS,SAAW,IAAA,IAAa,EAAgB,IAAI,EAAS,OAAO,CAG9E,SAAS,EAAe,EAAoC,CAC1D,OAAO,EAAS,gBAAkB,kBAGpC,SAAS,EAAU,EAA2B,EAA+B,CAG3E,OAFI,IAAY,YAAoB,EAAe,EAAS,CACxD,IAAY,aAAqB,EAAW,EAAS,CAClD,EAAe,EAAS,EAAI,EAAW,EAAS,CAIzD,SAAS,EAAU,EAAuC,CACxD,OAAO,EAAe,EAAS,CAAG,YAAc,qCAhFY,aAKxD,EAAuC,IAAI,IAAI,CAAC,WAAY,YAAa,SAAU,SAAU,UAAU,CAAC,CAExG,EAAqB,KAAU,IAC/B,EAA2B,MA4E1B,KAAgD,CACrD,YAAY,EAAqF,CAA3B,KAAA,QAAA,EAOtE,eAA4C,EAC5C,iBAA8C,EAE9C,KAAwB,CACtB,OAAO,KAAK,KAAK,CAInB,MAAgB,EAA2B,CACzC,OAAO,IAAI,QAAS,GAAY,CAChB,WAAW,EAAS,EAC7B,CAAC,SAAS,EACf,CAGJ,MAAM,KAAK,EAA4C,CACrD,IAAM,EAAY,KAAK,KAAK,CACtB,EACJ,EAAQ,YAAc,IAAA,IAAa,EAAQ,UAAY,EAAI,EAAQ,UAAY,KAAK,iBAChF,EAAU,EAAQ,SAAW,MAE7B,EAAO,EAAQ,UAAY,KAAK,QAAQ,WAAW,EAAQ,UAAU,CAAG,KAAK,QAC7E,EAAS,KAAK,iBAAiB,EAAQ,OAAQ,EAAK,CAC1D,IAAK,IAAM,KAAS,EAAQ,EAAK,MAAM,EAAM,CAE7C,IAAM,MAAwB,KAAK,KAAK,CAAG,EACrC,MAAuC,EAAO,IAAK,GAAU,EAAa,EAAO,EAAK,IAAI,EAAM,CAAC,CAAC,CAExG,GAAI,EAAO,SAAW,EACpB,MAAO,CAAE,OAAQ,iBAAkB,UAAW,EAAG,KAAM,EAAE,CAAE,CAG7D,IAAM,EAAc,GAAa,CACjC,GAAI,EAAY,MAAO,GAAa,EAAS,SAAW,IAAA,GAAU,CAChE,MAAO,CAAE,OAAQ,iBAAkB,UAAW,GAAS,CAAE,KAAM,EAAa,CAG9E,OAAS,CACP,IAAM,EAAO,GAAa,CACpB,EAAY,EAAK,KAAM,GAAa,EAAU,EAAU,EAAQ,CAAC,CACvE,GAAI,EAQF,MAAO,CAAE,OADP,IAAY,aAAe,YAAc,IAAY,YAAc,YAAc,EAAU,EAAU,CACtF,UAAW,GAAS,CAAE,OAAM,CAE/C,GAAI,EAAQ,QAAQ,QAClB,MAAO,CAAE,OAAQ,UAAW,UAAW,GAAS,CAAE,OAAM,CAE1D,GAAI,GAAS,EAAI,EACf,MAAO,CAAE,OAAQ,UAAW,UAAW,GAAS,CAAE,OAAM,CAE1D,MAAM,KAAK,MAAM,KAAK,IAAI,KAAK,eAAgB,KAAK,IAAI,EAAG,EAAY,GAAS,CAAC,CAAC,CAAC,EAIvF,iBAAyB,EAAoB,EAAmC,CAG9E,MAFI,OAAQ,EAAe,CAAC,EAAO,GAAG,CAClC,QAAS,EAAe,EAAO,IAC5B,EAAK,MAAM,CAAC,IAAK,GAAQ,EAAI,MAAM,QAxE7C,GAAY,KAEE,EAAO,EAAgB,gBAAgB,CAAA"}
@@ -1,2 +1,2 @@
1
- import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{init_atomicJson as t,writeAtomicJson as n}from"../../shared/atomicJson.mjs";import{__decorate as r,init_decorate as i}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{DOOM_TEAM_TYPES as a,init_types as o}from"../../container/types.mjs";import{__decorateMetadata as s,init_decorateMetadata as c}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as l,init_decorateParam as u}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{currentRunsDir as d,init_paths as f}from"../../shared/paths.mjs";import*as p from"node:fs";import*as m from"node:path";import{inject as h,injectable as g}from"inversify";import*as _ from"node:os";function v(e){return m.join(d(),e,b)}function y(e){return 128+_.constants.signals[e]}var b,x,S,C,w=e((()=>{o(),t(),f(),c(),u(),i(),b=`runner-crash-marker.json`,x=1,S=1,C=class{constructor(e){this.statusWriter=e}runId;mutateTerminalStatus;finalized=!0;handlersInstalled=!1;trackedChildPids=new Set;handleSigterm=()=>this.handleSignal(`SIGTERM`);handleSigint=()=>this.handleSignal(`SIGINT`);handleSighup=()=>this.handleSignal(`SIGHUP`);handleUncaughtException=e=>this.handleFatal({kind:`uncaughtException`,error:e});handleUnhandledRejection=e=>this.handleFatal({kind:`unhandledRejection`,reason:e});now(){return Date.now()}onSignal(e,t){process.on(e,t)}offSignal(e,t){process.off(e,t)}onUncaughtException(e){process.on(`uncaughtException`,e)}offUncaughtException(e){process.off(`uncaughtException`,e)}onUnhandledRejection(e){process.on(`unhandledRejection`,e)}offUnhandledRejection(e){process.off(`unhandledRejection`,e)}killProcess(e,t){try{process.kill(e,t)}catch{}}exitProcess(e){process.exit(e)}begin(e,t){this.dispose(),this.runId=e,this.mutateTerminalStatus=t,this.finalized=!1,this.trackedChildPids.clear(),this.writeCrashMarker(e),this.installHandlers()}trackChild(e){this.trackedChildPids.add(e)}untrackChild(e){this.trackedChildPids.delete(e)}finalize(){this.triggerFinalize(void 0)}dispose(){this.handlersInstalled&&(this.handlersInstalled=!1,this.offSignal(`SIGTERM`,this.handleSigterm),this.offSignal(`SIGINT`,this.handleSigint),this.offSignal(`SIGHUP`,this.handleSighup),this.offUncaughtException(this.handleUncaughtException),this.offUnhandledRejection(this.handleUnhandledRejection))}installHandlers(){this.handlersInstalled||(this.handlersInstalled=!0,this.onSignal(`SIGTERM`,this.handleSigterm),this.onSignal(`SIGINT`,this.handleSigint),this.onSignal(`SIGHUP`,this.handleSighup),this.onUncaughtException(this.handleUncaughtException),this.onUnhandledRejection(this.handleUnhandledRejection))}handleSignal(e){this.triggerFinalize({kind:`signal`,signal:e}),this.exitProcess(y(e))}handleFatal(e){this.triggerFinalize(e),this.exitProcess(S)}triggerFinalize(e){if(!this.finalized){this.finalized=!0,this.dispose();try{let t=this.mutateTerminalStatus;t&&this.statusWriter.updateSync(n=>t(n,e)),this.clearCrashMarker()}finally{this.killTrackedChildren()}}}killTrackedChildren(){for(let e of this.trackedChildPids)this.killProcess(e,`SIGKILL`);this.trackedChildPids.clear()}writeCrashMarker(e){let t={version:x,runId:e,pid:process.pid,startedAt:this.now()};n(v(e),t)}clearCrashMarker(){if(this.runId)try{p.rmSync(v(this.runId),{force:!0})}catch{}}},C=r([g(),l(0,h(a.CoalescedStatusWriter)),s(`design:paramtypes`,[Object])],C)}));w();export{b as CRASH_MARKER_FILE_NAME,C as TerminalPersistenceService,w as init_terminalPersistence};
2
- //# sourceMappingURL=terminalPersistence.mjs.map
1
+ const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),n=require(`../../../container/types.cjs`),r=require(`../../../adapters/atomicJson.cjs`);require(`../../../shared/atomicJson.cjs`);const i=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs`),a=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs`),o=require(`../../../shared/paths.cjs`);let s=require(`node:fs`);s=e.__toESM(s,1);let c=require(`node:path`);c=e.__toESM(c,1);let l=require(`inversify`),u=require(`node:os`);u=e.__toESM(u,1);const d=`runner-crash-marker.json`;function f(e){return c.join(o.currentRunsDir(),e,d)}function p(e){return 128+u.constants.signals[e]}let m=class{constructor(e){this.statusWriter=e}runId;mutateTerminalStatus;finalized=!0;handlersInstalled=!1;trackedChildPids=new Set;handleSigterm=()=>this.handleSignal(`SIGTERM`);handleSigint=()=>this.handleSignal(`SIGINT`);handleSighup=()=>this.handleSignal(`SIGHUP`);handleUncaughtException=e=>this.handleFatal({kind:`uncaughtException`,error:e});handleUnhandledRejection=e=>this.handleFatal({kind:`unhandledRejection`,reason:e});now(){return Date.now()}onSignal(e,t){process.on(e,t)}offSignal(e,t){process.off(e,t)}onUncaughtException(e){process.on(`uncaughtException`,e)}offUncaughtException(e){process.off(`uncaughtException`,e)}onUnhandledRejection(e){process.on(`unhandledRejection`,e)}offUnhandledRejection(e){process.off(`unhandledRejection`,e)}killProcess(e,t){try{process.kill(e,t)}catch{}}exitProcess(e){process.exit(e)}begin(e,t){this.dispose(),this.runId=e,this.mutateTerminalStatus=t,this.finalized=!1,this.trackedChildPids.clear(),this.writeCrashMarker(e),this.installHandlers()}trackChild(e){this.trackedChildPids.add(e)}untrackChild(e){this.trackedChildPids.delete(e)}finalize(){this.triggerFinalize(void 0)}dispose(){this.handlersInstalled&&(this.handlersInstalled=!1,this.offSignal(`SIGTERM`,this.handleSigterm),this.offSignal(`SIGINT`,this.handleSigint),this.offSignal(`SIGHUP`,this.handleSighup),this.offUncaughtException(this.handleUncaughtException),this.offUnhandledRejection(this.handleUnhandledRejection))}installHandlers(){this.handlersInstalled||(this.handlersInstalled=!0,this.onSignal(`SIGTERM`,this.handleSigterm),this.onSignal(`SIGINT`,this.handleSigint),this.onSignal(`SIGHUP`,this.handleSighup),this.onUncaughtException(this.handleUncaughtException),this.onUnhandledRejection(this.handleUnhandledRejection))}handleSignal(e){this.triggerFinalize({kind:`signal`,signal:e}),this.exitProcess(p(e))}handleFatal(e){this.triggerFinalize(e),this.exitProcess(1)}triggerFinalize(e){if(!this.finalized){this.finalized=!0,this.dispose();try{let t=this.mutateTerminalStatus;t&&this.statusWriter.updateSync(n=>t(n,e)),this.clearCrashMarker()}finally{this.killTrackedChildren()}}}killTrackedChildren(){for(let e of this.trackedChildPids)this.killProcess(e,`SIGKILL`);this.trackedChildPids.clear()}writeCrashMarker(e){let t={version:1,runId:e,pid:process.pid,startedAt:this.now()};r.writeAtomicJson(f(e),t)}clearCrashMarker(){if(this.runId)try{s.rmSync(f(this.runId),{force:!0})}catch{}}};m=t.__decorate([(0,l.injectable)(),a.__decorateParam(0,(0,l.inject)(n.DOOM_TEAM_TYPES.CoalescedStatusWriter)),i.__decorateMetadata(`design:paramtypes`,[Object])],m),exports.CRASH_MARKER_FILE_NAME=d,Object.defineProperty(exports,`TerminalPersistenceService`,{enumerable:!0,get:function(){return m}});
2
+ //# sourceMappingURL=terminalPersistence.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminalPersistence.cjs","names":["path","currentRunsDir","os","DOOM_TEAM_TYPES"],"sources":["../../../../src/services/runs/background/terminalPersistence.ts"],"sourcesContent":["/**\n * Guarantees one runner process ends with a persisted terminal result.\n *\n * WHY THIS EXISTS:\n * The predecessor's runner finalization\n * (`doom-pi-subagents/src/runs/background/subagent-runner.ts`) is straight-line\n * code with no `try`/`finally` and no signal handling beyond `SIGUSR2`. A throw\n * anywhere in that path, or a `SIGTERM`/`SIGKILL` of the runner process, leaves\n * the run's `status.json` reporting `running` for up to 24 hours, with the\n * `pi` child it spawned still alive and still burning tokens. A user who hits\n * Ctrl-C on a foreground run gets exactly this: the terminal returns, but the\n * child and the stale \"running\" status both live on.\n *\n * DESIGN PATTERNS:\n * - One runner process runs exactly one run, so this is a singleton holding the\n * state of the one run it is guarding, the same shape as `CoalescedStatusWriter`\n * - `ICoalescedStatusWriter` is injected, not assumed: this class holds a real\n * reference to the one true `status.json` writer and calls its synchronous\n * `updateSync` itself, rather than trusting a caller-supplied closure to have\n * done so. `mutateTerminalStatus` stays a caller-supplied closure for the\n * run's own richer status SHAPE only - this class does not know what fields\n * a terminal status carries, just that writing them goes through the one\n * writer, by construction rather than by docstring\n * - Idempotency is a single boolean guard checked and set before anything else\n * happens, so a second trigger - a duplicate signal, a throw during the first\n * finalize - observes \"already finalized\" before it can do anything\n * - Signal and process-exit machinery are `protected` seams, not constructor\n * arguments: inversify cannot inject a primitive, and a unit test must never\n * deliver a real signal to, or call the real `process.exit` on, the process\n * running the test\n *\n * THE CRASH MARKER:\n * `begin()` writes a small marker file before the run does anything else, and\n * a successful `finalize()` deletes it. A run that dies so hard it never reaches\n * `finalize()` at all (`SIGKILL`, a host crash) leaves the marker behind with no\n * result file next to it - the marker's `pid` is then dead, which is what lets a\n * later reader tell \"crashed\" apart from \"still running\": a genuinely running\n * run also has a live marker, but with a live pid. This module only writes and\n * clears the marker; reading it to make that determination is a different\n * module's job.\n *\n * AVOID:\n * - Calling `mutateTerminalStatus` with anything that can be asynchronous. The\n * whole guarantee here depends on finalize running to completion before\n * another trigger can observe the `finalized` flag, which only holds if\n * every step, including `CoalescedStatusWriter.updateSync`, is synchronous\n * - Writing `status.json` from here directly with `writeAtomicJson`; the\n * injected `ICoalescedStatusWriter` is the only thing that may touch it\n * - Calling `process.removeAllListeners` for a signal; that would remove a\n * listener some other module in the same process installed, not just ours\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport { writeAtomicJson } from '../../../shared/atomicJson.ts';\nimport { currentRunsDir } from '../../../shared/paths.ts';\nimport type { ICoalescedStatusWriter, StatusWithRecentEntries } from './statusWriter.ts';\n\n/**\n * The one crash-marker filename.\n *\n * Exported because `processTerminal.ts` reads this marker to decide whether a\n * run died without finishing. It writes the marker, that module reads it, and\n * they must not drift.\n */\nexport const CRASH_MARKER_FILE_NAME = 'runner-crash-marker.json';\nconst CRASH_MARKER_VERSION = 1;\n\n/** Exit code used for a fatal exception, matching Node's own default for an uncaught error. */\nconst FATAL_EXCEPTION_EXIT_CODE = 1;\n\n/** What triggered a terminal finalize, so the caller's `mutateTerminalStatus` closure can record why. */\nexport type TerminalTrigger =\n | { kind: 'signal'; signal: NodeJS.Signals }\n | { kind: 'uncaughtException'; error: Error }\n | { kind: 'unhandledRejection'; reason: unknown };\n\ninterface CrashMarkerRecord {\n version: typeof CRASH_MARKER_VERSION;\n runId: string;\n pid: number;\n startedAt: number;\n}\n\nfunction crashMarkerPath(runId: string): string {\n return path.join(currentRunsDir(), runId, CRASH_MARKER_FILE_NAME);\n}\n\n/** `128 + signal number`, the shell convention for a process killed by a signal. */\nfunction signalExitCode(signal: NodeJS.Signals): number {\n return 128 + os.constants.signals[signal];\n}\n\nexport interface ITerminalPersistenceService<TStatus extends StatusWithRecentEntries = StatusWithRecentEntries> {\n /**\n * Start guarding one run: writes the crash marker and installs the signal\n * and exception handlers. Safe to call again, on the same or a different\n * run id - a second call tears down whatever this instance was previously\n * guarding first, the same reset semantics as `CoalescedStatusWriter.open`.\n *\n * `mutateTerminalStatus` is called at most once, synchronously, on the\n * already-open status passed to `CoalescedStatusWriter.updateSync`, whenever\n * the run reaches a terminal state: `trigger` is undefined for the run's own\n * call to `finalize()`, and describes the signal or exception for every\n * other path. The caller is responsible for having called\n * `ICoalescedStatusWriter.open()` before the run's terminal state can be\n * reached; this class only mutates and flushes, it does not open.\n */\n begin(runId: string, mutateTerminalStatus: (status: TStatus, trigger: TerminalTrigger | undefined) => void): void;\n /** Track a spawned `pi` child so it is killed when this run finalizes. */\n trackChild(pid: number): void;\n /** Stop tracking a child that already exited on its own. */\n untrackChild(pid: number): void;\n /**\n * The run's own terminal call: persists the result through the injected\n * status writer's synchronous flush path, kills every tracked child, and\n * clears the crash marker. Idempotent - a second call, from any source,\n * including a signal that arrives after this one returns, is a no-op.\n */\n finalize(): void;\n /** Remove the installed signal and exception handlers without finalizing. */\n dispose(): void;\n}\n\n/**\n * Owner of the one run a runner process guards.\n *\n * WHY THIS IS A SERVICE AND NOT A TOP-LEVEL FUNCTION:\n * The predecessor's finalization lived inline in the runner's main function, so\n * nothing else in that module could trigger it, and a signal handler installed\n * elsewhere had no way to reach it. Putting it on an injectable instance gives\n * every part of the runner - the main run loop, a spawn-site failure, a signal\n * handler - the same `finalize()` to call, all guarded by the same flag.\n */\n@injectable()\nexport class TerminalPersistenceService<\n TStatus extends StatusWithRecentEntries = StatusWithRecentEntries,\n> implements ITerminalPersistenceService<TStatus> {\n constructor(\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter) private readonly statusWriter: ICoalescedStatusWriter<TStatus>,\n ) {}\n\n private runId: string | undefined;\n private mutateTerminalStatus: ((status: TStatus, trigger: TerminalTrigger | undefined) => void) | undefined;\n /**\n * Starts `true` so that a call to `finalize()` or a delivered signal before\n * `begin()` has ever run is a safe no-op rather than a crash on undefined\n * state.\n */\n private finalized = true;\n private handlersInstalled = false;\n private readonly trackedChildPids = new Set<number>();\n\n // Bound once per instance so `offSignal`/`offUncaughtException` remove the\n // exact function reference `onSignal`/`onUncaughtException` installed.\n private readonly handleSigterm = (): void => this.handleSignal('SIGTERM');\n private readonly handleSigint = (): void => this.handleSignal('SIGINT');\n private readonly handleSighup = (): void => this.handleSignal('SIGHUP');\n private readonly handleUncaughtException = (error: Error): void =>\n this.handleFatal({ kind: 'uncaughtException', error });\n private readonly handleUnhandledRejection = (reason: unknown): void =>\n this.handleFatal({ kind: 'unhandledRejection', reason });\n\n protected now(): number {\n return Date.now();\n }\n\n // ---------------------------------------------------------------------\n // Process seams. A test subclass overrides every one of these so a unit\n // test never registers a real signal handler, never signals a real pid,\n // and never calls the real `process.exit` on the process running the test.\n // ---------------------------------------------------------------------\n\n protected onSignal(event: NodeJS.Signals, handler: NodeJS.SignalsListener): void {\n process.on(event, handler);\n }\n\n protected offSignal(event: NodeJS.Signals, handler: NodeJS.SignalsListener): void {\n process.off(event, handler);\n }\n\n protected onUncaughtException(handler: (error: Error, origin: NodeJS.UncaughtExceptionOrigin) => void): void {\n process.on('uncaughtException', handler);\n }\n\n protected offUncaughtException(handler: (error: Error, origin: NodeJS.UncaughtExceptionOrigin) => void): void {\n process.off('uncaughtException', handler);\n }\n\n protected onUnhandledRejection(handler: (reason: unknown, promise: Promise<unknown>) => void): void {\n process.on('unhandledRejection', handler);\n }\n\n protected offUnhandledRejection(handler: (reason: unknown, promise: Promise<unknown>) => void): void {\n process.off('unhandledRejection', handler);\n }\n\n protected killProcess(pid: number, signal: NodeJS.Signals): void {\n try {\n process.kill(pid, signal);\n } catch {\n // Already exited, or never existed. Nothing left to kill, which is the\n // outcome we wanted anyway.\n }\n }\n\n protected exitProcess(code: number): void {\n // Excluded from coverage: calling the real implementation would terminate\n // the process running whichever suite exercises this class, so every\n // test overrides this seam instead of triggering it for real.\n /* v8 ignore next */\n process.exit(code);\n }\n\n // ---------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------\n\n begin(runId: string, mutateTerminalStatus: (status: TStatus, trigger: TerminalTrigger | undefined) => void): void {\n // A second begin() is a reset: tear down whatever this instance was\n // previously guarding before starting the new one.\n this.dispose();\n this.runId = runId;\n this.mutateTerminalStatus = mutateTerminalStatus;\n this.finalized = false;\n this.trackedChildPids.clear();\n this.writeCrashMarker(runId);\n this.installHandlers();\n }\n\n trackChild(pid: number): void {\n this.trackedChildPids.add(pid);\n }\n\n untrackChild(pid: number): void {\n this.trackedChildPids.delete(pid);\n }\n\n finalize(): void {\n this.triggerFinalize(undefined);\n }\n\n dispose(): void {\n if (!this.handlersInstalled) return;\n this.handlersInstalled = false;\n this.offSignal('SIGTERM', this.handleSigterm);\n this.offSignal('SIGINT', this.handleSigint);\n this.offSignal('SIGHUP', this.handleSighup);\n this.offUncaughtException(this.handleUncaughtException);\n this.offUnhandledRejection(this.handleUnhandledRejection);\n }\n\n // ---------------------------------------------------------------------\n // Internals\n // ---------------------------------------------------------------------\n\n private installHandlers(): void {\n if (this.handlersInstalled) return;\n this.handlersInstalled = true;\n this.onSignal('SIGTERM', this.handleSigterm);\n this.onSignal('SIGINT', this.handleSigint);\n this.onSignal('SIGHUP', this.handleSighup);\n this.onUncaughtException(this.handleUncaughtException);\n this.onUnhandledRejection(this.handleUnhandledRejection);\n }\n\n private handleSignal(signal: NodeJS.Signals): void {\n this.triggerFinalize({ kind: 'signal', signal });\n // Registering a listener for these signals suppresses Node's default\n // terminate-on-signal behaviour, so once we have finalized we must end\n // the process ourselves - otherwise the signal is effectively swallowed\n // and the process hangs instead of honouring the Ctrl-C or `kill` that\n // was sent to it.\n this.exitProcess(signalExitCode(signal));\n }\n\n private handleFatal(trigger: TerminalTrigger): void {\n this.triggerFinalize(trigger);\n this.exitProcess(FATAL_EXCEPTION_EXIT_CODE);\n }\n\n /**\n * The idempotency gate every trigger funnels through.\n *\n * FIX: checked and set before anything else runs, so a second trigger -\n * whether that is `finalize()` called twice, a signal that arrives while\n * the first finalize is still on the stack, or a signal delivered after a\n * normal finalize already ran - observes `finalized === true` and returns\n * immediately. Because every step from here down is synchronous, there is\n * no `await` between the check and the set for a second trigger to race.\n */\n private triggerFinalize(trigger: TerminalTrigger | undefined): void {\n if (this.finalized) return;\n this.finalized = true;\n // Removed before persisting, not after: if a fatal signal arrives while\n // `mutateTerminalStatus` itself is running, it must not re-enter this\n // method through a reinstalled handler while the first pass is already\n // committed to exiting.\n this.dispose();\n try {\n const mutate = this.mutateTerminalStatus;\n // Excluded from coverage: unreachable by construction, for the same\n // reason as the runId guard below - mutateTerminalStatus is always set\n // once begin() has run, which finalized guards.\n /* v8 ignore next */\n if (mutate) this.statusWriter.updateSync((status) => mutate(status, trigger));\n // Only cleared once a result has actually been persisted. If the\n // mutator throws, the marker is left in place - that is the accurate\n // state: the run started and never produced a result, which is exactly\n // what the marker exists to record.\n this.clearCrashMarker();\n } finally {\n // Killing tracked children is unconditional: an orphaned child must not\n // survive a failed persist any more than it survives a successful one.\n this.killTrackedChildren();\n }\n }\n\n private killTrackedChildren(): void {\n for (const pid of this.trackedChildPids) this.killProcess(pid, 'SIGKILL');\n this.trackedChildPids.clear();\n }\n\n private writeCrashMarker(runId: string): void {\n const marker: CrashMarkerRecord = {\n version: CRASH_MARKER_VERSION,\n runId,\n pid: process.pid,\n startedAt: this.now(),\n };\n writeAtomicJson(crashMarkerPath(runId), marker);\n }\n\n private clearCrashMarker(): void {\n // Excluded from coverage: unreachable by construction. triggerFinalize\n // only reaches here once `finalized` has been observed false, which only\n // happens after begin() has already set runId - kept as a defensive\n // check instead of a non-null assertion, not as a reachable branch.\n /* v8 ignore next */\n if (!this.runId) return;\n try {\n fs.rmSync(crashMarkerPath(this.runId), { force: true });\n } catch {\n // Best effort: a marker that resists removal still correctly reads as\n // \"this run may have crashed\" to a later reader, which is the safe\n // direction for this cleanup to fail in.\n }\n }\n}\n"],"mappings":"ooBAqEA,MAAa,EAAyB,2BAmBtC,SAAS,EAAgB,EAAuB,CAC9C,OAAOA,EAAK,KAAKC,EAAAA,gBAAgB,CAAE,EAAO,EAAuB,CAInE,SAAS,EAAe,EAAgC,CACtD,MAAO,KAAMC,EAAG,UAAU,QAAQ,GA6C7B,IAAA,EAAA,KAE2C,CAChD,YACE,EACA,CADgE,KAAA,aAAA,EAGlE,MACA,qBAMA,UAAoB,GACpB,kBAA4B,GAC5B,iBAAoC,IAAI,IAIxC,kBAA6C,KAAK,aAAa,UAAU,CACzE,iBAA4C,KAAK,aAAa,SAAS,CACvE,iBAA4C,KAAK,aAAa,SAAS,CACvE,wBAA4C,GAC1C,KAAK,YAAY,CAAE,KAAM,oBAAqB,QAAO,CAAC,CACxD,yBAA6C,GAC3C,KAAK,YAAY,CAAE,KAAM,qBAAsB,SAAQ,CAAC,CAE1D,KAAwB,CACtB,OAAO,KAAK,KAAK,CASnB,SAAmB,EAAuB,EAAuC,CAC/E,QAAQ,GAAG,EAAO,EAAQ,CAG5B,UAAoB,EAAuB,EAAuC,CAChF,QAAQ,IAAI,EAAO,EAAQ,CAG7B,oBAA8B,EAA+E,CAC3G,QAAQ,GAAG,oBAAqB,EAAQ,CAG1C,qBAA+B,EAA+E,CAC5G,QAAQ,IAAI,oBAAqB,EAAQ,CAG3C,qBAA+B,EAAqE,CAClG,QAAQ,GAAG,qBAAsB,EAAQ,CAG3C,sBAAgC,EAAqE,CACnG,QAAQ,IAAI,qBAAsB,EAAQ,CAG5C,YAAsB,EAAa,EAA8B,CAC/D,GAAI,CACF,QAAQ,KAAK,EAAK,EAAO,MACnB,GAMV,YAAsB,EAAoB,CAKxC,QAAQ,KAAK,EAAK,CAOpB,MAAM,EAAe,EAA6F,CAGhH,KAAK,SAAS,CACd,KAAK,MAAQ,EACb,KAAK,qBAAuB,EAC5B,KAAK,UAAY,GACjB,KAAK,iBAAiB,OAAO,CAC7B,KAAK,iBAAiB,EAAM,CAC5B,KAAK,iBAAiB,CAGxB,WAAW,EAAmB,CAC5B,KAAK,iBAAiB,IAAI,EAAI,CAGhC,aAAa,EAAmB,CAC9B,KAAK,iBAAiB,OAAO,EAAI,CAGnC,UAAiB,CACf,KAAK,gBAAgB,IAAA,GAAU,CAGjC,SAAgB,CACT,KAAK,oBACV,KAAK,kBAAoB,GACzB,KAAK,UAAU,UAAW,KAAK,cAAc,CAC7C,KAAK,UAAU,SAAU,KAAK,aAAa,CAC3C,KAAK,UAAU,SAAU,KAAK,aAAa,CAC3C,KAAK,qBAAqB,KAAK,wBAAwB,CACvD,KAAK,sBAAsB,KAAK,yBAAyB,EAO3D,iBAAgC,CAC1B,KAAK,oBACT,KAAK,kBAAoB,GACzB,KAAK,SAAS,UAAW,KAAK,cAAc,CAC5C,KAAK,SAAS,SAAU,KAAK,aAAa,CAC1C,KAAK,SAAS,SAAU,KAAK,aAAa,CAC1C,KAAK,oBAAoB,KAAK,wBAAwB,CACtD,KAAK,qBAAqB,KAAK,yBAAyB,EAG1D,aAAqB,EAA8B,CACjD,KAAK,gBAAgB,CAAE,KAAM,SAAU,SAAQ,CAAC,CAMhD,KAAK,YAAY,EAAe,EAAO,CAAC,CAG1C,YAAoB,EAAgC,CAClD,KAAK,gBAAgB,EAAQ,CAC7B,KAAK,YAAY,EAA0B,CAa7C,gBAAwB,EAA4C,CAC9D,SAAK,UAMT,CALA,KAAK,UAAY,GAKjB,KAAK,SAAS,CACd,GAAI,CACF,IAAM,EAAS,KAAK,qBAKhB,GAAQ,KAAK,aAAa,WAAY,GAAW,EAAO,EAAQ,EAAQ,CAAC,CAK7E,KAAK,kBAAkB,QACf,CAGR,KAAK,qBAAqB,GAI9B,qBAAoC,CAClC,IAAK,IAAM,KAAO,KAAK,iBAAkB,KAAK,YAAY,EAAK,UAAU,CACzE,KAAK,iBAAiB,OAAO,CAG/B,iBAAyB,EAAqB,CAC5C,IAAM,EAA4B,CAChC,QAAS,EACT,QACA,IAAK,QAAQ,IACb,UAAW,KAAK,KAAK,CACtB,CACD,EAAA,gBAAgB,EAAgB,EAAM,CAAE,EAAO,CAGjD,kBAAiC,CAM1B,QAAK,MACV,GAAI,CACF,EAAG,OAAO,EAAgB,KAAK,MAAM,CAAE,CAAE,MAAO,GAAM,CAAC,MACjD,uCAhNC,kCAKDC,EAAAA,gBAAgB,sBAAsB,CAAA"}
@@ -0,0 +1,2 @@
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{DOOM_TEAM_TYPES as r,init_types as i}from"../../../container/types.mjs";import{writeAtomicJson as a}from"../../../adapters/atomicJson.mjs";import{init_atomicJson as o}from"../../../shared/atomicJson.mjs";import{__decorateMetadata as s,init_decorateMetadata as c}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as l,init_decorateParam as u}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{currentRunsDir as d,init_paths as f}from"../../../shared/paths.mjs";import*as p from"node:fs";import*as m from"node:path";import{inject as h,injectable as g}from"inversify";import*as _ from"node:os";function v(e){return m.join(d(),e,b)}function y(e){return 128+_.constants.signals[e]}var b,x,S,C,w=e((()=>{i(),o(),f(),c(),u(),n(),b=`runner-crash-marker.json`,x=1,S=1,C=class{constructor(e){this.statusWriter=e}runId;mutateTerminalStatus;finalized=!0;handlersInstalled=!1;trackedChildPids=new Set;handleSigterm=()=>this.handleSignal(`SIGTERM`);handleSigint=()=>this.handleSignal(`SIGINT`);handleSighup=()=>this.handleSignal(`SIGHUP`);handleUncaughtException=e=>this.handleFatal({kind:`uncaughtException`,error:e});handleUnhandledRejection=e=>this.handleFatal({kind:`unhandledRejection`,reason:e});now(){return Date.now()}onSignal(e,t){process.on(e,t)}offSignal(e,t){process.off(e,t)}onUncaughtException(e){process.on(`uncaughtException`,e)}offUncaughtException(e){process.off(`uncaughtException`,e)}onUnhandledRejection(e){process.on(`unhandledRejection`,e)}offUnhandledRejection(e){process.off(`unhandledRejection`,e)}killProcess(e,t){try{process.kill(e,t)}catch{}}exitProcess(e){process.exit(e)}begin(e,t){this.dispose(),this.runId=e,this.mutateTerminalStatus=t,this.finalized=!1,this.trackedChildPids.clear(),this.writeCrashMarker(e),this.installHandlers()}trackChild(e){this.trackedChildPids.add(e)}untrackChild(e){this.trackedChildPids.delete(e)}finalize(){this.triggerFinalize(void 0)}dispose(){this.handlersInstalled&&(this.handlersInstalled=!1,this.offSignal(`SIGTERM`,this.handleSigterm),this.offSignal(`SIGINT`,this.handleSigint),this.offSignal(`SIGHUP`,this.handleSighup),this.offUncaughtException(this.handleUncaughtException),this.offUnhandledRejection(this.handleUnhandledRejection))}installHandlers(){this.handlersInstalled||(this.handlersInstalled=!0,this.onSignal(`SIGTERM`,this.handleSigterm),this.onSignal(`SIGINT`,this.handleSigint),this.onSignal(`SIGHUP`,this.handleSighup),this.onUncaughtException(this.handleUncaughtException),this.onUnhandledRejection(this.handleUnhandledRejection))}handleSignal(e){this.triggerFinalize({kind:`signal`,signal:e}),this.exitProcess(y(e))}handleFatal(e){this.triggerFinalize(e),this.exitProcess(S)}triggerFinalize(e){if(!this.finalized){this.finalized=!0,this.dispose();try{let t=this.mutateTerminalStatus;t&&this.statusWriter.updateSync(n=>t(n,e)),this.clearCrashMarker()}finally{this.killTrackedChildren()}}}killTrackedChildren(){for(let e of this.trackedChildPids)this.killProcess(e,`SIGKILL`);this.trackedChildPids.clear()}writeCrashMarker(e){let t={version:x,runId:e,pid:process.pid,startedAt:this.now()};a(v(e),t)}clearCrashMarker(){if(this.runId)try{p.rmSync(v(this.runId),{force:!0})}catch{}}},C=t([g(),l(0,h(r.CoalescedStatusWriter)),s(`design:paramtypes`,[Object])],C)}));w();export{b as CRASH_MARKER_FILE_NAME,C as TerminalPersistenceService,w as init_terminalPersistence};
2
+ //# sourceMappingURL=terminalPersistence.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminalPersistence.mjs","names":[],"sources":["../../../../src/services/runs/background/terminalPersistence.ts"],"sourcesContent":["/**\n * Guarantees one runner process ends with a persisted terminal result.\n *\n * WHY THIS EXISTS:\n * The predecessor's runner finalization\n * (`doom-pi-subagents/src/runs/background/subagent-runner.ts`) is straight-line\n * code with no `try`/`finally` and no signal handling beyond `SIGUSR2`. A throw\n * anywhere in that path, or a `SIGTERM`/`SIGKILL` of the runner process, leaves\n * the run's `status.json` reporting `running` for up to 24 hours, with the\n * `pi` child it spawned still alive and still burning tokens. A user who hits\n * Ctrl-C on a foreground run gets exactly this: the terminal returns, but the\n * child and the stale \"running\" status both live on.\n *\n * DESIGN PATTERNS:\n * - One runner process runs exactly one run, so this is a singleton holding the\n * state of the one run it is guarding, the same shape as `CoalescedStatusWriter`\n * - `ICoalescedStatusWriter` is injected, not assumed: this class holds a real\n * reference to the one true `status.json` writer and calls its synchronous\n * `updateSync` itself, rather than trusting a caller-supplied closure to have\n * done so. `mutateTerminalStatus` stays a caller-supplied closure for the\n * run's own richer status SHAPE only - this class does not know what fields\n * a terminal status carries, just that writing them goes through the one\n * writer, by construction rather than by docstring\n * - Idempotency is a single boolean guard checked and set before anything else\n * happens, so a second trigger - a duplicate signal, a throw during the first\n * finalize - observes \"already finalized\" before it can do anything\n * - Signal and process-exit machinery are `protected` seams, not constructor\n * arguments: inversify cannot inject a primitive, and a unit test must never\n * deliver a real signal to, or call the real `process.exit` on, the process\n * running the test\n *\n * THE CRASH MARKER:\n * `begin()` writes a small marker file before the run does anything else, and\n * a successful `finalize()` deletes it. A run that dies so hard it never reaches\n * `finalize()` at all (`SIGKILL`, a host crash) leaves the marker behind with no\n * result file next to it - the marker's `pid` is then dead, which is what lets a\n * later reader tell \"crashed\" apart from \"still running\": a genuinely running\n * run also has a live marker, but with a live pid. This module only writes and\n * clears the marker; reading it to make that determination is a different\n * module's job.\n *\n * AVOID:\n * - Calling `mutateTerminalStatus` with anything that can be asynchronous. The\n * whole guarantee here depends on finalize running to completion before\n * another trigger can observe the `finalized` flag, which only holds if\n * every step, including `CoalescedStatusWriter.updateSync`, is synchronous\n * - Writing `status.json` from here directly with `writeAtomicJson`; the\n * injected `ICoalescedStatusWriter` is the only thing that may touch it\n * - Calling `process.removeAllListeners` for a signal; that would remove a\n * listener some other module in the same process installed, not just ours\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport { writeAtomicJson } from '../../../shared/atomicJson.ts';\nimport { currentRunsDir } from '../../../shared/paths.ts';\nimport type { ICoalescedStatusWriter, StatusWithRecentEntries } from './statusWriter.ts';\n\n/**\n * The one crash-marker filename.\n *\n * Exported because `processTerminal.ts` reads this marker to decide whether a\n * run died without finishing. It writes the marker, that module reads it, and\n * they must not drift.\n */\nexport const CRASH_MARKER_FILE_NAME = 'runner-crash-marker.json';\nconst CRASH_MARKER_VERSION = 1;\n\n/** Exit code used for a fatal exception, matching Node's own default for an uncaught error. */\nconst FATAL_EXCEPTION_EXIT_CODE = 1;\n\n/** What triggered a terminal finalize, so the caller's `mutateTerminalStatus` closure can record why. */\nexport type TerminalTrigger =\n | { kind: 'signal'; signal: NodeJS.Signals }\n | { kind: 'uncaughtException'; error: Error }\n | { kind: 'unhandledRejection'; reason: unknown };\n\ninterface CrashMarkerRecord {\n version: typeof CRASH_MARKER_VERSION;\n runId: string;\n pid: number;\n startedAt: number;\n}\n\nfunction crashMarkerPath(runId: string): string {\n return path.join(currentRunsDir(), runId, CRASH_MARKER_FILE_NAME);\n}\n\n/** `128 + signal number`, the shell convention for a process killed by a signal. */\nfunction signalExitCode(signal: NodeJS.Signals): number {\n return 128 + os.constants.signals[signal];\n}\n\nexport interface ITerminalPersistenceService<TStatus extends StatusWithRecentEntries = StatusWithRecentEntries> {\n /**\n * Start guarding one run: writes the crash marker and installs the signal\n * and exception handlers. Safe to call again, on the same or a different\n * run id - a second call tears down whatever this instance was previously\n * guarding first, the same reset semantics as `CoalescedStatusWriter.open`.\n *\n * `mutateTerminalStatus` is called at most once, synchronously, on the\n * already-open status passed to `CoalescedStatusWriter.updateSync`, whenever\n * the run reaches a terminal state: `trigger` is undefined for the run's own\n * call to `finalize()`, and describes the signal or exception for every\n * other path. The caller is responsible for having called\n * `ICoalescedStatusWriter.open()` before the run's terminal state can be\n * reached; this class only mutates and flushes, it does not open.\n */\n begin(runId: string, mutateTerminalStatus: (status: TStatus, trigger: TerminalTrigger | undefined) => void): void;\n /** Track a spawned `pi` child so it is killed when this run finalizes. */\n trackChild(pid: number): void;\n /** Stop tracking a child that already exited on its own. */\n untrackChild(pid: number): void;\n /**\n * The run's own terminal call: persists the result through the injected\n * status writer's synchronous flush path, kills every tracked child, and\n * clears the crash marker. Idempotent - a second call, from any source,\n * including a signal that arrives after this one returns, is a no-op.\n */\n finalize(): void;\n /** Remove the installed signal and exception handlers without finalizing. */\n dispose(): void;\n}\n\n/**\n * Owner of the one run a runner process guards.\n *\n * WHY THIS IS A SERVICE AND NOT A TOP-LEVEL FUNCTION:\n * The predecessor's finalization lived inline in the runner's main function, so\n * nothing else in that module could trigger it, and a signal handler installed\n * elsewhere had no way to reach it. Putting it on an injectable instance gives\n * every part of the runner - the main run loop, a spawn-site failure, a signal\n * handler - the same `finalize()` to call, all guarded by the same flag.\n */\n@injectable()\nexport class TerminalPersistenceService<\n TStatus extends StatusWithRecentEntries = StatusWithRecentEntries,\n> implements ITerminalPersistenceService<TStatus> {\n constructor(\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter) private readonly statusWriter: ICoalescedStatusWriter<TStatus>,\n ) {}\n\n private runId: string | undefined;\n private mutateTerminalStatus: ((status: TStatus, trigger: TerminalTrigger | undefined) => void) | undefined;\n /**\n * Starts `true` so that a call to `finalize()` or a delivered signal before\n * `begin()` has ever run is a safe no-op rather than a crash on undefined\n * state.\n */\n private finalized = true;\n private handlersInstalled = false;\n private readonly trackedChildPids = new Set<number>();\n\n // Bound once per instance so `offSignal`/`offUncaughtException` remove the\n // exact function reference `onSignal`/`onUncaughtException` installed.\n private readonly handleSigterm = (): void => this.handleSignal('SIGTERM');\n private readonly handleSigint = (): void => this.handleSignal('SIGINT');\n private readonly handleSighup = (): void => this.handleSignal('SIGHUP');\n private readonly handleUncaughtException = (error: Error): void =>\n this.handleFatal({ kind: 'uncaughtException', error });\n private readonly handleUnhandledRejection = (reason: unknown): void =>\n this.handleFatal({ kind: 'unhandledRejection', reason });\n\n protected now(): number {\n return Date.now();\n }\n\n // ---------------------------------------------------------------------\n // Process seams. A test subclass overrides every one of these so a unit\n // test never registers a real signal handler, never signals a real pid,\n // and never calls the real `process.exit` on the process running the test.\n // ---------------------------------------------------------------------\n\n protected onSignal(event: NodeJS.Signals, handler: NodeJS.SignalsListener): void {\n process.on(event, handler);\n }\n\n protected offSignal(event: NodeJS.Signals, handler: NodeJS.SignalsListener): void {\n process.off(event, handler);\n }\n\n protected onUncaughtException(handler: (error: Error, origin: NodeJS.UncaughtExceptionOrigin) => void): void {\n process.on('uncaughtException', handler);\n }\n\n protected offUncaughtException(handler: (error: Error, origin: NodeJS.UncaughtExceptionOrigin) => void): void {\n process.off('uncaughtException', handler);\n }\n\n protected onUnhandledRejection(handler: (reason: unknown, promise: Promise<unknown>) => void): void {\n process.on('unhandledRejection', handler);\n }\n\n protected offUnhandledRejection(handler: (reason: unknown, promise: Promise<unknown>) => void): void {\n process.off('unhandledRejection', handler);\n }\n\n protected killProcess(pid: number, signal: NodeJS.Signals): void {\n try {\n process.kill(pid, signal);\n } catch {\n // Already exited, or never existed. Nothing left to kill, which is the\n // outcome we wanted anyway.\n }\n }\n\n protected exitProcess(code: number): void {\n // Excluded from coverage: calling the real implementation would terminate\n // the process running whichever suite exercises this class, so every\n // test overrides this seam instead of triggering it for real.\n /* v8 ignore next */\n process.exit(code);\n }\n\n // ---------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------\n\n begin(runId: string, mutateTerminalStatus: (status: TStatus, trigger: TerminalTrigger | undefined) => void): void {\n // A second begin() is a reset: tear down whatever this instance was\n // previously guarding before starting the new one.\n this.dispose();\n this.runId = runId;\n this.mutateTerminalStatus = mutateTerminalStatus;\n this.finalized = false;\n this.trackedChildPids.clear();\n this.writeCrashMarker(runId);\n this.installHandlers();\n }\n\n trackChild(pid: number): void {\n this.trackedChildPids.add(pid);\n }\n\n untrackChild(pid: number): void {\n this.trackedChildPids.delete(pid);\n }\n\n finalize(): void {\n this.triggerFinalize(undefined);\n }\n\n dispose(): void {\n if (!this.handlersInstalled) return;\n this.handlersInstalled = false;\n this.offSignal('SIGTERM', this.handleSigterm);\n this.offSignal('SIGINT', this.handleSigint);\n this.offSignal('SIGHUP', this.handleSighup);\n this.offUncaughtException(this.handleUncaughtException);\n this.offUnhandledRejection(this.handleUnhandledRejection);\n }\n\n // ---------------------------------------------------------------------\n // Internals\n // ---------------------------------------------------------------------\n\n private installHandlers(): void {\n if (this.handlersInstalled) return;\n this.handlersInstalled = true;\n this.onSignal('SIGTERM', this.handleSigterm);\n this.onSignal('SIGINT', this.handleSigint);\n this.onSignal('SIGHUP', this.handleSighup);\n this.onUncaughtException(this.handleUncaughtException);\n this.onUnhandledRejection(this.handleUnhandledRejection);\n }\n\n private handleSignal(signal: NodeJS.Signals): void {\n this.triggerFinalize({ kind: 'signal', signal });\n // Registering a listener for these signals suppresses Node's default\n // terminate-on-signal behaviour, so once we have finalized we must end\n // the process ourselves - otherwise the signal is effectively swallowed\n // and the process hangs instead of honouring the Ctrl-C or `kill` that\n // was sent to it.\n this.exitProcess(signalExitCode(signal));\n }\n\n private handleFatal(trigger: TerminalTrigger): void {\n this.triggerFinalize(trigger);\n this.exitProcess(FATAL_EXCEPTION_EXIT_CODE);\n }\n\n /**\n * The idempotency gate every trigger funnels through.\n *\n * FIX: checked and set before anything else runs, so a second trigger -\n * whether that is `finalize()` called twice, a signal that arrives while\n * the first finalize is still on the stack, or a signal delivered after a\n * normal finalize already ran - observes `finalized === true` and returns\n * immediately. Because every step from here down is synchronous, there is\n * no `await` between the check and the set for a second trigger to race.\n */\n private triggerFinalize(trigger: TerminalTrigger | undefined): void {\n if (this.finalized) return;\n this.finalized = true;\n // Removed before persisting, not after: if a fatal signal arrives while\n // `mutateTerminalStatus` itself is running, it must not re-enter this\n // method through a reinstalled handler while the first pass is already\n // committed to exiting.\n this.dispose();\n try {\n const mutate = this.mutateTerminalStatus;\n // Excluded from coverage: unreachable by construction, for the same\n // reason as the runId guard below - mutateTerminalStatus is always set\n // once begin() has run, which finalized guards.\n /* v8 ignore next */\n if (mutate) this.statusWriter.updateSync((status) => mutate(status, trigger));\n // Only cleared once a result has actually been persisted. If the\n // mutator throws, the marker is left in place - that is the accurate\n // state: the run started and never produced a result, which is exactly\n // what the marker exists to record.\n this.clearCrashMarker();\n } finally {\n // Killing tracked children is unconditional: an orphaned child must not\n // survive a failed persist any more than it survives a successful one.\n this.killTrackedChildren();\n }\n }\n\n private killTrackedChildren(): void {\n for (const pid of this.trackedChildPids) this.killProcess(pid, 'SIGKILL');\n this.trackedChildPids.clear();\n }\n\n private writeCrashMarker(runId: string): void {\n const marker: CrashMarkerRecord = {\n version: CRASH_MARKER_VERSION,\n runId,\n pid: process.pid,\n startedAt: this.now(),\n };\n writeAtomicJson(crashMarkerPath(runId), marker);\n }\n\n private clearCrashMarker(): void {\n // Excluded from coverage: unreachable by construction. triggerFinalize\n // only reaches here once `finalized` has been observed false, which only\n // happens after begin() has already set runId - kept as a defensive\n // check instead of a non-null assertion, not as a reachable branch.\n /* v8 ignore next */\n if (!this.runId) return;\n try {\n fs.rmSync(crashMarkerPath(this.runId), { force: true });\n } catch {\n // Best effort: a marker that resists removal still correctly reads as\n // \"this run may have crashed\" to a later reader, which is the safe\n // direction for this cleanup to fail in.\n }\n }\n}\n"],"mappings":"02BAwFA,SAAS,EAAgB,EAAuB,CAC9C,OAAO,EAAK,KAAK,GAAgB,CAAE,EAAO,EAAuB,CAInE,SAAS,EAAe,EAAgC,CACtD,MAAO,KAAM,EAAG,UAAU,QAAQ,4BArC0B,IACE,IACN,aAU7C,EAAyB,2BAChC,EAAuB,EAGvB,EAA4B,IAkE3B,KAE2C,CAChD,YACE,EACA,CADgE,KAAA,aAAA,EAGlE,MACA,qBAMA,UAAoB,GACpB,kBAA4B,GAC5B,iBAAoC,IAAI,IAIxC,kBAA6C,KAAK,aAAa,UAAU,CACzE,iBAA4C,KAAK,aAAa,SAAS,CACvE,iBAA4C,KAAK,aAAa,SAAS,CACvE,wBAA4C,GAC1C,KAAK,YAAY,CAAE,KAAM,oBAAqB,QAAO,CAAC,CACxD,yBAA6C,GAC3C,KAAK,YAAY,CAAE,KAAM,qBAAsB,SAAQ,CAAC,CAE1D,KAAwB,CACtB,OAAO,KAAK,KAAK,CASnB,SAAmB,EAAuB,EAAuC,CAC/E,QAAQ,GAAG,EAAO,EAAQ,CAG5B,UAAoB,EAAuB,EAAuC,CAChF,QAAQ,IAAI,EAAO,EAAQ,CAG7B,oBAA8B,EAA+E,CAC3G,QAAQ,GAAG,oBAAqB,EAAQ,CAG1C,qBAA+B,EAA+E,CAC5G,QAAQ,IAAI,oBAAqB,EAAQ,CAG3C,qBAA+B,EAAqE,CAClG,QAAQ,GAAG,qBAAsB,EAAQ,CAG3C,sBAAgC,EAAqE,CACnG,QAAQ,IAAI,qBAAsB,EAAQ,CAG5C,YAAsB,EAAa,EAA8B,CAC/D,GAAI,CACF,QAAQ,KAAK,EAAK,EAAO,MACnB,GAMV,YAAsB,EAAoB,CAKxC,QAAQ,KAAK,EAAK,CAOpB,MAAM,EAAe,EAA6F,CAGhH,KAAK,SAAS,CACd,KAAK,MAAQ,EACb,KAAK,qBAAuB,EAC5B,KAAK,UAAY,GACjB,KAAK,iBAAiB,OAAO,CAC7B,KAAK,iBAAiB,EAAM,CAC5B,KAAK,iBAAiB,CAGxB,WAAW,EAAmB,CAC5B,KAAK,iBAAiB,IAAI,EAAI,CAGhC,aAAa,EAAmB,CAC9B,KAAK,iBAAiB,OAAO,EAAI,CAGnC,UAAiB,CACf,KAAK,gBAAgB,IAAA,GAAU,CAGjC,SAAgB,CACT,KAAK,oBACV,KAAK,kBAAoB,GACzB,KAAK,UAAU,UAAW,KAAK,cAAc,CAC7C,KAAK,UAAU,SAAU,KAAK,aAAa,CAC3C,KAAK,UAAU,SAAU,KAAK,aAAa,CAC3C,KAAK,qBAAqB,KAAK,wBAAwB,CACvD,KAAK,sBAAsB,KAAK,yBAAyB,EAO3D,iBAAgC,CAC1B,KAAK,oBACT,KAAK,kBAAoB,GACzB,KAAK,SAAS,UAAW,KAAK,cAAc,CAC5C,KAAK,SAAS,SAAU,KAAK,aAAa,CAC1C,KAAK,SAAS,SAAU,KAAK,aAAa,CAC1C,KAAK,oBAAoB,KAAK,wBAAwB,CACtD,KAAK,qBAAqB,KAAK,yBAAyB,EAG1D,aAAqB,EAA8B,CACjD,KAAK,gBAAgB,CAAE,KAAM,SAAU,SAAQ,CAAC,CAMhD,KAAK,YAAY,EAAe,EAAO,CAAC,CAG1C,YAAoB,EAAgC,CAClD,KAAK,gBAAgB,EAAQ,CAC7B,KAAK,YAAY,EAA0B,CAa7C,gBAAwB,EAA4C,CAC9D,SAAK,UAMT,CALA,KAAK,UAAY,GAKjB,KAAK,SAAS,CACd,GAAI,CACF,IAAM,EAAS,KAAK,qBAKhB,GAAQ,KAAK,aAAa,WAAY,GAAW,EAAO,EAAQ,EAAQ,CAAC,CAK7E,KAAK,kBAAkB,QACf,CAGR,KAAK,qBAAqB,GAI9B,qBAAoC,CAClC,IAAK,IAAM,KAAO,KAAK,iBAAkB,KAAK,YAAY,EAAK,UAAU,CACzE,KAAK,iBAAiB,OAAO,CAG/B,iBAAyB,EAAqB,CAC5C,IAAM,EAA4B,CAChC,QAAS,EACT,QACA,IAAK,QAAQ,IACb,UAAW,KAAK,KAAK,CACtB,CACD,EAAgB,EAAgB,EAAM,CAAE,EAAO,CAGjD,kBAAiC,CAM1B,QAAK,MACV,GAAI,CACF,EAAG,OAAO,EAAgB,KAAK,MAAM,CAAE,CAAE,MAAO,GAAM,CAAC,MACjD,UAhNX,GAAY,KAKR,EAAO,EAAgB,sBAAsB,CAAA"}
@@ -0,0 +1,2 @@
1
+ function e(e){return e.split(/[\\/]/u).at(-1)??e}function t(e){let t=Error(JSON.stringify({thrownValue:e.error,extensionPath:e.extensionPath,hookOperation:e.event}));return t.name=`ExtensionHookError`,e.stack&&(t.stack=e.stack),t}function n(n,r){if(!n)return;let i=t(r);n.recordError(`doom_team.extension_error`,i,{"extension.name":e(r.extensionPath),"extension.operation":r.event,"failure.kind":`extension`,"failure.scope":`non_blocking`,runtime:`sdk`},{includeException:!0}).catch(()=>void 0)}exports.recordNonBlockingExtensionError=n;
2
+ //# sourceMappingURL=extensionErrorTelemetry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensionErrorTelemetry.cjs","names":[],"sources":["../../../src/services/runs/extensionErrorTelemetry.ts"],"sourcesContent":["import type { DoomTelemetry } from '@agimon-ai/doompi-telemetry';\n\nexport const EXTENSION_ERROR_EVENT = 'doom_team.extension_error';\n\nexport interface ExtensionHookFailure {\n extensionPath: string;\n event: string;\n error: string;\n stack?: string;\n}\n\nfunction extensionName(extensionPath: string): string {\n return extensionPath.split(/[\\\\/]/u).at(-1) ?? extensionPath;\n}\n\nexport function createExtensionHookError(failure: ExtensionHookFailure): Error {\n const error = new Error(\n JSON.stringify({\n thrownValue: failure.error,\n extensionPath: failure.extensionPath,\n hookOperation: failure.event,\n }),\n );\n error.name = 'ExtensionHookError';\n if (failure.stack) error.stack = failure.stack;\n return error;\n}\n\nexport function recordNonBlockingExtensionError(\n telemetry: Pick<DoomTelemetry, 'recordError'> | undefined,\n failure: ExtensionHookFailure,\n): void {\n if (!telemetry) return;\n\n const error = createExtensionHookError(failure);\n void telemetry\n .recordError(\n EXTENSION_ERROR_EVENT,\n error,\n {\n 'extension.name': extensionName(failure.extensionPath),\n 'extension.operation': failure.event,\n 'failure.kind': 'extension',\n 'failure.scope': 'non_blocking',\n runtime: 'sdk',\n },\n { includeException: true },\n )\n .catch(() => undefined);\n}\n"],"mappings":"AAWA,SAAS,EAAc,EAA+B,CACpD,OAAO,EAAc,MAAM,SAAS,CAAC,GAAG,GAAG,EAAI,EAGjD,SAAgB,EAAyB,EAAsC,CAC7E,IAAM,EAAY,MAChB,KAAK,UAAU,CACb,YAAa,EAAQ,MACrB,cAAe,EAAQ,cACvB,cAAe,EAAQ,MACxB,CAAC,CACH,CAGD,MAFA,GAAM,KAAO,qBACT,EAAQ,QAAO,EAAM,MAAQ,EAAQ,OAClC,EAGT,SAAgB,EACd,EACA,EACM,CACN,GAAI,CAAC,EAAW,OAEhB,IAAM,EAAQ,EAAyB,EAAQ,CAC1C,EACF,YACC,4BACA,EACA,CACE,iBAAkB,EAAc,EAAQ,cAAc,CACtD,sBAAuB,EAAQ,MAC/B,eAAgB,YAChB,gBAAiB,eACjB,QAAS,MACV,CACD,CAAE,iBAAkB,GAAM,CAC3B,CACA,UAAY,IAAA,GAAU"}
@@ -0,0 +1,2 @@
1
+ function e(e){return e.split(/[\\/]/u).at(-1)??e}function t(e){let t=Error(JSON.stringify({thrownValue:e.error,extensionPath:e.extensionPath,hookOperation:e.event}));return t.name=`ExtensionHookError`,e.stack&&(t.stack=e.stack),t}function n(n,r){if(!n)return;let i=t(r);n.recordError(`doom_team.extension_error`,i,{"extension.name":e(r.extensionPath),"extension.operation":r.event,"failure.kind":`extension`,"failure.scope":`non_blocking`,runtime:`sdk`},{includeException:!0}).catch(()=>void 0)}export{n as recordNonBlockingExtensionError};
2
+ //# sourceMappingURL=extensionErrorTelemetry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensionErrorTelemetry.mjs","names":[],"sources":["../../../src/services/runs/extensionErrorTelemetry.ts"],"sourcesContent":["import type { DoomTelemetry } from '@agimon-ai/doompi-telemetry';\n\nexport const EXTENSION_ERROR_EVENT = 'doom_team.extension_error';\n\nexport interface ExtensionHookFailure {\n extensionPath: string;\n event: string;\n error: string;\n stack?: string;\n}\n\nfunction extensionName(extensionPath: string): string {\n return extensionPath.split(/[\\\\/]/u).at(-1) ?? extensionPath;\n}\n\nexport function createExtensionHookError(failure: ExtensionHookFailure): Error {\n const error = new Error(\n JSON.stringify({\n thrownValue: failure.error,\n extensionPath: failure.extensionPath,\n hookOperation: failure.event,\n }),\n );\n error.name = 'ExtensionHookError';\n if (failure.stack) error.stack = failure.stack;\n return error;\n}\n\nexport function recordNonBlockingExtensionError(\n telemetry: Pick<DoomTelemetry, 'recordError'> | undefined,\n failure: ExtensionHookFailure,\n): void {\n if (!telemetry) return;\n\n const error = createExtensionHookError(failure);\n void telemetry\n .recordError(\n EXTENSION_ERROR_EVENT,\n error,\n {\n 'extension.name': extensionName(failure.extensionPath),\n 'extension.operation': failure.event,\n 'failure.kind': 'extension',\n 'failure.scope': 'non_blocking',\n runtime: 'sdk',\n },\n { includeException: true },\n )\n .catch(() => undefined);\n}\n"],"mappings":"AAWA,SAAS,EAAc,EAA+B,CACpD,OAAO,EAAc,MAAM,SAAS,CAAC,GAAG,GAAG,EAAI,EAGjD,SAAgB,EAAyB,EAAsC,CAC7E,IAAM,EAAY,MAChB,KAAK,UAAU,CACb,YAAa,EAAQ,MACrB,cAAe,EAAQ,cACvB,cAAe,EAAQ,MACxB,CAAC,CACH,CAGD,MAFA,GAAM,KAAO,qBACT,EAAQ,QAAO,EAAM,MAAQ,EAAQ,OAClC,EAGT,SAAgB,EACd,EACA,EACM,CACN,GAAI,CAAC,EAAW,OAEhB,IAAM,EAAQ,EAAyB,EAAQ,CAC1C,EACF,YACC,4BACA,EACA,CACE,iBAAkB,EAAc,EAAQ,cAAc,CACtD,sBAAuB,EAAQ,MAC/B,eAAgB,YAChB,gBAAiB,eACjB,QAAS,MACV,CACD,CAAE,iBAAkB,GAAM,CAC3B,CACA,UAAY,IAAA,GAAU"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processGroup.cjs","names":[],"sources":["../../../../src/services/runs/registry/processGroup.ts"],"sourcesContent":["/**\n * Stopping a run's whole process tree.\n *\n * Every child is spawned `detached: true`, which puts it in its own process\n * group, which is what makes `kill(-pid)` reach the agent AND whatever it\n * spawned. Signalling the pid alone would leave a CLI's own subprocesses\n * running after their parent died.\n *\n * WHY SIGTERM THEN SIGKILL, RATHER THAN EITHER ALONE:\n * SIGTERM lets a runner finalize - persist its terminal status, release its\n * team membership, kill its own children. SIGKILL cannot be caught, so a runner\n * that is wedged still goes away. Doing only the first leaves a hung child\n * running; doing only the second guarantees a stale `running` status on disk.\n *\n * `doom-runner` does the same thing at `services/Launcher/Launcher.ts`, and Pi\n * itself at `utils/shell.ts`'s `killProcessTree`. Neither is importable from\n * here - Pi exports only `getShellConfig` from that module - so this is a third\n * copy of a nine-line idea rather than a shared dependency.\n *\n * AVOID:\n * - Signalling `pid` instead of `-pid`; that misses the group\n */\n\nconst DEFAULT_GRACE_MS = 3_000;\n\nexport interface ProcessGroupSignals {\n /** Send `signal` to the group led by `pid`. Returns false when it is already gone. */\n signalGroup(pid: number, signal: NodeJS.Signals): boolean;\n isAlive(pid: number): boolean;\n wait(ms: number): Promise<void>;\n}\n\nexport const nodeProcessGroupSignals: ProcessGroupSignals = {\n signalGroup(pid, signal) {\n try {\n process.kill(-pid, signal);\n return true;\n } catch {\n // ESRCH: already gone, which is the outcome we wanted. EPERM: not ours to\n // signal, and retrying with SIGKILL will not change that.\n return false;\n }\n },\n isAlive(pid) {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n },\n wait(ms) {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n },\n};\n\n/**\n * Stop one run's process group, escalating if it outlasts the grace period.\n *\n * Resolves `true` once nothing in the group is alive, `false` if something\n * survived even SIGKILL - which should be impossible on POSIX and is reported\n * rather than asserted, because a caller sweeping many runs needs to know which\n * one it could not clear.\n */\nexport async function stopProcessGroup(\n pid: number,\n graceMs: number = DEFAULT_GRACE_MS,\n signals: ProcessGroupSignals = nodeProcessGroupSignals,\n): Promise<boolean> {\n if (!signals.isAlive(pid)) return true;\n signals.signalGroup(pid, 'SIGTERM');\n await signals.wait(graceMs);\n if (!signals.isAlive(pid)) return true;\n signals.signalGroup(pid, 'SIGKILL');\n await signals.wait(graceMs);\n return !signals.isAlive(pid);\n}\n"],"mappings":"AAuBA,MASa,EAA+C,CAC1D,YAAY,EAAK,EAAQ,CACvB,GAAI,CAEF,OADA,QAAQ,KAAK,CAAC,EAAK,EAAO,CACnB,QACD,CAGN,MAAO,KAGX,QAAQ,EAAK,CACX,GAAI,CAEF,OADA,QAAQ,KAAK,EAAK,EAAE,CACb,SACA,EAAO,CACd,OAAQ,EAAgC,OAAS,UAGrD,KAAK,EAAI,CACP,OAAO,IAAI,QAAS,GAAY,CAChB,WAAW,EAAS,EAC7B,CAAC,SAAS,EACf,EAEL,CAUD,eAAsB,EACpB,EACA,EAAkB,IAClB,EAA+B,EACb,CAOlB,MANI,CAAC,EAAQ,QAAQ,EAAI,GACzB,EAAQ,YAAY,EAAK,UAAU,CACnC,MAAM,EAAQ,KAAK,EAAQ,CACvB,CAAC,EAAQ,QAAQ,EAAI,EAAS,IAClC,EAAQ,YAAY,EAAK,UAAU,CACnC,MAAM,EAAQ,KAAK,EAAQ,CACpB,CAAC,EAAQ,QAAQ,EAAI"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processGroup.mjs","names":[],"sources":["../../../../src/services/runs/registry/processGroup.ts"],"sourcesContent":["/**\n * Stopping a run's whole process tree.\n *\n * Every child is spawned `detached: true`, which puts it in its own process\n * group, which is what makes `kill(-pid)` reach the agent AND whatever it\n * spawned. Signalling the pid alone would leave a CLI's own subprocesses\n * running after their parent died.\n *\n * WHY SIGTERM THEN SIGKILL, RATHER THAN EITHER ALONE:\n * SIGTERM lets a runner finalize - persist its terminal status, release its\n * team membership, kill its own children. SIGKILL cannot be caught, so a runner\n * that is wedged still goes away. Doing only the first leaves a hung child\n * running; doing only the second guarantees a stale `running` status on disk.\n *\n * `doom-runner` does the same thing at `services/Launcher/Launcher.ts`, and Pi\n * itself at `utils/shell.ts`'s `killProcessTree`. Neither is importable from\n * here - Pi exports only `getShellConfig` from that module - so this is a third\n * copy of a nine-line idea rather than a shared dependency.\n *\n * AVOID:\n * - Signalling `pid` instead of `-pid`; that misses the group\n */\n\nconst DEFAULT_GRACE_MS = 3_000;\n\nexport interface ProcessGroupSignals {\n /** Send `signal` to the group led by `pid`. Returns false when it is already gone. */\n signalGroup(pid: number, signal: NodeJS.Signals): boolean;\n isAlive(pid: number): boolean;\n wait(ms: number): Promise<void>;\n}\n\nexport const nodeProcessGroupSignals: ProcessGroupSignals = {\n signalGroup(pid, signal) {\n try {\n process.kill(-pid, signal);\n return true;\n } catch {\n // ESRCH: already gone, which is the outcome we wanted. EPERM: not ours to\n // signal, and retrying with SIGKILL will not change that.\n return false;\n }\n },\n isAlive(pid) {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n },\n wait(ms) {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n },\n};\n\n/**\n * Stop one run's process group, escalating if it outlasts the grace period.\n *\n * Resolves `true` once nothing in the group is alive, `false` if something\n * survived even SIGKILL - which should be impossible on POSIX and is reported\n * rather than asserted, because a caller sweeping many runs needs to know which\n * one it could not clear.\n */\nexport async function stopProcessGroup(\n pid: number,\n graceMs: number = DEFAULT_GRACE_MS,\n signals: ProcessGroupSignals = nodeProcessGroupSignals,\n): Promise<boolean> {\n if (!signals.isAlive(pid)) return true;\n signals.signalGroup(pid, 'SIGTERM');\n await signals.wait(graceMs);\n if (!signals.isAlive(pid)) return true;\n signals.signalGroup(pid, 'SIGKILL');\n await signals.wait(graceMs);\n return !signals.isAlive(pid);\n}\n"],"mappings":"AAuBA,MASa,EAA+C,CAC1D,YAAY,EAAK,EAAQ,CACvB,GAAI,CAEF,OADA,QAAQ,KAAK,CAAC,EAAK,EAAO,CACnB,QACD,CAGN,MAAO,KAGX,QAAQ,EAAK,CACX,GAAI,CAEF,OADA,QAAQ,KAAK,EAAK,EAAE,CACb,SACA,EAAO,CACd,OAAQ,EAAgC,OAAS,UAGrD,KAAK,EAAI,CACP,OAAO,IAAI,QAAS,GAAY,CAChB,WAAW,EAAS,EAC7B,CAAC,SAAS,EACf,EAEL,CAUD,eAAsB,EACpB,EACA,EAAkB,IAClB,EAA+B,EACb,CAOlB,MANI,CAAC,EAAQ,QAAQ,EAAI,GACzB,EAAQ,YAAY,EAAK,UAAU,CACnC,MAAM,EAAQ,KAAK,EAAQ,CACvB,CAAC,EAAQ,QAAQ,EAAI,EAAS,IAClC,EAAQ,YAAY,EAAK,UAAU,CACnC,MAAM,EAAQ,KAAK,EAAQ,CACpB,CAAC,EAAQ,QAAQ,EAAI"}