machinaos 0.0.76 → 0.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -107
- package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
- package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
- package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
- package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
- package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
- package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
- package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
- package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
- package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
- package/client/dist/assets/index-DQ0nwhec.js +257 -0
- package/client/dist/assets/index-DxmbVskS.css +1 -0
- package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
- package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
- package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
- package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
- package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
- package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
- package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
- package/client/dist/index.html +37 -3
- package/client/index.html +28 -1
- package/client/package.json +44 -40
- package/client/src/App.tsx +2 -0
- package/client/src/Dashboard.tsx +157 -45
- package/client/src/ParameterPanel.tsx +3 -5
- package/client/src/adapters/nodeSpecToDescription.ts +1 -0
- package/client/src/assets/icons/NodeIcon.tsx +32 -0
- package/client/src/assets/icons/index.ts +4 -0
- package/client/src/assets/icons/stripe.svg +1 -0
- package/client/src/assets/icons/themedGlyphs.ts +404 -0
- package/client/src/components/AIAgentNode.tsx +77 -53
- package/client/src/components/GenericNode.tsx +34 -52
- package/client/src/components/OutputPanel.tsx +64 -147
- package/client/src/components/ParameterRenderer.tsx +5 -3
- package/client/src/components/SkillEditorModal.tsx +9 -18
- package/client/src/components/SquareNode.tsx +97 -115
- package/client/src/components/StartNode.tsx +32 -42
- package/client/src/components/SvgFilterDefs.tsx +54 -0
- package/client/src/components/TeamMonitorNode.tsx +12 -14
- package/client/src/components/ToolkitNode.tsx +35 -60
- package/client/src/components/TriggerNode.tsx +43 -77
- package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
- package/client/src/components/credentials/CredentialsModal.tsx +98 -30
- package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
- package/client/src/components/credentials/catalogueAdapter.ts +17 -1
- package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
- package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
- package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
- package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
- package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
- package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
- package/client/src/components/credentials/types.ts +12 -2
- package/client/src/components/credentials/useCredentialPanel.ts +43 -19
- package/client/src/components/icons/AIProviderIcons.tsx +16 -0
- package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
- package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
- package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
- package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
- package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
- package/client/src/components/parameterPanel/InputSection.tsx +9 -7
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
- package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
- package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
- package/client/src/components/ui/AIResultModal.tsx +1 -1
- package/client/src/components/ui/CollapsibleSection.tsx +9 -5
- package/client/src/components/ui/CommandPalette.tsx +147 -0
- package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
- package/client/src/components/ui/ComponentItem.tsx +13 -7
- package/client/src/components/ui/ComponentPalette.tsx +24 -13
- package/client/src/components/ui/ConsolePanel.tsx +19 -11
- package/client/src/components/ui/DropCap.tsx +28 -0
- package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
- package/client/src/components/ui/InputNodesPanel.tsx +1 -1
- package/client/src/components/ui/Modal.tsx +38 -6
- package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
- package/client/src/components/ui/SettingsPanel.tsx +42 -13
- package/client/src/components/ui/StatusBar.tsx +108 -0
- package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
- package/client/src/components/ui/TopToolbar.tsx +42 -25
- package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
- package/client/src/components/ui/action-button.tsx +40 -15
- package/client/src/components/ui/button.tsx +24 -1
- package/client/src/components/ui/dropdown-menu.tsx +24 -2
- package/client/src/components/ui/input.tsx +19 -2
- package/client/src/components/ui/select.tsx +15 -0
- package/client/src/components/ui/textarea.tsx +15 -2
- package/client/src/contexts/AuthContext.tsx +148 -109
- package/client/src/contexts/ThemeContext.tsx +93 -17
- package/client/src/contexts/WebSocketContext.tsx +373 -206
- package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
- package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
- package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
- package/client/src/hooks/useAppTheme.ts +209 -7
- package/client/src/hooks/useAutoSkillEdges.ts +7 -2
- package/client/src/hooks/useCatalogueQuery.ts +67 -1
- package/client/src/hooks/useDragVariable.ts +1 -1
- package/client/src/hooks/useNodeAllowlist.ts +115 -8
- package/client/src/hooks/useOnboarding.ts +20 -8
- package/client/src/hooks/useParameterPanel.ts +2 -1
- package/client/src/hooks/useReactFlowNodes.ts +2 -1
- package/client/src/hooks/useSound.ts +185 -0
- package/client/src/hooks/useWorkflowManagement.ts +6 -8
- package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
- package/client/src/index.css +65 -3
- package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
- package/client/src/lib/aiModelProviders.ts +8 -0
- package/client/src/lib/connectionConfig.ts +107 -0
- package/client/src/lib/queryPersist.ts +13 -5
- package/client/src/lib/sound.ts +393 -0
- package/client/src/main.tsx +20 -0
- package/client/src/store/useAppStore.ts +26 -0
- package/client/src/styles/canvasAnimations.ts +37 -36
- package/client/src/styles/theme.ts +36 -20
- package/client/src/test/setup.ts +1 -0
- package/client/src/themes/atomic.css +253 -0
- package/client/src/themes/base.css +373 -0
- package/client/src/themes/cyber.css +890 -0
- package/client/src/themes/dark.css +70 -0
- package/client/src/themes/edo.css +246 -0
- package/client/src/themes/greek.css +293 -0
- package/client/src/themes/light.css +78 -0
- package/client/src/themes/plague.css +253 -0
- package/client/src/themes/renaissance.css +727 -0
- package/client/src/themes/rot.css +249 -0
- package/client/src/themes/steampunk.css +272 -0
- package/client/src/themes/surveillance.css +289 -0
- package/client/src/themes/wasteland.css +250 -0
- package/client/src/types/INodeProperties.ts +5 -0
- package/client/src/types/NodeTypes.ts +11 -1
- package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
- package/client/src/types/cloudEvents.ts +78 -0
- package/client/src/vite-env.d.ts +7 -0
- package/client/tsconfig.json +1 -1
- package/client/vite.config.js +62 -2
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/machina/commands/build.py +51 -7
- package/machina/pyproject.toml +4 -0
- package/machina/supervisor.py +12 -2
- package/machina/tree.py +71 -21
- package/package.json +4 -4
- package/scripts/install.js +16 -1
- package/server/config/ai_cli_providers.json +54 -0
- package/server/config/credential_providers.json +109 -2
- package/server/config/llm_defaults.json +24 -0
- package/server/config/model_registry.json +338 -499
- package/server/config/node_allowlist.json +16 -1
- package/server/config/pricing.json +8 -0
- package/server/constants.py +38 -15
- package/server/core/container.py +2 -2
- package/server/core/credentials_database.py +35 -2
- package/server/core/logging.py +4 -3
- package/server/main.py +99 -13
- package/server/models/node_metadata.py +1 -0
- package/server/nodejs/package.json +8 -6
- package/server/nodejs/src/index.ts +22 -5
- package/server/nodes/README.md +31 -4
- package/server/nodes/agent/_inline.py +2 -0
- package/server/nodes/agent/_specialized.py +6 -3
- package/server/nodes/agent/ai_agent.py +13 -3
- package/server/nodes/agent/chat_agent.py +6 -3
- package/server/nodes/agent/claude_code_agent.py +287 -75
- package/server/nodes/agent/codex_agent.py +239 -0
- package/server/nodes/agent/deep_agent.py +3 -3
- package/server/nodes/agent/rlm_agent.py +3 -3
- package/server/nodes/android/__init__.py +31 -1
- package/server/nodes/android/_base.py +9 -5
- package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
- package/server/nodes/android/_handlers.py +154 -0
- package/server/nodes/android/_option_loaders.py +44 -0
- package/server/nodes/android/_refresh.py +127 -0
- package/server/{services/android → nodes/android/_relay}/client.py +4 -4
- package/server/{routers/android.py → nodes/android/_router.py} +27 -8
- package/server/nodes/browser/browser.py +2 -2
- package/server/nodes/code/_base.py +6 -2
- package/server/nodes/code/_claude_code.py +134 -0
- package/server/nodes/document/embedding_generator.py +3 -3
- package/server/nodes/document/http_scraper.py +3 -3
- package/server/nodes/document/vector_store.py +5 -5
- package/server/nodes/email/__init__.py +11 -1
- package/server/nodes/email/_filters.py +21 -0
- package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
- package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
- package/server/nodes/email/email_read.py +1 -1
- package/server/nodes/email/email_receive.py +54 -5
- package/server/nodes/email/email_send.py +1 -1
- package/server/nodes/filesystem/shell.py +24 -1
- package/server/nodes/google/__init__.py +55 -1
- package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
- package/server/nodes/google/_base.py +2 -2
- package/server/nodes/google/_credentials.py +5 -5
- package/server/nodes/google/_filters.py +25 -0
- package/server/nodes/google/_handlers.py +57 -0
- package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
- package/server/nodes/google/_option_loaders.py +107 -0
- package/server/nodes/google/_refresh.py +66 -0
- package/server/nodes/google/_router.py +131 -0
- package/server/nodes/google/gmail_receive.py +41 -4
- package/server/nodes/groups.py +1 -0
- package/server/nodes/location/_credentials.py +45 -1
- package/server/{services/maps.py → nodes/location/_service.py} +18 -3
- package/server/nodes/location/gmaps_create.py +4 -4
- package/server/nodes/location/gmaps_locations.py +4 -4
- package/server/nodes/location/gmaps_nearby_places.py +4 -4
- package/server/nodes/model/_base.py +8 -3
- package/server/nodes/model/_credentials.py +96 -8
- package/server/nodes/model/_local_validator.py +345 -0
- package/server/nodes/model/lmstudio_chat_model.py +23 -0
- package/server/nodes/model/ollama_chat_model.py +25 -0
- package/server/nodes/proxy/_usage.py +2 -2
- package/server/nodes/proxy/proxy_config.py +14 -14
- package/server/nodes/proxy/proxy_request.py +4 -4
- package/server/nodes/scraper/_credentials.py +29 -1
- package/server/nodes/scraper/apify_actor.py +9 -9
- package/server/nodes/scraper/crawlee_scraper.py +5 -5
- package/server/nodes/search/brave_search.py +4 -0
- package/server/nodes/search/perplexity_search.py +9 -0
- package/server/nodes/search/serper_search.py +3 -0
- package/server/nodes/skill/simple_memory.py +12 -0
- package/server/nodes/social/_base.py +2 -2
- package/server/nodes/stripe/__init__.py +46 -0
- package/server/nodes/stripe/_credentials.py +33 -0
- package/server/nodes/stripe/_handlers.py +270 -0
- package/server/nodes/stripe/_install.py +127 -0
- package/server/nodes/stripe/_source.py +174 -0
- package/server/nodes/stripe/stripe_action.py +81 -0
- package/server/nodes/stripe/stripe_receive.py +92 -0
- package/server/nodes/telegram/_credentials.py +52 -1
- package/server/nodes/telegram/_handlers.py +19 -18
- package/server/nodes/telegram/_service.py +134 -32
- package/server/nodes/telegram/telegram_send.py +5 -6
- package/server/nodes/text/file_handler.py +2 -2
- package/server/nodes/text/text_generator.py +2 -2
- package/server/nodes/tool/agent_builder.py +630 -0
- package/server/nodes/tool/task_manager.py +144 -2
- package/server/nodes/twitter/__init__.py +38 -1
- package/server/nodes/twitter/_base.py +7 -7
- package/server/nodes/twitter/_credentials.py +1 -1
- package/server/nodes/twitter/_filters.py +37 -0
- package/server/nodes/twitter/_handlers.py +77 -0
- package/server/nodes/twitter/_oauth.py +124 -0
- package/server/nodes/twitter/_refresh.py +78 -0
- package/server/nodes/twitter/_router.py +29 -0
- package/server/nodes/twitter/twitter_receive.py +4 -0
- package/server/nodes/visuals.json +64 -19
- package/server/nodes/whatsapp/__init__.py +45 -5
- package/server/nodes/whatsapp/_base.py +3 -3
- package/server/nodes/whatsapp/_filters.py +137 -0
- package/server/nodes/whatsapp/_handlers.py +167 -0
- package/server/nodes/whatsapp/_option_loaders.py +68 -0
- package/server/nodes/whatsapp/_refresh.py +62 -0
- package/server/nodes/whatsapp/_runtime.py +1 -1
- package/server/pyproject.toml +29 -7
- package/server/routers/schemas.py +2 -2
- package/server/routers/webhook.py +26 -9
- package/server/routers/websocket.py +149 -810
- package/server/services/ai.py +89 -8
- package/server/services/auth.py +220 -43
- package/server/services/claude_oauth.py +126 -100
- package/server/services/cli_agent/__init__.py +78 -0
- package/server/services/cli_agent/_handlers.py +237 -0
- package/server/services/cli_agent/config.py +112 -0
- package/server/services/cli_agent/factory.py +48 -0
- package/server/services/cli_agent/lockfile.py +141 -0
- package/server/services/cli_agent/mcp_server.py +482 -0
- package/server/services/cli_agent/protocol.py +173 -0
- package/server/services/cli_agent/providers/__init__.py +9 -0
- package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
- package/server/services/cli_agent/providers/google_gemini.py +80 -0
- package/server/services/cli_agent/providers/openai_codex.py +310 -0
- package/server/services/cli_agent/service.py +607 -0
- package/server/services/cli_agent/session.py +618 -0
- package/server/services/cli_agent/types.py +227 -0
- package/server/services/cli_agent/workflow_tools.py +233 -0
- package/server/services/credential_registry.py +26 -1
- package/server/services/deployment/manager.py +26 -145
- package/server/services/deployment/poll_registry.py +59 -0
- package/server/services/event_waiter.py +76 -246
- package/server/services/events/__init__.py +54 -0
- package/server/services/events/cli.py +78 -0
- package/server/services/events/daemon.py +163 -0
- package/server/services/events/envelope.py +281 -0
- package/server/services/events/lifecycle.py +99 -0
- package/server/services/events/oauth_lifecycle.py +534 -0
- package/server/services/events/polling.py +60 -0
- package/server/services/events/push.py +36 -0
- package/server/services/events/source.py +63 -0
- package/server/services/events/triggers.py +118 -0
- package/server/services/events/verifiers/__init__.py +25 -0
- package/server/services/events/verifiers/base.py +28 -0
- package/server/services/events/verifiers/github.py +25 -0
- package/server/services/events/verifiers/hmac_basic.py +32 -0
- package/server/services/events/verifiers/standard_webhooks.py +47 -0
- package/server/services/events/verifiers/stripe.py +42 -0
- package/server/services/events/webhook.py +105 -0
- package/server/services/handlers/tools.py +28 -186
- package/server/services/llm/config.py +7 -0
- package/server/services/llm/factory.py +8 -2
- package/server/services/memory/__init__.py +52 -0
- package/server/services/memory/jsonl.py +80 -0
- package/server/services/memory/markdown.py +65 -0
- package/server/services/memory/state.py +112 -0
- package/server/services/memory/vector_store.py +40 -0
- package/server/services/model_registry.py +76 -0
- package/server/services/node_allowlist.py +71 -15
- package/server/services/node_executor.py +2 -2
- package/server/services/node_output_schemas.py +21 -10
- package/server/services/node_spec.py +1 -1
- package/server/services/oauth_utils.py +1 -1
- package/server/services/plugin/__init__.py +2 -0
- package/server/services/plugin/base.py +44 -2
- package/server/services/plugin/credential.py +288 -1
- package/server/services/plugin/deps.py +105 -0
- package/server/services/plugin/edge_walker.py +12 -4
- package/server/services/plugin/oauth.py +381 -0
- package/server/services/plugin/polling.py +247 -0
- package/server/services/plugin/registry.py +145 -0
- package/server/services/plugin/singleton.py +65 -0
- package/server/services/plugin/ws.py +81 -0
- package/server/services/process_service.py +31 -2
- package/server/services/status_broadcaster.py +155 -238
- package/server/services/temporal/workflow.py +7 -7
- package/server/services/workflow.py +21 -3
- package/server/services/ws_handler_registry.py +111 -28
- package/server/skills/GUIDE.md +16 -1
- package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
- package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
- package/server/tests/credentials/test_auth_service.py +16 -9
- package/server/tests/credentials/test_credential_broadcasts.py +219 -0
- package/server/tests/credentials/test_google_oauth.py +6 -6
- package/server/tests/credentials/test_oauth_utils.py +1 -1
- package/server/tests/credentials/test_twitter_oauth.py +2 -2
- package/server/tests/credentials/test_websocket_handlers.py +44 -20
- package/server/tests/llm/test_factory.py +1 -0
- package/server/tests/llm/test_wiring.py +5 -1
- package/server/tests/nodes/_compat.py +24 -24
- package/server/tests/nodes/test_agent_builder.py +439 -0
- package/server/tests/nodes/test_ai_tools.py +18 -14
- package/server/tests/nodes/test_code_fs_process.py +17 -8
- package/server/tests/nodes/test_email.py +10 -9
- package/server/tests/nodes/test_google_workspace.py +2 -2
- package/server/tests/nodes/test_specialized_agents.py +100 -53
- package/server/tests/nodes/test_stripe_plugin.py +293 -0
- package/server/tests/nodes/test_telegram_social.py +4 -4
- package/server/tests/nodes/test_twitter.py +1 -1
- package/server/tests/nodes/test_web_automation.py +2 -2
- package/server/tests/nodes/test_whatsapp.py +9 -9
- package/server/tests/services/cli_agent/__init__.py +0 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
- package/server/tests/services/cli_agent/test_providers.py +358 -0
- package/server/tests/services/cli_agent/test_service.py +298 -0
- package/server/tests/services/memory/__init__.py +0 -0
- package/server/tests/services/memory/test_jsonl.py +188 -0
- package/server/tests/services/test_events.py +333 -0
- package/server/tests/test_node_spec.py +56 -16
- package/server/tests/test_plugin_helpers.py +116 -0
- package/server/tests/test_plugin_self_containment.py +486 -0
- package/server/tests/test_status_broadcasts.py +425 -0
- package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
- package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
- package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
- package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
- package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
- package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
- package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
- package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
- package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
- package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
- package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
- package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
- package/client/dist/assets/index-D9tZfgvi.js +0 -363
- package/client/dist/assets/index-al7snTkG.css +0 -1
- package/client/src/components/credentials/providers.tsx +0 -177
- package/server/routers/google.py +0 -277
- package/server/routers/maps.py +0 -142
- package/server/routers/twitter.py +0 -365
- package/server/services/claude_code_service.py +0 -106
- package/server/services/memory.py +0 -159
- package/server/services/node_option_loaders/__init__.py +0 -77
- package/server/services/node_option_loaders/android_loaders.py +0 -55
- package/server/services/node_option_loaders/google_loaders.py +0 -97
- package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
- package/server/services/twitter_oauth.py +0 -411
- package/server/services/websocket_client.py +0 -29
- /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
- /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
- /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
- /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the TanStack-Query-backed AuthContext.
|
|
3
|
+
*
|
|
4
|
+
* Locks in the contracts that drive perceived launch time + the
|
|
5
|
+
* disconnect-reconnect bug-fix:
|
|
6
|
+
*
|
|
7
|
+
* 1. Anonymous-mode happy path: backend reports `auth_enabled: false`
|
|
8
|
+
* → user is auto-set to the anonymous owner without further work.
|
|
9
|
+
* 2. Retry-then-recover: 503 fails N times then 200 succeeds → user
|
|
10
|
+
* is set, no LoginPage flash.
|
|
11
|
+
* 3. 401 fast-fail: backend returns 401 → query reports error
|
|
12
|
+
* immediately, NO retry budget burned (would otherwise wait 10s).
|
|
13
|
+
* 4. Logout invalidates the cache: after logout the cached data shows
|
|
14
|
+
* `authenticated: false` so the WebSocketContext logout effect
|
|
15
|
+
* fires deterministically.
|
|
16
|
+
*
|
|
17
|
+
* Backoff is verified at the unit level (the AUTH_RETRY constant is
|
|
18
|
+
* used by `lib/connectionConfig.ts`); the E2E backoff curve is covered
|
|
19
|
+
* by the manual flake-test plan in docs-internal/release_build_pipeline.md.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import React from 'react';
|
|
23
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
24
|
+
import { render, waitFor, act } from '@testing-library/react';
|
|
25
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
26
|
+
import { AuthProvider, useAuth, AUTH_STATUS_QUERY_KEY } from '../AuthContext';
|
|
27
|
+
|
|
28
|
+
// Mock the API config so the fetch URL is predictable in test logs.
|
|
29
|
+
vi.mock('../../config/api', () => ({
|
|
30
|
+
API_CONFIG: { PYTHON_BASE_URL: 'http://test' },
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Each test gets a fresh QueryClient with retries disabled by default;
|
|
34
|
+
// individual tests opt back into retries to exercise the retry path.
|
|
35
|
+
function makeQueryClient(opts?: { retry?: number }): QueryClient {
|
|
36
|
+
return new QueryClient({
|
|
37
|
+
defaultOptions: {
|
|
38
|
+
queries: { retry: opts?.retry ?? 0, retryDelay: 0 },
|
|
39
|
+
mutations: { retry: 0 },
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function Wrapper({
|
|
45
|
+
children,
|
|
46
|
+
client,
|
|
47
|
+
}: {
|
|
48
|
+
children: React.ReactNode;
|
|
49
|
+
client: QueryClient;
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<QueryClientProvider client={client}>
|
|
53
|
+
<AuthProvider>{children}</AuthProvider>
|
|
54
|
+
</QueryClientProvider>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Captures the `useAuth()` value across renders so tests can assert on
|
|
59
|
+
// state transitions without remembering to await effects manually.
|
|
60
|
+
function makeProbe() {
|
|
61
|
+
const states: ReturnType<typeof useAuth>[] = [];
|
|
62
|
+
function Probe() {
|
|
63
|
+
states.push(useAuth());
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return { states, Probe };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
vi.restoreAllMocks();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('AuthContext (TanStack Query)', () => {
|
|
74
|
+
it('sets the anonymous user when backend reports auth_enabled: false', async () => {
|
|
75
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
|
76
|
+
new Response(
|
|
77
|
+
JSON.stringify({
|
|
78
|
+
auth_enabled: false,
|
|
79
|
+
auth_mode: 'single',
|
|
80
|
+
authenticated: false,
|
|
81
|
+
user: null,
|
|
82
|
+
can_register: false,
|
|
83
|
+
}),
|
|
84
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const client = makeQueryClient();
|
|
89
|
+
const { states, Probe } = makeProbe();
|
|
90
|
+
render(
|
|
91
|
+
<Wrapper client={client}>
|
|
92
|
+
<Probe />
|
|
93
|
+
</Wrapper>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
const last = states[states.length - 1];
|
|
98
|
+
expect(last.isLoading).toBe(false);
|
|
99
|
+
expect(last.isAuthenticated).toBe(true);
|
|
100
|
+
expect(last.user?.email).toBe('anonymous');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('retries on 503 and surfaces the user on the 200', async () => {
|
|
105
|
+
let attempt = 0;
|
|
106
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {
|
|
107
|
+
attempt += 1;
|
|
108
|
+
if (attempt < 3) {
|
|
109
|
+
return new Response('upstream not ready', { status: 503 });
|
|
110
|
+
}
|
|
111
|
+
return new Response(
|
|
112
|
+
JSON.stringify({
|
|
113
|
+
auth_enabled: true,
|
|
114
|
+
auth_mode: 'single',
|
|
115
|
+
authenticated: true,
|
|
116
|
+
user: { id: 1, email: 'a@b', display_name: 'A', is_owner: true },
|
|
117
|
+
can_register: false,
|
|
118
|
+
}),
|
|
119
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Allow up to 3 retries here so the third attempt resolves the 200.
|
|
124
|
+
const client = makeQueryClient({ retry: 3 });
|
|
125
|
+
const { states, Probe } = makeProbe();
|
|
126
|
+
render(
|
|
127
|
+
<Wrapper client={client}>
|
|
128
|
+
<Probe />
|
|
129
|
+
</Wrapper>,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
await waitFor(() => {
|
|
133
|
+
const last = states[states.length - 1];
|
|
134
|
+
expect(last.isAuthenticated).toBe(true);
|
|
135
|
+
expect(last.user?.email).toBe('a@b');
|
|
136
|
+
});
|
|
137
|
+
expect(attempt).toBeGreaterThanOrEqual(3);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('does not retry a 401 — surfaces "not authenticated" immediately', async () => {
|
|
141
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
|
142
|
+
new Response(JSON.stringify({ detail: 'unauthorized' }), { status: 401 }),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Even with a generous retry budget the AuthContext's `retry`
|
|
146
|
+
// predicate refuses 401/403 — `fetchSpy` should be called exactly
|
|
147
|
+
// ONCE.
|
|
148
|
+
const client = makeQueryClient({ retry: 5 });
|
|
149
|
+
const { states, Probe } = makeProbe();
|
|
150
|
+
render(
|
|
151
|
+
<Wrapper client={client}>
|
|
152
|
+
<Probe />
|
|
153
|
+
</Wrapper>,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
const last = states[states.length - 1];
|
|
158
|
+
expect(last.isLoading).toBe(false);
|
|
159
|
+
expect(last.isAuthenticated).toBe(false);
|
|
160
|
+
expect(last.error).not.toBeNull();
|
|
161
|
+
});
|
|
162
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('logout invalidates the auth-status cache and flips authenticated → false', async () => {
|
|
166
|
+
// Initial auth-enabled, logged-in user.
|
|
167
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
|
168
|
+
const url = typeof input === 'string' ? input : (input as Request).url;
|
|
169
|
+
if (url.endsWith('/logout')) {
|
|
170
|
+
return new Response('', { status: 200 });
|
|
171
|
+
}
|
|
172
|
+
// /status — return a logged-in user the first call, an
|
|
173
|
+
// unauthenticated response on every subsequent call (after logout
|
|
174
|
+
// invalidates the cache and refetches).
|
|
175
|
+
return new Response(
|
|
176
|
+
JSON.stringify({
|
|
177
|
+
auth_enabled: true,
|
|
178
|
+
auth_mode: 'single',
|
|
179
|
+
authenticated: false,
|
|
180
|
+
user: null,
|
|
181
|
+
can_register: false,
|
|
182
|
+
}),
|
|
183
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const client = makeQueryClient();
|
|
188
|
+
// Seed the cache with an authenticated user so we can observe the
|
|
189
|
+
// transition triggered by `logout()`. This mirrors the real flow:
|
|
190
|
+
// on first mount the query resolves authenticated, then later
|
|
191
|
+
// logout flips it.
|
|
192
|
+
client.setQueryData([...AUTH_STATUS_QUERY_KEY], {
|
|
193
|
+
auth_enabled: true,
|
|
194
|
+
auth_mode: 'single',
|
|
195
|
+
authenticated: true,
|
|
196
|
+
user: { id: 1, email: 'a@b', display_name: 'A', is_owner: true },
|
|
197
|
+
can_register: false,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const { states, Probe } = makeProbe();
|
|
201
|
+
render(
|
|
202
|
+
<Wrapper client={client}>
|
|
203
|
+
<Probe />
|
|
204
|
+
</Wrapper>,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
await waitFor(() => {
|
|
208
|
+
expect(states[states.length - 1].isAuthenticated).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await act(async () => {
|
|
212
|
+
await states[states.length - 1].logout();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await waitFor(() => {
|
|
216
|
+
const last = states[states.length - 1];
|
|
217
|
+
expect(last.isAuthenticated).toBe(false);
|
|
218
|
+
expect(last.user).toBeNull();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -18,8 +18,14 @@ const storeState: { currentWorkflow: any } = {
|
|
|
18
18
|
currentWorkflow: null,
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
// `useAppStore` is consumed via the documented Zustand slice-selector
|
|
22
|
+
// pattern: `useAppStore((s) => s.currentWorkflow)`. The mock has to
|
|
23
|
+
// thread the selector through so it returns the right slice — calling
|
|
24
|
+
// `useAppStore()` without a selector inside the hook would have given us
|
|
25
|
+
// the whole store object and `currentWorkflow.nodes` would be undefined.
|
|
21
26
|
vi.mock('../../store/useAppStore', () => ({
|
|
22
|
-
useAppStore: () => storeState
|
|
27
|
+
useAppStore: <T,>(selector?: (state: typeof storeState) => T): T | typeof storeState =>
|
|
28
|
+
selector ? selector(storeState) : storeState,
|
|
23
29
|
}));
|
|
24
30
|
|
|
25
31
|
// nodeDefinitions/ was deleted in commit fc10fd3 — the hook now
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for useWorkflowOpsListener.
|
|
3
|
+
*
|
|
4
|
+
* Locks the runtime broadcast contract:
|
|
5
|
+
* - subscribes to `workflow_ops_apply` via the WS context
|
|
6
|
+
* addEventListener API
|
|
7
|
+
* - applies events scoped to the current workflow via applyOperations
|
|
8
|
+
* - other-workflow events trigger a sonner toast (no canvas mutation)
|
|
9
|
+
* - unsubscribes on unmount
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
13
|
+
import { renderHook, act } from '@testing-library/react';
|
|
14
|
+
import type { Node, Edge } from 'reactflow';
|
|
15
|
+
|
|
16
|
+
import { useWorkflowOpsListener } from '../useWorkflowOpsListener';
|
|
17
|
+
|
|
18
|
+
// --- mocks (must come before importing modules that read them) ----------
|
|
19
|
+
|
|
20
|
+
const wsMock = {
|
|
21
|
+
addEventListener: vi.fn<(type: string, handler: (data: any) => void) => () => void>(),
|
|
22
|
+
saveNodeParameters: vi.fn().mockResolvedValue(true),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const storeMock = {
|
|
26
|
+
currentWorkflowId: 'wf-current',
|
|
27
|
+
loadWorkflow: vi.fn(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
vi.mock('../../contexts/WebSocketContext', () => ({
|
|
31
|
+
useWebSocket: () => wsMock,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock('../../store/useAppStore', () => ({
|
|
35
|
+
useAppStore: (selector: any) => selector({
|
|
36
|
+
currentWorkflow: { id: storeMock.currentWorkflowId },
|
|
37
|
+
loadWorkflow: storeMock.loadWorkflow,
|
|
38
|
+
}),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const applyOpsMock = vi.fn().mockResolvedValue({ applied: 0, errors: [], refMap: {} });
|
|
42
|
+
vi.mock('../../lib/workflowOps', () => ({
|
|
43
|
+
applyOperations: (...args: any[]) => applyOpsMock(...args),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const toastMessageMock = vi.fn();
|
|
47
|
+
vi.mock('sonner', () => ({
|
|
48
|
+
toast: { message: (...args: any[]) => toastMessageMock(...args) },
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// --- test scaffolding ---------------------------------------------------
|
|
52
|
+
|
|
53
|
+
function _ctx(overrides: Partial<{ nodes: Node[]; edges: Edge[] }> = {}) {
|
|
54
|
+
return {
|
|
55
|
+
nodes: overrides.nodes ?? [],
|
|
56
|
+
edges: overrides.edges ?? [],
|
|
57
|
+
setNodes: vi.fn(),
|
|
58
|
+
setEdges: vi.fn(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let registeredHandler: ((data: any) => void) | null = null;
|
|
63
|
+
let unsubscribeMock = vi.fn();
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
vi.clearAllMocks();
|
|
67
|
+
registeredHandler = null;
|
|
68
|
+
unsubscribeMock = vi.fn();
|
|
69
|
+
wsMock.addEventListener.mockImplementation((type, handler) => {
|
|
70
|
+
if (type === 'workflow_ops_apply') registeredHandler = handler;
|
|
71
|
+
return unsubscribeMock;
|
|
72
|
+
});
|
|
73
|
+
storeMock.currentWorkflowId = 'wf-current';
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
describe('useWorkflowOpsListener', () => {
|
|
79
|
+
it('subscribes to workflow_ops_apply on mount', () => {
|
|
80
|
+
renderHook(() => useWorkflowOpsListener(_ctx()));
|
|
81
|
+
expect(wsMock.addEventListener).toHaveBeenCalledWith(
|
|
82
|
+
'workflow_ops_apply',
|
|
83
|
+
expect.any(Function),
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('unsubscribes on unmount', () => {
|
|
88
|
+
const { unmount } = renderHook(() => useWorkflowOpsListener(_ctx()));
|
|
89
|
+
expect(unsubscribeMock).not.toHaveBeenCalled();
|
|
90
|
+
unmount();
|
|
91
|
+
expect(unsubscribeMock).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('applies operations scoped to the current workflow', async () => {
|
|
95
|
+
renderHook(() => useWorkflowOpsListener(_ctx()));
|
|
96
|
+
expect(registeredHandler).not.toBeNull();
|
|
97
|
+
|
|
98
|
+
const ops = [{ type: 'add_node', client_ref: 'n', node_type: 'x', parameters: {} }];
|
|
99
|
+
await act(async () => {
|
|
100
|
+
registeredHandler!({
|
|
101
|
+
workflow_id: 'wf-current',
|
|
102
|
+
caller_node_id: 'agent-1',
|
|
103
|
+
operations: ops,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(applyOpsMock).toHaveBeenCalledTimes(1);
|
|
108
|
+
expect(applyOpsMock.mock.calls[0][0]).toEqual(ops);
|
|
109
|
+
expect(toastMessageMock).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('toasts (not applies) when event targets a different workflow', () => {
|
|
113
|
+
renderHook(() => useWorkflowOpsListener(_ctx()));
|
|
114
|
+
expect(registeredHandler).not.toBeNull();
|
|
115
|
+
|
|
116
|
+
act(() => {
|
|
117
|
+
registeredHandler!({
|
|
118
|
+
workflow_id: 'wf-other',
|
|
119
|
+
caller_node_id: 'agent-1',
|
|
120
|
+
operations: [{ type: 'add_node', client_ref: 'n', node_type: 'x', parameters: {} }],
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(applyOpsMock).not.toHaveBeenCalled();
|
|
125
|
+
expect(toastMessageMock).toHaveBeenCalledTimes(1);
|
|
126
|
+
const [title, opts] = toastMessageMock.mock.calls[0];
|
|
127
|
+
expect(title).toMatch(/workflow created/i);
|
|
128
|
+
expect(opts.action.label).toBe('Switch');
|
|
129
|
+
// Switch handler triggers loadWorkflow with the foreign id.
|
|
130
|
+
opts.action.onClick();
|
|
131
|
+
expect(storeMock.loadWorkflow).toHaveBeenCalledWith('wf-other');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('ignores empty-ops events that target the current workflow', async () => {
|
|
135
|
+
renderHook(() => useWorkflowOpsListener(_ctx()));
|
|
136
|
+
await act(async () => {
|
|
137
|
+
registeredHandler!({ workflow_id: 'wf-current', operations: [] });
|
|
138
|
+
});
|
|
139
|
+
expect(applyOpsMock).not.toHaveBeenCalled();
|
|
140
|
+
expect(toastMessageMock).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -1,17 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAppTheme — canvas + map theme accessor.
|
|
3
|
+
*
|
|
4
|
+
* Returns a flat colour pack with the same shape the canvas + map +
|
|
5
|
+
* grandfathered-modal call sites expect (`theme.colors.X`,
|
|
6
|
+
* `theme.isDarkMode`). Under light/dark this is identity-pass; under
|
|
7
|
+
* Renaissance / Greek / Edo / Steampunk / Atomic / Cyber / Wasteland
|
|
8
|
+
* / Rot / Plague / Surveillance the base pack (light or dark, picked
|
|
9
|
+
* by `DARK_FAMILY`) gets a small overlay of theme-specific accents
|
|
10
|
+
* (primary, edges, action colours) so canvas selection rings, edge
|
|
11
|
+
* strokes, and action button colours match the theme's identity.
|
|
12
|
+
*
|
|
13
|
+
* This is the production realisation of MIGRATION_PLAYBOOK Wave 1 —
|
|
14
|
+
* the upstream playbook proposes a full per-theme NodePack record;
|
|
15
|
+
* the overlay form here is the same idea expressed without forking
|
|
16
|
+
* the existing 50-key Colors shape, so the existing 14+ call sites
|
|
17
|
+
* keep compiling and the visual delta lives in one place.
|
|
18
|
+
*
|
|
19
|
+
* Adding a new theme override: drop an entry in `THEME_OVERRIDES`
|
|
20
|
+
* with whichever subset of `Colors` keys you want to override. Empty
|
|
21
|
+
* overrides are a no-op (theme falls back to pure light/dark).
|
|
22
|
+
*/
|
|
23
|
+
|
|
1
24
|
import { useMemo } from 'react';
|
|
2
|
-
import { useTheme } from '../contexts/ThemeContext';
|
|
25
|
+
import { useTheme, type ThemeName } from '../contexts/ThemeContext';
|
|
3
26
|
import { theme as baseTheme, lightColors, darkColors } from '../styles/theme';
|
|
4
27
|
|
|
28
|
+
type Colors = typeof lightColors;
|
|
29
|
+
/** Loose form for override entries — `lightColors` is `as const`, which
|
|
30
|
+
* narrows each value to a literal hex string. Themes need to substitute
|
|
31
|
+
* arbitrary hex / rgba strings, so the override map widens to `string`. */
|
|
32
|
+
type ColorOverride = Partial<Record<keyof Colors, string>>;
|
|
33
|
+
|
|
34
|
+
/** Themes whose canvas + chrome read as dark backgrounds. Mirrors the
|
|
35
|
+
* DARK_FAMILY in ThemeContext so the two stay in lockstep. */
|
|
36
|
+
const DARK_BASE_THEMES: ReadonlySet<ThemeName> = new Set([
|
|
37
|
+
'dark', 'cyber', 'wasteland', 'rot', 'surveillance', 'steampunk',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/** Theme-specific overlays. Each entry is a partial Colors object —
|
|
41
|
+
* whatever keys appear here override the chosen base pack. Missing
|
|
42
|
+
* keys fall through to lightColors / darkColors. Hex values come
|
|
43
|
+
* from the matching client/src/themes/<theme>.css token block. */
|
|
44
|
+
const THEME_OVERRIDES: Partial<Record<ThemeName, ColorOverride>> = {
|
|
45
|
+
renaissance: {
|
|
46
|
+
primary: '#b8893c', // gold accent
|
|
47
|
+
focus: '#d4a030',
|
|
48
|
+
focusRing: 'rgba(212, 160, 48, 0.35)',
|
|
49
|
+
actionRun: '#4a6818', // olive (success)
|
|
50
|
+
actionDeploy: '#b8893c', // gold
|
|
51
|
+
actionStop: '#8a1410', // crimson
|
|
52
|
+
actionSave: '#d4a030', // gold leaf
|
|
53
|
+
edgeDefault: '#5a3a14', // ink brown
|
|
54
|
+
edgeSelected: '#d4a030', // gold
|
|
55
|
+
edgeCompleted: '#4a6818',
|
|
56
|
+
edgeError: '#8a1410',
|
|
57
|
+
edgeMemoryActive: '#b8893c',
|
|
58
|
+
edgeToolActive: '#c08020',
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
greek: {
|
|
62
|
+
primary: '#284b82', // lapis
|
|
63
|
+
focus: '#284b82',
|
|
64
|
+
focusRing: 'rgba(40, 75, 130, 0.3)',
|
|
65
|
+
actionRun: '#6a7a32', // olive
|
|
66
|
+
actionDeploy: '#284b82', // lapis
|
|
67
|
+
actionStop: '#7a1a18', // oxblood
|
|
68
|
+
actionSave: '#c8a040', // gold
|
|
69
|
+
edgeDefault: '#4a3818', // dark stone
|
|
70
|
+
edgeSelected: '#284b82',
|
|
71
|
+
edgeCompleted: '#6a7a32',
|
|
72
|
+
edgeError: '#7a1a18',
|
|
73
|
+
edgeMemoryActive: '#284b82',
|
|
74
|
+
edgeToolActive: '#c8a040',
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
edo: {
|
|
78
|
+
primary: '#b41e1e', // vermillion
|
|
79
|
+
focus: '#b41e1e',
|
|
80
|
+
focusRing: 'rgba(180, 30, 30, 0.3)',
|
|
81
|
+
actionRun: '#4a6a3a', // bamboo
|
|
82
|
+
actionDeploy: '#b41e1e',
|
|
83
|
+
actionStop: '#b41e1e',
|
|
84
|
+
actionSave: '#1a1410', // sumi
|
|
85
|
+
edgeDefault: '#1a1410',
|
|
86
|
+
edgeSelected: '#b41e1e',
|
|
87
|
+
edgeCompleted: '#4a6a3a',
|
|
88
|
+
edgeError: '#b41e1e',
|
|
89
|
+
edgeMemoryActive: '#b41e1e',
|
|
90
|
+
edgeToolActive: '#c89832',
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
steampunk: {
|
|
94
|
+
primary: '#d8a848', // brass
|
|
95
|
+
focus: '#d8a848',
|
|
96
|
+
focusRing: 'rgba(216, 168, 72, 0.4)',
|
|
97
|
+
actionRun: '#6a8a3a',
|
|
98
|
+
actionDeploy: '#d8a848',
|
|
99
|
+
actionStop: '#8a3a1a', // rust
|
|
100
|
+
actionSave: '#b8602a', // copper
|
|
101
|
+
edgeDefault: '#b88838',
|
|
102
|
+
edgeSelected: '#d8a848',
|
|
103
|
+
edgeCompleted: '#6a8a3a',
|
|
104
|
+
edgeError: '#8a3a1a',
|
|
105
|
+
edgeMemoryActive: '#b8602a',
|
|
106
|
+
edgeToolActive: '#d8a848',
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
atomic: {
|
|
110
|
+
primary: '#e85a26', // atomic orange
|
|
111
|
+
focus: '#e85a26',
|
|
112
|
+
focusRing: 'rgba(232, 90, 38, 0.4)',
|
|
113
|
+
actionRun: '#5a8a5a',
|
|
114
|
+
actionDeploy: '#e85a26',
|
|
115
|
+
actionStop: '#e85a26',
|
|
116
|
+
actionSave: '#3a9aa0', // turquoise
|
|
117
|
+
edgeDefault: '#2a3a4a', // slate
|
|
118
|
+
edgeSelected: '#e85a26',
|
|
119
|
+
edgeCompleted: '#5a8a5a',
|
|
120
|
+
edgeError: '#e85a26',
|
|
121
|
+
edgeMemoryActive: '#3a9aa0',
|
|
122
|
+
edgeToolActive: '#d8a838', // mustard
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
cyber: {
|
|
126
|
+
primary: '#f51eb6', // neon magenta
|
|
127
|
+
focus: '#1dd9e5', // neon cyan
|
|
128
|
+
focusRing: 'rgba(245, 30, 182, 0.5)',
|
|
129
|
+
actionRun: '#26d97a', // neon green
|
|
130
|
+
actionDeploy: '#f51eb6',
|
|
131
|
+
actionStop: '#ff2050',
|
|
132
|
+
actionSave: '#1dd9e5',
|
|
133
|
+
edgeDefault: '#f51eb6',
|
|
134
|
+
edgeSelected: '#1dd9e5',
|
|
135
|
+
edgeCompleted: '#26d97a',
|
|
136
|
+
edgeError: '#ff2050',
|
|
137
|
+
edgeMemoryActive: '#f51eb6',
|
|
138
|
+
edgeToolActive: '#ffd028', // neon yellow
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
wasteland: {
|
|
142
|
+
primary: '#e88a28', // ochre
|
|
143
|
+
focus: '#e88a28',
|
|
144
|
+
focusRing: 'rgba(232, 138, 40, 0.45)',
|
|
145
|
+
actionRun: '#8a9028',
|
|
146
|
+
actionDeploy: '#e88a28',
|
|
147
|
+
actionStop: '#b8281a', // rust red
|
|
148
|
+
actionSave: '#c8d038', // radioactive
|
|
149
|
+
edgeDefault: '#8a3a18',
|
|
150
|
+
edgeSelected: '#e88a28',
|
|
151
|
+
edgeCompleted: '#8a9028',
|
|
152
|
+
edgeError: '#b8281a',
|
|
153
|
+
edgeMemoryActive: '#e88a28',
|
|
154
|
+
edgeToolActive: '#c8d038',
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
rot: {
|
|
158
|
+
primary: '#78c878', // moss bloom
|
|
159
|
+
focus: '#78c878',
|
|
160
|
+
focusRing: 'rgba(120, 200, 120, 0.4)',
|
|
161
|
+
actionRun: '#78c878',
|
|
162
|
+
actionDeploy: '#e8a838', // candleflame
|
|
163
|
+
actionStop: '#a83838',
|
|
164
|
+
actionSave: '#e8a838',
|
|
165
|
+
edgeDefault: '#4a6a4a',
|
|
166
|
+
edgeSelected: '#78c878',
|
|
167
|
+
edgeCompleted: '#78c878',
|
|
168
|
+
edgeError: '#a83838',
|
|
169
|
+
edgeMemoryActive: '#e8a838',
|
|
170
|
+
edgeToolActive: '#5898b8',
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
plague: {
|
|
174
|
+
primary: '#783c28', // dried blood
|
|
175
|
+
focus: '#783c28',
|
|
176
|
+
focusRing: 'rgba(120, 60, 40, 0.4)',
|
|
177
|
+
actionRun: '#5a7028',
|
|
178
|
+
actionDeploy: '#783c28',
|
|
179
|
+
actionStop: '#783c28',
|
|
180
|
+
actionSave: '#98a838', // bile
|
|
181
|
+
edgeDefault: '#2a1c14',
|
|
182
|
+
edgeSelected: '#783c28',
|
|
183
|
+
edgeCompleted: '#5a7028',
|
|
184
|
+
edgeError: '#783c28',
|
|
185
|
+
edgeMemoryActive: '#98a838',
|
|
186
|
+
edgeToolActive: '#4a5868',
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
surveillance: {
|
|
190
|
+
primary: '#e82626', // REC red
|
|
191
|
+
focus: '#e82626',
|
|
192
|
+
focusRing: 'rgba(232, 38, 38, 0.45)',
|
|
193
|
+
actionRun: '#6acc6a', // phosphor green
|
|
194
|
+
actionDeploy: '#e82626',
|
|
195
|
+
actionStop: '#e82626',
|
|
196
|
+
actionSave: '#6acc6a',
|
|
197
|
+
edgeDefault: '#5a5e62',
|
|
198
|
+
edgeSelected: '#e82626',
|
|
199
|
+
edgeCompleted: '#6acc6a',
|
|
200
|
+
edgeError: '#e82626',
|
|
201
|
+
edgeMemoryActive: '#e82626',
|
|
202
|
+
edgeToolActive: '#5a8cc8',
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
5
206
|
export const useAppTheme = () => {
|
|
6
|
-
const {
|
|
207
|
+
const { theme } = useTheme();
|
|
208
|
+
const isDarkMode = DARK_BASE_THEMES.has(theme);
|
|
7
209
|
|
|
8
|
-
|
|
210
|
+
return useMemo(() => {
|
|
211
|
+
const base = isDarkMode ? darkColors : lightColors;
|
|
212
|
+
const overrides = THEME_OVERRIDES[theme] ?? {};
|
|
9
213
|
return {
|
|
10
214
|
...baseTheme,
|
|
11
|
-
colors:
|
|
215
|
+
colors: { ...base, ...overrides } as Colors,
|
|
12
216
|
isDarkMode,
|
|
13
217
|
};
|
|
14
|
-
}, [isDarkMode]);
|
|
15
|
-
|
|
16
|
-
return dynamicTheme;
|
|
218
|
+
}, [theme, isDarkMode]);
|
|
17
219
|
};
|
|
@@ -27,10 +27,15 @@ import type { Node, Edge, Connection } from 'reactflow';
|
|
|
27
27
|
import { useWebSocket } from '../contexts/WebSocketContext';
|
|
28
28
|
import { useUserSettingsQuery } from './useUserSettingsQuery';
|
|
29
29
|
import { applyOperations, type WorkflowOperation } from '../lib/workflowOps';
|
|
30
|
+
import { getCachedNodeSpec } from '../lib/nodeSpec';
|
|
30
31
|
|
|
31
|
-
const MASTER_SKILL_TYPE = 'masterSkill';
|
|
32
32
|
const SKILL_HANDLE = 'input-skill';
|
|
33
33
|
|
|
34
|
+
const isMasterSkillNode = (nodeType: string | undefined): boolean => {
|
|
35
|
+
if (!nodeType) return false;
|
|
36
|
+
return (getCachedNodeSpec(nodeType)?.uiHints as any)?.isMasterSkillEditor === true;
|
|
37
|
+
};
|
|
38
|
+
|
|
34
39
|
interface UseAutoSkillEdgesProps {
|
|
35
40
|
baseOnConnect: (params: Edge | Connection) => void;
|
|
36
41
|
baseOnEdgesDelete: (deleted: Edge[]) => void;
|
|
@@ -73,7 +78,7 @@ export function useAutoSkillEdges({
|
|
|
73
78
|
e => e.target === targetId && e.targetHandle === SKILL_HANDLE,
|
|
74
79
|
);
|
|
75
80
|
const masterSkillNode = skillEdge && nodes.find(
|
|
76
|
-
n => n.id === skillEdge.source && n.type
|
|
81
|
+
n => n.id === skillEdge.source && isMasterSkillNode(n.type),
|
|
77
82
|
);
|
|
78
83
|
const masterSkillId = masterSkillNode?.id ?? null;
|
|
79
84
|
// node.data only stores the label (see CLAUDE.md "Node Data
|