@agimon-ai/doompi-workflow 0.0.1-alpha.70 → 0.0.1-alpha.71
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/README.md +13 -0
- package/dist/api-contracts.cjs +1 -0
- package/dist/api-contracts.d.cts +2 -0
- package/dist/api-contracts.d.mts +2 -0
- package/dist/api-contracts.mjs +1 -0
- package/dist/extensions/pi.cjs +2 -1
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +6 -2
- package/dist/extensions/pi.d.cts.map +1 -0
- package/dist/extensions/pi.d.mts +6 -2
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +2 -1
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/server.cjs +2 -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 +2 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +1 -1
- package/dist/src/constants/workflow.cjs +2 -0
- package/dist/src/constants/workflow.cjs.map +1 -0
- package/dist/src/constants/workflow.mjs +2 -0
- package/dist/src/constants/workflow.mjs.map +1 -0
- package/dist/src/controllers/serverRuntime.cjs +2 -0
- package/dist/src/controllers/serverRuntime.cjs.map +1 -0
- package/dist/src/controllers/serverRuntime.mjs +2 -0
- package/dist/src/controllers/serverRuntime.mjs.map +1 -0
- package/dist/src/controllers/workflowCatalogChannel.cjs +2 -0
- package/dist/src/controllers/workflowCatalogChannel.cjs.map +1 -0
- package/dist/src/controllers/workflowCatalogChannel.mjs +2 -0
- package/dist/src/controllers/workflowCatalogChannel.mjs.map +1 -0
- package/dist/src/controllers/workflowHubApi.cjs +2 -0
- package/dist/src/controllers/workflowHubApi.cjs.map +1 -0
- package/dist/src/controllers/workflowHubApi.mjs +2 -0
- package/dist/src/controllers/workflowHubApi.mjs.map +1 -0
- package/dist/src/controllers/workflowsHubChannel.cjs +2 -0
- package/dist/src/controllers/workflowsHubChannel.cjs.map +1 -0
- package/dist/src/controllers/workflowsHubChannel.mjs +2 -0
- package/dist/src/controllers/workflowsHubChannel.mjs.map +1 -0
- package/dist/src/schemas/apiContracts.cjs +2 -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 +2 -0
- package/dist/src/schemas/apiContracts.mjs.map +1 -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/{schemas/workflowPi.cjs.map → src/schemas/workflowPi.mjs.map} +1 -1
- package/dist/src/services/backgroundWork/index.cjs +2 -0
- package/dist/src/services/backgroundWork/index.cjs.map +1 -0
- package/dist/src/services/backgroundWork/index.mjs +2 -0
- package/dist/src/services/backgroundWork/index.mjs.map +1 -0
- package/dist/{adapters/pi/workflow/piToolBridge.cjs → src/services/piToolBridge/index.cjs} +1 -1
- package/dist/src/services/piToolBridge/index.cjs.map +1 -0
- package/dist/{adapters/pi/workflow/piToolBridge.mjs → src/services/piToolBridge/index.mjs} +1 -1
- package/dist/src/services/piToolBridge/index.mjs.map +1 -0
- package/dist/{services/webWorkflowCatalog.cjs → src/services/webWorkflowCatalog/index.cjs} +1 -1
- package/dist/src/services/webWorkflowCatalog/index.cjs.map +1 -0
- package/dist/{services/webWorkflowCatalog.mjs → src/services/webWorkflowCatalog/index.mjs} +1 -1
- package/dist/src/services/webWorkflowCatalog/index.mjs.map +1 -0
- package/dist/src/services/workflowCatalogDeps/index.cjs +2 -0
- package/dist/src/services/workflowCatalogDeps/index.cjs.map +1 -0
- package/dist/src/services/workflowCatalogDeps/index.mjs +2 -0
- package/dist/src/services/workflowCatalogDeps/index.mjs.map +1 -0
- package/dist/src/services/workflowExecution/index.cjs +2 -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 +2 -0
- package/dist/src/services/workflowExecution/index.mjs.map +1 -0
- package/dist/src/services/workflowFence/index.cjs +2 -0
- package/dist/src/services/workflowFence/index.cjs.map +1 -0
- package/dist/src/services/workflowFence/index.mjs +2 -0
- package/dist/src/services/workflowFence/index.mjs.map +1 -0
- package/dist/{services/workflowLaunchCommand.cjs → src/services/workflowLaunchCommand/index.cjs} +1 -1
- 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/{services/workflowLaunchCommand.mjs → src/services/workflowLaunchCommand/index.mjs} +1 -1
- package/dist/src/services/workflowLaunchCommand/index.mjs.map +1 -0
- package/dist/src/services/workflowLauncher/index.cjs +2 -0
- package/dist/{adapters/pi/workflow/workflowLauncher.mjs.map → src/services/workflowLauncher/index.cjs.map} +1 -1
- package/dist/src/services/workflowLauncher/index.mjs +2 -0
- package/dist/src/services/workflowLauncher/index.mjs.map +1 -0
- package/dist/{services/workflowNarration.cjs → src/services/workflowNarration/index.cjs} +1 -1
- package/dist/{services/workflowNarration.cjs.map → src/services/workflowNarration/index.cjs.map} +1 -1
- package/dist/{services/workflowNarration.mjs → src/services/workflowNarration/index.mjs} +1 -1
- package/dist/{services/workflowNarration.mjs.map → src/services/workflowNarration/index.mjs.map} +1 -1
- package/dist/src/services/workflowResource/index.cjs +2 -0
- package/dist/src/services/workflowResource/index.cjs.map +1 -0
- package/dist/src/services/workflowResource/index.mjs +2 -0
- package/dist/src/services/workflowResource/index.mjs.map +1 -0
- package/dist/{services/workflowRuns.cjs → src/services/workflowRuns/index.cjs} +1 -1
- package/dist/src/services/workflowRuns/index.cjs.map +1 -0
- package/dist/{services/workflowRuns.mjs → src/services/workflowRuns/index.mjs} +1 -1
- package/dist/src/services/workflowRuns/index.mjs.map +1 -0
- package/dist/{services/workflowTerminal.cjs → src/services/workflowTerminal/index.cjs} +1 -1
- package/dist/src/services/workflowTerminal/index.cjs.map +1 -0
- package/dist/{services/workflowTerminal.mjs → src/services/workflowTerminal/index.mjs} +1 -1
- package/dist/src/services/workflowTerminal/index.mjs.map +1 -0
- package/dist/src/services/workflowWatcher/index.cjs +2 -0
- package/dist/src/services/workflowWatcher/index.cjs.map +1 -0
- package/dist/src/services/workflowWatcher/index.mjs +2 -0
- package/dist/src/services/workflowWatcher/index.mjs.map +1 -0
- package/dist/src/tools/workflowTools.cjs +5 -0
- package/dist/src/tools/workflowTools.cjs.map +1 -0
- package/dist/src/tools/workflowTools.mjs +5 -0
- package/dist/src/tools/workflowTools.mjs.map +1 -0
- package/dist/src/tui/leader.cjs +2 -0
- package/dist/src/tui/leader.cjs.map +1 -0
- package/dist/src/tui/leader.mjs +2 -0
- package/dist/src/tui/leader.mjs.map +1 -0
- package/dist/src/tui/workflow/doomOverlay.cjs.map +1 -0
- package/dist/src/tui/workflow/doomOverlay.mjs.map +1 -0
- package/dist/src/tui/workflow/overlayText.cjs.map +1 -0
- package/dist/src/tui/workflow/overlayText.mjs.map +1 -0
- package/dist/src/tui/workflow/rendering.cjs.map +1 -0
- package/dist/src/tui/workflow/rendering.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowCatalog.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowCatalog.mjs.map +1 -0
- package/dist/{tui → src/tui}/workflow/workflowFinishedMessage.cjs +1 -1
- package/dist/src/tui/workflow/workflowFinishedMessage.cjs.map +1 -0
- package/dist/{tui → src/tui}/workflow/workflowFinishedMessage.mjs +1 -1
- package/dist/src/tui/workflow/workflowFinishedMessage.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowInspector.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowInspector.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowOverlay.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowOverlay.mjs.map +1 -0
- package/dist/{tui/workflow/workflowPicker.mjs.map → src/tui/workflow/workflowPicker.cjs.map} +1 -1
- package/dist/src/tui/workflow/workflowPicker.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowProgressOverlay.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowProgressOverlay.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowPrompt.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowPrompt.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowRunPanel.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowRunPanel.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowStatusRow.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowStatusRow.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowStepMessage.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowStepMessage.mjs.map +1 -0
- package/dist/src/tui/workflow/workflowToolRender.cjs.map +1 -0
- package/dist/src/tui/workflow/workflowToolRender.mjs.map +1 -0
- package/dist/src/tui/workflowRuntime.cjs +9 -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 +9 -0
- package/dist/src/tui/workflowRuntime.mjs.map +1 -0
- package/dist/src/types/webWorkflowTerminal.cjs +2 -0
- package/dist/src/types/webWorkflowTerminal.cjs.map +1 -0
- package/dist/src/types/webWorkflowTerminal.mjs +2 -0
- package/dist/src/types/webWorkflowTerminal.mjs.map +1 -0
- package/dist/src/types/webWorkflows.cjs.map +1 -0
- package/dist/src/types/webWorkflows.mjs.map +1 -0
- package/package.json +28 -22
- package/src/extensions/web.ts +80 -0
- package/src/types/webWorkflowTerminal.ts +2 -2
- package/src/types/webWorkflows.ts +5 -0
- package/src/web/api/terminalApi.ts +3 -4
- package/src/web/components/ArtifactsPane.tsx +5 -4
- package/src/web/components/LaunchWorkflowDialog.tsx +5 -4
- package/src/web/components/StepTerminalPanel.tsx +6 -5
- package/src/web/components/WorkflowCatalogDrawer.tsx +3 -2
- package/src/web/components/WorkflowToolMessage.tsx +3 -2
- package/src/web/components/WorkflowsActivitySection.tsx +6 -5
- package/src/web/components/WorkflowsPanel.tsx +13 -12
- package/src/web/lib/launchLine.ts +1 -1
- package/src/web/lib/workflowActivity.ts +2 -2
- package/src/web/lib/workflowToolRender.ts +1 -1
- package/src/web/stores/catalogStore.ts +3 -2
- package/src/web/stores/workflowsStore.ts +5 -4
- package/src/web/tsconfig.json +1 -1
- package/dist/adapters/pi/extension.cjs +0 -2
- package/dist/adapters/pi/extension.cjs.map +0 -1
- package/dist/adapters/pi/extension.d.cts +0 -8
- package/dist/adapters/pi/extension.d.cts.map +0 -1
- package/dist/adapters/pi/extension.d.mts +0 -8
- package/dist/adapters/pi/extension.d.mts.map +0 -1
- package/dist/adapters/pi/extension.mjs +0 -2
- package/dist/adapters/pi/extension.mjs.map +0 -1
- package/dist/adapters/pi/leader.cjs +0 -2
- package/dist/adapters/pi/leader.cjs.map +0 -1
- package/dist/adapters/pi/leader.mjs +0 -2
- package/dist/adapters/pi/leader.mjs.map +0 -1
- package/dist/adapters/pi/workflow/piExtension.cjs +0 -9
- package/dist/adapters/pi/workflow/piExtension.cjs.map +0 -1
- package/dist/adapters/pi/workflow/piExtension.mjs +0 -9
- package/dist/adapters/pi/workflow/piExtension.mjs.map +0 -1
- package/dist/adapters/pi/workflow/piToolBridge.cjs.map +0 -1
- package/dist/adapters/pi/workflow/piToolBridge.mjs.map +0 -1
- package/dist/adapters/pi/workflow/piTools.cjs +0 -5
- package/dist/adapters/pi/workflow/piTools.cjs.map +0 -1
- package/dist/adapters/pi/workflow/piTools.mjs +0 -5
- package/dist/adapters/pi/workflow/piTools.mjs.map +0 -1
- package/dist/adapters/pi/workflow/workflowLauncher.cjs +0 -2
- package/dist/adapters/pi/workflow/workflowLauncher.cjs.map +0 -1
- package/dist/adapters/pi/workflow/workflowLauncher.mjs +0 -2
- package/dist/adapters/workflowCatalogChannel.cjs +0 -2
- package/dist/adapters/workflowCatalogChannel.cjs.map +0 -1
- package/dist/adapters/workflowCatalogChannel.d.cts +0 -22
- package/dist/adapters/workflowCatalogChannel.d.cts.map +0 -1
- package/dist/adapters/workflowCatalogChannel.d.mts +0 -22
- package/dist/adapters/workflowCatalogChannel.d.mts.map +0 -1
- package/dist/adapters/workflowCatalogChannel.mjs +0 -2
- package/dist/adapters/workflowCatalogChannel.mjs.map +0 -1
- package/dist/adapters/workflowHubApi.cjs +0 -2
- package/dist/adapters/workflowHubApi.cjs.map +0 -1
- package/dist/adapters/workflowHubApi.d.cts +0 -23
- package/dist/adapters/workflowHubApi.d.cts.map +0 -1
- package/dist/adapters/workflowHubApi.d.mts +0 -23
- package/dist/adapters/workflowHubApi.d.mts.map +0 -1
- package/dist/adapters/workflowHubApi.mjs +0 -2
- package/dist/adapters/workflowHubApi.mjs.map +0 -1
- package/dist/adapters/workflowWatcher.cjs +0 -2
- package/dist/adapters/workflowWatcher.cjs.map +0 -1
- package/dist/adapters/workflowWatcher.d.cts +0 -23
- package/dist/adapters/workflowWatcher.d.cts.map +0 -1
- package/dist/adapters/workflowWatcher.d.mts +0 -23
- package/dist/adapters/workflowWatcher.d.mts.map +0 -1
- package/dist/adapters/workflowWatcher.mjs +0 -2
- package/dist/adapters/workflowWatcher.mjs.map +0 -1
- package/dist/adapters/workflowsHubChannel.cjs +0 -2
- package/dist/adapters/workflowsHubChannel.cjs.map +0 -1
- package/dist/adapters/workflowsHubChannel.d.cts +0 -19
- package/dist/adapters/workflowsHubChannel.d.cts.map +0 -1
- package/dist/adapters/workflowsHubChannel.d.mts +0 -19
- package/dist/adapters/workflowsHubChannel.d.mts.map +0 -1
- package/dist/adapters/workflowsHubChannel.mjs +0 -2
- package/dist/adapters/workflowsHubChannel.mjs.map +0 -1
- package/dist/hubApi.cjs +0 -1
- package/dist/hubApi.d.cts +0 -2
- package/dist/hubApi.d.mts +0 -2
- package/dist/hubApi.mjs +0 -1
- package/dist/schemas/workflowPi.mjs.map +0 -1
- package/dist/services/backgroundWork.cjs +0 -2
- package/dist/services/backgroundWork.cjs.map +0 -1
- package/dist/services/backgroundWork.mjs +0 -2
- package/dist/services/backgroundWork.mjs.map +0 -1
- package/dist/services/webWorkflowCatalog.cjs.map +0 -1
- package/dist/services/webWorkflowCatalog.d.cts +0 -48
- package/dist/services/webWorkflowCatalog.d.cts.map +0 -1
- package/dist/services/webWorkflowCatalog.d.mts +0 -48
- package/dist/services/webWorkflowCatalog.d.mts.map +0 -1
- package/dist/services/webWorkflowCatalog.mjs.map +0 -1
- package/dist/services/workflowLaunchCommand.cjs.map +0 -1
- package/dist/services/workflowLaunchCommand.mjs.map +0 -1
- package/dist/services/workflowRuns.cjs.map +0 -1
- package/dist/services/workflowRuns.d.cts +0 -14
- package/dist/services/workflowRuns.d.cts.map +0 -1
- package/dist/services/workflowRuns.d.mts +0 -14
- package/dist/services/workflowRuns.d.mts.map +0 -1
- package/dist/services/workflowRuns.mjs.map +0 -1
- package/dist/services/workflowTerminal.cjs.map +0 -1
- package/dist/services/workflowTerminal.mjs.map +0 -1
- package/dist/tui/workflow/doomOverlay.cjs.map +0 -1
- package/dist/tui/workflow/doomOverlay.mjs.map +0 -1
- package/dist/tui/workflow/overlayText.cjs.map +0 -1
- package/dist/tui/workflow/overlayText.mjs.map +0 -1
- package/dist/tui/workflow/rendering.cjs.map +0 -1
- package/dist/tui/workflow/rendering.mjs.map +0 -1
- package/dist/tui/workflow/workflowCatalog.cjs.map +0 -1
- package/dist/tui/workflow/workflowCatalog.mjs.map +0 -1
- package/dist/tui/workflow/workflowFinishedMessage.cjs.map +0 -1
- package/dist/tui/workflow/workflowFinishedMessage.mjs.map +0 -1
- package/dist/tui/workflow/workflowInspector.cjs.map +0 -1
- package/dist/tui/workflow/workflowInspector.mjs.map +0 -1
- package/dist/tui/workflow/workflowOverlay.cjs.map +0 -1
- package/dist/tui/workflow/workflowOverlay.mjs.map +0 -1
- package/dist/tui/workflow/workflowPicker.cjs.map +0 -1
- package/dist/tui/workflow/workflowProgressOverlay.cjs.map +0 -1
- package/dist/tui/workflow/workflowProgressOverlay.mjs.map +0 -1
- package/dist/tui/workflow/workflowPrompt.cjs.map +0 -1
- package/dist/tui/workflow/workflowPrompt.mjs.map +0 -1
- package/dist/tui/workflow/workflowRunPanel.cjs.map +0 -1
- package/dist/tui/workflow/workflowRunPanel.mjs.map +0 -1
- package/dist/tui/workflow/workflowStatusRow.cjs.map +0 -1
- package/dist/tui/workflow/workflowStatusRow.mjs.map +0 -1
- package/dist/tui/workflow/workflowStepMessage.cjs.map +0 -1
- package/dist/tui/workflow/workflowStepMessage.mjs.map +0 -1
- package/dist/tui/workflow/workflowToolRender.cjs.map +0 -1
- package/dist/tui/workflow/workflowToolRender.mjs.map +0 -1
- package/dist/types/index.cjs +0 -2
- package/dist/types/index.cjs.map +0 -1
- package/dist/types/index.mjs +0 -2
- package/dist/types/index.mjs.map +0 -1
- package/dist/types/webWorkflowTerminal.cjs +0 -2
- package/dist/types/webWorkflowTerminal.cjs.map +0 -1
- package/dist/types/webWorkflowTerminal.mjs +0 -2
- package/dist/types/webWorkflowTerminal.mjs.map +0 -1
- package/dist/types/webWorkflows.cjs.map +0 -1
- package/dist/types/webWorkflows.d.cts +0 -94
- package/dist/types/webWorkflows.d.cts.map +0 -1
- package/dist/types/webWorkflows.d.mts +0 -94
- package/dist/types/webWorkflows.d.mts.map +0 -1
- package/dist/types/webWorkflows.mjs.map +0 -1
- package/dist/webHub.cjs +0 -1
- package/dist/webHub.d.cts +0 -3
- package/dist/webHub.d.mts +0 -3
- package/dist/webHub.mjs +0 -1
- package/src/exports/webClient.ts +0 -1
- package/src/web/index.ts +0 -74
- /package/dist/{schemas → src/schemas}/workflowPi.cjs +0 -0
- /package/dist/{schemas → src/schemas}/workflowPi.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/doomOverlay.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/doomOverlay.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/overlayText.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/overlayText.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/rendering.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/rendering.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowCatalog.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowCatalog.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowInspector.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowInspector.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowOverlay.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowOverlay.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowPicker.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowPicker.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowProgressOverlay.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowProgressOverlay.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowPrompt.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowPrompt.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowRunPanel.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowRunPanel.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowStatusRow.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowStatusRow.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowStepMessage.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowStepMessage.mjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowToolRender.cjs +0 -0
- /package/dist/{tui → src/tui}/workflow/workflowToolRender.mjs +0 -0
- /package/dist/{types → src/types}/webWorkflows.cjs +0 -0
- /package/dist/{types → src/types}/webWorkflows.mjs +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../constants/workflow.cjs"),t=require("../piToolBridge/index.cjs");let n=require("@agimon-ai/doompi-core/child-process");const r=`AGIFLOW_PROJECT_ID`;function i(t){let n=new Set(e.WORKFLOW_PI_TOOL_NAMES);return e=>t?e:e.filter(e=>!n.has(e))}function a(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function o(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function s(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function c(e,t){let n=s(t);return e===`stop`?[`Next: call ${n} to confirm the run reached a terminal stage.`]:e===`recover`?[`Report the recovery failure and do not retry it blindly: call ${n} first.`,`Ask the user before launching fresh work when the recorded recovery is no longer valid.`]:[`Next: call ${n} to verify the recorded execution state.`]}const l=[`Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.`,`list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.`,`workflow_run with action status: check whether an earlier attempt is already running before launching again.`];function u(e){return e instanceof Error?e:Error(String(e),{cause:e})}function d(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function f(e,t,n){let r=t.findLaunchedRun;if(!r)return{kind:`settled`,outcome:await e};let i=t.launchAckPollMs??500,a=Date.now()+(t.launchAckTimeoutMs??15e3);for(;;){let t=await Promise.race([e,d(i)]);if(t)return{kind:`settled`,outcome:t};let o=await r(n).catch(()=>void 0);if(o)return{kind:`record`,record:o};if(Date.now()>=a)return{kind:`handoff`}}}function p(e,n,r){e.then(e=>{if(e.kind===`error`){n.onLateFailure?.(u(e.error),r);return}e.result.isError&&n.onLateFailure?.(Error(t.toolResultText(e.result)||`Workflow launch failed.`),r)})}function m(i){return{async execute(o,s,c){let d=(0,n.resolveRootSessionId)(s.sessionManager.getSessionId(),i.environment);i.observeSession?.(d);let m=o.env?.AGIFLOW_JOB_KIND?.trim(),h=o.env?.AGIFLOW_JOB_ID?.trim(),g=o.env?.[r]?.trim();if(!!m!=!!h)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(m&&m!==`task`&&m!==`work-unit`)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.`);if(m&&!g)throw Error(`Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.`);if(m&&!o.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);a(c,e.LAUNCH_WORKFLOW_TOOL_NAME,`Checking workflow capacity...`);let _=t.resolveMaxConcurrent(i.environment),v=await i.activeRunCount?.()??0;if(v>=_)throw Error(t.withOptions(`This session is at capacity: ${v}/${_} workflows running.`,[`Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.`,`workflow_run with action status: show what is running, so the user can judge whether something is stuck.`,`workflow_run with action stop: free a slot by stopping a run the user no longer wants.`]));if(o.runner){let e=i.rejectRunner?.(o.workflowPath,o.runner);if(e)throw Error(e)}a(c,e.LAUNCH_WORKFLOW_TOOL_NAME,`Launching workflow ${o.workflowPath}...`);let y={...o.env},b=i.environment.AGIFLOW_DISPATCH_CONTEXT_FILE,x=i.environment[r]?.trim();if(b&&m&&x&&g&&x!==g)throw Error(`Agiflow workflow project identity conflicts with the dispatcher context (${g} versus ${x}).`);if(b)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=i.environment[e];t&&(y[e]=t)}let S=Date.now(),C=i.trackPendingRun(i.runTool.execute({...o,env:{...y,[t.PI_SESSION_ENV]:d}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),w=await f(C,i,{sessionId:d,since:S,workflowPath:o.workflowPath});if(w.kind===`settled`){let e=w.outcome;if(e.kind===`error`)throw u(e.error);let n=e.result;if(n.isError)throw Error(t.withOptions(t.toolResultText(n)||`Workflow launch failed.`,l));return await i.onLaunch?.(s),n}return p(C,i,s),await i.onLaunch?.(s),{content:[{type:`text`,text:w.kind===`record`?t.launchedRunSummary(w.record):t.launchHandoffSummary(o.workflowPath)}]}}}}exports.actionGuidance=c,exports.appendGuidance=o,exports.createWorkflowLaunchExecutor=m,exports.launchFailureOptions=l,exports.reportProgress=a,exports.workflowToolRestriction=i;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["WORKFLOW_PI_TOOL_NAMES","toolResultText","resolveRootSessionId","LAUNCH_WORKFLOW_TOOL_NAME","resolveMaxConcurrent","withOptions","PI_SESSION_ENV","launchedRunSummary","launchHandoffSummary"],"sources":["../../../../src/services/workflowExecution/index.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-core/child-process';\nimport type { DoomToolRestriction } from '@agimon-ai/doompi-core/tool-surface';\nimport { type EmbeddedWorkflowFeature, type WorkflowRunRecord } from '@agimon-ai/workflow-mcp';\nimport type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\n\nimport { type WorkflowRunInput } from '../../schemas/workflowPi';\nimport {\n launchedRunSummary,\n launchHandoffSummary,\n PI_SESSION_ENV,\n resolveMaxConcurrent,\n toolResultText,\n withOptions,\n} from '../piToolBridge';\n\nconst AGIFLOW_JOB_ID_ENV = 'AGIFLOW_JOB_ID';\nconst AGIFLOW_JOB_KIND_ENV = 'AGIFLOW_JOB_KIND';\nconst AGIFLOW_PROJECT_ID_ENV = 'AGIFLOW_PROJECT_ID';\n\nimport { WORKFLOW_PI_TOOL_NAMES, LAUNCH_WORKFLOW_TOOL_NAME } from '../../constants/workflow';\n\n/**\n * What workflow mode does to the tool surface: nothing but hide its own tools.\n *\n * The tools are registered for the whole session because Pi has no other way to\n * add one later, so the mode is expressed as a restriction rather than as a\n * registration.\n */\nexport function workflowToolRestriction(enabled: boolean): DoomToolRestriction {\n const hidden = new Set<string>(WORKFLOW_PI_TOOL_NAMES);\n return (incoming) => (enabled ? incoming : incoming.filter((name) => !hidden.has(name)));\n}\n\nexport type WorkflowRunSelector = Pick<WorkflowRunInput, 'runKey' | 'workspace'>;\n\nexport type WorkflowLaunchInput = z.infer<ReturnType<EmbeddedWorkflowFeature['runTool']['getInputSchema']>>;\n\n/**\n * Where a launch was answered from, which decides what the caller is told.\n *\n * Tagged rather than inferred from an absent field: the failure carries an\n * `unknown`, and `unknown` includes `undefined`, so only a tag can tell a\n * rejection apart from a result.\n */\ntype LaunchOutcome = { kind: 'value'; result: CallToolResult } | { kind: 'error'; error: unknown };\ntype LaunchAck =\n | { kind: 'settled'; outcome: LaunchOutcome }\n | { kind: 'record'; record: WorkflowRunRecord }\n | { kind: 'handoff' };\n\n/** How often the registry is asked whether the launch has produced a run. */\nconst LAUNCH_ACK_POLL_MS = 500;\n/**\n * How long to wait for that before answering without a run key.\n *\n * Long enough to cover a launcher that is merely slow, short enough that a\n * caller is never left holding a turn open for a launcher that will not answer.\n */\nconst LAUNCH_ACK_TIMEOUT_MS = 15_000;\n\nexport interface LaunchedRunQuery {\n sessionId: string | undefined;\n /** Epoch milliseconds the launch began, so an earlier run cannot match. */\n since: number;\n workflowPath: string;\n}\n\nexport interface WorkflowLaunchExecutorDependencies {\n readonly environment: Readonly<Record<string, string | undefined>>;\n activeRunCount?: () => Promise<number>;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n observeSession?: (sessionId: string | undefined) => void;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n runTool: EmbeddedWorkflowFeature['runTool'];\n trackPendingRun: <T>(run: Promise<T>) => Promise<T>;\n /**\n * The run this launch registered, or undefined while none has appeared.\n *\n * Its presence is what lets a launch answer on the registry instead of on the\n * launcher process exiting. Left out, the launch waits for the process, which\n * is the behaviour every caller had before.\n */\n findLaunchedRun?: (query: LaunchedRunQuery) => Promise<WorkflowRunRecord | undefined>;\n /** Report a launch that fails after this call already answered \"started\". */\n onLateFailure?: (error: unknown, ctx: ExtensionContext) => void;\n launchAckPollMs?: number;\n launchAckTimeoutMs?: number;\n}\n\nexport interface WorkflowLaunchExecutor {\n execute(\n input: WorkflowLaunchInput,\n ctx: ExtensionContext,\n onUpdate?: AgentToolUpdateCallback<{ tool: string }>,\n ): Promise<CallToolResult>;\n}\n\nexport interface WorkflowPiToolDependencies {\n readonly environment: Readonly<Record<string, string | undefined>>;\n launchExecutor?: WorkflowLaunchExecutor;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n activeRunCount?: () => Promise<number>;\n followRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n tailRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n openRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n requireSessionRun?: (input: WorkflowRunSelector, sessionId: string | undefined) => Promise<WorkflowRunRecord>;\n /**\n * Resolve a failed recovery target globally.\n *\n * Recovery is the one ownership-transferring action: a replacement Pi\n * session must be able to adopt a terminal run whose launching session is\n * gone. Every non-recovery action remains session-scoped.\n */\n requireRecoverableRun?: (input: WorkflowRunSelector) => Promise<WorkflowRunRecord>;\n readRecoveryEvidence?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n /**\n * Hand a recovery to a terminal launcher, returning what to tell the caller.\n *\n * Undefined means this recovery cannot be delegated, and the in-process\n * replay below is used instead.\n */\n delegateRecovery?: (\n input: { runKey: string; workspace?: string; runner?: string },\n ctx: ExtensionContext,\n ) => Promise<string | undefined>;\n observeSession?: (sessionId: string | undefined) => void;\n trackPendingRun?: <T>(run: Promise<T>) => Promise<T>;\n feature?: EmbeddedWorkflowFeature;\n recoverTool?: {\n execute(input: {\n dryRun?: boolean;\n job?: string;\n runKey: string;\n runner?: string;\n workspace?: string;\n }): Promise<CallToolResult>;\n };\n controlTool?: ReturnType<EmbeddedWorkflowFeature['createControlTool']>;\n runTool?: EmbeddedWorkflowFeature['runTool'];\n listWorkflowsTool?: EmbeddedWorkflowFeature['listWorkflowsTool'];\n}\n\nexport function reportProgress(\n onUpdate: AgentToolUpdateCallback<{ tool: string }> | undefined,\n tool: string,\n message: string,\n): void {\n onUpdate?.({ content: [{ type: 'text', text: message }], details: { tool } });\n}\n\nexport function appendGuidance(\n result: AgentToolResult<{ tool: string }>,\n guidance: string,\n): AgentToolResult<{ tool: string }> {\n return { ...result, content: [...result.content, { type: 'text', text: guidance }] };\n}\n\nfunction statusAction(runKey: string): string {\n return `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(runKey)}}`;\n}\n\nexport function actionGuidance(action: WorkflowRunInput['action'], runKey: string): string[] {\n const status = statusAction(runKey);\n if (action === 'stop') return [`Next: call ${status} to confirm the run reached a terminal stage.`];\n if (action === 'recover') {\n return [\n `Report the recovery failure and do not retry it blindly: call ${status} first.`,\n 'Ask the user before launching fresh work when the recorded recovery is no longer valid.',\n ];\n }\n return [`Next: call ${status} to verify the recorded execution state.`];\n}\n\nexport const launchFailureOptions = [\n 'Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.',\n 'list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.',\n 'workflow_run with action status: check whether an earlier attempt is already running before launching again.',\n];\n\n/**\n * An Error, whatever the launch rejected with.\n *\n * A rejection reaches here as `unknown` because it crossed a promise boundary,\n * and rethrowing that as-is hands Pi's tool layer something it cannot read a\n * message or a stack off. The original is kept as the cause rather than\n * flattened into a string.\n */\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\n/** Resolves to undefined, so a race against it reads as \"nothing settled yet\". */\nfunction sleep(ms: number): Promise<undefined> {\n return new Promise((settle) => {\n const timer = setTimeout(() => settle(undefined), ms);\n timer.unref?.();\n });\n}\n\n/**\n * Wait for whichever answer arrives first: the launch settling, the run\n * appearing in the registry, or the budget running out.\n *\n * A launch that delegates to a terminal launcher settles when that launcher's\n * whole process chain closes, which on a loaded machine has been measured\n * minutes after the run itself was registered and working. The registry is the\n * earlier and more truthful signal: a run recorded there is a run that started.\n */\nasync function awaitLaunchAck(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n query: LaunchedRunQuery,\n): Promise<LaunchAck> {\n const findLaunchedRun = dependencies.findLaunchedRun;\n if (!findLaunchedRun) return { kind: 'settled', outcome: await settled };\n\n const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;\n const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);\n for (;;) {\n const outcome = await Promise.race([settled, sleep(pollMs)]);\n if (outcome) return { kind: 'settled', outcome };\n // A registry read that throws must not fail the launch: the run may be\n // perfectly healthy, and the settled promise is still the fallback.\n const record = await findLaunchedRun(query).catch(() => undefined);\n if (record) return { kind: 'record', record };\n if (Date.now() >= deadline) return { kind: 'handoff' };\n }\n}\n\n/**\n * Carry a failure that lands after the caller was told the run started.\n *\n * Answering early trades the launcher's exit code for timeliness. Dropping that\n * code entirely is not part of the trade: a launch that fails a minute later\n * would otherwise leave a user waiting on a run that never existed.\n */\nfunction reportLateFailure(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n ctx: ExtensionContext,\n): void {\n void settled.then((outcome) => {\n if (outcome.kind === 'error') {\n dependencies.onLateFailure?.(asError(outcome.error), ctx);\n return;\n }\n if (outcome.result.isError) {\n dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || 'Workflow launch failed.'), ctx);\n }\n });\n}\n\nexport function createWorkflowLaunchExecutor(dependencies: WorkflowLaunchExecutorDependencies): WorkflowLaunchExecutor {\n return {\n async execute(input, ctx, onUpdate) {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId(), dependencies.environment);\n dependencies.observeSession?.(sessionId);\n const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();\n const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();\n const explicitProjectId = input.env?.[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.');\n }\n if (agiflowJobKind && agiflowJobKind !== 'task' && agiflowJobKind !== 'work-unit') {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.');\n }\n if (agiflowJobKind && !explicitProjectId) {\n throw new Error('Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.');\n }\n if (agiflowJobKind && !input.prompt?.trim()) {\n throw new Error(\n 'Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.',\n );\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, 'Checking workflow capacity...');\n const maxConcurrent = resolveMaxConcurrent(dependencies.environment);\n const active = (await dependencies.activeRunCount?.()) ?? 0;\n if (active >= maxConcurrent) {\n throw new Error(\n withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [\n 'Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.',\n 'workflow_run with action status: show what is running, so the user can judge whether something is stuck.',\n 'workflow_run with action stop: free a slot by stopping a run the user no longer wants.',\n ]),\n );\n }\n\n if (input.runner) {\n const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);\n if (rejection) throw new Error(rejection);\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);\n const workflowEnv = { ...input.env };\n const dispatcherContextFile = dependencies.environment.AGIFLOW_DISPATCH_CONTEXT_FILE;\n const dispatcherProjectId = dependencies.environment[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (\n dispatcherContextFile &&\n agiflowJobKind &&\n dispatcherProjectId &&\n explicitProjectId &&\n dispatcherProjectId !== explicitProjectId\n ) {\n throw new Error(\n `Agiflow workflow project identity conflicts with the dispatcher context (${explicitProjectId} versus ${dispatcherProjectId}).`,\n );\n }\n if (dispatcherContextFile) {\n for (const key of [\n 'AGIFLOW_ORGANIZATION_ID',\n 'AGIFLOW_PROJECT_ID',\n 'AGIFLOW_DEVICE_ID',\n 'BACKEND_AGIFLOW_API_ENDPOINT',\n 'AGIFLOW_DISPATCH_SECRET_FILE',\n ] as const) {\n const hostValue = dependencies.environment[key];\n if (hostValue) workflowEnv[key] = hostValue;\n }\n }\n const since = Date.now();\n const launch = dependencies.trackPendingRun(\n dependencies.runTool.execute({\n ...input,\n env: { ...workflowEnv, [PI_SESSION_ENV]: sessionId },\n }),\n );\n // Folded into a value before anything races it. A promise this function\n // may stop awaiting must never be able to reject unobserved, which in\n // Node ends the whole process, Pi's TUI included.\n const settled: Promise<LaunchOutcome> = launch.then(\n (value): LaunchOutcome => ({ kind: 'value', result: value }),\n (error: unknown): LaunchOutcome => ({ kind: 'error', error }),\n );\n\n const ack = await awaitLaunchAck(settled, dependencies, {\n sessionId,\n since,\n workflowPath: input.workflowPath,\n });\n\n if (ack.kind === 'settled') {\n const outcome = ack.outcome;\n if (outcome.kind === 'error') throw asError(outcome.error);\n const result = outcome.result;\n if (result.isError)\n throw new Error(withOptions(toolResultText(result) || 'Workflow launch failed.', launchFailureOptions));\n await dependencies.onLaunch?.(ctx);\n return result;\n }\n\n reportLateFailure(settled, dependencies, ctx);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [\n {\n type: 'text',\n text: ack.kind === 'record' ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath),\n },\n ],\n };\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "../../schemas/workflowPi.mjs";
|
|
2
|
+
import { EmbeddedWorkflowFeature } from "@agimon-ai/workflow-mcp";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import "@earendil-works/pi-coding-agent";
|
|
5
|
+
import "@agimon-ai/doompi-core/tool-surface";
|
|
6
|
+
import "@modelcontextprotocol/sdk/types.js";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{LAUNCH_WORKFLOW_TOOL_NAME as e,WORKFLOW_PI_TOOL_NAMES as t}from"../../constants/workflow.mjs";import{PI_SESSION_ENV as n,launchHandoffSummary as r,launchedRunSummary as i,resolveMaxConcurrent as a,toolResultText as o,withOptions as s}from"../piToolBridge/index.mjs";import{resolveRootSessionId as c}from"@agimon-ai/doompi-core/child-process";const l=`AGIFLOW_PROJECT_ID`;function u(e){let n=new Set(t);return t=>e?t:t.filter(e=>!n.has(e))}function d(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function f(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function p(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function m(e,t){let n=p(t);return e===`stop`?[`Next: call ${n} to confirm the run reached a terminal stage.`]:e===`recover`?[`Report the recovery failure and do not retry it blindly: call ${n} first.`,`Ask the user before launching fresh work when the recorded recovery is no longer valid.`]:[`Next: call ${n} to verify the recorded execution state.`]}const h=[`Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.`,`list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.`,`workflow_run with action status: check whether an earlier attempt is already running before launching again.`];function g(e){return e instanceof Error?e:Error(String(e),{cause:e})}function _(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function v(e,t,n){let r=t.findLaunchedRun;if(!r)return{kind:`settled`,outcome:await e};let i=t.launchAckPollMs??500,a=Date.now()+(t.launchAckTimeoutMs??15e3);for(;;){let t=await Promise.race([e,_(i)]);if(t)return{kind:`settled`,outcome:t};let o=await r(n).catch(()=>void 0);if(o)return{kind:`record`,record:o};if(Date.now()>=a)return{kind:`handoff`}}}function y(e,t,n){e.then(e=>{if(e.kind===`error`){t.onLateFailure?.(g(e.error),n);return}e.result.isError&&t.onLateFailure?.(Error(o(e.result)||`Workflow launch failed.`),n)})}function b(t){return{async execute(u,f,p){let m=c(f.sessionManager.getSessionId(),t.environment);t.observeSession?.(m);let _=u.env?.AGIFLOW_JOB_KIND?.trim(),b=u.env?.AGIFLOW_JOB_ID?.trim(),x=u.env?.[l]?.trim();if(!!_!=!!b)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(_&&_!==`task`&&_!==`work-unit`)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.`);if(_&&!x)throw Error(`Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.`);if(_&&!u.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);d(p,e,`Checking workflow capacity...`);let S=a(t.environment),C=await t.activeRunCount?.()??0;if(C>=S)throw Error(s(`This session is at capacity: ${C}/${S} workflows running.`,[`Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.`,`workflow_run with action status: show what is running, so the user can judge whether something is stuck.`,`workflow_run with action stop: free a slot by stopping a run the user no longer wants.`]));if(u.runner){let e=t.rejectRunner?.(u.workflowPath,u.runner);if(e)throw Error(e)}d(p,e,`Launching workflow ${u.workflowPath}...`);let w={...u.env},T=t.environment.AGIFLOW_DISPATCH_CONTEXT_FILE,E=t.environment[l]?.trim();if(T&&_&&E&&x&&E!==x)throw Error(`Agiflow workflow project identity conflicts with the dispatcher context (${x} versus ${E}).`);if(T)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let n=t.environment[e];n&&(w[e]=n)}let D=Date.now(),O=t.trackPendingRun(t.runTool.execute({...u,env:{...w,[n]:m}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),k=await v(O,t,{sessionId:m,since:D,workflowPath:u.workflowPath});if(k.kind===`settled`){let e=k.outcome;if(e.kind===`error`)throw g(e.error);let n=e.result;if(n.isError)throw Error(s(o(n)||`Workflow launch failed.`,h));return await t.onLaunch?.(f),n}return y(O,t,f),await t.onLaunch?.(f),{content:[{type:`text`,text:k.kind===`record`?i(k.record):r(u.workflowPath)}]}}}}export{m as actionGuidance,f as appendGuidance,b as createWorkflowLaunchExecutor,h as launchFailureOptions,d as reportProgress,u as workflowToolRestriction};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowExecution/index.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-core/child-process';\nimport type { DoomToolRestriction } from '@agimon-ai/doompi-core/tool-surface';\nimport { type EmbeddedWorkflowFeature, type WorkflowRunRecord } from '@agimon-ai/workflow-mcp';\nimport type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\n\nimport { type WorkflowRunInput } from '../../schemas/workflowPi';\nimport {\n launchedRunSummary,\n launchHandoffSummary,\n PI_SESSION_ENV,\n resolveMaxConcurrent,\n toolResultText,\n withOptions,\n} from '../piToolBridge';\n\nconst AGIFLOW_JOB_ID_ENV = 'AGIFLOW_JOB_ID';\nconst AGIFLOW_JOB_KIND_ENV = 'AGIFLOW_JOB_KIND';\nconst AGIFLOW_PROJECT_ID_ENV = 'AGIFLOW_PROJECT_ID';\n\nimport { WORKFLOW_PI_TOOL_NAMES, LAUNCH_WORKFLOW_TOOL_NAME } from '../../constants/workflow';\n\n/**\n * What workflow mode does to the tool surface: nothing but hide its own tools.\n *\n * The tools are registered for the whole session because Pi has no other way to\n * add one later, so the mode is expressed as a restriction rather than as a\n * registration.\n */\nexport function workflowToolRestriction(enabled: boolean): DoomToolRestriction {\n const hidden = new Set<string>(WORKFLOW_PI_TOOL_NAMES);\n return (incoming) => (enabled ? incoming : incoming.filter((name) => !hidden.has(name)));\n}\n\nexport type WorkflowRunSelector = Pick<WorkflowRunInput, 'runKey' | 'workspace'>;\n\nexport type WorkflowLaunchInput = z.infer<ReturnType<EmbeddedWorkflowFeature['runTool']['getInputSchema']>>;\n\n/**\n * Where a launch was answered from, which decides what the caller is told.\n *\n * Tagged rather than inferred from an absent field: the failure carries an\n * `unknown`, and `unknown` includes `undefined`, so only a tag can tell a\n * rejection apart from a result.\n */\ntype LaunchOutcome = { kind: 'value'; result: CallToolResult } | { kind: 'error'; error: unknown };\ntype LaunchAck =\n | { kind: 'settled'; outcome: LaunchOutcome }\n | { kind: 'record'; record: WorkflowRunRecord }\n | { kind: 'handoff' };\n\n/** How often the registry is asked whether the launch has produced a run. */\nconst LAUNCH_ACK_POLL_MS = 500;\n/**\n * How long to wait for that before answering without a run key.\n *\n * Long enough to cover a launcher that is merely slow, short enough that a\n * caller is never left holding a turn open for a launcher that will not answer.\n */\nconst LAUNCH_ACK_TIMEOUT_MS = 15_000;\n\nexport interface LaunchedRunQuery {\n sessionId: string | undefined;\n /** Epoch milliseconds the launch began, so an earlier run cannot match. */\n since: number;\n workflowPath: string;\n}\n\nexport interface WorkflowLaunchExecutorDependencies {\n readonly environment: Readonly<Record<string, string | undefined>>;\n activeRunCount?: () => Promise<number>;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n observeSession?: (sessionId: string | undefined) => void;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n runTool: EmbeddedWorkflowFeature['runTool'];\n trackPendingRun: <T>(run: Promise<T>) => Promise<T>;\n /**\n * The run this launch registered, or undefined while none has appeared.\n *\n * Its presence is what lets a launch answer on the registry instead of on the\n * launcher process exiting. Left out, the launch waits for the process, which\n * is the behaviour every caller had before.\n */\n findLaunchedRun?: (query: LaunchedRunQuery) => Promise<WorkflowRunRecord | undefined>;\n /** Report a launch that fails after this call already answered \"started\". */\n onLateFailure?: (error: unknown, ctx: ExtensionContext) => void;\n launchAckPollMs?: number;\n launchAckTimeoutMs?: number;\n}\n\nexport interface WorkflowLaunchExecutor {\n execute(\n input: WorkflowLaunchInput,\n ctx: ExtensionContext,\n onUpdate?: AgentToolUpdateCallback<{ tool: string }>,\n ): Promise<CallToolResult>;\n}\n\nexport interface WorkflowPiToolDependencies {\n readonly environment: Readonly<Record<string, string | undefined>>;\n launchExecutor?: WorkflowLaunchExecutor;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n activeRunCount?: () => Promise<number>;\n followRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n tailRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n openRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n requireSessionRun?: (input: WorkflowRunSelector, sessionId: string | undefined) => Promise<WorkflowRunRecord>;\n /**\n * Resolve a failed recovery target globally.\n *\n * Recovery is the one ownership-transferring action: a replacement Pi\n * session must be able to adopt a terminal run whose launching session is\n * gone. Every non-recovery action remains session-scoped.\n */\n requireRecoverableRun?: (input: WorkflowRunSelector) => Promise<WorkflowRunRecord>;\n readRecoveryEvidence?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n /**\n * Hand a recovery to a terminal launcher, returning what to tell the caller.\n *\n * Undefined means this recovery cannot be delegated, and the in-process\n * replay below is used instead.\n */\n delegateRecovery?: (\n input: { runKey: string; workspace?: string; runner?: string },\n ctx: ExtensionContext,\n ) => Promise<string | undefined>;\n observeSession?: (sessionId: string | undefined) => void;\n trackPendingRun?: <T>(run: Promise<T>) => Promise<T>;\n feature?: EmbeddedWorkflowFeature;\n recoverTool?: {\n execute(input: {\n dryRun?: boolean;\n job?: string;\n runKey: string;\n runner?: string;\n workspace?: string;\n }): Promise<CallToolResult>;\n };\n controlTool?: ReturnType<EmbeddedWorkflowFeature['createControlTool']>;\n runTool?: EmbeddedWorkflowFeature['runTool'];\n listWorkflowsTool?: EmbeddedWorkflowFeature['listWorkflowsTool'];\n}\n\nexport function reportProgress(\n onUpdate: AgentToolUpdateCallback<{ tool: string }> | undefined,\n tool: string,\n message: string,\n): void {\n onUpdate?.({ content: [{ type: 'text', text: message }], details: { tool } });\n}\n\nexport function appendGuidance(\n result: AgentToolResult<{ tool: string }>,\n guidance: string,\n): AgentToolResult<{ tool: string }> {\n return { ...result, content: [...result.content, { type: 'text', text: guidance }] };\n}\n\nfunction statusAction(runKey: string): string {\n return `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(runKey)}}`;\n}\n\nexport function actionGuidance(action: WorkflowRunInput['action'], runKey: string): string[] {\n const status = statusAction(runKey);\n if (action === 'stop') return [`Next: call ${status} to confirm the run reached a terminal stage.`];\n if (action === 'recover') {\n return [\n `Report the recovery failure and do not retry it blindly: call ${status} first.`,\n 'Ask the user before launching fresh work when the recorded recovery is no longer valid.',\n ];\n }\n return [`Next: call ${status} to verify the recorded execution state.`];\n}\n\nexport const launchFailureOptions = [\n 'Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.',\n 'list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.',\n 'workflow_run with action status: check whether an earlier attempt is already running before launching again.',\n];\n\n/**\n * An Error, whatever the launch rejected with.\n *\n * A rejection reaches here as `unknown` because it crossed a promise boundary,\n * and rethrowing that as-is hands Pi's tool layer something it cannot read a\n * message or a stack off. The original is kept as the cause rather than\n * flattened into a string.\n */\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\n/** Resolves to undefined, so a race against it reads as \"nothing settled yet\". */\nfunction sleep(ms: number): Promise<undefined> {\n return new Promise((settle) => {\n const timer = setTimeout(() => settle(undefined), ms);\n timer.unref?.();\n });\n}\n\n/**\n * Wait for whichever answer arrives first: the launch settling, the run\n * appearing in the registry, or the budget running out.\n *\n * A launch that delegates to a terminal launcher settles when that launcher's\n * whole process chain closes, which on a loaded machine has been measured\n * minutes after the run itself was registered and working. The registry is the\n * earlier and more truthful signal: a run recorded there is a run that started.\n */\nasync function awaitLaunchAck(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n query: LaunchedRunQuery,\n): Promise<LaunchAck> {\n const findLaunchedRun = dependencies.findLaunchedRun;\n if (!findLaunchedRun) return { kind: 'settled', outcome: await settled };\n\n const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;\n const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);\n for (;;) {\n const outcome = await Promise.race([settled, sleep(pollMs)]);\n if (outcome) return { kind: 'settled', outcome };\n // A registry read that throws must not fail the launch: the run may be\n // perfectly healthy, and the settled promise is still the fallback.\n const record = await findLaunchedRun(query).catch(() => undefined);\n if (record) return { kind: 'record', record };\n if (Date.now() >= deadline) return { kind: 'handoff' };\n }\n}\n\n/**\n * Carry a failure that lands after the caller was told the run started.\n *\n * Answering early trades the launcher's exit code for timeliness. Dropping that\n * code entirely is not part of the trade: a launch that fails a minute later\n * would otherwise leave a user waiting on a run that never existed.\n */\nfunction reportLateFailure(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n ctx: ExtensionContext,\n): void {\n void settled.then((outcome) => {\n if (outcome.kind === 'error') {\n dependencies.onLateFailure?.(asError(outcome.error), ctx);\n return;\n }\n if (outcome.result.isError) {\n dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || 'Workflow launch failed.'), ctx);\n }\n });\n}\n\nexport function createWorkflowLaunchExecutor(dependencies: WorkflowLaunchExecutorDependencies): WorkflowLaunchExecutor {\n return {\n async execute(input, ctx, onUpdate) {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId(), dependencies.environment);\n dependencies.observeSession?.(sessionId);\n const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();\n const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();\n const explicitProjectId = input.env?.[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.');\n }\n if (agiflowJobKind && agiflowJobKind !== 'task' && agiflowJobKind !== 'work-unit') {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.');\n }\n if (agiflowJobKind && !explicitProjectId) {\n throw new Error('Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.');\n }\n if (agiflowJobKind && !input.prompt?.trim()) {\n throw new Error(\n 'Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.',\n );\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, 'Checking workflow capacity...');\n const maxConcurrent = resolveMaxConcurrent(dependencies.environment);\n const active = (await dependencies.activeRunCount?.()) ?? 0;\n if (active >= maxConcurrent) {\n throw new Error(\n withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [\n 'Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.',\n 'workflow_run with action status: show what is running, so the user can judge whether something is stuck.',\n 'workflow_run with action stop: free a slot by stopping a run the user no longer wants.',\n ]),\n );\n }\n\n if (input.runner) {\n const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);\n if (rejection) throw new Error(rejection);\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);\n const workflowEnv = { ...input.env };\n const dispatcherContextFile = dependencies.environment.AGIFLOW_DISPATCH_CONTEXT_FILE;\n const dispatcherProjectId = dependencies.environment[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (\n dispatcherContextFile &&\n agiflowJobKind &&\n dispatcherProjectId &&\n explicitProjectId &&\n dispatcherProjectId !== explicitProjectId\n ) {\n throw new Error(\n `Agiflow workflow project identity conflicts with the dispatcher context (${explicitProjectId} versus ${dispatcherProjectId}).`,\n );\n }\n if (dispatcherContextFile) {\n for (const key of [\n 'AGIFLOW_ORGANIZATION_ID',\n 'AGIFLOW_PROJECT_ID',\n 'AGIFLOW_DEVICE_ID',\n 'BACKEND_AGIFLOW_API_ENDPOINT',\n 'AGIFLOW_DISPATCH_SECRET_FILE',\n ] as const) {\n const hostValue = dependencies.environment[key];\n if (hostValue) workflowEnv[key] = hostValue;\n }\n }\n const since = Date.now();\n const launch = dependencies.trackPendingRun(\n dependencies.runTool.execute({\n ...input,\n env: { ...workflowEnv, [PI_SESSION_ENV]: sessionId },\n }),\n );\n // Folded into a value before anything races it. A promise this function\n // may stop awaiting must never be able to reject unobserved, which in\n // Node ends the whole process, Pi's TUI included.\n const settled: Promise<LaunchOutcome> = launch.then(\n (value): LaunchOutcome => ({ kind: 'value', result: value }),\n (error: unknown): LaunchOutcome => ({ kind: 'error', error }),\n );\n\n const ack = await awaitLaunchAck(settled, dependencies, {\n sessionId,\n since,\n workflowPath: input.workflowPath,\n });\n\n if (ack.kind === 'settled') {\n const outcome = ack.outcome;\n if (outcome.kind === 'error') throw asError(outcome.error);\n const result = outcome.result;\n if (result.isError)\n throw new Error(withOptions(toolResultText(result) || 'Workflow launch failed.', launchFailureOptions));\n await dependencies.onLaunch?.(ctx);\n return result;\n }\n\n reportLateFailure(settled, dependencies, ctx);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [\n {\n type: 'text',\n text: ack.kind === 'record' ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath),\n },\n ],\n };\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require("node:async_hooks"),t=require("@agimon-ai/doompi-core/child-process");const n=`agiflow-dispatcher`,r=`The workflow runtime is no longer active.`,i=Symbol(`doom-workflow-cleanup`),a=new Set([`list_workflows`,`launch_workflow`]),o=new Set([`launch_workflow`]);function s(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function c(e,t){let n=new Proxy(e.sessionManager,{get(e,n){return n===`getSessionId`?()=>t:s(e,n)}});return new Proxy(e,{get(e,t){return t===`sessionManager`?n:s(e,t)}})}function l(){return e=>e.filter(e=>e!==`workflow_run`)}function u(e,n,r){let i=n?(0,t.resolveRootSessionId)(n,r):void 0;return e.filter(e=>a.has(e.name)).map(e=>!i||!o.has(e.name)?e:{...e,execute:(t,n,r,a,o)=>e.execute(t,n,r,a,c(o,i))})}function d(e){return(0,t.readChildProcessContext)(e)?.parentSessionId??(e[t.SUBAGENT_PARENT_SESSION_ENV]?.trim()||void 0)}function f(e){return(0,t.readChildProcessContext)(e)?.mode===n||e.PI_SUBAGENT_CHILD_AGENT===n&&!!e[t.SUBAGENT_PARENT_SESSION_ENV]?.trim()}function p(t,n){let a=new e.AsyncLocalStorage,o=!0,c=!1,l=0,u,d,f=()=>{let e=a.getStore();return!o||n?.aborted&&e!==i?!1:e===void 0||e===i||e===l},p=e=>{let t=a.getStore();return!o||n?.aborted&&t!==i?!1:t===i||e===l&&(t===void 0||t===e)},m=(e,t)=>{let n=new Proxy(e.ui,{get(e,n){let r=Reflect.get(e,n,e);return typeof r==`function`?(...i)=>{if(p(t))return Reflect.apply(r,e,i);if(n===`confirm`)return Promise.resolve(!1);if(n===`custom`||n===`editor`||n===`input`||n===`select`)return Promise.resolve(void 0)}:r}});return new Proxy(e,{get(e,t){return t===`ui`?n:s(e,t)}})};return{pi:new Proxy(t,{get(e,t){return t===`sendMessage`?(...t)=>{f()&&e.sendMessage(...t)}:t===`sendUserMessage`?(...t)=>{f()&&e.sendUserMessage(...t)}:t===`exec`?(...t)=>f()?e.exec(...t):Promise.resolve({code:1,killed:!1,stderr:r,stdout:``}):s(e,t)}}),tool:e=>({...e,execute:async(t,n,i,s,u)=>{let d=l;if(!o||c||i?.aborted)throw Error(r);let f=m(u,d),p=s?e=>{o&&d===l&&!i?.aborted&&s(e)}:void 0,h=await a.run(d,()=>e.execute(t,n,i,p,f));if(!o||c||d!==l)throw Error(r);return h}}),command:(e,t)=>[e,{...t,handler:async(e,n)=>{let r=l;o&&!c&&await a.run(r,()=>t.handler(e,m(n,r)))}}],shortcut:(e,t)=>[e,{...t,handler:e=>{let n=l;if(o&&!c)return a.run(n,()=>t.handler(m(e,n)))}}],events:e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,(n,r)=>{if(!o||c)return;e===`session_start`&&(n!==u||r!==d)&&(l+=1,u=n,d=r);let i=l;return a.run(i,()=>t(n,m(r,i)))}])),beginDisposal(){!c&&o&&(c=!0,l+=1)},finishDisposal(){o=!1,c=!1,l+=1},isCurrentInvocation:f,runCleanup:e=>a.run(i,e)}}exports.createWorkflowFence=p,exports.dispatcherToolRestriction=l,exports.dispatcherTools=u,exports.isWorkflowDispatcherProcess=f,exports.resolveDispatcherParentSession=d;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["resolveRootSessionId","readChildProcessContext","SUBAGENT_PARENT_SESSION_ENV","AsyncLocalStorage"],"sources":["../../../../src/services/workflowFence/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport {\n readChildProcessContext,\n resolveRootSessionId,\n SUBAGENT_PARENT_SESSION_ENV,\n} from '@agimon-ai/doompi-core/child-process';\nimport type { PiEventHandlers } from '@agimon-ai/doompi-core/pi-extension';\nimport type { DoomToolRestriction } from '@agimon-ai/doompi-core/tool-surface';\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nconst DISPATCHER_AGENT_NAME = 'agiflow-dispatcher';\nconst SESSION_START_EVENT = 'session_start';\nconst INACTIVE_RUNTIME_MESSAGE = 'The workflow runtime is no longer active.';\nconst CLEANUP_SCOPE = Symbol('doom-workflow-cleanup');\n\nconst DISPATCHER_TOOL_NAMES = new Set(['list_workflows', 'launch_workflow']);\nconst ROOT_SCOPED_TOOL_NAMES = new Set(['launch_workflow']);\nconst WORKFLOW_RUN_TOOL_NAME = 'workflow_run';\n\ntype ToolSchema = Parameters<ExtensionAPI['registerTool']>[0]['parameters'];\ntype CommandOptions = Parameters<ExtensionAPI['registerCommand']>[1];\ntype ShortcutOptions = Parameters<ExtensionAPI['registerShortcut']>[1];\ntype RuntimeScope = number | typeof CLEANUP_SCOPE;\ntype LifecycleHandler = (event: unknown, context: ExtensionContext) => unknown;\n\nexport interface WorkflowFence {\n tool<TSchema extends ToolSchema, TDetails>(\n tool: ToolDefinition<TSchema, TDetails>,\n ): ToolDefinition<TSchema, TDetails>;\n command(name: string, options: CommandOptions): readonly [string, CommandOptions];\n shortcut(\n shortcut: Parameters<ExtensionAPI['registerShortcut']>[0],\n options: ShortcutOptions,\n ): readonly [Parameters<ExtensionAPI['registerShortcut']>[0], ShortcutOptions];\n events(handlers: PiEventHandlers): PiEventHandlers;\n readonly pi: ExtensionAPI;\n readonly beginDisposal: () => void;\n readonly finishDisposal: () => void;\n readonly isCurrentInvocation: () => boolean;\n readonly runCleanup: <T>(operation: () => T) => T;\n}\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction withParentSession(ctx: ExtensionContext, parentSessionId: string): ExtensionContext {\n const sessionManager = new Proxy(ctx.sessionManager, {\n get(target, property) {\n if (property === 'getSessionId') return () => parentSessionId;\n return boundValue(target, property);\n },\n });\n return new Proxy(ctx, {\n get(target, property) {\n if (property === 'sessionManager') return sessionManager;\n return boundValue(target, property);\n },\n });\n}\n\n/**\n * A dispatcher child launches workflows for its root session and never runs one\n * itself, so `workflow_run` stays off its surface even if something registers it.\n */\nexport function dispatcherToolRestriction(): DoomToolRestriction {\n return (incoming) => incoming.filter((name) => name !== WORKFLOW_RUN_TOOL_NAME);\n}\n\n/** Restrict a dispatcher child to discovery and root-owned launch. */\nexport function dispatcherTools<TSchema extends ToolSchema, TDetails>(\n tools: readonly ToolDefinition<TSchema, TDetails>[],\n parentSessionId: string | undefined,\n environment: Readonly<Record<string, string | undefined>>,\n): ToolDefinition<TSchema, TDetails>[] {\n const rootSessionId = parentSessionId ? resolveRootSessionId(parentSessionId, environment) : undefined;\n return tools\n .filter((tool) => DISPATCHER_TOOL_NAMES.has(tool.name))\n .map((tool) => {\n if (!rootSessionId || !ROOT_SCOPED_TOOL_NAMES.has(tool.name)) return tool;\n return {\n ...tool,\n execute: (toolCallId, params, signal, onUpdate, ctx) =>\n tool.execute(toolCallId, params, signal, onUpdate, withParentSession(ctx, rootSessionId)),\n };\n });\n}\n\nexport function resolveDispatcherParentSession(\n environment: Readonly<Record<string, string | undefined>>,\n): string | undefined {\n const childContext = readChildProcessContext(environment);\n return childContext?.parentSessionId ?? (environment[SUBAGENT_PARENT_SESSION_ENV]?.trim() || undefined);\n}\n\nexport function isWorkflowDispatcherProcess(environment: Readonly<Record<string, string | undefined>>): boolean {\n const childContext = readChildProcessContext(environment);\n if (childContext?.mode === DISPATCHER_AGENT_NAME) return true;\n return (\n environment.PI_SUBAGENT_CHILD_AGENT === DISPATCHER_AGENT_NAME &&\n Boolean(environment[SUBAGENT_PARENT_SESSION_ENV]?.trim())\n );\n}\n\n/** Fence every callback retained by Pi after this package root is replaced. */\nexport function createWorkflowFence(host: ExtensionAPI, signal?: AbortSignal): WorkflowFence {\n const scope = new AsyncLocalStorage<RuntimeScope>();\n let active = true;\n let disposing = false;\n let generation = 0;\n let lastSessionStartEvent: unknown;\n let lastSessionStartContext: ExtensionContext | undefined;\n\n const isCurrentInvocation = (): boolean => {\n const currentScope = scope.getStore();\n if (!active || (signal?.aborted && currentScope !== CLEANUP_SCOPE)) return false;\n return currentScope === undefined || currentScope === CLEANUP_SCOPE || currentScope === generation;\n };\n const ownsContext = (contextGeneration: number): boolean => {\n const currentScope = scope.getStore();\n if (!active || (signal?.aborted && currentScope !== CLEANUP_SCOPE)) return false;\n return (\n currentScope === CLEANUP_SCOPE ||\n (contextGeneration === generation && (currentScope === undefined || currentScope === contextGeneration))\n );\n };\n\n const guardContext = <TContext extends ExtensionContext>(context: TContext, contextGeneration: number): TContext => {\n const guardedUi = new Proxy(context.ui, {\n get(target, property) {\n const value = Reflect.get(target, property, target);\n if (typeof value !== 'function') return value;\n return (...args: unknown[]): unknown => {\n if (ownsContext(contextGeneration)) return Reflect.apply(value, target, args);\n if (property === 'confirm') return Promise.resolve(false);\n if (property === 'custom' || property === 'editor' || property === 'input' || property === 'select') {\n return Promise.resolve(undefined);\n }\n return undefined;\n };\n },\n });\n return new Proxy(context, {\n get(target, property) {\n if (property === 'ui') return guardedUi;\n return boundValue(target, property);\n },\n }) as TContext;\n };\n\n const tool = <TSchema extends ToolSchema, TDetails>(\n definition: ToolDefinition<TSchema, TDetails>,\n ): ToolDefinition<TSchema, TDetails> => {\n return {\n ...definition,\n execute: async (toolCallId, params, signal, onUpdate, context) => {\n const executionGeneration = generation;\n if (!active || disposing || signal?.aborted) throw new Error(INACTIVE_RUNTIME_MESSAGE);\n const guardedContext = guardContext(context, executionGeneration);\n const guardedUpdate = onUpdate\n ? (update: Parameters<NonNullable<typeof onUpdate>>[0]): void => {\n if (active && executionGeneration === generation && !signal?.aborted) onUpdate(update);\n }\n : undefined;\n const result = await scope.run(executionGeneration, () =>\n definition.execute(toolCallId, params, signal, guardedUpdate, guardedContext),\n );\n if (!active || disposing || executionGeneration !== generation) throw new Error(INACTIVE_RUNTIME_MESSAGE);\n return result;\n },\n };\n };\n\n const command: WorkflowFence['command'] = (name, options) => {\n return [\n name,\n {\n ...options,\n handler: async (args, context) => {\n const executionGeneration = generation;\n if (!active || disposing) return;\n await scope.run(executionGeneration, () => options.handler(args, guardContext(context, executionGeneration)));\n },\n },\n ];\n };\n\n const shortcut: WorkflowFence['shortcut'] = (key, options) => {\n return [\n key,\n {\n ...options,\n handler: (context) => {\n const executionGeneration = generation;\n if (!active || disposing) return;\n return scope.run(executionGeneration, () => options.handler(guardContext(context, executionGeneration)));\n },\n },\n ];\n };\n\n const events = (handlers: PiEventHandlers): PiEventHandlers =>\n Object.fromEntries(\n Object.entries(handlers).map(([eventName, handler]) => [\n eventName,\n (event: unknown, context: ExtensionContext) => {\n if (!active || disposing) return undefined;\n if (\n eventName === SESSION_START_EVENT &&\n (event !== lastSessionStartEvent || context !== lastSessionStartContext)\n ) {\n generation += 1;\n lastSessionStartEvent = event;\n lastSessionStartContext = context;\n }\n const eventGeneration = generation;\n return scope.run(eventGeneration, () =>\n (handler as LifecycleHandler)(event, guardContext(context, eventGeneration)),\n );\n },\n ]),\n ) as PiEventHandlers;\n\n const guardedPi = new Proxy(host, {\n get(target, property) {\n if (property === 'sendMessage') {\n return (...args: Parameters<ExtensionAPI['sendMessage']>): void => {\n if (isCurrentInvocation()) target.sendMessage(...args);\n };\n }\n if (property === 'sendUserMessage') {\n return (...args: Parameters<ExtensionAPI['sendUserMessage']>): void => {\n if (isCurrentInvocation()) target.sendUserMessage(...args);\n };\n }\n if (property === 'exec') {\n return (...args: Parameters<ExtensionAPI['exec']>): ReturnType<ExtensionAPI['exec']> =>\n isCurrentInvocation()\n ? target.exec(...args)\n : Promise.resolve({ code: 1, killed: false, stderr: INACTIVE_RUNTIME_MESSAGE, stdout: '' });\n }\n return boundValue(target, property);\n },\n });\n\n return {\n pi: guardedPi,\n tool,\n command,\n shortcut,\n events,\n beginDisposal() {\n if (disposing || !active) return;\n disposing = true;\n generation += 1;\n },\n finishDisposal() {\n active = false;\n disposing = false;\n generation += 1;\n },\n isCurrentInvocation,\n runCleanup: (operation) => scope.run(CLEANUP_SCOPE, operation),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{AsyncLocalStorage as e}from"node:async_hooks";import{SUBAGENT_PARENT_SESSION_ENV as t,readChildProcessContext as n,resolveRootSessionId as r}from"@agimon-ai/doompi-core/child-process";const i=`agiflow-dispatcher`,a=`The workflow runtime is no longer active.`,o=Symbol(`doom-workflow-cleanup`),s=new Set([`list_workflows`,`launch_workflow`]),c=new Set([`launch_workflow`]);function l(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function u(e,t){let n=new Proxy(e.sessionManager,{get(e,n){return n===`getSessionId`?()=>t:l(e,n)}});return new Proxy(e,{get(e,t){return t===`sessionManager`?n:l(e,t)}})}function d(){return e=>e.filter(e=>e!==`workflow_run`)}function f(e,t,n){let i=t?r(t,n):void 0;return e.filter(e=>s.has(e.name)).map(e=>!i||!c.has(e.name)?e:{...e,execute:(t,n,r,a,o)=>e.execute(t,n,r,a,u(o,i))})}function p(e){return n(e)?.parentSessionId??(e[t]?.trim()||void 0)}function m(e){return n(e)?.mode===i||e.PI_SUBAGENT_CHILD_AGENT===i&&!!e[t]?.trim()}function h(t,n){let r=new e,i=!0,s=!1,c=0,u,d,f=()=>{let e=r.getStore();return!i||n?.aborted&&e!==o?!1:e===void 0||e===o||e===c},p=e=>{let t=r.getStore();return!i||n?.aborted&&t!==o?!1:t===o||e===c&&(t===void 0||t===e)},m=(e,t)=>{let n=new Proxy(e.ui,{get(e,n){let r=Reflect.get(e,n,e);return typeof r==`function`?(...i)=>{if(p(t))return Reflect.apply(r,e,i);if(n===`confirm`)return Promise.resolve(!1);if(n===`custom`||n===`editor`||n===`input`||n===`select`)return Promise.resolve(void 0)}:r}});return new Proxy(e,{get(e,t){return t===`ui`?n:l(e,t)}})};return{pi:new Proxy(t,{get(e,t){return t===`sendMessage`?(...t)=>{f()&&e.sendMessage(...t)}:t===`sendUserMessage`?(...t)=>{f()&&e.sendUserMessage(...t)}:t===`exec`?(...t)=>f()?e.exec(...t):Promise.resolve({code:1,killed:!1,stderr:a,stdout:``}):l(e,t)}}),tool:e=>({...e,execute:async(t,n,o,l,u)=>{let d=c;if(!i||s||o?.aborted)throw Error(a);let f=m(u,d),p=l?e=>{i&&d===c&&!o?.aborted&&l(e)}:void 0,h=await r.run(d,()=>e.execute(t,n,o,p,f));if(!i||s||d!==c)throw Error(a);return h}}),command:(e,t)=>[e,{...t,handler:async(e,n)=>{let a=c;i&&!s&&await r.run(a,()=>t.handler(e,m(n,a)))}}],shortcut:(e,t)=>[e,{...t,handler:e=>{let n=c;if(i&&!s)return r.run(n,()=>t.handler(m(e,n)))}}],events:e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,(n,a)=>{if(!i||s)return;e===`session_start`&&(n!==u||a!==d)&&(c+=1,u=n,d=a);let o=c;return r.run(o,()=>t(n,m(a,o)))}])),beginDisposal(){!s&&i&&(s=!0,c+=1)},finishDisposal(){i=!1,s=!1,c+=1},isCurrentInvocation:f,runCleanup:e=>r.run(o,e)}}export{h as createWorkflowFence,d as dispatcherToolRestriction,f as dispatcherTools,m as isWorkflowDispatcherProcess,p as resolveDispatcherParentSession};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowFence/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport {\n readChildProcessContext,\n resolveRootSessionId,\n SUBAGENT_PARENT_SESSION_ENV,\n} from '@agimon-ai/doompi-core/child-process';\nimport type { PiEventHandlers } from '@agimon-ai/doompi-core/pi-extension';\nimport type { DoomToolRestriction } from '@agimon-ai/doompi-core/tool-surface';\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nconst DISPATCHER_AGENT_NAME = 'agiflow-dispatcher';\nconst SESSION_START_EVENT = 'session_start';\nconst INACTIVE_RUNTIME_MESSAGE = 'The workflow runtime is no longer active.';\nconst CLEANUP_SCOPE = Symbol('doom-workflow-cleanup');\n\nconst DISPATCHER_TOOL_NAMES = new Set(['list_workflows', 'launch_workflow']);\nconst ROOT_SCOPED_TOOL_NAMES = new Set(['launch_workflow']);\nconst WORKFLOW_RUN_TOOL_NAME = 'workflow_run';\n\ntype ToolSchema = Parameters<ExtensionAPI['registerTool']>[0]['parameters'];\ntype CommandOptions = Parameters<ExtensionAPI['registerCommand']>[1];\ntype ShortcutOptions = Parameters<ExtensionAPI['registerShortcut']>[1];\ntype RuntimeScope = number | typeof CLEANUP_SCOPE;\ntype LifecycleHandler = (event: unknown, context: ExtensionContext) => unknown;\n\nexport interface WorkflowFence {\n tool<TSchema extends ToolSchema, TDetails>(\n tool: ToolDefinition<TSchema, TDetails>,\n ): ToolDefinition<TSchema, TDetails>;\n command(name: string, options: CommandOptions): readonly [string, CommandOptions];\n shortcut(\n shortcut: Parameters<ExtensionAPI['registerShortcut']>[0],\n options: ShortcutOptions,\n ): readonly [Parameters<ExtensionAPI['registerShortcut']>[0], ShortcutOptions];\n events(handlers: PiEventHandlers): PiEventHandlers;\n readonly pi: ExtensionAPI;\n readonly beginDisposal: () => void;\n readonly finishDisposal: () => void;\n readonly isCurrentInvocation: () => boolean;\n readonly runCleanup: <T>(operation: () => T) => T;\n}\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction withParentSession(ctx: ExtensionContext, parentSessionId: string): ExtensionContext {\n const sessionManager = new Proxy(ctx.sessionManager, {\n get(target, property) {\n if (property === 'getSessionId') return () => parentSessionId;\n return boundValue(target, property);\n },\n });\n return new Proxy(ctx, {\n get(target, property) {\n if (property === 'sessionManager') return sessionManager;\n return boundValue(target, property);\n },\n });\n}\n\n/**\n * A dispatcher child launches workflows for its root session and never runs one\n * itself, so `workflow_run` stays off its surface even if something registers it.\n */\nexport function dispatcherToolRestriction(): DoomToolRestriction {\n return (incoming) => incoming.filter((name) => name !== WORKFLOW_RUN_TOOL_NAME);\n}\n\n/** Restrict a dispatcher child to discovery and root-owned launch. */\nexport function dispatcherTools<TSchema extends ToolSchema, TDetails>(\n tools: readonly ToolDefinition<TSchema, TDetails>[],\n parentSessionId: string | undefined,\n environment: Readonly<Record<string, string | undefined>>,\n): ToolDefinition<TSchema, TDetails>[] {\n const rootSessionId = parentSessionId ? resolveRootSessionId(parentSessionId, environment) : undefined;\n return tools\n .filter((tool) => DISPATCHER_TOOL_NAMES.has(tool.name))\n .map((tool) => {\n if (!rootSessionId || !ROOT_SCOPED_TOOL_NAMES.has(tool.name)) return tool;\n return {\n ...tool,\n execute: (toolCallId, params, signal, onUpdate, ctx) =>\n tool.execute(toolCallId, params, signal, onUpdate, withParentSession(ctx, rootSessionId)),\n };\n });\n}\n\nexport function resolveDispatcherParentSession(\n environment: Readonly<Record<string, string | undefined>>,\n): string | undefined {\n const childContext = readChildProcessContext(environment);\n return childContext?.parentSessionId ?? (environment[SUBAGENT_PARENT_SESSION_ENV]?.trim() || undefined);\n}\n\nexport function isWorkflowDispatcherProcess(environment: Readonly<Record<string, string | undefined>>): boolean {\n const childContext = readChildProcessContext(environment);\n if (childContext?.mode === DISPATCHER_AGENT_NAME) return true;\n return (\n environment.PI_SUBAGENT_CHILD_AGENT === DISPATCHER_AGENT_NAME &&\n Boolean(environment[SUBAGENT_PARENT_SESSION_ENV]?.trim())\n );\n}\n\n/** Fence every callback retained by Pi after this package root is replaced. */\nexport function createWorkflowFence(host: ExtensionAPI, signal?: AbortSignal): WorkflowFence {\n const scope = new AsyncLocalStorage<RuntimeScope>();\n let active = true;\n let disposing = false;\n let generation = 0;\n let lastSessionStartEvent: unknown;\n let lastSessionStartContext: ExtensionContext | undefined;\n\n const isCurrentInvocation = (): boolean => {\n const currentScope = scope.getStore();\n if (!active || (signal?.aborted && currentScope !== CLEANUP_SCOPE)) return false;\n return currentScope === undefined || currentScope === CLEANUP_SCOPE || currentScope === generation;\n };\n const ownsContext = (contextGeneration: number): boolean => {\n const currentScope = scope.getStore();\n if (!active || (signal?.aborted && currentScope !== CLEANUP_SCOPE)) return false;\n return (\n currentScope === CLEANUP_SCOPE ||\n (contextGeneration === generation && (currentScope === undefined || currentScope === contextGeneration))\n );\n };\n\n const guardContext = <TContext extends ExtensionContext>(context: TContext, contextGeneration: number): TContext => {\n const guardedUi = new Proxy(context.ui, {\n get(target, property) {\n const value = Reflect.get(target, property, target);\n if (typeof value !== 'function') return value;\n return (...args: unknown[]): unknown => {\n if (ownsContext(contextGeneration)) return Reflect.apply(value, target, args);\n if (property === 'confirm') return Promise.resolve(false);\n if (property === 'custom' || property === 'editor' || property === 'input' || property === 'select') {\n return Promise.resolve(undefined);\n }\n return undefined;\n };\n },\n });\n return new Proxy(context, {\n get(target, property) {\n if (property === 'ui') return guardedUi;\n return boundValue(target, property);\n },\n }) as TContext;\n };\n\n const tool = <TSchema extends ToolSchema, TDetails>(\n definition: ToolDefinition<TSchema, TDetails>,\n ): ToolDefinition<TSchema, TDetails> => {\n return {\n ...definition,\n execute: async (toolCallId, params, signal, onUpdate, context) => {\n const executionGeneration = generation;\n if (!active || disposing || signal?.aborted) throw new Error(INACTIVE_RUNTIME_MESSAGE);\n const guardedContext = guardContext(context, executionGeneration);\n const guardedUpdate = onUpdate\n ? (update: Parameters<NonNullable<typeof onUpdate>>[0]): void => {\n if (active && executionGeneration === generation && !signal?.aborted) onUpdate(update);\n }\n : undefined;\n const result = await scope.run(executionGeneration, () =>\n definition.execute(toolCallId, params, signal, guardedUpdate, guardedContext),\n );\n if (!active || disposing || executionGeneration !== generation) throw new Error(INACTIVE_RUNTIME_MESSAGE);\n return result;\n },\n };\n };\n\n const command: WorkflowFence['command'] = (name, options) => {\n return [\n name,\n {\n ...options,\n handler: async (args, context) => {\n const executionGeneration = generation;\n if (!active || disposing) return;\n await scope.run(executionGeneration, () => options.handler(args, guardContext(context, executionGeneration)));\n },\n },\n ];\n };\n\n const shortcut: WorkflowFence['shortcut'] = (key, options) => {\n return [\n key,\n {\n ...options,\n handler: (context) => {\n const executionGeneration = generation;\n if (!active || disposing) return;\n return scope.run(executionGeneration, () => options.handler(guardContext(context, executionGeneration)));\n },\n },\n ];\n };\n\n const events = (handlers: PiEventHandlers): PiEventHandlers =>\n Object.fromEntries(\n Object.entries(handlers).map(([eventName, handler]) => [\n eventName,\n (event: unknown, context: ExtensionContext) => {\n if (!active || disposing) return undefined;\n if (\n eventName === SESSION_START_EVENT &&\n (event !== lastSessionStartEvent || context !== lastSessionStartContext)\n ) {\n generation += 1;\n lastSessionStartEvent = event;\n lastSessionStartContext = context;\n }\n const eventGeneration = generation;\n return scope.run(eventGeneration, () =>\n (handler as LifecycleHandler)(event, guardContext(context, eventGeneration)),\n );\n },\n ]),\n ) as PiEventHandlers;\n\n const guardedPi = new Proxy(host, {\n get(target, property) {\n if (property === 'sendMessage') {\n return (...args: Parameters<ExtensionAPI['sendMessage']>): void => {\n if (isCurrentInvocation()) target.sendMessage(...args);\n };\n }\n if (property === 'sendUserMessage') {\n return (...args: Parameters<ExtensionAPI['sendUserMessage']>): void => {\n if (isCurrentInvocation()) target.sendUserMessage(...args);\n };\n }\n if (property === 'exec') {\n return (...args: Parameters<ExtensionAPI['exec']>): ReturnType<ExtensionAPI['exec']> =>\n isCurrentInvocation()\n ? target.exec(...args)\n : Promise.resolve({ code: 1, killed: false, stderr: INACTIVE_RUNTIME_MESSAGE, stdout: '' });\n }\n return boundValue(target, property);\n },\n });\n\n return {\n pi: guardedPi,\n tool,\n command,\n shortcut,\n events,\n beginDisposal() {\n if (disposing || !active) return;\n disposing = true;\n generation += 1;\n },\n finishDisposal() {\n active = false;\n disposing = false;\n generation += 1;\n },\n isCurrentInvocation,\n runCleanup: (operation) => scope.run(CLEANUP_SCOPE, operation),\n };\n}\n"],"mappings":"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"}
|
package/dist/{services/workflowLaunchCommand.cjs → src/services/workflowLaunchCommand/index.cjs}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
const e=/^([A-Za-z_][\w.-]*)=(.*)$/s;function t(e){return`error`in e}function n(e){return e.length>1&&e.startsWith(`"`)&&e.endsWith(`"`)?e.slice(1,-1):e}function r(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;let r=n,i=!1;for(;n<e.length&&(i||!/\s/.test(e[n]));)e[n]===`"`&&(i=!i),n+=1;t.push({text:e.slice(r,n),start:r})}return t}function i(t){let i=r(t.trim()),a=i[0];if(a===void 0)return{error:`Usage: /workflow-launch <workflow> [key=value …] [prompt]`};let o={},s,c=1;for(;c<i.length;c+=1){let t=i[c],r=e.exec(t.text);if(!r)break;let a=r[1],l=n(r[2]);a===`runner`?s=l:o[a]=l}let l=i[c],u=l===void 0?void 0:t.trim().slice(l.start).trim();return{workflow:n(a.text),...s===void 0?{}:{runner:s},inputs:o,...u===void 0||u===``?{}:{prompt:u}}}function a(e,t){let n=t.trim(),r=n.toLocaleLowerCase();return e.find(e=>e.name.toLocaleLowerCase()===r)??e.find(e=>e.relativePath===n||e.path===n)??e.find(e=>e.relativePath.endsWith(`/${n}`)||e.path.endsWith(`/${n}`))}function o(e,t){let n=[];e.triggers.includes(`user_prompt`)&&(t.prompt??``)===``&&n.push(`This workflow is triggered by a prompt, so it needs one.`);let r=e.inputs.filter(e=>e.required===!0&&(t.inputs[e.name]??``)===``).map(e=>e.name);r.length>0&&n.push(`Missing required input${r.length>1?`s`:``}: ${r.join(`, `)}.`);for(let r of e.inputs){let e=t.inputs[r.name];e===void 0||r.options===void 0||r.options.includes(e)||n.push(`Input ${r.name} must be one of: ${r.options.join(`, `)}.`)}let i=e.runners;return t.runner!==void 0&&i!==void 0&&!i.includes(t.runner)&&n.push(i.length===0?`This workflow declares no runner its steps agree on.`:`Runner ${t.runner} is not one this workflow declares: ${i.join(`, `)}.`),n}exports.isLaunchParseFailure=t,exports.parseWorkflowLaunchCommand=i,exports.resolveWorkflowEntry=a,exports.validateWorkflowLaunch=o;
|
|
2
|
-
//# sourceMappingURL=
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/workflowLaunchCommand/index.ts"],"sourcesContent":["/**\n * The `/workflow-launch` line, parsed.\n *\n * WHY A VERB AT ALL:\n * A browser can only send a session a prompt frame, so every cockpit action\n * that has to run inside the session travels as a slash line, the way the\n * agent catalog sends `/run`. The workflow tools answer to the agent, not to a\n * page, and the leader board answers to a keyboard, so neither of them can be\n * what the launch dialog sends.\n *\n * THE SHAPE:\n * /workflow-launch <workflow> [key=value ...] [prompt words]\n *\n * Key-value pairs come first and stop at the first token that is not one, so\n * everything after them is the prompt exactly as it was typed, including any\n * `=` inside it. `runner` is the one reserved key; the rest are the workflow's\n * own `workflow_dispatch` inputs. Values may be double quoted to hold spaces.\n */\n\n/** The reserved key: which runner map entry the run executes with. */\nconst RUNNER_KEY = 'runner';\nconst KEY_VALUE = /^([A-Za-z_][\\w.-]*)=(.*)$/s;\n\nexport interface ParsedWorkflowLaunch {\n /** The workflow's name or its path, as typed; resolution belongs to the caller. */\n workflow: string;\n runner?: string;\n inputs: Record<string, string>;\n prompt?: string;\n}\n\nexport interface WorkflowLaunchParseFailure {\n error: string;\n}\n\nexport function isLaunchParseFailure(\n value: ParsedWorkflowLaunch | WorkflowLaunchParseFailure,\n): value is WorkflowLaunchParseFailure {\n return 'error' in value;\n}\n\n/** Strips one layer of surrounding double quotes, which is how a value holds spaces. */\nfunction unquote(value: string): string {\n return value.length > 1 && value.startsWith('\"') && value.endsWith('\"') ? value.slice(1, -1) : value;\n}\n\n/**\n * Splits on whitespace, keeping double-quoted runs together.\n *\n * Returns each token with the offset it started at, so the caller can take the\n * rest of the line verbatim rather than rebuilding it from tokens and losing\n * the spacing a prompt was written with.\n */\nfunction tokenize(line: string): { text: string; start: number }[] {\n const tokens: { text: string; start: number }[] = [];\n let index = 0;\n while (index < line.length) {\n while (index < line.length && /\\s/.test(line[index] as string)) index += 1;\n if (index >= line.length) break;\n const start = index;\n let quoted = false;\n while (index < line.length && (quoted || !/\\s/.test(line[index] as string))) {\n if (line[index] === '\"') quoted = !quoted;\n index += 1;\n }\n tokens.push({ text: line.slice(start, index), start });\n }\n return tokens;\n}\n\nexport function parseWorkflowLaunchCommand(args: string): ParsedWorkflowLaunch | WorkflowLaunchParseFailure {\n const tokens = tokenize(args.trim());\n const first = tokens[0];\n if (first === undefined) return { error: 'Usage: /workflow-launch <workflow> [key=value …] [prompt]' };\n\n const inputs: Record<string, string> = {};\n let runner: string | undefined;\n let index = 1;\n for (; index < tokens.length; index += 1) {\n const token = tokens[index] as { text: string; start: number };\n const match = KEY_VALUE.exec(token.text);\n if (!match) break;\n const key = match[1] as string;\n const value = unquote(match[2] as string);\n if (key === RUNNER_KEY) runner = value;\n else inputs[key] = value;\n }\n\n const rest = tokens[index];\n const prompt = rest === undefined ? undefined : args.trim().slice(rest.start).trim();\n return {\n workflow: unquote(first.text),\n ...(runner === undefined ? {} : { runner }),\n inputs,\n ...(prompt === undefined || prompt === '' ? {} : { prompt }),\n };\n}\n\n/** The line the cockpit sends, built from what its dialog collected. */\nexport function workflowLaunchCommand(request: ParsedWorkflowLaunch): string {\n const quote = (value: string): string => (/\\s/.test(value) ? `\"${value}\"` : value);\n const pairs = [\n ...(request.runner === undefined ? [] : [`${RUNNER_KEY}=${quote(request.runner)}`]),\n ...Object.entries(request.inputs).map(([key, value]) => `${key}=${quote(value)}`),\n ];\n const prompt = request.prompt?.trim();\n return ['/workflow-launch', quote(request.workflow), ...pairs, ...(prompt ? [prompt] : [])].join(' ');\n}\n\n/** One catalog row, as much of it as resolution needs. */\nexport interface LaunchResolvableWorkflow {\n name: string;\n path: string;\n relativePath: string;\n}\n\n/**\n * The workflow a token names: its name, its path in the repository, or the\n * tail of that path. Case-insensitive on the name, because the catalog shows\n * names as their author capitalised them and nobody types that back exactly.\n */\nexport function resolveWorkflowEntry<T extends LaunchResolvableWorkflow>(\n entries: readonly T[],\n token: string,\n): T | undefined {\n const needle = token.trim();\n const lower = needle.toLocaleLowerCase();\n return (\n entries.find((entry) => entry.name.toLocaleLowerCase() === lower) ??\n entries.find((entry) => entry.relativePath === needle || entry.path === needle) ??\n entries.find((entry) => entry.relativePath.endsWith(`/${needle}`) || entry.path.endsWith(`/${needle}`))\n );\n}\n\n/** What a workflow declares, as much of it as a launch has to satisfy. */\nexport interface LaunchRequirements {\n triggers: readonly string[];\n inputs: readonly { name: string; required?: boolean; options?: readonly string[] }[];\n /** Absent means the workflow names no runner map, so any runner will do. */\n runners?: readonly string[];\n}\n\n/** The trigger that makes a prompt mandatory: the run waits for one otherwise. */\nconst USER_PROMPT_TRIGGER = 'user_prompt';\n\n/**\n * Everything wrong with a launch, before it is started.\n *\n * Checked here rather than left to the engine because the failure would\n * otherwise be a run that starts and then waits forever for terminal input, or\n * one that dies on a runner its steps never declared. Both read as the launch\n * having worked.\n */\nexport function validateWorkflowLaunch(\n requirements: LaunchRequirements,\n parsed: Pick<ParsedWorkflowLaunch, 'inputs' | 'prompt' | 'runner'>,\n): string[] {\n const problems: string[] = [];\n if (requirements.triggers.includes(USER_PROMPT_TRIGGER) && (parsed.prompt ?? '') === '') {\n problems.push('This workflow is triggered by a prompt, so it needs one.');\n }\n const missing = requirements.inputs\n .filter((input) => input.required === true && (parsed.inputs[input.name] ?? '') === '')\n .map((input) => input.name);\n if (missing.length > 0)\n problems.push(`Missing required input${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}.`);\n for (const input of requirements.inputs) {\n const value = parsed.inputs[input.name];\n if (value === undefined || input.options === undefined || input.options.includes(value)) continue;\n problems.push(`Input ${input.name} must be one of: ${input.options.join(', ')}.`);\n }\n const declared = requirements.runners;\n if (parsed.runner !== undefined && declared !== undefined && !declared.includes(parsed.runner)) {\n problems.push(\n declared.length === 0\n ? 'This workflow declares no runner its steps agree on.'\n : `Runner ${parsed.runner} is not one this workflow declares: ${declared.join(', ')}.`,\n );\n }\n return problems;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/services/workflowLaunchCommand/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The `/workflow-launch` line, parsed.
|
|
4
|
+
*
|
|
5
|
+
* WHY A VERB AT ALL:
|
|
6
|
+
* A browser can only send a session a prompt frame, so every cockpit action
|
|
7
|
+
* that has to run inside the session travels as a slash line, the way the
|
|
8
|
+
* agent catalog sends `/run`. The workflow tools answer to the agent, not to a
|
|
9
|
+
* page, and the leader board answers to a keyboard, so neither of them can be
|
|
10
|
+
* what the launch dialog sends.
|
|
11
|
+
*
|
|
12
|
+
* THE SHAPE:
|
|
13
|
+
* /workflow-launch <workflow> [key=value ...] [prompt words]
|
|
14
|
+
*
|
|
15
|
+
* Key-value pairs come first and stop at the first token that is not one, so
|
|
16
|
+
* everything after them is the prompt exactly as it was typed, including any
|
|
17
|
+
* `=` inside it. `runner` is the one reserved key; the rest are the workflow's
|
|
18
|
+
* own `workflow_dispatch` inputs. Values may be double quoted to hold spaces.
|
|
19
|
+
*/
|
|
20
|
+
export interface ParsedWorkflowLaunch {
|
|
21
|
+
/** The workflow's name or its path, as typed; resolution belongs to the caller. */
|
|
22
|
+
workflow: string;
|
|
23
|
+
runner?: string;
|
|
24
|
+
inputs: Record<string, string>;
|
|
25
|
+
prompt?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface WorkflowLaunchParseFailure {
|
|
28
|
+
error: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function parseWorkflowLaunchCommand(args: string): ParsedWorkflowLaunch | WorkflowLaunchParseFailure;
|
|
31
|
+
/** What a workflow declares, as much of it as a launch has to satisfy. */
|
|
32
|
+
export interface LaunchRequirements {
|
|
33
|
+
triggers: readonly string[];
|
|
34
|
+
inputs: readonly {
|
|
35
|
+
name: string;
|
|
36
|
+
required?: boolean;
|
|
37
|
+
options?: readonly string[];
|
|
38
|
+
}[];
|
|
39
|
+
/** Absent means the workflow names no runner map, so any runner will do. */
|
|
40
|
+
runners?: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Everything wrong with a launch, before it is started.
|
|
44
|
+
*
|
|
45
|
+
* Checked here rather than left to the engine because the failure would
|
|
46
|
+
* otherwise be a run that starts and then waits forever for terminal input, or
|
|
47
|
+
* one that dies on a runner its steps never declared. Both read as the launch
|
|
48
|
+
* having worked.
|
|
49
|
+
*/
|
|
50
|
+
export declare function validateWorkflowLaunch(requirements: LaunchRequirements, parsed: Pick<ParsedWorkflowLaunch, 'inputs' | 'prompt' | 'runner'>): string[];
|
|
51
|
+
//#endregion
|
|
52
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/workflowLaunchCommand/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBAuBiB;;EAEf;EACA;EACA,QAAQ;EACR;;iBAGe;EACf;;wBAsCc,2BAA2B,eAAe,uBAAuB;;iBAiEhE;EACf;EACA;IAAmB;IAAc;IAAoB;;;EAErD;;;;;;;;;;wBAcc,uBACd,cAAc,oBACd,QAAQ,KAAK"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/services/workflowLaunchCommand/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The `/workflow-launch` line, parsed.
|
|
4
|
+
*
|
|
5
|
+
* WHY A VERB AT ALL:
|
|
6
|
+
* A browser can only send a session a prompt frame, so every cockpit action
|
|
7
|
+
* that has to run inside the session travels as a slash line, the way the
|
|
8
|
+
* agent catalog sends `/run`. The workflow tools answer to the agent, not to a
|
|
9
|
+
* page, and the leader board answers to a keyboard, so neither of them can be
|
|
10
|
+
* what the launch dialog sends.
|
|
11
|
+
*
|
|
12
|
+
* THE SHAPE:
|
|
13
|
+
* /workflow-launch <workflow> [key=value ...] [prompt words]
|
|
14
|
+
*
|
|
15
|
+
* Key-value pairs come first and stop at the first token that is not one, so
|
|
16
|
+
* everything after them is the prompt exactly as it was typed, including any
|
|
17
|
+
* `=` inside it. `runner` is the one reserved key; the rest are the workflow's
|
|
18
|
+
* own `workflow_dispatch` inputs. Values may be double quoted to hold spaces.
|
|
19
|
+
*/
|
|
20
|
+
export interface ParsedWorkflowLaunch {
|
|
21
|
+
/** The workflow's name or its path, as typed; resolution belongs to the caller. */
|
|
22
|
+
workflow: string;
|
|
23
|
+
runner?: string;
|
|
24
|
+
inputs: Record<string, string>;
|
|
25
|
+
prompt?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface WorkflowLaunchParseFailure {
|
|
28
|
+
error: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function parseWorkflowLaunchCommand(args: string): ParsedWorkflowLaunch | WorkflowLaunchParseFailure;
|
|
31
|
+
/** What a workflow declares, as much of it as a launch has to satisfy. */
|
|
32
|
+
export interface LaunchRequirements {
|
|
33
|
+
triggers: readonly string[];
|
|
34
|
+
inputs: readonly {
|
|
35
|
+
name: string;
|
|
36
|
+
required?: boolean;
|
|
37
|
+
options?: readonly string[];
|
|
38
|
+
}[];
|
|
39
|
+
/** Absent means the workflow names no runner map, so any runner will do. */
|
|
40
|
+
runners?: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Everything wrong with a launch, before it is started.
|
|
44
|
+
*
|
|
45
|
+
* Checked here rather than left to the engine because the failure would
|
|
46
|
+
* otherwise be a run that starts and then waits forever for terminal input, or
|
|
47
|
+
* one that dies on a runner its steps never declared. Both read as the launch
|
|
48
|
+
* having worked.
|
|
49
|
+
*/
|
|
50
|
+
export declare function validateWorkflowLaunch(requirements: LaunchRequirements, parsed: Pick<ParsedWorkflowLaunch, 'inputs' | 'prompt' | 'runner'>): string[];
|
|
51
|
+
//#endregion
|
|
52
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/workflowLaunchCommand/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBAuBiB;;EAEf;EACA;EACA,QAAQ;EACR;;iBAGe;EACf;;wBAsCc,2BAA2B,eAAe,uBAAuB;;iBAiEhE;EACf;EACA;IAAmB;IAAc;IAAoB;;;EAErD;;;;;;;;;;wBAcc,uBACd,cAAc,oBACd,QAAQ,KAAK"}
|
package/dist/{services/workflowLaunchCommand.mjs → src/services/workflowLaunchCommand/index.mjs}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
const e=/^([A-Za-z_][\w.-]*)=(.*)$/s;function t(e){return`error`in e}function n(e){return e.length>1&&e.startsWith(`"`)&&e.endsWith(`"`)?e.slice(1,-1):e}function r(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;let r=n,i=!1;for(;n<e.length&&(i||!/\s/.test(e[n]));)e[n]===`"`&&(i=!i),n+=1;t.push({text:e.slice(r,n),start:r})}return t}function i(t){let i=r(t.trim()),a=i[0];if(a===void 0)return{error:`Usage: /workflow-launch <workflow> [key=value …] [prompt]`};let o={},s,c=1;for(;c<i.length;c+=1){let t=i[c],r=e.exec(t.text);if(!r)break;let a=r[1],l=n(r[2]);a===`runner`?s=l:o[a]=l}let l=i[c],u=l===void 0?void 0:t.trim().slice(l.start).trim();return{workflow:n(a.text),...s===void 0?{}:{runner:s},inputs:o,...u===void 0||u===``?{}:{prompt:u}}}function a(e,t){let n=t.trim(),r=n.toLocaleLowerCase();return e.find(e=>e.name.toLocaleLowerCase()===r)??e.find(e=>e.relativePath===n||e.path===n)??e.find(e=>e.relativePath.endsWith(`/${n}`)||e.path.endsWith(`/${n}`))}function o(e,t){let n=[];e.triggers.includes(`user_prompt`)&&(t.prompt??``)===``&&n.push(`This workflow is triggered by a prompt, so it needs one.`);let r=e.inputs.filter(e=>e.required===!0&&(t.inputs[e.name]??``)===``).map(e=>e.name);r.length>0&&n.push(`Missing required input${r.length>1?`s`:``}: ${r.join(`, `)}.`);for(let r of e.inputs){let e=t.inputs[r.name];e===void 0||r.options===void 0||r.options.includes(e)||n.push(`Input ${r.name} must be one of: ${r.options.join(`, `)}.`)}let i=e.runners;return t.runner!==void 0&&i!==void 0&&!i.includes(t.runner)&&n.push(i.length===0?`This workflow declares no runner its steps agree on.`:`Runner ${t.runner} is not one this workflow declares: ${i.join(`, `)}.`),n}export{t as isLaunchParseFailure,i as parseWorkflowLaunchCommand,a as resolveWorkflowEntry,o as validateWorkflowLaunch};
|
|
2
|
-
//# sourceMappingURL=
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowLaunchCommand/index.ts"],"sourcesContent":["/**\n * The `/workflow-launch` line, parsed.\n *\n * WHY A VERB AT ALL:\n * A browser can only send a session a prompt frame, so every cockpit action\n * that has to run inside the session travels as a slash line, the way the\n * agent catalog sends `/run`. The workflow tools answer to the agent, not to a\n * page, and the leader board answers to a keyboard, so neither of them can be\n * what the launch dialog sends.\n *\n * THE SHAPE:\n * /workflow-launch <workflow> [key=value ...] [prompt words]\n *\n * Key-value pairs come first and stop at the first token that is not one, so\n * everything after them is the prompt exactly as it was typed, including any\n * `=` inside it. `runner` is the one reserved key; the rest are the workflow's\n * own `workflow_dispatch` inputs. Values may be double quoted to hold spaces.\n */\n\n/** The reserved key: which runner map entry the run executes with. */\nconst RUNNER_KEY = 'runner';\nconst KEY_VALUE = /^([A-Za-z_][\\w.-]*)=(.*)$/s;\n\nexport interface ParsedWorkflowLaunch {\n /** The workflow's name or its path, as typed; resolution belongs to the caller. */\n workflow: string;\n runner?: string;\n inputs: Record<string, string>;\n prompt?: string;\n}\n\nexport interface WorkflowLaunchParseFailure {\n error: string;\n}\n\nexport function isLaunchParseFailure(\n value: ParsedWorkflowLaunch | WorkflowLaunchParseFailure,\n): value is WorkflowLaunchParseFailure {\n return 'error' in value;\n}\n\n/** Strips one layer of surrounding double quotes, which is how a value holds spaces. */\nfunction unquote(value: string): string {\n return value.length > 1 && value.startsWith('\"') && value.endsWith('\"') ? value.slice(1, -1) : value;\n}\n\n/**\n * Splits on whitespace, keeping double-quoted runs together.\n *\n * Returns each token with the offset it started at, so the caller can take the\n * rest of the line verbatim rather than rebuilding it from tokens and losing\n * the spacing a prompt was written with.\n */\nfunction tokenize(line: string): { text: string; start: number }[] {\n const tokens: { text: string; start: number }[] = [];\n let index = 0;\n while (index < line.length) {\n while (index < line.length && /\\s/.test(line[index] as string)) index += 1;\n if (index >= line.length) break;\n const start = index;\n let quoted = false;\n while (index < line.length && (quoted || !/\\s/.test(line[index] as string))) {\n if (line[index] === '\"') quoted = !quoted;\n index += 1;\n }\n tokens.push({ text: line.slice(start, index), start });\n }\n return tokens;\n}\n\nexport function parseWorkflowLaunchCommand(args: string): ParsedWorkflowLaunch | WorkflowLaunchParseFailure {\n const tokens = tokenize(args.trim());\n const first = tokens[0];\n if (first === undefined) return { error: 'Usage: /workflow-launch <workflow> [key=value …] [prompt]' };\n\n const inputs: Record<string, string> = {};\n let runner: string | undefined;\n let index = 1;\n for (; index < tokens.length; index += 1) {\n const token = tokens[index] as { text: string; start: number };\n const match = KEY_VALUE.exec(token.text);\n if (!match) break;\n const key = match[1] as string;\n const value = unquote(match[2] as string);\n if (key === RUNNER_KEY) runner = value;\n else inputs[key] = value;\n }\n\n const rest = tokens[index];\n const prompt = rest === undefined ? undefined : args.trim().slice(rest.start).trim();\n return {\n workflow: unquote(first.text),\n ...(runner === undefined ? {} : { runner }),\n inputs,\n ...(prompt === undefined || prompt === '' ? {} : { prompt }),\n };\n}\n\n/** The line the cockpit sends, built from what its dialog collected. */\nexport function workflowLaunchCommand(request: ParsedWorkflowLaunch): string {\n const quote = (value: string): string => (/\\s/.test(value) ? `\"${value}\"` : value);\n const pairs = [\n ...(request.runner === undefined ? [] : [`${RUNNER_KEY}=${quote(request.runner)}`]),\n ...Object.entries(request.inputs).map(([key, value]) => `${key}=${quote(value)}`),\n ];\n const prompt = request.prompt?.trim();\n return ['/workflow-launch', quote(request.workflow), ...pairs, ...(prompt ? [prompt] : [])].join(' ');\n}\n\n/** One catalog row, as much of it as resolution needs. */\nexport interface LaunchResolvableWorkflow {\n name: string;\n path: string;\n relativePath: string;\n}\n\n/**\n * The workflow a token names: its name, its path in the repository, or the\n * tail of that path. Case-insensitive on the name, because the catalog shows\n * names as their author capitalised them and nobody types that back exactly.\n */\nexport function resolveWorkflowEntry<T extends LaunchResolvableWorkflow>(\n entries: readonly T[],\n token: string,\n): T | undefined {\n const needle = token.trim();\n const lower = needle.toLocaleLowerCase();\n return (\n entries.find((entry) => entry.name.toLocaleLowerCase() === lower) ??\n entries.find((entry) => entry.relativePath === needle || entry.path === needle) ??\n entries.find((entry) => entry.relativePath.endsWith(`/${needle}`) || entry.path.endsWith(`/${needle}`))\n );\n}\n\n/** What a workflow declares, as much of it as a launch has to satisfy. */\nexport interface LaunchRequirements {\n triggers: readonly string[];\n inputs: readonly { name: string; required?: boolean; options?: readonly string[] }[];\n /** Absent means the workflow names no runner map, so any runner will do. */\n runners?: readonly string[];\n}\n\n/** The trigger that makes a prompt mandatory: the run waits for one otherwise. */\nconst USER_PROMPT_TRIGGER = 'user_prompt';\n\n/**\n * Everything wrong with a launch, before it is started.\n *\n * Checked here rather than left to the engine because the failure would\n * otherwise be a run that starts and then waits forever for terminal input, or\n * one that dies on a runner its steps never declared. Both read as the launch\n * having worked.\n */\nexport function validateWorkflowLaunch(\n requirements: LaunchRequirements,\n parsed: Pick<ParsedWorkflowLaunch, 'inputs' | 'prompt' | 'runner'>,\n): string[] {\n const problems: string[] = [];\n if (requirements.triggers.includes(USER_PROMPT_TRIGGER) && (parsed.prompt ?? '') === '') {\n problems.push('This workflow is triggered by a prompt, so it needs one.');\n }\n const missing = requirements.inputs\n .filter((input) => input.required === true && (parsed.inputs[input.name] ?? '') === '')\n .map((input) => input.name);\n if (missing.length > 0)\n problems.push(`Missing required input${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}.`);\n for (const input of requirements.inputs) {\n const value = parsed.inputs[input.name];\n if (value === undefined || input.options === undefined || input.options.includes(value)) continue;\n problems.push(`Input ${input.name} must be one of: ${input.options.join(', ')}.`);\n }\n const declared = requirements.runners;\n if (parsed.runner !== undefined && declared !== undefined && !declared.includes(parsed.runner)) {\n problems.push(\n declared.length === 0\n ? 'This workflow declares no runner its steps agree on.'\n : `Runner ${parsed.runner} is not one this workflow declares: ${declared.join(', ')}.`,\n );\n }\n return problems;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../schemas/workflowPi.cjs"),t=require("../piToolBridge/index.cjs");let n=require("node:path");function r(e){return e&&typeof e==`object`&&!Array.isArray(e)?{...e}:{}}function i(e){return r(e.on)}function a(t){let n=i(t).workflow_dispatch;if(n==null)return{};let r=e.workflowDispatchInputsSchema.safeParse(n);if(!r.success)throw Error(`Invalid workflow_dispatch input definitions: ${r.error.message}`);return r.data.inputs??{}}function o(e){return Error(t.toolResultText(e)||`Workflow launch failed.`)}function s(r){if(r.isError)throw o(r);let i;try{i=JSON.parse(t.toolResultText(r))}catch(e){throw Error(`Workflow catalog returned invalid JSON.`,{cause:e})}let a=e.workflowCatalogPageSchema.safeParse(i);if(!a.success)throw Error(`Workflow catalog response was invalid: ${a.error.message}`);return{directory:a.data.directory,hasNextPage:a.data.hasNextPage,page:a.data.page,pageSize:a.data.pageSize,workflows:a.data.workflows.map(e=>({description:e.description,name:e.name,path:(0,n.resolve)(a.data.directory,e.path),relativePath:e.path,tags:e.tags}))}}async function c(e,t){let n=[],r=1,i=!0;for(;i;){let a={directory:t,page:r,pageSize:100},o=s(await e.execute(a));n.push(...o.workflows),i=o.hasNextPage,r+=1}return n}async function l(e){for(;;){let t=await e.editor(`Workflow prompt`);if(t===void 0)return;let n=t.trim();if(n)return n;e.notify(`A workflow prompt is required.`,`warning`)}}async function u(e,t,n){let r=n.description?`${t}: ${n.description}`:`Workflow input: ${t}`;if(n.options&&n.options.length>0)return e.select(r,n.options);if(n.type===`boolean`)return e.select(r,[`true`,`false`]);for(;;){let i=await e.input(r,n.default);if(i===void 0)return;let a=i.trim()||n.default;if(a||!n.required)return a||void 0;e.notify(`A value is required for workflow input ${t}.`,`warning`)}}async function d(e,t){let n=i(e),r=Object.hasOwn(n,`user_prompt`),o=r?await l(t):void 0;if(r&&o===void 0)return;let s=a(e),c={};for(let[e,n]of Object.entries(s)){let r=await u(t,e,n);if(r===void 0&&n.required)return;r!==void 0&&(c[e]=r)}return{...o===void 0?{}:{prompt:o},...Object.keys(c).length===0?{}:{inputs:c}}}function f(e,t){return e.name?e.name:e.uses?e.uses:e.id?e.id:`step ${t+1}`}function p(e){return Object.entries(a(e)).map(([e,t])=>({name:e,...t.description?{description:t.description}:{},...t.required===void 0?{}:{required:t.required},...t.default===void 0?{}:{default:t.default},...t.options?{options:t.options}:{}}))}function m(e){return Object.entries(e.jobs??{}).map(([e,t])=>({name:e,steps:(t.steps??[]).map((e,t)=>f(e,t))}))}function h(e,t){try{let n=t.parseWorkflow(e),r=t.compatibleRunners(n);return{triggers:Object.keys(i(n)),inputs:p(n),jobs:m(n),...r?{runners:r}:{}}}catch(e){return{triggers:[],inputs:[],jobs:[],error:e instanceof Error?e.message:String(e)}}}async function g(e,t,n){let r=e.parseWorkflow(t.path),i=e.compatibleRunners(r);if(i&&i.length===0)throw Error(`No compatible runner is available for this workflow.`);let a;if(i&&(a=await e.ui.select(`Select a workflow runner`,i),a===void 0))return;let o=await d(r,e.ui);if(o===void 0)return;let s={workflowPath:t.path,...a===void 0?{}:{runner:a},...typeof r.workspace==`string`?{workspace:r.workspace}:{},...o};return e.launch(s,n)}exports.collectWorkflowInputs=d,exports.launchWorkflowEntry=g,exports.loadWorkflowCatalog=c,exports.parseWorkflowCatalogPage=s,exports.summarizeWorkflowFile=h;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflowLauncher.mjs","names":[],"sources":["../../../../src/adapters/pi/workflow/workflowLauncher.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { type EmbeddedWorkflowFeature, type Workflow } from '@agimon-ai/workflow-mcp';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport {\n type DispatchInput,\n workflowCatalogPageSchema,\n workflowDispatchInputsSchema,\n} from '../../../schemas/workflowPi.ts';\nimport type {\n WorkflowCatalogDetail,\n WorkflowInputSummary,\n WorkflowJobSummary,\n} from '../../../tui/workflow/workflowCatalog';\nimport { toolResultText } from './piToolBridge';\nimport { type WorkflowLaunchExecutor, type WorkflowLaunchInput } from './piTools';\n\nconst CATALOG_PAGE_SIZE = 100;\nconst USER_PROMPT_TRIGGER = 'user_prompt';\nconst WORKFLOW_DISPATCH_TRIGGER = 'workflow_dispatch';\nconst BOOLEAN_INPUT_TYPE = 'boolean';\n\ntype WorkflowListTool = EmbeddedWorkflowFeature['listWorkflowsTool'];\ntype WorkflowListInput = Parameters<WorkflowListTool['execute']>[0];\n\nexport interface WorkflowCatalogEntry {\n description: string;\n name: string;\n path: string;\n relativePath: string;\n tags: string[];\n}\n\nexport interface WorkflowCatalogPage {\n directory: string;\n hasNextPage: boolean;\n page: number;\n pageSize: number;\n workflows: WorkflowCatalogEntry[];\n}\n\nexport interface WorkflowLauncherUi {\n editor(title: string, prefill?: string): Promise<string | undefined>;\n input(title: string, placeholder?: string): Promise<string | undefined>;\n notify(message: string, type?: 'info' | 'warning' | 'error'): void;\n select(title: string, options: string[]): Promise<string | undefined>;\n}\n\nexport interface WorkflowEntryLaunchOptions {\n compatibleRunners(workflow: Workflow): string[] | undefined;\n launch: WorkflowLaunchExecutor['execute'];\n parseWorkflow(workflowPath: string): Workflow;\n ui: WorkflowLauncherUi;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};\n}\n\nfunction workflowTriggerBlock(workflow: Workflow): Record<string, unknown> {\n return asRecord(workflow.on);\n}\n\nfunction dispatchInputs(workflow: Workflow): Record<string, DispatchInput> {\n const dispatch = workflowTriggerBlock(workflow)[WORKFLOW_DISPATCH_TRIGGER];\n if (dispatch === null || dispatch === undefined) return {};\n const parsed = workflowDispatchInputsSchema.safeParse(dispatch);\n if (!parsed.success) throw new Error(`Invalid workflow_dispatch input definitions: ${parsed.error.message}`);\n return parsed.data.inputs ?? {};\n}\n\nfunction launchResultError(result: CallToolResult): Error {\n return new Error(toolResultText(result) || 'Workflow launch failed.');\n}\n\nexport function parseWorkflowCatalogPage(result: CallToolResult): WorkflowCatalogPage {\n if (result.isError) throw launchResultError(result);\n let value: unknown;\n try {\n value = JSON.parse(toolResultText(result));\n } catch (cause) {\n throw new Error('Workflow catalog returned invalid JSON.', { cause });\n }\n const parsed = workflowCatalogPageSchema.safeParse(value);\n if (!parsed.success) throw new Error(`Workflow catalog response was invalid: ${parsed.error.message}`);\n return {\n directory: parsed.data.directory,\n hasNextPage: parsed.data.hasNextPage,\n page: parsed.data.page,\n pageSize: parsed.data.pageSize,\n workflows: parsed.data.workflows.map((entry) => ({\n description: entry.description,\n name: entry.name,\n path: resolve(parsed.data.directory, entry.path),\n relativePath: entry.path,\n tags: entry.tags,\n })),\n };\n}\n\nexport async function loadWorkflowCatalog(\n listWorkflowsTool: WorkflowListTool,\n directory: string,\n): Promise<WorkflowCatalogEntry[]> {\n const entries: WorkflowCatalogEntry[] = [];\n let page = 1;\n let hasNextPage = true;\n while (hasNextPage) {\n const input: WorkflowListInput = { directory, page, pageSize: CATALOG_PAGE_SIZE };\n const result = await listWorkflowsTool.execute(input);\n const catalogPage = parseWorkflowCatalogPage(result);\n entries.push(...catalogPage.workflows);\n hasNextPage = catalogPage.hasNextPage;\n page += 1;\n }\n return entries;\n}\n\nasync function requestPrompt(ui: WorkflowLauncherUi): Promise<string | undefined> {\n while (true) {\n const value = await ui.editor('Workflow prompt');\n if (value === undefined) return undefined;\n const prompt = value.trim();\n if (prompt) return prompt;\n ui.notify('A workflow prompt is required.', 'warning');\n }\n}\n\nasync function requestDispatchInput(\n ui: WorkflowLauncherUi,\n name: string,\n input: DispatchInput,\n): Promise<string | undefined> {\n const title = input.description ? `${name}: ${input.description}` : `Workflow input: ${name}`;\n if (input.options && input.options.length > 0) return ui.select(title, input.options);\n if (input.type === BOOLEAN_INPUT_TYPE) return ui.select(title, ['true', 'false']);\n\n while (true) {\n const value = await ui.input(title, input.default);\n if (value === undefined) return undefined;\n const normalized = value.trim() || input.default;\n if (normalized || !input.required) return normalized || undefined;\n ui.notify(`A value is required for workflow input ${name}.`, 'warning');\n }\n}\n\nexport async function collectWorkflowInputs(\n workflow: Workflow,\n ui: WorkflowLauncherUi,\n): Promise<{ inputs?: Record<string, string>; prompt?: string } | undefined> {\n const triggers = workflowTriggerBlock(workflow);\n const requiresPrompt = Object.hasOwn(triggers, USER_PROMPT_TRIGGER);\n const prompt = requiresPrompt ? await requestPrompt(ui) : undefined;\n if (requiresPrompt && prompt === undefined) return undefined;\n\n const definitions = dispatchInputs(workflow);\n const inputs: Record<string, string> = {};\n for (const [name, definition] of Object.entries(definitions)) {\n const value = await requestDispatchInput(ui, name, definition);\n if (value === undefined && definition.required) return undefined;\n if (value !== undefined) inputs[name] = value;\n }\n return {\n ...(prompt === undefined ? {} : { prompt }),\n ...(Object.keys(inputs).length === 0 ? {} : { inputs }),\n };\n}\n\n/**\n * Everything a launch needs once the workflow is chosen: the runner, the\n * prompt, and any `workflow_dispatch` inputs, each asked for through the\n * host's own dialogs. Selection is the caller's business - `SPC w l` launches\n * the row under its cursor, so this takes an entry rather than picking one.\n */\nfunction stepLabel(step: { name?: string; uses?: string; run?: unknown; id?: string }, index: number): string {\n if (step.name) return step.name;\n if (step.uses) return step.uses;\n if (step.id) return step.id;\n // A `run:` step without a name is identified by its position; printing the\n // command would put a shell line into a summary pane.\n return `step ${index + 1}`;\n}\n\nfunction catalogInputs(workflow: Workflow): WorkflowInputSummary[] {\n return Object.entries(dispatchInputs(workflow)).map(([name, definition]) => ({\n name,\n ...(definition.description ? { description: definition.description } : {}),\n ...(definition.required === undefined ? {} : { required: definition.required }),\n ...(definition.default === undefined ? {} : { default: definition.default }),\n ...(definition.options ? { options: definition.options } : {}),\n }));\n}\n\nfunction catalogJobs(workflow: Workflow): WorkflowJobSummary[] {\n return Object.entries(workflow.jobs ?? {}).map(([name, job]) => ({\n name,\n steps: (job.steps ?? []).map((step, index) => stepLabel(step, index)),\n }));\n}\n\n/**\n * The parsed shape `SPC w l`'s detail pane shows. A parse failure is returned\n * as `error` rather than thrown: one unparseable file in a repository must not\n * take the whole board down.\n */\nexport function summarizeWorkflowFile(\n workflowPath: string,\n options: Pick<WorkflowEntryLaunchOptions, 'compatibleRunners' | 'parseWorkflow'>,\n): WorkflowCatalogDetail {\n try {\n const workflow = options.parseWorkflow(workflowPath);\n const runners = options.compatibleRunners(workflow);\n return {\n triggers: Object.keys(workflowTriggerBlock(workflow)),\n inputs: catalogInputs(workflow),\n jobs: catalogJobs(workflow),\n ...(runners ? { runners } : {}),\n };\n } catch (cause) {\n return {\n triggers: [],\n inputs: [],\n jobs: [],\n error: cause instanceof Error ? cause.message : String(cause),\n };\n }\n}\n\nexport async function launchWorkflowEntry(\n options: WorkflowEntryLaunchOptions,\n selected: Pick<WorkflowCatalogEntry, 'path'>,\n ctx: ExtensionContext,\n): Promise<CallToolResult | undefined> {\n const workflow = options.parseWorkflow(selected.path);\n const runners = options.compatibleRunners(workflow);\n if (runners && runners.length === 0) throw new Error('No compatible runner is available for this workflow.');\n let runner: string | undefined;\n if (runners) {\n runner = await options.ui.select('Select a workflow runner', runners);\n if (runner === undefined) return undefined;\n }\n\n const triggerInputs = await collectWorkflowInputs(workflow, options.ui);\n if (triggerInputs === undefined) return undefined;\n const launchInput: WorkflowLaunchInput = {\n workflowPath: selected.path,\n ...(runner === undefined ? {} : { runner }),\n ...(typeof workflow.workspace === 'string' ? { workspace: workflow.workspace } : {}),\n ...triggerInputs,\n };\n return options.launch(launchInput, ctx);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["workflowDispatchInputsSchema","toolResultText","workflowCatalogPageSchema","resolve"],"sources":["../../../../src/services/workflowLauncher/index.ts"],"sourcesContent":["import { resolve } from 'node:path';\n\nimport { type EmbeddedWorkflowFeature, type Workflow } from '@agimon-ai/workflow-mcp';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nimport { type DispatchInput, workflowCatalogPageSchema, workflowDispatchInputsSchema } from '../../schemas/workflowPi';\nimport type {\n WorkflowCatalogDetail,\n WorkflowInputSummary,\n WorkflowJobSummary,\n} from '../../tui/workflow/workflowCatalog';\nimport { toolResultText } from '../piToolBridge';\nimport { type WorkflowLaunchExecutor, type WorkflowLaunchInput } from '../workflowExecution';\n\nconst CATALOG_PAGE_SIZE = 100;\nconst USER_PROMPT_TRIGGER = 'user_prompt';\nconst WORKFLOW_DISPATCH_TRIGGER = 'workflow_dispatch';\nconst BOOLEAN_INPUT_TYPE = 'boolean';\n\ntype WorkflowListTool = EmbeddedWorkflowFeature['listWorkflowsTool'];\ntype WorkflowListInput = Parameters<WorkflowListTool['execute']>[0];\n\nexport interface WorkflowCatalogEntry {\n description: string;\n name: string;\n path: string;\n relativePath: string;\n tags: string[];\n}\n\nexport interface WorkflowCatalogPage {\n directory: string;\n hasNextPage: boolean;\n page: number;\n pageSize: number;\n workflows: WorkflowCatalogEntry[];\n}\n\nexport interface WorkflowLauncherUi {\n editor(title: string, prefill?: string): Promise<string | undefined>;\n input(title: string, placeholder?: string): Promise<string | undefined>;\n notify(message: string, type?: 'info' | 'warning' | 'error'): void;\n select(title: string, options: string[]): Promise<string | undefined>;\n}\n\nexport interface WorkflowEntryLaunchOptions {\n compatibleRunners(workflow: Workflow): string[] | undefined;\n launch: WorkflowLaunchExecutor['execute'];\n parseWorkflow(workflowPath: string): Workflow;\n ui: WorkflowLauncherUi;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};\n}\n\nfunction workflowTriggerBlock(workflow: Workflow): Record<string, unknown> {\n return asRecord(workflow.on);\n}\n\nfunction dispatchInputs(workflow: Workflow): Record<string, DispatchInput> {\n const dispatch = workflowTriggerBlock(workflow)[WORKFLOW_DISPATCH_TRIGGER];\n if (dispatch === null || dispatch === undefined) return {};\n const parsed = workflowDispatchInputsSchema.safeParse(dispatch);\n if (!parsed.success) throw new Error(`Invalid workflow_dispatch input definitions: ${parsed.error.message}`);\n return parsed.data.inputs ?? {};\n}\n\nfunction launchResultError(result: CallToolResult): Error {\n return new Error(toolResultText(result) || 'Workflow launch failed.');\n}\n\nexport function parseWorkflowCatalogPage(result: CallToolResult): WorkflowCatalogPage {\n if (result.isError) throw launchResultError(result);\n let value: unknown;\n try {\n value = JSON.parse(toolResultText(result));\n } catch (cause) {\n throw new Error('Workflow catalog returned invalid JSON.', { cause });\n }\n const parsed = workflowCatalogPageSchema.safeParse(value);\n if (!parsed.success) throw new Error(`Workflow catalog response was invalid: ${parsed.error.message}`);\n return {\n directory: parsed.data.directory,\n hasNextPage: parsed.data.hasNextPage,\n page: parsed.data.page,\n pageSize: parsed.data.pageSize,\n workflows: parsed.data.workflows.map((entry) => ({\n description: entry.description,\n name: entry.name,\n path: resolve(parsed.data.directory, entry.path),\n relativePath: entry.path,\n tags: entry.tags,\n })),\n };\n}\n\nexport async function loadWorkflowCatalog(\n listWorkflowsTool: WorkflowListTool,\n directory: string,\n): Promise<WorkflowCatalogEntry[]> {\n const entries: WorkflowCatalogEntry[] = [];\n let page = 1;\n let hasNextPage = true;\n while (hasNextPage) {\n const input: WorkflowListInput = { directory, page, pageSize: CATALOG_PAGE_SIZE };\n const result = await listWorkflowsTool.execute(input);\n const catalogPage = parseWorkflowCatalogPage(result);\n entries.push(...catalogPage.workflows);\n hasNextPage = catalogPage.hasNextPage;\n page += 1;\n }\n return entries;\n}\n\nasync function requestPrompt(ui: WorkflowLauncherUi): Promise<string | undefined> {\n while (true) {\n const value = await ui.editor('Workflow prompt');\n if (value === undefined) return undefined;\n const prompt = value.trim();\n if (prompt) return prompt;\n ui.notify('A workflow prompt is required.', 'warning');\n }\n}\n\nasync function requestDispatchInput(\n ui: WorkflowLauncherUi,\n name: string,\n input: DispatchInput,\n): Promise<string | undefined> {\n const title = input.description ? `${name}: ${input.description}` : `Workflow input: ${name}`;\n if (input.options && input.options.length > 0) return ui.select(title, input.options);\n if (input.type === BOOLEAN_INPUT_TYPE) return ui.select(title, ['true', 'false']);\n\n while (true) {\n const value = await ui.input(title, input.default);\n if (value === undefined) return undefined;\n const normalized = value.trim() || input.default;\n if (normalized || !input.required) return normalized || undefined;\n ui.notify(`A value is required for workflow input ${name}.`, 'warning');\n }\n}\n\nexport async function collectWorkflowInputs(\n workflow: Workflow,\n ui: WorkflowLauncherUi,\n): Promise<{ inputs?: Record<string, string>; prompt?: string } | undefined> {\n const triggers = workflowTriggerBlock(workflow);\n const requiresPrompt = Object.hasOwn(triggers, USER_PROMPT_TRIGGER);\n const prompt = requiresPrompt ? await requestPrompt(ui) : undefined;\n if (requiresPrompt && prompt === undefined) return undefined;\n\n const definitions = dispatchInputs(workflow);\n const inputs: Record<string, string> = {};\n for (const [name, definition] of Object.entries(definitions)) {\n const value = await requestDispatchInput(ui, name, definition);\n if (value === undefined && definition.required) return undefined;\n if (value !== undefined) inputs[name] = value;\n }\n return {\n ...(prompt === undefined ? {} : { prompt }),\n ...(Object.keys(inputs).length === 0 ? {} : { inputs }),\n };\n}\n\n/**\n * Everything a launch needs once the workflow is chosen: the runner, the\n * prompt, and any `workflow_dispatch` inputs, each asked for through the\n * host's own dialogs. Selection is the caller's business - `SPC w l` launches\n * the row under its cursor, so this takes an entry rather than picking one.\n */\nfunction stepLabel(step: { name?: string; uses?: string; run?: unknown; id?: string }, index: number): string {\n if (step.name) return step.name;\n if (step.uses) return step.uses;\n if (step.id) return step.id;\n // A `run:` step without a name is identified by its position; printing the\n // command would put a shell line into a summary pane.\n return `step ${index + 1}`;\n}\n\nfunction catalogInputs(workflow: Workflow): WorkflowInputSummary[] {\n return Object.entries(dispatchInputs(workflow)).map(([name, definition]) => ({\n name,\n ...(definition.description ? { description: definition.description } : {}),\n ...(definition.required === undefined ? {} : { required: definition.required }),\n ...(definition.default === undefined ? {} : { default: definition.default }),\n ...(definition.options ? { options: definition.options } : {}),\n }));\n}\n\nfunction catalogJobs(workflow: Workflow): WorkflowJobSummary[] {\n return Object.entries(workflow.jobs ?? {}).map(([name, job]) => ({\n name,\n steps: (job.steps ?? []).map((step, index) => stepLabel(step, index)),\n }));\n}\n\n/**\n * The parsed shape `SPC w l`'s detail pane shows. A parse failure is returned\n * as `error` rather than thrown: one unparseable file in a repository must not\n * take the whole board down.\n */\nexport function summarizeWorkflowFile(\n workflowPath: string,\n options: Pick<WorkflowEntryLaunchOptions, 'compatibleRunners' | 'parseWorkflow'>,\n): WorkflowCatalogDetail {\n try {\n const workflow = options.parseWorkflow(workflowPath);\n const runners = options.compatibleRunners(workflow);\n return {\n triggers: Object.keys(workflowTriggerBlock(workflow)),\n inputs: catalogInputs(workflow),\n jobs: catalogJobs(workflow),\n ...(runners ? { runners } : {}),\n };\n } catch (cause) {\n return {\n triggers: [],\n inputs: [],\n jobs: [],\n error: cause instanceof Error ? cause.message : String(cause),\n };\n }\n}\n\nexport async function launchWorkflowEntry(\n options: WorkflowEntryLaunchOptions,\n selected: Pick<WorkflowCatalogEntry, 'path'>,\n ctx: ExtensionContext,\n): Promise<CallToolResult | undefined> {\n const workflow = options.parseWorkflow(selected.path);\n const runners = options.compatibleRunners(workflow);\n if (runners && runners.length === 0) throw new Error('No compatible runner is available for this workflow.');\n let runner: string | undefined;\n if (runners) {\n runner = await options.ui.select('Select a workflow runner', runners);\n if (runner === undefined) return undefined;\n }\n\n const triggerInputs = await collectWorkflowInputs(workflow, options.ui);\n if (triggerInputs === undefined) return undefined;\n const launchInput: WorkflowLaunchInput = {\n workflowPath: selected.path,\n ...(runner === undefined ? {} : { runner }),\n ...(typeof workflow.workspace === 'string' ? { workspace: workflow.workspace } : {}),\n ...triggerInputs,\n };\n return options.launch(launchInput, ctx);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{workflowCatalogPageSchema as e,workflowDispatchInputsSchema as t}from"../../schemas/workflowPi.mjs";import{toolResultText as n}from"../piToolBridge/index.mjs";import{resolve as r}from"node:path";function i(e){return e&&typeof e==`object`&&!Array.isArray(e)?{...e}:{}}function a(e){return i(e.on)}function o(e){let n=a(e).workflow_dispatch;if(n==null)return{};let r=t.safeParse(n);if(!r.success)throw Error(`Invalid workflow_dispatch input definitions: ${r.error.message}`);return r.data.inputs??{}}function s(e){return Error(n(e)||`Workflow launch failed.`)}function c(t){if(t.isError)throw s(t);let i;try{i=JSON.parse(n(t))}catch(e){throw Error(`Workflow catalog returned invalid JSON.`,{cause:e})}let a=e.safeParse(i);if(!a.success)throw Error(`Workflow catalog response was invalid: ${a.error.message}`);return{directory:a.data.directory,hasNextPage:a.data.hasNextPage,page:a.data.page,pageSize:a.data.pageSize,workflows:a.data.workflows.map(e=>({description:e.description,name:e.name,path:r(a.data.directory,e.path),relativePath:e.path,tags:e.tags}))}}async function l(e,t){let n=[],r=1,i=!0;for(;i;){let a={directory:t,page:r,pageSize:100},o=c(await e.execute(a));n.push(...o.workflows),i=o.hasNextPage,r+=1}return n}async function u(e){for(;;){let t=await e.editor(`Workflow prompt`);if(t===void 0)return;let n=t.trim();if(n)return n;e.notify(`A workflow prompt is required.`,`warning`)}}async function d(e,t,n){let r=n.description?`${t}: ${n.description}`:`Workflow input: ${t}`;if(n.options&&n.options.length>0)return e.select(r,n.options);if(n.type===`boolean`)return e.select(r,[`true`,`false`]);for(;;){let i=await e.input(r,n.default);if(i===void 0)return;let a=i.trim()||n.default;if(a||!n.required)return a||void 0;e.notify(`A value is required for workflow input ${t}.`,`warning`)}}async function f(e,t){let n=a(e),r=Object.hasOwn(n,`user_prompt`),i=r?await u(t):void 0;if(r&&i===void 0)return;let s=o(e),c={};for(let[e,n]of Object.entries(s)){let r=await d(t,e,n);if(r===void 0&&n.required)return;r!==void 0&&(c[e]=r)}return{...i===void 0?{}:{prompt:i},...Object.keys(c).length===0?{}:{inputs:c}}}function p(e,t){return e.name?e.name:e.uses?e.uses:e.id?e.id:`step ${t+1}`}function m(e){return Object.entries(o(e)).map(([e,t])=>({name:e,...t.description?{description:t.description}:{},...t.required===void 0?{}:{required:t.required},...t.default===void 0?{}:{default:t.default},...t.options?{options:t.options}:{}}))}function h(e){return Object.entries(e.jobs??{}).map(([e,t])=>({name:e,steps:(t.steps??[]).map((e,t)=>p(e,t))}))}function g(e,t){try{let n=t.parseWorkflow(e),r=t.compatibleRunners(n);return{triggers:Object.keys(a(n)),inputs:m(n),jobs:h(n),...r?{runners:r}:{}}}catch(e){return{triggers:[],inputs:[],jobs:[],error:e instanceof Error?e.message:String(e)}}}async function _(e,t,n){let r=e.parseWorkflow(t.path),i=e.compatibleRunners(r);if(i&&i.length===0)throw Error(`No compatible runner is available for this workflow.`);let a;if(i&&(a=await e.ui.select(`Select a workflow runner`,i),a===void 0))return;let o=await f(r,e.ui);if(o===void 0)return;let s={workflowPath:t.path,...a===void 0?{}:{runner:a},...typeof r.workspace==`string`?{workspace:r.workspace}:{},...o};return e.launch(s,n)}export{f as collectWorkflowInputs,_ as launchWorkflowEntry,l as loadWorkflowCatalog,c as parseWorkflowCatalogPage,g as summarizeWorkflowFile};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowLauncher/index.ts"],"sourcesContent":["import { resolve } from 'node:path';\n\nimport { type EmbeddedWorkflowFeature, type Workflow } from '@agimon-ai/workflow-mcp';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nimport { type DispatchInput, workflowCatalogPageSchema, workflowDispatchInputsSchema } from '../../schemas/workflowPi';\nimport type {\n WorkflowCatalogDetail,\n WorkflowInputSummary,\n WorkflowJobSummary,\n} from '../../tui/workflow/workflowCatalog';\nimport { toolResultText } from '../piToolBridge';\nimport { type WorkflowLaunchExecutor, type WorkflowLaunchInput } from '../workflowExecution';\n\nconst CATALOG_PAGE_SIZE = 100;\nconst USER_PROMPT_TRIGGER = 'user_prompt';\nconst WORKFLOW_DISPATCH_TRIGGER = 'workflow_dispatch';\nconst BOOLEAN_INPUT_TYPE = 'boolean';\n\ntype WorkflowListTool = EmbeddedWorkflowFeature['listWorkflowsTool'];\ntype WorkflowListInput = Parameters<WorkflowListTool['execute']>[0];\n\nexport interface WorkflowCatalogEntry {\n description: string;\n name: string;\n path: string;\n relativePath: string;\n tags: string[];\n}\n\nexport interface WorkflowCatalogPage {\n directory: string;\n hasNextPage: boolean;\n page: number;\n pageSize: number;\n workflows: WorkflowCatalogEntry[];\n}\n\nexport interface WorkflowLauncherUi {\n editor(title: string, prefill?: string): Promise<string | undefined>;\n input(title: string, placeholder?: string): Promise<string | undefined>;\n notify(message: string, type?: 'info' | 'warning' | 'error'): void;\n select(title: string, options: string[]): Promise<string | undefined>;\n}\n\nexport interface WorkflowEntryLaunchOptions {\n compatibleRunners(workflow: Workflow): string[] | undefined;\n launch: WorkflowLaunchExecutor['execute'];\n parseWorkflow(workflowPath: string): Workflow;\n ui: WorkflowLauncherUi;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};\n}\n\nfunction workflowTriggerBlock(workflow: Workflow): Record<string, unknown> {\n return asRecord(workflow.on);\n}\n\nfunction dispatchInputs(workflow: Workflow): Record<string, DispatchInput> {\n const dispatch = workflowTriggerBlock(workflow)[WORKFLOW_DISPATCH_TRIGGER];\n if (dispatch === null || dispatch === undefined) return {};\n const parsed = workflowDispatchInputsSchema.safeParse(dispatch);\n if (!parsed.success) throw new Error(`Invalid workflow_dispatch input definitions: ${parsed.error.message}`);\n return parsed.data.inputs ?? {};\n}\n\nfunction launchResultError(result: CallToolResult): Error {\n return new Error(toolResultText(result) || 'Workflow launch failed.');\n}\n\nexport function parseWorkflowCatalogPage(result: CallToolResult): WorkflowCatalogPage {\n if (result.isError) throw launchResultError(result);\n let value: unknown;\n try {\n value = JSON.parse(toolResultText(result));\n } catch (cause) {\n throw new Error('Workflow catalog returned invalid JSON.', { cause });\n }\n const parsed = workflowCatalogPageSchema.safeParse(value);\n if (!parsed.success) throw new Error(`Workflow catalog response was invalid: ${parsed.error.message}`);\n return {\n directory: parsed.data.directory,\n hasNextPage: parsed.data.hasNextPage,\n page: parsed.data.page,\n pageSize: parsed.data.pageSize,\n workflows: parsed.data.workflows.map((entry) => ({\n description: entry.description,\n name: entry.name,\n path: resolve(parsed.data.directory, entry.path),\n relativePath: entry.path,\n tags: entry.tags,\n })),\n };\n}\n\nexport async function loadWorkflowCatalog(\n listWorkflowsTool: WorkflowListTool,\n directory: string,\n): Promise<WorkflowCatalogEntry[]> {\n const entries: WorkflowCatalogEntry[] = [];\n let page = 1;\n let hasNextPage = true;\n while (hasNextPage) {\n const input: WorkflowListInput = { directory, page, pageSize: CATALOG_PAGE_SIZE };\n const result = await listWorkflowsTool.execute(input);\n const catalogPage = parseWorkflowCatalogPage(result);\n entries.push(...catalogPage.workflows);\n hasNextPage = catalogPage.hasNextPage;\n page += 1;\n }\n return entries;\n}\n\nasync function requestPrompt(ui: WorkflowLauncherUi): Promise<string | undefined> {\n while (true) {\n const value = await ui.editor('Workflow prompt');\n if (value === undefined) return undefined;\n const prompt = value.trim();\n if (prompt) return prompt;\n ui.notify('A workflow prompt is required.', 'warning');\n }\n}\n\nasync function requestDispatchInput(\n ui: WorkflowLauncherUi,\n name: string,\n input: DispatchInput,\n): Promise<string | undefined> {\n const title = input.description ? `${name}: ${input.description}` : `Workflow input: ${name}`;\n if (input.options && input.options.length > 0) return ui.select(title, input.options);\n if (input.type === BOOLEAN_INPUT_TYPE) return ui.select(title, ['true', 'false']);\n\n while (true) {\n const value = await ui.input(title, input.default);\n if (value === undefined) return undefined;\n const normalized = value.trim() || input.default;\n if (normalized || !input.required) return normalized || undefined;\n ui.notify(`A value is required for workflow input ${name}.`, 'warning');\n }\n}\n\nexport async function collectWorkflowInputs(\n workflow: Workflow,\n ui: WorkflowLauncherUi,\n): Promise<{ inputs?: Record<string, string>; prompt?: string } | undefined> {\n const triggers = workflowTriggerBlock(workflow);\n const requiresPrompt = Object.hasOwn(triggers, USER_PROMPT_TRIGGER);\n const prompt = requiresPrompt ? await requestPrompt(ui) : undefined;\n if (requiresPrompt && prompt === undefined) return undefined;\n\n const definitions = dispatchInputs(workflow);\n const inputs: Record<string, string> = {};\n for (const [name, definition] of Object.entries(definitions)) {\n const value = await requestDispatchInput(ui, name, definition);\n if (value === undefined && definition.required) return undefined;\n if (value !== undefined) inputs[name] = value;\n }\n return {\n ...(prompt === undefined ? {} : { prompt }),\n ...(Object.keys(inputs).length === 0 ? {} : { inputs }),\n };\n}\n\n/**\n * Everything a launch needs once the workflow is chosen: the runner, the\n * prompt, and any `workflow_dispatch` inputs, each asked for through the\n * host's own dialogs. Selection is the caller's business - `SPC w l` launches\n * the row under its cursor, so this takes an entry rather than picking one.\n */\nfunction stepLabel(step: { name?: string; uses?: string; run?: unknown; id?: string }, index: number): string {\n if (step.name) return step.name;\n if (step.uses) return step.uses;\n if (step.id) return step.id;\n // A `run:` step without a name is identified by its position; printing the\n // command would put a shell line into a summary pane.\n return `step ${index + 1}`;\n}\n\nfunction catalogInputs(workflow: Workflow): WorkflowInputSummary[] {\n return Object.entries(dispatchInputs(workflow)).map(([name, definition]) => ({\n name,\n ...(definition.description ? { description: definition.description } : {}),\n ...(definition.required === undefined ? {} : { required: definition.required }),\n ...(definition.default === undefined ? {} : { default: definition.default }),\n ...(definition.options ? { options: definition.options } : {}),\n }));\n}\n\nfunction catalogJobs(workflow: Workflow): WorkflowJobSummary[] {\n return Object.entries(workflow.jobs ?? {}).map(([name, job]) => ({\n name,\n steps: (job.steps ?? []).map((step, index) => stepLabel(step, index)),\n }));\n}\n\n/**\n * The parsed shape `SPC w l`'s detail pane shows. A parse failure is returned\n * as `error` rather than thrown: one unparseable file in a repository must not\n * take the whole board down.\n */\nexport function summarizeWorkflowFile(\n workflowPath: string,\n options: Pick<WorkflowEntryLaunchOptions, 'compatibleRunners' | 'parseWorkflow'>,\n): WorkflowCatalogDetail {\n try {\n const workflow = options.parseWorkflow(workflowPath);\n const runners = options.compatibleRunners(workflow);\n return {\n triggers: Object.keys(workflowTriggerBlock(workflow)),\n inputs: catalogInputs(workflow),\n jobs: catalogJobs(workflow),\n ...(runners ? { runners } : {}),\n };\n } catch (cause) {\n return {\n triggers: [],\n inputs: [],\n jobs: [],\n error: cause instanceof Error ? cause.message : String(cause),\n };\n }\n}\n\nexport async function launchWorkflowEntry(\n options: WorkflowEntryLaunchOptions,\n selected: Pick<WorkflowCatalogEntry, 'path'>,\n ctx: ExtensionContext,\n): Promise<CallToolResult | undefined> {\n const workflow = options.parseWorkflow(selected.path);\n const runners = options.compatibleRunners(workflow);\n if (runners && runners.length === 0) throw new Error('No compatible runner is available for this workflow.');\n let runner: string | undefined;\n if (runners) {\n runner = await options.ui.select('Select a workflow runner', runners);\n if (runner === undefined) return undefined;\n }\n\n const triggerInputs = await collectWorkflowInputs(workflow, options.ui);\n if (triggerInputs === undefined) return undefined;\n const launchInput: WorkflowLaunchInput = {\n workflowPath: selected.path,\n ...(runner === undefined ? {} : { runner }),\n ...(typeof workflow.workspace === 'string' ? { workspace: workflow.workspace } : {}),\n ...triggerInputs,\n };\n return options.launch(launchInput, ctx);\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
function e(e,t,n,r){let i=n===void 0&&r;i&&e.narrate(`Workflow launched: ${t.displayName}.`),(n===`running`||i)&&(t.stage===`completed`&&e.narrate(`Workflow succeeded: ${t.displayName}.`),t.stage===`error`&&e.narrate(`Workflow failed: ${t.displayName}.`))}exports.narrateWorkflowTransition=e;
|
|
2
|
-
//# sourceMappingURL=
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
package/dist/{services/workflowNarration.cjs.map → src/services/workflowNarration/index.cjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/workflowNarration/index.ts"],"sourcesContent":["import type { WorkflowRunRecord, WorkflowStage } from '@agimon-ai/workflow-mcp';\n\n/** Package-local narration sink; the Pi adapter binds it to the injected Cordis service. */\nexport interface WorkflowNarrationSink {\n narrate(text: string): void;\n}\n\nconst RUNNING_STAGE: WorkflowStage = 'running';\nconst COMPLETED_STAGE: WorkflowStage = 'completed';\nconst ERROR_STAGE: WorkflowStage = 'error';\n\ntype NarratedWorkflow = Pick<WorkflowRunRecord, 'displayName' | 'stage'>;\n\n/**\n * Translate an observed workflow lifecycle transition into generic speech.\n * Voice owns delivery and ignores requests while its minor mode is inactive.\n */\nexport function narrateWorkflowTransition(\n requester: WorkflowNarrationSink,\n record: NarratedWorkflow,\n previousStage: WorkflowStage | undefined,\n startedDuringSession: boolean,\n): void {\n const newlyLaunched = previousStage === undefined && startedDuringSession;\n if (newlyLaunched) requester.narrate(`Workflow launched: ${record.displayName}.`);\n\n if (previousStage !== RUNNING_STAGE && !newlyLaunched) return;\n if (record.stage === COMPLETED_STAGE) requester.narrate(`Workflow succeeded: ${record.displayName}.`);\n if (record.stage === ERROR_STAGE) requester.narrate(`Workflow failed: ${record.displayName}.`);\n}\n"],"mappings":"AAiBA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,IAAM,EAAgB,IAAkB,IAAA,IAAa,EACjD,GAAe,EAAU,QAAQ,sBAAsB,EAAO,YAAY,EAAE,GAE5E,IAAkB,WAAkB,KACpC,EAAO,QAAU,aAAiB,EAAU,QAAQ,uBAAuB,EAAO,YAAY,EAAE,EAChG,EAAO,QAAU,SAAa,EAAU,QAAQ,oBAAoB,EAAO,YAAY,EAAE,EAC/F"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
function e(e,t,n,r){let i=n===void 0&&r;i&&e.narrate(`Workflow launched: ${t.displayName}.`),(n===`running`||i)&&(t.stage===`completed`&&e.narrate(`Workflow succeeded: ${t.displayName}.`),t.stage===`error`&&e.narrate(`Workflow failed: ${t.displayName}.`))}export{e as narrateWorkflowTransition};
|
|
2
|
-
//# sourceMappingURL=
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
package/dist/{services/workflowNarration.mjs.map → src/services/workflowNarration/index.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowNarration/index.ts"],"sourcesContent":["import type { WorkflowRunRecord, WorkflowStage } from '@agimon-ai/workflow-mcp';\n\n/** Package-local narration sink; the Pi adapter binds it to the injected Cordis service. */\nexport interface WorkflowNarrationSink {\n narrate(text: string): void;\n}\n\nconst RUNNING_STAGE: WorkflowStage = 'running';\nconst COMPLETED_STAGE: WorkflowStage = 'completed';\nconst ERROR_STAGE: WorkflowStage = 'error';\n\ntype NarratedWorkflow = Pick<WorkflowRunRecord, 'displayName' | 'stage'>;\n\n/**\n * Translate an observed workflow lifecycle transition into generic speech.\n * Voice owns delivery and ignores requests while its minor mode is inactive.\n */\nexport function narrateWorkflowTransition(\n requester: WorkflowNarrationSink,\n record: NarratedWorkflow,\n previousStage: WorkflowStage | undefined,\n startedDuringSession: boolean,\n): void {\n const newlyLaunched = previousStage === undefined && startedDuringSession;\n if (newlyLaunched) requester.narrate(`Workflow launched: ${record.displayName}.`);\n\n if (previousStage !== RUNNING_STAGE && !newlyLaunched) return;\n if (record.stage === COMPLETED_STAGE) requester.narrate(`Workflow succeeded: ${record.displayName}.`);\n if (record.stage === ERROR_STAGE) requester.narrate(`Workflow failed: ${record.displayName}.`);\n}\n"],"mappings":"AAiBA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,IAAM,EAAgB,IAAkB,IAAA,IAAa,EACjD,GAAe,EAAU,QAAQ,sBAAsB,EAAO,YAAY,EAAE,GAE5E,IAAkB,WAAkB,KACpC,EAAO,QAAU,aAAiB,EAAU,QAAQ,uBAAuB,EAAO,YAAY,EAAE,EAChG,EAAO,QAAU,SAAa,EAAU,QAAQ,oBAAoB,EAAO,YAAY,EAAE,EAC/F"}
|