@xdelivered/emberflow 0.2.0
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/LICENSE +21 -0
- package/README.md +153 -0
- package/bin/commands.test.ts +185 -0
- package/bin/commands.ts +306 -0
- package/bin/emberflow.mjs +38 -0
- package/bin/init.test.ts +165 -0
- package/bin/init.ts +297 -0
- package/bin/runtime.mjs +105 -0
- package/bin/runtime.test.ts +167 -0
- package/bin/skillTargets.test.ts +108 -0
- package/bin/skillTargets.ts +36 -0
- package/bin/skillTemplates.test.ts +37 -0
- package/bin/tsconfig.json +23 -0
- package/dist/bin/commands.d.ts +24 -0
- package/dist/bin/commands.js +252 -0
- package/dist/bin/init.d.ts +17 -0
- package/dist/bin/init.js +241 -0
- package/dist/bin/runtime.d.mts +39 -0
- package/dist/bin/runtime.mjs +96 -0
- package/dist/bin/skillTargets.d.ts +13 -0
- package/dist/bin/skillTargets.js +22 -0
- package/dist/server/agents/claudeAdapter.d.ts +8 -0
- package/dist/server/agents/claudeAdapter.js +190 -0
- package/dist/server/agents/claudeParse.d.ts +2 -0
- package/dist/server/agents/claudeParse.js +162 -0
- package/dist/server/agents/codexAdapter.d.ts +13 -0
- package/dist/server/agents/codexAdapter.js +174 -0
- package/dist/server/agents/codexParse.d.ts +2 -0
- package/dist/server/agents/codexParse.js +74 -0
- package/dist/server/agents/detect.d.ts +23 -0
- package/dist/server/agents/detect.js +35 -0
- package/dist/server/agents/gitScope.d.ts +24 -0
- package/dist/server/agents/gitScope.js +95 -0
- package/dist/server/agents/modelRejectionHint.d.ts +8 -0
- package/dist/server/agents/modelRejectionHint.js +12 -0
- package/dist/server/agents/prompt.d.ts +79 -0
- package/dist/server/agents/prompt.js +364 -0
- package/dist/server/agents/runManager.d.ts +90 -0
- package/dist/server/agents/runManager.js +259 -0
- package/dist/server/agents/types.d.ts +15 -0
- package/dist/server/agents/types.js +1 -0
- package/dist/server/apiStore.d.ts +60 -0
- package/dist/server/apiStore.js +249 -0
- package/dist/server/auth/enforce.d.ts +1 -0
- package/dist/server/auth/enforce.js +6 -0
- package/dist/server/auth/types.d.ts +1 -0
- package/dist/server/auth/types.js +1 -0
- package/dist/server/auth/verifiers.d.ts +1 -0
- package/dist/server/auth/verifiers.js +4 -0
- package/dist/server/authAttach.d.ts +19 -0
- package/dist/server/authAttach.js +79 -0
- package/dist/server/cli.d.ts +7 -0
- package/dist/server/cli.js +804 -0
- package/dist/server/client.d.ts +130 -0
- package/dist/server/client.js +247 -0
- package/dist/server/environments.d.ts +103 -0
- package/dist/server/environments.js +486 -0
- package/dist/server/flowStore.d.ts +37 -0
- package/dist/server/flowStore.js +117 -0
- package/dist/server/httpOperations.d.ts +32 -0
- package/dist/server/httpOperations.js +62 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1022 -0
- package/dist/server/infrastructure.d.ts +51 -0
- package/dist/server/infrastructure.js +116 -0
- package/dist/server/login.d.ts +21 -0
- package/dist/server/login.js +44 -0
- package/dist/server/mcp.d.ts +1 -0
- package/dist/server/mcp.js +182 -0
- package/dist/server/migrateFlows.d.ts +8 -0
- package/dist/server/migrateFlows.js +25 -0
- package/dist/server/mockHandler.d.ts +15 -0
- package/dist/server/mockHandler.js +35 -0
- package/dist/server/nodesPayload.d.ts +14 -0
- package/dist/server/nodesPayload.js +14 -0
- package/dist/server/normalizeFlow.d.ts +21 -0
- package/dist/server/normalizeFlow.js +48 -0
- package/dist/server/openBrowser.d.ts +8 -0
- package/dist/server/openBrowser.js +16 -0
- package/dist/server/operationResult.d.ts +12 -0
- package/dist/server/operationResult.js +26 -0
- package/dist/server/pathGuard.d.ts +1 -0
- package/dist/server/pathGuard.js +16 -0
- package/dist/server/pathSafety.d.ts +20 -0
- package/dist/server/pathSafety.js +35 -0
- package/dist/server/projectConfig.d.ts +47 -0
- package/dist/server/projectConfig.js +61 -0
- package/dist/server/projectMode.d.ts +26 -0
- package/dist/server/projectMode.js +44 -0
- package/dist/server/redact.d.ts +6 -0
- package/dist/server/redact.js +37 -0
- package/dist/server/runRegistry.d.ts +84 -0
- package/dist/server/runRegistry.js +200 -0
- package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
- package/dist/server/runtime/RuntimeAdapter.js +1 -0
- package/dist/server/runtime/expressAdapter.d.ts +19 -0
- package/dist/server/runtime/expressAdapter.js +56 -0
- package/dist/server/scenarioTest.d.ts +14 -0
- package/dist/server/scenarioTest.js +66 -0
- package/dist/server/subflowRunner.d.ts +32 -0
- package/dist/server/subflowRunner.js +72 -0
- package/dist/server/testRunner.d.ts +70 -0
- package/dist/server/testRunner.js +151 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +2 -0
- package/dist/src/engine/authVerify.d.ts +33 -0
- package/dist/src/engine/authVerify.js +63 -0
- package/dist/src/engine/diagnostics.d.ts +62 -0
- package/dist/src/engine/diagnostics.js +148 -0
- package/dist/src/engine/executor.d.ts +151 -0
- package/dist/src/engine/executor.js +684 -0
- package/dist/src/engine/httpError.d.ts +7 -0
- package/dist/src/engine/httpError.js +14 -0
- package/dist/src/engine/index.d.ts +11 -0
- package/dist/src/engine/index.js +11 -0
- package/dist/src/engine/output.d.ts +11 -0
- package/dist/src/engine/output.js +20 -0
- package/dist/src/engine/publish.d.ts +30 -0
- package/dist/src/engine/publish.js +95 -0
- package/dist/src/engine/registry.d.ts +34 -0
- package/dist/src/engine/registry.js +67 -0
- package/dist/src/engine/scenarios.d.ts +12 -0
- package/dist/src/engine/scenarios.js +19 -0
- package/dist/src/engine/schemaCheck.d.ts +4 -0
- package/dist/src/engine/schemaCheck.js +34 -0
- package/dist/src/engine/trace.d.ts +12 -0
- package/dist/src/engine/trace.js +12 -0
- package/dist/src/engine/types.d.ts +308 -0
- package/dist/src/engine/types.js +1 -0
- package/dist/src/engine/validation.d.ts +26 -0
- package/dist/src/engine/validation.js +318 -0
- package/dist/src/flows/anomaly-flows.d.ts +23 -0
- package/dist/src/flows/anomaly-flows.js +151 -0
- package/dist/src/flows/city-sweep-flow.d.ts +10 -0
- package/dist/src/flows/city-sweep-flow.js +127 -0
- package/dist/src/flows/login-flow.d.ts +13 -0
- package/dist/src/flows/login-flow.js +145 -0
- package/dist/src/flows/pradar-flows.d.ts +5 -0
- package/dist/src/flows/pradar-flows.js +375 -0
- package/dist/src/flows/weather-flow.d.ts +12 -0
- package/dist/src/flows/weather-flow.js +150 -0
- package/dist/src/lib/pathParams.d.ts +3 -0
- package/dist/src/lib/pathParams.js +6 -0
- package/dist/src/nodes/anomaly.d.ts +2 -0
- package/dist/src/nodes/anomaly.js +556 -0
- package/dist/src/nodes/flow-control.d.ts +7 -0
- package/dist/src/nodes/flow-control.js +270 -0
- package/dist/src/nodes/index.d.ts +3 -0
- package/dist/src/nodes/index.js +20 -0
- package/dist/src/nodes/login.d.ts +7 -0
- package/dist/src/nodes/login.js +158 -0
- package/dist/src/nodes/loops.d.ts +11 -0
- package/dist/src/nodes/loops.js +60 -0
- package/dist/src/nodes/pradar/index.d.ts +7 -0
- package/dist/src/nodes/pradar/index.js +9 -0
- package/dist/src/nodes/pradar/logic.d.ts +189 -0
- package/dist/src/nodes/pradar/logic.js +370 -0
- package/dist/src/nodes/pradar/nodes.d.ts +42 -0
- package/dist/src/nodes/pradar/nodes.js +801 -0
- package/dist/src/nodes/requireAuth.d.ts +20 -0
- package/dist/src/nodes/requireAuth.js +48 -0
- package/dist/src/nodes/response.d.ts +8 -0
- package/dist/src/nodes/response.js +33 -0
- package/dist/src/nodes/weather.d.ts +33 -0
- package/dist/src/nodes/weather.js +260 -0
- package/package.json +92 -0
- package/server/agentRoute.test.ts +157 -0
- package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
- package/server/agents/__fixtures__/fake-claude.mjs +55 -0
- package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
- package/server/agents/__fixtures__/fake-codex.mjs +52 -0
- package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
- package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
- package/server/agents/claudeAdapter.test.ts +127 -0
- package/server/agents/claudeAdapter.ts +212 -0
- package/server/agents/claudeParse.ts +193 -0
- package/server/agents/codexAdapter.test.ts +125 -0
- package/server/agents/codexAdapter.ts +198 -0
- package/server/agents/codexParse.test.ts +58 -0
- package/server/agents/codexParse.ts +126 -0
- package/server/agents/detect.test.ts +65 -0
- package/server/agents/detect.ts +43 -0
- package/server/agents/gitScope.test.ts +129 -0
- package/server/agents/gitScope.ts +110 -0
- package/server/agents/modelRejectionHint.test.ts +30 -0
- package/server/agents/modelRejectionHint.ts +14 -0
- package/server/agents/prompt.test.ts +613 -0
- package/server/agents/prompt.ts +527 -0
- package/server/agents/runManager.test.ts +251 -0
- package/server/agents/runManager.ts +286 -0
- package/server/agents/types.ts +17 -0
- package/server/apiMount.test.ts +591 -0
- package/server/apiStore.test.ts +324 -0
- package/server/apiStore.ts +241 -0
- package/server/auth/enforce.test.ts +61 -0
- package/server/auth/enforce.ts +6 -0
- package/server/auth/types.ts +5 -0
- package/server/auth/verifiers.test.ts +37 -0
- package/server/auth/verifiers.ts +9 -0
- package/server/authAttach.test.ts +175 -0
- package/server/authAttach.ts +99 -0
- package/server/cli.ts +847 -0
- package/server/cliAuth.test.ts +229 -0
- package/server/cliCreate.test.ts +131 -0
- package/server/cliDoctor.test.ts +326 -0
- package/server/cliTest.test.ts +735 -0
- package/server/client.ts +314 -0
- package/server/demoProject.test.ts +29 -0
- package/server/diagnosticsRoute.test.ts +169 -0
- package/server/environments.test.ts +620 -0
- package/server/environments.ts +603 -0
- package/server/environmentsRoute.test.ts +385 -0
- package/server/errorWorkflow.test.ts +205 -0
- package/server/flowStore.test.ts +84 -0
- package/server/flowStore.ts +125 -0
- package/server/httpOperations.test.ts +377 -0
- package/server/httpOperations.ts +96 -0
- package/server/index.ts +1111 -0
- package/server/infrastructure.test.ts +154 -0
- package/server/infrastructure.ts +161 -0
- package/server/infrastructureRoute.test.ts +105 -0
- package/server/login.test.ts +153 -0
- package/server/login.ts +67 -0
- package/server/mcp.ts +278 -0
- package/server/migrateFlows.test.ts +55 -0
- package/server/migrateFlows.ts +23 -0
- package/server/mockHandler.test.ts +138 -0
- package/server/mockHandler.ts +52 -0
- package/server/mockRun.test.ts +367 -0
- package/server/mockServe.test.ts +467 -0
- package/server/nodesPayload.test.ts +29 -0
- package/server/nodesPayload.ts +26 -0
- package/server/normalizeFlow.test.ts +80 -0
- package/server/normalizeFlow.ts +59 -0
- package/server/openBrowser.test.ts +20 -0
- package/server/openBrowser.ts +22 -0
- package/server/operationResult.test.ts +67 -0
- package/server/operationResult.ts +27 -0
- package/server/pathGuard.test.ts +19 -0
- package/server/pathGuard.ts +12 -0
- package/server/pathSafety.test.ts +38 -0
- package/server/pathSafety.ts +33 -0
- package/server/projectConfig.test.ts +67 -0
- package/server/projectConfig.ts +101 -0
- package/server/projectMode.test.ts +106 -0
- package/server/projectMode.ts +59 -0
- package/server/redact.test.ts +88 -0
- package/server/redact.ts +35 -0
- package/server/redactEgress.test.ts +281 -0
- package/server/runRegistry.ts +270 -0
- package/server/runsAuth.test.ts +119 -0
- package/server/runtime/RuntimeAdapter.ts +24 -0
- package/server/runtime/expressAdapter.test.ts +38 -0
- package/server/runtime/expressAdapter.ts +70 -0
- package/server/scenarioTest.test.ts +148 -0
- package/server/scenarioTest.ts +88 -0
- package/server/setupStatus.test.ts +127 -0
- package/server/subflowRunner.ts +104 -0
- package/server/testRunner.ts +216 -0
- package/server/tsconfig.json +20 -0
- package/server/workflowTestRoute.test.ts +297 -0
- package/src/App.tsx +233 -0
- package/src/components/AgentConsole.tsx +461 -0
- package/src/components/BranchRulesEditor.tsx +156 -0
- package/src/components/CreateModal.tsx +275 -0
- package/src/components/Dock.tsx +487 -0
- package/src/components/EnvironmentDialog.tsx +569 -0
- package/src/components/EnvironmentPicker.tsx +357 -0
- package/src/components/EnvironmentsDialog.tsx +144 -0
- package/src/components/ExecutionPager.tsx +67 -0
- package/src/components/InfraPanel.test.tsx +103 -0
- package/src/components/InfraPanel.tsx +171 -0
- package/src/components/Inspector.tsx +1182 -0
- package/src/components/Json.tsx +50 -0
- package/src/components/JsonModal.tsx +52 -0
- package/src/components/LogMessage.tsx +61 -0
- package/src/components/NodeRunModal.tsx +213 -0
- package/src/components/RunLogPanel.tsx +397 -0
- package/src/components/RunbookView.tsx +1085 -0
- package/src/components/Scenarios.tsx +476 -0
- package/src/components/SettingsDialog.tsx +229 -0
- package/src/components/Sidebar.tsx +436 -0
- package/src/components/StatusBar.tsx +302 -0
- package/src/components/Toolbar.tsx +663 -0
- package/src/components/WelcomeDialog.test.tsx +107 -0
- package/src/components/WelcomeDialog.tsx +412 -0
- package/src/components/agentMarkdown.test.tsx +91 -0
- package/src/components/agentMarkdown.tsx +158 -0
- package/src/components/ui/badge.tsx +31 -0
- package/src/components/ui/button.tsx +46 -0
- package/src/components/ui/combobox.tsx +114 -0
- package/src/components/ui/command.tsx +82 -0
- package/src/components/ui/dialog.tsx +73 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/popover.tsx +32 -0
- package/src/components/ui/resizable.tsx +44 -0
- package/src/components/ui/tabs.tsx +36 -0
- package/src/config.ts +4 -0
- package/src/engine/authVerify.ts +81 -0
- package/src/engine/diagnostics.test.ts +437 -0
- package/src/engine/diagnostics.ts +211 -0
- package/src/engine/executor.test.ts +1339 -0
- package/src/engine/executor.ts +840 -0
- package/src/engine/httpError.test.ts +12 -0
- package/src/engine/httpError.ts +16 -0
- package/src/engine/index.ts +11 -0
- package/src/engine/output.ts +21 -0
- package/src/engine/publish.test.ts +176 -0
- package/src/engine/publish.ts +127 -0
- package/src/engine/registry.test.ts +64 -0
- package/src/engine/registry.ts +80 -0
- package/src/engine/scenarios.test.ts +55 -0
- package/src/engine/scenarios.ts +29 -0
- package/src/engine/schemaCheck.test.ts +21 -0
- package/src/engine/schemaCheck.ts +35 -0
- package/src/engine/trace.test.ts +19 -0
- package/src/engine/trace.ts +23 -0
- package/src/engine/types.test.ts +22 -0
- package/src/engine/types.ts +316 -0
- package/src/engine/validation.test.ts +314 -0
- package/src/engine/validation.ts +354 -0
- package/src/flows/anomaly-flows.test.ts +81 -0
- package/src/flows/anomaly-flows.ts +202 -0
- package/src/flows/city-sweep-flow.ts +131 -0
- package/src/flows/flows.test.ts +30 -0
- package/src/flows/login-flow.ts +150 -0
- package/src/flows/pradar-flows.test.ts +204 -0
- package/src/flows/pradar-flows.ts +419 -0
- package/src/flows/weather-flow.ts +154 -0
- package/src/globals.css +147 -0
- package/src/lib/pathParams.test.ts +24 -0
- package/src/lib/pathParams.ts +6 -0
- package/src/lib/registerLens.test.ts +79 -0
- package/src/lib/registerLens.ts +59 -0
- package/src/lib/runbookModel.test.ts +61 -0
- package/src/lib/runbookModel.ts +290 -0
- package/src/lib/runbookProjection.test.ts +417 -0
- package/src/lib/runbookProjection.ts +254 -0
- package/src/lib/useTailAnchor.ts +53 -0
- package/src/lib/utils.ts +7 -0
- package/src/main.tsx +13 -0
- package/src/nodes/anomaly.test.ts +98 -0
- package/src/nodes/anomaly.ts +637 -0
- package/src/nodes/effects-scan.test.ts +44 -0
- package/src/nodes/flow-control.test.ts +280 -0
- package/src/nodes/flow-control.ts +305 -0
- package/src/nodes/index.ts +22 -0
- package/src/nodes/login.test.ts +81 -0
- package/src/nodes/login.ts +186 -0
- package/src/nodes/loops.ts +69 -0
- package/src/nodes/pradar/index.ts +11 -0
- package/src/nodes/pradar/logic.test.ts +288 -0
- package/src/nodes/pradar/logic.ts +582 -0
- package/src/nodes/pradar/nodes.test.ts +146 -0
- package/src/nodes/pradar/nodes.ts +955 -0
- package/src/nodes/requireAuth.test.ts +75 -0
- package/src/nodes/requireAuth.ts +55 -0
- package/src/nodes/response.test.ts +33 -0
- package/src/nodes/response.ts +39 -0
- package/src/nodes/traceDetail.test.ts +39 -0
- package/src/nodes/weather.test.ts +65 -0
- package/src/nodes/weather.ts +306 -0
- package/src/store/agentClient.test.ts +103 -0
- package/src/store/agentClient.ts +131 -0
- package/src/store/apiTree.test.ts +17 -0
- package/src/store/apiTree.ts +73 -0
- package/src/store/builderStore.test.ts +1234 -0
- package/src/store/builderStore.ts +1879 -0
- package/src/store/infraClient.ts +63 -0
- package/src/store/nodeMeta.test.ts +24 -0
- package/src/store/nodeMeta.ts +21 -0
- package/src/store/persistence.test.ts +15 -0
- package/src/store/persistence.ts +54 -0
- package/src/store/serverRunner.ts +381 -0
- package/src/store/setupClient.ts +57 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/studio-dist/assets/index-DNJwW-hM.css +1 -0
- package/studio-dist/assets/index-O26dKRjW.js +65 -0
- package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/studio-dist/favicon.svg +1 -0
- package/studio-dist/icons.svg +24 -0
- package/studio-dist/index.html +14 -0
- package/templates/skills/emberflow-basics/SKILL.md +316 -0
- package/templates/skills/emberflow-model-process/SKILL.md +209 -0
- package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
- package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
package/src/globals.css
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@import 'tw-animate-css';
|
|
3
|
+
|
|
4
|
+
@theme inline {
|
|
5
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
6
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
7
|
+
--radius-lg: var(--radius);
|
|
8
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
9
|
+
--color-background: var(--background);
|
|
10
|
+
--color-foreground: var(--foreground);
|
|
11
|
+
--color-card: var(--card);
|
|
12
|
+
--color-card-foreground: var(--card-foreground);
|
|
13
|
+
--color-popover: var(--popover);
|
|
14
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
15
|
+
--color-primary: var(--primary);
|
|
16
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
17
|
+
--color-secondary: var(--secondary);
|
|
18
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
19
|
+
--color-muted: var(--muted);
|
|
20
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
21
|
+
--color-accent: var(--accent);
|
|
22
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
23
|
+
--color-tertiary: var(--tertiary);
|
|
24
|
+
--color-tertiary-active: var(--tertiary-active);
|
|
25
|
+
--color-destructive: var(--destructive);
|
|
26
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
27
|
+
--color-border: var(--border);
|
|
28
|
+
--color-input: var(--input);
|
|
29
|
+
--color-ring: var(--ring);
|
|
30
|
+
--color-highlight: var(--highlight);
|
|
31
|
+
--color-highlight-foreground: var(--highlight-foreground);
|
|
32
|
+
--color-sidebar: var(--sidebar);
|
|
33
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
34
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
35
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
36
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
37
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
38
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
39
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
40
|
+
--color-success: var(--success);
|
|
41
|
+
--color-warn: var(--warn);
|
|
42
|
+
--font-sans: 'Inter Variable', 'Inter', ui-sans-serif, system-ui, sans-serif;
|
|
43
|
+
--font-mono: 'IBM Plex Mono', 'SF Mono', ui-monospace, Menlo, monospace;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Superset desktop "ember" theme (dark default) — tokens verbatim from
|
|
47
|
+
apps/desktop/src/renderer/globals.css in superset-sh/superset. */
|
|
48
|
+
:root {
|
|
49
|
+
color-scheme: dark;
|
|
50
|
+
--background: #151110;
|
|
51
|
+
--foreground: #eae8e6;
|
|
52
|
+
--card: #201e1c;
|
|
53
|
+
--card-foreground: #eae8e6;
|
|
54
|
+
--popover: #201e1c;
|
|
55
|
+
--popover-foreground: #eae8e6;
|
|
56
|
+
--primary: #eae8e6;
|
|
57
|
+
--primary-foreground: #151110;
|
|
58
|
+
--secondary: #2a2827;
|
|
59
|
+
--secondary-foreground: #eae8e6;
|
|
60
|
+
--muted: #2a2827;
|
|
61
|
+
--muted-foreground: #a8a5a3;
|
|
62
|
+
--accent: #2a2827;
|
|
63
|
+
--accent-foreground: #eae8e6;
|
|
64
|
+
--tertiary: #1a1716;
|
|
65
|
+
--tertiary-active: #252220;
|
|
66
|
+
--destructive: #cc4444;
|
|
67
|
+
--destructive-foreground: #ffcccc;
|
|
68
|
+
--border: #2a2827;
|
|
69
|
+
--input: #2a2827;
|
|
70
|
+
--ring: #3a3837;
|
|
71
|
+
--radius: 0.625rem;
|
|
72
|
+
--sidebar: #1a1716;
|
|
73
|
+
--sidebar-foreground: #eae8e6;
|
|
74
|
+
--sidebar-primary: #e07850;
|
|
75
|
+
--sidebar-primary-foreground: #151110;
|
|
76
|
+
--sidebar-accent: #252220;
|
|
77
|
+
--sidebar-accent-foreground: #eae8e6;
|
|
78
|
+
--sidebar-border: #2a2827;
|
|
79
|
+
--sidebar-ring: #3a3837;
|
|
80
|
+
--highlight: #e07850;
|
|
81
|
+
--highlight-foreground: #151110;
|
|
82
|
+
--success: #50a878;
|
|
83
|
+
--warn: #d9a441;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@layer base {
|
|
87
|
+
* {
|
|
88
|
+
@apply border-border outline-ring/50;
|
|
89
|
+
}
|
|
90
|
+
body {
|
|
91
|
+
@apply bg-background text-foreground font-sans text-[13px] antialiased;
|
|
92
|
+
}
|
|
93
|
+
::-webkit-scrollbar {
|
|
94
|
+
width: 8px;
|
|
95
|
+
height: 8px;
|
|
96
|
+
}
|
|
97
|
+
::-webkit-scrollbar-thumb {
|
|
98
|
+
background: rgb(63 63 70 / 0.5);
|
|
99
|
+
border-radius: 6px;
|
|
100
|
+
}
|
|
101
|
+
::-webkit-scrollbar-track {
|
|
102
|
+
background: transparent;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ── App shell ── */
|
|
107
|
+
|
|
108
|
+
#root { height: 100vh; }
|
|
109
|
+
|
|
110
|
+
/* Runbook step-active breathing: an ember pulse scaled down for the 9px inline
|
|
111
|
+
status square that marks the step currently being revealed. */
|
|
112
|
+
.ember-step-active {
|
|
113
|
+
animation: ember-step-pulse 1.3s ease-in-out infinite;
|
|
114
|
+
}
|
|
115
|
+
@keyframes ember-step-pulse {
|
|
116
|
+
0%,
|
|
117
|
+
100% {
|
|
118
|
+
box-shadow:
|
|
119
|
+
0 0 0 1px rgb(224 120 80 / 0.6),
|
|
120
|
+
0 0 6px rgb(224 120 80 / 0.35);
|
|
121
|
+
}
|
|
122
|
+
50% {
|
|
123
|
+
box-shadow:
|
|
124
|
+
0 0 0 2px rgb(224 120 80 / 0.4),
|
|
125
|
+
0 0 10px rgb(224 120 80 / 0.6);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
@media (prefers-reduced-motion: reduce) {
|
|
129
|
+
.ember-step-active {
|
|
130
|
+
animation: none;
|
|
131
|
+
box-shadow:
|
|
132
|
+
0 0 0 2px rgb(224 120 80 / 0.5),
|
|
133
|
+
0 0 8px rgb(224 120 80 / 0.4);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Panel choreography: layout panels (sidebar toggle, dock, inspector) ease
|
|
138
|
+
into their new sizes instead of snapping. Suspended while a resize handle
|
|
139
|
+
is actively dragged so the pointer never lags, and under reduced motion. */
|
|
140
|
+
[data-panel-group]:not(:has([data-resize-handle-state='drag'])) > [data-panel] {
|
|
141
|
+
transition: flex-grow 280ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
142
|
+
}
|
|
143
|
+
@media (prefers-reduced-motion: reduce) {
|
|
144
|
+
[data-panel-group] > [data-panel] {
|
|
145
|
+
transition: none;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parsePathParams } from './pathParams';
|
|
3
|
+
|
|
4
|
+
// The Run-guard semantics that used to live here (missingPathParams) moved to
|
|
5
|
+
// src/engine/diagnostics.ts as the `missing-param-default` diagnostic; its
|
|
6
|
+
// behavior contract is covered by src/engine/diagnostics.test.ts.
|
|
7
|
+
|
|
8
|
+
describe('parsePathParams', () => {
|
|
9
|
+
it('extracts a single param', () => {
|
|
10
|
+
expect(parsePathParams('/api/channels/:id/approvals')).toEqual(['id']);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('extracts multiple params in order', () => {
|
|
14
|
+
expect(parsePathParams('/api/channels/:id/approvals/:approvalId')).toEqual(['id', 'approvalId']);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns empty array when there are no colon segments', () => {
|
|
18
|
+
expect(parsePathParams('/api/channels')).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('ignores standalone colons that do not start a param name', () => {
|
|
22
|
+
expect(parsePathParams('/api/channels/id')).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Extract `:name` path-param segments from an HTTP path, in order, e.g.
|
|
2
|
+
* `/api/channels/:id/approvals/:approvalId` → `['id', 'approvalId']`. */
|
|
3
|
+
export function parsePathParams(path: string): string[] {
|
|
4
|
+
const matches = path.match(/:([A-Za-z0-9_]+)/g) ?? [];
|
|
5
|
+
return matches.map((m) => m.slice(1));
|
|
6
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { filterLogs, firstSentence, formatDuration, keyValueRows, payloadOpenByDefault, simpleNodeDescription } from './registerLens';
|
|
3
|
+
import type { LogLine } from '../engine';
|
|
4
|
+
|
|
5
|
+
const line = (level: LogLine['level'], message: string): LogLine => ({
|
|
6
|
+
timestamp: '2026-07-04T00:00:00Z', level, runId: 'r1', nodeId: 'n1', nodeLabel: 'N', message,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('filterLogs', () => {
|
|
10
|
+
const logs = [line('debug', '#1 ▶ execute'), line('info', 'ok'), line('warn', 'careful'), line('error', 'boom')];
|
|
11
|
+
it('simple drops debug only — warn/error always survive', () => {
|
|
12
|
+
expect(filterLogs(logs, 'simple').map((l) => l.level)).toEqual(['info', 'warn', 'error']);
|
|
13
|
+
});
|
|
14
|
+
it('technical keeps everything', () => {
|
|
15
|
+
expect(filterLogs(logs, 'technical')).toHaveLength(4);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('formatDuration', () => {
|
|
20
|
+
it('simple rounds calm', () => {
|
|
21
|
+
expect(formatDuration(337, 'simple')).toBe('340ms');
|
|
22
|
+
expect(formatDuration(4636, 'simple')).toBe('4.6s');
|
|
23
|
+
expect(formatDuration(4, 'simple')).toBe('<10ms');
|
|
24
|
+
expect(formatDuration(997, 'simple')).toBe('1.0s');
|
|
25
|
+
});
|
|
26
|
+
it('technical stays exact', () => {
|
|
27
|
+
expect(formatDuration(337, 'technical')).toBe('337ms');
|
|
28
|
+
expect(formatDuration(4636, 'technical')).toBe('4.64s');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('payloadOpenByDefault', () => {
|
|
33
|
+
it('simple collapsed, technical open', () => {
|
|
34
|
+
expect(payloadOpenByDefault('simple')).toBe(false);
|
|
35
|
+
expect(payloadOpenByDefault('technical')).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('keyValueRows', () => {
|
|
40
|
+
it('primitives, truncation, and shape summaries', () => {
|
|
41
|
+
const rows = keyValueRows({
|
|
42
|
+
surplus: 2500, ok: true, note: 'x'.repeat(150),
|
|
43
|
+
series: [1, 2, 3], nested: { a: 1, b: 2 },
|
|
44
|
+
});
|
|
45
|
+
expect(rows[0]).toEqual({ key: 'surplus', display: '2500', complex: false });
|
|
46
|
+
expect(rows[1]).toEqual({ key: 'ok', display: 'true', complex: false });
|
|
47
|
+
expect(rows[2].display.endsWith('…')).toBe(true);
|
|
48
|
+
expect(rows[2].display.length).toBe(121);
|
|
49
|
+
expect(rows[3]).toEqual({ key: 'series', display: '[3 items]', complex: true });
|
|
50
|
+
expect(rows[4]).toEqual({ key: 'nested', display: '{2 keys}', complex: true });
|
|
51
|
+
});
|
|
52
|
+
it('non-object input yields empty list', () => {
|
|
53
|
+
expect(keyValueRows(null)).toEqual([]);
|
|
54
|
+
expect(keyValueRows('str')).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('firstSentence', () => {
|
|
59
|
+
it('takes the text before the first period', () => {
|
|
60
|
+
expect(firstSentence('Loads the thing. PORT NOTE: mechanism detail.')).toBe('Loads the thing');
|
|
61
|
+
});
|
|
62
|
+
it('empty input yields empty output', () => {
|
|
63
|
+
expect(firstSentence('')).toBe('');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('simpleNodeDescription', () => {
|
|
68
|
+
it('prefers an authored simpleDescription over the technical one', () => {
|
|
69
|
+
expect(simpleNodeDescription({ description: 'Verbatim port of X. Mechanism.', simpleDescription: 'Does the thing' })).toBe(
|
|
70
|
+
'Does the thing',
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
it('falls back to the first sentence of description when absent', () => {
|
|
74
|
+
expect(simpleNodeDescription({ description: 'Does the thing. Mechanism detail.' })).toBe('Does the thing');
|
|
75
|
+
});
|
|
76
|
+
it('is empty for an undefined definition', () => {
|
|
77
|
+
expect(simpleNodeDescription(undefined)).toBe('');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { LogLine } from '../engine';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The register lens: every simple-vs-technical presentation decision lives
|
|
5
|
+
* here, so the run console, dock, and Inspector cannot drift apart. One event
|
|
6
|
+
* stream, two projections — simple hides mechanism (debug receipts, exact
|
|
7
|
+
* timings, raw payload walls), never consequence (warn/error lines, effects,
|
|
8
|
+
* environment state are identical in both registers).
|
|
9
|
+
*/
|
|
10
|
+
export type ViewRegister = 'simple' | 'technical';
|
|
11
|
+
|
|
12
|
+
export function filterLogs(logs: LogLine[], register: ViewRegister): LogLine[] {
|
|
13
|
+
if (register === 'technical') return logs;
|
|
14
|
+
return logs.filter((l) => l.level !== 'debug');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function formatDuration(ms: number, register: ViewRegister): string {
|
|
18
|
+
if (register === 'technical') {
|
|
19
|
+
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(2)}s`;
|
|
20
|
+
}
|
|
21
|
+
if (ms < 10) return '<10ms';
|
|
22
|
+
if (ms < 1000) {
|
|
23
|
+
// 995–999 round up to 1000 — hand those to the seconds branch so the
|
|
24
|
+
// display never reads '1000ms' beside '1.0s'.
|
|
25
|
+
const rounded = Math.round(ms / 10) * 10;
|
|
26
|
+
return rounded >= 1000 ? `${(rounded / 1000).toFixed(1)}s` : `${rounded}ms`;
|
|
27
|
+
}
|
|
28
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function payloadOpenByDefault(register: ViewRegister): boolean {
|
|
32
|
+
return register === 'technical';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The first sentence of a node description — a cheap plain-reading fallback
|
|
36
|
+
* for nodes that haven't been given a `simpleDescription` yet. */
|
|
37
|
+
export function firstSentence(text: string): string {
|
|
38
|
+
return text ? text.split('.')[0].trim() : '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Simple-register description line for a node: its authored plain-language
|
|
42
|
+
* one-liner, or the first sentence of the technical description, honest empty
|
|
43
|
+
* string when neither exists. */
|
|
44
|
+
export function simpleNodeDescription(definition: { description?: string; simpleDescription?: string } | undefined): string {
|
|
45
|
+
return definition?.simpleDescription ?? firstSentence(definition?.description ?? '');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function keyValueRows(value: unknown): Array<{ key: string; display: string; complex: boolean }> {
|
|
49
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return [];
|
|
50
|
+
return Object.entries(value as Record<string, unknown>).map(([key, v]) => {
|
|
51
|
+
if (Array.isArray(v)) return { key, display: `[${v.length} items]`, complex: true };
|
|
52
|
+
if (v !== null && typeof v === 'object') {
|
|
53
|
+
return { key, display: `{${Object.keys(v).length} keys}`, complex: true };
|
|
54
|
+
}
|
|
55
|
+
const raw = String(v);
|
|
56
|
+
const display = raw.length > 120 ? `${raw.slice(0, 120)}…` : raw;
|
|
57
|
+
return { key, display, complex: false };
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildRunbook } from './runbookModel';
|
|
3
|
+
import { createPradarFlows } from '../flows/pradar-flows';
|
|
4
|
+
import { createDefaultRegistry } from '../nodes';
|
|
5
|
+
|
|
6
|
+
const registry = createDefaultRegistry();
|
|
7
|
+
const flow = (id: string) =>
|
|
8
|
+
[...createPradarFlows()].find((f) => f.id === id)!;
|
|
9
|
+
|
|
10
|
+
function flat(items: any[], out: any[] = []): any[] {
|
|
11
|
+
for (const it of items) { out.push(it); if (it.items) flat(it.items, out); }
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('buildRunbook', () => {
|
|
16
|
+
it('covers every node exactly once (steps + loop headers), Collect excluded', () => {
|
|
17
|
+
for (const id of ['ev-evaluate-cycle']) {
|
|
18
|
+
const f = flow(id);
|
|
19
|
+
const doc = buildRunbook(f, registry);
|
|
20
|
+
const all = flat(doc.items);
|
|
21
|
+
const stepIds = all.filter((i) => i.kind === 'step').map((i) => i.nodeId);
|
|
22
|
+
const loopHeads = all.filter((i) => i.kind === 'loop');
|
|
23
|
+
const collectIds = loopHeads.map((l) => l.collectId);
|
|
24
|
+
const forEachIds = loopHeads.map((l) => l.forEachId);
|
|
25
|
+
const expected = f.nodes.map((n) => n.id).filter((id2) => !collectIds.includes(id2) && !forEachIds.includes(id2));
|
|
26
|
+
expect([...stepIds].sort()).toEqual([...expected].sort());
|
|
27
|
+
expect(new Set(stepIds).size).toBe(stepIds.length);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('ev-evaluate-cycle: night-window work is nested under the off arm, not a sibling of solar work', () => {
|
|
32
|
+
const doc = buildRunbook(flow('ev-evaluate-cycle'), registry);
|
|
33
|
+
const all = flat(doc.items);
|
|
34
|
+
const night = all.find((i) => i.kind === 'step' && i.nodeId === 'subflowNight');
|
|
35
|
+
const surplus = all.find((i) => i.kind === 'step' && i.nodeId === 'surplus');
|
|
36
|
+
expect(night.depth).toBeGreaterThan(0);
|
|
37
|
+
expect(surplus.depth).toBeGreaterThan(0);
|
|
38
|
+
// they sit under DIFFERENT arms of condSolar
|
|
39
|
+
const nightGuards = doc.guards.get('subflowNight')!.map((g) => `${g.ownerId}:${g.arm}`);
|
|
40
|
+
const surplusGuards = doc.guards.get('surplus')!.map((g) => `${g.ownerId}:${g.arm}`);
|
|
41
|
+
expect(nightGuards.some((g) => g.startsWith('condSolar:'))).toBe(true);
|
|
42
|
+
expect(surplusGuards.some((g) => g.startsWith('condSolar:'))).toBe(true);
|
|
43
|
+
expect(nightGuards.find((g) => g.startsWith('condSolar:'))).not.toBe(
|
|
44
|
+
surplusGuards.find((g) => g.startsWith('condSolar:')));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('numbers are hierarchical and unique', () => {
|
|
48
|
+
const doc = buildRunbook(flow('ev-evaluate-cycle'), registry);
|
|
49
|
+
const nums = flat(doc.items).map((i) => i.number);
|
|
50
|
+
expect(new Set(nums).size).toBe(nums.length);
|
|
51
|
+
expect(nums.some((n) => /^\d+\.\d+$/.test(n))).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('Subflow steps carry subflowId; mutation steps carry mutation flag', () => {
|
|
55
|
+
const doc = buildRunbook(flow('ev-evaluate-cycle'), registry);
|
|
56
|
+
const all = flat(doc.items);
|
|
57
|
+
const sub = all.find((i) => i.kind === 'step' && i.subflow);
|
|
58
|
+
expect(sub.subflowId).toBe('ev-night-charge');
|
|
59
|
+
expect(all.some((i) => i.kind === 'step' && i.mutation)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import type { WorkflowDefinition, WorkflowEdge } from '../engine';
|
|
2
|
+
import { computeLoopRegions, topoSort, type LoopRegion } from '../engine';
|
|
3
|
+
import type { NodeRegistry } from '../engine';
|
|
4
|
+
import { firstSentence, simpleNodeDescription } from './registerLens';
|
|
5
|
+
|
|
6
|
+
export interface RunbookStep {
|
|
7
|
+
kind: 'step';
|
|
8
|
+
nodeId: string;
|
|
9
|
+
number: string; // "3" or "3.2" or "3.2.1"
|
|
10
|
+
depth: number; // 0 = root
|
|
11
|
+
label: string; // node.label
|
|
12
|
+
typeName: string; // node.type
|
|
13
|
+
description: string; // registry definition.description first sentence, '' if unknown type
|
|
14
|
+
simpleDescription: string; // definition.simpleDescription, else same as `description`
|
|
15
|
+
traceKind?: 'db' | 'http' | 'llm' | 'compute'; // definition.traceKind, for the technical register badge
|
|
16
|
+
mutation: boolean; // definition.effects === 'mutation'
|
|
17
|
+
subflow: boolean; // node.type === 'Subflow'
|
|
18
|
+
subflowId?: string; // config.workflowId when subflow
|
|
19
|
+
decisionArms?: string[]; // outgoing sourceHandles when this node branches but arms are rendered as groups elsewhere
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RunbookBranchGroup {
|
|
23
|
+
kind: 'branch';
|
|
24
|
+
ownerId: string; // the branching node
|
|
25
|
+
arm: string; // sourceHandle name
|
|
26
|
+
number: string;
|
|
27
|
+
depth: number;
|
|
28
|
+
items: RunbookItem[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RunbookLoopGroup {
|
|
32
|
+
kind: 'loop';
|
|
33
|
+
forEachId: string;
|
|
34
|
+
collectId: string;
|
|
35
|
+
number: string;
|
|
36
|
+
depth: number;
|
|
37
|
+
items: RunbookItem[]; // body steps (Collect excluded; ForEach is the header)
|
|
38
|
+
label: string; // ForEach node label
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type RunbookItem = RunbookStep | RunbookBranchGroup | RunbookLoopGroup;
|
|
42
|
+
|
|
43
|
+
export interface RunbookDoc {
|
|
44
|
+
items: RunbookItem[];
|
|
45
|
+
/** nodeId -> the (ownerId, arm) guards that gate it; used by projection for taken/dim state. */
|
|
46
|
+
guards: Map<string, Array<{ ownerId: string; arm: string }>>;
|
|
47
|
+
/** every (ownerId, arm) pair that exists in the flow, for coverage math. */
|
|
48
|
+
arms: Array<{ ownerId: string; arm: string }>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface GuardEntry {
|
|
52
|
+
ownerId: string;
|
|
53
|
+
arm: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Builds the guard set for every node in `nodeIds`, walking `order` (already
|
|
58
|
+
* topologically valid for this subset) and intersecting incoming guard sets —
|
|
59
|
+
* a node reached via two different arms of the same owner is a join and
|
|
60
|
+
* carries neither arm (see executor.isReachable for the same branch-guard
|
|
61
|
+
* semantics, mirrored here for grouping instead of execution).
|
|
62
|
+
*/
|
|
63
|
+
function buildGuardMap(
|
|
64
|
+
nodeIds: Set<string>,
|
|
65
|
+
order: string[],
|
|
66
|
+
edges: WorkflowEdge[],
|
|
67
|
+
): Map<string, Set<string>> {
|
|
68
|
+
const incoming = new Map<string, WorkflowEdge[]>();
|
|
69
|
+
for (const e of edges) {
|
|
70
|
+
if (!nodeIds.has(e.source) || !nodeIds.has(e.target)) continue;
|
|
71
|
+
if (!incoming.has(e.target)) incoming.set(e.target, []);
|
|
72
|
+
incoming.get(e.target)!.push(e);
|
|
73
|
+
}
|
|
74
|
+
const guardMap = new Map<string, Set<string>>();
|
|
75
|
+
for (const id of order) {
|
|
76
|
+
const inc = incoming.get(id) ?? [];
|
|
77
|
+
if (inc.length === 0) {
|
|
78
|
+
guardMap.set(id, new Set());
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// Each edge contributes guardSet(source) ∪ {source::handle if branch
|
|
82
|
+
// edge}. Edges that carry no gating information at all (no handle AND an
|
|
83
|
+
// unguarded source, e.g. a data edge straight from "input") are excluded
|
|
84
|
+
// from the intersection — they must not smuggle a node out of its branch.
|
|
85
|
+
// But an unguarded-looking plain edge whose SOURCE inherited guards does
|
|
86
|
+
// participate: a join fed by arm X directly and arm Y through a chain
|
|
87
|
+
// intersects {X} ∩ {Y} = ∅ and correctly carries neither. If every edge
|
|
88
|
+
// is information-free the guard set is ∅.
|
|
89
|
+
let result: Set<string> | undefined;
|
|
90
|
+
for (const e of inc) {
|
|
91
|
+
const base = guardMap.get(e.source) ?? new Set<string>();
|
|
92
|
+
if (!e.sourceHandle && base.size === 0) continue; // information-free
|
|
93
|
+
const g = new Set(base);
|
|
94
|
+
if (e.sourceHandle) g.add(`${e.source}::${e.sourceHandle}`);
|
|
95
|
+
result = result === undefined ? g : new Set([...result].filter((x) => g.has(x)));
|
|
96
|
+
}
|
|
97
|
+
guardMap.set(id, result ?? new Set());
|
|
98
|
+
}
|
|
99
|
+
return guardMap;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function guardPath(id: string, guardMap: Map<string, Set<string>>, order: string[]): GuardEntry[] {
|
|
103
|
+
const set = guardMap.get(id) ?? new Set<string>();
|
|
104
|
+
const indexOf = (nodeId: string) => order.indexOf(nodeId);
|
|
105
|
+
return [...set]
|
|
106
|
+
.map((s): GuardEntry => {
|
|
107
|
+
const idx = s.indexOf('::');
|
|
108
|
+
return { ownerId: s.slice(0, idx), arm: s.slice(idx + 2) };
|
|
109
|
+
})
|
|
110
|
+
.sort((a, b) => indexOf(a.ownerId) - indexOf(b.ownerId));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function sameGuard(a: GuardEntry, b: GuardEntry): boolean {
|
|
114
|
+
return a.ownerId === b.ownerId && a.arm === b.arm;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Distinct outgoing sourceHandles of `id`, in the order they first appear in flow.edges. */
|
|
118
|
+
function distinctArms(edges: WorkflowEdge[], id: string): string[] {
|
|
119
|
+
const seen = new Set<string>();
|
|
120
|
+
const arms: string[] = [];
|
|
121
|
+
for (const e of edges) {
|
|
122
|
+
if (e.source === id && e.sourceHandle && !seen.has(e.sourceHandle)) {
|
|
123
|
+
seen.add(e.sourceHandle);
|
|
124
|
+
arms.push(e.sourceHandle);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return arms;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function buildRunbook(flow: WorkflowDefinition, registry: NodeRegistry): RunbookDoc {
|
|
131
|
+
const order = topoSort(flow);
|
|
132
|
+
const regions = computeLoopRegions(flow);
|
|
133
|
+
|
|
134
|
+
const bodyNodeIds = new Set<string>(regions.flatMap((r) => r.bodyIds));
|
|
135
|
+
const collectIds = new Set<string>(regions.map((r) => r.collectId));
|
|
136
|
+
const regionByForEach = new Map<string, LoopRegion>(regions.map((r) => [r.forEachId, r]));
|
|
137
|
+
|
|
138
|
+
// ── root scope: everything except loop-body-internal nodes ──
|
|
139
|
+
const extendedNodeIds = new Set<string>(flow.nodes.map((n) => n.id).filter((id) => !bodyNodeIds.has(id)));
|
|
140
|
+
const syntheticEdges: WorkflowEdge[] = regions.map((r) => ({
|
|
141
|
+
id: `synthetic-${r.forEachId}`,
|
|
142
|
+
source: r.forEachId,
|
|
143
|
+
target: r.collectId,
|
|
144
|
+
}));
|
|
145
|
+
const rootEdges: WorkflowEdge[] = [
|
|
146
|
+
...flow.edges.filter(
|
|
147
|
+
(e) => extendedNodeIds.has(e.source) && extendedNodeIds.has(e.target) && !collectIds.has(e.target),
|
|
148
|
+
),
|
|
149
|
+
...syntheticEdges,
|
|
150
|
+
];
|
|
151
|
+
const rootOrder = order.filter((id) => extendedNodeIds.has(id));
|
|
152
|
+
const rootGuardMap = buildGuardMap(extendedNodeIds, rootOrder, rootEdges);
|
|
153
|
+
const rootPool = order.filter((id) => !bodyNodeIds.has(id) && !collectIds.has(id));
|
|
154
|
+
|
|
155
|
+
// ── per-region body scope: guards computed relative to the body's own node set ──
|
|
156
|
+
const bodyScopes = new Map<
|
|
157
|
+
string,
|
|
158
|
+
{ bodySet: Set<string>; bodyOrder: string[]; bodyGuardMap: Map<string, Set<string>> }
|
|
159
|
+
>();
|
|
160
|
+
for (const region of regions) {
|
|
161
|
+
const bodySet = new Set(region.bodyIds);
|
|
162
|
+
const bodyEdges = flow.edges.filter((e) => bodySet.has(e.source) && bodySet.has(e.target));
|
|
163
|
+
const bodyOrder = order.filter((id) => bodySet.has(id));
|
|
164
|
+
const bodyGuardMap = buildGuardMap(bodySet, bodyOrder, bodyEdges);
|
|
165
|
+
bodyScopes.set(region.forEachId, { bodySet, bodyOrder, bodyGuardMap });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function buildStep(id: string): RunbookStep {
|
|
169
|
+
const node = flow.nodes.find((n) => n.id === id)!;
|
|
170
|
+
const registered = registry.has(node.type) ? registry.get(node.type) : undefined;
|
|
171
|
+
const description = firstSentence(registered?.definition.description ?? '');
|
|
172
|
+
const simpleDescription = simpleNodeDescription(registered?.definition);
|
|
173
|
+
const mutation = registered?.definition.effects === 'mutation';
|
|
174
|
+
const subflow = node.type === 'Subflow';
|
|
175
|
+
const step: RunbookStep = {
|
|
176
|
+
kind: 'step',
|
|
177
|
+
nodeId: id,
|
|
178
|
+
number: '',
|
|
179
|
+
depth: 0,
|
|
180
|
+
label: node.label,
|
|
181
|
+
typeName: node.type,
|
|
182
|
+
description,
|
|
183
|
+
simpleDescription,
|
|
184
|
+
mutation,
|
|
185
|
+
subflow,
|
|
186
|
+
};
|
|
187
|
+
if (registered?.definition.traceKind) step.traceKind = registered.definition.traceKind;
|
|
188
|
+
if (subflow) step.subflowId = node.config.workflowId as string;
|
|
189
|
+
return step;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildLoopGroup(region: LoopRegion): RunbookLoopGroup {
|
|
193
|
+
const scope = bodyScopes.get(region.forEachId)!;
|
|
194
|
+
const items = buildLevel(scope.bodyOrder, [], scope.bodyGuardMap);
|
|
195
|
+
const forEachNode = flow.nodes.find((n) => n.id === region.forEachId)!;
|
|
196
|
+
return {
|
|
197
|
+
kind: 'loop',
|
|
198
|
+
forEachId: region.forEachId,
|
|
199
|
+
collectId: region.collectId,
|
|
200
|
+
number: '',
|
|
201
|
+
depth: 0,
|
|
202
|
+
items,
|
|
203
|
+
label: forEachNode.label,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Builds the item list for one level of nesting. `pool` holds every node
|
|
209
|
+
* id whose guard path (in `guardMap`) has `context` as a prefix — nodes
|
|
210
|
+
* further nested are deferred to the recursive call triggered by their
|
|
211
|
+
* governing branch owner, encountered later in this same loop.
|
|
212
|
+
*/
|
|
213
|
+
function buildLevel(
|
|
214
|
+
pool: string[],
|
|
215
|
+
context: GuardEntry[],
|
|
216
|
+
guardMap: Map<string, Set<string>>,
|
|
217
|
+
): RunbookItem[] {
|
|
218
|
+
const items: RunbookItem[] = [];
|
|
219
|
+
for (const id of pool) {
|
|
220
|
+
const gp = guardPath(id, guardMap, order);
|
|
221
|
+
if (gp.length !== context.length) continue; // belongs to a deeper level; handled via its owner below
|
|
222
|
+
|
|
223
|
+
const region = regionByForEach.get(id);
|
|
224
|
+
if (region) {
|
|
225
|
+
items.push(buildLoopGroup(region));
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const step = buildStep(id);
|
|
230
|
+
items.push(step);
|
|
231
|
+
|
|
232
|
+
const arms = distinctArms(flow.edges, id);
|
|
233
|
+
if (arms.length === 0) continue;
|
|
234
|
+
|
|
235
|
+
const leftover: string[] = [];
|
|
236
|
+
for (const arm of arms) {
|
|
237
|
+
const entry: GuardEntry = { ownerId: id, arm };
|
|
238
|
+
const childPool = pool.filter((pid) => {
|
|
239
|
+
const pgp = guardPath(pid, guardMap, order);
|
|
240
|
+
return pgp.length > context.length && sameGuard(pgp[context.length], entry);
|
|
241
|
+
});
|
|
242
|
+
if (childPool.length === 0) {
|
|
243
|
+
leftover.push(arm);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const childItems = buildLevel(childPool, [...context, entry], guardMap);
|
|
247
|
+
items.push({ kind: 'branch', ownerId: id, arm, number: '', depth: 0, items: childItems });
|
|
248
|
+
}
|
|
249
|
+
if (leftover.length > 0) step.decisionArms = leftover;
|
|
250
|
+
}
|
|
251
|
+
return items;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const rootItems = buildLevel(rootPool, [], rootGuardMap);
|
|
255
|
+
|
|
256
|
+
function numberItems(items: RunbookItem[], prefix: string, depth: number): void {
|
|
257
|
+
items.forEach((item, i) => {
|
|
258
|
+
const number = prefix ? `${prefix}.${i + 1}` : `${i + 1}`;
|
|
259
|
+
item.number = number;
|
|
260
|
+
item.depth = depth;
|
|
261
|
+
if (item.kind !== 'step') numberItems(item.items, number, depth + 1);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
numberItems(rootItems, '', 0);
|
|
265
|
+
|
|
266
|
+
// ── guards: every node's full guard chain, for projection's taken/dim math ──
|
|
267
|
+
const guards = new Map<string, Array<{ ownerId: string; arm: string }>>();
|
|
268
|
+
for (const id of extendedNodeIds) {
|
|
269
|
+
guards.set(id, guardPath(id, rootGuardMap, rootOrder));
|
|
270
|
+
}
|
|
271
|
+
for (const region of regions) {
|
|
272
|
+
const scope = bodyScopes.get(region.forEachId)!;
|
|
273
|
+
for (const id of region.bodyIds) {
|
|
274
|
+
guards.set(id, guardPath(id, scope.bodyGuardMap, scope.bodyOrder));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── arms: every (ownerId, arm) pair declared anywhere in the flow ──
|
|
279
|
+
const armsSeen = new Set<string>();
|
|
280
|
+
const arms: Array<{ ownerId: string; arm: string }> = [];
|
|
281
|
+
for (const e of flow.edges) {
|
|
282
|
+
if (!e.sourceHandle) continue;
|
|
283
|
+
const key = `${e.source}::${e.sourceHandle}`;
|
|
284
|
+
if (armsSeen.has(key)) continue;
|
|
285
|
+
armsSeen.add(key);
|
|
286
|
+
arms.push({ ownerId: e.source, arm: e.sourceHandle });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { items: rootItems, guards, arms };
|
|
290
|
+
}
|