@agimon-ai/doompi-workflow 0.0.1-alpha.76 → 0.0.1-alpha.78

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 (332) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  2. package/dist/api-contracts.cjs +7 -0
  3. package/dist/api-contracts.d.cts +2 -0
  4. package/dist/api-contracts.d.mts +2 -0
  5. package/dist/api-contracts.mjs +2 -0
  6. package/dist/extensions/pi.cjs +63 -0
  7. package/dist/extensions/pi.cjs.map +1 -0
  8. package/dist/extensions/pi.d.cts +6 -0
  9. package/dist/extensions/pi.d.cts.map +1 -0
  10. package/dist/extensions/pi.d.mts +6 -0
  11. package/dist/extensions/pi.d.mts.map +1 -0
  12. package/dist/extensions/pi.mjs +58 -0
  13. package/dist/extensions/pi.mjs.map +1 -0
  14. package/dist/extensions/server.cjs +96 -0
  15. package/dist/extensions/server.cjs.map +1 -0
  16. package/dist/extensions/server.d.cts +5 -0
  17. package/dist/extensions/server.d.cts.map +1 -0
  18. package/dist/extensions/server.d.mts +5 -0
  19. package/dist/extensions/server.d.mts.map +1 -0
  20. package/dist/extensions/server.mjs +91 -0
  21. package/dist/extensions/server.mjs.map +1 -0
  22. package/dist/extensions/web.mjs +3219 -0
  23. package/dist/extensions/web.mjs.map +1 -0
  24. package/dist/index.cjs +4 -0
  25. package/dist/index.d.cts +2 -0
  26. package/dist/index.d.mts +2 -0
  27. package/dist/index.mjs +2 -0
  28. package/dist/src/constants/workflow.cjs +32 -0
  29. package/dist/src/constants/workflow.cjs.map +1 -0
  30. package/dist/src/constants/workflow.mjs +21 -0
  31. package/dist/src/constants/workflow.mjs.map +1 -0
  32. package/dist/src/extensions/(backend)/_lib/index.server.cjs +12 -0
  33. package/dist/src/extensions/(backend)/_lib/index.server.cjs.map +1 -0
  34. package/dist/src/extensions/(backend)/_lib/index.server.mjs +12 -0
  35. package/dist/src/extensions/(backend)/_lib/index.server.mjs.map +1 -0
  36. package/dist/src/extensions/(backend)/api/workflow/route.server.cjs +7 -0
  37. package/dist/src/extensions/(backend)/api/workflow/route.server.cjs.map +1 -0
  38. package/dist/src/extensions/(backend)/api/workflow/route.server.mjs +8 -0
  39. package/dist/src/extensions/(backend)/api/workflow/route.server.mjs.map +1 -0
  40. package/dist/src/extensions/(backend)/channel/workflow-catalog.server.cjs +7 -0
  41. package/dist/src/extensions/(backend)/channel/workflow-catalog.server.cjs.map +1 -0
  42. package/dist/src/extensions/(backend)/channel/workflow-catalog.server.mjs +8 -0
  43. package/dist/src/extensions/(backend)/channel/workflow-catalog.server.mjs.map +1 -0
  44. package/dist/src/extensions/(backend)/channel/workflows.server.cjs +7 -0
  45. package/dist/src/extensions/(backend)/channel/workflows.server.cjs.map +1 -0
  46. package/dist/src/extensions/(backend)/channel/workflows.server.mjs +8 -0
  47. package/dist/src/extensions/(backend)/channel/workflows.server.mjs.map +1 -0
  48. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.cli.cjs +100 -0
  49. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.cli.cjs.map +1 -0
  50. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.cli.mjs +100 -0
  51. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.cli.mjs.map +1 -0
  52. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.server.cjs +11 -0
  53. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.server.cjs.map +1 -0
  54. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.server.mjs +11 -0
  55. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.server.mjs.map +1 -0
  56. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/workflowSkillsResource.cjs +12 -0
  57. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/workflowSkillsResource.cjs.map +1 -0
  58. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/workflowSkillsResource.mjs +12 -0
  59. package/dist/src/extensions/workspaces/sessions/(backend)/_lib/workflowSkillsResource.mjs.map +1 -0
  60. package/dist/src/extensions/workspaces/sessions/(backend)/api/workflow/route.server.cjs +6 -0
  61. package/dist/src/extensions/workspaces/sessions/(backend)/api/workflow/route.server.cjs.map +1 -0
  62. package/dist/src/extensions/workspaces/sessions/(backend)/api/workflow/route.server.mjs +7 -0
  63. package/dist/src/extensions/workspaces/sessions/(backend)/api/workflow/route.server.mjs.map +1 -0
  64. package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.cli.cjs +6 -0
  65. package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.cli.cjs.map +1 -0
  66. package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.cli.mjs +7 -0
  67. package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.cli.mjs.map +1 -0
  68. package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.server.cjs +6 -0
  69. package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.server.cjs.map +1 -0
  70. package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.server.mjs +7 -0
  71. package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.server.mjs.map +1 -0
  72. package/dist/src/extensions/workspaces/sessions/(backend)/hook/resources-discover.cli.cjs +6 -0
  73. package/dist/src/extensions/workspaces/sessions/(backend)/hook/resources-discover.cli.cjs.map +1 -0
  74. package/dist/src/extensions/workspaces/sessions/(backend)/hook/resources-discover.cli.mjs +7 -0
  75. package/dist/src/extensions/workspaces/sessions/(backend)/hook/resources-discover.cli.mjs.map +1 -0
  76. package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.cjs +6 -0
  77. package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.cjs.map +1 -0
  78. package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.mjs +7 -0
  79. package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.mjs.map +1 -0
  80. package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.cli.cjs +6 -0
  81. package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.cli.cjs.map +1 -0
  82. package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.cli.mjs +7 -0
  83. package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.cli.mjs.map +1 -0
  84. package/dist/src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.cjs +6 -0
  85. package/dist/src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.cjs.map +1 -0
  86. package/dist/src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.mjs +7 -0
  87. package/dist/src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.mjs.map +1 -0
  88. package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-author-workflow.server.cjs +6 -0
  89. package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-author-workflow.server.cjs.map +1 -0
  90. package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-author-workflow.server.mjs +7 -0
  91. package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-author-workflow.server.mjs.map +1 -0
  92. package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-workflow.server.cjs +6 -0
  93. package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-workflow.server.cjs.map +1 -0
  94. package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-workflow.server.mjs +7 -0
  95. package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-workflow.server.mjs.map +1 -0
  96. package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-recovery.server.cjs +6 -0
  97. package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-recovery.server.cjs.map +1 -0
  98. package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-recovery.server.mjs +7 -0
  99. package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-recovery.server.mjs.map +1 -0
  100. package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-skills.cli.cjs +7 -0
  101. package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-skills.cli.cjs.map +1 -0
  102. package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-skills.cli.mjs +8 -0
  103. package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-skills.cli.mjs.map +1 -0
  104. package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs +15 -0
  105. package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs.map +1 -0
  106. package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs +16 -0
  107. package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs.map +1 -0
  108. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs +17 -0
  109. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs.map +1 -0
  110. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs +18 -0
  111. package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs.map +1 -0
  112. package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-panel.cli.cjs +6 -0
  113. package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-panel.cli.cjs.map +1 -0
  114. package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-panel.cli.mjs +7 -0
  115. package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-panel.cli.mjs.map +1 -0
  116. package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-view-close.cli.cjs +6 -0
  117. package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-view-close.cli.cjs.map +1 -0
  118. package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-view-close.cli.mjs +7 -0
  119. package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-view-close.cli.mjs.map +1 -0
  120. package/dist/src/extensions/workspaces/sessions/(backend)/tool/launch-workflow.server.cjs +6 -0
  121. package/dist/src/extensions/workspaces/sessions/(backend)/tool/launch-workflow.server.cjs.map +1 -0
  122. package/dist/src/extensions/workspaces/sessions/(backend)/tool/launch-workflow.server.mjs +7 -0
  123. package/dist/src/extensions/workspaces/sessions/(backend)/tool/launch-workflow.server.mjs.map +1 -0
  124. package/dist/src/extensions/workspaces/sessions/(backend)/tool/list-workflows.server.cjs +6 -0
  125. package/dist/src/extensions/workspaces/sessions/(backend)/tool/list-workflows.server.cjs.map +1 -0
  126. package/dist/src/extensions/workspaces/sessions/(backend)/tool/list-workflows.server.mjs +7 -0
  127. package/dist/src/extensions/workspaces/sessions/(backend)/tool/list-workflows.server.mjs.map +1 -0
  128. package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow-run.server.cjs +6 -0
  129. package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow-run.server.cjs.map +1 -0
  130. package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow-run.server.mjs +7 -0
  131. package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow-run.server.mjs.map +1 -0
  132. package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow.cli.cjs +6 -0
  133. package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow.cli.cjs.map +1 -0
  134. package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow.cli.mjs +7 -0
  135. package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow.cli.mjs.map +1 -0
  136. package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.cli.cjs +6 -0
  137. package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.cli.cjs.map +1 -0
  138. package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.cli.mjs +7 -0
  139. package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.cli.mjs.map +1 -0
  140. package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.server.cjs +6 -0
  141. package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.server.cjs.map +1 -0
  142. package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.server.mjs +7 -0
  143. package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.server.mjs.map +1 -0
  144. package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowFinishedRenderer.cjs +50 -0
  145. package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowFinishedRenderer.cjs.map +1 -0
  146. package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowFinishedRenderer.mjs +47 -0
  147. package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowFinishedRenderer.mjs.map +1 -0
  148. package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowStepRenderer.cjs +36 -0
  149. package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowStepRenderer.cjs.map +1 -0
  150. package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowStepRenderer.mjs +35 -0
  151. package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowStepRenderer.mjs.map +1 -0
  152. package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-finished.cli.cjs +7 -0
  153. package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-finished.cli.cjs.map +1 -0
  154. package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-finished.cli.mjs +8 -0
  155. package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-finished.cli.mjs.map +1 -0
  156. package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-step.cli.cjs +7 -0
  157. package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-step.cli.cjs.map +1 -0
  158. package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-step.cli.mjs +8 -0
  159. package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-step.cli.mjs.map +1 -0
  160. package/dist/src/schemas/apiContracts.cjs +330 -0
  161. package/dist/src/schemas/apiContracts.cjs.map +1 -0
  162. package/dist/src/schemas/apiContracts.d.cts +53 -0
  163. package/dist/src/schemas/apiContracts.d.cts.map +1 -0
  164. package/dist/src/schemas/apiContracts.d.mts +53 -0
  165. package/dist/src/schemas/apiContracts.d.mts.map +1 -0
  166. package/dist/src/schemas/apiContracts.mjs +327 -0
  167. package/dist/src/schemas/apiContracts.mjs.map +1 -0
  168. package/dist/src/schemas/workflowPi.cjs +77 -0
  169. package/dist/src/schemas/workflowPi.cjs.map +1 -0
  170. package/dist/src/schemas/workflowPi.d.cts +1 -0
  171. package/dist/src/schemas/workflowPi.d.mts +1 -0
  172. package/dist/src/schemas/workflowPi.mjs +74 -0
  173. package/dist/src/schemas/workflowPi.mjs.map +1 -0
  174. package/dist/src/services/backgroundWork/index.cjs +61 -0
  175. package/dist/src/services/backgroundWork/index.cjs.map +1 -0
  176. package/dist/src/services/backgroundWork/index.mjs +61 -0
  177. package/dist/src/services/backgroundWork/index.mjs.map +1 -0
  178. package/dist/src/services/piToolBridge/index.cjs +205 -0
  179. package/dist/src/services/piToolBridge/index.cjs.map +1 -0
  180. package/dist/src/services/piToolBridge/index.mjs +195 -0
  181. package/dist/src/services/piToolBridge/index.mjs.map +1 -0
  182. package/dist/src/services/serverRuntime/index.cjs +387 -0
  183. package/dist/src/services/serverRuntime/index.cjs.map +1 -0
  184. package/dist/src/services/serverRuntime/index.mjs +387 -0
  185. package/dist/src/services/serverRuntime/index.mjs.map +1 -0
  186. package/dist/src/services/webWorkflowCatalog/index.cjs +53 -0
  187. package/dist/src/services/webWorkflowCatalog/index.cjs.map +1 -0
  188. package/dist/src/services/webWorkflowCatalog/index.mjs +52 -0
  189. package/dist/src/services/webWorkflowCatalog/index.mjs.map +1 -0
  190. package/dist/src/services/workflowCatalogChannel/index.cjs +74 -0
  191. package/dist/src/services/workflowCatalogChannel/index.cjs.map +1 -0
  192. package/dist/src/services/workflowCatalogChannel/index.mjs +74 -0
  193. package/dist/src/services/workflowCatalogChannel/index.mjs.map +1 -0
  194. package/dist/src/services/workflowCatalogDeps/index.cjs +25 -0
  195. package/dist/src/services/workflowCatalogDeps/index.cjs.map +1 -0
  196. package/dist/src/services/workflowCatalogDeps/index.mjs +25 -0
  197. package/dist/src/services/workflowCatalogDeps/index.mjs.map +1 -0
  198. package/dist/src/services/workflowExecution/index.cjs +205 -0
  199. package/dist/src/services/workflowExecution/index.cjs.map +1 -0
  200. package/dist/src/services/workflowExecution/index.d.cts +6 -0
  201. package/dist/src/services/workflowExecution/index.d.mts +6 -0
  202. package/dist/src/services/workflowExecution/index.mjs +200 -0
  203. package/dist/src/services/workflowExecution/index.mjs.map +1 -0
  204. package/dist/src/services/workflowFence/index.cjs +170 -0
  205. package/dist/src/services/workflowFence/index.cjs.map +1 -0
  206. package/dist/src/services/workflowFence/index.mjs +166 -0
  207. package/dist/src/services/workflowFence/index.mjs.map +1 -0
  208. package/dist/src/services/workflowHubApi/index.cjs +415 -0
  209. package/dist/src/services/workflowHubApi/index.cjs.map +1 -0
  210. package/dist/src/services/workflowHubApi/index.mjs +411 -0
  211. package/dist/src/services/workflowHubApi/index.mjs.map +1 -0
  212. package/dist/src/services/workflowLaunchCommand/index.cjs +121 -0
  213. package/dist/src/services/workflowLaunchCommand/index.cjs.map +1 -0
  214. package/dist/src/services/workflowLaunchCommand/index.d.cts +52 -0
  215. package/dist/src/services/workflowLaunchCommand/index.d.cts.map +1 -0
  216. package/dist/src/services/workflowLaunchCommand/index.d.mts +52 -0
  217. package/dist/src/services/workflowLaunchCommand/index.d.mts.map +1 -0
  218. package/dist/src/services/workflowLaunchCommand/index.mjs +118 -0
  219. package/dist/src/services/workflowLaunchCommand/index.mjs.map +1 -0
  220. package/dist/src/services/workflowLauncher/index.cjs +181 -0
  221. package/dist/src/services/workflowLauncher/index.cjs.map +1 -0
  222. package/dist/src/services/workflowLauncher/index.mjs +177 -0
  223. package/dist/src/services/workflowLauncher/index.mjs.map +1 -0
  224. package/dist/src/services/workflowNarration/index.cjs +19 -0
  225. package/dist/src/services/workflowNarration/index.cjs.map +1 -0
  226. package/dist/src/services/workflowNarration/index.mjs +19 -0
  227. package/dist/src/services/workflowNarration/index.mjs.map +1 -0
  228. package/dist/src/services/workflowResource/index.cjs +27 -0
  229. package/dist/src/services/workflowResource/index.cjs.map +1 -0
  230. package/dist/src/services/workflowResource/index.mjs +24 -0
  231. package/dist/src/services/workflowResource/index.mjs.map +1 -0
  232. package/dist/src/services/workflowRuns/index.cjs +333 -0
  233. package/dist/src/services/workflowRuns/index.cjs.map +1 -0
  234. package/dist/src/services/workflowRuns/index.mjs +319 -0
  235. package/dist/src/services/workflowRuns/index.mjs.map +1 -0
  236. package/dist/src/services/workflowTerminal/index.cjs +69 -0
  237. package/dist/src/services/workflowTerminal/index.cjs.map +1 -0
  238. package/dist/src/services/workflowTerminal/index.mjs +69 -0
  239. package/dist/src/services/workflowTerminal/index.mjs.map +1 -0
  240. package/dist/src/services/workflowTools/index.cjs +221 -0
  241. package/dist/src/services/workflowTools/index.cjs.map +1 -0
  242. package/dist/src/services/workflowTools/index.mjs +221 -0
  243. package/dist/src/services/workflowTools/index.mjs.map +1 -0
  244. package/dist/src/services/workflowWatcher/index.cjs +78 -0
  245. package/dist/src/services/workflowWatcher/index.cjs.map +1 -0
  246. package/dist/src/services/workflowWatcher/index.mjs +74 -0
  247. package/dist/src/services/workflowWatcher/index.mjs.map +1 -0
  248. package/dist/src/services/workflowsHubChannel/index.cjs +58 -0
  249. package/dist/src/services/workflowsHubChannel/index.cjs.map +1 -0
  250. package/dist/src/services/workflowsHubChannel/index.mjs +58 -0
  251. package/dist/src/services/workflowsHubChannel/index.mjs.map +1 -0
  252. package/dist/src/tui/leader.cjs +84 -0
  253. package/dist/src/tui/leader.cjs.map +1 -0
  254. package/dist/src/tui/leader.mjs +83 -0
  255. package/dist/src/tui/leader.mjs.map +1 -0
  256. package/dist/src/tui/workflow/doomOverlay.cjs +159 -0
  257. package/dist/src/tui/workflow/doomOverlay.cjs.map +1 -0
  258. package/dist/src/tui/workflow/doomOverlay.mjs +157 -0
  259. package/dist/src/tui/workflow/doomOverlay.mjs.map +1 -0
  260. package/dist/src/tui/workflow/overlayText.cjs +83 -0
  261. package/dist/src/tui/workflow/overlayText.cjs.map +1 -0
  262. package/dist/src/tui/workflow/overlayText.mjs +78 -0
  263. package/dist/src/tui/workflow/overlayText.mjs.map +1 -0
  264. package/dist/src/tui/workflow/rendering.cjs +22 -0
  265. package/dist/src/tui/workflow/rendering.cjs.map +1 -0
  266. package/dist/src/tui/workflow/rendering.mjs +20 -0
  267. package/dist/src/tui/workflow/rendering.mjs.map +1 -0
  268. package/dist/src/tui/workflow/workflowCatalog.cjs +336 -0
  269. package/dist/src/tui/workflow/workflowCatalog.cjs.map +1 -0
  270. package/dist/src/tui/workflow/workflowCatalog.mjs +333 -0
  271. package/dist/src/tui/workflow/workflowCatalog.mjs.map +1 -0
  272. package/dist/src/tui/workflow/workflowFinishedMessage.cjs +50 -0
  273. package/dist/src/tui/workflow/workflowFinishedMessage.cjs.map +1 -0
  274. package/dist/src/tui/workflow/workflowFinishedMessage.mjs +47 -0
  275. package/dist/src/tui/workflow/workflowFinishedMessage.mjs.map +1 -0
  276. package/dist/src/tui/workflow/workflowInspector.cjs +276 -0
  277. package/dist/src/tui/workflow/workflowInspector.cjs.map +1 -0
  278. package/dist/src/tui/workflow/workflowInspector.mjs +276 -0
  279. package/dist/src/tui/workflow/workflowInspector.mjs.map +1 -0
  280. package/dist/src/tui/workflow/workflowOverlay.cjs +126 -0
  281. package/dist/src/tui/workflow/workflowOverlay.cjs.map +1 -0
  282. package/dist/src/tui/workflow/workflowOverlay.mjs +125 -0
  283. package/dist/src/tui/workflow/workflowOverlay.mjs.map +1 -0
  284. package/dist/src/tui/workflow/workflowPicker.cjs +138 -0
  285. package/dist/src/tui/workflow/workflowPicker.cjs.map +1 -0
  286. package/dist/src/tui/workflow/workflowPicker.mjs +136 -0
  287. package/dist/src/tui/workflow/workflowPicker.mjs.map +1 -0
  288. package/dist/src/tui/workflow/workflowProgressOverlay.cjs +197 -0
  289. package/dist/src/tui/workflow/workflowProgressOverlay.cjs.map +1 -0
  290. package/dist/src/tui/workflow/workflowProgressOverlay.mjs +192 -0
  291. package/dist/src/tui/workflow/workflowProgressOverlay.mjs.map +1 -0
  292. package/dist/src/tui/workflow/workflowPrompt.cjs +142 -0
  293. package/dist/src/tui/workflow/workflowPrompt.cjs.map +1 -0
  294. package/dist/src/tui/workflow/workflowPrompt.mjs +139 -0
  295. package/dist/src/tui/workflow/workflowPrompt.mjs.map +1 -0
  296. package/dist/src/tui/workflow/workflowRunPanel.cjs +95 -0
  297. package/dist/src/tui/workflow/workflowRunPanel.cjs.map +1 -0
  298. package/dist/src/tui/workflow/workflowRunPanel.mjs +94 -0
  299. package/dist/src/tui/workflow/workflowRunPanel.mjs.map +1 -0
  300. package/dist/src/tui/workflow/workflowStatusRow.cjs +20 -0
  301. package/dist/src/tui/workflow/workflowStatusRow.cjs.map +1 -0
  302. package/dist/src/tui/workflow/workflowStatusRow.mjs +20 -0
  303. package/dist/src/tui/workflow/workflowStatusRow.mjs.map +1 -0
  304. package/dist/src/tui/workflow/workflowStepMessage.cjs +32 -0
  305. package/dist/src/tui/workflow/workflowStepMessage.cjs.map +1 -0
  306. package/dist/src/tui/workflow/workflowStepMessage.mjs +31 -0
  307. package/dist/src/tui/workflow/workflowStepMessage.mjs.map +1 -0
  308. package/dist/src/tui/workflow/workflowToolRender.cjs +491 -0
  309. package/dist/src/tui/workflow/workflowToolRender.cjs.map +1 -0
  310. package/dist/src/tui/workflow/workflowToolRender.mjs +490 -0
  311. package/dist/src/tui/workflow/workflowToolRender.mjs.map +1 -0
  312. package/dist/src/tui/workflowRuntime.cjs +1934 -0
  313. package/dist/src/tui/workflowRuntime.cjs.map +1 -0
  314. package/dist/src/tui/workflowRuntime.d.cts +33 -0
  315. package/dist/src/tui/workflowRuntime.d.cts.map +1 -0
  316. package/dist/src/tui/workflowRuntime.d.mts +33 -0
  317. package/dist/src/tui/workflowRuntime.d.mts.map +1 -0
  318. package/dist/src/tui/workflowRuntime.mjs +1930 -0
  319. package/dist/src/tui/workflowRuntime.mjs.map +1 -0
  320. package/dist/src/types/apiRoutes.cjs +82 -0
  321. package/dist/src/types/apiRoutes.cjs.map +1 -0
  322. package/dist/src/types/apiRoutes.mjs +80 -0
  323. package/dist/src/types/apiRoutes.mjs.map +1 -0
  324. package/dist/src/types/webWorkflowTerminal.cjs +23 -0
  325. package/dist/src/types/webWorkflowTerminal.cjs.map +1 -0
  326. package/dist/src/types/webWorkflowTerminal.mjs +22 -0
  327. package/dist/src/types/webWorkflowTerminal.mjs.map +1 -0
  328. package/dist/src/types/webWorkflows.cjs +8 -0
  329. package/dist/src/types/webWorkflows.cjs.map +1 -0
  330. package/dist/src/types/webWorkflows.mjs +7 -0
  331. package/dist/src/types/webWorkflows.mjs.map +1 -0
  332. package/package.json +8 -8
