@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.
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/api-contracts.cjs +7 -0
- package/dist/api-contracts.d.cts +2 -0
- package/dist/api-contracts.d.mts +2 -0
- package/dist/api-contracts.mjs +2 -0
- package/dist/extensions/pi.cjs +63 -0
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +6 -0
- package/dist/extensions/pi.d.cts.map +1 -0
- package/dist/extensions/pi.d.mts +6 -0
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +58 -0
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/server.cjs +96 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.cts +5 -0
- package/dist/extensions/server.d.cts.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +91 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +3219 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2 -0
- package/dist/src/constants/workflow.cjs +32 -0
- package/dist/src/constants/workflow.cjs.map +1 -0
- package/dist/src/constants/workflow.mjs +21 -0
- package/dist/src/constants/workflow.mjs.map +1 -0
- package/dist/src/extensions/(backend)/_lib/index.server.cjs +12 -0
- package/dist/src/extensions/(backend)/_lib/index.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/_lib/index.server.mjs +12 -0
- package/dist/src/extensions/(backend)/_lib/index.server.mjs.map +1 -0
- package/dist/src/extensions/(backend)/api/workflow/route.server.cjs +7 -0
- package/dist/src/extensions/(backend)/api/workflow/route.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/api/workflow/route.server.mjs +8 -0
- package/dist/src/extensions/(backend)/api/workflow/route.server.mjs.map +1 -0
- package/dist/src/extensions/(backend)/channel/workflow-catalog.server.cjs +7 -0
- package/dist/src/extensions/(backend)/channel/workflow-catalog.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/channel/workflow-catalog.server.mjs +8 -0
- package/dist/src/extensions/(backend)/channel/workflow-catalog.server.mjs.map +1 -0
- package/dist/src/extensions/(backend)/channel/workflows.server.cjs +7 -0
- package/dist/src/extensions/(backend)/channel/workflows.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/channel/workflows.server.mjs +8 -0
- package/dist/src/extensions/(backend)/channel/workflows.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.cli.cjs +100 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.cli.mjs +100 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.server.cjs +11 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.server.mjs +11 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/index.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/workflowSkillsResource.cjs +12 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/workflowSkillsResource.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/workflowSkillsResource.mjs +12 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/workflowSkillsResource.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/api/workflow/route.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/api/workflow/route.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/api/workflow/route.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/api/workflow/route.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/workflow-launch.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/resources-discover.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/resources-discover.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/resources-discover.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/resources-discover.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-author-workflow.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-author-workflow.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-author-workflow.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-author-workflow.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-workflow.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-workflow.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-workflow.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-workflow.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-recovery.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-recovery.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-recovery.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-recovery.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-skills.cli.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-skills.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-skills.cli.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/workflow-skills.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs +15 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs +16 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs +17 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs +18 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-panel.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-panel.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-panel.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-panel.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-view-close.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-view-close.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-view-close.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/shortcut/workflow-view-close.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/launch-workflow.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/launch-workflow.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/launch-workflow.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/launch-workflow.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/list-workflows.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/list-workflows.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/list-workflows.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/list-workflows.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow-run.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow-run.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow-run.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow-run.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/workflow.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool-restriction/workflow.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowFinishedRenderer.cjs +50 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowFinishedRenderer.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowFinishedRenderer.mjs +47 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowFinishedRenderer.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowStepRenderer.cjs +36 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowStepRenderer.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowStepRenderer.mjs +35 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/_lib/workflowStepRenderer.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-finished.cli.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-finished.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-finished.cli.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-finished.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-step.cli.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-step.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-step.cli.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(frontend)/message/workflow-step.cli.mjs.map +1 -0
- package/dist/src/schemas/apiContracts.cjs +330 -0
- package/dist/src/schemas/apiContracts.cjs.map +1 -0
- package/dist/src/schemas/apiContracts.d.cts +53 -0
- package/dist/src/schemas/apiContracts.d.cts.map +1 -0
- package/dist/src/schemas/apiContracts.d.mts +53 -0
- package/dist/src/schemas/apiContracts.d.mts.map +1 -0
- package/dist/src/schemas/apiContracts.mjs +327 -0
- package/dist/src/schemas/apiContracts.mjs.map +1 -0
- package/dist/src/schemas/workflowPi.cjs +77 -0
- package/dist/src/schemas/workflowPi.cjs.map +1 -0
- package/dist/src/schemas/workflowPi.d.cts +1 -0
- package/dist/src/schemas/workflowPi.d.mts +1 -0
- package/dist/src/schemas/workflowPi.mjs +74 -0
- package/dist/src/schemas/workflowPi.mjs.map +1 -0
- package/dist/src/services/backgroundWork/index.cjs +61 -0
- package/dist/src/services/backgroundWork/index.cjs.map +1 -0
- package/dist/src/services/backgroundWork/index.mjs +61 -0
- package/dist/src/services/backgroundWork/index.mjs.map +1 -0
- package/dist/src/services/piToolBridge/index.cjs +205 -0
- package/dist/src/services/piToolBridge/index.cjs.map +1 -0
- package/dist/src/services/piToolBridge/index.mjs +195 -0
- package/dist/src/services/piToolBridge/index.mjs.map +1 -0
- package/dist/src/services/serverRuntime/index.cjs +387 -0
- package/dist/src/services/serverRuntime/index.cjs.map +1 -0
- package/dist/src/services/serverRuntime/index.mjs +387 -0
- package/dist/src/services/serverRuntime/index.mjs.map +1 -0
- package/dist/src/services/webWorkflowCatalog/index.cjs +53 -0
- package/dist/src/services/webWorkflowCatalog/index.cjs.map +1 -0
- package/dist/src/services/webWorkflowCatalog/index.mjs +52 -0
- package/dist/src/services/webWorkflowCatalog/index.mjs.map +1 -0
- package/dist/src/services/workflowCatalogChannel/index.cjs +74 -0
- package/dist/src/services/workflowCatalogChannel/index.cjs.map +1 -0
- package/dist/src/services/workflowCatalogChannel/index.mjs +74 -0
- package/dist/src/services/workflowCatalogChannel/index.mjs.map +1 -0
- package/dist/src/services/workflowCatalogDeps/index.cjs +25 -0
- package/dist/src/services/workflowCatalogDeps/index.cjs.map +1 -0
- package/dist/src/services/workflowCatalogDeps/index.mjs +25 -0
- package/dist/src/services/workflowCatalogDeps/index.mjs.map +1 -0
- package/dist/src/services/workflowExecution/index.cjs +205 -0
- package/dist/src/services/workflowExecution/index.cjs.map +1 -0
- package/dist/src/services/workflowExecution/index.d.cts +6 -0
- package/dist/src/services/workflowExecution/index.d.mts +6 -0
- package/dist/src/services/workflowExecution/index.mjs +200 -0
- package/dist/src/services/workflowExecution/index.mjs.map +1 -0
- package/dist/src/services/workflowFence/index.cjs +170 -0
- package/dist/src/services/workflowFence/index.cjs.map +1 -0
- package/dist/src/services/workflowFence/index.mjs +166 -0
- package/dist/src/services/workflowFence/index.mjs.map +1 -0
- package/dist/src/services/workflowHubApi/index.cjs +415 -0
- package/dist/src/services/workflowHubApi/index.cjs.map +1 -0
- package/dist/src/services/workflowHubApi/index.mjs +411 -0
- package/dist/src/services/workflowHubApi/index.mjs.map +1 -0
- package/dist/src/services/workflowLaunchCommand/index.cjs +121 -0
- package/dist/src/services/workflowLaunchCommand/index.cjs.map +1 -0
- package/dist/src/services/workflowLaunchCommand/index.d.cts +52 -0
- package/dist/src/services/workflowLaunchCommand/index.d.cts.map +1 -0
- package/dist/src/services/workflowLaunchCommand/index.d.mts +52 -0
- package/dist/src/services/workflowLaunchCommand/index.d.mts.map +1 -0
- package/dist/src/services/workflowLaunchCommand/index.mjs +118 -0
- package/dist/src/services/workflowLaunchCommand/index.mjs.map +1 -0
- package/dist/src/services/workflowLauncher/index.cjs +181 -0
- package/dist/src/services/workflowLauncher/index.cjs.map +1 -0
- package/dist/src/services/workflowLauncher/index.mjs +177 -0
- package/dist/src/services/workflowLauncher/index.mjs.map +1 -0
- package/dist/src/services/workflowNarration/index.cjs +19 -0
- package/dist/src/services/workflowNarration/index.cjs.map +1 -0
- package/dist/src/services/workflowNarration/index.mjs +19 -0
- package/dist/src/services/workflowNarration/index.mjs.map +1 -0
- package/dist/src/services/workflowResource/index.cjs +27 -0
- package/dist/src/services/workflowResource/index.cjs.map +1 -0
- package/dist/src/services/workflowResource/index.mjs +24 -0
- package/dist/src/services/workflowResource/index.mjs.map +1 -0
- package/dist/src/services/workflowRuns/index.cjs +333 -0
- package/dist/src/services/workflowRuns/index.cjs.map +1 -0
- package/dist/src/services/workflowRuns/index.mjs +319 -0
- package/dist/src/services/workflowRuns/index.mjs.map +1 -0
- package/dist/src/services/workflowTerminal/index.cjs +69 -0
- package/dist/src/services/workflowTerminal/index.cjs.map +1 -0
- package/dist/src/services/workflowTerminal/index.mjs +69 -0
- package/dist/src/services/workflowTerminal/index.mjs.map +1 -0
- package/dist/src/services/workflowTools/index.cjs +221 -0
- package/dist/src/services/workflowTools/index.cjs.map +1 -0
- package/dist/src/services/workflowTools/index.mjs +221 -0
- package/dist/src/services/workflowTools/index.mjs.map +1 -0
- package/dist/src/services/workflowWatcher/index.cjs +78 -0
- package/dist/src/services/workflowWatcher/index.cjs.map +1 -0
- package/dist/src/services/workflowWatcher/index.mjs +74 -0
- package/dist/src/services/workflowWatcher/index.mjs.map +1 -0
- package/dist/src/services/workflowsHubChannel/index.cjs +58 -0
- package/dist/src/services/workflowsHubChannel/index.cjs.map +1 -0
- package/dist/src/services/workflowsHubChannel/index.mjs +58 -0
- package/dist/src/services/workflowsHubChannel/index.mjs.map +1 -0
- package/dist/src/tui/leader.cjs +84 -0
- package/dist/src/tui/leader.cjs.map +1 -0
- package/dist/src/tui/leader.mjs +83 -0
- package/dist/src/tui/leader.mjs.map +1 -0
- package/dist/src/tui/workflow/doomOverlay.cjs +159 -0
- package/dist/src/tui/workflow/doomOverlay.cjs.map +1 -0
- package/dist/src/tui/workflow/doomOverlay.mjs +157 -0
- package/dist/src/tui/workflow/doomOverlay.mjs.map +1 -0
- package/dist/src/tui/workflow/overlayText.cjs +83 -0
- package/dist/src/tui/workflow/overlayText.cjs.map +1 -0
- package/dist/src/tui/workflow/overlayText.mjs +78 -0
- package/dist/src/tui/workflow/overlayText.mjs.map +1 -0
- package/dist/src/tui/workflow/rendering.cjs +22 -0
- package/dist/src/tui/workflow/rendering.cjs.map +1 -0
- package/dist/src/tui/workflow/rendering.mjs +20 -0
- package/dist/src/tui/workflow/rendering.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowCatalog.cjs +336 -0
- package/dist/src/tui/workflow/workflowCatalog.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowCatalog.mjs +333 -0
- package/dist/src/tui/workflow/workflowCatalog.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowFinishedMessage.cjs +50 -0
- package/dist/src/tui/workflow/workflowFinishedMessage.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowFinishedMessage.mjs +47 -0
- package/dist/src/tui/workflow/workflowFinishedMessage.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowInspector.cjs +276 -0
- package/dist/src/tui/workflow/workflowInspector.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowInspector.mjs +276 -0
- package/dist/src/tui/workflow/workflowInspector.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowOverlay.cjs +126 -0
- package/dist/src/tui/workflow/workflowOverlay.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowOverlay.mjs +125 -0
- package/dist/src/tui/workflow/workflowOverlay.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowPicker.cjs +138 -0
- package/dist/src/tui/workflow/workflowPicker.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowPicker.mjs +136 -0
- package/dist/src/tui/workflow/workflowPicker.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowProgressOverlay.cjs +197 -0
- package/dist/src/tui/workflow/workflowProgressOverlay.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowProgressOverlay.mjs +192 -0
- package/dist/src/tui/workflow/workflowProgressOverlay.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowPrompt.cjs +142 -0
- package/dist/src/tui/workflow/workflowPrompt.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowPrompt.mjs +139 -0
- package/dist/src/tui/workflow/workflowPrompt.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowRunPanel.cjs +95 -0
- package/dist/src/tui/workflow/workflowRunPanel.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowRunPanel.mjs +94 -0
- package/dist/src/tui/workflow/workflowRunPanel.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowStatusRow.cjs +20 -0
- package/dist/src/tui/workflow/workflowStatusRow.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowStatusRow.mjs +20 -0
- package/dist/src/tui/workflow/workflowStatusRow.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowStepMessage.cjs +32 -0
- package/dist/src/tui/workflow/workflowStepMessage.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowStepMessage.mjs +31 -0
- package/dist/src/tui/workflow/workflowStepMessage.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowToolRender.cjs +491 -0
- package/dist/src/tui/workflow/workflowToolRender.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowToolRender.mjs +490 -0
- package/dist/src/tui/workflow/workflowToolRender.mjs.map +1 -0
- package/dist/src/tui/workflowRuntime.cjs +1934 -0
- package/dist/src/tui/workflowRuntime.cjs.map +1 -0
- package/dist/src/tui/workflowRuntime.d.cts +33 -0
- package/dist/src/tui/workflowRuntime.d.cts.map +1 -0
- package/dist/src/tui/workflowRuntime.d.mts +33 -0
- package/dist/src/tui/workflowRuntime.d.mts.map +1 -0
- package/dist/src/tui/workflowRuntime.mjs +1930 -0
- package/dist/src/tui/workflowRuntime.mjs.map +1 -0
- package/dist/src/types/apiRoutes.cjs +82 -0
- package/dist/src/types/apiRoutes.cjs.map +1 -0
- package/dist/src/types/apiRoutes.mjs +80 -0
- package/dist/src/types/apiRoutes.mjs.map +1 -0
- package/dist/src/types/webWorkflowTerminal.cjs +23 -0
- package/dist/src/types/webWorkflowTerminal.cjs.map +1 -0
- package/dist/src/types/webWorkflowTerminal.mjs +22 -0
- package/dist/src/types/webWorkflowTerminal.mjs.map +1 -0
- package/dist/src/types/webWorkflows.cjs +8 -0
- package/dist/src/types/webWorkflows.cjs.map +1 -0
- package/dist/src/types/webWorkflows.mjs +7 -0
- package/dist/src/types/webWorkflows.mjs.map +1 -0
- 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
|