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
|
@@ -41,6 +41,12 @@ class AIAgentParams(BaseModel):
|
|
|
41
41
|
provider: Literal[
|
|
42
42
|
"openai", "anthropic", "gemini", "openrouter",
|
|
43
43
|
"groq", "cerebras", "deepseek", "kimi", "mistral",
|
|
44
|
+
# Local-server providers — agent execution reads
|
|
45
|
+
# ``{provider}_proxy`` to point the LangChain client at the
|
|
46
|
+
# user's localhost server. Without these entries the dropdown
|
|
47
|
+
# silently falls back to ``"openai"`` and execute_agent ends
|
|
48
|
+
# up calling api.openai.com instead.
|
|
49
|
+
"ollama", "lmstudio",
|
|
44
50
|
] = "openai"
|
|
45
51
|
model: str = Field(
|
|
46
52
|
default="", json_schema_extra={"placeholder": "Select a model..."},
|
|
@@ -114,18 +120,22 @@ class AIAgentNode(ActionNode):
|
|
|
114
120
|
underlying LangGraph orchestration + tool binding + memory
|
|
115
121
|
I/O + streaming hooks stay in AIService.
|
|
116
122
|
"""
|
|
117
|
-
from
|
|
123
|
+
from services.plugin.deps import get_ai_service, get_database
|
|
118
124
|
|
|
119
125
|
from ._inline import prepare_agent_call
|
|
120
126
|
|
|
121
|
-
ai_service =
|
|
122
|
-
database =
|
|
127
|
+
ai_service = get_ai_service()
|
|
128
|
+
database = get_database()
|
|
123
129
|
kwargs = await prepare_agent_call(
|
|
124
130
|
node_id=ctx.node_id, node_type=self.type,
|
|
125
131
|
parameters=params.model_dump(),
|
|
126
132
|
context=ctx.raw, database=database,
|
|
127
133
|
log_prefix="[AI Agent]",
|
|
128
134
|
)
|
|
135
|
+
# ``execute_agent`` raises ``NodeUserError`` directly for typed
|
|
136
|
+
# openai SDK failures (context overflow, bad key, server down).
|
|
137
|
+
# Anything that comes back here as ``success=False`` is a real
|
|
138
|
+
# bug worth a stacktrace via ``RuntimeError``.
|
|
129
139
|
response = await ai_service.execute_agent(ctx.node_id, **kwargs)
|
|
130
140
|
if response.get("success"):
|
|
131
141
|
return response.get("result") or response
|
|
@@ -28,6 +28,9 @@ class ChatAgentParams(BaseModel):
|
|
|
28
28
|
provider: Literal[
|
|
29
29
|
"openai", "anthropic", "gemini", "openrouter",
|
|
30
30
|
"groq", "cerebras", "deepseek", "kimi", "mistral",
|
|
31
|
+
# Local-server providers — see ai_agent.Params for the proxy_url
|
|
32
|
+
# rationale. Same fix; same reason.
|
|
33
|
+
"ollama", "lmstudio",
|
|
31
34
|
] = "openai"
|
|
32
35
|
model: str = Field(
|
|
33
36
|
default="", json_schema_extra={"placeholder": "Select a model..."},
|
|
@@ -100,12 +103,12 @@ class ChatAgentNode(ActionNode):
|
|
|
100
103
|
@Operation("execute", cost={"service": "chat_agent", "action": "run", "count": 1})
|
|
101
104
|
async def execute_op(self, ctx: NodeContext, params: ChatAgentParams) -> Any:
|
|
102
105
|
"""Inlined from handlers/ai.handle_chat_agent (Wave 11.D.6)."""
|
|
103
|
-
from
|
|
106
|
+
from services.plugin.deps import get_ai_service, get_database
|
|
104
107
|
|
|
105
108
|
from ._inline import prepare_agent_call
|
|
106
109
|
|
|
107
|
-
ai_service =
|
|
108
|
-
database =
|
|
110
|
+
ai_service = get_ai_service()
|
|
111
|
+
database = get_database()
|
|
109
112
|
kwargs = await prepare_agent_call(
|
|
110
113
|
node_id=ctx.node_id, node_type=self.type,
|
|
111
114
|
parameters=params.model_dump(),
|
|
@@ -1,48 +1,123 @@
|
|
|
1
|
-
"""Claude Code Agent —
|
|
1
|
+
"""Claude Code Agent — multi-instance via `AICliService`.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
``
|
|
5
|
-
|
|
3
|
+
Refactored from the single-task shim. ``Params.tasks`` is a list of
|
|
4
|
+
``ClaudeTaskSpec``; each task gets its own git worktree, its own session
|
|
5
|
+
id, and runs in parallel under a 5-way semaphore.
|
|
6
|
+
|
|
7
|
+
Back-compat: an empty `tasks` array with a non-empty `prompt` falls back
|
|
8
|
+
to a single-task batch — preserves the legacy single-shot UX.
|
|
9
|
+
|
|
10
|
+
Skill instructions connected via the parent node's ``input-skill`` handle
|
|
11
|
+
are exposed to the CLI through the MCP server's ``listSkills`` /
|
|
12
|
+
``getSkill`` tools (NOT concatenated into the system prompt — the
|
|
13
|
+
agent fetches them on-demand). Connected skill names ARE collected here
|
|
14
|
+
and passed to ``run_batch()`` so the MCP server can scope its responses.
|
|
6
15
|
"""
|
|
7
16
|
|
|
8
17
|
from __future__ import annotations
|
|
9
18
|
|
|
10
19
|
import time
|
|
20
|
+
import uuid
|
|
11
21
|
from datetime import datetime
|
|
12
|
-
from
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, List, Optional
|
|
13
24
|
|
|
14
|
-
from pydantic import ConfigDict, Field
|
|
25
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
15
26
|
|
|
16
27
|
from core.logging import get_logger
|
|
17
|
-
from services.plugin import ActionNode, NodeContext, Operation, TaskQueue
|
|
28
|
+
from services.plugin import ActionNode, NodeContext, NodeUserError, Operation, TaskQueue
|
|
18
29
|
from services.plugin.edge_walker import collect_agent_connections
|
|
19
30
|
|
|
20
31
|
from ._handles import STD_AGENT_HINTS, std_agent_handles
|
|
21
|
-
from ._specialized import SpecializedAgentOutput, SpecializedAgentParams
|
|
22
32
|
|
|
23
33
|
logger = get_logger(__name__)
|
|
24
34
|
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
# Late import the cli_agent types so node_registry import doesn't pull
|
|
37
|
+
# the whole MCP / pool stack at module import time.
|
|
38
|
+
from services.cli_agent import ClaudeTaskSpec # noqa: E402
|
|
39
|
+
from services.cli_agent.types import SessionResultModel # noqa: E402
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
|
|
42
|
+
class ClaudeCodeAgentParams(BaseModel):
|
|
43
|
+
"""Multi-task batch parameters for Claude Code.
|
|
44
|
+
|
|
45
|
+
Two paths:
|
|
46
|
+
1. ``tasks=[...]`` — explicit list, runs N in parallel
|
|
47
|
+
2. ``tasks=[]`` + legacy ``prompt`` — synthesises a single-task batch
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
tasks: List[ClaudeTaskSpec] = Field(
|
|
51
|
+
default_factory=list,
|
|
52
|
+
description="List of Claude tasks to run in parallel (max 5 concurrent).",
|
|
53
|
+
json_schema_extra={"rows": 1},
|
|
54
|
+
)
|
|
55
|
+
# Legacy single-task fallback ----------------------------------------
|
|
56
|
+
prompt: str = Field(
|
|
57
|
+
default="",
|
|
58
|
+
description="Legacy: single-prompt fallback used only when "
|
|
59
|
+
"`tasks` is empty.",
|
|
60
|
+
json_schema_extra={"rows": 4, "placeholder": "Or use the tasks array..."},
|
|
61
|
+
)
|
|
62
|
+
model: str = Field(
|
|
63
|
+
default="claude-sonnet-4-6",
|
|
64
|
+
description="Default model for tasks that don't override it.",
|
|
65
|
+
)
|
|
66
|
+
system_prompt: Optional[str] = Field(default=None, json_schema_extra={"rows": 3})
|
|
67
|
+
working_directory: Optional[str] = Field(
|
|
68
|
+
default=None,
|
|
69
|
+
description="Git repo root. Defaults to the workflow's workspace dir.",
|
|
70
|
+
)
|
|
71
|
+
max_parallel: int = Field(
|
|
72
|
+
default=5, ge=1, le=20,
|
|
73
|
+
description="Concurrency cap.",
|
|
74
|
+
)
|
|
75
|
+
allowed_credentials: List[str] = Field(
|
|
76
|
+
default_factory=list,
|
|
77
|
+
description="Credential names the CLI is permitted to fetch via MCP.",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Saved workflow JSON may persist these list fields as `null` rather
|
|
81
|
+
# than `[]` when the user has never edited them. Coerce so Pydantic's
|
|
82
|
+
# strict list validation doesn't reject the params on load.
|
|
83
|
+
@field_validator("tasks", "allowed_credentials", mode="before")
|
|
84
|
+
@classmethod
|
|
85
|
+
def _none_is_empty_list(cls, v: Any) -> Any:
|
|
86
|
+
return [] if v is None else v
|
|
36
87
|
|
|
37
88
|
model_config = ConfigDict(extra="ignore")
|
|
38
89
|
|
|
39
90
|
|
|
91
|
+
class ClaudeCodeAgentOutput(BaseModel):
|
|
92
|
+
"""Aggregated batch output."""
|
|
93
|
+
success: bool = True
|
|
94
|
+
n_tasks: int = 0
|
|
95
|
+
n_succeeded: int = 0
|
|
96
|
+
n_failed: int = 0
|
|
97
|
+
total_cost_usd: Optional[float] = None
|
|
98
|
+
wall_clock_ms: int = 0
|
|
99
|
+
tasks: List[SessionResultModel] = Field(default_factory=list)
|
|
100
|
+
provider: str = "claude"
|
|
101
|
+
timestamp: Optional[str] = None
|
|
102
|
+
# Legacy single-task fields, populated when n_tasks==1 for back-compat:
|
|
103
|
+
response: Optional[str] = None
|
|
104
|
+
session_id: Optional[str] = None
|
|
105
|
+
cost_usd: Optional[float] = None
|
|
106
|
+
# `extra="forbid"` so a malformed CLI envelope raises a real
|
|
107
|
+
# ValidationError at the Output boundary instead of silently emitting
|
|
108
|
+
# an unparseable shape downstream.
|
|
109
|
+
model_config = ConfigDict(extra="forbid")
|
|
110
|
+
|
|
111
|
+
|
|
40
112
|
class ClaudeCodeAgentNode(ActionNode):
|
|
41
113
|
type = "claude_code_agent"
|
|
42
114
|
display_name = "Claude Code"
|
|
43
115
|
subtitle = "Agentic Coding"
|
|
44
116
|
group = ("agent",)
|
|
45
|
-
description =
|
|
117
|
+
description = (
|
|
118
|
+
"Run N parallel Claude Code CLI sessions over a list of tasks. "
|
|
119
|
+
"Each task is isolated in its own git worktree."
|
|
120
|
+
)
|
|
46
121
|
component_kind = "agent"
|
|
47
122
|
handles = std_agent_handles()
|
|
48
123
|
ui_hints = STD_AGENT_HINTS
|
|
@@ -50,84 +125,221 @@ class ClaudeCodeAgentNode(ActionNode):
|
|
|
50
125
|
task_queue = TaskQueue.AI_HEAVY
|
|
51
126
|
|
|
52
127
|
Params = ClaudeCodeAgentParams
|
|
53
|
-
Output =
|
|
128
|
+
Output = ClaudeCodeAgentOutput
|
|
54
129
|
|
|
55
|
-
@Operation(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
130
|
+
@Operation(
|
|
131
|
+
"execute",
|
|
132
|
+
cost={"service": "claude_code_agent", "action": "run", "count": 1},
|
|
133
|
+
)
|
|
134
|
+
async def execute_op(
|
|
135
|
+
self, ctx: NodeContext, params: ClaudeCodeAgentParams,
|
|
136
|
+
) -> Any:
|
|
137
|
+
from services.cli_agent.service import get_ai_cli_service
|
|
138
|
+
from services.cli_agent.types import session_result_to_model
|
|
139
|
+
from services.plugin.deps import get_database
|
|
59
140
|
from services.status_broadcaster import get_status_broadcaster
|
|
60
141
|
|
|
61
142
|
start_time = time.time()
|
|
62
143
|
broadcaster = get_status_broadcaster()
|
|
63
144
|
workflow_id = ctx.workflow_id
|
|
64
145
|
node_id = ctx.node_id
|
|
65
|
-
payload = params.model_dump()
|
|
66
146
|
|
|
67
147
|
await broadcaster.update_node_status(
|
|
68
148
|
node_id, "executing",
|
|
69
|
-
{"message": "Starting Claude Code..."},
|
|
149
|
+
{"message": "Starting Claude Code batch..."},
|
|
70
150
|
workflow_id=workflow_id,
|
|
71
151
|
)
|
|
72
152
|
|
|
73
|
-
#
|
|
74
|
-
# the
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
prompt = str(src)
|
|
84
|
-
if prompt:
|
|
85
|
-
break
|
|
86
|
-
if not prompt:
|
|
87
|
-
raise RuntimeError("No prompt provided")
|
|
88
|
-
|
|
89
|
-
database = container.database()
|
|
90
|
-
_, skill_data, _, _, _ = await collect_agent_connections(node_id, ctx.raw, database)
|
|
91
|
-
|
|
92
|
-
system_parts = []
|
|
93
|
-
if payload.get("system_prompt"):
|
|
94
|
-
system_parts.append(payload["system_prompt"])
|
|
95
|
-
for skill in skill_data:
|
|
96
|
-
instr = skill.get("parameters", {}).get("instructions", "")
|
|
97
|
-
if instr:
|
|
98
|
-
system_parts.append(instr)
|
|
99
|
-
|
|
100
|
-
model = payload.get("model", "claude-sonnet-4-6")
|
|
101
|
-
await broadcaster.update_node_status(
|
|
102
|
-
node_id, "executing",
|
|
103
|
-
{"message": f"Running Claude Code ({model})..."},
|
|
104
|
-
workflow_id=workflow_id,
|
|
153
|
+
# Collect connected memory/skills/tools/input/task in one pass —
|
|
154
|
+
# same edge-walker the AI Agent uses (services/plugin/edge_walker.py).
|
|
155
|
+
# This must run BEFORE prompt resolution so the auto-fallback can
|
|
156
|
+
# read from `input_data` exactly the way `nodes/agent/_inline.py`
|
|
157
|
+
# does for the standard agent path.
|
|
158
|
+
database = get_database()
|
|
159
|
+
memory_data, skill_data, tool_data, input_data, _ = (
|
|
160
|
+
await collect_agent_connections(
|
|
161
|
+
node_id, ctx.raw, database, log_prefix="[Claude Code]",
|
|
162
|
+
)
|
|
105
163
|
)
|
|
164
|
+
connected_skills = [
|
|
165
|
+
s.get("skill_name") or s.get("label")
|
|
166
|
+
for s in skill_data
|
|
167
|
+
if s.get("skill_name") or s.get("label")
|
|
168
|
+
]
|
|
106
169
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
170
|
+
# Memory bridge: claude maintains its own session JSONL on disk
|
|
171
|
+
# under `<CLAUDE_CONFIG_DIR>/projects/<cwd-encoded>/<UUID>.jsonl`.
|
|
172
|
+
# The project_key is derived from cwd (`[^a-zA-Z0-9.-] -> -`),
|
|
173
|
+
# so memory continuity needs (a) a STABLE cwd across runs and
|
|
174
|
+
# (b) the right session_id passed on argv.
|
|
175
|
+
#
|
|
176
|
+
# (a) is handled by AICliService passing memory_bound=True so
|
|
177
|
+
# AICliSession spawns under repo_root instead of an ephemeral
|
|
178
|
+
# worktree — see `services/cli_agent/session.py:cwd()`.
|
|
179
|
+
#
|
|
180
|
+
# (b) splits on whether claude has run for this memory before:
|
|
181
|
+
#
|
|
182
|
+
# - First run (no last_session_id): pass `--session-id <UUID5>`
|
|
183
|
+
# where UUID5 is deterministically derived from
|
|
184
|
+
# (memory_node_id, simpleMemory.session_id). Claude creates
|
|
185
|
+
# the session at exactly that UUID, writes its JSONL to a
|
|
186
|
+
# predictable location. _persist_memory saves the same
|
|
187
|
+
# UUID as last_session_id after success.
|
|
188
|
+
#
|
|
189
|
+
# - Subsequent runs: pass `--resume <last_session_id>`.
|
|
190
|
+
# Claude finds its own JSONL and continues.
|
|
191
|
+
#
|
|
192
|
+
# Stale-`last_session_id` self-healing: if `--resume` fails with
|
|
193
|
+
# "No conversation found", `_persist_memory` clears the stale
|
|
194
|
+
# UUID; the next run falls into the first-run branch and
|
|
195
|
+
# recovers automatically.
|
|
196
|
+
resume_session_id: Optional[str] = None
|
|
197
|
+
first_run_session_uuid: Optional[str] = None
|
|
198
|
+
if memory_data:
|
|
199
|
+
last = memory_data.get("last_session_id") or None
|
|
200
|
+
if last:
|
|
201
|
+
resume_session_id = last
|
|
202
|
+
else:
|
|
203
|
+
raw = memory_data.get("session_id") or memory_data["node_id"]
|
|
204
|
+
name = f"{memory_data['node_id']}:{raw}"
|
|
205
|
+
first_run_session_uuid = str(
|
|
206
|
+
uuid.uuid5(uuid.NAMESPACE_OID, name)
|
|
207
|
+
)
|
|
208
|
+
logger.info(
|
|
209
|
+
"[Claude Code memory] memory_node=%s last_session_id=%s "
|
|
210
|
+
"-> %s",
|
|
211
|
+
memory_data.get("node_id"),
|
|
212
|
+
last,
|
|
213
|
+
f"--resume {resume_session_id}" if resume_session_id
|
|
214
|
+
else f"--session-id {first_run_session_uuid} (first run)",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
tasks = list(params.tasks)
|
|
218
|
+
if not tasks:
|
|
219
|
+
# AI-Agent-style prompt resolution: explicit `prompt` field
|
|
220
|
+
# wins; otherwise extract from upstream input via the same
|
|
221
|
+
# `message > text > content > str()` chain `_inline.py` uses.
|
|
222
|
+
prompt = (params.prompt or "").strip()
|
|
223
|
+
if not prompt and input_data:
|
|
224
|
+
prompt = (
|
|
225
|
+
(input_data.get("message") if isinstance(input_data, dict) else None)
|
|
226
|
+
or (input_data.get("text") if isinstance(input_data, dict) else None)
|
|
227
|
+
or (input_data.get("content") if isinstance(input_data, dict) else None)
|
|
228
|
+
or str(input_data)
|
|
229
|
+
)
|
|
230
|
+
if not prompt:
|
|
231
|
+
raise NodeUserError(
|
|
232
|
+
"Claude Code agent has no prompt — fill in the Prompt "
|
|
233
|
+
"field, populate the Tasks array, or connect a node "
|
|
234
|
+
"to the Input handle."
|
|
235
|
+
)
|
|
236
|
+
spec_kwargs: dict = {
|
|
237
|
+
"prompt": prompt,
|
|
238
|
+
"model": params.model,
|
|
239
|
+
"system_prompt": params.system_prompt,
|
|
240
|
+
}
|
|
241
|
+
if resume_session_id:
|
|
242
|
+
spec_kwargs["resume_session_id"] = resume_session_id
|
|
243
|
+
elif first_run_session_uuid:
|
|
244
|
+
spec_kwargs["session_id"] = first_run_session_uuid
|
|
245
|
+
tasks = [ClaudeTaskSpec(**spec_kwargs)]
|
|
246
|
+
else:
|
|
247
|
+
# Apply node-level defaults to tasks that don't override.
|
|
248
|
+
for i, t in enumerate(tasks):
|
|
249
|
+
changed: dict = {}
|
|
250
|
+
if not t.model and params.model:
|
|
251
|
+
changed["model"] = params.model
|
|
252
|
+
if not t.system_prompt and params.system_prompt:
|
|
253
|
+
changed["system_prompt"] = params.system_prompt
|
|
254
|
+
if not t.session_id and not t.resume_session_id:
|
|
255
|
+
if resume_session_id:
|
|
256
|
+
changed["resume_session_id"] = resume_session_id
|
|
257
|
+
elif first_run_session_uuid:
|
|
258
|
+
changed["session_id"] = first_run_session_uuid
|
|
259
|
+
if changed:
|
|
260
|
+
tasks[i] = t.model_copy(update=changed)
|
|
261
|
+
|
|
262
|
+
# Memory continuity requires serial execution; parallel
|
|
263
|
+
# `--resume` against one session JSONL would corrupt it.
|
|
264
|
+
if memory_data and len(tasks) > 1:
|
|
265
|
+
raise NodeUserError(
|
|
266
|
+
"Memory-bound batches must run one task at a time. "
|
|
267
|
+
"Reduce Tasks to a single entry, or disconnect the "
|
|
268
|
+
"memory node."
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Workspace dir — workflow.py injects this into context
|
|
272
|
+
workspace_dir = ctx.raw.get("workspace_dir") or params.working_directory
|
|
273
|
+
if workspace_dir is None:
|
|
274
|
+
from core.config import Settings
|
|
275
|
+
workspace_dir = Path(Settings().workspace_base_resolved) / (
|
|
276
|
+
workflow_id or "default"
|
|
277
|
+
)
|
|
278
|
+
workspace_dir = Path(workspace_dir)
|
|
279
|
+
|
|
280
|
+
repo_root = (
|
|
281
|
+
Path(params.working_directory) if params.working_directory else None
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
svc = get_ai_cli_service()
|
|
285
|
+
result = await svc.run_batch(
|
|
286
|
+
"claude",
|
|
287
|
+
tasks=tasks,
|
|
110
288
|
node_id=node_id,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
289
|
+
workflow_id=workflow_id or "",
|
|
290
|
+
workspace_dir=workspace_dir,
|
|
291
|
+
broadcaster=broadcaster,
|
|
292
|
+
repo_root=repo_root,
|
|
293
|
+
connected_skill_names=connected_skills,
|
|
294
|
+
connected_tools=tool_data,
|
|
295
|
+
connected_memory=memory_data,
|
|
296
|
+
allowed_credentials=params.allowed_credentials,
|
|
297
|
+
max_parallel=params.max_parallel,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
elapsed = time.time() - start_time
|
|
301
|
+
logger.debug(
|
|
302
|
+
"[claude_code_agent] node=%s tasks=%d ok=%d fail=%d elapsed=%.2fs",
|
|
303
|
+
node_id, result.n_tasks, result.n_succeeded, result.n_failed, elapsed,
|
|
117
304
|
)
|
|
118
305
|
|
|
119
306
|
await broadcaster.update_node_status(
|
|
120
|
-
node_id, "success",
|
|
121
|
-
{
|
|
307
|
+
node_id, "success" if result.n_failed == 0 else "warning",
|
|
308
|
+
{
|
|
309
|
+
"message": (
|
|
310
|
+
f"Batch complete: {result.n_succeeded}/{result.n_tasks} "
|
|
311
|
+
f"succeeded"
|
|
312
|
+
),
|
|
313
|
+
"n_tasks": result.n_tasks,
|
|
314
|
+
"n_succeeded": result.n_succeeded,
|
|
315
|
+
"n_failed": result.n_failed,
|
|
316
|
+
},
|
|
122
317
|
workflow_id=workflow_id,
|
|
123
318
|
)
|
|
124
|
-
|
|
319
|
+
|
|
320
|
+
task_models = [session_result_to_model(t).model_dump() for t in result.tasks]
|
|
321
|
+
|
|
322
|
+
# Legacy single-task convenience fields
|
|
323
|
+
legacy_response = None
|
|
324
|
+
legacy_session_id = None
|
|
325
|
+
legacy_cost = None
|
|
326
|
+
if len(result.tasks) == 1:
|
|
327
|
+
legacy_response = result.tasks[0].response
|
|
328
|
+
legacy_session_id = result.tasks[0].session_id
|
|
329
|
+
legacy_cost = result.tasks[0].cost_usd
|
|
125
330
|
|
|
126
331
|
return {
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
332
|
+
"success": result.n_failed == 0,
|
|
333
|
+
"n_tasks": result.n_tasks,
|
|
334
|
+
"n_succeeded": result.n_succeeded,
|
|
335
|
+
"n_failed": result.n_failed,
|
|
336
|
+
"total_cost_usd": result.total_cost_usd,
|
|
337
|
+
"wall_clock_ms": result.wall_clock_ms,
|
|
338
|
+
"tasks": task_models,
|
|
339
|
+
"provider": result.provider,
|
|
340
|
+
"timestamp": result.timestamp or datetime.now().isoformat(),
|
|
341
|
+
"response": legacy_response,
|
|
342
|
+
"session_id": legacy_session_id,
|
|
343
|
+
"cost_usd": legacy_cost,
|
|
133
344
|
}
|
|
345
|
+
|