@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
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { ArrowUpIcon, ChevronRightIcon, SparklesIcon, TerminalIcon, XIcon, ZapIcon } from 'lucide-react';
|
|
3
|
+
import { Badge } from '@/components/ui/badge';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
import { useBuilderStore } from '../store/builderStore';
|
|
7
|
+
import type { AgentEvent } from '../store/agentClient';
|
|
8
|
+
import { renderAgentMarkdown } from './agentMarkdown';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const statusDot: Record<string, string> = {
|
|
12
|
+
running: 'bg-highlight animate-pulse',
|
|
13
|
+
done: 'bg-success',
|
|
14
|
+
error: 'bg-destructive',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** A command's status → a small glyph colour. */
|
|
18
|
+
const cmdDot: Record<string, string> = {
|
|
19
|
+
in_progress: 'bg-highlight animate-pulse',
|
|
20
|
+
completed: 'bg-success/70',
|
|
21
|
+
failed: 'bg-destructive',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** The `emberflow` CLI operation commands the agent uses (run/inspect/delete). */
|
|
25
|
+
const EMBERFLOW_OPS = [
|
|
26
|
+
'run', 'get-workflow', 'list-workflows', 'list-nodes', 'node-schema', 'list-environments',
|
|
27
|
+
'create', 'delete', 'rename', 'validate', 'publish', 'save',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/** If a shell command invokes the emberflow CLI bin with an operation command,
|
|
31
|
+
* pull out the operation + its target id so it can render as a distinct step. */
|
|
32
|
+
function parseEmberflowOp(command?: string): { op: string; target?: string } | null {
|
|
33
|
+
if (!command || !/emberflow\.mjs\b/.test(command)) return null;
|
|
34
|
+
const after = command.split(/emberflow\.mjs\b/)[1] ?? '';
|
|
35
|
+
const toks = after.trim().split(/\s+/).filter(Boolean);
|
|
36
|
+
const op = toks.find((t) => EMBERFLOW_OPS.includes(t));
|
|
37
|
+
if (!op) return null;
|
|
38
|
+
const target = toks.slice(toks.indexOf(op) + 1).find((t) => !t.startsWith('-'));
|
|
39
|
+
return { op, target };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A run of consecutive shell commands, collapsed to a single quiet line
|
|
44
|
+
* ("Ran N commands") that expands to the individual commands + their output.
|
|
45
|
+
* A failed command in the group forces it open and tints it. This is the big
|
|
46
|
+
* de-noiser: a single edit can run 30 inspection commands — nobody wants 30
|
|
47
|
+
* rows, they want the reasoning with the mechanics tucked underneath.
|
|
48
|
+
*/
|
|
49
|
+
function CommandGroup({ commands }: { commands: AgentEvent[] }) {
|
|
50
|
+
const anyFailed = commands.some((c) => c.commandStatus === 'failed');
|
|
51
|
+
const [open, setOpen] = useState(anyFailed);
|
|
52
|
+
const running = commands.some((c) => c.commandStatus === 'in_progress');
|
|
53
|
+
const label = commands.length === 1 ? '1 command' : `${commands.length} commands`;
|
|
54
|
+
return (
|
|
55
|
+
<div className="my-0.5">
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
onClick={() => setOpen((o) => !o)}
|
|
59
|
+
className="group flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left transition-colors hover:bg-secondary/50"
|
|
60
|
+
>
|
|
61
|
+
<ChevronRightIcon
|
|
62
|
+
className={cn('size-3 shrink-0 text-muted-foreground/50 transition-transform', open && 'rotate-90')}
|
|
63
|
+
/>
|
|
64
|
+
<span
|
|
65
|
+
className={cn(
|
|
66
|
+
'size-1.5 shrink-0 rounded-full',
|
|
67
|
+
running ? cmdDot.in_progress : anyFailed ? cmdDot.failed : cmdDot.completed,
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
<span className={cn('text-[11px]', anyFailed ? 'text-destructive/90' : 'text-muted-foreground/70')}>
|
|
71
|
+
{running ? 'Running' : 'Ran'} {label}
|
|
72
|
+
</span>
|
|
73
|
+
</button>
|
|
74
|
+
{open && (
|
|
75
|
+
<div className="ml-[18px] mt-0.5 space-y-0.5 border-l border-border/50 pl-2">
|
|
76
|
+
{commands.map((c, k) => (
|
|
77
|
+
<div key={k}>
|
|
78
|
+
<div className="flex items-center gap-1.5">
|
|
79
|
+
<span className={cn('size-1 shrink-0 rounded-full', cmdDot[c.commandStatus ?? 'in_progress'])} />
|
|
80
|
+
<span className="min-w-0 truncate font-mono text-[10.5px] text-muted-foreground/80">{c.command}</span>
|
|
81
|
+
{c.commandStatus === 'failed' && c.exitCode != null && (
|
|
82
|
+
<span className="ml-auto shrink-0 font-mono text-[9px] text-destructive">exit {c.exitCode}</span>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
{(c.commandStatus === 'failed' || open) && c.output && c.output.trim() && (
|
|
86
|
+
<pre className="mt-0.5 ml-3 max-h-32 overflow-auto rounded border border-border/50 bg-background/40 p-1.5 font-mono text-[10px] leading-relaxed whitespace-pre-wrap text-muted-foreground/70">
|
|
87
|
+
{c.output}
|
|
88
|
+
</pre>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** An MCP tool call the agent made against the runner (run/list/delete/etc.) —
|
|
99
|
+
* visually distinct from a shell command (MCP-blue, a "MCP" chip), expandable
|
|
100
|
+
* to its result. This is the operational channel: files for edits, MCP for
|
|
101
|
+
* actions like running the operation or deleting it. */
|
|
102
|
+
function McpCall({ e }: { e: AgentEvent }) {
|
|
103
|
+
const [open, setOpen] = useState(false);
|
|
104
|
+
const status = e.mcpStatus ?? 'completed';
|
|
105
|
+
const failed = status === 'failed';
|
|
106
|
+
const running = status === 'in_progress';
|
|
107
|
+
return (
|
|
108
|
+
<div className="my-0.5">
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
onClick={() => setOpen((o) => !o)}
|
|
112
|
+
className="group flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left transition-colors hover:bg-secondary/50"
|
|
113
|
+
>
|
|
114
|
+
<ChevronRightIcon
|
|
115
|
+
className={cn('size-3 shrink-0 text-muted-foreground/50 transition-transform', open && 'rotate-90')}
|
|
116
|
+
/>
|
|
117
|
+
<span className={cn('size-1.5 shrink-0 rounded-full', running ? 'animate-pulse bg-highlight' : failed ? 'bg-destructive' : 'bg-[#8fb8d8]')} />
|
|
118
|
+
<ZapIcon className="size-3 shrink-0 text-[#8fb8d8]" />
|
|
119
|
+
<span className="shrink-0 rounded border border-[#8fb8d8]/40 px-1 font-mono text-[8.5px] uppercase tracking-wide text-[#8fb8d8]">
|
|
120
|
+
MCP
|
|
121
|
+
</span>
|
|
122
|
+
<span className={cn('min-w-0 truncate font-mono text-[11px]', failed ? 'text-destructive/90' : 'text-foreground/85')}>
|
|
123
|
+
{e.mcpServer}·{e.mcpTool}
|
|
124
|
+
</span>
|
|
125
|
+
<span className="ml-auto shrink-0 text-[9.5px] text-muted-foreground/60">{running ? '…' : status}</span>
|
|
126
|
+
</button>
|
|
127
|
+
{open && e.output && e.output.trim() && (
|
|
128
|
+
<pre className="ml-[18px] mt-0.5 max-h-40 overflow-auto rounded border border-border/50 bg-background/40 p-1.5 font-mono text-[10px] leading-relaxed whitespace-pre-wrap text-muted-foreground/75">
|
|
129
|
+
{e.output}
|
|
130
|
+
</pre>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** An `emberflow` CLI operation the agent ran (run/inspect/delete against the
|
|
137
|
+
* live runner) — surfaced as a distinct operation step, expandable to its
|
|
138
|
+
* output. This is the agent's operational channel: files for edits, CLI ops
|
|
139
|
+
* for running/inspecting/removing. */
|
|
140
|
+
function OpCall({ op, target, e }: { op: string; target?: string; e: AgentEvent }) {
|
|
141
|
+
const [open, setOpen] = useState(false);
|
|
142
|
+
const status = e.commandStatus ?? 'completed';
|
|
143
|
+
const failed = status === 'failed' || (e.exitCode != null && e.exitCode !== 0);
|
|
144
|
+
const running = status === 'in_progress';
|
|
145
|
+
return (
|
|
146
|
+
<div className="my-0.5">
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
onClick={() => setOpen((o) => !o)}
|
|
150
|
+
className="group flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left transition-colors hover:bg-secondary/50"
|
|
151
|
+
>
|
|
152
|
+
<ChevronRightIcon className={cn('size-3 shrink-0 text-muted-foreground/50 transition-transform', open && 'rotate-90')} />
|
|
153
|
+
<span className={cn('size-1.5 shrink-0 rounded-full', running ? 'animate-pulse bg-highlight' : failed ? 'bg-destructive' : 'bg-[#8fb8d8]')} />
|
|
154
|
+
<ZapIcon className="size-3 shrink-0 text-[#8fb8d8]" />
|
|
155
|
+
<span className="shrink-0 rounded border border-[#8fb8d8]/40 px-1 font-mono text-[8.5px] uppercase tracking-wide text-[#8fb8d8]">
|
|
156
|
+
OP
|
|
157
|
+
</span>
|
|
158
|
+
<span className={cn('min-w-0 truncate font-mono text-[11px]', failed ? 'text-destructive/90' : 'text-foreground/85')}>
|
|
159
|
+
{op}{target ? ` · ${target}` : ''}
|
|
160
|
+
</span>
|
|
161
|
+
<span className="ml-auto shrink-0 text-[9.5px] text-muted-foreground/60">{running ? '…' : failed ? 'failed' : 'ok'}</span>
|
|
162
|
+
</button>
|
|
163
|
+
{open && e.output && e.output.trim() && (
|
|
164
|
+
<pre className="ml-[18px] mt-0.5 max-h-40 overflow-auto rounded border border-border/50 bg-background/40 p-1.5 font-mono text-[10px] leading-relaxed whitespace-pre-wrap text-muted-foreground/75">
|
|
165
|
+
{e.output}
|
|
166
|
+
</pre>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Docked panel that streams a studio-triggered coding-agent run. Presents it
|
|
174
|
+
* the way Codex/Claude present their own work: the agent's reasoning is the
|
|
175
|
+
* content (comfortable prose), commands collapse to quiet one-liners you can
|
|
176
|
+
* expand, and background diagnostics (MCP/hook noise) are tucked into a count.
|
|
177
|
+
* Then the diff with a Revert button once the run finishes.
|
|
178
|
+
*/
|
|
179
|
+
export function AgentConsole({ onDismiss }: { onDismiss: () => void }) {
|
|
180
|
+
const agentRun = useBuilderStore((s) => s.agentRun);
|
|
181
|
+
const revertAgentRun = useBuilderStore((s) => s.revertAgentRun);
|
|
182
|
+
const runAgent = useBuilderStore((s) => s.runAgent);
|
|
183
|
+
const flowId = useBuilderStore((s) => s.flow.id);
|
|
184
|
+
const flowName = useBuilderStore((s) => s.flow.name);
|
|
185
|
+
const envSetup = useBuilderStore((s) => s.agentEnvSetup);
|
|
186
|
+
const [instruction, setInstruction] = useState('');
|
|
187
|
+
const running = agentRun?.status === 'running';
|
|
188
|
+
|
|
189
|
+
// Slide in from the right on mount: start translated off-screen, then flip to
|
|
190
|
+
// 0 on the next frame so the transition runs. (A CSS keyframe races the
|
|
191
|
+
// resizable panel's JS-driven width and often no-ops; this is reliable.)
|
|
192
|
+
const [entered, setEntered] = useState(false);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
const r = requestAnimationFrame(() => setEntered(true));
|
|
195
|
+
return () => cancelAnimationFrame(r);
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
const send = () => {
|
|
199
|
+
const text = instruction.trim();
|
|
200
|
+
if (!text || running) return;
|
|
201
|
+
// Chat drives the OPEN operation — unless the panel was opened from the
|
|
202
|
+
// Environments dialog, in which case it drives environment setup and
|
|
203
|
+
// follow-ups keep refining the environments file.
|
|
204
|
+
if (envSetup) {
|
|
205
|
+
void runAgent({ action: 'setup-environments', instruction: text });
|
|
206
|
+
} else {
|
|
207
|
+
// One input, no modes: the edit-flow prompt classifies the message —
|
|
208
|
+
// questions get answered read-only, change requests get edited. The
|
|
209
|
+
// explicit `ask` intent still exists for programmatic callers.
|
|
210
|
+
void runAgent({ action: 'edit-flow', flowId, instruction: text });
|
|
211
|
+
}
|
|
212
|
+
setInstruction('');
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Partition the raw stream into what's worth showing vs. noise. The agent's
|
|
216
|
+
// ⚠ diagnostics (an MCP server failing to auth, a hook firing) are real but
|
|
217
|
+
// not what the user is watching for — collapse them to a count.
|
|
218
|
+
const { blocks, noiseCount } = useMemo(() => {
|
|
219
|
+
// Fold the raw stream into display blocks: reasoning prose and error lines
|
|
220
|
+
// stand alone; consecutive commands collapse into one CommandGroup. Started/
|
|
221
|
+
// done are structural; ⚠ diagnostics are noise → a count.
|
|
222
|
+
type Block =
|
|
223
|
+
| { kind: 'message' | 'error' | 'approval' | 'mcp'; e: AgentEvent }
|
|
224
|
+
| { kind: 'op'; e: AgentEvent; op: string; target?: string }
|
|
225
|
+
| { kind: 'commands'; commands: AgentEvent[] };
|
|
226
|
+
const blocks: Block[] = [];
|
|
227
|
+
let noiseCount = 0;
|
|
228
|
+
let group: AgentEvent[] | null = null;
|
|
229
|
+
const flush = (): void => {
|
|
230
|
+
if (group && group.length) blocks.push({ kind: 'commands', commands: group });
|
|
231
|
+
group = null;
|
|
232
|
+
};
|
|
233
|
+
for (const e of agentRun?.events ?? []) {
|
|
234
|
+
// An `emberflow` CLI operation (run/inspect/delete) — surface it as a
|
|
235
|
+
// distinct "operation" step, not a generic shell command.
|
|
236
|
+
const opInfo = e.type === 'command' ? parseEmberflowOp(e.command) : null;
|
|
237
|
+
if (e.type === 'command' && opInfo) {
|
|
238
|
+
flush();
|
|
239
|
+
const last = blocks[blocks.length - 1];
|
|
240
|
+
if (last && last.kind === 'op' && last.e.command === e.command && last.e.commandStatus === 'in_progress') {
|
|
241
|
+
last.e = e;
|
|
242
|
+
} else {
|
|
243
|
+
blocks.push({ kind: 'op', e, op: opInfo.op, target: opInfo.target });
|
|
244
|
+
}
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (e.type === 'command') {
|
|
248
|
+
group ??= [];
|
|
249
|
+
// Each command streams as a start (in_progress) then a complete
|
|
250
|
+
// (completed/failed) event with the same text — collapse the pair into
|
|
251
|
+
// one entry carrying the final status + output, so counts are accurate.
|
|
252
|
+
const last = group[group.length - 1];
|
|
253
|
+
if (last && last.command === e.command && last.commandStatus === 'in_progress') {
|
|
254
|
+
group[group.length - 1] = e;
|
|
255
|
+
} else {
|
|
256
|
+
group.push(e);
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
flush();
|
|
261
|
+
if (e.type === 'mcp') {
|
|
262
|
+
// MCP calls stream in_progress → completed for the same tool; collapse
|
|
263
|
+
// the pair so the completed status/result replaces the pending one.
|
|
264
|
+
const last = blocks[blocks.length - 1];
|
|
265
|
+
if (last && last.kind === 'mcp' && last.e.mcpTool === e.mcpTool && last.e.mcpStatus === 'in_progress') {
|
|
266
|
+
last.e = e;
|
|
267
|
+
} else {
|
|
268
|
+
blocks.push({ kind: 'mcp', e });
|
|
269
|
+
}
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (e.type === 'started' || e.type === 'done') continue;
|
|
273
|
+
if (e.type === 'message' && e.text?.startsWith('⚠')) {
|
|
274
|
+
noiseCount += 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (e.type === 'message') blocks.push({ kind: 'message', e });
|
|
278
|
+
else if (e.type === 'error') blocks.push({ kind: 'error', e });
|
|
279
|
+
else if (e.type === 'approval-request') blocks.push({ kind: 'approval', e });
|
|
280
|
+
}
|
|
281
|
+
flush();
|
|
282
|
+
return { blocks, noiseCount };
|
|
283
|
+
}, [agentRun?.events]);
|
|
284
|
+
|
|
285
|
+
const finished = agentRun != null && agentRun.status !== 'running';
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<div
|
|
289
|
+
className={cn(
|
|
290
|
+
'flex h-full min-h-0 flex-col overflow-hidden border-l border-border bg-card transition-transform duration-200 ease-out motion-reduce:transition-none',
|
|
291
|
+
entered ? 'translate-x-0' : 'translate-x-full',
|
|
292
|
+
)}
|
|
293
|
+
>
|
|
294
|
+
<header className="flex h-11 shrink-0 items-center gap-2.5 border-b border-border/70 px-3.5">
|
|
295
|
+
<span className={cn('size-2 rounded-full', agentRun ? statusDot[agentRun.status] : 'bg-muted-foreground/40')} />
|
|
296
|
+
<span className="min-w-0 truncate text-[12.5px] font-medium">
|
|
297
|
+
{envSetup ? 'Agent — environments' : 'Agent'}
|
|
298
|
+
</span>
|
|
299
|
+
{agentRun && (
|
|
300
|
+
<Badge variant="outline" className="shrink-0 text-[9px] uppercase">
|
|
301
|
+
{agentRun.status}
|
|
302
|
+
</Badge>
|
|
303
|
+
)}
|
|
304
|
+
<Button
|
|
305
|
+
variant="ghost"
|
|
306
|
+
size="icon"
|
|
307
|
+
className="ml-auto size-6 shrink-0 text-muted-foreground hover:text-foreground"
|
|
308
|
+
onClick={onDismiss}
|
|
309
|
+
aria-label="Dismiss agent console"
|
|
310
|
+
>
|
|
311
|
+
<XIcon className="size-3.5" />
|
|
312
|
+
</Button>
|
|
313
|
+
</header>
|
|
314
|
+
|
|
315
|
+
<div className="min-h-0 flex-1 space-y-1.5 overflow-y-auto px-3.5 py-3">
|
|
316
|
+
{/* Your message — the instruction you sent, shown first like a chat. */}
|
|
317
|
+
{agentRun?.instruction && (
|
|
318
|
+
<div className="mb-1 flex justify-end">
|
|
319
|
+
<div className="max-w-[90%] rounded-lg rounded-br-sm bg-highlight/15 px-2.5 py-1.5 text-[12.5px] leading-relaxed whitespace-pre-wrap text-foreground">
|
|
320
|
+
{agentRun.instruction}
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
)}
|
|
324
|
+
{!agentRun && (
|
|
325
|
+
<div className="flex h-full flex-col items-center justify-center gap-3 px-4 text-center">
|
|
326
|
+
<span className="flex size-9 items-center justify-center rounded-full bg-highlight/12 text-highlight">
|
|
327
|
+
<SparklesIcon className="size-4" />
|
|
328
|
+
</span>
|
|
329
|
+
<p className="text-[12.5px] leading-relaxed text-muted-foreground">
|
|
330
|
+
{envSetup ? (
|
|
331
|
+
<>
|
|
332
|
+
Describe the environments this project should have — dev? prod? test? Base URLs
|
|
333
|
+
and which ones are protected. The agent writes the config; secret values stay
|
|
334
|
+
yours to add afterwards.
|
|
335
|
+
</>
|
|
336
|
+
) : (
|
|
337
|
+
<>
|
|
338
|
+
Ask the agent to change <span className="text-foreground/80">{flowName}</span>. It edits the
|
|
339
|
+
operation — adding nodes, wiring, or a new registered node — and the canvas updates live when it’s done.
|
|
340
|
+
</>
|
|
341
|
+
)}
|
|
342
|
+
</p>
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
345
|
+
{agentRun && blocks.length === 0 && !finished && (
|
|
346
|
+
<div className="flex items-center gap-2 text-[12px] text-muted-foreground/70">
|
|
347
|
+
<span className="size-1.5 animate-pulse rounded-full bg-highlight" />
|
|
348
|
+
Thinking…
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
{blocks.map((b, i) => {
|
|
352
|
+
if (b.kind === 'commands') return <CommandGroup key={i} commands={b.commands} />;
|
|
353
|
+
if (b.kind === 'op') return <OpCall key={i} op={b.op} target={b.target} e={b.e} />;
|
|
354
|
+
if (b.kind === 'mcp') return <McpCall key={i} e={b.e} />;
|
|
355
|
+
if (b.kind === 'message') {
|
|
356
|
+
// The reasoning: the star of the panel — comfortable, readable prose.
|
|
357
|
+
// Render the markdown subset (bold/code/lists/headings). The renderer
|
|
358
|
+
// owns block structure, so whitespace-pre-wrap stays OFF here.
|
|
359
|
+
return (
|
|
360
|
+
<div key={i} className="space-y-1.5">
|
|
361
|
+
{renderAgentMarkdown(b.e.text ?? '')}
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
if (b.kind === 'error') {
|
|
366
|
+
return (
|
|
367
|
+
<p key={i} className="text-[12px] leading-relaxed text-destructive">
|
|
368
|
+
{b.e.text ?? 'The agent run failed.'}
|
|
369
|
+
</p>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
return (
|
|
373
|
+
<p key={i} className="text-[12px] text-yellow-500">
|
|
374
|
+
Approval requested{b.e.text ? `: ${b.e.text}` : ''}
|
|
375
|
+
</p>
|
|
376
|
+
);
|
|
377
|
+
})}
|
|
378
|
+
|
|
379
|
+
{/* Live working indicator once there's content but the run's still going. */}
|
|
380
|
+
{!finished && blocks.length > 0 && (
|
|
381
|
+
<div className="flex items-center gap-2 pt-1 text-[11px] text-muted-foreground/60">
|
|
382
|
+
<span className="size-1.5 animate-pulse rounded-full bg-highlight" />
|
|
383
|
+
Working…
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
|
|
387
|
+
{/* Background diagnostics, tucked away — acknowledged, not shouting. */}
|
|
388
|
+
{noiseCount > 0 && (
|
|
389
|
+
<div className="flex items-center gap-1.5 pt-1 text-[10.5px] text-muted-foreground/45">
|
|
390
|
+
<TerminalIcon className="size-3" />
|
|
391
|
+
{noiseCount} background {noiseCount === 1 ? 'diagnostic' : 'diagnostics'} hidden
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
{finished && agentRun && (agentRun.diff !== undefined || agentRun.status === 'error') && (
|
|
397
|
+
<div className="min-h-0 shrink-0 border-t border-border/70">
|
|
398
|
+
<div className="max-h-64 overflow-auto p-3">
|
|
399
|
+
{agentRun.diff ? (
|
|
400
|
+
<>
|
|
401
|
+
<div className="mb-1.5 text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
|
|
402
|
+
Changes{agentRun.files && agentRun.files.length > 0 ? ` · ${agentRun.files.join(', ')}` : ''}
|
|
403
|
+
</div>
|
|
404
|
+
<pre className="overflow-auto rounded-md border border-border bg-background/40 p-2 font-mono text-[10.5px] leading-relaxed whitespace-pre-wrap">
|
|
405
|
+
{agentRun.diff}
|
|
406
|
+
</pre>
|
|
407
|
+
</>
|
|
408
|
+
) : (
|
|
409
|
+
<div className="text-[12px] text-muted-foreground/70">No changes were made.</div>
|
|
410
|
+
)}
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
)}
|
|
414
|
+
|
|
415
|
+
{/* Revert stays available after a completed change. */}
|
|
416
|
+
{finished && agentRun && agentRun.status === 'done' && agentRun.diff && (
|
|
417
|
+
<div className="flex shrink-0 items-center border-t border-border/70 px-3.5 py-1.5">
|
|
418
|
+
<Button variant="destructive" size="sm" onClick={() => void revertAgentRun()}>
|
|
419
|
+
Revert last change
|
|
420
|
+
</Button>
|
|
421
|
+
</div>
|
|
422
|
+
)}
|
|
423
|
+
|
|
424
|
+
{/* Persistent chat input — the way to drive the open operation. */}
|
|
425
|
+
<footer className="shrink-0 border-t border-border/70 p-2.5">
|
|
426
|
+
<div className="flex items-end gap-2">
|
|
427
|
+
<textarea
|
|
428
|
+
value={instruction}
|
|
429
|
+
onChange={(e) => setInstruction(e.target.value)}
|
|
430
|
+
onKeyDown={(e) => {
|
|
431
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
|
432
|
+
e.preventDefault();
|
|
433
|
+
send();
|
|
434
|
+
}
|
|
435
|
+
}}
|
|
436
|
+
disabled={running}
|
|
437
|
+
placeholder={
|
|
438
|
+
running
|
|
439
|
+
? 'Agent is working…'
|
|
440
|
+
: envSetup
|
|
441
|
+
? 'e.g. dev on localhost:3001, prod at api.example.com (protected)'
|
|
442
|
+
: `Ask about ${flowName}, or tell the agent what to change…`
|
|
443
|
+
}
|
|
444
|
+
rows={2}
|
|
445
|
+
className="min-h-0 flex-1 resize-none rounded-md border border-input bg-input/30 px-2.5 py-1.5 text-[12.5px] leading-relaxed text-foreground outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-60"
|
|
446
|
+
/>
|
|
447
|
+
<Button
|
|
448
|
+
size="icon"
|
|
449
|
+
className="size-8 shrink-0"
|
|
450
|
+
onClick={send}
|
|
451
|
+
disabled={running || !instruction.trim()}
|
|
452
|
+
aria-label="Send to agent"
|
|
453
|
+
title="Send (⌘⏎)"
|
|
454
|
+
>
|
|
455
|
+
<ArrowUpIcon className="size-4" />
|
|
456
|
+
</Button>
|
|
457
|
+
</div>
|
|
458
|
+
</footer>
|
|
459
|
+
</div>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { PlusIcon, Trash2Icon } from 'lucide-react';
|
|
2
|
+
import { Button } from '@/components/ui/button';
|
|
3
|
+
import { Combobox } from '@/components/ui/combobox';
|
|
4
|
+
import type { ComboboxOption } from '@/components/ui/combobox';
|
|
5
|
+
import { Input } from '@/components/ui/input';
|
|
6
|
+
import { useBuilderStore } from '../store/builderStore';
|
|
7
|
+
import type { WorkflowNode } from '../engine';
|
|
8
|
+
|
|
9
|
+
/** A single Conditional rule row: `{ name, op, value? }`. Mirrors src/nodes/flow-control.ts. */
|
|
10
|
+
interface BranchRule {
|
|
11
|
+
name: string;
|
|
12
|
+
op: string;
|
|
13
|
+
value?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const OP_OPTIONS: ComboboxOption[] = [
|
|
17
|
+
{ value: 'eq', label: '= eq' },
|
|
18
|
+
{ value: 'neq', label: '≠ neq' },
|
|
19
|
+
{ value: 'gt', label: '> gt' },
|
|
20
|
+
{ value: 'gte', label: '≥ gte' },
|
|
21
|
+
{ value: 'lt', label: '< lt' },
|
|
22
|
+
{ value: 'lte', label: '≤ lte' },
|
|
23
|
+
{ value: 'contains', label: 'contains' },
|
|
24
|
+
{ value: 'exists', label: 'exists' },
|
|
25
|
+
{ value: 'truthy', label: 'truthy' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** exists/truthy read only the input value — no comparand to configure. */
|
|
29
|
+
const NO_COMPARAND_OPS = new Set(['exists', 'truthy']);
|
|
30
|
+
|
|
31
|
+
/** Numbers, booleans, and everything else stays a string. */
|
|
32
|
+
export function parseComparand(text: string): unknown {
|
|
33
|
+
if (text === 'true') return true;
|
|
34
|
+
if (text === 'false') return false;
|
|
35
|
+
if (text.trim() !== '' && Number.isFinite(Number(text))) return Number(text);
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function comparandText(value: unknown): string {
|
|
40
|
+
if (value === undefined) return '';
|
|
41
|
+
return typeof value === 'string' ? value : String(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function asRules(raw: unknown): BranchRule[] {
|
|
45
|
+
if (!Array.isArray(raw)) return [];
|
|
46
|
+
return raw.map((r) => {
|
|
47
|
+
const rule = (r !== null && typeof r === 'object' ? r : {}) as Record<string, unknown>;
|
|
48
|
+
return {
|
|
49
|
+
name: typeof rule.name === 'string' ? rule.name : '',
|
|
50
|
+
op: typeof rule.op === 'string' ? rule.op : 'eq',
|
|
51
|
+
value: rule.value,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Custom Config editor for Conditional nodes — an ordered list of
|
|
58
|
+
* name/operator/comparand rules plus a fallback branch, in place of the
|
|
59
|
+
* generic config field list (which can't express structured rule rows).
|
|
60
|
+
*/
|
|
61
|
+
export function BranchRulesEditor({ node }: { node: WorkflowNode }) {
|
|
62
|
+
const updateNodeConfig = useBuilderStore((s) => s.updateNodeConfig);
|
|
63
|
+
const rules = asRules(node.config.branches);
|
|
64
|
+
const fallback = typeof node.config.fallback === 'string' ? node.config.fallback : '';
|
|
65
|
+
|
|
66
|
+
const setRules = (next: BranchRule[]) => updateNodeConfig(node.id, 'branches', next);
|
|
67
|
+
|
|
68
|
+
const updateRule = (index: number, patch: Partial<BranchRule>) => {
|
|
69
|
+
setRules(rules.map((r, i) => (i === index ? { ...r, ...patch } : r)));
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const removeRule = (index: number) => {
|
|
73
|
+
setRules(rules.filter((_, i) => i !== index));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const addRule = () => {
|
|
77
|
+
setRules([...rules, { name: '', op: 'eq', value: '' }]);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div>
|
|
82
|
+
{rules.length === 0 ? (
|
|
83
|
+
<div className="mb-2.5 text-[12.5px] leading-relaxed text-muted-foreground">
|
|
84
|
+
No rules — every input takes the fallback.
|
|
85
|
+
</div>
|
|
86
|
+
) : (
|
|
87
|
+
<div className="mb-2 space-y-1">
|
|
88
|
+
{rules.map((rule, index) => {
|
|
89
|
+
const showComparand = !NO_COMPARAND_OPS.has(rule.op);
|
|
90
|
+
return (
|
|
91
|
+
<div key={index} className="group flex items-center gap-1.5">
|
|
92
|
+
<span className="w-3 shrink-0 text-right font-mono text-[10px] text-muted-foreground/50">
|
|
93
|
+
{index + 1}
|
|
94
|
+
</span>
|
|
95
|
+
<Input
|
|
96
|
+
className="min-w-0 flex-1"
|
|
97
|
+
value={rule.name}
|
|
98
|
+
placeholder="branch name"
|
|
99
|
+
spellCheck={false}
|
|
100
|
+
onChange={(e) => updateRule(index, { name: e.target.value })}
|
|
101
|
+
/>
|
|
102
|
+
<Combobox
|
|
103
|
+
className="w-28 shrink-0"
|
|
104
|
+
options={OP_OPTIONS}
|
|
105
|
+
value={rule.op}
|
|
106
|
+
onChange={(op) => updateRule(index, { op })}
|
|
107
|
+
placeholder="op"
|
|
108
|
+
searchPlaceholder="Search operators…"
|
|
109
|
+
/>
|
|
110
|
+
{showComparand ? (
|
|
111
|
+
<Input
|
|
112
|
+
className="min-w-0 flex-1"
|
|
113
|
+
value={comparandText(rule.value)}
|
|
114
|
+
placeholder="value"
|
|
115
|
+
spellCheck={false}
|
|
116
|
+
onChange={(e) => updateRule(index, { value: parseComparand(e.target.value) })}
|
|
117
|
+
/>
|
|
118
|
+
) : (
|
|
119
|
+
<div className="flex-1" />
|
|
120
|
+
)}
|
|
121
|
+
<Button
|
|
122
|
+
variant="ghost"
|
|
123
|
+
size="icon"
|
|
124
|
+
className="size-6 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 hover:text-destructive"
|
|
125
|
+
onClick={() => removeRule(index)}
|
|
126
|
+
aria-label="Remove rule"
|
|
127
|
+
>
|
|
128
|
+
<Trash2Icon className="size-3" />
|
|
129
|
+
</Button>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
})}
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
<Button
|
|
136
|
+
variant="ghost"
|
|
137
|
+
size="xs"
|
|
138
|
+
className="-ml-1.5 gap-1 text-muted-foreground"
|
|
139
|
+
onClick={addRule}
|
|
140
|
+
>
|
|
141
|
+
<PlusIcon className="size-3.5" />
|
|
142
|
+
Add branch
|
|
143
|
+
</Button>
|
|
144
|
+
<div className="mt-2.5 flex items-center gap-1.5 border-t border-border pt-2.5">
|
|
145
|
+
<span className="shrink-0 text-[11.5px] text-muted-foreground">otherwise →</span>
|
|
146
|
+
<Input
|
|
147
|
+
className="min-w-0 flex-1"
|
|
148
|
+
value={fallback}
|
|
149
|
+
placeholder="fallback branch"
|
|
150
|
+
spellCheck={false}
|
|
151
|
+
onChange={(e) => updateNodeConfig(node.id, 'fallback', e.target.value)}
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|