@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,319 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
//#region src/services/workflowRuns/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* MIRRORS @agimon-ai/workflow-mcp's on-disk registry
|
|
5
|
+
* (src/services/WorkflowRegistryService.ts and WorkflowProgressService.ts in
|
|
6
|
+
* that package). The engine keeps one directory per run under
|
|
7
|
+
* `<home>/workspaces/<workspace>/<stage>/<runKey>/` holding a `run.json`
|
|
8
|
+
* record and an append-only `progress.ndjson` job/step log; the hub only ever
|
|
9
|
+
* reads them. The derivation is duplicated rather than imported because
|
|
10
|
+
* doompi-web must not depend on the engine at runtime; the unit suite pins
|
|
11
|
+
* this mirror against the package's published types.
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_WORKFLOW_HOME_DIR_NAME = ".workflow-mcp";
|
|
14
|
+
const WORKFLOW_HOME_ENV = "WORKFLOW_MCP_HOME";
|
|
15
|
+
const WORKSPACES_DIR_NAME = "workspaces";
|
|
16
|
+
const RUN_RECORD_FILE_NAME = "run.json";
|
|
17
|
+
const PROGRESS_FILE_NAME = "progress.ndjson";
|
|
18
|
+
const WORKFLOW_STAGES = [
|
|
19
|
+
"running",
|
|
20
|
+
"completed",
|
|
21
|
+
"error"
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Errored runs stay a day, which is how long recovery has to act on them.
|
|
25
|
+
*
|
|
26
|
+
* Finished runs have no retention of their own: a session keeps every run it
|
|
27
|
+
* launched, grouped by outcome, for as long as the session lives. A run leaves
|
|
28
|
+
* the view when the engine prunes its registry directory, not on a timer here,
|
|
29
|
+
* because "what did this session run today" is a question asked long after the
|
|
30
|
+
* run settled.
|
|
31
|
+
*/
|
|
32
|
+
const WORKFLOW_ERROR_RETENTION_MS = 864e5;
|
|
33
|
+
/** The env key doompi-workflow stamps on runs it launches; ties a run to a Pi session. */
|
|
34
|
+
const PI_SESSION_ENV = "PI_SESSION_ID";
|
|
35
|
+
const PROGRESS_STATES = /* @__PURE__ */ new Set([
|
|
36
|
+
"running",
|
|
37
|
+
"completed",
|
|
38
|
+
"skipped",
|
|
39
|
+
"failed",
|
|
40
|
+
"pause_requested",
|
|
41
|
+
"paused",
|
|
42
|
+
"resumed"
|
|
43
|
+
]);
|
|
44
|
+
const OUTCOMES = /* @__PURE__ */ new Set([
|
|
45
|
+
"success",
|
|
46
|
+
"skipped",
|
|
47
|
+
"failed",
|
|
48
|
+
"interrupted"
|
|
49
|
+
]);
|
|
50
|
+
const EXECUTION_STATES = /* @__PURE__ */ new Set([
|
|
51
|
+
"running",
|
|
52
|
+
"pause_requested",
|
|
53
|
+
"paused",
|
|
54
|
+
"resume_requested"
|
|
55
|
+
]);
|
|
56
|
+
const STEP_TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
57
|
+
"completed",
|
|
58
|
+
"skipped",
|
|
59
|
+
"failed"
|
|
60
|
+
]);
|
|
61
|
+
/** The engine records its pre:/post: blocks under these reserved pseudo-job names. */
|
|
62
|
+
const PHASE_JOB_PRE = "pre";
|
|
63
|
+
const PHASE_JOB_POST = "post";
|
|
64
|
+
const ANSI_ESCAPE_PATTERN = /\u001b\[[0-9;]*[A-Za-z]/g;
|
|
65
|
+
/** Where workflow-mcp keeps its registry, mirroring the engine's own resolution. */
|
|
66
|
+
function resolveWorkflowHome(input) {
|
|
67
|
+
return resolve(input.envValue !== void 0 && input.envValue !== "" ? input.envValue : resolve(input.homeDir, DEFAULT_WORKFLOW_HOME_DIR_NAME));
|
|
68
|
+
}
|
|
69
|
+
function isRecord(value) {
|
|
70
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
71
|
+
}
|
|
72
|
+
function asOptionalString(value) {
|
|
73
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
74
|
+
}
|
|
75
|
+
function asOptionalNumber(value) {
|
|
76
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
77
|
+
}
|
|
78
|
+
function stripAnsi(text) {
|
|
79
|
+
return text.replace(ANSI_ESCAPE_PATTERN, "").trim();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Validates one run.json into the wire shape plus scoping facts.
|
|
83
|
+
*
|
|
84
|
+
* Returns undefined for anything unreadable or a foreign format. The record
|
|
85
|
+
* is trusted for its own stage: the registry rewrites it when a run moves
|
|
86
|
+
* between stage directories.
|
|
87
|
+
*/
|
|
88
|
+
function parseWorkflowRunRecord(raw) {
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(raw);
|
|
92
|
+
} catch {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!isRecord(parsed)) return void 0;
|
|
96
|
+
const runKey = asOptionalString(parsed.runKey);
|
|
97
|
+
const workspace = asOptionalString(parsed.workspace);
|
|
98
|
+
const workflowPath = asOptionalString(parsed.workflowPath);
|
|
99
|
+
const startedAt = asOptionalString(parsed.startedAt);
|
|
100
|
+
const stage = asOptionalString(parsed.stage);
|
|
101
|
+
if (!runKey || !workspace || !workflowPath || !startedAt) return void 0;
|
|
102
|
+
if (stage === void 0 || !WORKFLOW_STAGES.includes(stage)) return void 0;
|
|
103
|
+
const outcome = asOptionalString(parsed.outcome);
|
|
104
|
+
const executionState = asOptionalString(parsed.executionState);
|
|
105
|
+
const rawError = asOptionalString(parsed.errorMessage);
|
|
106
|
+
const env = isRecord(parsed.env) ? parsed.env : void 0;
|
|
107
|
+
const view = {
|
|
108
|
+
runKey,
|
|
109
|
+
workspace,
|
|
110
|
+
displayName: asOptionalString(parsed.displayName) ?? runKey,
|
|
111
|
+
...asOptionalString(parsed.workflowName) === void 0 ? {} : { workflowName: asOptionalString(parsed.workflowName) },
|
|
112
|
+
workflowPath,
|
|
113
|
+
stage,
|
|
114
|
+
...outcome !== void 0 && OUTCOMES.has(outcome) ? { outcome } : {},
|
|
115
|
+
...executionState !== void 0 && EXECUTION_STATES.has(executionState) ? { executionState } : {},
|
|
116
|
+
...asOptionalString(parsed.prompt) === void 0 ? {} : { prompt: asOptionalString(parsed.prompt) },
|
|
117
|
+
startedAt,
|
|
118
|
+
...asOptionalString(parsed.finishedAt) === void 0 ? {} : { finishedAt: asOptionalString(parsed.finishedAt) },
|
|
119
|
+
...rawError === void 0 ? {} : { errorMessage: stripAnsi(rawError) },
|
|
120
|
+
...asOptionalString(parsed.failedJob) === void 0 ? {} : { failedJob: asOptionalString(parsed.failedJob) },
|
|
121
|
+
...parsed.stale === true ? { stale: true } : {},
|
|
122
|
+
...asOptionalString(parsed.staleReason) === void 0 ? {} : { staleReason: asOptionalString(parsed.staleReason) },
|
|
123
|
+
...asOptionalString(parsed.worktreeBranch) === void 0 ? {} : { worktreeBranch: asOptionalString(parsed.worktreeBranch) },
|
|
124
|
+
jobs: []
|
|
125
|
+
};
|
|
126
|
+
const piSessionId = env === void 0 ? void 0 : asOptionalString(env[PI_SESSION_ENV]);
|
|
127
|
+
return {
|
|
128
|
+
view,
|
|
129
|
+
...piSessionId === void 0 ? {} : { piSessionId }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Parses the append-only progress log. Malformed lines are skipped rather
|
|
134
|
+
* than failing the file: a reader routinely catches a torn final line
|
|
135
|
+
* mid-append.
|
|
136
|
+
*/
|
|
137
|
+
function parseWorkflowProgress(raw) {
|
|
138
|
+
const events = [];
|
|
139
|
+
for (const line of raw.split("\n")) {
|
|
140
|
+
const trimmed = line.trim();
|
|
141
|
+
if (trimmed === "") continue;
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(trimmed);
|
|
145
|
+
} catch {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!isRecord(parsed)) continue;
|
|
149
|
+
const type = parsed.type;
|
|
150
|
+
const status = asOptionalString(parsed.status);
|
|
151
|
+
const job = asOptionalString(parsed.job);
|
|
152
|
+
const at = asOptionalString(parsed.at);
|
|
153
|
+
if (type !== "job" && type !== "step" || !job || !at) continue;
|
|
154
|
+
if (status === void 0 || !PROGRESS_STATES.has(status)) continue;
|
|
155
|
+
const step = asOptionalString(parsed.step);
|
|
156
|
+
const index = asOptionalNumber(parsed.index);
|
|
157
|
+
const total = asOptionalNumber(parsed.total);
|
|
158
|
+
const reason = asOptionalString(parsed.reason);
|
|
159
|
+
events.push({
|
|
160
|
+
type,
|
|
161
|
+
status,
|
|
162
|
+
job,
|
|
163
|
+
...step === void 0 ? {} : { step },
|
|
164
|
+
...index === void 0 ? {} : { index },
|
|
165
|
+
...total === void 0 ? {} : { total },
|
|
166
|
+
...reason === void 0 ? {} : { reason },
|
|
167
|
+
at
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return events;
|
|
171
|
+
}
|
|
172
|
+
function jobPhase(name) {
|
|
173
|
+
if (name === PHASE_JOB_PRE) return PHASE_JOB_PRE;
|
|
174
|
+
if (name === PHASE_JOB_POST) return PHASE_JOB_POST;
|
|
175
|
+
return "job";
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Folds the event log into the current job tree; later events win, so a job
|
|
179
|
+
* that started and later failed reads as failed. Jobs and steps keep the
|
|
180
|
+
* order of their first appearance.
|
|
181
|
+
*/
|
|
182
|
+
function foldWorkflowProgress(events) {
|
|
183
|
+
const jobs = [];
|
|
184
|
+
const jobByName = /* @__PURE__ */ new Map();
|
|
185
|
+
for (const event of events) {
|
|
186
|
+
let job = jobByName.get(event.job);
|
|
187
|
+
if (job === void 0) {
|
|
188
|
+
job = {
|
|
189
|
+
name: event.job,
|
|
190
|
+
phase: jobPhase(event.job),
|
|
191
|
+
status: event.status,
|
|
192
|
+
steps: []
|
|
193
|
+
};
|
|
194
|
+
jobByName.set(event.job, job);
|
|
195
|
+
jobs.push(job);
|
|
196
|
+
}
|
|
197
|
+
if (event.type === "job") {
|
|
198
|
+
job.status = event.status;
|
|
199
|
+
if (event.reason !== void 0) job.reason = event.reason;
|
|
200
|
+
if (event.index !== void 0) job.index = event.index;
|
|
201
|
+
if (event.total !== void 0) job.total = event.total;
|
|
202
|
+
if (event.status === "running" && job.startedAt === void 0) job.startedAt = event.at;
|
|
203
|
+
if (STEP_TERMINAL_STATES.has(event.status)) job.endedAt = event.at;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (event.step === void 0) continue;
|
|
207
|
+
let step = job.steps.find((candidate) => candidate.name === event.step);
|
|
208
|
+
if (step === void 0) {
|
|
209
|
+
step = {
|
|
210
|
+
name: event.step,
|
|
211
|
+
status: event.status
|
|
212
|
+
};
|
|
213
|
+
job.steps.push(step);
|
|
214
|
+
}
|
|
215
|
+
step.status = event.status;
|
|
216
|
+
if (event.reason !== void 0) step.reason = event.reason;
|
|
217
|
+
if (event.status === "running" && step.startedAt === void 0) step.startedAt = event.at;
|
|
218
|
+
if (STEP_TERMINAL_STATES.has(event.status)) step.endedAt = event.at;
|
|
219
|
+
}
|
|
220
|
+
return jobs;
|
|
221
|
+
}
|
|
222
|
+
const ACTIVE_PROGRESS_STATES = /* @__PURE__ */ new Set([
|
|
223
|
+
"running",
|
|
224
|
+
"pause_requested",
|
|
225
|
+
"paused",
|
|
226
|
+
"resumed"
|
|
227
|
+
]);
|
|
228
|
+
/** The job and step a run is on right now, for the NOW breadcrumb. */
|
|
229
|
+
function workflowPosition(jobs) {
|
|
230
|
+
for (let jobIndex = jobs.length - 1; jobIndex >= 0; jobIndex -= 1) {
|
|
231
|
+
const job = jobs[jobIndex];
|
|
232
|
+
if (!ACTIVE_PROGRESS_STATES.has(job.status)) continue;
|
|
233
|
+
for (let stepIndex = job.steps.length - 1; stepIndex >= 0; stepIndex -= 1) {
|
|
234
|
+
const step = job.steps[stepIndex];
|
|
235
|
+
if (ACTIVE_PROGRESS_STATES.has(step.status)) return {
|
|
236
|
+
job: job.name,
|
|
237
|
+
step: step.name,
|
|
238
|
+
...job.index === void 0 ? {} : { index: job.index },
|
|
239
|
+
...job.total === void 0 ? {} : { total: job.total }
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
job: job.name,
|
|
244
|
+
...job.index === void 0 ? {} : { index: job.index },
|
|
245
|
+
...job.total === void 0 ? {} : { total: job.total }
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** The finished view: record fields plus the folded job tree and position. */
|
|
250
|
+
function completeWorkflowRunView(view, jobs) {
|
|
251
|
+
const position = view.stage === "running" ? workflowPosition(jobs) : void 0;
|
|
252
|
+
return {
|
|
253
|
+
...view,
|
|
254
|
+
jobs,
|
|
255
|
+
...position === void 0 ? {} : { position }
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Whether a run belongs on a session's workflow tab: launched by that Pi
|
|
260
|
+
* session, which is the same env test doompi-workflow's isSessionRun applies.
|
|
261
|
+
*
|
|
262
|
+
* The registry is one directory under $HOME shared by every repository and
|
|
263
|
+
* every Pi session on the machine, so repository proximity is not ownership.
|
|
264
|
+
* Scoping on it as well let two sessions open in one repo read each other's
|
|
265
|
+
* history, and kept a dead session's runs on the board of every session that
|
|
266
|
+
* replaced it. Fails closed on an unstamped record, which is what a launch
|
|
267
|
+
* from the CLI rather than from a session leaves behind.
|
|
268
|
+
*/
|
|
269
|
+
function runBelongsToSession(run, sessionId) {
|
|
270
|
+
return run.piSessionId !== void 0 && run.piSessionId === sessionId;
|
|
271
|
+
}
|
|
272
|
+
function parseTime(value) {
|
|
273
|
+
if (value === void 0) return 0;
|
|
274
|
+
const time = Date.parse(value);
|
|
275
|
+
return Number.isFinite(time) ? time : 0;
|
|
276
|
+
}
|
|
277
|
+
const STAGE_ORDER = {
|
|
278
|
+
running: 0,
|
|
279
|
+
error: 1,
|
|
280
|
+
completed: 2
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Whether a run still belongs in the view.
|
|
284
|
+
*
|
|
285
|
+
* Only errored runs age out here, and slowly: everything else a session
|
|
286
|
+
* started stays until the engine forgets the run itself.
|
|
287
|
+
*/
|
|
288
|
+
function withinRetention(run, now) {
|
|
289
|
+
if (run.stage !== "error") return true;
|
|
290
|
+
return now - (parseTime(run.finishedAt) || parseTime(run.startedAt)) < WORKFLOW_ERROR_RETENTION_MS;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* When a run last moved, which is what orders it within its group.
|
|
294
|
+
*
|
|
295
|
+
* A running run has only its start; a settled one is ordered by when it
|
|
296
|
+
* settled, because a list kept for the whole session is read as a history and
|
|
297
|
+
* two runs started together can finish an hour apart.
|
|
298
|
+
*/
|
|
299
|
+
function lastMovedAt(run) {
|
|
300
|
+
return run.stage === "running" ? parseTime(run.startedAt) : parseTime(run.finishedAt) || parseTime(run.startedAt);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Running runs first, then errored (recovery acts on them), then finished;
|
|
304
|
+
* newest first within a group, and each group capped on its own.
|
|
305
|
+
*/
|
|
306
|
+
function presentWorkflowRuns(runs, now) {
|
|
307
|
+
const groups = /* @__PURE__ */ new Map();
|
|
308
|
+
for (const run of runs) {
|
|
309
|
+
if (!withinRetention(run, now)) continue;
|
|
310
|
+
const group = groups.get(run.stage);
|
|
311
|
+
if (group) group.push(run);
|
|
312
|
+
else groups.set(run.stage, [run]);
|
|
313
|
+
}
|
|
314
|
+
return [...groups.entries()].sort(([left], [right]) => STAGE_ORDER[left] - STAGE_ORDER[right]).flatMap(([, group]) => group.sort((left, right) => lastMovedAt(right) - lastMovedAt(left)).slice(0, 24));
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
export { PI_SESSION_ENV, PROGRESS_FILE_NAME, RUN_RECORD_FILE_NAME, WORKFLOW_ERROR_RETENTION_MS, WORKFLOW_HOME_ENV, WORKFLOW_STAGES, WORKSPACES_DIR_NAME, completeWorkflowRunView, foldWorkflowProgress, parseWorkflowProgress, parseWorkflowRunRecord, presentWorkflowRuns, resolveWorkflowHome, runBelongsToSession, workflowPosition };
|
|
318
|
+
|
|
319
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowRuns/index.ts"],"sourcesContent":["import { resolve } from 'node:path';\n\nimport type {\n WorkflowJobPhase,\n WorkflowJobView,\n WorkflowOutcome,\n WorkflowPosition,\n WorkflowProgressState,\n WorkflowRunView,\n WorkflowStage,\n WorkflowStepView,\n} from '../../types/webWorkflows';\n\n/**\n * MIRRORS @agimon-ai/workflow-mcp's on-disk registry\n * (src/services/WorkflowRegistryService.ts and WorkflowProgressService.ts in\n * that package). The engine keeps one directory per run under\n * `<home>/workspaces/<workspace>/<stage>/<runKey>/` holding a `run.json`\n * record and an append-only `progress.ndjson` job/step log; the hub only ever\n * reads them. The derivation is duplicated rather than imported because\n * doompi-web must not depend on the engine at runtime; the unit suite pins\n * this mirror against the package's published types.\n */\nconst DEFAULT_WORKFLOW_HOME_DIR_NAME = '.workflow-mcp';\nexport const WORKFLOW_HOME_ENV = 'WORKFLOW_MCP_HOME';\nexport const WORKSPACES_DIR_NAME = 'workspaces';\nexport const RUN_RECORD_FILE_NAME = 'run.json';\nexport const PROGRESS_FILE_NAME = 'progress.ndjson';\nexport const WORKFLOW_STAGES: readonly WorkflowStage[] = ['running', 'completed', 'error'];\n\n/**\n * Errored runs stay a day, which is how long recovery has to act on them.\n *\n * Finished runs have no retention of their own: a session keeps every run it\n * launched, grouped by outcome, for as long as the session lives. A run leaves\n * the view when the engine prunes its registry directory, not on a timer here,\n * because \"what did this session run today\" is a question asked long after the\n * run settled.\n */\nexport const WORKFLOW_ERROR_RETENTION_MS = 24 * 60 * 60 * 1000;\n/**\n * Upper bound per outcome group rather than over the whole list.\n *\n * One cap across every run let a long stream of green runs push a failure out\n * of the list, which is the one thing that must never fall off the end.\n */\nexport const MAX_PRESENTED_WORKFLOW_RUNS_PER_GROUP = 24;\n\n/** The env key doompi-workflow stamps on runs it launches; ties a run to a Pi session. */\nexport const PI_SESSION_ENV = 'PI_SESSION_ID';\n\nconst PROGRESS_STATES: ReadonlySet<string> = new Set([\n 'running',\n 'completed',\n 'skipped',\n 'failed',\n 'pause_requested',\n 'paused',\n 'resumed',\n]);\nconst OUTCOMES: ReadonlySet<string> = new Set(['success', 'skipped', 'failed', 'interrupted']);\nconst EXECUTION_STATES: ReadonlySet<string> = new Set(['running', 'pause_requested', 'paused', 'resume_requested']);\nconst STEP_TERMINAL_STATES: ReadonlySet<WorkflowProgressState> = new Set(['completed', 'skipped', 'failed']);\n/** The engine records its pre:/post: blocks under these reserved pseudo-job names. */\nconst PHASE_JOB_PRE = 'pre';\nconst PHASE_JOB_POST = 'post';\n\n// oxlint-disable-next-line no-control-regex -- stripping terminal color codes from errorMessage is the point\nconst ANSI_ESCAPE_PATTERN = /\\u001b\\[[0-9;]*[A-Za-z]/g;\n\nexport interface WorkflowHomeInput {\n /** process.env.WORKFLOW_MCP_HOME, supplied by the caller. */\n envValue: string | undefined;\n /** os.homedir(), supplied by the caller. */\n homeDir: string;\n}\n\n/** Where workflow-mcp keeps its registry, mirroring the engine's own resolution. */\nexport function resolveWorkflowHome(input: WorkflowHomeInput): string {\n return resolve(\n input.envValue !== undefined && input.envValue !== ''\n ? input.envValue\n : resolve(input.homeDir, DEFAULT_WORKFLOW_HOME_DIR_NAME),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asOptionalNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction stripAnsi(text: string): string {\n return text.replace(ANSI_ESCAPE_PATTERN, '').trim();\n}\n\n/**\n * One run record plus the facts the hub needs for session scoping but the\n * page never sees.\n */\nexport interface ParsedWorkflowRun {\n /** The wire view, before the progress log is folded in (jobs empty). */\n view: WorkflowRunView;\n /** env.PI_SESSION_ID from the record, when the launcher stamped one. */\n piSessionId?: string;\n}\n\n/**\n * Validates one run.json into the wire shape plus scoping facts.\n *\n * Returns undefined for anything unreadable or a foreign format. The record\n * is trusted for its own stage: the registry rewrites it when a run moves\n * between stage directories.\n */\nexport function parseWorkflowRunRecord(raw: string): ParsedWorkflowRun | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n const runKey = asOptionalString(parsed.runKey);\n const workspace = asOptionalString(parsed.workspace);\n const workflowPath = asOptionalString(parsed.workflowPath);\n const startedAt = asOptionalString(parsed.startedAt);\n const stage = asOptionalString(parsed.stage);\n if (!runKey || !workspace || !workflowPath || !startedAt) return undefined;\n if (stage === undefined || !WORKFLOW_STAGES.includes(stage as WorkflowStage)) return undefined;\n\n const outcome = asOptionalString(parsed.outcome);\n const executionState = asOptionalString(parsed.executionState);\n const rawError = asOptionalString(parsed.errorMessage);\n const env = isRecord(parsed.env) ? parsed.env : undefined;\n const view: WorkflowRunView = {\n runKey,\n workspace,\n displayName: asOptionalString(parsed.displayName) ?? runKey,\n ...(asOptionalString(parsed.workflowName) === undefined\n ? {}\n : { workflowName: asOptionalString(parsed.workflowName) }),\n workflowPath,\n stage: stage as WorkflowStage,\n ...(outcome !== undefined && OUTCOMES.has(outcome) ? { outcome: outcome as WorkflowOutcome } : {}),\n ...(executionState !== undefined && EXECUTION_STATES.has(executionState)\n ? { executionState: executionState as WorkflowRunView['executionState'] }\n : {}),\n ...(asOptionalString(parsed.prompt) === undefined ? {} : { prompt: asOptionalString(parsed.prompt) }),\n startedAt,\n ...(asOptionalString(parsed.finishedAt) === undefined ? {} : { finishedAt: asOptionalString(parsed.finishedAt) }),\n ...(rawError === undefined ? {} : { errorMessage: stripAnsi(rawError) }),\n ...(asOptionalString(parsed.failedJob) === undefined ? {} : { failedJob: asOptionalString(parsed.failedJob) }),\n ...(parsed.stale === true ? { stale: true } : {}),\n ...(asOptionalString(parsed.staleReason) === undefined\n ? {}\n : { staleReason: asOptionalString(parsed.staleReason) }),\n ...(asOptionalString(parsed.worktreeBranch) === undefined\n ? {}\n : { worktreeBranch: asOptionalString(parsed.worktreeBranch) }),\n jobs: [],\n };\n const piSessionId = env === undefined ? undefined : asOptionalString(env[PI_SESSION_ENV]);\n return { view, ...(piSessionId === undefined ? {} : { piSessionId }) };\n}\n\ninterface WorkflowProgressEvent {\n type: 'job' | 'step';\n status: WorkflowProgressState;\n job: string;\n step?: string;\n index?: number;\n total?: number;\n reason?: string;\n at: string;\n}\n\n/**\n * Parses the append-only progress log. Malformed lines are skipped rather\n * than failing the file: a reader routinely catches a torn final line\n * mid-append.\n */\nexport function parseWorkflowProgress(raw: string): WorkflowProgressEvent[] {\n const events: WorkflowProgressEvent[] = [];\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '') continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n continue;\n }\n if (!isRecord(parsed)) continue;\n const type = parsed.type;\n const status = asOptionalString(parsed.status);\n const job = asOptionalString(parsed.job);\n const at = asOptionalString(parsed.at);\n if ((type !== 'job' && type !== 'step') || !job || !at) continue;\n if (status === undefined || !PROGRESS_STATES.has(status)) continue;\n const step = asOptionalString(parsed.step);\n const index = asOptionalNumber(parsed.index);\n const total = asOptionalNumber(parsed.total);\n const reason = asOptionalString(parsed.reason);\n events.push({\n type,\n status: status as WorkflowProgressState,\n job,\n ...(step === undefined ? {} : { step }),\n ...(index === undefined ? {} : { index }),\n ...(total === undefined ? {} : { total }),\n ...(reason === undefined ? {} : { reason }),\n at,\n });\n }\n return events;\n}\n\nfunction jobPhase(name: string): WorkflowJobPhase {\n if (name === PHASE_JOB_PRE) return PHASE_JOB_PRE;\n if (name === PHASE_JOB_POST) return PHASE_JOB_POST;\n return 'job';\n}\n\n/**\n * Folds the event log into the current job tree; later events win, so a job\n * that started and later failed reads as failed. Jobs and steps keep the\n * order of their first appearance.\n */\nexport function foldWorkflowProgress(events: readonly WorkflowProgressEvent[]): WorkflowJobView[] {\n const jobs: WorkflowJobView[] = [];\n const jobByName = new Map<string, WorkflowJobView>();\n for (const event of events) {\n let job = jobByName.get(event.job);\n if (job === undefined) {\n job = { name: event.job, phase: jobPhase(event.job), status: event.status, steps: [] };\n jobByName.set(event.job, job);\n jobs.push(job);\n }\n if (event.type === 'job') {\n job.status = event.status;\n if (event.reason !== undefined) job.reason = event.reason;\n if (event.index !== undefined) job.index = event.index;\n if (event.total !== undefined) job.total = event.total;\n if (event.status === 'running' && job.startedAt === undefined) job.startedAt = event.at;\n if (STEP_TERMINAL_STATES.has(event.status)) job.endedAt = event.at;\n continue;\n }\n if (event.step === undefined) continue;\n let step: WorkflowStepView | undefined = job.steps.find((candidate) => candidate.name === event.step);\n if (step === undefined) {\n step = { name: event.step, status: event.status };\n job.steps.push(step);\n }\n step.status = event.status;\n if (event.reason !== undefined) step.reason = event.reason;\n if (event.status === 'running' && step.startedAt === undefined) step.startedAt = event.at;\n if (STEP_TERMINAL_STATES.has(event.status)) step.endedAt = event.at;\n }\n return jobs;\n}\n\nconst ACTIVE_PROGRESS_STATES: ReadonlySet<WorkflowProgressState> = new Set([\n 'running',\n 'pause_requested',\n 'paused',\n 'resumed',\n]);\n\n/** The job and step a run is on right now, for the NOW breadcrumb. */\nexport function workflowPosition(jobs: readonly WorkflowJobView[]): WorkflowPosition | undefined {\n for (let jobIndex = jobs.length - 1; jobIndex >= 0; jobIndex -= 1) {\n const job = jobs[jobIndex]!;\n if (!ACTIVE_PROGRESS_STATES.has(job.status)) continue;\n for (let stepIndex = job.steps.length - 1; stepIndex >= 0; stepIndex -= 1) {\n const step = job.steps[stepIndex]!;\n if (ACTIVE_PROGRESS_STATES.has(step.status)) {\n return {\n job: job.name,\n step: step.name,\n ...(job.index === undefined ? {} : { index: job.index }),\n ...(job.total === undefined ? {} : { total: job.total }),\n };\n }\n }\n return {\n job: job.name,\n ...(job.index === undefined ? {} : { index: job.index }),\n ...(job.total === undefined ? {} : { total: job.total }),\n };\n }\n return undefined;\n}\n\n/** The finished view: record fields plus the folded job tree and position. */\nexport function completeWorkflowRunView(view: WorkflowRunView, jobs: WorkflowJobView[]): WorkflowRunView {\n const position = view.stage === 'running' ? workflowPosition(jobs) : undefined;\n return { ...view, jobs, ...(position === undefined ? {} : { position }) };\n}\n\n/**\n * Whether a run belongs on a session's workflow tab: launched by that Pi\n * session, which is the same env test doompi-workflow's isSessionRun applies.\n *\n * The registry is one directory under $HOME shared by every repository and\n * every Pi session on the machine, so repository proximity is not ownership.\n * Scoping on it as well let two sessions open in one repo read each other's\n * history, and kept a dead session's runs on the board of every session that\n * replaced it. Fails closed on an unstamped record, which is what a launch\n * from the CLI rather than from a session leaves behind.\n */\nexport function runBelongsToSession(run: ParsedWorkflowRun, sessionId: string): boolean {\n return run.piSessionId !== undefined && run.piSessionId === sessionId;\n}\n\nfunction parseTime(value: string | undefined): number {\n if (value === undefined) return 0;\n const time = Date.parse(value);\n return Number.isFinite(time) ? time : 0;\n}\n\nconst STAGE_ORDER: Readonly<Record<WorkflowStage, number>> = { running: 0, error: 1, completed: 2 };\n\n/**\n * Whether a run still belongs in the view.\n *\n * Only errored runs age out here, and slowly: everything else a session\n * started stays until the engine forgets the run itself.\n */\nfunction withinRetention(run: WorkflowRunView, now: number): boolean {\n if (run.stage !== 'error') return true;\n const settledAt = parseTime(run.finishedAt) || parseTime(run.startedAt);\n return now - settledAt < WORKFLOW_ERROR_RETENTION_MS;\n}\n\n/**\n * When a run last moved, which is what orders it within its group.\n *\n * A running run has only its start; a settled one is ordered by when it\n * settled, because a list kept for the whole session is read as a history and\n * two runs started together can finish an hour apart.\n */\nfunction lastMovedAt(run: WorkflowRunView): number {\n return run.stage === 'running' ? parseTime(run.startedAt) : parseTime(run.finishedAt) || parseTime(run.startedAt);\n}\n\n/**\n * Running runs first, then errored (recovery acts on them), then finished;\n * newest first within a group, and each group capped on its own.\n */\nexport function presentWorkflowRuns(runs: readonly WorkflowRunView[], now: number): WorkflowRunView[] {\n const groups = new Map<WorkflowStage, WorkflowRunView[]>();\n for (const run of runs) {\n if (!withinRetention(run, now)) continue;\n const group = groups.get(run.stage);\n if (group) group.push(run);\n else groups.set(run.stage, [run]);\n }\n return [...groups.entries()]\n .sort(([left], [right]) => STAGE_ORDER[left] - STAGE_ORDER[right])\n .flatMap(([, group]) =>\n group\n .sort((left, right) => lastMovedAt(right) - lastMovedAt(left))\n .slice(0, MAX_PRESENTED_WORKFLOW_RUNS_PER_GROUP),\n );\n}\n"],"mappings":";;;;;;;;;;;;AAuBA,MAAM,iCAAiC;AACvC,MAAa,oBAAoB;AACjC,MAAa,sBAAsB;AACnC,MAAa,uBAAuB;AACpC,MAAa,qBAAqB;AAClC,MAAa,kBAA4C;CAAC;CAAW;CAAa;AAAO;;;;;;;;;;AAWzF,MAAa,8BAA8B;;AAU3C,MAAa,iBAAiB;AAE9B,MAAM,kCAAuC,IAAI,IAAI;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,2BAAgC,IAAI,IAAI;CAAC;CAAW;CAAW;CAAU;AAAa,CAAC;AAC7F,MAAM,mCAAwC,IAAI,IAAI;CAAC;CAAW;CAAmB;CAAU;AAAkB,CAAC;AAClH,MAAM,uCAA2D,IAAI,IAAI;CAAC;CAAa;CAAW;AAAQ,CAAC;;AAE3G,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AAGvB,MAAM,sBAAsB;;AAU5B,SAAgB,oBAAoB,OAAkC;CACpE,OAAO,QACL,MAAM,aAAa,KAAA,KAAa,MAAM,aAAa,KAC/C,MAAM,WACN,QAAQ,MAAM,SAAS,8BAA8B,CAC3D;AACF;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,iBAAiB,OAAoC;CAC5D,OAAO,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ,KAAA;AAC7D;AAEA,SAAS,iBAAiB,OAAoC;CAC5D,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACvE;AAEA,SAAS,UAAU,MAAsB;CACvC,OAAO,KAAK,QAAQ,qBAAqB,EAAE,CAAC,CAAC,KAAK;AACpD;;;;;;;;AAoBA,SAAgB,uBAAuB,KAA4C;CACjF,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EACN;CACF;CACA,IAAI,CAAC,SAAS,MAAM,GAAG,OAAO,KAAA;CAC9B,MAAM,SAAS,iBAAiB,OAAO,MAAM;CAC7C,MAAM,YAAY,iBAAiB,OAAO,SAAS;CACnD,MAAM,eAAe,iBAAiB,OAAO,YAAY;CACzD,MAAM,YAAY,iBAAiB,OAAO,SAAS;CACnD,MAAM,QAAQ,iBAAiB,OAAO,KAAK;CAC3C,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,gBAAgB,CAAC,WAAW,OAAO,KAAA;CACjE,IAAI,UAAU,KAAA,KAAa,CAAC,gBAAgB,SAAS,KAAsB,GAAG,OAAO,KAAA;CAErF,MAAM,UAAU,iBAAiB,OAAO,OAAO;CAC/C,MAAM,iBAAiB,iBAAiB,OAAO,cAAc;CAC7D,MAAM,WAAW,iBAAiB,OAAO,YAAY;CACrD,MAAM,MAAM,SAAS,OAAO,GAAG,IAAI,OAAO,MAAM,KAAA;CAChD,MAAM,OAAwB;EAC5B;EACA;EACA,aAAa,iBAAiB,OAAO,WAAW,KAAK;EACrD,GAAI,iBAAiB,OAAO,YAAY,MAAM,KAAA,IAC1C,CAAC,IACD,EAAE,cAAc,iBAAiB,OAAO,YAAY,EAAE;EAC1D;EACO;EACP,GAAI,YAAY,KAAA,KAAa,SAAS,IAAI,OAAO,IAAI,EAAW,QAA2B,IAAI,CAAC;EAChG,GAAI,mBAAmB,KAAA,KAAa,iBAAiB,IAAI,cAAc,IACnE,EAAkB,eAAoD,IACtE,CAAC;EACL,GAAI,iBAAiB,OAAO,MAAM,MAAM,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,iBAAiB,OAAO,MAAM,EAAE;EACnG;EACA,GAAI,iBAAiB,OAAO,UAAU,MAAM,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,iBAAiB,OAAO,UAAU,EAAE;EAC/G,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc,UAAU,QAAQ,EAAE;EACtE,GAAI,iBAAiB,OAAO,SAAS,MAAM,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,iBAAiB,OAAO,SAAS,EAAE;EAC5G,GAAI,OAAO,UAAU,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;EAC/C,GAAI,iBAAiB,OAAO,WAAW,MAAM,KAAA,IACzC,CAAC,IACD,EAAE,aAAa,iBAAiB,OAAO,WAAW,EAAE;EACxD,GAAI,iBAAiB,OAAO,cAAc,MAAM,KAAA,IAC5C,CAAC,IACD,EAAE,gBAAgB,iBAAiB,OAAO,cAAc,EAAE;EAC9D,MAAM,CAAC;CACT;CACA,MAAM,cAAc,QAAQ,KAAA,IAAY,KAAA,IAAY,iBAAiB,IAAI,eAAe;CACxF,OAAO;EAAE;EAAM,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;CAAG;AACvE;;;;;;AAkBA,SAAgB,sBAAsB,KAAsC;CAC1E,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,GAAG;EAClC,MAAM,UAAU,KAAK,KAAK;EAC1B,IAAI,YAAY,IAAI;EACpB,IAAI;EACJ,IAAI;GACF,SAAS,KAAK,MAAM,OAAO;EAC7B,QAAQ;GACN;EACF;EACA,IAAI,CAAC,SAAS,MAAM,GAAG;EACvB,MAAM,OAAO,OAAO;EACpB,MAAM,SAAS,iBAAiB,OAAO,MAAM;EAC7C,MAAM,MAAM,iBAAiB,OAAO,GAAG;EACvC,MAAM,KAAK,iBAAiB,OAAO,EAAE;EACrC,IAAK,SAAS,SAAS,SAAS,UAAW,CAAC,OAAO,CAAC,IAAI;EACxD,IAAI,WAAW,KAAA,KAAa,CAAC,gBAAgB,IAAI,MAAM,GAAG;EAC1D,MAAM,OAAO,iBAAiB,OAAO,IAAI;EACzC,MAAM,QAAQ,iBAAiB,OAAO,KAAK;EAC3C,MAAM,QAAQ,iBAAiB,OAAO,KAAK;EAC3C,MAAM,SAAS,iBAAiB,OAAO,MAAM;EAC7C,OAAO,KAAK;GACV;GACQ;GACR;GACA,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;GACrC,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;GACvC,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;GACvC,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO;GACzC;EACF,CAAC;CACH;CACA,OAAO;AACT;AAEA,SAAS,SAAS,MAAgC;CAChD,IAAI,SAAS,eAAe,OAAO;CACnC,IAAI,SAAS,gBAAgB,OAAO;CACpC,OAAO;AACT;;;;;;AAOA,SAAgB,qBAAqB,QAA6D;CAChG,MAAM,OAA0B,CAAC;CACjC,MAAM,4BAAY,IAAI,IAA6B;CACnD,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,MAAM,UAAU,IAAI,MAAM,GAAG;EACjC,IAAI,QAAQ,KAAA,GAAW;GACrB,MAAM;IAAE,MAAM,MAAM;IAAK,OAAO,SAAS,MAAM,GAAG;IAAG,QAAQ,MAAM;IAAQ,OAAO,CAAC;GAAE;GACrF,UAAU,IAAI,MAAM,KAAK,GAAG;GAC5B,KAAK,KAAK,GAAG;EACf;EACA,IAAI,MAAM,SAAS,OAAO;GACxB,IAAI,SAAS,MAAM;GACnB,IAAI,MAAM,WAAW,KAAA,GAAW,IAAI,SAAS,MAAM;GACnD,IAAI,MAAM,UAAU,KAAA,GAAW,IAAI,QAAQ,MAAM;GACjD,IAAI,MAAM,UAAU,KAAA,GAAW,IAAI,QAAQ,MAAM;GACjD,IAAI,MAAM,WAAW,aAAa,IAAI,cAAc,KAAA,GAAW,IAAI,YAAY,MAAM;GACrF,IAAI,qBAAqB,IAAI,MAAM,MAAM,GAAG,IAAI,UAAU,MAAM;GAChE;EACF;EACA,IAAI,MAAM,SAAS,KAAA,GAAW;EAC9B,IAAI,OAAqC,IAAI,MAAM,MAAM,cAAc,UAAU,SAAS,MAAM,IAAI;EACpG,IAAI,SAAS,KAAA,GAAW;GACtB,OAAO;IAAE,MAAM,MAAM;IAAM,QAAQ,MAAM;GAAO;GAChD,IAAI,MAAM,KAAK,IAAI;EACrB;EACA,KAAK,SAAS,MAAM;EACpB,IAAI,MAAM,WAAW,KAAA,GAAW,KAAK,SAAS,MAAM;EACpD,IAAI,MAAM,WAAW,aAAa,KAAK,cAAc,KAAA,GAAW,KAAK,YAAY,MAAM;EACvF,IAAI,qBAAqB,IAAI,MAAM,MAAM,GAAG,KAAK,UAAU,MAAM;CACnE;CACA,OAAO;AACT;AAEA,MAAM,yCAA6D,IAAI,IAAI;CACzE;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAgB,iBAAiB,MAAgE;CAC/F,KAAK,IAAI,WAAW,KAAK,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG;EACjE,MAAM,MAAM,KAAK;EACjB,IAAI,CAAC,uBAAuB,IAAI,IAAI,MAAM,GAAG;EAC7C,KAAK,IAAI,YAAY,IAAI,MAAM,SAAS,GAAG,aAAa,GAAG,aAAa,GAAG;GACzE,MAAM,OAAO,IAAI,MAAM;GACvB,IAAI,uBAAuB,IAAI,KAAK,MAAM,GACxC,OAAO;IACL,KAAK,IAAI;IACT,MAAM,KAAK;IACX,GAAI,IAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,IAAI,MAAM;IACtD,GAAI,IAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,IAAI,MAAM;GACxD;EAEJ;EACA,OAAO;GACL,KAAK,IAAI;GACT,GAAI,IAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,IAAI,MAAM;GACtD,GAAI,IAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,IAAI,MAAM;EACxD;CACF;AAEF;;AAGA,SAAgB,wBAAwB,MAAuB,MAA0C;CACvG,MAAM,WAAW,KAAK,UAAU,YAAY,iBAAiB,IAAI,IAAI,KAAA;CACrE,OAAO;EAAE,GAAG;EAAM;EAAM,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;CAAG;AAC1E;;;;;;;;;;;;AAaA,SAAgB,oBAAoB,KAAwB,WAA4B;CACtF,OAAO,IAAI,gBAAgB,KAAA,KAAa,IAAI,gBAAgB;AAC9D;AAEA,SAAS,UAAU,OAAmC;CACpD,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,MAAM,OAAO,KAAK,MAAM,KAAK;CAC7B,OAAO,OAAO,SAAS,IAAI,IAAI,OAAO;AACxC;AAEA,MAAM,cAAuD;CAAE,SAAS;CAAG,OAAO;CAAG,WAAW;AAAE;;;;;;;AAQlG,SAAS,gBAAgB,KAAsB,KAAsB;CACnE,IAAI,IAAI,UAAU,SAAS,OAAO;CAElC,OAAO,OADW,UAAU,IAAI,UAAU,KAAK,UAAU,IAAI,SAAS,KAC7C;AAC3B;;;;;;;;AASA,SAAS,YAAY,KAA8B;CACjD,OAAO,IAAI,UAAU,YAAY,UAAU,IAAI,SAAS,IAAI,UAAU,IAAI,UAAU,KAAK,UAAU,IAAI,SAAS;AAClH;;;;;AAMA,SAAgB,oBAAoB,MAAkC,KAAgC;CACpG,MAAM,yBAAS,IAAI,IAAsC;CACzD,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,CAAC,gBAAgB,KAAK,GAAG,GAAG;EAChC,MAAM,QAAQ,OAAO,IAAI,IAAI,KAAK;EAClC,IAAI,OAAO,MAAM,KAAK,GAAG;OACpB,OAAO,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;CAClC;CACA,OAAO,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CACzB,MAAM,CAAC,OAAO,CAAC,WAAW,YAAY,QAAQ,YAAY,MAAM,CAAC,CACjE,SAAS,GAAG,WACX,MACG,MAAM,MAAM,UAAU,YAAY,KAAK,IAAI,YAAY,IAAI,CAAC,CAAC,CAC7D,MAAM,GAAA,EAAwC,CACnD;AACJ"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/services/workflowTerminal/index.ts
|
|
2
|
+
const DEFAULT_REFRESH_MS = 750;
|
|
3
|
+
const DEFAULT_LEASE_MS = 6e4;
|
|
4
|
+
const NOT_HELD = "Another reader holds the keyboard for this run.";
|
|
5
|
+
function createWorkflowTerminalService(deps) {
|
|
6
|
+
const refreshMs = deps.refreshMs ?? DEFAULT_REFRESH_MS;
|
|
7
|
+
const leaseMs = deps.leaseMs ?? DEFAULT_LEASE_MS;
|
|
8
|
+
const screens = /* @__PURE__ */ new Map();
|
|
9
|
+
const leases = /* @__PURE__ */ new Map();
|
|
10
|
+
const heldBy = (identity) => {
|
|
11
|
+
const lease = leases.get(identity);
|
|
12
|
+
if (lease === void 0) return void 0;
|
|
13
|
+
if (lease.expiresAt > deps.now()) return lease;
|
|
14
|
+
leases.delete(identity);
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
async screen(identity, record, lines) {
|
|
18
|
+
const entry = screens.get(identity) ?? { lines: [] };
|
|
19
|
+
screens.set(identity, entry);
|
|
20
|
+
if (entry.inFlight) return entry.lines;
|
|
21
|
+
if (entry.at !== void 0 && deps.now() - entry.at < refreshMs) return entry.lines;
|
|
22
|
+
entry.inFlight = deps.terminal.screen(record, { lines });
|
|
23
|
+
try {
|
|
24
|
+
entry.lines = await entry.inFlight;
|
|
25
|
+
} finally {
|
|
26
|
+
entry.at = deps.now();
|
|
27
|
+
entry.inFlight = void 0;
|
|
28
|
+
}
|
|
29
|
+
return entry.lines;
|
|
30
|
+
},
|
|
31
|
+
capabilities(record) {
|
|
32
|
+
return deps.terminal.capabilities(record);
|
|
33
|
+
},
|
|
34
|
+
takeControl(identity, token) {
|
|
35
|
+
const holder = heldBy(identity);
|
|
36
|
+
if (holder !== void 0 && holder.token !== token) return false;
|
|
37
|
+
leases.set(identity, {
|
|
38
|
+
token,
|
|
39
|
+
expiresAt: deps.now() + leaseMs
|
|
40
|
+
});
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
releaseControl(identity, token) {
|
|
44
|
+
if (heldBy(identity)?.token === token) leases.delete(identity);
|
|
45
|
+
},
|
|
46
|
+
async write(identity, record, token, data) {
|
|
47
|
+
const holder = heldBy(identity);
|
|
48
|
+
if (holder === void 0 || holder.token !== token) throw new Error(NOT_HELD);
|
|
49
|
+
leases.set(identity, {
|
|
50
|
+
token,
|
|
51
|
+
expiresAt: deps.now() + leaseMs
|
|
52
|
+
});
|
|
53
|
+
await deps.terminal.write(record, data);
|
|
54
|
+
},
|
|
55
|
+
async resize(identity, record, token, columns, rows) {
|
|
56
|
+
const holder = heldBy(identity);
|
|
57
|
+
if (holder === void 0 || holder.token !== token) throw new Error(NOT_HELD);
|
|
58
|
+
return deps.terminal.resize(record, columns, rows);
|
|
59
|
+
},
|
|
60
|
+
forget(live) {
|
|
61
|
+
for (const identity of screens.keys()) if (!live.has(identity)) screens.delete(identity);
|
|
62
|
+
for (const identity of leases.keys()) if (!live.has(identity)) leases.delete(identity);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
exports.createWorkflowTerminalService = createWorkflowTerminalService;
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/workflowTerminal/index.ts"],"sourcesContent":["import type { WorkflowTerminalCapabilitiesView } from '../../types/webWorkflowTerminal';\n\n/**\n * One run's terminal, shared by every surface watching it.\n *\n * WHY COALESCING LIVES HERE:\n * Reading a multiplexer's screen forks a CLI, measured at roughly 75ms a call.\n * Several readers on one run (a step tab, a second browser, the session's own\n * panel) would otherwise each fork on their own timer, and a read that runs\n * long would have the next one start on top of it, unbounded. One read per run\n * at a time, no faster than the interval, however many surfaces are watching.\n *\n * WHY A CONTROL LEASE:\n * A run's terminal takes keystrokes from anyone who can reach it, so two\n * readers typing into one nested agent is a real state, not a hypothetical.\n * The lease does not make it impossible, since the session's own panel can\n * still type; it makes it visible, and keeps two cockpit tabs from fighting.\n */\n\n/** The engine's terminal facade, as this service needs it. */\nexport interface TerminalPort<Record> {\n capabilities(record: Record): WorkflowTerminalCapabilitiesView;\n screen(record: Record, options: { lines?: number }): Promise<string[]>;\n write(record: Record, data: string): Promise<void>;\n resize(record: Record, columns: number, rows: number): Promise<boolean>;\n}\n\nexport interface WorkflowTerminalDeps<Record> {\n terminal: TerminalPort<Record>;\n /** Injected so the cadence and the lease clock are assertable. */\n now: () => number;\n /** Shortest gap between two reads of one run. */\n refreshMs?: number;\n /** How long a keyboard lease survives without being renewed. */\n leaseMs?: number;\n}\n\n/** A read in flight, and what the last one returned. */\ninterface ScreenEntry {\n /**\n * When the last read finished, absent until one has.\n *\n * Absent rather than zero: zero is a real instant, and a service whose clock\n * starts near it would answer the very first read from an empty cache and\n * paint nothing until the interval had passed.\n */\n at?: number;\n lines: string[];\n inFlight?: Promise<string[]>;\n}\n\ninterface Lease {\n token: string;\n expiresAt: number;\n}\n\nconst DEFAULT_REFRESH_MS = 750;\nconst DEFAULT_LEASE_MS = 60_000;\nconst NOT_HELD = 'Another reader holds the keyboard for this run.';\n\nexport interface WorkflowTerminalService<Record> {\n /** The run's recent screen, reusing a recent read rather than forking again. */\n screen(identity: string, record: Record, lines: number): Promise<string[]>;\n capabilities(record: Record): WorkflowTerminalCapabilitiesView;\n /** Takes the keyboard, or reports who has it. Renews when the holder asks again. */\n takeControl(identity: string, token: string): boolean;\n releaseControl(identity: string, token: string): void;\n /** Writes on behalf of a lease holder; throws when the lease is not theirs. */\n write(identity: string, record: Record, token: string, data: string): Promise<void>;\n /**\n * Matches the run's terminal to the holder's viewport.\n *\n * Behind the same lease as writing: geometry is shared by everyone watching,\n * so a passive reader must not reflow the screen under the person typing.\n */\n resize(identity: string, record: Record, token: string, columns: number, rows: number): Promise<boolean>;\n /** Drops what is remembered for runs that can never change again. */\n forget(live: ReadonlySet<string>): void;\n}\n\nexport function createWorkflowTerminalService<Record>(\n deps: WorkflowTerminalDeps<Record>,\n): WorkflowTerminalService<Record> {\n const refreshMs = deps.refreshMs ?? DEFAULT_REFRESH_MS;\n const leaseMs = deps.leaseMs ?? DEFAULT_LEASE_MS;\n const screens = new Map<string, ScreenEntry>();\n const leases = new Map<string, Lease>();\n\n const heldBy = (identity: string): Lease | undefined => {\n const lease = leases.get(identity);\n if (lease === undefined) return undefined;\n if (lease.expiresAt > deps.now()) return lease;\n leases.delete(identity);\n return undefined;\n };\n\n return {\n async screen(identity, record, lines) {\n const entry = screens.get(identity) ?? { lines: [] };\n screens.set(identity, entry);\n if (entry.inFlight) return entry.lines;\n if (entry.at !== undefined && deps.now() - entry.at < refreshMs) return entry.lines;\n entry.inFlight = deps.terminal.screen(record, { lines });\n try {\n entry.lines = await entry.inFlight;\n } finally {\n // Stamped after the read, not before: the interval is a gap between\n // reads, so a slow scrape must not immediately earn another one.\n entry.at = deps.now();\n entry.inFlight = undefined;\n }\n return entry.lines;\n },\n capabilities(record) {\n return deps.terminal.capabilities(record);\n },\n takeControl(identity, token) {\n const holder = heldBy(identity);\n if (holder !== undefined && holder.token !== token) return false;\n leases.set(identity, { token, expiresAt: deps.now() + leaseMs });\n return true;\n },\n releaseControl(identity, token) {\n if (heldBy(identity)?.token === token) leases.delete(identity);\n },\n async write(identity, record, token, data) {\n const holder = heldBy(identity);\n if (holder === undefined || holder.token !== token) throw new Error(NOT_HELD);\n leases.set(identity, { token, expiresAt: deps.now() + leaseMs });\n await deps.terminal.write(record, data);\n },\n async resize(identity, record, token, columns, rows) {\n const holder = heldBy(identity);\n if (holder === undefined || holder.token !== token) throw new Error(NOT_HELD);\n return deps.terminal.resize(record, columns, rows);\n },\n forget(live) {\n for (const identity of screens.keys()) {\n if (!live.has(identity)) screens.delete(identity);\n }\n for (const identity of leases.keys()) {\n if (!live.has(identity)) leases.delete(identity);\n }\n },\n };\n}\n"],"mappings":";AAwDA,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,WAAW;AAsBjB,SAAgB,8BACd,MACiC;CACjC,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,0BAAU,IAAI,IAAyB;CAC7C,MAAM,yBAAS,IAAI,IAAmB;CAEtC,MAAM,UAAU,aAAwC;EACtD,MAAM,QAAQ,OAAO,IAAI,QAAQ;EACjC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;EAChC,IAAI,MAAM,YAAY,KAAK,IAAI,GAAG,OAAO;EACzC,OAAO,OAAO,QAAQ;CAExB;CAEA,OAAO;EACL,MAAM,OAAO,UAAU,QAAQ,OAAO;GACpC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,KAAK,EAAE,OAAO,CAAC,EAAE;GACnD,QAAQ,IAAI,UAAU,KAAK;GAC3B,IAAI,MAAM,UAAU,OAAO,MAAM;GACjC,IAAI,MAAM,OAAO,KAAA,KAAa,KAAK,IAAI,IAAI,MAAM,KAAK,WAAW,OAAO,MAAM;GAC9E,MAAM,WAAW,KAAK,SAAS,OAAO,QAAQ,EAAE,MAAM,CAAC;GACvD,IAAI;IACF,MAAM,QAAQ,MAAM,MAAM;GAC5B,UAAU;IAGR,MAAM,KAAK,KAAK,IAAI;IACpB,MAAM,WAAW,KAAA;GACnB;GACA,OAAO,MAAM;EACf;EACA,aAAa,QAAQ;GACnB,OAAO,KAAK,SAAS,aAAa,MAAM;EAC1C;EACA,YAAY,UAAU,OAAO;GAC3B,MAAM,SAAS,OAAO,QAAQ;GAC9B,IAAI,WAAW,KAAA,KAAa,OAAO,UAAU,OAAO,OAAO;GAC3D,OAAO,IAAI,UAAU;IAAE;IAAO,WAAW,KAAK,IAAI,IAAI;GAAQ,CAAC;GAC/D,OAAO;EACT;EACA,eAAe,UAAU,OAAO;GAC9B,IAAI,OAAO,QAAQ,CAAC,EAAE,UAAU,OAAO,OAAO,OAAO,QAAQ;EAC/D;EACA,MAAM,MAAM,UAAU,QAAQ,OAAO,MAAM;GACzC,MAAM,SAAS,OAAO,QAAQ;GAC9B,IAAI,WAAW,KAAA,KAAa,OAAO,UAAU,OAAO,MAAM,IAAI,MAAM,QAAQ;GAC5E,OAAO,IAAI,UAAU;IAAE;IAAO,WAAW,KAAK,IAAI,IAAI;GAAQ,CAAC;GAC/D,MAAM,KAAK,SAAS,MAAM,QAAQ,IAAI;EACxC;EACA,MAAM,OAAO,UAAU,QAAQ,OAAO,SAAS,MAAM;GACnD,MAAM,SAAS,OAAO,QAAQ;GAC9B,IAAI,WAAW,KAAA,KAAa,OAAO,UAAU,OAAO,MAAM,IAAI,MAAM,QAAQ;GAC5E,OAAO,KAAK,SAAS,OAAO,QAAQ,SAAS,IAAI;EACnD;EACA,OAAO,MAAM;GACX,KAAK,MAAM,YAAY,QAAQ,KAAK,GAClC,IAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ;GAElD,KAAK,MAAM,YAAY,OAAO,KAAK,GACjC,IAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,OAAO,OAAO,QAAQ;EAEnD;CACF;AACF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/services/workflowTerminal/index.ts
|
|
2
|
+
const DEFAULT_REFRESH_MS = 750;
|
|
3
|
+
const DEFAULT_LEASE_MS = 6e4;
|
|
4
|
+
const NOT_HELD = "Another reader holds the keyboard for this run.";
|
|
5
|
+
function createWorkflowTerminalService(deps) {
|
|
6
|
+
const refreshMs = deps.refreshMs ?? DEFAULT_REFRESH_MS;
|
|
7
|
+
const leaseMs = deps.leaseMs ?? DEFAULT_LEASE_MS;
|
|
8
|
+
const screens = /* @__PURE__ */ new Map();
|
|
9
|
+
const leases = /* @__PURE__ */ new Map();
|
|
10
|
+
const heldBy = (identity) => {
|
|
11
|
+
const lease = leases.get(identity);
|
|
12
|
+
if (lease === void 0) return void 0;
|
|
13
|
+
if (lease.expiresAt > deps.now()) return lease;
|
|
14
|
+
leases.delete(identity);
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
async screen(identity, record, lines) {
|
|
18
|
+
const entry = screens.get(identity) ?? { lines: [] };
|
|
19
|
+
screens.set(identity, entry);
|
|
20
|
+
if (entry.inFlight) return entry.lines;
|
|
21
|
+
if (entry.at !== void 0 && deps.now() - entry.at < refreshMs) return entry.lines;
|
|
22
|
+
entry.inFlight = deps.terminal.screen(record, { lines });
|
|
23
|
+
try {
|
|
24
|
+
entry.lines = await entry.inFlight;
|
|
25
|
+
} finally {
|
|
26
|
+
entry.at = deps.now();
|
|
27
|
+
entry.inFlight = void 0;
|
|
28
|
+
}
|
|
29
|
+
return entry.lines;
|
|
30
|
+
},
|
|
31
|
+
capabilities(record) {
|
|
32
|
+
return deps.terminal.capabilities(record);
|
|
33
|
+
},
|
|
34
|
+
takeControl(identity, token) {
|
|
35
|
+
const holder = heldBy(identity);
|
|
36
|
+
if (holder !== void 0 && holder.token !== token) return false;
|
|
37
|
+
leases.set(identity, {
|
|
38
|
+
token,
|
|
39
|
+
expiresAt: deps.now() + leaseMs
|
|
40
|
+
});
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
releaseControl(identity, token) {
|
|
44
|
+
if (heldBy(identity)?.token === token) leases.delete(identity);
|
|
45
|
+
},
|
|
46
|
+
async write(identity, record, token, data) {
|
|
47
|
+
const holder = heldBy(identity);
|
|
48
|
+
if (holder === void 0 || holder.token !== token) throw new Error(NOT_HELD);
|
|
49
|
+
leases.set(identity, {
|
|
50
|
+
token,
|
|
51
|
+
expiresAt: deps.now() + leaseMs
|
|
52
|
+
});
|
|
53
|
+
await deps.terminal.write(record, data);
|
|
54
|
+
},
|
|
55
|
+
async resize(identity, record, token, columns, rows) {
|
|
56
|
+
const holder = heldBy(identity);
|
|
57
|
+
if (holder === void 0 || holder.token !== token) throw new Error(NOT_HELD);
|
|
58
|
+
return deps.terminal.resize(record, columns, rows);
|
|
59
|
+
},
|
|
60
|
+
forget(live) {
|
|
61
|
+
for (const identity of screens.keys()) if (!live.has(identity)) screens.delete(identity);
|
|
62
|
+
for (const identity of leases.keys()) if (!live.has(identity)) leases.delete(identity);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { createWorkflowTerminalService };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowTerminal/index.ts"],"sourcesContent":["import type { WorkflowTerminalCapabilitiesView } from '../../types/webWorkflowTerminal';\n\n/**\n * One run's terminal, shared by every surface watching it.\n *\n * WHY COALESCING LIVES HERE:\n * Reading a multiplexer's screen forks a CLI, measured at roughly 75ms a call.\n * Several readers on one run (a step tab, a second browser, the session's own\n * panel) would otherwise each fork on their own timer, and a read that runs\n * long would have the next one start on top of it, unbounded. One read per run\n * at a time, no faster than the interval, however many surfaces are watching.\n *\n * WHY A CONTROL LEASE:\n * A run's terminal takes keystrokes from anyone who can reach it, so two\n * readers typing into one nested agent is a real state, not a hypothetical.\n * The lease does not make it impossible, since the session's own panel can\n * still type; it makes it visible, and keeps two cockpit tabs from fighting.\n */\n\n/** The engine's terminal facade, as this service needs it. */\nexport interface TerminalPort<Record> {\n capabilities(record: Record): WorkflowTerminalCapabilitiesView;\n screen(record: Record, options: { lines?: number }): Promise<string[]>;\n write(record: Record, data: string): Promise<void>;\n resize(record: Record, columns: number, rows: number): Promise<boolean>;\n}\n\nexport interface WorkflowTerminalDeps<Record> {\n terminal: TerminalPort<Record>;\n /** Injected so the cadence and the lease clock are assertable. */\n now: () => number;\n /** Shortest gap between two reads of one run. */\n refreshMs?: number;\n /** How long a keyboard lease survives without being renewed. */\n leaseMs?: number;\n}\n\n/** A read in flight, and what the last one returned. */\ninterface ScreenEntry {\n /**\n * When the last read finished, absent until one has.\n *\n * Absent rather than zero: zero is a real instant, and a service whose clock\n * starts near it would answer the very first read from an empty cache and\n * paint nothing until the interval had passed.\n */\n at?: number;\n lines: string[];\n inFlight?: Promise<string[]>;\n}\n\ninterface Lease {\n token: string;\n expiresAt: number;\n}\n\nconst DEFAULT_REFRESH_MS = 750;\nconst DEFAULT_LEASE_MS = 60_000;\nconst NOT_HELD = 'Another reader holds the keyboard for this run.';\n\nexport interface WorkflowTerminalService<Record> {\n /** The run's recent screen, reusing a recent read rather than forking again. */\n screen(identity: string, record: Record, lines: number): Promise<string[]>;\n capabilities(record: Record): WorkflowTerminalCapabilitiesView;\n /** Takes the keyboard, or reports who has it. Renews when the holder asks again. */\n takeControl(identity: string, token: string): boolean;\n releaseControl(identity: string, token: string): void;\n /** Writes on behalf of a lease holder; throws when the lease is not theirs. */\n write(identity: string, record: Record, token: string, data: string): Promise<void>;\n /**\n * Matches the run's terminal to the holder's viewport.\n *\n * Behind the same lease as writing: geometry is shared by everyone watching,\n * so a passive reader must not reflow the screen under the person typing.\n */\n resize(identity: string, record: Record, token: string, columns: number, rows: number): Promise<boolean>;\n /** Drops what is remembered for runs that can never change again. */\n forget(live: ReadonlySet<string>): void;\n}\n\nexport function createWorkflowTerminalService<Record>(\n deps: WorkflowTerminalDeps<Record>,\n): WorkflowTerminalService<Record> {\n const refreshMs = deps.refreshMs ?? DEFAULT_REFRESH_MS;\n const leaseMs = deps.leaseMs ?? DEFAULT_LEASE_MS;\n const screens = new Map<string, ScreenEntry>();\n const leases = new Map<string, Lease>();\n\n const heldBy = (identity: string): Lease | undefined => {\n const lease = leases.get(identity);\n if (lease === undefined) return undefined;\n if (lease.expiresAt > deps.now()) return lease;\n leases.delete(identity);\n return undefined;\n };\n\n return {\n async screen(identity, record, lines) {\n const entry = screens.get(identity) ?? { lines: [] };\n screens.set(identity, entry);\n if (entry.inFlight) return entry.lines;\n if (entry.at !== undefined && deps.now() - entry.at < refreshMs) return entry.lines;\n entry.inFlight = deps.terminal.screen(record, { lines });\n try {\n entry.lines = await entry.inFlight;\n } finally {\n // Stamped after the read, not before: the interval is a gap between\n // reads, so a slow scrape must not immediately earn another one.\n entry.at = deps.now();\n entry.inFlight = undefined;\n }\n return entry.lines;\n },\n capabilities(record) {\n return deps.terminal.capabilities(record);\n },\n takeControl(identity, token) {\n const holder = heldBy(identity);\n if (holder !== undefined && holder.token !== token) return false;\n leases.set(identity, { token, expiresAt: deps.now() + leaseMs });\n return true;\n },\n releaseControl(identity, token) {\n if (heldBy(identity)?.token === token) leases.delete(identity);\n },\n async write(identity, record, token, data) {\n const holder = heldBy(identity);\n if (holder === undefined || holder.token !== token) throw new Error(NOT_HELD);\n leases.set(identity, { token, expiresAt: deps.now() + leaseMs });\n await deps.terminal.write(record, data);\n },\n async resize(identity, record, token, columns, rows) {\n const holder = heldBy(identity);\n if (holder === undefined || holder.token !== token) throw new Error(NOT_HELD);\n return deps.terminal.resize(record, columns, rows);\n },\n forget(live) {\n for (const identity of screens.keys()) {\n if (!live.has(identity)) screens.delete(identity);\n }\n for (const identity of leases.keys()) {\n if (!live.has(identity)) leases.delete(identity);\n }\n },\n };\n}\n"],"mappings":";AAwDA,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,WAAW;AAsBjB,SAAgB,8BACd,MACiC;CACjC,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,0BAAU,IAAI,IAAyB;CAC7C,MAAM,yBAAS,IAAI,IAAmB;CAEtC,MAAM,UAAU,aAAwC;EACtD,MAAM,QAAQ,OAAO,IAAI,QAAQ;EACjC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;EAChC,IAAI,MAAM,YAAY,KAAK,IAAI,GAAG,OAAO;EACzC,OAAO,OAAO,QAAQ;CAExB;CAEA,OAAO;EACL,MAAM,OAAO,UAAU,QAAQ,OAAO;GACpC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,KAAK,EAAE,OAAO,CAAC,EAAE;GACnD,QAAQ,IAAI,UAAU,KAAK;GAC3B,IAAI,MAAM,UAAU,OAAO,MAAM;GACjC,IAAI,MAAM,OAAO,KAAA,KAAa,KAAK,IAAI,IAAI,MAAM,KAAK,WAAW,OAAO,MAAM;GAC9E,MAAM,WAAW,KAAK,SAAS,OAAO,QAAQ,EAAE,MAAM,CAAC;GACvD,IAAI;IACF,MAAM,QAAQ,MAAM,MAAM;GAC5B,UAAU;IAGR,MAAM,KAAK,KAAK,IAAI;IACpB,MAAM,WAAW,KAAA;GACnB;GACA,OAAO,MAAM;EACf;EACA,aAAa,QAAQ;GACnB,OAAO,KAAK,SAAS,aAAa,MAAM;EAC1C;EACA,YAAY,UAAU,OAAO;GAC3B,MAAM,SAAS,OAAO,QAAQ;GAC9B,IAAI,WAAW,KAAA,KAAa,OAAO,UAAU,OAAO,OAAO;GAC3D,OAAO,IAAI,UAAU;IAAE;IAAO,WAAW,KAAK,IAAI,IAAI;GAAQ,CAAC;GAC/D,OAAO;EACT;EACA,eAAe,UAAU,OAAO;GAC9B,IAAI,OAAO,QAAQ,CAAC,EAAE,UAAU,OAAO,OAAO,OAAO,QAAQ;EAC/D;EACA,MAAM,MAAM,UAAU,QAAQ,OAAO,MAAM;GACzC,MAAM,SAAS,OAAO,QAAQ;GAC9B,IAAI,WAAW,KAAA,KAAa,OAAO,UAAU,OAAO,MAAM,IAAI,MAAM,QAAQ;GAC5E,OAAO,IAAI,UAAU;IAAE;IAAO,WAAW,KAAK,IAAI,IAAI;GAAQ,CAAC;GAC/D,MAAM,KAAK,SAAS,MAAM,QAAQ,IAAI;EACxC;EACA,MAAM,OAAO,UAAU,QAAQ,OAAO,SAAS,MAAM;GACnD,MAAM,SAAS,OAAO,QAAQ;GAC9B,IAAI,WAAW,KAAA,KAAa,OAAO,UAAU,OAAO,MAAM,IAAI,MAAM,QAAQ;GAC5E,OAAO,KAAK,SAAS,OAAO,QAAQ,SAAS,IAAI;EACnD;EACA,OAAO,MAAM;GACX,KAAK,MAAM,YAAY,QAAQ,KAAK,GAClC,IAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ;GAElD,KAAK,MAAM,YAAY,OAAO,KAAK,GACjC,IAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,OAAO,OAAO,QAAQ;EAEnD;CACF;AACF"}
|