@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,3219 @@
1
+ import { apiResponse, createApiClient, defineActivityGroup, defineApiRoutes, defineChannelFile, defineContextAction, defineFill, defineLeaderBinding, defineMinorModeFile, defineSessionStore, defineToolRenderer, defineWebPlugin, sessionApiAddress } from "@agimon-ai/doompi-core/web";
2
+ import { AnsiLine, Badge, Button, ChevronDownIcon, ChevronRightIcon, CloseIcon, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Dot, EmptyState, Input, Kbd, Markdown, MessageItem, MessageItemBody, MessageItemHeader, MessageLines, OptionLabel, OptionRow, Popover, PopoverContent, PopoverFooter, PopoverHeader, PopoverTrigger, SearchIcon, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StatusBadge, StreamCursor, Textarea, cn, toolTone } from "@agimon-ai/doompi-web-components";
3
+ import { useStore } from "@tanstack/react-store";
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { sealedTransport } from "@agimon-ai/doompi-web-security/browser";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/types/webWorkflows.ts
8
+ const WORKFLOW_RUNS_TYPE = "workflow_runs";
9
+ const WORKFLOW_CATALOG_TYPE = "workflow_catalog";
10
+ //#endregion
11
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/catalogStore.ts
12
+ const catalog = defineSessionStore({
13
+ cwd: "",
14
+ workflows: [],
15
+ warning: void 0,
16
+ open: false,
17
+ filter: "",
18
+ prompt: "",
19
+ selected: void 0,
20
+ inspected: void 0,
21
+ launch: void 0
22
+ });
23
+ function isRecord$2(value) {
24
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25
+ }
26
+ function openCatalog(sessionId, prompt = "") {
27
+ catalog.update(sessionId, (current) => ({
28
+ ...current,
29
+ open: true,
30
+ prompt
31
+ }));
32
+ }
33
+ /** Opens the Workflow picker with context supplied by another installed plugin. */
34
+ function openWorkflowCatalogForContext(context, tab) {
35
+ if (context.sessionId === null) return;
36
+ openCatalog(context.sessionId, context.item.content);
37
+ context.openTransientTab(tab());
38
+ }
39
+ function closeCatalog(sessionId) {
40
+ catalog.update(sessionId, (current) => ({
41
+ ...current,
42
+ open: false,
43
+ prompt: "",
44
+ launch: void 0
45
+ }));
46
+ }
47
+ function setCatalogFilter(sessionId, filter) {
48
+ catalog.update(sessionId, (current) => ({
49
+ ...current,
50
+ filter
51
+ }));
52
+ }
53
+ function selectWorkflow(sessionId, path) {
54
+ catalog.update(sessionId, (current) => ({
55
+ ...current,
56
+ selected: path
57
+ }));
58
+ }
59
+ function toggleInspect(sessionId, path) {
60
+ catalog.update(sessionId, (current) => ({
61
+ ...current,
62
+ inspected: current.inspected === path ? void 0 : path
63
+ }));
64
+ }
65
+ function openLaunch(sessionId, path) {
66
+ catalog.update(sessionId, (current) => ({
67
+ ...current,
68
+ selected: path,
69
+ launch: path
70
+ }));
71
+ }
72
+ function closeLaunch(sessionId) {
73
+ catalog.update(sessionId, (current) => ({
74
+ ...current,
75
+ launch: void 0
76
+ }));
77
+ }
78
+ /** Rows whose name, path, tag or job matches the filter, case-insensitively. */
79
+ function filterCatalog(workflows, filter) {
80
+ const needle = filter.trim().toLowerCase();
81
+ if (needle === "") return [...workflows];
82
+ return workflows.filter((workflow) => [
83
+ workflow.name,
84
+ workflow.relativePath,
85
+ workflow.description,
86
+ ...workflow.tags,
87
+ ...workflow.jobs.map((job) => job.name)
88
+ ].join("\n").toLowerCase().includes(needle));
89
+ }
90
+ /** The plugin's catalog channel: 'workflow_catalog' payloads into the store. */
91
+ const workflowCatalogChannel = catalog.channel({
92
+ channel: WORKFLOW_CATALOG_TYPE,
93
+ parse(input) {
94
+ if (!isRecord$2(input) || typeof input.cwd !== "string" || !Array.isArray(input.workflows)) return null;
95
+ return {
96
+ cwd: input.cwd,
97
+ workflows: input.workflows.filter(isRecord$2),
98
+ ...typeof input.warning === "string" ? { warning: input.warning } : {}
99
+ };
100
+ },
101
+ reduce(current, payload) {
102
+ const paths = new Set(payload.workflows.map((workflow) => workflow.path));
103
+ return {
104
+ ...current,
105
+ cwd: payload.cwd,
106
+ workflows: payload.workflows,
107
+ warning: payload.warning,
108
+ selected: current.selected !== void 0 && paths.has(current.selected) ? current.selected : void 0,
109
+ inspected: current.inspected !== void 0 && paths.has(current.inspected) ? current.inspected : void 0,
110
+ launch: current.launch !== void 0 && paths.has(current.launch) ? current.launch : void 0
111
+ };
112
+ }
113
+ });
114
+ //#endregion
115
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/runDuration.ts
116
+ /**
117
+ * Local copy of the host's formatRunDuration (src/web/lib/sessionSummary.ts):
118
+ * this plugin keeps zero host-lib imports so its later move into the owning
119
+ * package is a file relocation.
120
+ */
121
+ const MINUTE_MS = 6e4;
122
+ const HOUR_MS = 60 * MINUTE_MS;
123
+ function formatRunDuration(elapsedMs) {
124
+ if (elapsedMs < MINUTE_MS) return "<1m";
125
+ if (elapsedMs < HOUR_MS) return `${Math.floor(elapsedMs / MINUTE_MS)}m`;
126
+ const hours = Math.floor(elapsedMs / HOUR_MS);
127
+ const minutes = Math.floor(elapsedMs % HOUR_MS / MINUTE_MS);
128
+ return `${hours}h ${String(minutes).padStart(2, "0")}m`;
129
+ }
130
+ //#endregion
131
+ //#region src/types/apiRoutes.ts
132
+ /**
133
+ * This package's routes, as data.
134
+ *
135
+ * No scope and no base path. The build reads both off
136
+ * `src/extensions/(backend)/api/workflow/`, so the mount is stated once, by the
137
+ * folder that creates it. The same API answers at all three scopes, because a
138
+ * global contribution cascades into workspace and session; the folder under
139
+ * `workspaces/sessions/` only re-mounts it for a session's own server.
140
+ *
141
+ * This is the one file the routes, the Hono app and the browser all read, so a
142
+ * path cannot move on one side alone. The page used to build the hub's own
143
+ * absolute URL and then rewrite its plugin prefix with String.replace to reach
144
+ * a session, which is exactly the kind of copy that can move by one character
145
+ * without anything noticing.
146
+ *
147
+ * `:workspace` and `:runKey` are Hono's own parameter syntax, because the app
148
+ * registers `path` verbatim.
149
+ */
150
+ /** One run, which every route below addresses. */
151
+ const RUN = "/runs/:workspace/:runKey";
152
+ /**
153
+ * The artifact route's parameter, as Hono spells a multi-segment match.
154
+ *
155
+ * An artifact is named by its path inside the run directory, so `reports/a.md`
156
+ * is one parameter across two segments. The client's `params` substitution
157
+ * fills a single segment and percent-encodes the slash with it, so the page
158
+ * substitutes this one itself; see the page's `_lib/terminalApi.ts`.
159
+ */
160
+ const ARTIFACT_NAME_PARAM = ":name{.+}";
161
+ const ARTIFACT_DOWNLOAD_PARAM = "download";
162
+ var apiRoutes_default = defineApiRoutes({
163
+ /**
164
+ * The terminal, as server-sent events.
165
+ *
166
+ * `stream: true` gets a URL and no call method: the consumer is the browser's
167
+ * own `EventSource`, and a relayed call would buffer a stream that never ends.
168
+ */
169
+ screen: {
170
+ method: "GET",
171
+ path: `${RUN}/screen/stream`,
172
+ stream: true
173
+ },
174
+ control: {
175
+ method: "POST",
176
+ path: `${RUN}/control`,
177
+ response: apiResponse()
178
+ },
179
+ keys: {
180
+ method: "POST",
181
+ path: `${RUN}/keys`
182
+ },
183
+ resize: {
184
+ method: "POST",
185
+ path: `${RUN}/resize`,
186
+ response: apiResponse()
187
+ },
188
+ remove: {
189
+ method: "DELETE",
190
+ path: RUN,
191
+ response: apiResponse()
192
+ },
193
+ artifacts: {
194
+ method: "GET",
195
+ path: `${RUN}/artifacts`,
196
+ response: apiResponse()
197
+ },
198
+ /** One artifact: its metadata and text, or its raw bytes under `raw=1`. */
199
+ artifact: {
200
+ method: "GET",
201
+ path: `${RUN}/artifacts/${ARTIFACT_NAME_PARAM}`,
202
+ query: ["raw", ARTIFACT_DOWNLOAD_PARAM],
203
+ response: apiResponse()
204
+ }
205
+ });
206
+ const api = createApiClient(apiRoutes_default, {
207
+ scopes: [
208
+ "global",
209
+ "workspace",
210
+ "session"
211
+ ],
212
+ basePath: "workflow",
213
+ transport: (input, init) => sealedTransport.fetch(input, init),
214
+ sessionAddress: sessionApiAddress
215
+ });
216
+ //#endregion
217
+ //#region src/types/webWorkflowTerminal.ts
218
+ /** The SSE event name the screen stream writes. */
219
+ const WORKFLOW_SCREEN_EVENT = "screen";
220
+ //#endregion
221
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/terminalApi.ts
222
+ /**
223
+ * The page's half of this package's hub API: one run's terminal and the files
224
+ * its run directory holds.
225
+ *
226
+ * These are adapters now, not a transport. The generated client owns the URL
227
+ * and the sealed transport with it, so nothing here spells a mount. What stays
228
+ * is this package's own vocabulary: which refusals the reader is shown, and the
229
+ * shapes the terminal expects back.
230
+ *
231
+ * A run belongs to its multiplexer rather than to a session, so the same API
232
+ * answers at the hub and inside a session's own server. Passing a session id
233
+ * addresses that session's copy and passing none addresses the hub. The deleted
234
+ * builder made that branch the other way round: it always built the hub's
235
+ * absolute URL, then rewrote the plugin prefix into the session's with
236
+ * String.replace, so a session's URL was a hub URL with its head swapped.
237
+ */
238
+ const UNREACHABLE = "The cockpit hub is unreachable.";
239
+ /** The hub, or one session's own copy of it; a null session id is the hub. */
240
+ function scoped(sessionId) {
241
+ return sessionId == null ? api.global : api.session(sessionId);
242
+ }
243
+ /** The run every route addresses, as the route table's two path parameters. */
244
+ function runParams(workspace, runKey) {
245
+ return {
246
+ workspace,
247
+ runKey
248
+ };
249
+ }
250
+ function isRecord$1(value) {
251
+ return typeof value === "object" && value !== null && !Array.isArray(value);
252
+ }
253
+ /**
254
+ * The message a reader is shown: the route's own words, or why there were none.
255
+ *
256
+ * `error` is optional because an answered call that simply carried the wrong
257
+ * shape reaches here too, and that result has no error field at all.
258
+ */
259
+ function messageOf(result, fallback) {
260
+ if (result.status === 0) return UNREACHABLE;
261
+ return result.error === void 0 || result.error === "" ? fallback : result.error;
262
+ }
263
+ /**
264
+ * One artifact's URL, with its path filled in.
265
+ *
266
+ * An artifact is named by its path inside the run directory, which Hono matches
267
+ * as the one multi-segment parameter `:name{.+}`. The client's own `params`
268
+ * substitution fills a single segment and percent-encodes the separating slash
269
+ * with it, which would move every nested artifact's URL, so that one segment is
270
+ * substituted here instead: each segment encoded, the slashes between them
271
+ * kept. Everything left of it still comes from the client.
272
+ */
273
+ function artifactUrl(scope, workspace, runKey, artifactPath, query) {
274
+ return scope.artifact.url({
275
+ params: runParams(workspace, runKey),
276
+ ...query === void 0 ? {} : { query }
277
+ }).replace(ARTIFACT_NAME_PARAM, artifactPath.split("/").map(encodeURIComponent).join("/"));
278
+ }
279
+ /**
280
+ * Follows one run's screen until the run settles or the caller stops.
281
+ *
282
+ * Returns the stop function. The stream is server-sent events, so a hub that
283
+ * restarts reconnects on its own and the page keeps painting. The route is
284
+ * declared `stream: true`, which is why the client hands out its address and no
285
+ * call: an `EventSource` opens it directly, and a relayed call would buffer a
286
+ * response that never ends.
287
+ */
288
+ function followScreen(workspace, runKey, onEvent, sessionId) {
289
+ const source = new EventSource(scoped(sessionId).screen.url({ params: runParams(workspace, runKey) }));
290
+ const handler = (message) => {
291
+ let parsed;
292
+ try {
293
+ parsed = JSON.parse(message.data);
294
+ } catch {
295
+ return;
296
+ }
297
+ if (!isRecord$1(parsed) || !Array.isArray(parsed.lines)) return;
298
+ onEvent(parsed);
299
+ };
300
+ source.addEventListener(WORKFLOW_SCREEN_EVENT, handler);
301
+ return () => {
302
+ source.removeEventListener(WORKFLOW_SCREEN_EVENT, handler);
303
+ source.close();
304
+ };
305
+ }
306
+ /** Takes the keyboard for one run, renewing when this page already holds it. */
307
+ async function takeControl(workspace, runKey, token, sessionId) {
308
+ const result = await scoped(sessionId).control({
309
+ params: runParams(workspace, runKey),
310
+ body: { token }
311
+ });
312
+ if (result.status === 0) return {
313
+ held: false,
314
+ reason: UNREACHABLE
315
+ };
316
+ if (isRecord$1(result.data) && typeof result.data.held === "boolean") return result.data;
317
+ return {
318
+ held: false,
319
+ reason: messageOf(result, "The run refused the keyboard.")
320
+ };
321
+ }
322
+ async function releaseControl(workspace, runKey, token, sessionId) {
323
+ await scoped(sessionId).control({
324
+ params: runParams(workspace, runKey),
325
+ body: {
326
+ token,
327
+ release: true
328
+ }
329
+ });
330
+ }
331
+ /** Sends literal keystrokes; the reason comes back when the lease has moved on. */
332
+ async function sendKeys(workspace, runKey, token, data, sessionId) {
333
+ const result = await scoped(sessionId).keys({
334
+ params: runParams(workspace, runKey),
335
+ body: {
336
+ token,
337
+ data
338
+ }
339
+ });
340
+ if (result.ok) return {};
341
+ return { error: messageOf(result, "The run would not take those keys.") };
342
+ }
343
+ /** Permanently removes one settled run and its run directory. */
344
+ async function deleteWorkflowRun(workspace, runKey, sessionId) {
345
+ const result = await scoped(sessionId).remove({ params: runParams(workspace, runKey) });
346
+ if (!result.ok) return { error: messageOf(result, "The workflow could not be deleted.") };
347
+ if (result.data.deleted === true) return { result: { deleted: true } };
348
+ return { error: "The workflow hub returned an invalid deletion response." };
349
+ }
350
+ async function fetchArtifacts(workspace, runKey, sessionId) {
351
+ const result = await scoped(sessionId).artifacts({ params: runParams(workspace, runKey) });
352
+ if (!result.ok) return { error: messageOf(result, "This run has no directory to read.") };
353
+ return { artifacts: result.data };
354
+ }
355
+ /** Trusted same-origin URL for browser-native media previews and downloads. */
356
+ function artifactContentUrl(workspace, runKey, path, download = false, sessionId) {
357
+ return artifactUrl(scoped(sessionId), workspace, runKey, path, {
358
+ ["raw"]: 1,
359
+ ...download ? { [ARTIFACT_DOWNLOAD_PARAM]: 1 } : {}
360
+ });
361
+ }
362
+ /**
363
+ * One artifact's metadata and text.
364
+ *
365
+ * The only route here that addresses itself rather than being called: its path
366
+ * parameter spans segments, so the URL comes from `artifactUrl` and the request
367
+ * goes over the same sealed transport the client is built on.
368
+ */
369
+ async function fetchArtifact(workspace, runKey, path, sessionId) {
370
+ try {
371
+ const response = await sealedTransport.fetch(artifactUrl(scoped(sessionId), workspace, runKey, path));
372
+ const body = await response.json();
373
+ if (!response.ok) {
374
+ const error = isRecord$1(body) && typeof body.error === "string" && body.error !== "" ? body.error : "";
375
+ return { error: error === "" ? "That file has not been written." : error };
376
+ }
377
+ return { artifact: body };
378
+ } catch {
379
+ return { error: UNREACHABLE };
380
+ }
381
+ }
382
+ //#endregion
383
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/workflowActivity.ts
384
+ const PAUSED_STATES = /* @__PURE__ */ new Set(["paused", "pause_requested"]);
385
+ function workflowRunIdentity(run) {
386
+ return `${run.workspace}/${run.runKey}`;
387
+ }
388
+ function toneOf(run) {
389
+ if (run.stage === "running") return run.executionState !== void 0 && PAUSED_STATES.has(run.executionState) ? "paused" : "running";
390
+ if (run.stage === "error") return "failed";
391
+ if (run.outcome !== void 0 && run.outcome !== "success") return run.outcome === "failed" ? "failed" : "skipped";
392
+ return "done";
393
+ }
394
+ function detailOf(run, tone) {
395
+ if (tone === "paused") return "paused; resume it from the owning session";
396
+ if (tone === "failed") return run.errorMessage ?? (run.failedJob === void 0 ? "failed" : `job '${run.failedJob}' failed`);
397
+ if (tone === "running") {
398
+ if (run.position === void 0) return "starting";
399
+ return run.position.step === void 0 ? run.position.job : `${run.position.job} · ${run.position.step}`;
400
+ }
401
+ return run.outcome ?? "done";
402
+ }
403
+ function elapsedOf(run, now) {
404
+ const start = Date.parse(run.startedAt);
405
+ if (!Number.isFinite(start)) return "";
406
+ const end = run.finishedAt === void 0 ? now : Date.parse(run.finishedAt);
407
+ return formatRunDuration(Math.max(0, (Number.isFinite(end) ? end : now) - start));
408
+ }
409
+ /** The hub's presented order is kept: it already puts live runs first. */
410
+ function workflowActivityRows(runs, now) {
411
+ return runs.map((run) => {
412
+ const tone = toneOf(run);
413
+ return {
414
+ identity: workflowRunIdentity(run),
415
+ runKey: run.runKey,
416
+ name: run.displayName,
417
+ tone,
418
+ elapsed: elapsedOf(run, now),
419
+ detail: detailOf(run, tone)
420
+ };
421
+ });
422
+ }
423
+ const GROUP_OF = {
424
+ running: "running",
425
+ paused: "running",
426
+ failed: "failed",
427
+ done: "successful",
428
+ skipped: "successful"
429
+ };
430
+ const GROUP_ORDER = [
431
+ "running",
432
+ "failed",
433
+ "successful"
434
+ ];
435
+ /** Rows split by outcome, in the order the dock shows them; empty groups are dropped. */
436
+ function workflowActivityGroups(rows) {
437
+ return GROUP_ORDER.map((name) => ({
438
+ name,
439
+ rows: rows.filter((row) => GROUP_OF[row.tone] === name),
440
+ openByDefault: name !== "successful"
441
+ })).filter((group) => group.rows.length > 0);
442
+ }
443
+ //#endregion
444
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/workflowsStore.ts
445
+ const workflows = defineSessionStore({
446
+ runs: [],
447
+ focusedRun: void 0,
448
+ pendingLaunch: void 0
449
+ });
450
+ function isRecord(value) {
451
+ return typeof value === "object" && value !== null && !Array.isArray(value);
452
+ }
453
+ /** Points the workflows tab at one run; the activity dock uses this before opening the tab. */
454
+ function focusRun(sessionId, identity) {
455
+ workflows.update(sessionId, (current) => ({
456
+ ...current,
457
+ focusedRun: identity
458
+ }));
459
+ }
460
+ /** Removes a deleted run immediately while the registry channel catches up. */
461
+ function removeRun(sessionId, identity) {
462
+ workflows.update(sessionId, (current) => {
463
+ const runs = current.runs.filter((run) => workflowRunIdentity(run) !== identity);
464
+ return {
465
+ ...current,
466
+ runs,
467
+ focusedRun: current.focusedRun === identity ? runs[0] === void 0 ? void 0 : workflowRunIdentity(runs[0]) : current.focusedRun
468
+ };
469
+ });
470
+ }
471
+ /**
472
+ * Sends a launch line to the session and remembers what was already running.
473
+ *
474
+ * The cockpit's only channel to a session is a prompt frame, so a launch is
475
+ * the verb the extension registers for exactly this; see web/launchLine.ts.
476
+ */
477
+ function requestLaunch(send, sessionId, line, workflowPath) {
478
+ send(sessionId, {
479
+ type: "prompt",
480
+ message: line
481
+ });
482
+ workflows.update(sessionId, (current) => ({
483
+ ...current,
484
+ pendingLaunch: {
485
+ workflowPath,
486
+ knownRuns: current.runs.map(workflowRunIdentity)
487
+ }
488
+ }));
489
+ }
490
+ /** The plugin's session data channel: 'workflow_runs' payloads into the store. */
491
+ const workflowRunsChannel = workflows.channel({
492
+ channel: WORKFLOW_RUNS_TYPE,
493
+ parse(input) {
494
+ if (!isRecord(input) || !Array.isArray(input.runs)) return null;
495
+ return { runs: input.runs.filter(isRecord) };
496
+ },
497
+ reduce: (current, { runs }) => {
498
+ const pending = current.pendingLaunch;
499
+ if (pending === void 0) return {
500
+ ...current,
501
+ runs
502
+ };
503
+ const known = new Set(pending.knownRuns);
504
+ const arrived = runs.find((run) => !known.has(workflowRunIdentity(run)));
505
+ if (arrived === void 0) return {
506
+ ...current,
507
+ runs
508
+ };
509
+ return {
510
+ ...current,
511
+ runs,
512
+ focusedRun: workflowRunIdentity(arrived),
513
+ pendingLaunch: void 0
514
+ };
515
+ }
516
+ });
517
+ //#endregion
518
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/ArtifactsPane.tsx
519
+ const TAB_ID_PREFIX$1 = "workflows-artifact-";
520
+ const BYTES_PER_UNIT = 1024;
521
+ const UNITS = [
522
+ "B",
523
+ "KB",
524
+ "MB"
525
+ ];
526
+ function formatSize(bytes) {
527
+ if (bytes === void 0) return "";
528
+ let value = bytes;
529
+ let unit = 0;
530
+ while (value >= BYTES_PER_UNIT && unit < UNITS.length - 1) {
531
+ value /= BYTES_PER_UNIT;
532
+ unit += 1;
533
+ }
534
+ return `${unit === 0 ? value : value.toFixed(1)} ${UNITS[unit]}`;
535
+ }
536
+ function formatWhen(iso) {
537
+ if (iso === void 0) return "";
538
+ const parsed = Date.parse(iso);
539
+ if (!Number.isFinite(parsed)) return "";
540
+ return new Date(parsed).toLocaleTimeString([], {
541
+ hour: "2-digit",
542
+ minute: "2-digit"
543
+ });
544
+ }
545
+ const STATE_GLYPH = {
546
+ written: {
547
+ glyph: "✓",
548
+ className: "text-doom-green"
549
+ },
550
+ empty: {
551
+ glyph: "○",
552
+ className: "text-doom-yellow"
553
+ },
554
+ pending: {
555
+ glyph: "○",
556
+ className: "text-doom-faint"
557
+ },
558
+ unreadable: {
559
+ glyph: "!",
560
+ className: "text-doom-red"
561
+ }
562
+ };
563
+ function parseDelimited(text, delimiter) {
564
+ const rows = [];
565
+ let row = [];
566
+ let cell = "";
567
+ let quoted = false;
568
+ for (let index = 0; index <= text.length && rows.length < 500; index += 1) {
569
+ const character = text[index] ?? "\n";
570
+ if (quoted) {
571
+ if (character === "\"" && text[index + 1] === "\"") {
572
+ cell += "\"";
573
+ index += 1;
574
+ } else if (character === "\"") quoted = false;
575
+ else cell += character;
576
+ } else if (character === "\"" && cell === "") quoted = true;
577
+ else if (character === delimiter) {
578
+ row.push(cell);
579
+ cell = "";
580
+ } else if (character === "\n") {
581
+ row.push(cell.replace(/\r$/, ""));
582
+ rows.push(row);
583
+ row = [];
584
+ cell = "";
585
+ } else cell += character;
586
+ }
587
+ return rows;
588
+ }
589
+ function DelimitedPreview({ text, delimiter }) {
590
+ const [head = [], ...body] = parseDelimited(text, delimiter);
591
+ return /* @__PURE__ */ jsx("div", {
592
+ className: "overflow-auto",
593
+ children: /* @__PURE__ */ jsxs("table", {
594
+ "data-testid": "artifact-table",
595
+ className: "min-w-full border-collapse font-mono text-sm",
596
+ children: [/* @__PURE__ */ jsx("thead", {
597
+ className: "sticky top-0 bg-doom-panel text-doom-hi",
598
+ children: /* @__PURE__ */ jsx("tr", { children: head.map((cell, index) => /* @__PURE__ */ jsx("th", {
599
+ className: "border border-doom-border px-2 py-1.5 text-left font-bold",
600
+ children: cell
601
+ }, index)) })
602
+ }), /* @__PURE__ */ jsx("tbody", { children: body.map((cells, rowIndex) => /* @__PURE__ */ jsx("tr", { children: cells.map((cell, cellIndex) => /* @__PURE__ */ jsx("td", {
603
+ className: "border border-doom-border px-2 py-1 align-top text-doom-text",
604
+ children: cell
605
+ }, cellIndex)) }, rowIndex)) })]
606
+ })
607
+ });
608
+ }
609
+ /** Keeps a newly built viewer compatible while an older hub process is restarting. */
610
+ function previewMimeType(content) {
611
+ if (content.mimeType !== void 0) return content.mimeType;
612
+ const extension = content.path.split(".").pop()?.toLowerCase();
613
+ if (extension === "md" || extension === "markdown") return "text/markdown";
614
+ if (extension === "json") return "application/json";
615
+ if (extension === "csv") return "text/csv";
616
+ if (extension === "tsv") return "text/tab-separated-values";
617
+ if (extension === "html" || extension === "htm") return "text/html";
618
+ if (extension === "pdf") return "application/pdf";
619
+ if ([
620
+ "bmp",
621
+ "gif",
622
+ "ico",
623
+ "jpeg",
624
+ "jpg",
625
+ "png",
626
+ "svg",
627
+ "webp"
628
+ ].includes(extension ?? "")) return "image/unknown";
629
+ if ([
630
+ "aac",
631
+ "m4a",
632
+ "mp3",
633
+ "oga",
634
+ "ogg",
635
+ "wav"
636
+ ].includes(extension ?? "")) return "audio/unknown";
637
+ if ([
638
+ "mov",
639
+ "mp4",
640
+ "ogv",
641
+ "webm"
642
+ ].includes(extension ?? "")) return "video/unknown";
643
+ return content.text === void 0 ? "application/octet-stream" : "text/plain";
644
+ }
645
+ function ArtifactPreview({ content, rawUrl, raw }) {
646
+ const text = content.text ?? "";
647
+ const mimeType = previewMimeType(content);
648
+ if (content.size === 0) return /* @__PURE__ */ jsx(EmptyState, {
649
+ "data-testid": "artifact-empty",
650
+ title: "this artifact is empty",
651
+ description: "The workflow created the file but did not write any content."
652
+ });
653
+ if (mimeType === "text/markdown") return raw ? /* @__PURE__ */ jsx("pre", {
654
+ "data-testid": "artifact-raw",
655
+ className: "whitespace-pre-wrap break-words font-mono text-sm leading-snug text-doom-text",
656
+ children: text
657
+ }) : /* @__PURE__ */ jsx("div", {
658
+ "data-testid": "artifact-markdown",
659
+ className: "max-w-5xl text-base text-doom-text",
660
+ children: /* @__PURE__ */ jsx(Markdown, { text })
661
+ });
662
+ if (mimeType === "application/json") {
663
+ let formatted = text;
664
+ try {
665
+ formatted = JSON.stringify(JSON.parse(text), null, 2);
666
+ } catch {}
667
+ return /* @__PURE__ */ jsx("pre", {
668
+ className: "whitespace-pre-wrap break-words font-mono text-sm leading-snug text-doom-text",
669
+ children: formatted
670
+ });
671
+ }
672
+ if (mimeType === "text/csv" || mimeType === "text/tab-separated-values") return /* @__PURE__ */ jsx(DelimitedPreview, {
673
+ text,
674
+ delimiter: mimeType === "text/csv" ? "," : " "
675
+ });
676
+ if (mimeType === "text/html") return /* @__PURE__ */ jsx("iframe", {
677
+ "data-testid": "artifact-html",
678
+ title: content.path,
679
+ sandbox: "",
680
+ referrerPolicy: "no-referrer",
681
+ srcDoc: text,
682
+ className: "min-h-[520px] w-full rounded bg-white"
683
+ });
684
+ if (mimeType.startsWith("image/")) return /* @__PURE__ */ jsx("img", {
685
+ "data-testid": "artifact-image",
686
+ src: rawUrl,
687
+ alt: content.path,
688
+ className: "max-h-full max-w-full object-contain"
689
+ });
690
+ if (mimeType === "application/pdf") return /* @__PURE__ */ jsx("object", {
691
+ "data-testid": "artifact-pdf",
692
+ data: rawUrl,
693
+ type: "application/pdf",
694
+ "aria-label": "PDF artifact",
695
+ className: "min-h-[600px] w-full"
696
+ });
697
+ if (mimeType.startsWith("audio/")) return /* @__PURE__ */ jsx("audio", {
698
+ "data-testid": "artifact-audio",
699
+ src: rawUrl,
700
+ controls: true,
701
+ preload: "metadata",
702
+ className: "w-full",
703
+ children: /* @__PURE__ */ jsx("track", { kind: "captions" })
704
+ });
705
+ if (mimeType.startsWith("video/")) return /* @__PURE__ */ jsx("video", {
706
+ "data-testid": "artifact-video",
707
+ src: rawUrl,
708
+ controls: true,
709
+ preload: "metadata",
710
+ className: "max-h-full max-w-full",
711
+ children: /* @__PURE__ */ jsx("track", { kind: "captions" })
712
+ });
713
+ if (content.text !== void 0) return /* @__PURE__ */ jsx("pre", {
714
+ className: "whitespace-pre-wrap break-words font-mono text-sm leading-snug text-doom-text",
715
+ children: text
716
+ });
717
+ return /* @__PURE__ */ jsx(EmptyState, {
718
+ title: "preview unavailable",
719
+ description: "Download this artifact to open it with a local application."
720
+ });
721
+ }
722
+ /** One artifact, read-only, in its own tab. */
723
+ function artifactTab(run, path) {
724
+ return {
725
+ id: `${TAB_ID_PREFIX$1}${run.workspace}-${run.runKey}-${path}`.replace(/[^\w-]+/g, "-"),
726
+ label: path.split("/").pop() ?? path,
727
+ panel: (props) => /* @__PURE__ */ jsx(ArtifactViewerPanel, {
728
+ ...props,
729
+ workspace: run.workspace,
730
+ runKey: run.runKey,
731
+ path
732
+ })
733
+ };
734
+ }
735
+ /**
736
+ * One artifact's text.
737
+ *
738
+ * Read-only and reloadable: a run keeps writing while somebody is reading, and
739
+ * the file on the next read is the one that matters.
740
+ */
741
+ function ArtifactViewerPanel({ workspace, runKey, path, sessionId }) {
742
+ const [content, setContent] = useState();
743
+ const [error, setError] = useState();
744
+ const [reloads, setReloads] = useState(0);
745
+ const [raw, setRaw] = useState(false);
746
+ useEffect(() => {
747
+ let live = true;
748
+ fetchArtifact(workspace, runKey, path, sessionId).then((result) => {
749
+ if (!live) return;
750
+ if ("error" in result) {
751
+ setError(result.error);
752
+ setContent(void 0);
753
+ return;
754
+ }
755
+ setError(void 0);
756
+ setContent(result.artifact);
757
+ });
758
+ return () => {
759
+ live = false;
760
+ };
761
+ }, [
762
+ workspace,
763
+ runKey,
764
+ path,
765
+ reloads,
766
+ sessionId
767
+ ]);
768
+ const rawUrl = artifactContentUrl(workspace, runKey, path, false, sessionId);
769
+ const downloadUrl = artifactContentUrl(workspace, runKey, path, true, sessionId);
770
+ const mimeType = content === void 0 ? void 0 : previewMimeType(content);
771
+ const isMarkdown = mimeType === "text/markdown";
772
+ return /* @__PURE__ */ jsxs("div", {
773
+ "data-testid": "artifact-viewer",
774
+ className: "flex min-h-0 flex-1 flex-col px-[26px] py-[18px]",
775
+ children: [
776
+ /* @__PURE__ */ jsxs("div", {
777
+ className: "flex items-center gap-2.5 pb-3",
778
+ children: [
779
+ /* @__PURE__ */ jsx("span", {
780
+ className: "truncate text-sm font-bold text-doom-hi",
781
+ children: path
782
+ }),
783
+ /* @__PURE__ */ jsx(Badge, {
784
+ size: "xs",
785
+ children: mimeType ?? "artifact"
786
+ }),
787
+ /* @__PURE__ */ jsx("span", {
788
+ className: "text-2xs text-doom-faint",
789
+ children: content === void 0 ? "" : `${formatSize(content.size)} · written ${formatWhen(content.modifiedAt)}`
790
+ }),
791
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
792
+ isMarkdown ? /* @__PURE__ */ jsxs("div", {
793
+ className: "flex items-center",
794
+ children: [/* @__PURE__ */ jsx(Button, {
795
+ variant: raw ? "outline" : "primary",
796
+ size: "xs",
797
+ "data-testid": "artifact-rendered-toggle",
798
+ onClick: () => setRaw(false),
799
+ children: "rendered"
800
+ }), /* @__PURE__ */ jsx(Button, {
801
+ variant: raw ? "primary" : "outline",
802
+ size: "xs",
803
+ "data-testid": "artifact-raw-toggle",
804
+ onClick: () => setRaw(true),
805
+ children: "raw"
806
+ })]
807
+ }) : null,
808
+ /* @__PURE__ */ jsx(Button, {
809
+ asChild: true,
810
+ variant: "ghost",
811
+ size: "xs",
812
+ children: /* @__PURE__ */ jsx("a", {
813
+ "data-testid": "artifact-download",
814
+ href: downloadUrl,
815
+ download: true,
816
+ children: "download"
817
+ })
818
+ }),
819
+ /* @__PURE__ */ jsx(Button, {
820
+ variant: "ghost",
821
+ size: "xs",
822
+ "data-testid": "artifact-reload",
823
+ onClick: () => setReloads((count) => count + 1),
824
+ children: "reload"
825
+ })
826
+ ]
827
+ }),
828
+ /* @__PURE__ */ jsx("div", {
829
+ className: "min-h-0 flex-1 overflow-auto rounded-md border border-doom-border bg-doom-deep p-4",
830
+ children: error !== void 0 ? /* @__PURE__ */ jsx("span", {
831
+ "data-testid": "artifact-error",
832
+ className: "text-xs text-doom-yellow",
833
+ children: error
834
+ }) : content === void 0 ? /* @__PURE__ */ jsx("span", {
835
+ className: "text-xs text-doom-faint",
836
+ children: "loading preview"
837
+ }) : /* @__PURE__ */ jsx(ArtifactPreview, {
838
+ content,
839
+ rawUrl,
840
+ raw
841
+ })
842
+ }),
843
+ content?.truncated === true ? /* @__PURE__ */ jsxs("span", {
844
+ className: "pt-2 text-2xs text-doom-faint",
845
+ children: [
846
+ "this preview shows the first ",
847
+ formatSize(content.text?.length),
848
+ "; download the artifact for the complete file"
849
+ ]
850
+ }) : null
851
+ ]
852
+ });
853
+ }
854
+ /**
855
+ * The run directory, as the run pane's second tab.
856
+ *
857
+ * The workflow's own declaration leads, in the order it was written and
858
+ * including entries no job has produced yet, because the declaration is what
859
+ * the run is for. What the folder holds besides that follows.
860
+ */
861
+ function ArtifactsPane({ run, sessionId, onOpen }) {
862
+ const [listing, setListing] = useState();
863
+ const [error, setError] = useState();
864
+ useEffect(() => {
865
+ let live = true;
866
+ const read = () => {
867
+ fetchArtifacts(run.workspace, run.runKey, sessionId).then((result) => {
868
+ if (!live) return;
869
+ if ("error" in result) {
870
+ setError(result.error);
871
+ return;
872
+ }
873
+ setError(void 0);
874
+ setListing(result.artifacts);
875
+ });
876
+ };
877
+ read();
878
+ if (run.stage !== "running") return;
879
+ const timer = setInterval(read, 5e3);
880
+ return () => {
881
+ live = false;
882
+ clearInterval(timer);
883
+ };
884
+ }, [
885
+ run.workspace,
886
+ run.runKey,
887
+ run.stage,
888
+ sessionId
889
+ ]);
890
+ const declared = listing?.artifacts.filter((entry) => entry.declared) ?? [];
891
+ const found = listing?.artifacts.filter((entry) => !entry.declared) ?? [];
892
+ const Row = ({ entry }) => {
893
+ const glyph = STATE_GLYPH[entry.state];
894
+ const openable = entry.kind === "file" && entry.state !== "pending";
895
+ return /* @__PURE__ */ jsxs("button", {
896
+ type: "button",
897
+ "data-testid": `artifact-row-${entry.path}`,
898
+ "data-artifact-state": entry.state,
899
+ disabled: !openable,
900
+ onClick: () => onOpen(entry.path),
901
+ className: `flex flex-col gap-0.5 px-3 py-1.5 text-left ${openable ? "cursor-pointer hover:bg-doom-panel" : "cursor-default"}`,
902
+ children: [/* @__PURE__ */ jsxs("span", {
903
+ className: "flex min-w-0 items-center gap-2",
904
+ children: [
905
+ /* @__PURE__ */ jsx("span", {
906
+ className: `w-3 shrink-0 text-xs ${glyph.className}`,
907
+ children: glyph.glyph
908
+ }),
909
+ /* @__PURE__ */ jsx("span", {
910
+ className: `truncate text-sm ${entry.state === "pending" ? "text-doom-dim" : "text-doom-hi"}`,
911
+ children: entry.path
912
+ }),
913
+ entry.producedBy.length === 0 ? null : /* @__PURE__ */ jsx(Badge, {
914
+ size: "xs",
915
+ tone: "violet",
916
+ className: "shrink-0",
917
+ children: entry.producedBy.join(", ")
918
+ }),
919
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
920
+ /* @__PURE__ */ jsx("span", {
921
+ className: "shrink-0 text-2xs text-doom-faint",
922
+ children: entry.state === "pending" ? "not written yet" : `${formatSize(entry.size)} · ${formatWhen(entry.modifiedAt)}`
923
+ })
924
+ ]
925
+ }), entry.description === "" ? null : /* @__PURE__ */ jsx("span", {
926
+ className: "truncate pl-5 text-2xs text-doom-faint",
927
+ children: entry.description
928
+ })]
929
+ });
930
+ };
931
+ return /* @__PURE__ */ jsxs("div", {
932
+ "data-testid": "artifacts-pane",
933
+ className: "flex min-h-0 flex-1 flex-col",
934
+ children: [
935
+ error !== void 0 ? /* @__PURE__ */ jsx(EmptyState, {
936
+ className: "py-4",
937
+ title: "the run directory cannot be read",
938
+ description: error
939
+ }) : null,
940
+ declared.length === 0 && found.length === 0 && error === void 0 ? /* @__PURE__ */ jsx(EmptyState, {
941
+ className: "py-4",
942
+ title: "nothing in the run directory yet"
943
+ }) : null,
944
+ declared.length === 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
945
+ className: "px-3 pb-1 pt-2 text-2xs font-bold tracking-wider text-doom-faint",
946
+ children: ["DECLARED · ", listing?.description || "run-directory"]
947
+ }), declared.map((entry) => /* @__PURE__ */ jsx(Row, { entry }, entry.path))] }),
948
+ found.length === 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
949
+ className: "px-3 pb-1 pt-3 text-2xs font-bold tracking-wider text-doom-faint",
950
+ children: "ALSO IN THE FOLDER"
951
+ }), found.map((entry) => /* @__PURE__ */ jsx(Row, { entry }, entry.path))] }),
952
+ listing === void 0 ? null : /* @__PURE__ */ jsx("span", {
953
+ className: "truncate px-3 py-2 text-2xs text-doom-faint",
954
+ children: listing.runDir
955
+ })
956
+ ]
957
+ });
958
+ }
959
+ //#endregion
960
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/launchLine.ts
961
+ /**
962
+ * The `/workflow-launch` line the dialog sends.
963
+ *
964
+ * WHY THE BUILDER LIVES HERE AND THE PARSER DOES NOT:
965
+ * Cockpit code is compiled into the host bundle and may import only this
966
+ * package's own web and type modules, so it cannot share the session's parser.
967
+ * The two are pinned together by a test that parses what this writes, which is
968
+ * the only guarantee that matters: the line has to survive the trip.
969
+ */
970
+ const WORKFLOW_LAUNCH_VERB = "/workflow-launch";
971
+ /** The reserved key; every other pair is one of the workflow's own inputs. */
972
+ const RUNNER_KEY = "runner";
973
+ /** Quotes a value that carries whitespace, which is how a value survives tokenizing. */
974
+ function quote(value) {
975
+ return /\s/.test(value) ? `"${value}"` : value;
976
+ }
977
+ function workflowLaunchLine(request) {
978
+ const pairs = [...request.runner === void 0 || request.runner === "" ? [] : [`${RUNNER_KEY}=${quote(request.runner)}`], ...Object.entries(request.inputs).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${quote(value)}`)];
979
+ const prompt = request.prompt?.trim();
980
+ return [
981
+ WORKFLOW_LAUNCH_VERB,
982
+ quote(request.workflow),
983
+ ...pairs,
984
+ ...prompt ? [prompt] : []
985
+ ].join(" ");
986
+ }
987
+ /** The trigger that makes a prompt mandatory. */
988
+ const USER_PROMPT_TRIGGER = "user_prompt";
989
+ /** What the dialog will not let a reader send, worded the way the session would answer. */
990
+ function launchProblems(workflow, request) {
991
+ const problems = [];
992
+ if (workflow.triggers.includes(USER_PROMPT_TRIGGER) && (request.prompt ?? "").trim() === "") problems.push("This workflow is triggered by a prompt, so it needs one.");
993
+ const missing = workflow.inputs.filter((input) => input.required === true && (request.inputs[input.name] ?? "") === "").map((input) => input.name);
994
+ if (missing.length > 0) problems.push(`Missing required input${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}.`);
995
+ return problems;
996
+ }
997
+ /** The default a field starts with: what the workflow declared, or empty. */
998
+ function initialInputs(workflow) {
999
+ return Object.fromEntries(workflow.inputs.map((input) => [input.name, input.default ?? ""]));
1000
+ }
1001
+ /** The runner a launch starts on: the workflow's first declared one, when it declares any. */
1002
+ function initialRunner(workflow) {
1003
+ return workflow.runners?.[0];
1004
+ }
1005
+ //#endregion
1006
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/LaunchWorkflowDialog.tsx
1007
+ const PROMPT_ROWS = 3;
1008
+ const BOOLEAN_TYPE = "boolean";
1009
+ const BOOLEAN_VALUES = ["true", "false"];
1010
+ function FieldLabel({ children }) {
1011
+ return /* @__PURE__ */ jsx("span", {
1012
+ className: "text-2xs font-bold tracking-widest text-doom-faint",
1013
+ children
1014
+ });
1015
+ }
1016
+ /** One `workflow_dispatch` input: a picker when the workflow constrains it, a field otherwise. */
1017
+ function InputField({ input, value, onChange }) {
1018
+ const options = input.options ?? (input.type === BOOLEAN_TYPE ? [...BOOLEAN_VALUES] : void 0);
1019
+ const hint = [
1020
+ input.required === true ? "required" : void 0,
1021
+ input.default === void 0 ? void 0 : `default: ${input.default}`,
1022
+ input.description
1023
+ ].filter((part) => part !== void 0 && part !== "").join(" · ");
1024
+ return /* @__PURE__ */ jsxs("div", {
1025
+ className: "flex flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-2.5",
1026
+ "data-testid": `launch-input-${input.name}`,
1027
+ children: [
1028
+ /* @__PURE__ */ jsxs("span", {
1029
+ className: "min-w-0 truncate text-xs font-bold text-doom-text sm:w-28 sm:shrink-0",
1030
+ children: [input.name, input.required === true ? /* @__PURE__ */ jsx("span", {
1031
+ className: "text-doom-yellow",
1032
+ children: " *"
1033
+ }) : null]
1034
+ }),
1035
+ options === void 0 ? /* @__PURE__ */ jsx(Input, {
1036
+ className: "min-w-0 flex-1",
1037
+ value,
1038
+ placeholder: input.default ?? "",
1039
+ onChange: (event) => onChange(event.target.value)
1040
+ }) : /* @__PURE__ */ jsxs(Select, {
1041
+ value: value === "" ? void 0 : value,
1042
+ onValueChange: onChange,
1043
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1044
+ className: "h-7 w-full min-w-0 text-xs sm:min-w-[160px]",
1045
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "choose…" })
1046
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
1047
+ value: option,
1048
+ children: option
1049
+ }, option)) })]
1050
+ }),
1051
+ /* @__PURE__ */ jsx("span", {
1052
+ className: "min-w-0 truncate text-2xs text-doom-faint sm:w-40 sm:shrink-0",
1053
+ children: hint
1054
+ })
1055
+ ]
1056
+ });
1057
+ }
1058
+ /**
1059
+ * Launches one workflow from the catalog: the prompt, every input the file
1060
+ * declares, the runner when it declares a runner map, then the exact line the
1061
+ * session will parse. Enter launches, since the composer sends the same way.
1062
+ */
1063
+ function LaunchWorkflowDialog({ sessionId, workflow, cwd, initialPrompt, send, onClose, onLaunched }) {
1064
+ const [prompt, setPrompt] = useState(initialPrompt);
1065
+ const [inputs, setInputs] = useState(() => initialInputs(workflow));
1066
+ const [runner, setRunner] = useState(() => initialRunner(workflow));
1067
+ const request = {
1068
+ workflow: workflow.name === "" ? workflow.relativePath : workflow.name,
1069
+ ...runner === void 0 ? {} : { runner },
1070
+ inputs,
1071
+ ...prompt.trim() === "" ? {} : { prompt: prompt.trim() }
1072
+ };
1073
+ const problems = launchProblems(workflow, request);
1074
+ const line = workflowLaunchLine(request);
1075
+ const launch = () => {
1076
+ if (problems.length > 0) return;
1077
+ requestLaunch(send, sessionId, line, workflow.path);
1078
+ onLaunched();
1079
+ };
1080
+ const onPromptKeyDown = (event) => {
1081
+ if (event.key === "Enter" && !event.shiftKey) {
1082
+ event.preventDefault();
1083
+ launch();
1084
+ }
1085
+ };
1086
+ return /* @__PURE__ */ jsx(Dialog, {
1087
+ open: true,
1088
+ onOpenChange: (open) => {
1089
+ if (!open) onClose();
1090
+ },
1091
+ children: /* @__PURE__ */ jsxs(DialogContent, {
1092
+ width: "lg",
1093
+ "data-testid": "launch-workflow-dialog",
1094
+ "aria-describedby": void 0,
1095
+ children: [/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsxs("div", {
1096
+ className: "flex min-w-0 flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-2.5",
1097
+ children: [
1098
+ /* @__PURE__ */ jsx("span", {
1099
+ className: "text-2xs text-doom-faint",
1100
+ children: "launch workflow"
1101
+ }),
1102
+ /* @__PURE__ */ jsx(DialogTitle, {
1103
+ "data-testid": "launch-workflow-name",
1104
+ className: "max-w-full break-words",
1105
+ children: workflow.name
1106
+ }),
1107
+ /* @__PURE__ */ jsx(Badge, {
1108
+ size: "xs",
1109
+ className: "max-w-full truncate",
1110
+ children: workflow.relativePath
1111
+ })
1112
+ ]
1113
+ }) }), /* @__PURE__ */ jsxs(DialogBody, { children: [
1114
+ /* @__PURE__ */ jsx("p", {
1115
+ className: "text-sm leading-relaxed text-doom-dim",
1116
+ children: workflow.description || "This workflow declares no description."
1117
+ }),
1118
+ /* @__PURE__ */ jsxs("div", {
1119
+ className: "flex flex-col gap-1.5",
1120
+ children: [
1121
+ /* @__PURE__ */ jsx(FieldLabel, { children: "PROMPT" }),
1122
+ /* @__PURE__ */ jsx(Textarea, {
1123
+ "data-testid": "launch-prompt",
1124
+ autoFocus: true,
1125
+ rows: PROMPT_ROWS,
1126
+ value: prompt,
1127
+ placeholder: "what should this run do? it becomes WORKFLOW_CONTEXT for every job",
1128
+ onChange: (event) => setPrompt(event.target.value),
1129
+ onKeyDown: onPromptKeyDown
1130
+ }),
1131
+ /* @__PURE__ */ jsx("span", {
1132
+ className: "text-2xs text-doom-faint",
1133
+ children: "enter launches · shift+enter for a new line"
1134
+ })
1135
+ ]
1136
+ }),
1137
+ workflow.inputs.length === 0 ? null : /* @__PURE__ */ jsxs("div", {
1138
+ className: "flex flex-col gap-2",
1139
+ children: [/* @__PURE__ */ jsxs("div", {
1140
+ className: "flex items-center gap-2",
1141
+ children: [/* @__PURE__ */ jsx(FieldLabel, { children: "INPUTS" }), /* @__PURE__ */ jsxs("span", {
1142
+ className: "text-2xs text-doom-faint",
1143
+ children: [
1144
+ "workflow_dispatch · ",
1145
+ workflow.inputs.length,
1146
+ " declared"
1147
+ ]
1148
+ })]
1149
+ }), workflow.inputs.map((input) => /* @__PURE__ */ jsx(InputField, {
1150
+ input,
1151
+ value: inputs[input.name] ?? "",
1152
+ onChange: (next) => setInputs((current) => ({
1153
+ ...current,
1154
+ [input.name]: next
1155
+ }))
1156
+ }, input.name))]
1157
+ }),
1158
+ /* @__PURE__ */ jsxs("div", {
1159
+ className: "flex flex-wrap gap-5",
1160
+ children: [
1161
+ /* @__PURE__ */ jsxs("div", {
1162
+ className: "flex flex-col gap-1.5",
1163
+ children: [
1164
+ /* @__PURE__ */ jsx(FieldLabel, { children: "RUNNER" }),
1165
+ workflow.runners === void 0 || workflow.runners.length === 0 ? /* @__PURE__ */ jsx("span", {
1166
+ className: "flex h-7 items-center text-xs text-doom-dim",
1167
+ children: "whatever the workflow resolves"
1168
+ }) : /* @__PURE__ */ jsxs(Select, {
1169
+ value: runner,
1170
+ onValueChange: setRunner,
1171
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1172
+ "data-testid": "launch-runner",
1173
+ className: "h-7 w-full min-w-0 text-xs sm:min-w-[160px]",
1174
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1175
+ }), /* @__PURE__ */ jsx(SelectContent, { children: workflow.runners.map((choice) => /* @__PURE__ */ jsx(SelectItem, {
1176
+ value: choice,
1177
+ children: choice
1178
+ }, choice)) })]
1179
+ }),
1180
+ /* @__PURE__ */ jsx("span", {
1181
+ className: "text-2xs text-doom-faint",
1182
+ children: workflow.runners === void 0 ? "no runner map declared" : "the runners every step agrees on"
1183
+ })
1184
+ ]
1185
+ }),
1186
+ /* @__PURE__ */ jsxs("div", {
1187
+ className: "flex flex-col gap-1.5",
1188
+ children: [
1189
+ /* @__PURE__ */ jsx(FieldLabel, { children: "JOBS" }),
1190
+ /* @__PURE__ */ jsx("span", {
1191
+ className: "flex h-7 items-center text-xs text-doom-dim",
1192
+ children: workflow.jobs.map((job) => job.name).join(" → ") || "none declared"
1193
+ }),
1194
+ /* @__PURE__ */ jsx("span", {
1195
+ className: "text-2xs text-doom-faint",
1196
+ children: "in the order the file declares them"
1197
+ })
1198
+ ]
1199
+ }),
1200
+ /* @__PURE__ */ jsxs("div", {
1201
+ className: "flex flex-col gap-1.5",
1202
+ children: [
1203
+ /* @__PURE__ */ jsx(FieldLabel, { children: "CWD" }),
1204
+ /* @__PURE__ */ jsx("span", {
1205
+ className: "flex h-7 items-center text-xs text-doom-dim",
1206
+ children: cwd
1207
+ }),
1208
+ /* @__PURE__ */ jsx("span", {
1209
+ className: "text-2xs text-doom-faint",
1210
+ children: "the session's directory"
1211
+ })
1212
+ ]
1213
+ })
1214
+ ]
1215
+ }),
1216
+ /* @__PURE__ */ jsxs("div", {
1217
+ className: "flex flex-col gap-1 rounded-md border border-doom-border bg-doom-deep px-3 py-2",
1218
+ children: [/* @__PURE__ */ jsx(FieldLabel, { children: "SENT TO THE SESSION" }), /* @__PURE__ */ jsx("pre", {
1219
+ "data-testid": "launch-line",
1220
+ className: "whitespace-pre-wrap break-words text-xs text-doom-green",
1221
+ children: line
1222
+ })]
1223
+ }),
1224
+ problems.length === 0 ? null : /* @__PURE__ */ jsx("p", {
1225
+ "data-testid": "launch-problems",
1226
+ className: "text-xs text-doom-yellow",
1227
+ children: problems.join(" ")
1228
+ }),
1229
+ /* @__PURE__ */ jsxs(DialogFooter, {
1230
+ className: "flex-wrap sm:flex-nowrap",
1231
+ children: [
1232
+ /* @__PURE__ */ jsx("span", {
1233
+ className: "w-full text-2xs text-doom-faint sm:w-auto",
1234
+ children: "the run appears on the board as soon as it starts"
1235
+ }),
1236
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1237
+ /* @__PURE__ */ jsx(Button, {
1238
+ variant: "outline",
1239
+ size: "xs",
1240
+ "data-testid": "launch-cancel",
1241
+ onClick: onClose,
1242
+ children: "cancel"
1243
+ }),
1244
+ /* @__PURE__ */ jsx(Button, {
1245
+ variant: "primary",
1246
+ size: "xs",
1247
+ "data-testid": "launch-submit",
1248
+ disabled: problems.length > 0,
1249
+ onClick: launch,
1250
+ children: "launch"
1251
+ })
1252
+ ]
1253
+ })
1254
+ ] })]
1255
+ })
1256
+ });
1257
+ }
1258
+ //#endregion
1259
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/StepTerminalPanel.tsx
1260
+ /** The tab id doubles as the URL segment, so it stays plain and unique across plugins. */
1261
+ const TAB_ID_PREFIX = "workflows-step-";
1262
+ /** Keys that mean something to a terminal but nothing to a text field. */
1263
+ const CONTROL_KEYS = {
1264
+ Enter: "\r",
1265
+ Backspace: "",
1266
+ Tab: " ",
1267
+ Escape: "\x1B",
1268
+ ArrowUp: "\x1B[A",
1269
+ ArrowDown: "\x1B[B",
1270
+ ArrowRight: "\x1B[C",
1271
+ ArrowLeft: "\x1B[D"
1272
+ };
1273
+ const CTRL_C = "";
1274
+ const CTRL_D = "";
1275
+ const CTRL_A_CODE = 64;
1276
+ /** The temporary tab for one step; the host keeps this panel while the tab is open. */
1277
+ function stepTerminalTab(run, job, step) {
1278
+ const target = {
1279
+ workspace: run.workspace,
1280
+ runKey: run.runKey,
1281
+ job,
1282
+ ...step === void 0 ? {} : { step }
1283
+ };
1284
+ const label = step ?? job;
1285
+ return {
1286
+ id: `${TAB_ID_PREFIX}${run.workspace}-${run.runKey}-${job}${step === void 0 ? "" : `-${step}`}`.replace(/[^\w-]+/g, "-"),
1287
+ label: `${run.displayName} › ${label}`,
1288
+ panel: (props) => /* @__PURE__ */ jsx(StepTerminalPanel, {
1289
+ ...props,
1290
+ target
1291
+ })
1292
+ };
1293
+ }
1294
+ function ScreenLine$1({ line }) {
1295
+ if (line.length === 0) return /* @__PURE__ */ jsx("span", { className: "block h-[15px]" });
1296
+ return /* @__PURE__ */ jsx(AnsiLine, {
1297
+ line,
1298
+ className: "block whitespace-pre"
1299
+ });
1300
+ }
1301
+ /**
1302
+ * One run's terminal, under a header naming the step the reader opened it from.
1303
+ *
1304
+ * A run has one terminal, shared by its steps in sequence, so this is the run's
1305
+ * screen and the header says which step is on it. Watching is free; typing
1306
+ * takes the keyboard first, because the pane belongs to whoever is answering
1307
+ * whatever the nested agent asked.
1308
+ */
1309
+ function StepTerminalPanel({ sessionId, target }) {
1310
+ const run = useStore(workflows.store, (state) => workflows.select(state, sessionId).runs).find((candidate) => candidate.workspace === target.workspace && candidate.runKey === target.runKey);
1311
+ const [lines, setLines] = useState([]);
1312
+ const [capabilities, setCapabilities] = useState();
1313
+ const [ended, setEnded] = useState(false);
1314
+ const [token, setToken] = useState();
1315
+ const [notice, setNotice] = useState();
1316
+ const screenRef = useRef(null);
1317
+ useEffect(() => {
1318
+ setEnded(false);
1319
+ return followScreen(target.workspace, target.runKey, (event) => {
1320
+ setLines(event.lines);
1321
+ setCapabilities(event.capabilities);
1322
+ if (event.ended === true) setEnded(true);
1323
+ }, sessionId);
1324
+ }, [
1325
+ sessionId,
1326
+ target.workspace,
1327
+ target.runKey
1328
+ ]);
1329
+ useEffect(() => {
1330
+ if (token === void 0) return;
1331
+ return () => {
1332
+ releaseControl(target.workspace, target.runKey, token, sessionId);
1333
+ };
1334
+ }, [
1335
+ sessionId,
1336
+ token,
1337
+ target.workspace,
1338
+ target.runKey
1339
+ ]);
1340
+ useEffect(() => {
1341
+ screenRef.current?.scrollTo({ top: screenRef.current.scrollHeight });
1342
+ }, [lines]);
1343
+ const arm = useCallback(async () => {
1344
+ const result = await takeControl(target.workspace, target.runKey, void 0, sessionId);
1345
+ if (result.held && result.token !== void 0) {
1346
+ setToken(result.token);
1347
+ setNotice(void 0);
1348
+ return;
1349
+ }
1350
+ setNotice(result.reason ?? "The keyboard is not available for this run.");
1351
+ }, [
1352
+ sessionId,
1353
+ target.workspace,
1354
+ target.runKey
1355
+ ]);
1356
+ const disarm = useCallback(async () => {
1357
+ if (token !== void 0) await releaseControl(target.workspace, target.runKey, token, sessionId);
1358
+ setToken(void 0);
1359
+ }, [
1360
+ sessionId,
1361
+ token,
1362
+ target.workspace,
1363
+ target.runKey
1364
+ ]);
1365
+ const type = async (data) => {
1366
+ if (token === void 0) return;
1367
+ const { error } = await sendKeys(target.workspace, target.runKey, token, data, sessionId);
1368
+ if (error !== void 0) {
1369
+ setNotice(error);
1370
+ setToken(void 0);
1371
+ }
1372
+ };
1373
+ const onKeyDown = (event) => {
1374
+ if (token === void 0) return;
1375
+ if (event.key === "Escape" && !event.ctrlKey) {
1376
+ event.preventDefault();
1377
+ disarm();
1378
+ return;
1379
+ }
1380
+ const control = CONTROL_KEYS[event.key];
1381
+ if (control !== void 0) {
1382
+ event.preventDefault();
1383
+ type(control);
1384
+ return;
1385
+ }
1386
+ if (event.ctrlKey && event.key.length === 1) {
1387
+ event.preventDefault();
1388
+ const letter = event.key.toUpperCase().charCodeAt(0);
1389
+ type(event.key === "c" ? CTRL_C : event.key === "d" ? CTRL_D : String.fromCharCode(letter - CTRL_A_CODE));
1390
+ return;
1391
+ }
1392
+ if (event.key.length === 1 && !event.metaKey && !event.altKey) {
1393
+ event.preventDefault();
1394
+ type(event.key);
1395
+ }
1396
+ };
1397
+ const held = token !== void 0;
1398
+ const writable = capabilities?.writable === true;
1399
+ return /* @__PURE__ */ jsxs("div", {
1400
+ "data-testid": "step-terminal-panel",
1401
+ className: "flex min-h-0 flex-1 flex-col px-[26px] py-[18px]",
1402
+ children: [
1403
+ /* @__PURE__ */ jsxs("div", {
1404
+ "data-testid": "step-terminal-head",
1405
+ className: "flex items-center gap-2.5 pb-3",
1406
+ children: [
1407
+ /* @__PURE__ */ jsx("span", {
1408
+ className: "shrink-0 truncate text-sm font-bold text-doom-hi",
1409
+ children: run?.displayName ?? target.runKey
1410
+ }),
1411
+ /* @__PURE__ */ jsx("span", {
1412
+ className: "text-xs text-doom-faint",
1413
+ children: "›"
1414
+ }),
1415
+ /* @__PURE__ */ jsx("span", {
1416
+ className: "shrink-0 truncate text-sm font-bold text-doom-blue",
1417
+ children: target.job
1418
+ }),
1419
+ target.step === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
1420
+ className: "text-xs text-doom-faint",
1421
+ children: "›"
1422
+ }), /* @__PURE__ */ jsx("span", {
1423
+ className: "min-w-0 truncate text-sm text-doom-text",
1424
+ children: target.step
1425
+ })] }),
1426
+ run === void 0 ? null : /* @__PURE__ */ jsx(StatusBadge, {
1427
+ tone: run.stage === "running" ? "running" : run.stage === "error" ? "error" : "ok",
1428
+ "data-testid": "step-terminal-stage",
1429
+ children: run.stage
1430
+ }),
1431
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1432
+ /* @__PURE__ */ jsx(Badge, {
1433
+ size: "xs",
1434
+ tone: held ? "blue" : "neutral",
1435
+ "data-testid": "step-terminal-control",
1436
+ className: held ? "border-doom-blue/60 bg-doom-tint-blue text-doom-hi" : "",
1437
+ children: held ? "keyboard yours" : "watching"
1438
+ }),
1439
+ writable ? /* @__PURE__ */ jsx(Button, {
1440
+ variant: held ? "outline" : "primary",
1441
+ size: "xs",
1442
+ "data-testid": "step-terminal-arm",
1443
+ onClick: () => void (held ? disarm() : arm()),
1444
+ children: held ? "release keyboard" : "take control"
1445
+ }) : null
1446
+ ]
1447
+ }),
1448
+ /* @__PURE__ */ jsxs("div", {
1449
+ ref: screenRef,
1450
+ "data-testid": "step-terminal-screen",
1451
+ tabIndex: 0,
1452
+ onKeyDown,
1453
+ className: `min-h-0 flex-1 overflow-y-auto rounded-md border bg-doom-deep px-4 py-3 font-mono text-sm leading-tight text-doom-text outline-none ${held ? "border-doom-blue/60" : "border-doom-border"}`,
1454
+ children: [lines.length === 0 ? /* @__PURE__ */ jsx("span", {
1455
+ className: "text-xs text-doom-faint",
1456
+ children: capabilities?.readable === false ? capabilities.reason ?? "This run has no terminal to read." : "waiting for the run to paint…"
1457
+ }) : lines.map((line, index) => /* @__PURE__ */ jsx(ScreenLine$1, { line }, index)), held && !ended ? /* @__PURE__ */ jsx(StreamCursor, { className: "mt-0.5 h-[12px] w-1.5" }) : null]
1458
+ }),
1459
+ /* @__PURE__ */ jsxs("div", {
1460
+ className: "flex items-center gap-3 pt-2.5",
1461
+ children: [
1462
+ /* @__PURE__ */ jsx("span", {
1463
+ "data-testid": "step-terminal-hint",
1464
+ className: "text-2xs text-doom-faint",
1465
+ children: held ? "keys go to the run · ctrl-c and ctrl-d pass through · esc releases the keyboard" : capabilities?.writable === false ? capabilities.reason ?? "this run cannot be typed into" : "read-only until you take control · the run keeps going either way"
1466
+ }),
1467
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1468
+ ended ? /* @__PURE__ */ jsx("span", {
1469
+ "data-testid": "step-terminal-ended",
1470
+ className: "text-2xs text-doom-faint",
1471
+ children: "the run has settled; this is its last screen"
1472
+ }) : null,
1473
+ notice === void 0 ? null : /* @__PURE__ */ jsx("span", {
1474
+ "data-testid": "step-terminal-notice",
1475
+ className: "text-2xs text-doom-yellow",
1476
+ children: notice
1477
+ })
1478
+ ]
1479
+ })
1480
+ ]
1481
+ });
1482
+ }
1483
+ //#endregion
1484
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/WorkflowCatalogDrawer.tsx
1485
+ const INPUT_TAG = "INPUT";
1486
+ /** The unfolded row: what the file declares, read-only, as the TUI catalog's tabs show it. */
1487
+ function Detail({ workflow }) {
1488
+ const rows = [
1489
+ ["triggers", workflow.triggers.length > 0 ? workflow.triggers.join(", ") : "none declared"],
1490
+ ["inputs", workflow.inputs.length > 0 ? workflow.inputs.map((input) => `${input.name}${input.required ? "*" : ""}${input.default ? ` = ${input.default}` : ""}`).join(", ") : "none declared"],
1491
+ ["jobs", workflow.jobs.length > 0 ? workflow.jobs.map((job) => job.name).join(" → ") : "none declared"],
1492
+ ["artifacts", workflow.artifacts.length > 0 ? workflow.artifacts.map((entry) => entry.path).join(", ") : "none declared"],
1493
+ ["runners", workflow.runners === void 0 ? "any runner" : workflow.runners.join(", ") || "none in common"],
1494
+ ["file", workflow.relativePath]
1495
+ ];
1496
+ return /* @__PURE__ */ jsx("dl", {
1497
+ "data-testid": `catalog-detail-${workflow.name}`,
1498
+ className: "flex flex-col gap-1 pt-1 text-2xs",
1499
+ children: rows.map(([label, value]) => /* @__PURE__ */ jsxs("div", {
1500
+ className: "flex gap-2",
1501
+ children: [/* @__PURE__ */ jsx("dt", {
1502
+ className: "w-16 shrink-0 text-doom-faint",
1503
+ children: label
1504
+ }), /* @__PURE__ */ jsx("dd", {
1505
+ className: "min-w-0 break-words text-doom-dim",
1506
+ children: value
1507
+ })]
1508
+ }, label))
1509
+ });
1510
+ }
1511
+ function WorkflowRow({ workflow, selected, inspected, onSelect, onLaunch, onInspect }) {
1512
+ const jobs = workflow.jobs.length;
1513
+ return /* @__PURE__ */ jsxs("div", {
1514
+ role: "option",
1515
+ "aria-selected": selected,
1516
+ tabIndex: -1,
1517
+ "data-testid": `catalog-workflow-${workflow.name}`,
1518
+ "data-selected": selected,
1519
+ onClick: onSelect,
1520
+ onDoubleClick: onLaunch,
1521
+ className: `flex cursor-pointer flex-col gap-1 border-l-2 px-4 py-2 ${selected ? "border-doom-blue bg-doom-panel" : "border-transparent hover:bg-doom-panel/60"}`,
1522
+ children: [
1523
+ /* @__PURE__ */ jsxs("div", {
1524
+ className: "flex min-w-0 items-center gap-2",
1525
+ children: [
1526
+ /* @__PURE__ */ jsx("span", {
1527
+ className: "truncate text-sm font-bold text-doom-hi",
1528
+ children: workflow.name
1529
+ }),
1530
+ workflow.tags.map((tag) => /* @__PURE__ */ jsx(Badge, {
1531
+ size: "xs",
1532
+ className: "shrink-0",
1533
+ children: tag
1534
+ }, tag)),
1535
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1536
+ /* @__PURE__ */ jsx("span", {
1537
+ className: `shrink-0 text-2xs ${workflow.error === void 0 ? "text-doom-cyan" : "text-doom-red"}`,
1538
+ children: workflow.error === void 0 ? `${jobs} job${jobs === 1 ? "" : "s"}` : "unreadable"
1539
+ })
1540
+ ]
1541
+ }),
1542
+ /* @__PURE__ */ jsx("span", {
1543
+ className: "truncate text-xs text-doom-dim",
1544
+ children: workflow.description || workflow.relativePath
1545
+ }),
1546
+ workflow.error === void 0 ? /* @__PURE__ */ jsxs("span", {
1547
+ className: "truncate text-2xs text-doom-faint",
1548
+ children: [
1549
+ workflow.inputs.length,
1550
+ " input",
1551
+ workflow.inputs.length === 1 ? "" : "s",
1552
+ " ·",
1553
+ " ",
1554
+ workflow.runners === void 0 ? "any runner" : workflow.runners.join(", ") || "no runner in common",
1555
+ workflow.artifacts.length > 0 ? ` · ${workflow.artifacts.length} artifacts` : ""
1556
+ ]
1557
+ }) : /* @__PURE__ */ jsx("span", {
1558
+ className: "truncate text-2xs text-doom-red",
1559
+ children: workflow.error
1560
+ }),
1561
+ selected ? /* @__PURE__ */ jsxs("div", {
1562
+ className: "flex items-center gap-3 pt-0.5",
1563
+ children: [/* @__PURE__ */ jsxs(Button, {
1564
+ variant: "link",
1565
+ size: "xs",
1566
+ "data-testid": `catalog-launch-${workflow.name}`,
1567
+ className: "px-0",
1568
+ disabled: workflow.error !== void 0,
1569
+ onClick: (event) => {
1570
+ event.stopPropagation();
1571
+ onLaunch();
1572
+ },
1573
+ children: ["launch ", /* @__PURE__ */ jsx(Kbd, { children: "↵" })]
1574
+ }), /* @__PURE__ */ jsxs(Button, {
1575
+ variant: "link",
1576
+ size: "xs",
1577
+ "data-testid": `catalog-inspect-${workflow.name}`,
1578
+ className: "px-0 text-doom-dim",
1579
+ onClick: (event) => {
1580
+ event.stopPropagation();
1581
+ onInspect();
1582
+ },
1583
+ children: ["inspect ", /* @__PURE__ */ jsx(Kbd, { children: "i" })]
1584
+ })]
1585
+ }) : null,
1586
+ selected && inspected ? /* @__PURE__ */ jsx(Detail, { workflow }) : null
1587
+ ]
1588
+ });
1589
+ }
1590
+ /**
1591
+ * The workflow catalog, in the workflows tab's drawer: every workflow the
1592
+ * session's directory declares, with a filter and the keys the TUI catalog
1593
+ * also answers to. Launching hands the chosen workflow to the dialog.
1594
+ */
1595
+ function WorkflowCatalogDrawer({ sessionId, onClose, onLaunch }) {
1596
+ const state = useStore(catalog.store, (current) => catalog.select(current, sessionId));
1597
+ const shown = filterCatalog(state.workflows, state.filter);
1598
+ const selected = shown.find((workflow) => workflow.path === state.selected) ?? shown[0];
1599
+ const move = (delta) => {
1600
+ if (shown.length === 0) return;
1601
+ const index = selected === void 0 ? -1 : shown.findIndex((workflow) => workflow.path === selected.path);
1602
+ const next = shown[(index + delta + shown.length) % shown.length];
1603
+ if (next) selectWorkflow(sessionId, next.path);
1604
+ };
1605
+ const onKeyDown = (event) => {
1606
+ const typing = event.target.tagName === INPUT_TAG;
1607
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1608
+ event.preventDefault();
1609
+ move(event.key === "ArrowDown" ? 1 : -1);
1610
+ return;
1611
+ }
1612
+ if (event.key === "Escape") {
1613
+ event.preventDefault();
1614
+ onClose();
1615
+ return;
1616
+ }
1617
+ if (selected === void 0) return;
1618
+ if (event.key === "Enter") {
1619
+ event.preventDefault();
1620
+ if (selected.error === void 0) onLaunch(selected);
1621
+ return;
1622
+ }
1623
+ if (typing) return;
1624
+ if (event.key === "i") toggleInspect(sessionId, selected.path);
1625
+ };
1626
+ return /* @__PURE__ */ jsxs("aside", {
1627
+ "data-testid": "workflow-catalog-drawer",
1628
+ tabIndex: 0,
1629
+ onKeyDown,
1630
+ className: "flex w-[min(440px,calc(100vw-24px))] shrink-0 flex-col overflow-hidden border-l border-doom-border bg-doom-rail outline-none",
1631
+ children: [
1632
+ /* @__PURE__ */ jsxs("div", {
1633
+ className: "flex h-11 shrink-0 items-center gap-2.5 border-b border-doom-border px-4",
1634
+ children: [
1635
+ /* @__PURE__ */ jsx("span", {
1636
+ className: "text-base font-bold text-doom-hi",
1637
+ children: "workflow catalog"
1638
+ }),
1639
+ /* @__PURE__ */ jsxs(Badge, {
1640
+ size: "xs",
1641
+ "data-testid": "catalog-count",
1642
+ children: [
1643
+ state.workflows.length,
1644
+ " workflow",
1645
+ state.workflows.length === 1 ? "" : "s"
1646
+ ]
1647
+ }),
1648
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1649
+ /* @__PURE__ */ jsx(Kbd, { children: "SPC w l" }),
1650
+ /* @__PURE__ */ jsx(Button, {
1651
+ variant: "ghost",
1652
+ size: "icon",
1653
+ "data-testid": "catalog-close",
1654
+ title: "close the catalog",
1655
+ onClick: onClose,
1656
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3 w-3" })
1657
+ })
1658
+ ]
1659
+ }),
1660
+ /* @__PURE__ */ jsx("div", {
1661
+ className: "px-4 pt-2.5 pb-1.5",
1662
+ children: /* @__PURE__ */ jsx(Input, {
1663
+ "data-testid": "catalog-filter",
1664
+ value: state.filter,
1665
+ placeholder: "filter by name, tag, or job…",
1666
+ autoFocus: true,
1667
+ onChange: (event) => setCatalogFilter(sessionId, event.target.value)
1668
+ })
1669
+ }),
1670
+ /* @__PURE__ */ jsxs("div", {
1671
+ role: "listbox",
1672
+ className: "flex min-h-0 flex-1 flex-col overflow-y-auto pb-2",
1673
+ children: [shown.length === 0 ? /* @__PURE__ */ jsx("p", {
1674
+ "data-testid": "catalog-empty",
1675
+ className: "px-4 py-3 text-xs text-doom-faint",
1676
+ children: state.warning ?? (state.workflows.length === 0 ? "no workflows found for this session; add one under automations/workflows" : "nothing matches the filter")
1677
+ }) : null, shown.map((workflow) => /* @__PURE__ */ jsx(WorkflowRow, {
1678
+ workflow,
1679
+ selected: selected?.path === workflow.path,
1680
+ inspected: state.inspected === workflow.path,
1681
+ onSelect: () => selectWorkflow(sessionId, workflow.path),
1682
+ onLaunch: () => onLaunch(workflow),
1683
+ onInspect: () => toggleInspect(sessionId, workflow.path)
1684
+ }, workflow.path))]
1685
+ }),
1686
+ /* @__PURE__ */ jsxs("div", {
1687
+ className: "flex h-8 shrink-0 items-center justify-between border-t border-doom-border-soft bg-doom-deep px-4",
1688
+ children: [/* @__PURE__ */ jsx("span", {
1689
+ className: "text-2xs text-doom-faint",
1690
+ children: "↑↓ choose · enter launch · i inspect · esc close"
1691
+ }), /* @__PURE__ */ jsx("span", {
1692
+ className: "text-2xs text-doom-faint",
1693
+ children: state.cwd
1694
+ })]
1695
+ })
1696
+ ]
1697
+ });
1698
+ }
1699
+ //#endregion
1700
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/WorkflowsPanel.tsx
1701
+ /** One workflows tab per session; the surface is singular, so the id needs nothing else. */
1702
+ const WORKFLOWS_TAB_ID = "workflows-runs";
1703
+ const TICK_MS$1 = 1e4;
1704
+ const STATE_ICON = {
1705
+ running: {
1706
+ glyph: "●",
1707
+ className: "text-doom-blue"
1708
+ },
1709
+ resumed: {
1710
+ glyph: "●",
1711
+ className: "text-doom-blue"
1712
+ },
1713
+ completed: {
1714
+ glyph: "✓",
1715
+ className: "text-doom-green"
1716
+ },
1717
+ failed: {
1718
+ glyph: "✕",
1719
+ className: "text-doom-red"
1720
+ },
1721
+ skipped: {
1722
+ glyph: "↷",
1723
+ className: "text-doom-faint"
1724
+ },
1725
+ pause_requested: {
1726
+ glyph: "!",
1727
+ className: "text-doom-yellow"
1728
+ },
1729
+ paused: {
1730
+ glyph: "!",
1731
+ className: "text-doom-yellow"
1732
+ }
1733
+ };
1734
+ const ACTIVE_STATES = /* @__PURE__ */ new Set([
1735
+ "running",
1736
+ "resumed",
1737
+ "pause_requested",
1738
+ "paused"
1739
+ ]);
1740
+ function spanDuration(startedAt, endedAt, now) {
1741
+ if (startedAt === void 0) return void 0;
1742
+ const start = Date.parse(startedAt);
1743
+ if (!Number.isFinite(start)) return void 0;
1744
+ const end = endedAt === void 0 ? now : Date.parse(endedAt);
1745
+ if (!Number.isFinite(end)) return void 0;
1746
+ return formatRunDuration(Math.max(0, end - start));
1747
+ }
1748
+ function attentionFor(run) {
1749
+ if (run.stage === "error") {
1750
+ const cause = run.errorMessage ?? "the run ended in the error stage";
1751
+ return {
1752
+ kind: "error",
1753
+ text: run.failedJob === void 0 ? cause : `job '${run.failedJob}' failed: ${cause}`
1754
+ };
1755
+ }
1756
+ if (run.executionState === "paused" || run.executionState === "pause_requested") return {
1757
+ kind: "paused",
1758
+ text: "the run is paused and will not move until it is resumed"
1759
+ };
1760
+ }
1761
+ function runDot(run) {
1762
+ if (run.stage === "error") return "red";
1763
+ if (run.executionState === "paused" || run.executionState === "pause_requested") return "yellow";
1764
+ if (run.stage === "running") return "blue";
1765
+ if (run.outcome === "success") return "green";
1766
+ return "neutral";
1767
+ }
1768
+ function runState(run) {
1769
+ if (run.stage === "error") return "failed";
1770
+ if (run.executionState === "paused" || run.executionState === "pause_requested") return "paused";
1771
+ if (run.stage === "running") return "running";
1772
+ return run.outcome ?? run.stage;
1773
+ }
1774
+ function runPriority(run) {
1775
+ if (attentionFor(run) !== void 0) return 0;
1776
+ if (run.stage === "running") return 1;
1777
+ return 2;
1778
+ }
1779
+ function runSearchText(run) {
1780
+ return [
1781
+ run.displayName,
1782
+ run.workflowName,
1783
+ run.stage,
1784
+ run.outcome,
1785
+ run.executionState,
1786
+ run.position?.job,
1787
+ run.position?.step
1788
+ ].filter((part) => typeof part === "string").join(" ").toLowerCase();
1789
+ }
1790
+ function WorkflowPicker({ runs, selected, onSelect }) {
1791
+ const [open, setOpen] = useState(false);
1792
+ const [query, setQuery] = useState("");
1793
+ const matching = useMemo(() => [...runs].sort((left, right) => runPriority(left) - runPriority(right)), [runs]).filter((run) => runSearchText(run).includes(query.trim().toLowerCase()));
1794
+ const sections = [
1795
+ {
1796
+ label: "needs you",
1797
+ runs: matching.filter((run) => attentionFor(run) !== void 0)
1798
+ },
1799
+ {
1800
+ label: "active",
1801
+ runs: matching.filter((run) => attentionFor(run) === void 0 && run.stage === "running")
1802
+ },
1803
+ {
1804
+ label: "recent",
1805
+ runs: matching.filter((run) => run.stage !== "running" && attentionFor(run) === void 0)
1806
+ }
1807
+ ].filter((section) => section.runs.length > 0);
1808
+ return /* @__PURE__ */ jsxs(Popover, {
1809
+ open,
1810
+ onOpenChange: (next) => {
1811
+ setOpen(next);
1812
+ if (!next) setQuery("");
1813
+ },
1814
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
1815
+ asChild: true,
1816
+ children: /* @__PURE__ */ jsxs(Button, {
1817
+ variant: "outline",
1818
+ size: "sm",
1819
+ "data-testid": "workflow-picker",
1820
+ className: "h-8 w-full max-w-[360px] justify-start border-doom-border bg-doom-deep px-2.5",
1821
+ children: [
1822
+ /* @__PURE__ */ jsx(SearchIcon, { className: "h-3 w-3 shrink-0 text-doom-faint" }),
1823
+ /* @__PURE__ */ jsx("span", {
1824
+ className: "min-w-0 flex-1 truncate text-left font-bold text-doom-hi",
1825
+ children: selected.displayName
1826
+ }),
1827
+ /* @__PURE__ */ jsxs("span", {
1828
+ className: "shrink-0 text-2xs font-normal text-doom-faint",
1829
+ children: [runs.length, " workflows"]
1830
+ }),
1831
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "h-3 w-3 shrink-0 text-doom-dim" })
1832
+ ]
1833
+ })
1834
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
1835
+ side: "bottom",
1836
+ align: "start",
1837
+ "data-testid": "workflow-picker-popover",
1838
+ className: "w-[600px]",
1839
+ children: [
1840
+ /* @__PURE__ */ jsx(PopoverHeader, { children: /* @__PURE__ */ jsxs("span", {
1841
+ className: "relative flex w-full items-center",
1842
+ children: [/* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute left-2.5 h-3 w-3 text-doom-blue" }), /* @__PURE__ */ jsx(Input, {
1843
+ autoFocus: true,
1844
+ size: "sm",
1845
+ "data-testid": "workflow-picker-search",
1846
+ value: query,
1847
+ onChange: (event) => setQuery(event.target.value),
1848
+ placeholder: `Search ${String(runs.length)} workflows by name, job, or status`,
1849
+ className: "w-full pl-8"
1850
+ })]
1851
+ }) }),
1852
+ /* @__PURE__ */ jsx("div", {
1853
+ className: "grid max-h-[360px] grid-cols-1 gap-2 overflow-y-auto p-2 sm:grid-cols-2",
1854
+ children: sections.length === 0 ? /* @__PURE__ */ jsx("span", {
1855
+ className: "px-2 py-5 text-center text-xs text-doom-faint sm:col-span-2",
1856
+ children: "no matching workflows"
1857
+ }) : sections.map((section) => /* @__PURE__ */ jsxs("div", {
1858
+ className: "flex min-w-0 flex-col gap-0.5",
1859
+ children: [/* @__PURE__ */ jsxs("span", {
1860
+ className: "px-2 py-1 text-2xs font-bold uppercase tracking-wider text-doom-faint",
1861
+ children: [
1862
+ section.label,
1863
+ " · ",
1864
+ section.runs.length
1865
+ ]
1866
+ }), section.runs.map((run) => {
1867
+ const active = workflowRunIdentity(run) === workflowRunIdentity(selected);
1868
+ return /* @__PURE__ */ jsxs("button", {
1869
+ type: "button",
1870
+ "data-testid": `workflow-option-${run.runKey}`,
1871
+ "data-run-stage": run.stage,
1872
+ "data-active": active,
1873
+ onClick: () => {
1874
+ onSelect(run);
1875
+ setOpen(false);
1876
+ },
1877
+ className: cn("flex min-w-0 flex-col gap-0.5 rounded px-2 py-1.5 text-left hover:bg-doom-deep", active && "bg-doom-tint-blue ring-1 ring-inset ring-doom-blue/50"),
1878
+ children: [/* @__PURE__ */ jsxs("span", {
1879
+ className: "flex min-w-0 items-center gap-2",
1880
+ children: [
1881
+ /* @__PURE__ */ jsx(Dot, {
1882
+ tone: runDot(run),
1883
+ pulse: run.stage === "running"
1884
+ }),
1885
+ /* @__PURE__ */ jsx("span", {
1886
+ className: cn("min-w-0 flex-1 truncate text-xs", active && "font-bold text-doom-hi"),
1887
+ children: run.displayName
1888
+ }),
1889
+ /* @__PURE__ */ jsx("span", {
1890
+ className: "shrink-0 text-2xs text-doom-faint",
1891
+ children: runState(run)
1892
+ })
1893
+ ]
1894
+ }), /* @__PURE__ */ jsx("span", {
1895
+ className: "truncate pl-3.5 text-2xs text-doom-dim",
1896
+ children: [run.position?.job, run.position?.step].filter(Boolean).join(" · ") || "settled"
1897
+ })]
1898
+ }, workflowRunIdentity(run));
1899
+ })]
1900
+ }, section.label))
1901
+ }),
1902
+ /* @__PURE__ */ jsxs(PopoverFooter, { children: [/* @__PURE__ */ jsx("span", { children: "needs you and active stay first" }), /* @__PURE__ */ jsx("span", { children: "esc closes" })] })
1903
+ ]
1904
+ })]
1905
+ });
1906
+ }
1907
+ function SelectedAttention({ run }) {
1908
+ const attention = attentionFor(run);
1909
+ if (attention === void 0) return null;
1910
+ return /* @__PURE__ */ jsxs("div", {
1911
+ "data-testid": "workflow-needs-you",
1912
+ className: cn("flex items-center gap-2 rounded border px-3 py-2", attention.kind === "error" ? "border-doom-edge-red bg-doom-tint-red/40" : "border-doom-edge-yellow bg-doom-tint-yellow/40"),
1913
+ children: [
1914
+ /* @__PURE__ */ jsx(StatusBadge, {
1915
+ size: "xs",
1916
+ tone: attention.kind === "error" ? "error" : "running",
1917
+ children: attention.kind === "error" ? "ERROR" : "PAUSED"
1918
+ }),
1919
+ /* @__PURE__ */ jsxs("span", {
1920
+ "data-testid": `needs-card-${run.runKey}`,
1921
+ className: "min-w-0 flex-1 truncate text-xs text-doom-text",
1922
+ children: [
1923
+ run.displayName,
1924
+ ": ",
1925
+ attention.text
1926
+ ]
1927
+ }),
1928
+ /* @__PURE__ */ jsx("span", {
1929
+ className: "shrink-0 text-2xs text-doom-faint",
1930
+ children: attention.kind === "error" ? "recover from the owning session" : "resume from the owning session"
1931
+ })
1932
+ ]
1933
+ });
1934
+ }
1935
+ function JobRow({ job, now, selected, onSelect }) {
1936
+ const icon = STATE_ICON[job.status];
1937
+ return /* @__PURE__ */ jsxs(OptionRow, {
1938
+ density: "compact",
1939
+ active: selected,
1940
+ "data-testid": `job-row-${job.name}`,
1941
+ "data-job-status": job.status,
1942
+ onClick: onSelect,
1943
+ className: cn("gap-2 rounded px-2 py-1.5", !selected && "hover:bg-doom-deep"),
1944
+ children: [
1945
+ /* @__PURE__ */ jsx("span", {
1946
+ className: `w-3 shrink-0 text-xs ${icon.className}`,
1947
+ children: icon.glyph
1948
+ }),
1949
+ /* @__PURE__ */ jsx(OptionLabel, {
1950
+ density: "compact",
1951
+ className: cn("text-xs", selected ? "text-doom-hi" : "text-doom-text"),
1952
+ children: job.name
1953
+ }),
1954
+ /* @__PURE__ */ jsx("span", {
1955
+ className: "shrink-0 text-2xs text-doom-faint",
1956
+ children: spanDuration(job.startedAt, job.endedAt, now) ?? ""
1957
+ })
1958
+ ]
1959
+ });
1960
+ }
1961
+ function StepRow({ step, now, selected, onSelect }) {
1962
+ const icon = STATE_ICON[step.status];
1963
+ return /* @__PURE__ */ jsxs("button", {
1964
+ type: "button",
1965
+ "data-testid": `step-row-${step.name}`,
1966
+ "data-step-status": step.status,
1967
+ "data-active": selected,
1968
+ onClick: onSelect,
1969
+ className: cn("flex w-full cursor-pointer flex-col gap-0.5 rounded px-2 py-1.5 text-left hover:bg-doom-deep", selected && "bg-doom-tint-blue ring-1 ring-inset ring-doom-blue/40"),
1970
+ children: [/* @__PURE__ */ jsxs("span", {
1971
+ className: "flex items-center gap-2",
1972
+ children: [
1973
+ /* @__PURE__ */ jsx("span", {
1974
+ className: `w-3 shrink-0 text-xs ${icon.className}`,
1975
+ children: icon.glyph
1976
+ }),
1977
+ /* @__PURE__ */ jsx("span", {
1978
+ className: cn("min-w-0 flex-1 truncate text-2xs", step.status === "failed" ? "text-doom-red" : "text-doom-text"),
1979
+ children: step.name
1980
+ }),
1981
+ /* @__PURE__ */ jsx("span", {
1982
+ className: "shrink-0 text-2xs text-doom-faint",
1983
+ children: spanDuration(step.startedAt, step.endedAt, now) ?? ""
1984
+ })
1985
+ ]
1986
+ }), step.reason === void 0 ? null : /* @__PURE__ */ jsx("span", {
1987
+ className: cn("truncate pl-5 text-2xs", step.status === "failed" ? "text-doom-red" : "text-doom-faint"),
1988
+ children: step.reason
1989
+ })]
1990
+ });
1991
+ }
1992
+ function ScreenLine({ line }) {
1993
+ if (line.length === 0) return /* @__PURE__ */ jsx("span", { className: "block h-[15px]" });
1994
+ return /* @__PURE__ */ jsx(AnsiLine, {
1995
+ line,
1996
+ className: "block whitespace-pre"
1997
+ });
1998
+ }
1999
+ function InlineStepOutput({ run, job, step, sessionId, onOpenTerminal }) {
2000
+ const [lines, setLines] = useState([]);
2001
+ const [capabilities, setCapabilities] = useState();
2002
+ const [ended, setEnded] = useState(false);
2003
+ const screenRef = useRef(null);
2004
+ useEffect(() => {
2005
+ setLines([]);
2006
+ setCapabilities(void 0);
2007
+ setEnded(false);
2008
+ return followScreen(run.workspace, run.runKey, (event) => {
2009
+ setLines(event.lines);
2010
+ setCapabilities(event.capabilities);
2011
+ if (event.ended === true) setEnded(true);
2012
+ }, sessionId);
2013
+ }, [
2014
+ run.workspace,
2015
+ run.runKey,
2016
+ sessionId
2017
+ ]);
2018
+ useEffect(() => {
2019
+ screenRef.current?.scrollTo({ top: screenRef.current.scrollHeight });
2020
+ }, [lines]);
2021
+ return /* @__PURE__ */ jsxs("div", {
2022
+ "data-testid": "workflow-inline-output",
2023
+ className: "flex min-h-0 flex-1 flex-col bg-doom-deep",
2024
+ children: [
2025
+ /* @__PURE__ */ jsxs("div", {
2026
+ className: "flex h-9 shrink-0 items-center gap-2 border-b border-doom-border-soft px-3",
2027
+ children: [
2028
+ /* @__PURE__ */ jsx(Dot, {
2029
+ tone: ended ? "neutral" : "blue",
2030
+ pulse: !ended && run.stage === "running"
2031
+ }),
2032
+ /* @__PURE__ */ jsx("span", {
2033
+ className: cn("text-2xs font-bold", ended ? "text-doom-faint" : "text-doom-blue"),
2034
+ children: ended ? "FINAL OUTPUT" : "LIVE OUTPUT"
2035
+ }),
2036
+ /* @__PURE__ */ jsx("span", {
2037
+ className: "min-w-0 truncate text-xs font-bold text-doom-hi",
2038
+ children: step?.name ?? job.name
2039
+ }),
2040
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
2041
+ /* @__PURE__ */ jsx("span", {
2042
+ className: "text-2xs text-doom-faint",
2043
+ children: ended ? "settled" : "following · 500ms"
2044
+ }),
2045
+ /* @__PURE__ */ jsx(Button, {
2046
+ variant: "outline",
2047
+ size: "xs",
2048
+ "data-testid": "workflow-open-terminal",
2049
+ onClick: onOpenTerminal,
2050
+ children: "open terminal"
2051
+ })
2052
+ ]
2053
+ }),
2054
+ /* @__PURE__ */ jsxs("div", {
2055
+ ref: screenRef,
2056
+ "data-testid": "workflow-inline-screen",
2057
+ className: "min-h-0 flex-1 overflow-y-auto px-4 py-3 font-mono text-sm leading-tight text-doom-text",
2058
+ children: [lines.length === 0 ? /* @__PURE__ */ jsx("span", {
2059
+ className: "text-xs text-doom-faint",
2060
+ children: capabilities?.readable === false ? capabilities.reason ?? "This run has no terminal to read." : "waiting for the run to paint…"
2061
+ }) : lines.map((line, index) => /* @__PURE__ */ jsx(ScreenLine, { line }, index)), !ended && run.stage === "running" ? /* @__PURE__ */ jsx(StreamCursor, { className: "mt-0.5 h-[12px] w-1.5" }) : null]
2062
+ }),
2063
+ /* @__PURE__ */ jsxs("div", {
2064
+ className: "flex h-7 shrink-0 items-center gap-2 border-t border-doom-border-soft px-3 text-2xs text-doom-faint",
2065
+ children: [
2066
+ /* @__PURE__ */ jsx("span", { children: "output follows automatically · select another step to inspect its current screen" }),
2067
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
2068
+ /* @__PURE__ */ jsx("span", { children: "last 48 lines" })
2069
+ ]
2070
+ })
2071
+ ]
2072
+ });
2073
+ }
2074
+ function DeleteWorkflowDialog({ run, sessionId, onClose, onDeleted }) {
2075
+ const [deleting, setDeleting] = useState(false);
2076
+ const [error, setError] = useState();
2077
+ const confirm = () => {
2078
+ setDeleting(true);
2079
+ setError(void 0);
2080
+ deleteWorkflowRun(run.workspace, run.runKey, sessionId).then((result) => {
2081
+ if ("error" in result) {
2082
+ setDeleting(false);
2083
+ setError(result.error);
2084
+ return;
2085
+ }
2086
+ onDeleted();
2087
+ });
2088
+ };
2089
+ return /* @__PURE__ */ jsx(Dialog, {
2090
+ open: true,
2091
+ onOpenChange: (open) => {
2092
+ if (!open && !deleting) onClose();
2093
+ },
2094
+ children: /* @__PURE__ */ jsxs(DialogContent, {
2095
+ width: "md",
2096
+ "data-testid": "delete-workflow-dialog",
2097
+ "aria-describedby": void 0,
2098
+ children: [/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsxs(DialogTitle, { children: [
2099
+ "Delete ",
2100
+ run.displayName,
2101
+ "?"
2102
+ ] }) }), /* @__PURE__ */ jsxs(DialogBody, { children: [
2103
+ /* @__PURE__ */ jsx("p", {
2104
+ className: "text-sm leading-relaxed text-doom-dim",
2105
+ children: "This permanently removes the workflow run, including its logs and artifacts. This action cannot be undone."
2106
+ }),
2107
+ error === void 0 ? null : /* @__PURE__ */ jsx("p", {
2108
+ "data-testid": "delete-workflow-error",
2109
+ className: "text-xs text-doom-red",
2110
+ children: error
2111
+ }),
2112
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
2113
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
2114
+ /* @__PURE__ */ jsx(Button, {
2115
+ variant: "outline",
2116
+ size: "xs",
2117
+ "data-testid": "delete-workflow-cancel",
2118
+ disabled: deleting,
2119
+ onClick: onClose,
2120
+ children: "cancel"
2121
+ }),
2122
+ /* @__PURE__ */ jsx(Button, {
2123
+ variant: "danger",
2124
+ size: "xs",
2125
+ "data-testid": "delete-workflow-confirm",
2126
+ loading: deleting,
2127
+ loadingLabel: "deleting workflow",
2128
+ onClick: confirm,
2129
+ children: "delete permanently"
2130
+ })
2131
+ ] })
2132
+ ] })]
2133
+ })
2134
+ });
2135
+ }
2136
+ function DetailPane({ children }) {
2137
+ return /* @__PURE__ */ jsx("div", {
2138
+ className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-md border border-doom-border",
2139
+ children
2140
+ });
2141
+ }
2142
+ /** The canonical workflow surface: a scalable picker, compact step navigation, and one dominant detail pane. */
2143
+ function WorkflowsPanel({ sessionId, openTransientTab, sendSessionFrame }) {
2144
+ const runs = useStore(workflows.store, (state) => workflows.select(state, sessionId).runs);
2145
+ const selectedRun = useStore(workflows.store, (state) => workflows.select(state, sessionId).focusedRun);
2146
+ const catalogState = useStore(catalog.store, (state) => catalog.select(state, sessionId));
2147
+ const [selectedJob, setSelectedJob] = useState(null);
2148
+ const [selectedStep, setSelectedStep] = useState(null);
2149
+ const [pane, setPane] = useState("output");
2150
+ const [deletingRun, setDeletingRun] = useState();
2151
+ const [now, setNow] = useState(() => Date.now());
2152
+ useEffect(() => {
2153
+ const timer = setInterval(() => setNow(Date.now()), TICK_MS$1);
2154
+ return () => clearInterval(timer);
2155
+ }, []);
2156
+ const run = runs.find((candidate) => workflowRunIdentity(candidate) === selectedRun) ?? runs[0];
2157
+ const jobs = run?.jobs ?? [];
2158
+ const fallbackJob = run?.position?.job ?? jobs.find((candidate) => ACTIVE_STATES.has(candidate.status))?.name ?? jobs.at(-1)?.name;
2159
+ const job = jobs.find((candidate) => candidate.name === selectedJob) ?? jobs.find((candidate) => candidate.name === fallbackJob);
2160
+ const fallbackStep = run?.position?.job === job?.name ? run?.position?.step : job?.steps.find((candidate) => ACTIVE_STATES.has(candidate.status))?.name ?? job?.steps.at(-1)?.name;
2161
+ const step = job?.steps.find((candidate) => candidate.name === selectedStep) ?? job?.steps.find((candidate) => candidate.name === fallbackStep);
2162
+ const launching = catalogState.workflows.find((workflow) => workflow.path === catalogState.launch);
2163
+ const selectRun = (candidate) => {
2164
+ if (sessionId !== null) focusRun(sessionId, workflowRunIdentity(candidate));
2165
+ setSelectedJob(null);
2166
+ setSelectedStep(null);
2167
+ setPane("output");
2168
+ };
2169
+ return /* @__PURE__ */ jsxs("div", {
2170
+ "data-testid": "workflows-panel",
2171
+ className: "flex min-h-0 flex-1",
2172
+ children: [
2173
+ /* @__PURE__ */ jsx("div", {
2174
+ className: "flex min-w-0 flex-1 flex-col gap-3 overflow-hidden px-[18px] py-[14px]",
2175
+ children: run === void 0 ? /* @__PURE__ */ jsx(EmptyState, {
2176
+ "data-testid": "workflows-empty",
2177
+ title: "no workflow runs yet",
2178
+ description: "pick one from the catalog, or launch it from the session with SPC w l, and the run shows up here live.",
2179
+ children: /* @__PURE__ */ jsxs(Button, {
2180
+ variant: "outline",
2181
+ size: "sm",
2182
+ "data-testid": "workflows-open-catalog",
2183
+ onClick: () => {
2184
+ if (sessionId !== null) openCatalog(sessionId);
2185
+ },
2186
+ children: ["launch workflow ", /* @__PURE__ */ jsx(Kbd, { children: "SPC w l" })]
2187
+ })
2188
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2189
+ /* @__PURE__ */ jsxs("div", {
2190
+ className: "flex h-[46px] shrink-0 items-center gap-2 rounded-md border border-doom-border bg-doom-panel px-2.5",
2191
+ children: [
2192
+ /* @__PURE__ */ jsx(WorkflowPicker, {
2193
+ runs,
2194
+ selected: run,
2195
+ onSelect: selectRun
2196
+ }),
2197
+ /* @__PURE__ */ jsx(StatusBadge, {
2198
+ tone: run.stage === "error" ? "error" : run.stage === "running" ? "info" : "ok",
2199
+ children: runState(run)
2200
+ }),
2201
+ /* @__PURE__ */ jsx("span", {
2202
+ className: "truncate text-2xs text-doom-dim",
2203
+ children: [run.position?.job, run.position?.step].filter(Boolean).join(" · ") || run.workflowName || run.displayName
2204
+ }),
2205
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
2206
+ /* @__PURE__ */ jsxs("span", {
2207
+ "data-testid": "workflows-tally",
2208
+ className: "text-2xs text-doom-faint",
2209
+ children: [
2210
+ runs.filter((candidate) => candidate.stage === "running").length,
2211
+ " running · ",
2212
+ runs.length,
2213
+ " total"
2214
+ ]
2215
+ }),
2216
+ run.stage === "running" ? null : /* @__PURE__ */ jsx(Button, {
2217
+ variant: "danger-outline",
2218
+ size: "xs",
2219
+ "data-testid": "delete-workflow",
2220
+ onClick: () => setDeletingRun(run),
2221
+ children: "delete"
2222
+ }),
2223
+ /* @__PURE__ */ jsx(Button, {
2224
+ variant: "outline",
2225
+ size: "xs",
2226
+ "data-testid": "workflows-open-catalog",
2227
+ onClick: () => {
2228
+ if (sessionId !== null) openCatalog(sessionId);
2229
+ },
2230
+ children: "launch"
2231
+ })
2232
+ ]
2233
+ }),
2234
+ /* @__PURE__ */ jsx(SelectedAttention, { run }),
2235
+ /* @__PURE__ */ jsxs("div", {
2236
+ className: "flex min-h-0 flex-1 flex-col gap-3 sm:flex-row",
2237
+ children: [/* @__PURE__ */ jsxs("div", {
2238
+ "data-testid": "jobs-pane",
2239
+ className: "flex max-h-44 w-full shrink-0 flex-col overflow-y-auto rounded-md border border-doom-border bg-doom-panel p-1.5 sm:max-h-none sm:w-[214px]",
2240
+ children: [
2241
+ /* @__PURE__ */ jsxs("div", {
2242
+ className: "flex items-center px-2 pb-1 pt-1",
2243
+ children: [
2244
+ /* @__PURE__ */ jsx("span", {
2245
+ className: "text-2xs font-bold tracking-wider text-doom-faint",
2246
+ children: "JOBS"
2247
+ }),
2248
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
2249
+ /* @__PURE__ */ jsxs("span", {
2250
+ className: "text-2xs text-doom-faint",
2251
+ children: [
2252
+ jobs.filter((candidate) => candidate.status === "completed").length,
2253
+ "/",
2254
+ jobs.length
2255
+ ]
2256
+ })
2257
+ ]
2258
+ }),
2259
+ jobs.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
2260
+ className: "py-4",
2261
+ title: "no progress recorded yet"
2262
+ }) : jobs.map((candidate) => /* @__PURE__ */ jsx(JobRow, {
2263
+ job: candidate,
2264
+ now,
2265
+ selected: job !== void 0 && candidate.name === job.name,
2266
+ onSelect: () => {
2267
+ setSelectedJob(candidate.name);
2268
+ setSelectedStep(null);
2269
+ setPane("output");
2270
+ }
2271
+ }, candidate.name)),
2272
+ job === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
2273
+ /* @__PURE__ */ jsx("div", { className: "mx-1 my-1.5 border-t border-doom-border-soft" }),
2274
+ /* @__PURE__ */ jsxs("div", {
2275
+ className: "flex items-center px-2 pb-1",
2276
+ children: [/* @__PURE__ */ jsx("span", {
2277
+ "data-testid": "job-pane-name",
2278
+ className: "min-w-0 flex-1 truncate text-2xs font-bold text-doom-hi",
2279
+ children: job.name
2280
+ }), /* @__PURE__ */ jsx("span", {
2281
+ className: "text-2xs text-doom-faint",
2282
+ children: spanDuration(job.startedAt, job.endedAt, now) ?? ""
2283
+ })]
2284
+ }),
2285
+ /* @__PURE__ */ jsx("div", {
2286
+ className: "flex flex-col gap-0.5",
2287
+ children: job.steps.map((candidate) => /* @__PURE__ */ jsx(StepRow, {
2288
+ step: candidate,
2289
+ now,
2290
+ selected: step !== void 0 && candidate.name === step.name,
2291
+ onSelect: () => {
2292
+ setSelectedStep(candidate.name);
2293
+ setPane("output");
2294
+ }
2295
+ }, candidate.name))
2296
+ })
2297
+ ] })
2298
+ ]
2299
+ }), /* @__PURE__ */ jsxs(DetailPane, { children: [/* @__PURE__ */ jsxs("div", {
2300
+ className: "flex h-9 shrink-0 items-center gap-2 border-b border-doom-border-soft bg-doom-panel px-3",
2301
+ children: [
2302
+ /* @__PURE__ */ jsx("span", {
2303
+ className: "min-w-0 truncate text-xs font-bold text-doom-hi",
2304
+ children: job?.name ?? run.displayName
2305
+ }),
2306
+ job === void 0 ? null : /* @__PURE__ */ jsx(StatusBadge, {
2307
+ className: cn("bg-doom-deep", STATE_ICON[job.status].className),
2308
+ children: job.status.replace("_", " ")
2309
+ }),
2310
+ /* @__PURE__ */ jsx(Button, {
2311
+ variant: pane === "output" ? "primary" : "outline",
2312
+ size: "xs",
2313
+ "data-testid": "pane-tab-output",
2314
+ "data-active": pane === "output",
2315
+ onClick: () => setPane("output"),
2316
+ children: "output"
2317
+ }),
2318
+ /* @__PURE__ */ jsx(Button, {
2319
+ variant: pane === "artifacts" ? "primary" : "outline",
2320
+ size: "xs",
2321
+ "data-testid": "pane-tab-artifacts",
2322
+ "data-active": pane === "artifacts",
2323
+ onClick: () => setPane("artifacts"),
2324
+ children: "artifacts"
2325
+ })
2326
+ ]
2327
+ }), pane === "artifacts" ? /* @__PURE__ */ jsx(ArtifactsPane, {
2328
+ run,
2329
+ sessionId,
2330
+ onOpen: (path) => openTransientTab(artifactTab(run, path))
2331
+ }) : job === void 0 ? /* @__PURE__ */ jsx(EmptyState, {
2332
+ className: "py-6",
2333
+ title: "select a job to see its output"
2334
+ }) : /* @__PURE__ */ jsx(InlineStepOutput, {
2335
+ run,
2336
+ job,
2337
+ step,
2338
+ sessionId,
2339
+ onOpenTerminal: () => openTransientTab(stepTerminalTab(run, job.name, step?.name))
2340
+ })] })]
2341
+ })
2342
+ ] })
2343
+ }),
2344
+ sessionId !== null && catalogState.open ? /* @__PURE__ */ jsx(WorkflowCatalogDrawer, {
2345
+ sessionId,
2346
+ onClose: () => closeCatalog(sessionId),
2347
+ onLaunch: (workflow) => openLaunch(sessionId, workflow.path)
2348
+ }) : null,
2349
+ sessionId !== null && launching !== void 0 ? /* @__PURE__ */ jsx(LaunchWorkflowDialog, {
2350
+ sessionId,
2351
+ workflow: launching,
2352
+ cwd: catalogState.cwd,
2353
+ initialPrompt: catalogState.prompt,
2354
+ send: sendSessionFrame,
2355
+ onClose: () => closeLaunch(sessionId),
2356
+ onLaunched: () => {
2357
+ closeLaunch(sessionId);
2358
+ closeCatalog(sessionId);
2359
+ }
2360
+ }) : null,
2361
+ sessionId !== null && deletingRun !== void 0 ? /* @__PURE__ */ jsx(DeleteWorkflowDialog, {
2362
+ run: deletingRun,
2363
+ sessionId,
2364
+ onClose: () => setDeletingRun(void 0),
2365
+ onDeleted: () => {
2366
+ removeRun(sessionId, workflowRunIdentity(deletingRun));
2367
+ setDeletingRun(void 0);
2368
+ }
2369
+ }) : null
2370
+ ]
2371
+ });
2372
+ }
2373
+ /** The temporary tab the workflow surface opens in; opening it again only focuses it. */
2374
+ function workflowsTab() {
2375
+ return {
2376
+ id: WORKFLOWS_TAB_ID,
2377
+ label: "workflows",
2378
+ panel: WorkflowsPanel
2379
+ };
2380
+ }
2381
+ //#endregion
2382
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/_components/WorkflowsActivitySection.tsx
2383
+ const TICK_MS = 1e4;
2384
+ const TONE_DOT = {
2385
+ running: "yellow",
2386
+ paused: "yellow",
2387
+ failed: "red",
2388
+ done: "green",
2389
+ skipped: "muted"
2390
+ };
2391
+ const TONE_DETAIL = {
2392
+ running: "text-doom-faint",
2393
+ paused: "text-doom-yellow",
2394
+ failed: "text-doom-red",
2395
+ done: "text-doom-faint",
2396
+ skipped: "text-doom-faint"
2397
+ };
2398
+ const GROUP_LABEL = {
2399
+ running: "text-doom-yellow",
2400
+ failed: "text-doom-red",
2401
+ successful: "text-doom-green"
2402
+ };
2403
+ /**
2404
+ * The workflows group's body in the activity dock: every run this session
2405
+ * started, split into running, failed and successful.
2406
+ *
2407
+ * A session keeps its whole history, so the groups fold: failures open because
2408
+ * they are the only ones that need an answer, and the rest stay out of the way
2409
+ * until somebody asks. Idle offers the catalog instead of saying nothing.
2410
+ */
2411
+ function WorkflowsActivitySection({ sessionId, openTransientTab }) {
2412
+ const runs = useStore(workflows.store, (state) => workflows.select(state, sessionId).runs);
2413
+ const [now, setNow] = useState(() => Date.now());
2414
+ const [folded, setFolded] = useState({});
2415
+ useEffect(() => {
2416
+ const timer = setInterval(() => setNow(Date.now()), TICK_MS);
2417
+ return () => clearInterval(timer);
2418
+ }, []);
2419
+ const groups = workflowActivityGroups(workflowActivityRows(runs, now));
2420
+ if (groups.length === 0) return /* @__PURE__ */ jsxs("div", {
2421
+ className: "flex items-center gap-2 px-1",
2422
+ children: [/* @__PURE__ */ jsx("p", {
2423
+ "data-testid": "activity-summary-workflows",
2424
+ className: "text-xs text-doom-faint",
2425
+ children: "idle"
2426
+ }), /* @__PURE__ */ jsx(Button, {
2427
+ variant: "link",
2428
+ size: "xs",
2429
+ "data-testid": "activity-workflow-launch",
2430
+ className: "px-0",
2431
+ onClick: () => {
2432
+ if (sessionId === null) return;
2433
+ openCatalog(sessionId);
2434
+ openTransientTab(workflowsTab());
2435
+ },
2436
+ children: "launch a workflow"
2437
+ })]
2438
+ });
2439
+ const RunRow = ({ row }) => /* @__PURE__ */ jsxs(Button, {
2440
+ variant: "ghost",
2441
+ size: "card",
2442
+ "data-testid": `activity-workflow-${row.runKey}`,
2443
+ "data-run-tone": row.tone,
2444
+ title: "open this run in the workflows tab",
2445
+ onClick: () => {
2446
+ if (sessionId === null) return;
2447
+ focusRun(sessionId, row.identity);
2448
+ openTransientTab(workflowsTab());
2449
+ },
2450
+ className: "min-w-0 gap-0.5 rounded-md px-1 py-1 hover:bg-doom-panel",
2451
+ children: [/* @__PURE__ */ jsxs("span", {
2452
+ className: "flex min-w-0 items-center gap-1.5",
2453
+ children: [
2454
+ /* @__PURE__ */ jsx(Dot, {
2455
+ tone: TONE_DOT[row.tone],
2456
+ pulse: row.tone === "running"
2457
+ }),
2458
+ /* @__PURE__ */ jsx("span", {
2459
+ className: `min-w-0 flex-1 truncate text-xs font-bold ${row.tone === "running" || row.tone === "paused" ? "text-doom-hi" : "text-doom-dim"}`,
2460
+ children: row.name
2461
+ }),
2462
+ /* @__PURE__ */ jsx("span", {
2463
+ className: "shrink-0 text-2xs text-doom-faint",
2464
+ children: row.elapsed
2465
+ })
2466
+ ]
2467
+ }), /* @__PURE__ */ jsx("span", {
2468
+ className: `truncate pl-3 text-2xs ${TONE_DETAIL[row.tone]}`,
2469
+ children: row.detail
2470
+ })]
2471
+ });
2472
+ return /* @__PURE__ */ jsx("div", {
2473
+ "data-testid": "activity-workflow-runs",
2474
+ className: "flex flex-col gap-0.5",
2475
+ children: groups.map((group) => {
2476
+ const open = folded[group.name] ?? group.openByDefault;
2477
+ return /* @__PURE__ */ jsxs("div", {
2478
+ className: "flex flex-col gap-0.5",
2479
+ children: [/* @__PURE__ */ jsxs(Button, {
2480
+ variant: "ghost",
2481
+ size: "xs",
2482
+ "data-testid": `activity-workflow-group-${group.name}`,
2483
+ "data-open": open,
2484
+ "aria-expanded": open,
2485
+ onClick: () => setFolded((current) => ({
2486
+ ...current,
2487
+ [group.name]: !open
2488
+ })),
2489
+ className: "justify-start gap-1.5 rounded-md px-1 py-0.5 hover:bg-doom-panel",
2490
+ children: [
2491
+ open ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "h-2.5 w-2.5 text-doom-faint" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "h-2.5 w-2.5 text-doom-faint" }),
2492
+ /* @__PURE__ */ jsx("span", {
2493
+ className: `text-xs font-bold ${GROUP_LABEL[group.name]}`,
2494
+ children: group.name
2495
+ }),
2496
+ /* @__PURE__ */ jsx("span", {
2497
+ className: "text-2xs text-doom-faint",
2498
+ children: group.rows.length
2499
+ })
2500
+ ]
2501
+ }), open ? /* @__PURE__ */ jsx("div", {
2502
+ className: "flex flex-col gap-0.5 pl-2",
2503
+ children: group.rows.map((row) => /* @__PURE__ */ jsx(RunRow, { row }, row.identity))
2504
+ }) : /* @__PURE__ */ jsxs("span", {
2505
+ className: "truncate pl-5 text-2xs text-doom-faint",
2506
+ children: [group.rows.slice(0, 2).map((row) => row.name).join(", "), group.rows.length > 2 ? `, and ${group.rows.length - 2} more` : ""]
2507
+ })]
2508
+ }, group.name);
2509
+ })
2510
+ });
2511
+ }
2512
+ //#endregion
2513
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/workflowToolRender.ts
2514
+ const COLLAPSED_CATALOG_ROWS = 6;
2515
+ const COLLAPSED_DETAIL_LINES = 8;
2516
+ const WORKFLOW_TOOL_NAMES = [
2517
+ "list_workflows",
2518
+ "launch_workflow",
2519
+ "workflow_run"
2520
+ ];
2521
+ function asRecord(value) {
2522
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2523
+ return value;
2524
+ }
2525
+ function inlineText(value) {
2526
+ let normalized = "";
2527
+ for (const character of value) {
2528
+ const code = character.codePointAt(0) ?? 0;
2529
+ normalized += code <= 31 || code === 127 ? " " : character;
2530
+ }
2531
+ return normalized.replace(/\s+/gu, " ").trim();
2532
+ }
2533
+ function readString(record, key) {
2534
+ const value = record?.[key];
2535
+ if (typeof value !== "string") return void 0;
2536
+ return inlineText(value) || void 0;
2537
+ }
2538
+ function readNumber(record, key) {
2539
+ const value = record?.[key];
2540
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2541
+ }
2542
+ function readBoolean(record, key) {
2543
+ const value = record?.[key];
2544
+ return typeof value === "boolean" ? value : void 0;
2545
+ }
2546
+ function readStringArray(record, key) {
2547
+ const value = record?.[key];
2548
+ if (!Array.isArray(value)) return [];
2549
+ return value.filter((item) => typeof item === "string");
2550
+ }
2551
+ function parseJsonRecord(text) {
2552
+ if (!text) return void 0;
2553
+ try {
2554
+ return asRecord(JSON.parse(text));
2555
+ } catch {
2556
+ return;
2557
+ }
2558
+ }
2559
+ function textBlocks(result) {
2560
+ if (!result) return [];
2561
+ return result.content.flatMap((item) => {
2562
+ const record = asRecord(item);
2563
+ return record?.type === "text" && typeof record.text === "string" ? [record.text] : [];
2564
+ });
2565
+ }
2566
+ function detailLines(result) {
2567
+ return textBlocks(result).flatMap((block) => block.split("\n")).map((entry) => entry.trimEnd()).filter((entry) => entry.length > 0);
2568
+ }
2569
+ function firstPayload(result) {
2570
+ return parseJsonRecord(textBlocks(result)[0]);
2571
+ }
2572
+ function plural(count, singular, pluralForm = `${singular}s`) {
2573
+ return count === 1 ? singular : pluralForm;
2574
+ }
2575
+ function line(text, tone, extra = {}) {
2576
+ return {
2577
+ text,
2578
+ tone,
2579
+ ...extra
2580
+ };
2581
+ }
2582
+ function labelValue(label, value) {
2583
+ return line(`${label} ${value}`, "text");
2584
+ }
2585
+ function hiddenLine(hidden) {
2586
+ return line(`… ${hidden} more`, "dim");
2587
+ }
2588
+ function targetScope(args) {
2589
+ const runKey = readString(args, "runKey");
2590
+ if (!runKey) return void 0;
2591
+ const workspace = readString(args, "workspace");
2592
+ return workspace ? `${workspace}/${runKey}` : runKey;
2593
+ }
2594
+ function toolAction(toolName, args) {
2595
+ if (toolName === "list_workflows") return "list";
2596
+ if (toolName === "launch_workflow") return "launch";
2597
+ return readString(args, "action") ?? "run";
2598
+ }
2599
+ function toolTarget(toolName, args) {
2600
+ if (toolName === "list_workflows") return readString(args, "directory");
2601
+ if (toolName === "launch_workflow") return readString(args, "workflowPath");
2602
+ return targetScope(args);
2603
+ }
2604
+ function statusTimestamp(value) {
2605
+ return value.replace("T", " ").replace(/\.\d{3}Z$/, "Z");
2606
+ }
2607
+ function workflowNameFromPath(path) {
2608
+ if (!path) return "workflow";
2609
+ const filename = path.split("/").at(-1) ?? path;
2610
+ return filename.replace(/\.workflow\.ya?ml$/u, "") || filename;
2611
+ }
2612
+ function callMetadata(toolName, args) {
2613
+ const values = [];
2614
+ if (toolName === "list_workflows") {
2615
+ const page = readNumber(args, "page");
2616
+ const pageSize = readNumber(args, "pageSize");
2617
+ if (page !== void 0) values.push(`page ${page}`);
2618
+ if (pageSize !== void 0) values.push(`${pageSize}/page`);
2619
+ if (readString(args, "filter")) values.push("filtered");
2620
+ const tags = readStringArray(args, "tags");
2621
+ if (tags.length > 0) values.push(`${tags.length} ${plural(tags.length, "tag")}`);
2622
+ } else if (toolName === "launch_workflow") {
2623
+ const job = readString(args, "job");
2624
+ const runner = readString(args, "runner");
2625
+ const workspace = readString(args, "workspace");
2626
+ const inputCount = Object.keys(asRecord(args.inputs) ?? {}).length;
2627
+ const environmentCount = Object.keys(asRecord(args.env) ?? {}).length;
2628
+ if (workspace) values.push(`workspace ${workspace}`);
2629
+ if (job) values.push(`job ${job}`);
2630
+ if (runner) values.push(`runner ${runner}`);
2631
+ if (readBoolean(args, "dryRun")) values.push("dry run");
2632
+ if (readString(args, "prompt")) values.push("with prompt");
2633
+ if (inputCount > 0) values.push(`${inputCount} ${plural(inputCount, "input")}`);
2634
+ if (environmentCount > 0) values.push(`${environmentCount} env`);
2635
+ } else {
2636
+ if (readString(args, "expectedRunId")) values.push("generation checked");
2637
+ if (readString(args, "reason")) values.push("with reason");
2638
+ if (readBoolean(args, "dryRun")) values.push("dry run");
2639
+ if (readString(args, "job")) values.push("job override");
2640
+ if (readString(args, "runner")) values.push("runner override");
2641
+ }
2642
+ return values;
2643
+ }
2644
+ /** The header beside the tool name: action, target, and the call's flags. */
2645
+ function workflowCallSummary(toolName, args) {
2646
+ const target = toolTarget(toolName, args);
2647
+ return {
2648
+ action: toolAction(toolName, args),
2649
+ ...target === void 0 ? {} : { target },
2650
+ metadata: callMetadata(toolName, args)
2651
+ };
2652
+ }
2653
+ function parseCatalog(payload) {
2654
+ if (!Array.isArray(payload?.workflows)) return void 0;
2655
+ const workflows = payload.workflows.flatMap((value) => {
2656
+ const record = asRecord(value);
2657
+ const path = readString(record, "path");
2658
+ if (!record || !path) return [];
2659
+ return [{
2660
+ description: readString(record, "description") ?? "",
2661
+ name: readString(record, "name") ?? "",
2662
+ path,
2663
+ tags: readStringArray(record, "tags")
2664
+ }];
2665
+ });
2666
+ const tags = Array.isArray(payload.tags) ? payload.tags.flatMap((value) => {
2667
+ const record = asRecord(value);
2668
+ const tag = readString(record, "tag");
2669
+ const count = readNumber(record, "count");
2670
+ return tag && count !== void 0 ? [{
2671
+ count,
2672
+ tag
2673
+ }] : [];
2674
+ }) : [];
2675
+ const directory = readString(payload, "directory");
2676
+ return {
2677
+ ...directory === void 0 ? {} : { directory },
2678
+ page: readNumber(payload, "page") ?? 1,
2679
+ pageSize: readNumber(payload, "pageSize") ?? workflows.length,
2680
+ tags,
2681
+ total: readNumber(payload, "total") ?? workflows.length,
2682
+ totalPages: readNumber(payload, "totalPages") ?? (workflows.length > 0 ? 1 : 0),
2683
+ workflows
2684
+ };
2685
+ }
2686
+ function catalogItemLines(item, expanded) {
2687
+ const lines = [line(`› ${item.name || workflowNameFromPath(item.path)} · ${item.path}`, "text")];
2688
+ if (!expanded) return lines;
2689
+ if (item.description) lines.push(line(item.description, "muted", { indent: true }));
2690
+ if (item.tags.length > 0) lines.push(line(`tags ${item.tags.join(", ")}`, "dim", { indent: true }));
2691
+ return lines;
2692
+ }
2693
+ function catalogLines(result, options) {
2694
+ const catalog = parseCatalog(firstPayload(result));
2695
+ if (!catalog) return void 0;
2696
+ const pageLabel = `page ${catalog.page}/${catalog.totalPages}`;
2697
+ const summary = line(`${catalog.total > 0 ? "✓" : "○"} ${catalog.total} ${plural(catalog.total, "workflow")} · ${pageLabel}`, "hi", { bold: true });
2698
+ const shown = options.expanded ? catalog.workflows : catalog.workflows.slice(0, COLLAPSED_CATALOG_ROWS);
2699
+ const lines = [summary, ...shown.flatMap((item) => catalogItemLines(item, options.expanded))];
2700
+ const hidden = catalog.workflows.length - shown.length;
2701
+ if (hidden > 0) lines.push(hiddenLine(hidden));
2702
+ if (options.expanded) {
2703
+ if (catalog.tags.length > 0) {
2704
+ const tags = catalog.tags.map(({ count, tag }) => `${tag} (${count})`).join(", ");
2705
+ lines.push(labelValue("available tags", tags));
2706
+ }
2707
+ if (catalog.directory) lines.push(labelValue("directory", catalog.directory));
2708
+ lines.push(labelValue("page size", String(catalog.pageSize)));
2709
+ }
2710
+ return lines;
2711
+ }
2712
+ function readWorkflowState(record) {
2713
+ const stage = readString(record, "effectiveStage") ?? readString(record, "stage") ?? "unknown";
2714
+ const outcome = readString(record, "effectiveOutcome") ?? readString(record, "outcome");
2715
+ const executionState = readString(record, "executionState");
2716
+ if (outcome === "interrupted") return {
2717
+ tone: "warning",
2718
+ glyph: "!",
2719
+ label: "interrupted"
2720
+ };
2721
+ if (stage === "error" || outcome === "failed") return {
2722
+ tone: "error",
2723
+ glyph: "✗",
2724
+ label: "failed"
2725
+ };
2726
+ if (stage === "completed" && outcome === "skipped") return {
2727
+ tone: "muted",
2728
+ glyph: "○",
2729
+ label: "skipped"
2730
+ };
2731
+ if (stage === "completed") return {
2732
+ tone: "success",
2733
+ glyph: "✓",
2734
+ label: "completed"
2735
+ };
2736
+ if (executionState === "paused") return {
2737
+ tone: "warning",
2738
+ glyph: "Ⅱ",
2739
+ label: "paused"
2740
+ };
2741
+ if (executionState === "pause_requested") return {
2742
+ tone: "warning",
2743
+ glyph: "◐",
2744
+ label: "pause requested"
2745
+ };
2746
+ if (executionState === "resume_requested") return {
2747
+ tone: "warning",
2748
+ glyph: "◐",
2749
+ label: "resume requested"
2750
+ };
2751
+ if (stage === "running") return {
2752
+ tone: "accent",
2753
+ glyph: "◐",
2754
+ label: "running"
2755
+ };
2756
+ return {
2757
+ tone: "muted",
2758
+ glyph: "○",
2759
+ label: stage
2760
+ };
2761
+ }
2762
+ function recordScope(record) {
2763
+ const runKey = readString(record, "runKey") ?? "unknown run";
2764
+ const workspace = readString(record, "workspace");
2765
+ return workspace ? `${workspace}/${runKey}` : runKey;
2766
+ }
2767
+ function currentActivity(record) {
2768
+ const cursor = asRecord(record.executionCursor);
2769
+ const job = readString(cursor, "job") ?? readString(record, "job");
2770
+ const step = readString(cursor, "stepName");
2771
+ const phase = readString(cursor, "phase");
2772
+ if (!job && !step && !phase) return void 0;
2773
+ return line(`[${job ?? phase ?? "workflow"}] ${step ?? phase ?? "running"}`, "text", { indent: true });
2774
+ }
2775
+ function launcherLabel(record) {
2776
+ const launcher = asRecord(record.launcher);
2777
+ const type = readString(launcher, "type");
2778
+ if (!type) return void 0;
2779
+ const identity = readString(launcher, "sessionName") ?? readString(launcher, "workspaceId");
2780
+ return identity ? `${type} ${identity}` : type;
2781
+ }
2782
+ function workflowRecordLines(record, expanded) {
2783
+ const runKey = readString(record, "runKey");
2784
+ const stage = readString(record, "stage");
2785
+ if (!runKey || !stage) return void 0;
2786
+ const state = readWorkflowState(record);
2787
+ const displayName = readString(record, "displayName") ?? runKey;
2788
+ const lines = [line(`${state.glyph} ${displayName} · ${recordScope(record)} · ${state.label}`, state.tone, { bold: true })];
2789
+ const activity = currentActivity(record);
2790
+ if (activity) lines.push(activity);
2791
+ const failure = readString(record, "errorMessage") ?? readString(record, "failureReason");
2792
+ if (failure) lines.push(line(`error ${failure}`, "error"));
2793
+ const staleReason = readString(record, "staleReason");
2794
+ if (staleReason) lines.push(line(`stale ${staleReason}`, "warning"));
2795
+ const startedAt = readString(record, "startedAt");
2796
+ const exitCode = readNumber(record, "exitCode");
2797
+ const conciseMetadata = [
2798
+ readString(record, "runner") ? `runner ${readString(record, "runner")}` : void 0,
2799
+ readBoolean(record, "dryRun") ? "dry run" : void 0,
2800
+ exitCode !== void 0 ? `exit ${exitCode}` : void 0,
2801
+ startedAt ? `started ${statusTimestamp(startedAt)}` : void 0
2802
+ ].filter((value) => !!value);
2803
+ if (conciseMetadata.length > 0) lines.push(line(conciseMetadata.join(" · "), "dim"));
2804
+ if (!expanded) return lines;
2805
+ const finishedAt = readString(record, "finishedAt");
2806
+ const repairId = readString(record, "activeRepairId");
2807
+ const detailedValues = [
2808
+ ["workflow", readString(record, "workflowPath")],
2809
+ ["workflow id", readString(record, "workflowId")],
2810
+ ["run id", readString(record, "runId")],
2811
+ ["launcher", launcherLabel(record)],
2812
+ ["job", readString(record, "job")],
2813
+ ["outcome", readString(record, "effectiveOutcome") ?? readString(record, "outcome")],
2814
+ ["finished", finishedAt ? statusTimestamp(finishedAt) : void 0],
2815
+ ["pid", readNumber(record, "pid")?.toString()],
2816
+ ["repair", repairId ? `#${repairId}` : void 0],
2817
+ ["worktree", readString(record, "worktreeBranch") ?? readString(record, "worktreePath")]
2818
+ ];
2819
+ for (const [label, value] of detailedValues) if (value) lines.push(labelValue(label, value));
2820
+ return lines;
2821
+ }
2822
+ function statusLines(result, options) {
2823
+ const payload = firstPayload(result);
2824
+ if (!payload) return void 0;
2825
+ return workflowRecordLines(payload, options.expanded);
2826
+ }
2827
+ function nestedRequestedAt(payload) {
2828
+ return readString(asRecord(payload?.request), "requestedAt") ?? readString(payload, "requestedAt");
2829
+ }
2830
+ function controlLines(action, args, result, options) {
2831
+ const payload = firstPayload(result);
2832
+ const scope = targetScope(args) ?? readString(payload, "runKey") ?? "workflow";
2833
+ const payloadWorkspace = readString(payload, "workspace");
2834
+ const lines = [line(`◐ ${action} requested · ${payloadWorkspace && !scope.includes("/") ? `${payloadWorkspace}/${scope}` : scope}`, "warning", { bold: true })];
2835
+ const requestedAt = nestedRequestedAt(payload);
2836
+ if (requestedAt) lines.push(labelValue("requested", statusTimestamp(requestedAt)));
2837
+ if (options.expanded) lines.push(line("verify the transition with workflow_run status", "dim"));
2838
+ return lines;
2839
+ }
2840
+ function matchLine(text, pattern) {
2841
+ return text.match(pattern)?.[1]?.trim();
2842
+ }
2843
+ function launchSummary(text, args) {
2844
+ const startedName = matchLine(text, /^Started (.+) in workspace .+\.$/mu);
2845
+ const workspace = matchLine(text, /^Started .+ in workspace (.+)\.$/mu) ?? readString(args, "workspace");
2846
+ const runKey = matchLine(text, /^Run key:\s*(.+)$/mu);
2847
+ const path = readString(args, "workflowPath");
2848
+ const name = startedName ?? workflowNameFromPath(path);
2849
+ const scope = runKey ? workspace ? `${workspace}/${runKey}` : runKey : workspace ?? path ?? "workflow";
2850
+ if (/Workflow completed successfully/iu.test(text)) return {
2851
+ name,
2852
+ scope,
2853
+ state: {
2854
+ tone: "success",
2855
+ glyph: "✓",
2856
+ label: "completed"
2857
+ }
2858
+ };
2859
+ if (/Workflow skipped/iu.test(text)) return {
2860
+ name,
2861
+ scope,
2862
+ state: {
2863
+ tone: "muted",
2864
+ glyph: "○",
2865
+ label: "skipped"
2866
+ }
2867
+ };
2868
+ if (/no run was registered/iu.test(text)) return {
2869
+ name,
2870
+ scope,
2871
+ state: {
2872
+ tone: "warning",
2873
+ glyph: "◐",
2874
+ label: "starting · run key pending"
2875
+ }
2876
+ };
2877
+ return {
2878
+ name,
2879
+ scope,
2880
+ state: {
2881
+ tone: "accent",
2882
+ glyph: "◐",
2883
+ label: runKey ? "started" : "launch accepted"
2884
+ }
2885
+ };
2886
+ }
2887
+ function launchLines(args, result, options) {
2888
+ const summary = launchSummary(textBlocks(result).join("\n"), args);
2889
+ const lines = [line(`${summary.state.glyph} ${summary.name} · ${summary.scope} · ${summary.state.label}`, summary.state.tone, { bold: true })];
2890
+ const metadata = [
2891
+ readString(args, "job") ? `job ${readString(args, "job")}` : void 0,
2892
+ readString(args, "runner") ? `runner ${readString(args, "runner")}` : void 0,
2893
+ readBoolean(args, "dryRun") ? "dry run" : void 0
2894
+ ].filter((value) => !!value);
2895
+ if (metadata.length > 0) lines.push(line(metadata.join(" · "), "dim"));
2896
+ if (options.expanded) {
2897
+ const details = detailLines(result).filter((entry) => !entry.startsWith("Next steps for the agent:"));
2898
+ lines.push(...details.map((entry) => line(entry, "text")));
2899
+ }
2900
+ return lines;
2901
+ }
2902
+ function splitEvidence(text) {
2903
+ const sections = [];
2904
+ let current;
2905
+ for (const entry of text.split("\n")) {
2906
+ const match = entry.match(/^--- (.+) ---$/u);
2907
+ if (match?.[1]) {
2908
+ current = {
2909
+ body: [],
2910
+ name: match[1]
2911
+ };
2912
+ sections.push(current);
2913
+ continue;
2914
+ }
2915
+ current?.body.push(entry);
2916
+ }
2917
+ return sections;
2918
+ }
2919
+ function recoveryEvidenceLines(args, result, options) {
2920
+ const sections = splitEvidence(textBlocks(result).join("\n"));
2921
+ const record = parseJsonRecord(sections.find((section) => section.name === "run.json")?.body.join("\n"));
2922
+ const recordLines = record ? workflowRecordLines(record, options.expanded) : void 0;
2923
+ const evidenceSections = sections.filter((section) => section.name !== "run.json");
2924
+ const scope = targetScope(args) ?? "workflow";
2925
+ const lines = recordLines ?? [line(`✓ recovery evidence · ${scope}`, "success", { bold: true })];
2926
+ const names = evidenceSections.map((section) => section.name);
2927
+ lines.push(names.length > 0 ? labelValue("evidence", names.join(", ")) : line("no durable evidence files recorded", "muted"));
2928
+ if (options.expanded) for (const section of evidenceSections) {
2929
+ lines.push(line(section.name, "hi", { bold: true }));
2930
+ lines.push(...section.body.filter((entry) => entry.length > 0).map((entry) => line(entry, "text")));
2931
+ }
2932
+ return lines;
2933
+ }
2934
+ function simpleActionLines(action, args, result, options) {
2935
+ const scope = targetScope(args) ?? "workflow";
2936
+ const actionLabel = {
2937
+ follow: "following output",
2938
+ open: "launcher opened",
2939
+ recover: readBoolean(args, "dryRun") ? "recovery dry run finished" : "recovery accepted",
2940
+ tail: "launcher output checked"
2941
+ };
2942
+ const state = action === "recover" ? {
2943
+ tone: "accent",
2944
+ glyph: "◐",
2945
+ label: actionLabel[action] ?? action
2946
+ } : {
2947
+ tone: "success",
2948
+ glyph: "✓",
2949
+ label: actionLabel[action] ?? action
2950
+ };
2951
+ const lines = [line(`${state.glyph} ${state.label} · ${scope}`, state.tone, { bold: true })];
2952
+ const details = detailLines(result);
2953
+ if (action === "tail") {
2954
+ const workflowLine = details.find((entry) => entry.startsWith("Workflow:"));
2955
+ const stageLine = details.find((entry) => entry.startsWith("Stage:"));
2956
+ if (workflowLine) lines.push(line(workflowLine, "text"));
2957
+ if (stageLine) lines.push(line(stageLine, "text"));
2958
+ }
2959
+ if (options.expanded) lines.push(...details.map((entry) => line(entry, "text")));
2960
+ return lines;
2961
+ }
2962
+ function failureLines(toolName, args, result, options) {
2963
+ const action = toolAction(toolName, args);
2964
+ const target = toolTarget(toolName, args);
2965
+ const details = detailLines(result);
2966
+ const shown = options.expanded ? details : details.slice(0, COLLAPSED_DETAIL_LINES);
2967
+ const lines = [line(`✗ ${action} failed${target ? ` · ${target}` : ""}`, "error", { bold: true }), ...shown.length > 0 ? shown.map((entry) => line(entry, "text")) : [line("Unknown workflow error", "error")]];
2968
+ const hidden = details.length - shown.length;
2969
+ if (hidden > 0) lines.push(hiddenLine(hidden));
2970
+ return lines;
2971
+ }
2972
+ function partialLines(toolName, args, result) {
2973
+ const details = detailLines(result);
2974
+ const shown = details.slice(-4);
2975
+ const action = toolAction(toolName, args);
2976
+ const target = toolTarget(toolName, args);
2977
+ const hidden = details.length - shown.length;
2978
+ const lines = [line(`◐ ${action}${target ? ` · ${target}` : ""}`, "accent", { bold: true })];
2979
+ if (hidden > 0) lines.push(line(`… ${hidden} earlier`, "dim"));
2980
+ lines.push(...shown.map((entry) => line(entry, "text")));
2981
+ return lines;
2982
+ }
2983
+ function fallbackLines(result, options) {
2984
+ const details = detailLines(result);
2985
+ if (details.length === 0) return [line("No workflow output.", "muted")];
2986
+ const shown = options.expanded ? details : details.slice(0, COLLAPSED_DETAIL_LINES);
2987
+ const lines = shown.map((entry) => line(entry, "text"));
2988
+ const hidden = details.length - shown.length;
2989
+ if (hidden > 0) lines.push(hiddenLine(hidden));
2990
+ return lines;
2991
+ }
2992
+ /** The body of a workflow tool card, chosen by tool, action, and result shape. */
2993
+ function workflowResultLines(toolName, args, result, options) {
2994
+ if (options.isPartial) return partialLines(toolName, args, result);
2995
+ if (options.isError) return failureLines(toolName, args, result, options);
2996
+ if (toolName === "list_workflows") return catalogLines(result, options) ?? fallbackLines(result, options);
2997
+ if (toolName === "launch_workflow") return launchLines(args, result, options);
2998
+ const action = readString(args, "action");
2999
+ if (action === "status") return statusLines(result, options) ?? fallbackLines(result, options);
3000
+ if (action === "pause" || action === "resume" || action === "stop") return controlLines(action, args, result, options);
3001
+ if (action === "recovery-evidence") return recoveryEvidenceLines(args, result, options);
3002
+ if (action === "follow" || action === "open" || action === "recover" || action === "tail") return simpleActionLines(action, args, result, options);
3003
+ return fallbackLines(result, options);
3004
+ }
3005
+ //#endregion
3006
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/WorkflowToolMessage.tsx
3007
+ function workflowToolName(name) {
3008
+ return WORKFLOW_TOOL_NAMES.includes(name) ? name : "workflow_run";
3009
+ }
3010
+ /**
3011
+ * The workflow tools' timeline item, the web analog of renderWorkflowToolCall
3012
+ * and renderWorkflowToolResult: `action · target · flags` in the header and
3013
+ * the shape-specific summary lines in the body.
3014
+ */
3015
+ function WorkflowToolMessage({ toolName, args, result, running, isError }) {
3016
+ const name = workflowToolName(toolName);
3017
+ const summary = workflowCallSummary(name, args);
3018
+ const collapsed = workflowResultLines(name, args, result, {
3019
+ expanded: false,
3020
+ isError,
3021
+ isPartial: running
3022
+ });
3023
+ const full = workflowResultLines(name, args, result, {
3024
+ expanded: true,
3025
+ isError,
3026
+ isPartial: running
3027
+ });
3028
+ return /* @__PURE__ */ jsx(MessageItem, {
3029
+ tone: toolTone({
3030
+ running,
3031
+ isError
3032
+ }),
3033
+ expandable: full.length > collapsed.length,
3034
+ children: ({ expanded }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
3035
+ title: toolName,
3036
+ children: /* @__PURE__ */ jsxs("span", {
3037
+ "data-testid": `tool-call-${toolName}`,
3038
+ className: "flex min-w-0 flex-1 items-center gap-2",
3039
+ children: [
3040
+ /* @__PURE__ */ jsx("span", {
3041
+ className: "font-bold text-doom-hi",
3042
+ children: summary.action
3043
+ }),
3044
+ summary.target ? /* @__PURE__ */ jsx("span", {
3045
+ className: "min-w-0 truncate text-doom-faint",
3046
+ children: summary.target
3047
+ }) : null,
3048
+ summary.metadata.map((value) => /* @__PURE__ */ jsxs("span", {
3049
+ className: "shrink-0 text-doom-dim",
3050
+ children: ["· ", value]
3051
+ }, value))
3052
+ ]
3053
+ })
3054
+ }), (expanded ? full : collapsed).length > 0 ? /* @__PURE__ */ jsx(MessageItemBody, {
3055
+ "data-testid": `tool-result-${toolName}`,
3056
+ children: /* @__PURE__ */ jsx(MessageLines, { lines: expanded ? full : collapsed })
3057
+ }) : null] })
3058
+ });
3059
+ }
3060
+ //#endregion
3061
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/index.ts
3062
+ const WORKFLOWS_GROUP = {
3063
+ key: "w",
3064
+ label: "workflows",
3065
+ detail: "multi-step agent runs"
3066
+ };
3067
+ var _lib_default = {
3068
+ channels: [workflowRunsChannel, workflowCatalogChannel],
3069
+ contextActions: [{
3070
+ id: "launch-workflow",
3071
+ label: "Launch Workflow",
3072
+ detail: "Choose a workflow, review its inputs, then launch it.",
3073
+ kinds: ["work-item"],
3074
+ order: 20,
3075
+ run: (context) => openWorkflowCatalogForContext(context, workflowsTab)
3076
+ }],
3077
+ minorModes: [{
3078
+ name: "workflow",
3079
+ keys: "w e",
3080
+ widgetKey: "workflow-mcp-progress",
3081
+ order: 50
3082
+ }],
3083
+ activityGroups: [{
3084
+ name: "workflows",
3085
+ keys: "w r",
3086
+ widgetKeys: ["workflow-mcp-progress", "workflow-mcp-follow"],
3087
+ activeSource: {
3088
+ subscribe(listener) {
3089
+ const subscription = workflows.store.subscribe(listener);
3090
+ return () => subscription.unsubscribe();
3091
+ },
3092
+ isActive(sessionId) {
3093
+ return workflows.select(workflows.store.state, sessionId).runs.some((run) => run.stage === "running");
3094
+ }
3095
+ },
3096
+ transientTab: workflowsTab,
3097
+ order: 30
3098
+ }],
3099
+ activitySections: [{
3100
+ id: "workflows",
3101
+ component: WorkflowsActivitySection
3102
+ }],
3103
+ toolRenderers: [{
3104
+ tools: [
3105
+ "list_workflows",
3106
+ "launch_workflow",
3107
+ "workflow_run"
3108
+ ],
3109
+ message: WorkflowToolMessage
3110
+ }],
3111
+ leaderBindings: [
3112
+ {
3113
+ id: "doom-workflow.manage",
3114
+ path: [WORKFLOWS_GROUP, {
3115
+ key: "r",
3116
+ label: "runs",
3117
+ detail: "runs in this session"
3118
+ }],
3119
+ run: (context) => context.openTransientTab(workflowsTab())
3120
+ },
3121
+ {
3122
+ id: "doom-workflow.catalog",
3123
+ path: [WORKFLOWS_GROUP, {
3124
+ key: "l",
3125
+ label: "launch",
3126
+ detail: "pick a workflow and launch it"
3127
+ }],
3128
+ run: (context) => {
3129
+ if (context.sessionId !== null) openCatalog(context.sessionId);
3130
+ context.openTransientTab(workflowsTab());
3131
+ }
3132
+ },
3133
+ {
3134
+ id: "doom-workflow.toggle",
3135
+ path: [WORKFLOWS_GROUP, {
3136
+ key: "e",
3137
+ label: "toggle",
3138
+ detail: "give the agent workflow tools or take them back"
3139
+ }],
3140
+ command: "minor workflow"
3141
+ }
3142
+ ]
3143
+ };
3144
+ //#endregion
3145
+ //#region src/extensions/workspaces/sessions/(frontend)/action/launch-workflow.web.ts
3146
+ var launch_workflow_web_default = defineContextAction(_lib_default.contextActions[0]);
3147
+ //#endregion
3148
+ //#region src/extensions/workspaces/sessions/(frontend)/activity-group/workflows.web.ts
3149
+ var workflows_web_default = defineActivityGroup(_lib_default.activityGroups[0]);
3150
+ //#endregion
3151
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/workflow-catalog.web.ts
3152
+ var workflow_catalog_web_default = defineChannelFile(_lib_default.channels[1]);
3153
+ //#endregion
3154
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/workflow-runs.web.ts
3155
+ var workflow_runs_web_default = defineChannelFile(_lib_default.channels[0]);
3156
+ //#endregion
3157
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/Workflows.activity.workflows.web.tsx
3158
+ var Workflows_activity_workflows_web_default = defineFill({
3159
+ slot: "activity.workflows",
3160
+ ..._lib_default.activitySections[0]
3161
+ });
3162
+ //#endregion
3163
+ //#region src/extensions/workspaces/sessions/(frontend)/leader/catalog.web.ts
3164
+ var catalog_web_default = defineLeaderBinding(_lib_default.leaderBindings[1]);
3165
+ //#endregion
3166
+ //#region src/extensions/workspaces/sessions/(frontend)/leader/manage.web.ts
3167
+ var manage_web_default = defineLeaderBinding(_lib_default.leaderBindings[0]);
3168
+ //#endregion
3169
+ //#region src/extensions/workspaces/sessions/(frontend)/leader/toggle.web.ts
3170
+ var toggle_web_default = defineLeaderBinding(_lib_default.leaderBindings[2]);
3171
+ //#endregion
3172
+ //#region src/extensions/workspaces/sessions/(frontend)/mode/workflow/mode.web.ts
3173
+ var mode_web_default = defineMinorModeFile(_lib_default.minorModes[0]);
3174
+ //#endregion
3175
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/workflow.web.tsx
3176
+ var workflow_web_default = defineToolRenderer(_lib_default.toolRenderers[0]);
3177
+ //#endregion
3178
+ //#region generated/web.ts
3179
+ const via = (identity, value) => ({
3180
+ ...identity,
3181
+ ...value
3182
+ });
3183
+ const webPlugin = defineWebPlugin({
3184
+ id: "workflows",
3185
+ session: {
3186
+ get activityGroups() {
3187
+ return [via({ name: "workflows" }, workflows_web_default)];
3188
+ },
3189
+ get channels() {
3190
+ return [via({ channel: "workflow_catalog" }, workflow_catalog_web_default), via({ channel: "workflow_runs" }, workflow_runs_web_default)];
3191
+ },
3192
+ get contextActions() {
3193
+ return [via({ id: "launch-workflow" }, launch_workflow_web_default)];
3194
+ },
3195
+ get fills() {
3196
+ return [via({
3197
+ slot: "activity.workflows",
3198
+ id: "workflows"
3199
+ }, Workflows_activity_workflows_web_default)];
3200
+ },
3201
+ get leaderBindings() {
3202
+ return [
3203
+ via({ id: "workflows.catalog" }, catalog_web_default),
3204
+ via({ id: "workflows.manage" }, manage_web_default),
3205
+ via({ id: "workflows.toggle" }, toggle_web_default)
3206
+ ];
3207
+ },
3208
+ get minorModes() {
3209
+ return [via({ name: "workflow" }, mode_web_default)];
3210
+ },
3211
+ get toolRenderers() {
3212
+ return [via({ tools: ["workflow"] }, workflow_web_default)];
3213
+ }
3214
+ }
3215
+ });
3216
+ //#endregion
3217
+ export { webPlugin };
3218
+
3219
+ //# sourceMappingURL=web.mjs.map