@@ -0,0 +1,1934 @@
1
+ const require_index = require("../services/workflowLaunchCommand/index.cjs");
2
+ const require_workflow = require("../constants/workflow.cjs");
3
+ const require_index$1 = require("../services/workflowResource/index.cjs");
4
+ const require_index$2 = require("../services/piToolBridge/index.cjs");
5
+ const require_index$3 = require("../services/workflowExecution/index.cjs");
6
+ const require_index$4 = require("../services/backgroundWork/index.cjs");
7
+ const require_index$5 = require("../services/workflowLauncher/index.cjs");
8
+ const require_index$6 = require("../services/workflowNarration/index.cjs");
9
+ const require_index$7 = require("../services/workflowTerminal/index.cjs");
10
+ const require_workflowCatalog = require("./workflow/workflowCatalog.cjs");
11
+ const require_workflowFinishedMessage = require("./workflow/workflowFinishedMessage.cjs");
12
+ const require_workflowInspector = require("./workflow/workflowInspector.cjs");
13
+ const require_workflowOverlay = require("./workflow/workflowOverlay.cjs");
14
+ const require_workflowPicker = require("./workflow/workflowPicker.cjs");
15
+ const require_workflowProgressOverlay = require("./workflow/workflowProgressOverlay.cjs");
16
+ const require_workflowPrompt = require("./workflow/workflowPrompt.cjs");
17
+ const require_workflowRunPanel = require("./workflow/workflowRunPanel.cjs");
18
+ const require_workflowStepMessage = require("./workflow/workflowStepMessage.cjs");
19
+ let _agimon_ai_doompi_core_ui_hub = require("@agimon-ai/doompi-core/ui-hub");
20
+ let _agimon_ai_doompi_core_child_process = require("@agimon-ai/doompi-core/child-process");
21
+ let node_fs = require("node:fs");
22
+ let node_fs_promises = require("node:fs/promises");
23
+ let node_path = require("node:path");
24
+ let _agimon_ai_workflow_mcp = require("@agimon-ai/workflow-mcp");
25
+ let node_child_process = require("node:child_process");
26
+ let node_crypto = require("node:crypto");
27
+ let node_module = require("node:module");
28
+ let _agimon_ai_doompi_core_narration = require("@agimon-ai/doompi-core/narration");
29
+ let _agimon_ai_doompi_core_readiness = require("@agimon-ai/doompi-core/readiness");
30
+ let _agimon_ai_doompi_minor_mode = require("@agimon-ai/doompi-minor-mode");
31
+ let _agimon_ai_doompi_telemetry = require("@agimon-ai/doompi-telemetry");
32
+ let _earendil_works_pi_tui = require("@earendil-works/pi-tui");
33
+ //#region src/tui/workflowRuntime.ts
34
+ const DOOM_FULLSCREEN_UI_OPTIONS = {
35
+ overlay: true,
36
+ overlayOptions: {
37
+ anchor: "top-left",
38
+ width: "100%",
39
+ maxHeight: "100%",
40
+ margin: 0
41
+ }
42
+ };
43
+ /** Renderer retained for step cards already persisted by older package versions. */
44
+ const LEGACY_WORKFLOW_STEP_MESSAGE = "workflow-step";
45
+ const FOLLOW_WIDGET_KEY = "workflow-mcp-follow";
46
+ const MODE_ID = "workflow";
47
+ const MODE_LABEL = "Workflow";
48
+ const MODE_LABEL_STYLE = "accent";
49
+ const MODE_ORDER = 20;
50
+ const MODE_ACTION_ACTIVATE = "activate";
51
+ const MODE_ACTION_DEACTIVATE = "deactivate";
52
+ function workflowModeState(active, runCount) {
53
+ return {
54
+ activation: active ? "active" : "inactive",
55
+ condition: "ready",
56
+ ...active ? {
57
+ detail: runCount > 0 ? `${runCount} active runs` : "tools enabled",
58
+ color: MODE_LABEL_STYLE
59
+ } : {},
60
+ actions: [...active ? [{
61
+ id: MODE_ACTION_ACTIVATE,
62
+ enabled: false,
63
+ disabledReason: "Workflow mode is active."
64
+ }] : [{
65
+ id: MODE_ACTION_ACTIVATE,
66
+ enabled: true
67
+ }], ...active ? [{
68
+ id: MODE_ACTION_DEACTIVATE,
69
+ enabled: true
70
+ }] : [{
71
+ id: MODE_ACTION_DEACTIVATE,
72
+ enabled: false,
73
+ disabledReason: "Workflow mode is inactive."
74
+ }]]
75
+ };
76
+ }
77
+ const LEADER_MODE_BREADCRUMB = "SPC w e";
78
+ const CATALOG_BREADCRUMB = "SPC › w / workflows › l / list";
79
+ const MANAGE_BREADCRUMB = "SPC › w / workflows › r / runs";
80
+ const RECOVER_BREADCRUMB = "SPC › w / workflows › c / recover";
81
+ const RUN_PANEL_BREADCRUMB = "SPC › w / workflows › run";
82
+ const PAUSED_EXECUTION_STATE = "paused";
83
+ const UNFOLLOW_SHORTCUT = "ctrl+alt+w";
84
+ /**
85
+ * Dismisses the run panel.
86
+ *
87
+ * Separate from the focus toggle because a focused panel hands every key to the
88
+ * run, Escape included, so a plain "Escape closes an overlay" is unavailable:
89
+ * interrupting the agent inside a step is worth more than a close key. Folding
90
+ * closing into the focus toggle instead would leave an unfocused panel with no
91
+ * way back to typing into the run.
92
+ *
93
+ * Not the only way out. This chord relies on a modifier some terminals do not
94
+ * transmit, so `DoubleEscapeDetector` backs it with a plain double-Escape that
95
+ * every terminal can send.
96
+ */
97
+ const CLOSE_OVERLAY_SHORTCUT = "ctrl+alt+q";
98
+ /** Modifier glyphs macOS users read on their own keyboards and in every menu. */
99
+ const MAC_MODIFIER_GLYPHS = {
100
+ alt: "⌥",
101
+ cmd: "⌘",
102
+ ctrl: "⌃",
103
+ meta: "⌘",
104
+ shift: "⇧"
105
+ };
106
+ /**
107
+ * Render a binding the way the user's own platform writes it.
108
+ *
109
+ * The binding string Pi registers stays canonical (`ctrl+alt+w`) on every
110
+ * platform; only the label changes. A macOS user reading "alt" has to translate
111
+ * it to the Option key they actually press, and the modifier is not even
112
+ * labelled "alt" on most Apple keyboards.
113
+ *
114
+ * Pi branches on `process.platform` for its own keybindings, so this follows a
115
+ * convention already in the host rather than inventing one.
116
+ */
117
+ function shortcutLabel(binding, platform = process.platform) {
118
+ const parts = binding.split("+");
119
+ if (platform === "darwin") return parts.map((part) => MAC_MODIFIER_GLYPHS[part] ?? part.toUpperCase()).join("");
120
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("+");
121
+ }
122
+ const DEFAULT_PAGE_SIZE = 100;
123
+ /**
124
+ * The reconcile sweep, which is a fallback rather than the mechanism.
125
+ *
126
+ * The observer reports a change as it happens, so this only bounds how long a
127
+ * change nothing notified us about stays invisible. It was 5s when it was the
128
+ * only way anything was noticed.
129
+ */
130
+ const MONITOR_INTERVAL_MS = 3e4;
131
+ /**
132
+ * Coalescing window for event-driven status refreshes.
133
+ *
134
+ * A run emits several transitions at once, and each one would otherwise page
135
+ * the registry for the authoritative lifecycle snapshot. One page per burst.
136
+ */
137
+ const STATUS_REFRESH_DEBOUNCE_MS = 120;
138
+ const PI_EXEC_TIMEOUT_MS = 1e4;
139
+ const RUNNING_STATUS = "running";
140
+ const COMPLETED_STATUS = "completed";
141
+ const FAILED_STATUS = "failed";
142
+ const ERROR_STAGE = "error";
143
+ const ERROR_COLOUR = "error";
144
+ /**
145
+ * How long shutdown waits for an interrupted inline run to finalize its record.
146
+ *
147
+ * Long enough for a step that honours SIGTERM to unwind and for the registry
148
+ * write that follows, short enough that a step ignoring it does not strand the
149
+ * user in a session that will not close.
150
+ */
151
+ const SHUTDOWN_FINALIZE_TIMEOUT_MS = 1e4;
152
+ const FOLLOW_INTERVAL_MS = 2e3;
153
+ const TAIL_LINES = 24;
154
+ /** Below these a resized run has no room left to draw, so it keeps its own size and the panel clips. */
155
+ /**
156
+ * Overlay refresh cadence and size.
157
+ *
158
+ * Faster than the background widget's 2s: a foregrounded terminal that lags
159
+ * behind your own typing reads as broken, and only the one run being watched
160
+ * pays for it.
161
+ */
162
+ const OVERLAY_REFRESH_MS = 200;
163
+ /**
164
+ * Slowest part of a refresh, given its own cadence.
165
+ *
166
+ * Reading a run's screen means forking `tmux capture-pane` or `cmux read-screen`,
167
+ * measured at ~75ms. Repainting at 200ms is right for a panel; forking at 200ms
168
+ * is not, so the two are decoupled and only the render keeps the fast tick. A
169
+ * terminal tail is a rolling window of the last few seconds either way, so the
170
+ * slower read costs the reader nothing they can perceive.
171
+ */
172
+ const SCREEN_REFRESH_MS = 750;
173
+ /**
174
+ * How many recent run announcements the launch handshake can look back through.
175
+ *
176
+ * Only ever searched for a run that registered within the acknowledgement
177
+ * window, so this needs to cover concurrent launches, not session history. The
178
+ * per-session concurrency ceiling is five.
179
+ */
180
+ const ANNOUNCED_RUN_HISTORY = 32;
181
+ /**
182
+ * Telemetry name for a launch being asked for.
183
+ *
184
+ * Named once because both launch paths, the wizard and the tool, report it and
185
+ * a query that spans them only works while the two agree.
186
+ */
187
+ const LAUNCH_REQUESTED_EVENT = "doom_workflow.launch_requested";
188
+ /**
189
+ * Recovery skill shipped by this package, offered to Pi at resource discovery.
190
+ *
191
+ * Recovery is the one path here complex enough to deserve a real skill rather
192
+ * than a few tool guidelines, and this package owns that domain: it cannot
193
+ * assume the host repository provides one. Discovery is cheap, since Pi puts
194
+ * only the name, description, and location in the system prompt and reads the
195
+ * body on demand.
196
+ *
197
+ * Resolved from the module directory, not the module file: `resolve` treats a
198
+ * trailing filename as a path segment. Both `src/extensions` and
199
+ * `dist/extensions` sit two levels below the package root.
200
+ */
201
+ /**
202
+ * Glyphs for widgets, panels, and notices.
203
+ *
204
+ * Deliberately restricted to characters that render in a default terminal font.
205
+ * A box-drawing or emoji glyph that falls back to tofu is worse than no glyph.
206
+ */
207
+ const GLYPH = {
208
+ running: "▸",
209
+ repair: "⚠",
210
+ completed: "✔",
211
+ failed: "✖",
212
+ separator: "·"
213
+ };
214
+ /**
215
+ * The panel's footer: how to leave, in the order a stuck user needs it.
216
+ *
217
+ * Escape leads on an interactive panel because it is the key every terminal
218
+ * transmits, and the one someone reaches for first when a view will not let go.
219
+ * A view-only panel says so instead: a user who does not know the run is
220
+ * unreachable reads the silence as the panel being broken.
221
+ */
222
+ function panelHint(interactive, platform = process.platform) {
223
+ const close = shortcutLabel(CLOSE_OVERLAY_SHORTCUT, platform);
224
+ if (!interactive) return `view only ${GLYPH.separator} ${close} closes this view`;
225
+ return [
226
+ "Esc Esc closes this view",
227
+ `${shortcutLabel(UNFOLLOW_SHORTCUT, platform)} switches typing`,
228
+ `${close} closes`
229
+ ].join(` ${GLYPH.separator} `);
230
+ }
231
+ /** The same hints as key caps, for the doom footer legend. */
232
+ function panelHints(interactive, platform = process.platform) {
233
+ const close = [shortcutLabel(CLOSE_OVERLAY_SHORTCUT, platform), "close"];
234
+ if (!interactive) return [close];
235
+ return [
236
+ ["Esc Esc", "close"],
237
+ [shortcutLabel(UNFOLLOW_SHORTCUT, platform), "switch typing"],
238
+ close
239
+ ];
240
+ }
241
+ /**
242
+ * Header and output lines for the follow widget, fitted to the terminal.
243
+ *
244
+ * Every line a component renders must fit the given width, so the header sheds
245
+ * detail right to left as the terminal narrows: the dismissal hint goes first,
246
+ * then the workspace/stage context, and the run key survives longest because it
247
+ * is the identifier the user needs to name the run.
248
+ *
249
+ * Colour is applied after fitting. Measuring plain text and styling afterwards
250
+ * keeps the arithmetic honest, since escape codes occupy no visible columns.
251
+ */
252
+ function renderFollowLines(record, output, theme, width) {
253
+ const key = `${GLYPH.running} ${record.runKey}`;
254
+ const meta = [
255
+ record.workspace,
256
+ stageLabel(record),
257
+ record.runner
258
+ ].filter(Boolean).join(` ${GLYPH.separator} `);
259
+ const hint = `${shortcutLabel(UNFOLLOW_SHORTCUT)} to background`;
260
+ const gap = " ";
261
+ let header = theme.fg("accent", (0, _earendil_works_pi_tui.truncateToWidth)(key, width));
262
+ if ((0, _earendil_works_pi_tui.visibleWidth)(key) + 3 + (0, _earendil_works_pi_tui.visibleWidth)(meta) <= width) {
263
+ header += gap + theme.fg("muted", meta);
264
+ if ((0, _earendil_works_pi_tui.visibleWidth)(key) + 3 + (0, _earendil_works_pi_tui.visibleWidth)(meta) + 3 + (0, _earendil_works_pi_tui.visibleWidth)(hint) <= width) header += gap + theme.fg("dim", hint);
265
+ }
266
+ return [header, ...output.map((line) => (0, _earendil_works_pi_tui.truncateToWidth)(line, width))];
267
+ }
268
+ /**
269
+ * Job tree for the follow widget, in the shape a CI run reads best.
270
+ *
271
+ * Finished jobs collapse to a single line so the eye lands on the one still
272
+ * running, whose steps are listed. That is the whole reason this exists: a
273
+ * scraped terminal shows whatever the runner last printed, which is not the
274
+ * same as knowing which job of how many is in flight.
275
+ */
276
+ function renderProgressLines(jobs, theme, width) {
277
+ const lines = [];
278
+ for (const job of jobs) {
279
+ const glyph = job.status === COMPLETED_STATUS ? GLYPH.completed : job.status === FAILED_STATUS ? GLYPH.failed : GLYPH.running;
280
+ const colour = job.status === COMPLETED_STATUS ? "success" : job.status === FAILED_STATUS ? ERROR_COLOUR : "accent";
281
+ const position = job.total ? ` ${GLYPH.separator} ${(job.index ?? 0) + 1}/${job.total}` : "";
282
+ lines.push((0, _earendil_works_pi_tui.truncateToWidth)(theme.fg(colour, `${glyph} ${job.name}`) + theme.fg("dim", position), width));
283
+ if (job.status !== RUNNING_STATUS) continue;
284
+ for (const step of job.steps) {
285
+ const stepGlyph = step.status === COMPLETED_STATUS ? GLYPH.completed : step.status === FAILED_STATUS ? GLYPH.failed : GLYPH.running;
286
+ const stepColour = step.status === RUNNING_STATUS ? "muted" : step.status === FAILED_STATUS ? ERROR_COLOUR : "dim";
287
+ lines.push((0, _earendil_works_pi_tui.truncateToWidth)(theme.fg(stepColour, ` ${stepGlyph} ${step.name}`), width));
288
+ }
289
+ }
290
+ return lines;
291
+ }
292
+ function progressEventsForRun(record, events) {
293
+ const startedAt = Date.parse(record.startedAt);
294
+ if (!Number.isFinite(startedAt)) return events;
295
+ return events.filter((event) => {
296
+ const eventAt = Date.parse(event.at);
297
+ return !Number.isFinite(eventAt) || eventAt >= startedAt;
298
+ });
299
+ }
300
+ /** Human-readable stage, rather than leaking the raw registry enum. */
301
+ function stageLabel(record) {
302
+ if (record.stale) return "stale";
303
+ if (record.stage === ERROR_STAGE) return FAILED_STATUS;
304
+ return record.stage;
305
+ }
306
+ const LAUNCHER_ENVIRONMENT_STRIPPED = [
307
+ "CMUX_SURFACE_ID",
308
+ _agimon_ai_doompi_core_child_process.SUBAGENT_ROOT_SESSION_ENV,
309
+ "CMUX_WORKSPACE_ID",
310
+ "TMUX_PANE",
311
+ "WORKFLOW_LAUNCHER_TYPE",
312
+ "WORKFLOW_TMUX_SESSION_NAME",
313
+ "WORKFLOW_STATUS_FILE"
314
+ ];
315
+ /**
316
+ * Slack between this process's clock and the run record's timestamp.
317
+ *
318
+ * The record is written by another process, and a launch acknowledged a
319
+ * fraction of a second "before" it began is a clock difference, not somebody
320
+ * else's run.
321
+ */
322
+ const LAUNCH_ACK_CLOCK_SKEW_MS = 2e3;
323
+ const LAUNCHER_ENV = "WORKFLOW_LAUNCHER";
324
+ const RECOVER_COMMAND = "recover-workflow";
325
+ const LAUNCH_PROCESS_COMMAND = "launch-process";
326
+ /**
327
+ * How a run is hosted, which is also the name of the binary that addresses it.
328
+ *
329
+ * One token per launcher rather than two, because they are the same token: a
330
+ * record's `launcher.type` is `tmux` precisely because tmux is what hosts it and
331
+ * `tmux` is what reads it back. `native` has no binary, which is exactly why
332
+ * every surface has to branch on it.
333
+ */
334
+ const LAUNCHER_TMUX = "tmux";
335
+ const LAUNCHER_CMUX = "cmux";
336
+ const LAUNCHER_NATIVE = "native";
337
+ /** The cmux socket method that answers with a styled render grid rather than plain text. */
338
+ /** The verb the cockpit's launch dialog sends; the leader board does the same work. */
339
+ const WORKFLOW_LAUNCH_COMMAND = "workflow-launch";
340
+ const HOST_TERMINAL_OUTPUT_BLOCKED = "This run points at Pi's own terminal, so its launcher screen is hidden. Use workflow status instead.";
341
+ /**
342
+ * Whether stale or inherited launcher metadata addresses this Pi process.
343
+ *
344
+ * Reading such a target recursively captures the chat into its own widget;
345
+ * opening or sending to it can foreground or type into Pi itself. New embedded
346
+ * runs record `native`, but this guard also makes older records safe.
347
+ */
348
+ /**
349
+ * A run with no recorded launcher, which reading and foregrounding both hit.
350
+ *
351
+ * Older records predate launcher identity being persisted, so this is a real
352
+ * state rather than a corrupt one, and both surfaces owe the user the same
353
+ * explanation for it.
354
+ */
355
+ const NO_LAUNCHER_RECORDED = "No durable launcher identity is recorded for this run.";
356
+ /**
357
+ * The identifier a run record carries for its definition.
358
+ *
359
+ * Derived the same way `RunWorkflowService` derives it, so the two agree: the
360
+ * file name without its final extension, which keeps the `.workflow` part.
361
+ */
362
+ function workflowIdFor(workflowPath) {
363
+ return (0, node_path.basename)(workflowPath, (0, node_path.extname)(workflowPath));
364
+ }
365
+ function launcherEnvironment(environment) {
366
+ const env = { ...environment };
367
+ for (const key of LAUNCHER_ENVIRONMENT_STRIPPED) delete env[key];
368
+ return env;
369
+ }
370
+ /** The launcher the workflow templates default to, when it is a known one. */
371
+ function preferredLauncher(environment) {
372
+ const value = environment[LAUNCHER_ENV];
373
+ return value === LAUNCHER_TMUX || value === LAUNCHER_CMUX ? value : void 0;
374
+ }
375
+ /**
376
+ * One shell word per argument.
377
+ *
378
+ * `launch-process` hands `--command` to the launcher as a shell string, so a
379
+ * run key or a path with a space in it has to survive one more round of shell
380
+ * parsing than the argv we spawn with.
381
+ */
382
+ function shellCommand(argv) {
383
+ return argv.map((word) => `'${word.replaceAll("'", `'\\''`)}'`).join(" ");
384
+ }
385
+ /**
386
+ * The workflow-mcp CLI this extension is built against.
387
+ *
388
+ * Resolved from the dependency rather than PATH: `pnpm exec workflow-mcp` finds
389
+ * whatever the workspace root happens to install, which is not necessarily this
390
+ * version, and it pays for a package-manager resolution on the launch path.
391
+ */
392
+ function resolveWorkflowCli() {
393
+ try {
394
+ return (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href).resolve("@agimon-ai/workflow-mcp/cli");
395
+ } catch {
396
+ return;
397
+ }
398
+ }
399
+ const detachedSpawn = async (command, args, options) => {
400
+ await new Promise((resolveSpawn, rejectSpawn) => {
401
+ const child = (0, node_child_process.spawn)(command, [...args], {
402
+ ...options,
403
+ detached: true,
404
+ stdio: "ignore"
405
+ });
406
+ child.once("error", rejectSpawn);
407
+ child.once("spawn", () => {
408
+ child.off("error", rejectSpawn);
409
+ child.unref();
410
+ resolveSpawn();
411
+ });
412
+ });
413
+ };
414
+ function collectRunnerMaps(value, maps) {
415
+ if (Array.isArray(value)) {
416
+ for (const item of value) collectRunnerMaps(item, maps);
417
+ return;
418
+ }
419
+ if (!value || typeof value !== "object") return;
420
+ for (const [key, nested] of Object.entries(value)) if ((key === "interactiveRun" || key === "run") && nested && typeof nested === "object" && !Array.isArray(nested)) maps.push(Object.keys(nested));
421
+ else collectRunnerMaps(nested, maps);
422
+ }
423
+ function compatibleRunners(workflow) {
424
+ const maps = [];
425
+ collectRunnerMaps(workflow, maps);
426
+ if (maps.length === 0) return void 0;
427
+ return maps[0].filter((runner) => maps.every((available) => available.includes(runner))).sort();
428
+ }
429
+ async function listAllRuns(registry, workspace) {
430
+ const records = [];
431
+ let page = 1;
432
+ let hasNextPage = true;
433
+ while (hasNextPage) {
434
+ const result = await registry.listRunsPage({
435
+ page,
436
+ pageSize: DEFAULT_PAGE_SIZE,
437
+ workspace
438
+ });
439
+ records.push(...result.items);
440
+ hasNextPage = result.hasNextPage;
441
+ page += 1;
442
+ }
443
+ return records;
444
+ }
445
+ /**
446
+ * One run, as a person reads it.
447
+ *
448
+ * Middle dots rather than pipes: pipes read as table borders in a terminal and
449
+ * collide visually with the launcher output the widget sits above.
450
+ */
451
+ function runLabel(record) {
452
+ return [
453
+ record.runKey,
454
+ record.workspace,
455
+ stageLabel(record),
456
+ record.runner
457
+ ].filter(Boolean).join(` ${GLYPH.separator} `);
458
+ }
459
+ function createWorkflowPiRuntime(pi, options) {
460
+ const services = [];
461
+ const feature = options.featureFactory?.() ?? (0, _agimon_ai_workflow_mcp.createEmbeddedWorkflowFeature)();
462
+ const runtimeGeneration = (0, node_crypto.randomUUID)();
463
+ let runtimeActive = true;
464
+ let sessionGeneration = 0;
465
+ let readinessAbort;
466
+ let readinessHandle;
467
+ let standaloneReadiness;
468
+ const invocationActive = options.isActive ?? (() => true);
469
+ const isRuntimeActive = () => runtimeActive && invocationActive();
470
+ const notifyStandaloneReadiness = (notification) => {
471
+ const diagnostics = notification.diagnostics.join("; ");
472
+ const detail = (notification.error?.message ?? diagnostics) || "Initialization did not complete.";
473
+ const message = `${notification.packageId} initialization ${notification.state}: ${detail}`;
474
+ if (sessionCtx?.hasUI) sessionCtx.ui.notify(message, "warning");
475
+ else process.emitWarning(message);
476
+ };
477
+ const readinessCoordinator = (_ctx) => {
478
+ const shared = options.cordis ? (0, _agimon_ai_doompi_core_readiness.readDoomReadinessCoordinator)(options.cordis) : void 0;
479
+ if (shared) return shared;
480
+ standaloneReadiness ??= (0, _agimon_ai_doompi_core_readiness.createDoomReadinessCoordinator)({ notify: notifyStandaloneReadiness });
481
+ return standaloneReadiness;
482
+ };
483
+ const waitForReadiness = async (signal) => {
484
+ signal?.throwIfAborted();
485
+ const pending = readinessHandle;
486
+ if (!pending) throw new Error("Workflow session initialization has not started.");
487
+ const handle = await pending;
488
+ signal?.throwIfAborted();
489
+ await handle.wait(signal ? { signal } : void 0);
490
+ if (!isRuntimeActive() || pending !== readinessHandle) throw new Error("Workflow readiness belongs to a stale extension generation.");
491
+ };
492
+ const { parser, recoverService, registry, runService, runTool } = feature;
493
+ const spawnDetached = options.spawnDetached ?? detachedSpawn;
494
+ const monitorIntervalMs = options.monitorIntervalMs ?? MONITOR_INTERVAL_MS;
495
+ const followIntervalMs = options.followIntervalMs ?? FOLLOW_INTERVAL_MS;
496
+ let monitorTimer;
497
+ let monitorRefreshGeneration;
498
+ let followTimer;
499
+ let followRefreshActive = false;
500
+ let followedRun;
501
+ let previousStages = /* @__PURE__ */ new Map();
502
+ const pendingTerminalRuns = /* @__PURE__ */ new Map();
503
+ const deliveredTerminalRuns = /* @__PURE__ */ new Set();
504
+ let sessionStartedAt = 0;
505
+ let runProvider;
506
+ services.push((context) => {
507
+ runProvider = require_index$4.registerRunProvider(context);
508
+ });
509
+ let lifecycleNarrationReady = false;
510
+ let sessionRunCount = 0;
511
+ let workflowMode = false;
512
+ let publishedWorkflowState;
513
+ const restrictionListeners = /* @__PURE__ */ new Set();
514
+ const modeContribution = (0, _agimon_ai_doompi_minor_mode.defineMinorMode)({
515
+ descriptor: {
516
+ source: require_workflow.PACKAGE_SOURCE,
517
+ id: MODE_ID,
518
+ label: MODE_LABEL,
519
+ description: "Workflow discovery, launch, inspection, control, and recovery tools.",
520
+ order: MODE_ORDER,
521
+ actions: [{
522
+ id: MODE_ACTION_ACTIVATE,
523
+ label: "Activate",
524
+ description: "Enable workflow tools for this session.",
525
+ contexts: ["tui", "headless"],
526
+ parameters: []
527
+ }, {
528
+ id: MODE_ACTION_DEACTIVATE,
529
+ label: "Deactivate",
530
+ description: "Disable workflow tools without stopping active runs.",
531
+ contexts: ["tui", "headless"],
532
+ parameters: []
533
+ }]
534
+ },
535
+ state: () => workflowModeState(workflowMode, sessionRunCount),
536
+ async handleAction(_runtime, actionId, _argumentsValue, execution) {
537
+ await waitForReadiness();
538
+ if (actionId === MODE_ACTION_ACTIVATE) {
539
+ await applyWorkflowMode(true, execution.context);
540
+ return { message: "Workflow mode activated." };
541
+ }
542
+ if (actionId === MODE_ACTION_DEACTIVATE) {
543
+ await applyWorkflowMode(false, execution.context);
544
+ return { message: "Workflow mode deactivated." };
545
+ }
546
+ throw new Error(`Unknown workflow mode action: ${actionId}`);
547
+ }
548
+ }).createOwner(void 0);
549
+ /** Kept so callers outside a handler can still resolve the session id. */
550
+ let sessionCtx;
551
+ let activeOverlay;
552
+ let inspectorHandle;
553
+ let inspectorOpen = false;
554
+ /** The runs the inspector last listed, keyed the way its items are. */
555
+ let inspectorRecords = /* @__PURE__ */ new Map();
556
+ /**
557
+ * The pushed view of runs this session owns.
558
+ *
559
+ * A delegated run executes in a launcher's process, so nothing in here ever
560
+ * observes it directly. What it observes is the registry the run writes to,
561
+ * which is why this replaces polling rather than instrumenting anything.
562
+ */
563
+ let runControl;
564
+ const disposeRunControl = /* @__PURE__ */ new Set();
565
+ /**
566
+ * Job and step transitions per run, accumulated from pushed events.
567
+ *
568
+ * The live list derives its current position from this stream and a failed
569
+ * run reuses the same events for its terminal summary. Released after that
570
+ * summary is delivered.
571
+ */
572
+ const runEvents = /* @__PURE__ */ new Map();
573
+ /**
574
+ * Runs the observer has announced, newest first, for the launch handshake.
575
+ *
576
+ * A launch needs the run key of the run it just started, and the launcher
577
+ * process never learns it. Bounded, because a session that launches all day
578
+ * must not accumulate a record per launch for the sake of a lookup that
579
+ * only ever cares about the last few seconds.
580
+ */
581
+ const announcedRuns = [];
582
+ /** Active records are the authoritative membership of the task-like progress widget. */
583
+ const activeRunRecords = /* @__PURE__ */ new Map();
584
+ /** Invalidates a registry snapshot when a newer pushed lifecycle event lands mid-read. */
585
+ let activeRunRevision = 0;
586
+ const progressOverlay = new require_workflowProgressOverlay.WorkflowProgressOverlay();
587
+ /**
588
+ * The session that owns this extension's runs.
589
+ *
590
+ * Taken from whatever context Pi hands us, and deliberately the SAME value
591
+ * `launch_workflow` stamps onto a run: reading the stamp from one place and
592
+ * the filter from another is how a run goes invisible to the session that
593
+ * just started it. `session_start` fills this in for a session that has
594
+ * launched nothing yet; before either, it is undefined and every ownership
595
+ * check fails closed, which is correct because such a session owns no runs.
596
+ */
597
+ let knownSessionId;
598
+ let telemetry;
599
+ const getTelemetry = (ctx) => {
600
+ telemetry ??= (0, _agimon_ai_doompi_telemetry.createDoomTelemetry)({
601
+ serviceName: "doom-workflow",
602
+ packageName: "@agimon-ai/doompi-workflow",
603
+ cwd: ctx?.cwd,
604
+ env: options.environment,
605
+ enableLogs: true,
606
+ enableTraces: true
607
+ });
608
+ return telemetry;
609
+ };
610
+ let narrationService;
611
+ const reportNarrationFailure = (error) => {
612
+ getTelemetry(sessionCtx).recordWarning("doom_workflow.narration_failed", error).catch((telemetryError) => {
613
+ process.emitWarning(`Doom-workflow could not record a narration failure: ${String(telemetryError)}`);
614
+ });
615
+ };
616
+ const narrationSink = { narrate(text) {
617
+ const service = narrationService;
618
+ const request = (0, _agimon_ai_doompi_core_narration.createNarrationRequest)(text);
619
+ if (!service || !request) return;
620
+ try {
621
+ Promise.resolve(service.request(request)).catch(reportNarrationFailure);
622
+ } catch (error) {
623
+ reportNarrationFailure(error);
624
+ }
625
+ } };
626
+ services.push((cordis) => {
627
+ cordis.inject([_agimon_ai_doompi_core_narration.DOOM_NARRATION_SERVICE], (serviceContext) => {
628
+ const service = (0, _agimon_ai_doompi_core_narration.requireDoomNarrationService)(serviceContext);
629
+ narrationService = service;
630
+ return () => {
631
+ if (narrationService === service) narrationService = void 0;
632
+ };
633
+ });
634
+ });
635
+ const observeSession = (sessionId) => {
636
+ if (sessionId && isRuntimeActive()) knownSessionId = (0, _agimon_ai_doompi_core_child_process.resolveRootSessionId)(sessionId, options.environment);
637
+ };
638
+ const currentSessionId = () => {
639
+ const sessionId = knownSessionId ?? sessionCtx?.sessionManager.getSessionId();
640
+ return sessionId ? (0, _agimon_ai_doompi_core_child_process.resolveRootSessionId)(sessionId, options.environment) : void 0;
641
+ };
642
+ const isRootProcess = (ctx, rootSessionId) => ctx.sessionManager.getSessionId().trim() === rootSessionId;
643
+ /**
644
+ * The registry is one directory under $HOME shared by every repository and
645
+ * every Pi session on the machine, so an unscoped tool would let any session
646
+ * list, foreground, stop, or recover any other session's runs. Scoping is a
647
+ * host-level filter on the tool instance, not a caller input: the agent
648
+ * cannot opt out of it, and the CLI (which is how orphaned runs are managed)
649
+ * keeps the global view.
650
+ */
651
+ const recoverTool = feature.createRecoverTool({ ownerSessionId: currentSessionId });
652
+ const controlTool = feature.createControlTool();
653
+ /**
654
+ * Inline executions still in flight, so shutdown can finalize them.
655
+ *
656
+ * A workflow declaring `launch-command` hands off to tmux or cmux and its
657
+ * promise settles almost immediately; one without runs its whole engine in
658
+ * this process. Only the latter lingers here, so the set needs no launcher
659
+ * check of its own.
660
+ */
661
+ const pendingInlineRuns = /* @__PURE__ */ new Set();
662
+ const trackPendingRun = (run) => {
663
+ const startedAt = Date.now();
664
+ const runTelemetry = getTelemetry();
665
+ pendingInlineRuns.add(run);
666
+ runTelemetry.recordEvent("doom_workflow.run_started", { outcome: "started" });
667
+ run.then(() => {
668
+ pendingInlineRuns.delete(run);
669
+ return runTelemetry.recordEvent("doom_workflow.run_finished", {
670
+ duration_ms: Date.now() - startedAt,
671
+ outcome: "completed"
672
+ });
673
+ }, (error) => {
674
+ pendingInlineRuns.delete(run);
675
+ return runTelemetry.recordError("doom_workflow.run_failed", error, { duration_ms: Date.now() - startedAt });
676
+ });
677
+ return run;
678
+ };
679
+ const rejectRunner = (workflowPath, runner) => {
680
+ const compatible = compatibleRunners(parser.parseWorkflowFile(workflowPath));
681
+ if (!compatible || compatible.includes(runner)) return void 0;
682
+ return `Runner ${JSON.stringify(runner)} is not available across this workflow. Compatible runners: ${compatible.join(", ") || "none"}.`;
683
+ };
684
+ const launchExecutor = require_index$3.createWorkflowLaunchExecutor({
685
+ environment: options.environment,
686
+ activeRunCount: async () => sessionRunCount,
687
+ observeSession,
688
+ rejectRunner,
689
+ runTool,
690
+ trackPendingRun,
691
+ ...options.launchAckPollMs === void 0 ? {} : { launchAckPollMs: options.launchAckPollMs },
692
+ ...options.launchAckTimeoutMs === void 0 ? {} : { launchAckTimeoutMs: options.launchAckTimeoutMs },
693
+ findLaunchedRun: async ({ sessionId, since, workflowPath }) => {
694
+ const expectedId = workflowIdFor(workflowPath);
695
+ const matches = (record) => {
696
+ if (!require_index$2.isSessionRun(record, sessionId) || record.stale) return false;
697
+ if (record.workflowId && record.workflowId !== expectedId) return false;
698
+ const startedAt = Date.parse(record.startedAt);
699
+ return Number.isFinite(startedAt) && startedAt >= since - LAUNCH_ACK_CLOCK_SKEW_MS;
700
+ };
701
+ if (!runControl) return (await listAllRuns(registry)).find(matches);
702
+ await runControl.refresh();
703
+ return announcedRuns.find(matches);
704
+ },
705
+ onLateFailure: (error, ctx) => {
706
+ const detail = error instanceof Error ? error.message : String(error);
707
+ ctx.ui.notify(require_index$2.launchNotice(`Workflow launch failed after it was reported started.\n${detail}`), "warning");
708
+ },
709
+ onLaunch: async (ctx) => {
710
+ await getTelemetry(ctx).recordEvent(LAUNCH_REQUESTED_EVENT, { outcome: "requested" });
711
+ return refreshStatus(ctx, false);
712
+ }
713
+ });
714
+ /** Repaint the live list from pushed state without writing progress into chat. */
715
+ const publishProgressOverlay = () => {
716
+ const records = [...activeRunRecords.values()].sort((left, right) => Date.parse(left.startedAt) - Date.parse(right.startedAt) || runIdentity(left).localeCompare(runIdentity(right)));
717
+ progressOverlay.update(records.map((record) => require_workflowProgressOverlay.workflowProgressRow(record, runEvents.get(runIdentity(record)) ?? [])));
718
+ };
719
+ /** Add, refresh, or remove one run from the live list. */
720
+ const noteActiveRecord = (record) => {
721
+ activeRunRevision += 1;
722
+ const identity = runIdentity(record);
723
+ if (record.stage === RUNNING_STATUS && !record.stale) activeRunRecords.set(identity, record);
724
+ else activeRunRecords.delete(identity);
725
+ publishProgressOverlay();
726
+ };
727
+ /** Replace widget membership from an authoritative registry snapshot. */
728
+ const replaceActiveRecords = (records) => {
729
+ activeRunRecords.clear();
730
+ for (const record of records) activeRunRecords.set(runIdentity(record), record);
731
+ publishProgressOverlay();
732
+ };
733
+ /** Keep every pushed progress event for both the live row and terminal summary. */
734
+ const noteProgressEvent = (identity, event) => {
735
+ const events = runEvents.get(identity) ?? [];
736
+ events.push(event);
737
+ runEvents.set(identity, events);
738
+ };
739
+ /**
740
+ * Ask for a status refresh, collapsing a burst of events into one.
741
+ *
742
+ * The live list updates from the event itself, but terminal detection still
743
+ * needs the session's authoritative record set, and that costs a registry
744
+ * page. A run emitting six transitions at once must not buy six.
745
+ */
746
+ let statusRefreshTimer;
747
+ const scheduleStatusRefresh = (ctx) => {
748
+ if (!isRuntimeActive() || ctx !== sessionCtx || statusRefreshTimer) return;
749
+ statusRefreshTimer = setTimeout(() => {
750
+ statusRefreshTimer = void 0;
751
+ if (isRuntimeActive() && ctx === sessionCtx) refreshStatus(ctx, true);
752
+ }, STATUS_REFRESH_DEBOUNCE_MS);
753
+ statusRefreshTimer.unref?.();
754
+ };
755
+ /** Everything remembered about a run, released once it can produce no more. */
756
+ const forgetRun = (identity) => {
757
+ runEvents.delete(identity);
758
+ };
759
+ /**
760
+ * Subscribe to the registry instead of asking it what changed.
761
+ *
762
+ * A run this session launched executes in a launcher's own process, so the
763
+ * only thing connecting it to this one is the session stamp on its record.
764
+ * The filter is that stamp, and it is applied inside the observer so a
765
+ * shared registry never leaks another session's runs into this UI.
766
+ */
767
+ const startRunControl = async (ctx) => {
768
+ const sessionId = (0, _agimon_ai_doompi_core_child_process.resolveRootSessionId)(ctx.sessionManager.getSessionId(), options.environment);
769
+ const control = feature.createRunControl({ recordFilter: (candidate) => require_index$2.isSessionRun(candidate, sessionId) });
770
+ runControl = control;
771
+ const controlIsCurrent = () => isRuntimeActive() && ctx === sessionCtx && runControl === control;
772
+ const controlDisposers = [
773
+ control.on("step", (event) => {
774
+ if (!controlIsCurrent()) return;
775
+ const identity = runIdentity(event.record);
776
+ noteProgressEvent(identity, {
777
+ at: event.at,
778
+ job: event.job,
779
+ status: event.status,
780
+ step: event.step,
781
+ type: "step",
782
+ ...event.reason === void 0 ? {} : { reason: event.reason }
783
+ });
784
+ noteActiveRecord(event.record);
785
+ scheduleStatusRefresh(ctx);
786
+ }),
787
+ control.on("job", (event) => {
788
+ if (!controlIsCurrent()) return;
789
+ const identity = runIdentity(event.record);
790
+ noteProgressEvent(identity, {
791
+ at: event.at,
792
+ job: event.job,
793
+ status: event.status,
794
+ type: "job",
795
+ ...event.index === void 0 ? {} : { index: event.index },
796
+ ...event.reason === void 0 ? {} : { reason: event.reason },
797
+ ...event.total === void 0 ? {} : { total: event.total }
798
+ });
799
+ noteActiveRecord(event.record);
800
+ scheduleStatusRefresh(ctx);
801
+ }),
802
+ control.on("runStarted", (event) => {
803
+ if (!controlIsCurrent()) return;
804
+ announcedRuns.unshift(event.record);
805
+ if (announcedRuns.length > ANNOUNCED_RUN_HISTORY) announcedRuns.length = ANNOUNCED_RUN_HISTORY;
806
+ noteActiveRecord(event.record);
807
+ scheduleStatusRefresh(ctx);
808
+ }),
809
+ control.on("runUpdated", (event) => {
810
+ if (!controlIsCurrent()) return;
811
+ noteActiveRecord(event.record);
812
+ refreshStatus(ctx, true);
813
+ }),
814
+ control.on("runFinished", (event) => {
815
+ if (!controlIsCurrent()) return;
816
+ noteActiveRecord(event.record);
817
+ refreshStatus(ctx, true);
818
+ })
819
+ ];
820
+ for (const dispose of controlDisposers) disposeRunControl.add(dispose);
821
+ try {
822
+ await control.start();
823
+ } catch (error) {
824
+ for (const dispose of controlDisposers) {
825
+ disposeRunControl.delete(dispose);
826
+ dispose();
827
+ }
828
+ control.dispose();
829
+ if (runControl === control) runControl = void 0;
830
+ throw error;
831
+ }
832
+ if (!isRuntimeActive() || ctx !== sessionCtx || runControl !== control) {
833
+ for (const dispose of controlDisposers) {
834
+ disposeRunControl.delete(dispose);
835
+ dispose();
836
+ }
837
+ control.dispose();
838
+ if (runControl === control) runControl = void 0;
839
+ }
840
+ };
841
+ const stopRunControl = () => {
842
+ if (statusRefreshTimer) clearTimeout(statusRefreshTimer);
843
+ statusRefreshTimer = void 0;
844
+ for (const dispose of disposeRunControl) dispose();
845
+ disposeRunControl.clear();
846
+ runControl?.dispose();
847
+ runControl = void 0;
848
+ runEvents.clear();
849
+ activeRunRevision += 1;
850
+ activeRunRecords.clear();
851
+ publishProgressOverlay();
852
+ };
853
+ /** Dismiss the run panel entirely, leaving the run itself alone. */
854
+ const closeOverlay = () => {
855
+ activeOverlay?.teardown();
856
+ activeOverlay?.handle?.hide();
857
+ activeOverlay = void 0;
858
+ };
859
+ const runIdentity = (record) => record.runId ?? `${record.workspace}/${record.runKey}`;
860
+ const publishModeWidget = () => {
861
+ const contribution = modeContribution;
862
+ if (!contribution) return;
863
+ const next = workflowModeState(workflowMode, sessionRunCount);
864
+ const serialized = JSON.stringify(next);
865
+ if (serialized === publishedWorkflowState) return;
866
+ publishedWorkflowState = serialized;
867
+ contribution.publish();
868
+ };
869
+ const refreshStatus = async (ctx, notifyChanges) => {
870
+ const refreshGeneration = sessionGeneration;
871
+ const refreshIsCurrent = () => isRuntimeActive() && refreshGeneration === sessionGeneration && ctx === sessionCtx;
872
+ if (!refreshIsCurrent()) return;
873
+ const activeTelemetry = getTelemetry(ctx);
874
+ if (monitorRefreshGeneration === refreshGeneration) {
875
+ activeTelemetry.recordEvent("doom_workflow.monitor_skipped", { outcome: "already_running" });
876
+ return;
877
+ }
878
+ const startedAt = Date.now();
879
+ const activeRevisionAtRead = activeRunRevision;
880
+ monitorRefreshGeneration = refreshGeneration;
881
+ try {
882
+ const records = await listAllRuns(registry);
883
+ if (!refreshIsCurrent()) return;
884
+ const sessionId = (0, _agimon_ai_doompi_core_child_process.resolveRootSessionId)(ctx.sessionManager.getSessionId(), options.environment);
885
+ const rootProcess = isRootProcess(ctx, sessionId);
886
+ const sessionRecords = records.filter((record) => require_index$2.isSessionRun(record, sessionId));
887
+ const running = sessionRecords.filter((record) => record.stage === RUNNING_STATUS && !record.stale);
888
+ const failed = sessionRecords.filter((record) => record.stage === ERROR_STAGE);
889
+ if (activeRevisionAtRead === activeRunRevision) replaceActiveRecords(running);
890
+ activeTelemetry.recordEvent("doom_workflow.monitor_counts", {
891
+ "workflow.run_count": sessionRecords.length,
892
+ "workflow.running_count": running.length,
893
+ "workflow.failed_count": failed.length,
894
+ mode: workflowMode ? "on" : "off"
895
+ });
896
+ const mine = require_index$2.runsForSession(records, sessionId).filter((record) => !record.stale);
897
+ sessionRunCount = mine.length;
898
+ publishModeWidget();
899
+ forgetLauncherScreens(new Set(sessionRecords.map((record) => runIdentity(record))));
900
+ const nextStages = new Map(sessionRecords.map((record) => [runIdentity(record), record.stage]));
901
+ for (const record of sessionRecords) {
902
+ const identity = runIdentity(record);
903
+ const previous = previousStages.get(identity);
904
+ const startedAt = Date.parse(record.startedAt);
905
+ const startedDuringSession = previous === void 0 && (sessionStartedAt === 0 || !Number.isFinite(startedAt) || startedAt >= sessionStartedAt);
906
+ if (lifecycleNarrationReady) require_index$6.narrateWorkflowTransition(narrationSink, record, previous === RUNNING_STATUS || previous === COMPLETED_STATUS || previous === ERROR_STAGE ? previous : void 0, startedDuringSession);
907
+ if (!notifyChanges || !rootProcess || record.stage === RUNNING_STATUS) continue;
908
+ if (!deliveredTerminalRuns.has(identity) && (previous === RUNNING_STATUS || startedDuringSession)) pendingTerminalRuns.set(identity, record);
909
+ }
910
+ const retainedWork = new Map(mine.map((record) => [runIdentity(record), record]));
911
+ for (const [identity, record] of pendingTerminalRuns) retainedWork.set(identity, record);
912
+ if (!refreshIsCurrent()) return;
913
+ runProvider?.update([...retainedWork.values()].map((record) => ({
914
+ id: record.runId ?? record.runKey,
915
+ sessionId
916
+ })));
917
+ const retainedEventIdentities = /* @__PURE__ */ new Set([...sessionRecords.filter((record) => record.stage === RUNNING_STATUS && !record.stale).map((record) => runIdentity(record)), ...pendingTerminalRuns.keys()]);
918
+ for (const identity of runEvents.keys()) if (!retainedEventIdentities.has(identity)) forgetRun(identity);
919
+ if (notifyChanges) for (const record of sessionRecords) {
920
+ if (previousStages.get(runIdentity(record)) !== RUNNING_STATUS || record.stage === RUNNING_STATUS) continue;
921
+ const completed = record.stage === COMPLETED_STATUS;
922
+ const detail = record.failedJob ? ` at ${record.failedJob}` : "";
923
+ ctx.ui.notify(completed ? `${GLYPH.completed} ${record.runKey} completed` : `${GLYPH.failed} ${record.runKey} ${stageLabel(record)}${detail}`, completed ? "info" : "warning");
924
+ activeTelemetry.recordEvent("doom_workflow.run_transition", { outcome: completed ? "completed" : stageLabel(record) });
925
+ }
926
+ if (rootProcess && pendingTerminalRuns.size > 0) try {
927
+ const terminalRecords = [...pendingTerminalRuns.values()];
928
+ const summaries = await Promise.all(terminalRecords.map(async (record) => {
929
+ if (record.stage === COMPLETED_STATUS) return require_index$2.finishedRunSummary(record, []);
930
+ const identity = runIdentity(record);
931
+ const events = runEvents.get(identity) ?? progressEventsForRun(record, await (0, _agimon_ai_workflow_mcp.readWorkflowProgress)(registry.runDirectoryFor(record)));
932
+ return require_index$2.finishedRunSummary(record, (0, _agimon_ai_workflow_mcp.summarizeWorkflowProgress)(events));
933
+ }));
934
+ if (!refreshIsCurrent()) return;
935
+ pi.sendMessage({
936
+ customType: require_workflowFinishedMessage.WORKFLOW_FINISHED_MESSAGE,
937
+ content: summaries.join("\n\n"),
938
+ display: true,
939
+ details: {
940
+ runIds: terminalRecords.map((record) => record.runId ?? runIdentity(record)),
941
+ runs: terminalRecords.map((record) => ({
942
+ runKey: record.runKey,
943
+ workspace: record.workspace,
944
+ stage: record.stage,
945
+ ...record.workflowId ? { workflowId: record.workflowId } : {},
946
+ ...record.failedJob ? { failedJob: record.failedJob } : {},
947
+ ...record.errorMessage ? { error: record.errorMessage } : {}
948
+ }))
949
+ }
950
+ }, {
951
+ triggerTurn: true,
952
+ deliverAs: "steer"
953
+ });
954
+ if (!refreshIsCurrent()) return;
955
+ for (const record of terminalRecords) {
956
+ const identity = runIdentity(record);
957
+ deliveredTerminalRuns.add(identity);
958
+ pendingTerminalRuns.delete(identity);
959
+ forgetRun(identity);
960
+ }
961
+ runProvider?.update(mine.map((record) => ({
962
+ id: record.runId ?? record.runKey,
963
+ sessionId
964
+ })));
965
+ } catch (error) {
966
+ await activeTelemetry.recordError("doom_workflow.terminal_delivery_failed", error, { "workflow.pending_terminal_count": pendingTerminalRuns.size });
967
+ }
968
+ if (!refreshIsCurrent()) return;
969
+ previousStages = nextStages;
970
+ activeTelemetry.recordEvent("doom_workflow.monitor_finished", {
971
+ duration_ms: Date.now() - startedAt,
972
+ outcome: "completed"
973
+ });
974
+ } catch (error) {
975
+ await activeTelemetry.recordError("doom_workflow.monitor_failed", error, { duration_ms: Date.now() - startedAt });
976
+ if (refreshIsCurrent()) ctx.ui.notify(`Workflow status refresh failed: ${error instanceof Error ? error.message : String(error)}`, "warning");
977
+ } finally {
978
+ if (monitorRefreshGeneration === refreshGeneration) monitorRefreshGeneration = void 0;
979
+ }
980
+ };
981
+ const clearFollow = (ctx) => {
982
+ if (followTimer) clearInterval(followTimer);
983
+ followTimer = void 0;
984
+ followedRun = void 0;
985
+ ctx.ui.setWidget(FOLLOW_WIDGET_KEY, void 0);
986
+ };
987
+ /**
988
+ * This session's view of the terminals its runs were launched into.
989
+ *
990
+ * The engine owns how a launcher is read and driven; this owns how often,
991
+ * which is a property of the surface rather than of the run. Every panel
992
+ * here repaints five times a second and a scrape forks a CLI, so the two
993
+ * concerns stayed separate when the reads moved down into the engine.
994
+ */
995
+ const terminalFacade = new _agimon_ai_workflow_mcp.WorkflowTerminalService({
996
+ run: async (command, args, options) => {
997
+ const result = await pi.exec(command, [...args], { timeout: options.timeoutMs });
998
+ return {
999
+ code: result.code,
1000
+ stdout: result.stdout,
1001
+ stderr: result.stderr
1002
+ };
1003
+ },
1004
+ lines: TAIL_LINES,
1005
+ timeoutMs: PI_EXEC_TIMEOUT_MS
1006
+ });
1007
+ const terminals = require_index$7.createWorkflowTerminalService({
1008
+ terminal: terminalFacade,
1009
+ now: () => Date.now(),
1010
+ refreshMs: SCREEN_REFRESH_MS
1011
+ });
1012
+ /**
1013
+ * Geometry last asked of each run, so a repaint that changes nothing costs
1014
+ * no exec. Keyed by run identity, like every other per-run cache here.
1015
+ */
1016
+ const launcherGeometry = /* @__PURE__ */ new Map();
1017
+ /**
1018
+ * Match the run's own terminal to the panel showing it.
1019
+ *
1020
+ * A run draws at whatever geometry its launcher gave it, so a panel
1021
+ * narrower than that shows a clipped window onto a wider screen rather than
1022
+ * a smaller rendering of it. Which launchers can be told a size is the
1023
+ * engine's business; not asking twice for the same size is this one's.
1024
+ *
1025
+ * The size is NOT restored when the panel closes: the window belongs to this
1026
+ * run, and a run that is watched at panel size should stay readable at panel
1027
+ * size for whoever attaches to it next.
1028
+ */
1029
+ const resizeLauncherViewport = (record, columns, rows) => {
1030
+ const identity = runIdentity(record);
1031
+ const geometry = `${columns}x${rows}`;
1032
+ if (launcherGeometry.get(identity) === geometry) return;
1033
+ launcherGeometry.set(identity, geometry);
1034
+ terminalFacade.resize(record, columns, rows);
1035
+ };
1036
+ /**
1037
+ * A run's recent output, reusing a recent read rather than forking again.
1038
+ *
1039
+ * Rendering stays as fast as it ever was: the caller gets the cached lines
1040
+ * immediately and the next repaint picks up whatever the refresh found.
1041
+ */
1042
+ const cachedLauncherScreen = (record) => {
1043
+ if (terminalFacade.targetsCurrentTerminal(record)) return Promise.resolve([HOST_TERMINAL_OUTPUT_BLOCKED]);
1044
+ return terminals.screen(runIdentity(record), record, TAIL_LINES);
1045
+ };
1046
+ /** Runs whose screens can never change again cannot earn their memory back. */
1047
+ const forgetLauncherScreens = (live) => {
1048
+ terminals.forget(live);
1049
+ for (const identity of launcherGeometry.keys()) if (!live.has(identity)) launcherGeometry.delete(identity);
1050
+ };
1051
+ /**
1052
+ * Bring a run's launcher to the foreground.
1053
+ *
1054
+ * Returns a status line rather than notifying, because its only caller is now
1055
+ * the `workflow_run` tool's `open` action and the agent needs to read the outcome.
1056
+ */
1057
+ const openLauncher = async (record) => {
1058
+ const launcher = record.launcher;
1059
+ if (!launcher) return NO_LAUNCHER_RECORDED;
1060
+ if (terminalFacade.targetsCurrentTerminal(record)) return HOST_TERMINAL_OUTPUT_BLOCKED;
1061
+ if (launcher.type === LAUNCHER_NATIVE) return "This run is hosted natively rather than in a multiplexer, so there is no separate window to bring forward.";
1062
+ if (launcher.type === LAUNCHER_CMUX) {
1063
+ await pi.exec(LAUNCHER_CMUX, [
1064
+ "select-workspace",
1065
+ "--workspace",
1066
+ launcher.workspaceId
1067
+ ], { timeout: PI_EXEC_TIMEOUT_MS });
1068
+ return `Opened ${record.runKey} in cmux.`;
1069
+ }
1070
+ if (!options.environment.TMUX) return "Pi is not attached to tmux. Follow the output instead, or attach to the recorded tmux session manually.";
1071
+ const target = launcher.sessionId ?? launcher.sessionName;
1072
+ const result = await pi.exec(LAUNCHER_TMUX, [
1073
+ "switch-client",
1074
+ "-t",
1075
+ target
1076
+ ], { timeout: PI_EXEC_TIMEOUT_MS });
1077
+ if (result.code !== 0) return result.stderr.trim() || `Unable to open tmux session ${target}.`;
1078
+ if (launcher.paneId) await pi.exec(LAUNCHER_TMUX, [
1079
+ "select-pane",
1080
+ "-t",
1081
+ launcher.paneId
1082
+ ], { timeout: PI_EXEC_TIMEOUT_MS });
1083
+ return `Opened ${record.runKey} in tmux.`;
1084
+ };
1085
+ const renderFollow = async (record, ctx) => {
1086
+ if (followRefreshActive) return;
1087
+ followRefreshActive = true;
1088
+ try {
1089
+ const jobs = (0, _agimon_ai_workflow_mcp.summarizeWorkflowProgress)(progressEventsForRun(record, await (0, _agimon_ai_workflow_mcp.readWorkflowProgress)(registry.runDirectoryFor(record))));
1090
+ const lines = jobs.length > 0 ? void 0 : await cachedLauncherScreen(record);
1091
+ ctx.ui.setWidget(FOLLOW_WIDGET_KEY, (_tui, theme) => ({
1092
+ render: (width) => renderFollowLines(record, lines ?? renderProgressLines(jobs, theme, width), theme, width),
1093
+ invalidate: () => {}
1094
+ }), { placement: "aboveEditor" });
1095
+ } finally {
1096
+ followRefreshActive = false;
1097
+ }
1098
+ };
1099
+ const followRun = async (record, ctx) => {
1100
+ clearFollow(ctx);
1101
+ followedRun = record;
1102
+ await renderFollow(record, ctx);
1103
+ followTimer = setInterval(() => {
1104
+ if (followedRun) renderFollow(followedRun, ctx);
1105
+ }, followIntervalMs);
1106
+ followTimer.unref?.();
1107
+ };
1108
+ /**
1109
+ * Forward literal text to a run's terminal.
1110
+ *
1111
+ * `tmux send-keys -l` and `cmux send` both take text verbatim, which keeps
1112
+ * escape sequences intact: an arrow key must arrive as one sequence, not as
1113
+ * a bracket and a letter. A natively hosted run has no multiplexer to
1114
+ * address, so it reports rather than silently dropping the keystrokes.
1115
+ */
1116
+ const sendToRun = async (record, text) => {
1117
+ if (!terminalFacade.capabilities(record).writable) return;
1118
+ await terminalFacade.write(record, text);
1119
+ };
1120
+ /**
1121
+ * Show a run over Pi: job tree, live output, and a keyboard that reaches it.
1122
+ *
1123
+ * Blocks until the user closes it, which is what makes it feel like the run
1124
+ * rather than a report about the run. The refresh runs faster than the
1125
+ * background widget because a foregrounded terminal that lags behind your
1126
+ * own typing reads as broken.
1127
+ */
1128
+ /**
1129
+ * Show a run over Pi as a persistent panel, not a modal.
1130
+ *
1131
+ * Returns as soon as the panel is up rather than awaiting its close. An
1132
+ * awaited overlay would hold the tool call open, which freezes the agent
1133
+ * mid-turn and makes "switch back to the main agent" impossible: there is
1134
+ * nothing to switch back to while it is still waiting for a tool result.
1135
+ *
1136
+ * Focus, not lifetime, is what the user toggles. The panel stays on screen
1137
+ * while typing goes back to Pi, so Escape stays free for the run's own
1138
+ * agent, which uses it to interrupt.
1139
+ */
1140
+ const openOverlay = async (record, ctx) => {
1141
+ closeOverlay();
1142
+ const interactive = terminalFacade.capabilities(record).writable;
1143
+ const input = new require_workflowOverlay.TerminalInputBatcher((text) => sendToRun(record, text));
1144
+ let jobs = [];
1145
+ let output = [];
1146
+ let refresh;
1147
+ let polling = false;
1148
+ const poll = async () => {
1149
+ jobs = (0, _agimon_ai_workflow_mcp.summarizeWorkflowProgress)(progressEventsForRun(record, await (0, _agimon_ai_workflow_mcp.readWorkflowProgress)(registry.runDirectoryFor(record))));
1150
+ output = await cachedLauncherScreen(record);
1151
+ };
1152
+ await poll();
1153
+ const teardown = () => {
1154
+ if (refresh) clearInterval(refresh);
1155
+ refresh = void 0;
1156
+ input.dispose();
1157
+ };
1158
+ const entry = {
1159
+ interactive,
1160
+ runKey: record.runKey,
1161
+ teardown
1162
+ };
1163
+ activeOverlay = entry;
1164
+ /** Shared by the panel footer, the close chord, and the double-Escape failsafe. */
1165
+ const closeWithNotice = () => {
1166
+ closeOverlay();
1167
+ ctx.ui.notify(`Closed the ${record.runKey} view. The run keeps going.`, "info");
1168
+ };
1169
+ ctx.ui.custom((tui, theme, _keybindings, done) => {
1170
+ refresh = setInterval(() => {
1171
+ if (polling) return;
1172
+ polling = true;
1173
+ poll().finally(() => {
1174
+ polling = false;
1175
+ tui.requestRender();
1176
+ });
1177
+ }, OVERLAY_REFRESH_MS);
1178
+ refresh.unref?.();
1179
+ return new require_workflowRunPanel.WorkflowRunPanelComponent(tui, theme, {
1180
+ runKey: record.runKey,
1181
+ label: `${GLYPH.running} ${runLabel(record)}`,
1182
+ breadcrumb: RUN_PANEL_BREADCRUMB,
1183
+ interactive,
1184
+ footer: panelHint(interactive),
1185
+ hints: panelHints(interactive),
1186
+ snapshot: (width) => ({
1187
+ progress: renderProgressLines(jobs, theme, width),
1188
+ output
1189
+ }),
1190
+ onViewport: (columns, rows) => resizeLauncherViewport(record, columns, rows),
1191
+ onUnfollow: () => {
1192
+ entry.handle?.unfocus();
1193
+ ctx.ui.notify("Typing goes to this session. The run keeps going.", "info");
1194
+ },
1195
+ onClose: closeWithNotice,
1196
+ sendInput: (data) => input.write(data),
1197
+ flushInput: () => void input.flush(),
1198
+ unfollowShortcut: UNFOLLOW_SHORTCUT,
1199
+ closeShortcut: CLOSE_OVERLAY_SHORTCUT,
1200
+ onDispose: () => {
1201
+ teardown();
1202
+ if (activeOverlay === entry) activeOverlay = void 0;
1203
+ done();
1204
+ }
1205
+ });
1206
+ }, {
1207
+ ...require_workflowRunPanel.runPanelUiOptions(interactive),
1208
+ onHandle: (handle) => {
1209
+ entry.handle = handle;
1210
+ }
1211
+ });
1212
+ ctx.ui.notify(interactive ? `${record.runKey}: typing goes to the run. Press Esc twice to close, ${shortcutLabel(UNFOLLOW_SHORTCUT)} to type here instead.` : `${record.runKey}: view only, because this run is hosted natively. ${shortcutLabel(CLOSE_OVERLAY_SHORTCUT)} closes it.`, "info");
1213
+ if (!interactive) return [
1214
+ `Showing ${record.runKey} over Pi, as a view only:`,
1215
+ "the run is hosted natively rather than in a multiplexer, so there is no terminal to type into.",
1216
+ `Typing stays with this session, and ${shortcutLabel(CLOSE_OVERLAY_SHORTCUT)} closes the view.`
1217
+ ].join(" ");
1218
+ return [
1219
+ `Showing ${record.runKey} over Pi. Typing goes to the run.`,
1220
+ "Pressing Escape twice quickly closes the view, and a single Escape still reaches the run.",
1221
+ `${shortcutLabel(UNFOLLOW_SHORTCUT)} moves typing between the run and this session,`,
1222
+ `and ${shortcutLabel(CLOSE_OVERLAY_SHORTCUT)} closes the view. The run keeps going either way.`
1223
+ ].join(" ");
1224
+ };
1225
+ const openInspector = async (ctx, manage = false) => {
1226
+ if (inspectorOpen || activeOverlay || followedRun) return;
1227
+ inspectorOpen = true;
1228
+ try {
1229
+ const selection = await ctx.ui.custom((tui, theme, _keybindings, done) => new require_workflowInspector.WorkflowInspectorComponent(tui, theme, {
1230
+ list: async () => {
1231
+ const sessionId = currentSessionId();
1232
+ const records = (await listAllRuns(registry)).filter((record) => record.stage === RUNNING_STATUS && !record.stale && require_index$2.isSessionRun(record, sessionId));
1233
+ inspectorRecords = new Map(records.map((record) => [runIdentity(record), record]));
1234
+ return Promise.all(records.map(async (record) => ({
1235
+ displayName: record.displayName,
1236
+ executionState: record.executionState,
1237
+ jobs: (0, _agimon_ai_workflow_mcp.summarizeWorkflowProgress)(progressEventsForRun(record, await (0, _agimon_ai_workflow_mcp.readWorkflowProgress)(registry.runDirectoryFor(record)))),
1238
+ key: runIdentity(record),
1239
+ output: [],
1240
+ runKey: record.runKey,
1241
+ startedAt: record.startedAt,
1242
+ workspace: record.workspace
1243
+ })));
1244
+ },
1245
+ output: async (item) => {
1246
+ const record = inspectorRecords.get(item.key);
1247
+ return record ? cachedLauncherScreen(record) : ["Workflow is no longer active."];
1248
+ }
1249
+ }, done), {
1250
+ ...DOOM_FULLSCREEN_UI_OPTIONS,
1251
+ onHandle: (handle) => {
1252
+ inspectorHandle = handle;
1253
+ }
1254
+ });
1255
+ if (selection) {
1256
+ const record = await requireRun(selection);
1257
+ if (manage) await manageWorkflow(record, ctx);
1258
+ else await openOverlay(record, ctx);
1259
+ }
1260
+ } finally {
1261
+ inspectorHandle = void 0;
1262
+ inspectorOpen = false;
1263
+ inspectorRecords.clear();
1264
+ }
1265
+ };
1266
+ /**
1267
+ * Resolve a run this session owns, erroring rather than picking blindly.
1268
+ *
1269
+ * A run another session launched is reported exactly like one that does not
1270
+ * exist. Distinguishing them would leak the shared registry's contents into
1271
+ * a session that has no way to act on them anyway, and would invite the
1272
+ * agent to keep trying keys it can never use.
1273
+ */
1274
+ const requireRun = async (input, sessionId = currentSessionId()) => {
1275
+ observeSession(sessionId);
1276
+ const matches = (await listAllRuns(registry, input.workspace)).filter((candidate) => candidate.runKey === input.runKey && require_index$2.isSessionRun(candidate, sessionId));
1277
+ if (matches.length > 1) throw new Error(require_index$2.withOptions(`Workflow run key ${JSON.stringify(input.runKey)} exists in more than one workspace in this session.`, ["Retry with the workspace from the run identity."]));
1278
+ const record = matches[0];
1279
+ if (!record) throw new Error(require_index$2.withOptions(`No workflow run matches ${JSON.stringify(input.runKey)} in this session.`, ["workflow_run with action status: use a run key from this session before retrying.", "Tell the user this session did not launch that run rather than guessing a similar key. A run launched by another Pi session, or from the command line, is managed there or with the workflow-mcp CLI."]));
1280
+ return record;
1281
+ };
1282
+ /**
1283
+ * Resolve a failed run that this session may adopt through recovery.
1284
+ *
1285
+ * Failed runs are terminal and have no live owner to conflict with. This is
1286
+ * intentionally the only cross-session lookup. Recovery-specific status
1287
+ * and durable evidence reads may use it; live output, lifecycle controls,
1288
+ * and running-run UI stay scoped to the launching session.
1289
+ */
1290
+ const requireRecoverableRun = async (input) => {
1291
+ const matches = (await listAllRuns(registry, input.workspace)).filter((candidate) => candidate.runKey === input.runKey && candidate.stage === ERROR_STAGE);
1292
+ if (matches.length > 1) throw new Error(require_index$2.withOptions(`Failed workflow run key ${JSON.stringify(input.runKey)} exists in more than one workspace.`, ["Retry with the workspace from the recovery picker or run identity."]));
1293
+ const record = matches[0];
1294
+ if (!record) throw new Error(require_index$2.withOptions(`No failed workflow run matches ${JSON.stringify(input.runKey)}.`, ["Use the recovery picker to select an interrupted or failed run, or retry with its workspace.", "Do not recover a running or completed workflow."]));
1295
+ return record;
1296
+ };
1297
+ const readRecoveryEvidenceTail = async (record) => {
1298
+ const runDir = registry.runDirectoryFor(record);
1299
+ const paths = [
1300
+ (0, node_path.resolve)(runDir, "changelog.md"),
1301
+ (0, node_path.resolve)(runDir, "context.md"),
1302
+ (0, node_path.resolve)(runDir, "progress.ndjson")
1303
+ ];
1304
+ const sections = [];
1305
+ for (const path of paths) try {
1306
+ const content = await (0, node_fs_promises.readFile)(path, "utf-8");
1307
+ sections.push(`--- ${(0, node_path.basename)(path)} ---`, content.slice(-65536).trimEnd());
1308
+ } catch (error) {
1309
+ if (error.code !== "ENOENT") throw error;
1310
+ }
1311
+ return sections.length > 0 ? sections : ["No durable recovery evidence files are recorded for this run."];
1312
+ };
1313
+ /** Read-only evidence access for the one globally adoptable record class. */
1314
+ const readRecoverableRunEvidence = async (input, ctx) => {
1315
+ const record = await requireRecoverableRun(input);
1316
+ await getTelemetry(ctx).recordEvent("doom_workflow.recovery_evidence_requested", { outcome: "requested" });
1317
+ return [
1318
+ `Workflow: ${runLabel(record)}`,
1319
+ "--- run.json ---",
1320
+ JSON.stringify(record, null, 2),
1321
+ ...await readRecoveryEvidenceTail(record)
1322
+ ].join("\n");
1323
+ };
1324
+ /**
1325
+ * Recover a run in a launcher of its own instead of replaying it here.
1326
+ *
1327
+ * `RecoverWorkflowService` replays with the launch step skipped, so an
1328
+ * in-process recovery runs every job inside this process. A workflow whose
1329
+ * steps are `interactiveRun` then has its agent spawned with inherited
1330
+ * stdio, because that agent needs a real TTY to draw. Inside Pi that TTY is
1331
+ * the TUI: the agent draws over it, and the reset sequence written when the
1332
+ * step ends is one Pi never issued and cannot repaint from. Delegating
1333
+ * costs a process and keeps the screen.
1334
+ *
1335
+ * Undefined leaves the caller on the in-process path: a workflow that
1336
+ * declares no `launch-command` has no launcher to delegate to, and a
1337
+ * definition that cannot be read is better diagnosed by the replay itself.
1338
+ */
1339
+ const delegateRecovery = async (input, ctx) => {
1340
+ const cli = resolveWorkflowCli();
1341
+ if (!cli) return void 0;
1342
+ const record = await requireRecoverableRun(input);
1343
+ let workflow;
1344
+ try {
1345
+ workflow = parser.parseWorkflowFile(record.workflowPath);
1346
+ } catch {
1347
+ return;
1348
+ }
1349
+ if (!workflow["launch-command"]) return void 0;
1350
+ const sessionId = (0, _agimon_ai_doompi_core_child_process.resolveRootSessionId)(ctx.sessionManager.getSessionId(), options.environment);
1351
+ const claim = await registry.claimRunRecovery(record.workspace, record.runKey, sessionId);
1352
+ const cwd = record.originalRepoPath ?? ctx.cwd;
1353
+ const recoverCommand = shellCommand([
1354
+ process.execPath,
1355
+ cli,
1356
+ RECOVER_COMMAND,
1357
+ record.runKey,
1358
+ "--workspace",
1359
+ record.workspace,
1360
+ "--recovery-claim",
1361
+ claim.claimId,
1362
+ ...input.runner ? ["--runner", input.runner] : []
1363
+ ]);
1364
+ const launcher = preferredLauncher(options.environment);
1365
+ try {
1366
+ await spawnDetached(process.execPath, [
1367
+ cli,
1368
+ LAUNCH_PROCESS_COMMAND,
1369
+ ...launcher ? ["--launcher", launcher] : [],
1370
+ "--name",
1371
+ `${record.displayName} recover`,
1372
+ "--cwd",
1373
+ cwd,
1374
+ "--command",
1375
+ recoverCommand
1376
+ ], {
1377
+ cwd,
1378
+ env: launcherEnvironment(options.environment)
1379
+ });
1380
+ } catch (error) {
1381
+ await registry.releaseRunRecoveryClaim(record.workspace, record.runKey, claim.claimId);
1382
+ throw error;
1383
+ }
1384
+ await getTelemetry(ctx).recordEvent("doom_workflow.recover_delegated", {
1385
+ launcher: launcher ?? "auto",
1386
+ outcome: "delegated"
1387
+ });
1388
+ return `Recovering ${record.runKey} in a launcher of its own, so this session keeps its terminal. It reports back here when it ends.`;
1389
+ };
1390
+ const manageWorkflow = async (selected, ctx) => {
1391
+ let record = selected;
1392
+ while (true) {
1393
+ const actions = ["Open output"];
1394
+ const state = record.executionState ?? RUNNING_STATUS;
1395
+ if (state === RUNNING_STATUS) actions.push("Pause");
1396
+ if (state === PAUSED_EXECUTION_STATE) actions.push("Resume");
1397
+ if (record.stage === RUNNING_STATUS) actions.push("Stop");
1398
+ actions.push("Back");
1399
+ const action = await require_workflowPrompt.openWorkflowChoice(ctx, {
1400
+ title: `Manage ${record.runKey}`,
1401
+ breadcrumb: MANAGE_BREADCRUMB,
1402
+ choices: actions
1403
+ });
1404
+ if (!action || action === "Back") return;
1405
+ record = await requireRun(record);
1406
+ if (record.stage !== RUNNING_STATUS) {
1407
+ ctx.ui.notify(`${record.runKey} is no longer running.`, "warning");
1408
+ return;
1409
+ }
1410
+ if (action === "Open output") {
1411
+ await openOverlay(record, ctx);
1412
+ return;
1413
+ }
1414
+ if (action === "Stop") {
1415
+ if (!await ctx.ui.confirm(`Stop ${record.runKey}?`, "The workflow will be interrupted and will not be restarted automatically.")) continue;
1416
+ record = await requireRun(record);
1417
+ if (record.stage !== RUNNING_STATUS) {
1418
+ ctx.ui.notify(`${record.runKey} is no longer running.`, "warning");
1419
+ return;
1420
+ }
1421
+ }
1422
+ if (action !== "Pause" && action !== "Resume" && action !== "Stop") continue;
1423
+ if (!record.runId) throw new Error(`${record.runKey} has no controllable run generation.`);
1424
+ const result = await controlTool.execute({
1425
+ action: action.toLowerCase(),
1426
+ expectedRunId: record.runId,
1427
+ ...action === "Stop" ? { reason: "Stopped from the workflow manager." } : {},
1428
+ runKey: record.runKey,
1429
+ workspace: record.workspace
1430
+ });
1431
+ if (result.isError) throw new Error(require_index$2.toolResultText(result) || `Unable to ${action.toLowerCase()} workflow.`);
1432
+ ctx.ui.notify(require_index$2.toolResultText(result) || `${action} requested for ${record.runKey}.`, "info");
1433
+ await refreshStatus(ctx, false);
1434
+ record = await requireRun(record);
1435
+ }
1436
+ };
1437
+ /**
1438
+ * The wizard's prompts, backed by doom overlays instead of Pi's native
1439
+ * widgets, so a launch reads as one framed sequence rather than three styles.
1440
+ *
1441
+ * `editor` stays with Pi: the workflow prompt is multi-line authoring, and
1442
+ * reimplementing a text editor to match a frame would be a poor trade.
1443
+ */
1444
+ const doomLauncherUi = (ctx, breadcrumb) => ({
1445
+ editor: (title, prefill) => ctx.ui.editor(title, prefill),
1446
+ input: (title, placeholder) => require_workflowPrompt.openWorkflowInput(ctx, {
1447
+ title,
1448
+ breadcrumb,
1449
+ ...placeholder ? { fallback: placeholder } : {}
1450
+ }),
1451
+ notify: (message, type) => ctx.ui.notify(message, type),
1452
+ select: (title, options) => require_workflowPrompt.openWorkflowChoice(ctx, {
1453
+ title,
1454
+ breadcrumb,
1455
+ choices: options
1456
+ })
1457
+ });
1458
+ /**
1459
+ * `SPC w l`: the repository's workflows, the cursor workflow's parsed
1460
+ * detail beside it, and `r` to launch that one. Launching closes the board
1461
+ * first - the launch prompts are host dialogs, and a fullscreen overlay
1462
+ * would sit over them.
1463
+ */
1464
+ const openWorkflowCatalog = async (ctx) => {
1465
+ if (!ctx.hasUI) {
1466
+ ctx.ui.notify("The workflow list needs an interactive Pi session.", "warning");
1467
+ return;
1468
+ }
1469
+ const entries = await require_index$5.loadWorkflowCatalog(feature.listWorkflowsTool, ctx.cwd);
1470
+ const byPath = new Map(entries.map((entry) => [entry.path, entry]));
1471
+ const launchOptions = {
1472
+ compatibleRunners,
1473
+ launch: (input, launchContext) => launchExecutor.execute(input, launchContext),
1474
+ parseWorkflow: (workflowPath) => parser.parseWorkflowFile(workflowPath),
1475
+ ui: doomLauncherUi(ctx, CATALOG_BREADCRUMB)
1476
+ };
1477
+ await require_workflowCatalog.openWorkflowCatalogOverlay(ctx, entries.map((entry) => ({
1478
+ key: entry.path,
1479
+ name: entry.name,
1480
+ relativePath: entry.relativePath,
1481
+ description: entry.description,
1482
+ tags: entry.tags
1483
+ })), {
1484
+ loadDetail: (row) => require_index$5.summarizeWorkflowFile(row.key, launchOptions),
1485
+ launchWorkflow: (row) => {
1486
+ const entry = byPath.get(row.key);
1487
+ if (!entry) return;
1488
+ require_index$5.launchWorkflowEntry(launchOptions, entry, ctx).then((result) => {
1489
+ if (result) ctx.ui.notify(require_index$2.launchNotice(require_index$2.toolResultText(result)), "info");
1490
+ }, (error) => {
1491
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1492
+ });
1493
+ }
1494
+ });
1495
+ };
1496
+ /**
1497
+ * `/workflow-launch`: the same launch the catalog board performs, as a line.
1498
+ *
1499
+ * The cockpit can only send a session a prompt frame, so this is how a
1500
+ * browser starts a workflow; typing it by hand does the same thing. Every
1501
+ * value the board would have asked for in a dialog arrives on the line, and
1502
+ * what the workflow requires is checked before anything is started, because
1503
+ * a run missing its prompt starts and then waits for terminal input that is
1504
+ * never coming.
1505
+ */
1506
+ const launchFromCommand = async (args, ctx) => {
1507
+ const parsed = require_index.parseWorkflowLaunchCommand(args);
1508
+ if (require_index.isLaunchParseFailure(parsed)) {
1509
+ ctx.ui.notify(parsed.error, "error");
1510
+ return;
1511
+ }
1512
+ const entries = await require_index$5.loadWorkflowCatalog(feature.listWorkflowsTool, ctx.cwd);
1513
+ const entry = require_index.resolveWorkflowEntry(entries, parsed.workflow);
1514
+ if (!entry) {
1515
+ ctx.ui.notify(`No workflow matches '${parsed.workflow}' under ${ctx.cwd}.`, "error");
1516
+ return;
1517
+ }
1518
+ const detail = require_index$5.summarizeWorkflowFile(entry.path, {
1519
+ compatibleRunners,
1520
+ parseWorkflow: (workflowPath) => parser.parseWorkflowFile(workflowPath)
1521
+ });
1522
+ if (detail.error !== void 0) {
1523
+ ctx.ui.notify(`${entry.name} could not be read: ${detail.error}`, "error");
1524
+ return;
1525
+ }
1526
+ const problems = require_index.validateWorkflowLaunch(detail, parsed);
1527
+ if (problems.length > 0) {
1528
+ ctx.ui.notify(problems.join(" "), "error");
1529
+ return;
1530
+ }
1531
+ const workflow = parser.parseWorkflowFile(entry.path);
1532
+ const input = {
1533
+ workflowPath: entry.path,
1534
+ ...parsed.runner === void 0 ? {} : { runner: parsed.runner },
1535
+ ...typeof workflow.workspace === "string" ? { workspace: workflow.workspace } : {},
1536
+ ...parsed.prompt === void 0 ? {} : { prompt: parsed.prompt },
1537
+ ...Object.keys(parsed.inputs).length === 0 ? {} : { inputs: parsed.inputs }
1538
+ };
1539
+ const result = await launchExecutor.execute(input, ctx);
1540
+ ctx.ui.notify(require_index$2.launchNotice(require_index$2.toolResultText(result)), "info");
1541
+ };
1542
+ const commands = [[WORKFLOW_LAUNCH_COMMAND, {
1543
+ description: "Launch a workflow: /workflow-launch <workflow> [runner=x] [key=value …] [prompt]",
1544
+ handler: async (args, ctx) => {
1545
+ await waitForReadiness();
1546
+ try {
1547
+ await launchFromCommand(args, ctx);
1548
+ } catch (error) {
1549
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1550
+ }
1551
+ }
1552
+ }]];
1553
+ const openRecoveryHandoff = async (ctx) => {
1554
+ const records = (await listAllRuns(registry)).filter((record) => record.stage === ERROR_STAGE);
1555
+ if (records.length === 0) {
1556
+ ctx.ui.notify("No failed workflows are available to recover.", "info");
1557
+ return;
1558
+ }
1559
+ const chosen = await require_workflowPicker.openWorkflowPickerOverlay(ctx, {
1560
+ title: "WORKFLOW RECOVERY",
1561
+ breadcrumb: RECOVER_BREADCRUMB,
1562
+ unit: "failed runs",
1563
+ action: "recover",
1564
+ filterPlaceholder: "type to filter by name, run key or failed job",
1565
+ emptyMessage: "No failed workflows are available to recover."
1566
+ }, records.map((record) => ({
1567
+ key: `${record.workspace}/${record.runKey}`,
1568
+ name: record.displayName,
1569
+ detail: `${record.workspace}/${record.runKey}${record.failedJob ? ` · ${record.failedJob}` : ""}`,
1570
+ ...record.failedJob ? { search: record.failedJob } : {},
1571
+ value: record
1572
+ })));
1573
+ if (!chosen) return;
1574
+ const record = await requireRecoverableRun(chosen);
1575
+ if (record.stage !== ERROR_STAGE) throw new Error(`${record.runKey} is no longer failed.`);
1576
+ const skillFile = (0, node_path.resolve)(require_index$1.workflowSkillDirectory(), "workflow-recovery", "SKILL.md");
1577
+ if (!(0, node_fs.existsSync)(skillFile)) throw new Error(`Workflow recovery skill is unavailable at ${skillFile}.`);
1578
+ setWorkflowMode(true);
1579
+ ctx.ui.notify("Workflow tools enabled. The agent will investigate this failure and choose the safe next step.", "info");
1580
+ pi.sendUserMessage([
1581
+ "Investigate recovery for this failed workflow run. This selection is permission to investigate, not permission to recover blindly.",
1582
+ `Run identity: ${JSON.stringify({
1583
+ runKey: record.runKey,
1584
+ workspace: record.workspace
1585
+ })}`,
1586
+ `Read the package-owned recovery skill with the read tool at ${JSON.stringify(skillFile)} before acting.`,
1587
+ "Inspect status, every relevant log source, process state, the staged run.json, and only the repair referenced by activeRepairId.",
1588
+ "Choose exactly one outcome: recover, launch fresh, or defer. Never edit issue.md or repair.json, never scan historical repairs, and never pass recovery dryRun or a job override.",
1589
+ "Do not recover terminal, approval-pending, blocked, or otherwise unsafe work. If evidence is incomplete, defer and explain what is missing.",
1590
+ "If recovery is justified, preserve the recorded worktree and repair metadata, then verify real process and registry progress before reporting success."
1591
+ ].join("\n"), { deliverAs: "followUp" });
1592
+ };
1593
+ /**
1594
+ * Turn workflow mode on or off.
1595
+ *
1596
+ * The tools are registered for the whole session, so the mode is a tool
1597
+ * surface restriction rather than a registration: dropping it is what makes
1598
+ * them visible again, and no other owner's tools are touched either way.
1599
+ *
1600
+ * Activating also injects each tool's `promptSnippet` and `promptGuidelines`
1601
+ * into the system prompt, and deactivating strips them: in Pi, tool gating
1602
+ * and instruction injection are the same lever.
1603
+ */
1604
+ const setWorkflowMode = (enabled) => {
1605
+ if (!isRuntimeActive()) return;
1606
+ workflowMode = enabled;
1607
+ restrictionListeners.forEach((listener) => listener());
1608
+ publishModeWidget();
1609
+ options.onModeChange?.(enabled);
1610
+ };
1611
+ /**
1612
+ * What the user sees on every mode change.
1613
+ *
1614
+ * Leads with the state, then the one thing they can do next. Turning the
1615
+ * mode on is useless without knowing that you now just ask in plain
1616
+ * language, so that sentence earns its place; the tool count does not, and
1617
+ * is left to the README.
1618
+ */
1619
+ const modeSummary = () => workflowMode ? [
1620
+ `${GLYPH.completed} Workflow mode on`,
1621
+ "Ask for workflows in plain language: the agent can list, launch, inspect, pause, resume, stop, and recover runs.",
1622
+ `${shortcutLabel(UNFOLLOW_SHORTCUT)} stops following a run without stopping it.`
1623
+ ].join("\n") : ["Workflow mode off", `Press ${LEADER_MODE_BREADCRUMB} to give the agent its workflow tools. Runs already going are unaffected.`].join("\n");
1624
+ /**
1625
+ * Toggle the mode from the leader menu.
1626
+ *
1627
+ * Replaces the `/workflow [on|off|status]` command: the mode line already
1628
+ * answers `status` at a glance, and the other two arguments were a keyboard
1629
+ * path to a switch the leader menu spells out. Plan mode is reached the same
1630
+ * way, so the two modes are no longer toggled through different surfaces.
1631
+ */
1632
+ const applyWorkflowMode = async (enabled, ctx) => {
1633
+ setWorkflowMode(enabled);
1634
+ if (!workflowMode) clearFollow(ctx);
1635
+ ctx.ui.notify(modeSummary(), "info");
1636
+ await refreshStatus(ctx, false);
1637
+ };
1638
+ const onResourcesDiscover = () => {
1639
+ if (!workflowMode) return { skillPaths: [] };
1640
+ const skillPath = require_index$1.workflowSkillDirectory();
1641
+ return { skillPaths: (0, node_fs.existsSync)(skillPath) ? [skillPath] : [] };
1642
+ };
1643
+ const messageRenderers = [require_workflowFinishedMessage.createWorkflowFinishedRenderer(), [LEGACY_WORKFLOW_STEP_MESSAGE, (message, { outputPad }, theme) => {
1644
+ const details = require_workflowStepMessage.isWorkflowStepMessageDetails(message.details) ? message.details : void 0;
1645
+ const content = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
1646
+ return require_workflowStepMessage.renderWorkflowStepMessage(details, content, outputPad, theme);
1647
+ }]];
1648
+ const toolDependencies = {
1649
+ environment: options.environment,
1650
+ runTool,
1651
+ recoverTool,
1652
+ launchExecutor,
1653
+ controlTool,
1654
+ requireSessionRun: requireRun,
1655
+ requireRecoverableRun,
1656
+ readRecoveryEvidence: readRecoverableRunEvidence,
1657
+ delegateRecovery,
1658
+ trackPendingRun,
1659
+ observeSession,
1660
+ activeRunCount: async () => sessionRunCount,
1661
+ onLaunch: async (ctx) => {
1662
+ await getTelemetry(ctx).recordEvent(LAUNCH_REQUESTED_EVENT, { outcome: "requested" });
1663
+ return refreshStatus(ctx, false);
1664
+ },
1665
+ followRun: async (input, ctx) => {
1666
+ const record = await requireRun(input, ctx.sessionManager.getSessionId());
1667
+ await getTelemetry(ctx).recordEvent("doom_workflow.follow_started", { outcome: "started" });
1668
+ await followRun(record, ctx);
1669
+ return `Following ${runLabel(record)}. Press ${shortcutLabel(UNFOLLOW_SHORTCUT)} to stop following; the run keeps going.`;
1670
+ },
1671
+ tailRun: async (input, ctx) => {
1672
+ const record = await requireRun(input, ctx.sessionManager.getSessionId());
1673
+ await getTelemetry(ctx).recordEvent("doom_workflow.tail_requested", { outcome: "requested" });
1674
+ return [
1675
+ `Workflow: ${runLabel(record)}`,
1676
+ `Stage: ${stageLabel(record)}`,
1677
+ "Raw launcher output is hidden from chat. Use action follow or open to view it."
1678
+ ].join("\n");
1679
+ },
1680
+ openRun: async (input, ctx) => {
1681
+ const record = await requireRun(input, ctx.sessionManager.getSessionId());
1682
+ await getTelemetry(ctx).recordEvent("doom_workflow.open_requested", { outcome: "requested" });
1683
+ if (!ctx.hasUI) return openLauncher(record);
1684
+ return openOverlay(record, ctx);
1685
+ },
1686
+ rejectRunner
1687
+ };
1688
+ let disposeLeaderActions;
1689
+ services.push((cordis) => {
1690
+ cordis.inject([_agimon_ai_doompi_core_ui_hub.DOOM_UI_HUB_SERVICE], (uiContext) => {
1691
+ const dispose = (0, _agimon_ai_doompi_core_ui_hub.requireDoomUiHub)(uiContext).registerLeaderActions({
1692
+ source: require_workflow.PACKAGE_SOURCE,
1693
+ handlers: {
1694
+ [require_workflow.LEADER_ENABLE_ACTION]: async (ctx) => {
1695
+ await waitForReadiness();
1696
+ return applyWorkflowMode(true, ctx);
1697
+ },
1698
+ [require_workflow.LEADER_DISABLE_ACTION]: async (ctx) => {
1699
+ await waitForReadiness();
1700
+ return applyWorkflowMode(false, ctx);
1701
+ },
1702
+ [require_workflow.LEADER_MANAGE_ACTION]: async (ctx) => {
1703
+ await waitForReadiness();
1704
+ return openInspector(ctx, true);
1705
+ },
1706
+ [require_workflow.LEADER_CATALOG_ACTION]: async (ctx) => {
1707
+ await waitForReadiness();
1708
+ return openWorkflowCatalog(ctx);
1709
+ },
1710
+ [require_workflow.LEADER_RECOVER_ACTION]: async (ctx) => {
1711
+ await waitForReadiness();
1712
+ return openRecoveryHandoff(ctx);
1713
+ }
1714
+ },
1715
+ onError: (error, _actionName, ctx) => {
1716
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
1717
+ }
1718
+ });
1719
+ disposeLeaderActions = dispose;
1720
+ return () => {
1721
+ dispose();
1722
+ if (disposeLeaderActions === dispose) disposeLeaderActions = void 0;
1723
+ };
1724
+ });
1725
+ });
1726
+ const onSessionStart = (_event, ctx) => {
1727
+ if (!runtimeActive || !invocationActive()) return;
1728
+ const generation = ++sessionGeneration;
1729
+ const previousReadiness = readinessHandle;
1730
+ readinessAbort?.abort();
1731
+ const ownReadinessAbort = new AbortController();
1732
+ readinessAbort = ownReadinessAbort;
1733
+ sessionCtx = ctx;
1734
+ const ownsSession = () => runtimeActive && generation === sessionGeneration && sessionCtx === ctx && invocationActive();
1735
+ const retireSessionResources = () => {
1736
+ stopRunControl();
1737
+ if (monitorTimer) clearInterval(monitorTimer);
1738
+ monitorTimer = void 0;
1739
+ runProvider?.update([]);
1740
+ clearFollow(ctx);
1741
+ inspectorHandle?.hide();
1742
+ inspectorHandle = void 0;
1743
+ inspectorOpen = false;
1744
+ inspectorRecords.clear();
1745
+ closeOverlay();
1746
+ terminals.forget(/* @__PURE__ */ new Set());
1747
+ progressOverlay.setUICtx(ctx.ui);
1748
+ };
1749
+ if (options.runCleanup) options.runCleanup(retireSessionResources);
1750
+ else retireSessionResources();
1751
+ getTelemetry(ctx).recordEvent("doom_workflow.session_started", {
1752
+ mode: options.environment.WORKFLOW_MCP_MODE === "on" ? "on" : "off",
1753
+ outcome: "started"
1754
+ });
1755
+ const sessionId = ctx.sessionManager.getSessionId();
1756
+ const rootSessionId = (0, _agimon_ai_doompi_core_child_process.resolveRootSessionId)(sessionId, options.environment);
1757
+ observeSession(rootSessionId);
1758
+ sessionStartedAt = Date.now();
1759
+ lifecycleNarrationReady = false;
1760
+ previousStages = /* @__PURE__ */ new Map();
1761
+ pendingTerminalRuns.clear();
1762
+ deliveredTerminalRuns.clear();
1763
+ setWorkflowMode(options.initialMode ?? options.environment.WORKFLOW_MCP_MODE === "on");
1764
+ const coordinator = readinessCoordinator(ctx);
1765
+ readinessHandle = (async () => {
1766
+ if (previousReadiness) await Promise.allSettled([previousReadiness.then((handle) => handle.wait())]);
1767
+ if (!ownsSession()) throw new Error("Workflow session initialization was superseded.");
1768
+ return coordinator.start(require_workflow.PACKAGE_SOURCE, `${runtimeGeneration}:${generation}`, async (signal) => {
1769
+ const retired = () => ownReadinessAbort.signal.aborted || !ownsSession();
1770
+ signal.throwIfAborted();
1771
+ if (retired()) return { value: void 0 };
1772
+ await startRunControl(ctx);
1773
+ signal.throwIfAborted();
1774
+ if (retired()) return { value: void 0 };
1775
+ await refreshStatus(ctx, false);
1776
+ signal.throwIfAborted();
1777
+ if (retired()) return { value: void 0 };
1778
+ lifecycleNarrationReady = true;
1779
+ monitorTimer = setInterval(() => {
1780
+ if (ownsSession()) refreshStatus(ctx, true);
1781
+ }, monitorIntervalMs);
1782
+ monitorTimer.unref?.();
1783
+ return { value: void 0 };
1784
+ });
1785
+ })();
1786
+ Promise.allSettled([readinessHandle.then((handle) => handle.wait())]);
1787
+ };
1788
+ const runCleanup = async (label, cleanup) => {
1789
+ try {
1790
+ await cleanup();
1791
+ } catch (error) {
1792
+ process.emitWarning(`Doom-workflow cleanup could not ${label}: ${String(error)}`);
1793
+ }
1794
+ };
1795
+ let disposal;
1796
+ const dispose = (context = sessionCtx) => {
1797
+ disposal ??= (async () => {
1798
+ if (!runtimeActive) return;
1799
+ runtimeActive = false;
1800
+ sessionGeneration += 1;
1801
+ const pendingReadiness = readinessHandle;
1802
+ readinessHandle = void 0;
1803
+ readinessAbort?.abort();
1804
+ readinessAbort = void 0;
1805
+ await runCleanup("stop the workflow observer", stopRunControl);
1806
+ const ownedReadiness = standaloneReadiness;
1807
+ standaloneReadiness = void 0;
1808
+ if (ownedReadiness) await runCleanup("dispose standalone workflow readiness", () => ownedReadiness.dispose());
1809
+ else if (pendingReadiness) await Promise.allSettled([pendingReadiness.then((handle) => handle.wait())]);
1810
+ if (pendingInlineRuns.size > 0) {
1811
+ for (const service of [runService, recoverService]) await runCleanup("interrupt an inline workflow", () => service.interrupt("SIGTERM", {
1812
+ phase: "workflow",
1813
+ source: "pi-session-shutdown"
1814
+ }));
1815
+ await Promise.race([Promise.allSettled(pendingInlineRuns), new Promise((settle) => setTimeout(settle, SHUTDOWN_FINALIZE_TIMEOUT_MS).unref?.())]);
1816
+ }
1817
+ if (monitorTimer) clearInterval(monitorTimer);
1818
+ monitorTimer = void 0;
1819
+ if (statusRefreshTimer) clearTimeout(statusRefreshTimer);
1820
+ statusRefreshTimer = void 0;
1821
+ const provider = runProvider;
1822
+ runProvider = void 0;
1823
+ if (provider) await runCleanup("unregister the background-work provider", () => provider.dispose());
1824
+ lifecycleNarrationReady = false;
1825
+ inspectorHandle?.hide();
1826
+ inspectorHandle = void 0;
1827
+ inspectorOpen = false;
1828
+ inspectorRecords.clear();
1829
+ terminals.forget(/* @__PURE__ */ new Set());
1830
+ closeOverlay();
1831
+ sessionRunCount = 0;
1832
+ if (context) clearFollow(context);
1833
+ else if (followTimer) clearInterval(followTimer);
1834
+ followTimer = void 0;
1835
+ followedRun = void 0;
1836
+ progressOverlay.dispose();
1837
+ workflowMode = false;
1838
+ publishedWorkflowState = void 0;
1839
+ const mode = modeContribution;
1840
+ if (mode) {
1841
+ await runCleanup("publish the inactive workflow mode", () => mode.publish());
1842
+ await runCleanup("dispose the workflow mode owner", () => mode.detach());
1843
+ }
1844
+ const leaderActions = disposeLeaderActions;
1845
+ disposeLeaderActions = void 0;
1846
+ if (leaderActions) await runCleanup("dispose workflow leader actions", leaderActions);
1847
+ const pendingCount = pendingInlineRuns.size;
1848
+ const activeTelemetry = telemetry;
1849
+ telemetry = void 0;
1850
+ if (activeTelemetry) {
1851
+ await runCleanup("record the workflow session finish", () => activeTelemetry.recordEvent("doom_workflow.session_finished", {
1852
+ "workflow.pending_count": pendingCount,
1853
+ outcome: pendingCount === 0 ? "completed" : "interrupted"
1854
+ }));
1855
+ await runCleanup("stop workflow telemetry", () => activeTelemetry.shutdown());
1856
+ }
1857
+ pendingInlineRuns.clear();
1858
+ pendingTerminalRuns.clear();
1859
+ deliveredTerminalRuns.clear();
1860
+ announcedRuns.length = 0;
1861
+ knownSessionId = void 0;
1862
+ sessionCtx = void 0;
1863
+ })();
1864
+ return disposal;
1865
+ };
1866
+ return {
1867
+ dispose,
1868
+ commands,
1869
+ messageRenderers,
1870
+ toolDependencies,
1871
+ waitForReadiness,
1872
+ services: [...services ?? [], (0, _agimon_ai_doompi_minor_mode.piMinorModes)([modeContribution])],
1873
+ shortcuts: [[UNFOLLOW_SHORTCUT, {
1874
+ description: "Open workflows or move typing between a workflow run and this session",
1875
+ handler: async (ctx) => {
1876
+ await waitForReadiness();
1877
+ const handle = activeOverlay?.handle;
1878
+ if (handle) {
1879
+ if (!activeOverlay?.interactive) {
1880
+ ctx.ui.notify("This run is hosted natively, so the view takes no typing. Your keys stay here.", "info");
1881
+ return;
1882
+ }
1883
+ if (handle.isFocused()) {
1884
+ handle.unfocus();
1885
+ ctx.ui.notify("Typing goes to this session. The run keeps going.", "info");
1886
+ } else {
1887
+ handle.focus();
1888
+ ctx.ui.notify(`Typing goes to ${activeOverlay?.runKey}.`, "info");
1889
+ }
1890
+ return;
1891
+ }
1892
+ if (followedRun) {
1893
+ clearFollow(ctx);
1894
+ ctx.ui.notify("Workflow continues in the background.", "info");
1895
+ return;
1896
+ }
1897
+ await openInspector(ctx);
1898
+ }
1899
+ }], [CLOSE_OVERLAY_SHORTCUT, {
1900
+ description: "Close the workflow run view",
1901
+ handler: async (ctx) => {
1902
+ await waitForReadiness();
1903
+ if (!activeOverlay) return;
1904
+ const runKey = activeOverlay.runKey;
1905
+ closeOverlay();
1906
+ ctx.ui.notify(`Closed the ${runKey} view. The run keeps going.`, "info");
1907
+ }
1908
+ }]],
1909
+ events: {
1910
+ session_start: onSessionStart,
1911
+ resources_discover: onResourcesDiscover
1912
+ },
1913
+ toolRestrictions: [{
1914
+ source: require_workflow.PACKAGE_SOURCE,
1915
+ get restrict() {
1916
+ return require_index$3.workflowToolRestriction(workflowMode);
1917
+ },
1918
+ subscribe(listener) {
1919
+ restrictionListeners.add(listener);
1920
+ return () => {
1921
+ restrictionListeners.delete(listener);
1922
+ };
1923
+ }
1924
+ }]
1925
+ };
1926
+ }
1927
+ //#endregion
1928
+ exports.compatibleRunners = compatibleRunners;
1929
+ exports.createWorkflowPiRuntime = createWorkflowPiRuntime;
1930
+ exports.panelHint = panelHint;
1931
+ exports.panelHints = panelHints;
1932
+ exports.shortcutLabel = shortcutLabel;
1933
+
1934
+ //# sourceMappingURL=workflowRuntime.cjs.map