machinaos 0.0.78 → 0.0.80
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/.env.template +74 -5
- package/{workflows/AI Assistant_workflow-1778504793388-ou1m1tz2x.json → .machina/workflows/AI Assistant_example_workflow-1779017037684-e2e5da7a.json } +164 -105
- package/{workflows/AI Employee_example_workflow-1777720598005-u4cm858dv.json → .machina/workflows/AI Employee_example_workflow-1779102911870-cbc76c82.json } +582 -328
- package/{workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json → .machina/workflows/Claude Assistant_example_workflow-1779095939967-2369cff4.json } +152 -83
- package/README.md +5 -2
- package/bin/cli.js +2 -2
- package/{machina → cli}/__main__.py +11 -7
- package/cli/_common.py +122 -0
- package/cli/buildenv.py +40 -0
- package/cli/cli.py +204 -0
- package/{machina → cli}/colors.py +10 -2
- package/cli/commands/__init__.py +1 -0
- package/cli/commands/_temporal_specs.py +59 -0
- package/{machina → cli}/commands/build.py +35 -45
- package/cli/commands/clean.py +141 -0
- package/cli/commands/daemon/__init__.py +47 -0
- package/cli/commands/daemon/_state.py +97 -0
- package/cli/commands/daemon/restart.py +14 -0
- package/cli/commands/daemon/start.py +49 -0
- package/cli/commands/daemon/status.py +20 -0
- package/cli/commands/daemon/stop.py +22 -0
- package/{machina → cli}/commands/dev.py +32 -42
- package/{machina → cli}/commands/docs.py +13 -11
- package/{machina → cli}/commands/start.py +69 -62
- package/{machina → cli}/commands/stop.py +7 -10
- package/{machina → cli}/commands/version.py +12 -6
- package/cli/config.py +170 -0
- package/cli/platform_.py +169 -0
- package/{machina → cli}/ports.py +42 -3
- package/{machina → cli}/run.py +29 -2
- package/{machina → cli}/supervisor.py +29 -12
- package/{machina → cli}/tcp.py +6 -2
- package/{machina → cli}/tree.py +38 -11
- package/client/dist/assets/{ActionBar-Du2MSFSz.js → ActionBar-Cjr3TF7g.js} +1 -1
- package/client/dist/assets/{ApiKeyInput-k2LBmBjb.js → ApiKeyInput-DIJE2PVA.js} +1 -1
- package/client/dist/assets/{ApiKeyPanel-C_bV9U0X.js → ApiKeyPanel-CPmye7uh.js} +1 -1
- package/client/dist/assets/{ApiUsageSection-CmVfwZzL.js → ApiUsageSection-TF_7gH2D.js} +1 -1
- package/client/dist/assets/{EmailPanel-CeKIMGu-.js → EmailPanel-Bs-xvbKR.js} +1 -1
- package/client/dist/assets/{OAuthPanel-KA3t3Q2K.js → OAuthPanel-BDtVJhAV.js} +1 -1
- package/client/dist/assets/{QrPairingPanel-NgNpJNuk.js → QrPairingPanel-BwJehTuZ.js} +1 -1
- package/client/dist/assets/{RateLimitSection-Du5YNVIA.js → RateLimitSection-CfNOoPIS.js} +1 -1
- package/client/dist/assets/{StatusCard-DNLyayXc.js → StatusCard-DkwIrgdP.js} +1 -1
- package/client/dist/assets/index-P2FzntoL.js +165 -0
- package/client/dist/index.html +1 -1
- package/client/package.json +1 -1
- package/client/src/Dashboard.tsx +128 -76
- package/client/src/adapters/nodeSpecToDescription.ts +7 -0
- package/client/src/assets/icons/index.test.ts +10 -0
- package/client/src/assets/icons/index.ts +16 -3
- package/client/src/components/AIAgentNode.tsx +8 -8
- package/client/src/components/ParameterRenderer.tsx +6 -3
- package/client/src/components/SkillEditorModal.tsx +1 -0
- package/client/src/components/credentials/panels/EmailPanel.tsx +2 -0
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +2 -0
- package/client/src/components/credentials/sections/RateLimitSection.tsx +1 -0
- package/client/src/components/icons/AIProviderIcons.tsx +1 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +1 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1 -0
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +1 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +1 -0
- package/client/src/components/ui/ComponentPalette.tsx +1 -0
- package/client/src/components/ui/MapSelector.tsx +1 -0
- package/client/src/components/ui/NodeContextMenu.tsx +3 -3
- package/client/src/components/ui/SettingsPanel.tsx +1 -0
- package/client/src/components/ui/action-button.tsx +1 -0
- package/client/src/components/ui/badge.tsx +1 -0
- package/client/src/components/ui/button.tsx +1 -0
- package/client/src/components/ui/form.tsx +1 -0
- package/client/src/components/ui/tabs.tsx +1 -0
- package/client/src/contexts/AuthContext.tsx +1 -0
- package/client/src/contexts/ThemeContext.tsx +1 -0
- package/client/src/contexts/WebSocketContext.tsx +104 -34
- package/client/src/hooks/__tests__/useApiKeys.test.ts +2 -2
- package/client/src/hooks/useReactFlowNodes.ts +1 -0
- package/client/src/hooks/useWorkflowValidation.ts +142 -0
- package/client/src/lib/nodeSpec.ts +1 -0
- package/client/src/test/providers.tsx +1 -0
- package/client/src/types/__tests__/cloudEvents.test.ts +5 -2
- package/client/src/types/cloudEvents.ts +19 -7
- package/client/src/utils/nodeUtils.ts +1 -1
- package/client/src/utils/workflow.ts +8 -2
- package/client/src/utils/workflowExport.ts +60 -3
- package/package.json +24 -23
- package/scripts/install.js +16 -27
- package/scripts/migrate_icons.py +3 -1
- package/scripts/migrate_skill_icons.py +6 -7
- package/scripts/postinstall.js +11 -9
- package/server/config/ai_cli_providers.json +2 -3
- package/server/config/credential_providers.json +15 -15
- package/server/config/llm_defaults.json +1 -1
- package/server/config/model_registry.json +416 -611
- package/server/constants.py +285 -223
- package/server/core/__init__.py +1 -1
- package/server/core/cache.py +9 -29
- package/server/core/cleanup.py +12 -24
- package/server/core/config.py +148 -24
- package/server/core/container.py +68 -59
- package/server/core/credential_backends.py +5 -13
- package/server/core/credentials_database.py +13 -43
- package/server/core/database.py +292 -353
- package/server/core/health.py +4 -5
- package/server/core/logging.py +241 -87
- package/server/core/paths.py +285 -0
- package/server/core/tracing.py +2 -8
- package/server/gunicorn.conf.py +1 -0
- package/server/main.py +150 -74
- package/server/middleware/auth.py +18 -24
- package/server/models/__init__.py +1 -1
- package/server/models/auth.py +5 -12
- package/server/models/database.py +36 -68
- package/server/models/node_metadata.py +25 -18
- package/server/nodejs/dist/index.js +107 -0
- package/server/nodes/README.md +11 -5
- package/server/nodes/__init__.py +1 -1
- package/server/nodes/_visuals.py +146 -14
- package/server/nodes/agent/_events.py +124 -0
- package/server/nodes/agent/_handles.py +15 -29
- package/server/nodes/agent/_inline.py +28 -25
- package/server/nodes/agent/_specialized.py +30 -15
- package/server/nodes/agent/{ai_agent.py → ai_agent/__init__.py} +33 -17
- package/server/nodes/agent/ai_agent/meta.json +3 -0
- package/server/nodes/agent/{ai_employee.py → ai_employee/__init__.py} +5 -2
- package/server/nodes/agent/ai_employee/meta.json +3 -0
- package/server/nodes/agent/{android_agent.py → android_agent/__init__.py} +1 -1
- package/server/nodes/agent/android_agent/meta.json +3 -0
- package/server/nodes/agent/{autonomous_agent.py → autonomous_agent/__init__.py} +2 -1
- package/server/nodes/agent/autonomous_agent/meta.json +3 -0
- package/server/nodes/agent/{chat_agent.py → chat_agent/__init__.py} +29 -12
- package/server/nodes/agent/chat_agent/meta.json +3 -0
- package/server/nodes/agent/{claude_code_agent.py → claude_code_agent/__init__.py} +192 -95
- package/server/nodes/agent/claude_code_agent/_handlers.py +169 -0
- package/server/{services/claude_oauth.py → nodes/agent/claude_code_agent/_oauth.py} +26 -13
- package/server/nodes/agent/claude_code_agent/_pool.py +1020 -0
- package/server/nodes/agent/claude_code_agent/_provider.py +513 -0
- package/server/nodes/agent/claude_code_agent/_skills.py +245 -0
- package/server/nodes/agent/claude_code_agent/meta.json +3 -0
- package/server/nodes/agent/{codex_agent.py → codex_agent/__init__.py} +26 -35
- package/server/nodes/agent/codex_agent/meta.json +3 -0
- package/server/nodes/agent/{coding_agent.py → coding_agent/__init__.py} +1 -1
- package/server/nodes/agent/coding_agent/meta.json +3 -0
- package/server/nodes/agent/{consumer_agent.py → consumer_agent/__init__.py} +1 -1
- package/server/nodes/agent/consumer_agent/meta.json +3 -0
- package/server/nodes/agent/{orchestrator_agent.py → orchestrator_agent/__init__.py} +5 -2
- package/server/nodes/agent/orchestrator_agent/meta.json +3 -0
- package/server/nodes/agent/{payments_agent.py → payments_agent/__init__.py} +1 -1
- package/server/nodes/agent/payments_agent/meta.json +3 -0
- package/server/nodes/agent/{productivity_agent.py → productivity_agent/__init__.py} +1 -1
- package/server/nodes/agent/productivity_agent/meta.json +3 -0
- package/server/nodes/agent/{rlm_agent.py → rlm_agent/__init__.py} +18 -17
- package/server/nodes/agent/rlm_agent/meta.json +3 -0
- package/server/nodes/agent/{social_agent.py → social_agent/__init__.py} +1 -1
- package/server/nodes/agent/social_agent/meta.json +3 -0
- package/server/nodes/agent/{task_agent.py → task_agent/__init__.py} +1 -1
- package/server/nodes/agent/task_agent/meta.json +3 -0
- package/server/nodes/agent/{tool_agent.py → tool_agent/__init__.py} +1 -1
- package/server/nodes/agent/tool_agent/meta.json +3 -0
- package/server/nodes/agent/{travel_agent.py → travel_agent/__init__.py} +1 -1
- package/server/nodes/agent/travel_agent/meta.json +3 -0
- package/server/nodes/agent/{web_agent.py → web_agent/__init__.py} +1 -1
- package/server/nodes/agent/web_agent/meta.json +3 -0
- package/server/nodes/android/__init__.py +24 -0
- package/server/nodes/android/_base.py +93 -76
- package/server/nodes/android/_dispatcher.py +140 -223
- package/server/nodes/android/_events.py +154 -0
- package/server/nodes/android/_handlers.py +13 -7
- package/server/nodes/android/_option_loaders.py +1 -4
- package/server/nodes/android/_refresh.py +27 -37
- package/server/nodes/android/_relay/broadcaster.py +25 -41
- package/server/nodes/android/_relay/client.py +23 -42
- package/server/nodes/android/_relay/manager.py +1 -0
- package/server/nodes/android/_relay/protocol.py +6 -0
- package/server/nodes/android/_router.py +48 -133
- package/server/nodes/android/{airplane_mode_control.py → airplane_mode_control/__init__.py} +2 -1
- package/server/nodes/android/airplane_mode_control/meta.json +3 -0
- package/server/nodes/android/{app_launcher.py → app_launcher/__init__.py} +2 -1
- package/server/nodes/android/app_launcher/meta.json +3 -0
- package/server/nodes/android/{app_list.py → app_list/__init__.py} +2 -1
- package/server/nodes/android/app_list/meta.json +3 -0
- package/server/nodes/android/{audio_automation.py → audio_automation/__init__.py} +2 -1
- package/server/nodes/android/audio_automation/meta.json +3 -0
- package/server/nodes/android/{battery_monitor.py → battery_monitor/__init__.py} +2 -1
- package/server/nodes/android/battery_monitor/meta.json +3 -0
- package/server/nodes/android/{bluetooth_automation.py → bluetooth_automation/__init__.py} +2 -1
- package/server/nodes/android/bluetooth_automation/meta.json +3 -0
- package/server/nodes/android/{camera_control.py → camera_control/__init__.py} +2 -1
- package/server/nodes/android/camera_control/meta.json +3 -0
- package/server/nodes/android/{device_state_automation.py → device_state_automation/__init__.py} +2 -1
- package/server/nodes/android/device_state_automation/meta.json +3 -0
- package/server/nodes/android/{environmental_sensors.py → environmental_sensors/__init__.py} +2 -1
- package/server/nodes/android/environmental_sensors/meta.json +3 -0
- package/server/nodes/android/{location.py → location/__init__.py} +2 -1
- package/server/nodes/android/location/meta.json +3 -0
- package/server/nodes/android/{media_control.py → media_control/__init__.py} +2 -1
- package/server/nodes/android/media_control/meta.json +3 -0
- package/server/nodes/android/{motion_detection.py → motion_detection/__init__.py} +2 -1
- package/server/nodes/android/motion_detection/meta.json +3 -0
- package/server/nodes/android/{network_monitor.py → network_monitor/__init__.py} +2 -1
- package/server/nodes/android/network_monitor/meta.json +3 -0
- package/server/nodes/android/{screen_control_automation.py → screen_control_automation/__init__.py} +2 -1
- package/server/nodes/android/screen_control_automation/meta.json +3 -0
- package/server/nodes/android/{system_info.py → system_info/__init__.py} +2 -1
- package/server/nodes/android/system_info/meta.json +3 -0
- package/server/nodes/android/{wifi_automation.py → wifi_automation/__init__.py} +2 -1
- package/server/nodes/android/wifi_automation/meta.json +3 -0
- package/server/nodes/browser/__init__.py +22 -1
- package/server/nodes/browser/_install.py +63 -0
- package/server/nodes/browser/_service.py +21 -25
- package/server/nodes/browser/{browser.py → browser/__init__.py} +58 -25
- package/server/nodes/browser/browser/meta.json +3 -0
- package/server/nodes/chat/{chat_history.py → chat_history/__init__.py} +2 -4
- package/server/nodes/chat/chat_history/meta.json +3 -0
- package/server/nodes/chat/{chat_send.py → chat_send/__init__.py} +2 -4
- package/server/nodes/chat/chat_send/icon.svg +1 -0
- package/server/nodes/chat/chat_send/meta.json +3 -0
- package/server/nodes/code/_base.py +1 -1
- package/server/nodes/code/{javascript_executor.py → javascript_executor/__init__.py} +5 -5
- package/server/nodes/code/javascript_executor/meta.json +3 -0
- package/server/nodes/code/{python_executor.py → python_executor/__init__.py} +32 -14
- package/server/nodes/code/python_executor/meta.json +3 -0
- package/server/nodes/code/{typescript_executor.py → typescript_executor/__init__.py} +5 -5
- package/server/nodes/code/typescript_executor/meta.json +3 -0
- package/server/nodes/document/{document_parser.py → document_parser/__init__.py} +26 -15
- package/server/nodes/document/document_parser/meta.json +3 -0
- package/server/nodes/document/{embedding_generator.py → embedding_generator/__init__.py} +16 -9
- package/server/nodes/document/embedding_generator/meta.json +3 -0
- package/server/nodes/document/{file_downloader.py → file_downloader/__init__.py} +30 -20
- package/server/nodes/document/file_downloader/meta.json +3 -0
- package/server/nodes/document/{http_scraper.py → http_scraper/__init__.py} +31 -21
- package/server/nodes/document/http_scraper/meta.json +3 -0
- package/server/nodes/document/{text_chunker.py → text_chunker/__init__.py} +17 -12
- package/server/nodes/document/text_chunker/meta.json +3 -0
- package/server/nodes/document/{vector_store.py → vector_store/__init__.py} +88 -72
- package/server/nodes/document/vector_store/meta.json +3 -0
- package/server/nodes/email/__init__.py +9 -2
- package/server/nodes/email/_events.py +54 -0
- package/server/nodes/email/_filters.py +3 -3
- package/server/nodes/email/_himalaya.py +95 -50
- package/server/nodes/email/_service.py +23 -13
- package/server/nodes/email/{email_read.py → email_read/__init__.py} +23 -11
- package/server/nodes/email/email_read/icon.svg +6 -0
- package/server/nodes/email/email_read/meta.json +3 -0
- package/server/nodes/email/{email_receive.py → email_receive/__init__.py} +45 -23
- package/server/nodes/email/email_receive/meta.json +3 -0
- package/server/nodes/email/{email_send.py → email_send/__init__.py} +13 -7
- package/server/nodes/email/email_send/meta.json +3 -0
- package/server/nodes/filesystem/_backend.py +1 -5
- package/server/nodes/filesystem/{file_modify.py → file_modify/__init__.py} +10 -5
- package/server/nodes/filesystem/file_modify/meta.json +3 -0
- package/server/nodes/filesystem/{file_read.py → file_read/__init__.py} +7 -3
- package/server/nodes/filesystem/file_read/meta.json +3 -0
- package/server/nodes/filesystem/{fs_search.py → fs_search/__init__.py} +11 -3
- package/server/nodes/filesystem/fs_search/meta.json +3 -0
- package/server/nodes/filesystem/{shell.py → shell/__init__.py} +12 -5
- package/server/nodes/filesystem/shell/meta.json +3 -0
- package/server/nodes/google/__init__.py +12 -0
- package/server/nodes/google/_auth_helper.py +7 -13
- package/server/nodes/google/_base.py +14 -11
- package/server/nodes/google/_credentials.py +2 -1
- package/server/nodes/google/_events.py +47 -0
- package/server/nodes/google/_filters.py +3 -3
- package/server/nodes/google/_gmail.py +70 -47
- package/server/nodes/google/_handlers.py +3 -1
- package/server/nodes/google/_oauth.py +25 -11
- package/server/nodes/google/_option_loaders.py +9 -30
- package/server/nodes/google/_refresh.py +8 -12
- package/server/nodes/google/_router.py +4 -5
- package/server/nodes/google/{calendar.py → calendar/__init__.py} +87 -64
- package/server/nodes/google/calendar/meta.json +3 -0
- package/server/nodes/google/{contacts.py → contacts/__init__.py} +84 -72
- package/server/nodes/google/contacts/meta.json +3 -0
- package/server/nodes/google/{drive.py → drive/__init__.py} +87 -72
- package/server/nodes/google/drive/meta.json +3 -0
- package/server/nodes/google/{gmail.py → gmail/__init__.py} +73 -39
- package/server/nodes/google/gmail/meta.json +3 -0
- package/server/nodes/google/{gmail_receive.py → gmail_receive/__init__.py} +31 -24
- package/server/nodes/google/gmail_receive/icon.svg +7 -0
- package/server/nodes/google/gmail_receive/meta.json +3 -0
- package/server/nodes/google/google.svg +7 -0
- package/server/nodes/google/{sheets.py → sheets/__init__.py} +54 -42
- package/server/nodes/google/sheets/meta.json +3 -0
- package/server/nodes/google/{tasks.py → tasks/__init__.py} +56 -43
- package/server/nodes/google/tasks/meta.json +3 -0
- package/server/nodes/groups.py +28 -28
- package/server/nodes/location/__init__.py +31 -1
- package/server/nodes/location/_credentials.py +1 -6
- package/server/nodes/location/_service.py +88 -107
- package/server/nodes/location/{gmaps_create.py → gmaps_create/__init__.py} +6 -6
- package/server/nodes/location/gmaps_create/meta.json +3 -0
- package/server/nodes/location/{gmaps_locations.py → gmaps_locations/__init__.py} +8 -6
- package/server/nodes/location/gmaps_locations/meta.json +3 -0
- package/server/nodes/location/{gmaps_nearby_places.py → gmaps_nearby_places/__init__.py} +8 -6
- package/server/nodes/location/gmaps_nearby_places/meta.json +3 -0
- package/server/nodes/model/_base.py +10 -7
- package/server/nodes/model/_credentials.py +10 -10
- package/server/nodes/model/_local_validator.py +28 -24
- package/server/nodes/model/{anthropic_chat_model.py → anthropic_chat_model/__init__.py} +5 -3
- package/server/nodes/model/anthropic_chat_model/meta.json +3 -0
- package/server/nodes/model/{cerebras_chat_model.py → cerebras_chat_model/__init__.py} +5 -3
- package/server/nodes/model/cerebras_chat_model/meta.json +3 -0
- package/server/nodes/model/{deepseek_chat_model.py → deepseek_chat_model/__init__.py} +8 -4
- package/server/nodes/model/deepseek_chat_model/meta.json +3 -0
- package/server/nodes/model/{gemini_chat_model.py → gemini_chat_model/__init__.py} +5 -3
- package/server/nodes/model/gemini_chat_model/meta.json +3 -0
- package/server/nodes/model/{groq_chat_model.py → groq_chat_model/__init__.py} +2 -2
- package/server/nodes/model/groq_chat_model/meta.json +3 -0
- package/server/nodes/model/{kimi_chat_model.py → kimi_chat_model/__init__.py} +2 -2
- package/server/nodes/model/kimi_chat_model/meta.json +3 -0
- package/server/nodes/model/{lmstudio_chat_model.py → lmstudio_chat_model/__init__.py} +2 -2
- package/server/nodes/model/lmstudio_chat_model/meta.json +3 -0
- package/server/nodes/model/{mistral_chat_model.py → mistral_chat_model/__init__.py} +2 -2
- package/server/nodes/model/mistral_chat_model/meta.json +3 -0
- package/server/nodes/model/{ollama_chat_model.py → ollama_chat_model/__init__.py} +2 -2
- package/server/nodes/model/ollama_chat_model/meta.json +3 -0
- package/server/nodes/model/{openai_chat_model.py → openai_chat_model/__init__.py} +8 -4
- package/server/nodes/model/openai_chat_model/meta.json +3 -0
- package/server/nodes/model/{openrouter_chat_model.py → openrouter_chat_model/__init__.py} +8 -4
- package/server/nodes/model/openrouter_chat_model/meta.json +3 -0
- package/server/nodes/proxy/_usage.py +14 -15
- package/server/nodes/proxy/{proxy_config.py → proxy_config/__init__.py} +39 -30
- package/server/nodes/proxy/proxy_config/meta.json +3 -0
- package/server/nodes/proxy/{proxy_request.py → proxy_request/__init__.py} +30 -16
- package/server/nodes/proxy/proxy_request/meta.json +3 -0
- package/server/nodes/proxy/{proxy_status.py → proxy_status/__init__.py} +2 -0
- package/server/nodes/proxy/proxy_status/meta.json +3 -0
- package/server/nodes/scheduler/{cron_scheduler.py → cron_scheduler/__init__.py} +96 -23
- package/server/nodes/scheduler/cron_scheduler/_workflow.py +155 -0
- package/server/nodes/scheduler/cron_scheduler/meta.json +3 -0
- package/server/nodes/scheduler/{timer.py → timer/__init__.py} +6 -5
- package/server/nodes/scheduler/timer/meta.json +3 -0
- package/server/nodes/scraper/_credentials.py +0 -1
- package/server/nodes/scraper/{apify_actor.py → apify_actor/__init__.py} +44 -35
- package/server/nodes/scraper/apify_actor/icon.svg +5 -0
- package/server/nodes/scraper/apify_actor/meta.json +3 -0
- package/server/nodes/scraper/{crawlee_scraper.py → crawlee_scraper/__init__.py} +96 -57
- package/server/nodes/scraper/crawlee_scraper/meta.json +3 -0
- package/server/nodes/search/{brave_search.py → brave_search/__init__.py} +6 -5
- package/server/nodes/search/brave_search/icon.svg +3 -0
- package/server/nodes/search/brave_search/meta.json +3 -0
- package/server/nodes/search/{duckduckgo_search.py → duckduckgo_search/__init__.py} +17 -6
- package/server/nodes/search/duckduckgo_search/meta.json +3 -0
- package/server/nodes/search/{perplexity_search.py → perplexity_search/__init__.py} +4 -5
- package/server/nodes/search/perplexity_search/icon.svg +3 -0
- package/server/nodes/search/perplexity_search/meta.json +3 -0
- package/server/nodes/search/{serper_search.py → serper_search/__init__.py} +32 -25
- package/server/nodes/search/serper_search/icon.svg +3 -0
- package/server/nodes/search/serper_search/meta.json +3 -0
- package/server/nodes/skill/__init__.py +21 -1
- package/server/nodes/skill/_expander.py +75 -0
- package/server/nodes/skill/{master_skill.py → master_skill/__init__.py} +2 -8
- package/server/nodes/skill/master_skill/_events.py +84 -0
- package/server/nodes/skill/master_skill/meta.json +3 -0
- package/server/nodes/skill/{simple_memory.py → simple_memory/__init__.py} +8 -16
- package/server/nodes/skill/simple_memory/meta.json +3 -0
- package/server/nodes/social/_base.py +223 -231
- package/server/nodes/social/{social_receive.py → social_receive/__init__.py} +38 -13
- package/server/nodes/social/social_receive/meta.json +3 -0
- package/server/nodes/social/{social_send.py → social_send/__init__.py} +71 -29
- package/server/nodes/social/social_send/icon.svg +1 -0
- package/server/nodes/social/social_send/meta.json +3 -0
- package/server/nodes/stripe/__init__.py +7 -3
- package/server/nodes/stripe/_credentials.py +0 -1
- package/server/nodes/stripe/_handlers.py +18 -7
- package/server/nodes/stripe/_install.py +14 -15
- package/server/nodes/stripe/_source.py +5 -5
- package/server/nodes/stripe/icon.svg +1 -0
- package/server/nodes/stripe/meta.json +3 -0
- package/server/nodes/stripe/stripe_action.py +4 -4
- package/server/nodes/stripe/stripe_receive.py +6 -9
- package/server/nodes/telegram/__init__.py +13 -0
- package/server/nodes/telegram/_credentials.py +2 -7
- package/server/nodes/telegram/_events.py +167 -0
- package/server/nodes/telegram/_filters.py +3 -11
- package/server/nodes/telegram/_handlers.py +17 -7
- package/server/nodes/telegram/_refresh.py +24 -34
- package/server/nodes/telegram/_service.py +29 -45
- package/server/nodes/telegram/meta.json +3 -0
- package/server/nodes/telegram/telegram.svg +3 -0
- package/server/nodes/telegram/telegram_receive.py +38 -18
- package/server/nodes/telegram/telegram_send.py +21 -19
- package/server/nodes/text/{file_handler.py → file_handler/__init__.py} +7 -1
- package/server/nodes/text/file_handler/meta.json +3 -0
- package/server/nodes/text/{text_generator.py → text_generator/__init__.py} +2 -1
- package/server/nodes/text/text_generator/meta.json +3 -0
- package/server/nodes/tool/{agent_builder.py → agent_builder/__init__.py} +105 -100
- package/server/nodes/tool/agent_builder/_events.py +91 -0
- package/server/nodes/tool/agent_builder/meta.json +3 -0
- package/server/nodes/tool/{calculator_tool.py → calculator_tool/__init__.py} +19 -7
- package/server/nodes/tool/calculator_tool/meta.json +3 -0
- package/server/nodes/tool/{current_time_tool.py → current_time_tool/__init__.py} +6 -4
- package/server/nodes/tool/current_time_tool/meta.json +3 -0
- package/server/nodes/tool/{task_manager.py → task_manager/__init__.py} +17 -18
- package/server/nodes/tool/task_manager/meta.json +3 -0
- package/server/nodes/tool/{write_todos.py → write_todos/__init__.py} +20 -6
- package/server/nodes/tool/write_todos/meta.json +3 -0
- package/server/nodes/trigger/{chat_trigger.py → chat_trigger/__init__.py} +11 -7
- package/server/nodes/trigger/chat_trigger/_events.py +53 -0
- package/server/nodes/trigger/chat_trigger/meta.json +3 -0
- package/server/nodes/trigger/{task_trigger.py → task_trigger/__init__.py} +10 -7
- package/server/nodes/trigger/task_trigger/meta.json +3 -0
- package/server/nodes/trigger/{webhook_trigger.py → webhook_trigger/__init__.py} +10 -7
- package/server/nodes/trigger/webhook_trigger/_events.py +54 -0
- package/server/nodes/trigger/webhook_trigger/meta.json +3 -0
- package/server/nodes/twitter/__init__.py +7 -1
- package/server/nodes/twitter/_base.py +86 -61
- package/server/nodes/twitter/_credentials.py +7 -5
- package/server/nodes/twitter/_events.py +101 -0
- package/server/nodes/twitter/_filters.py +9 -9
- package/server/nodes/twitter/_handlers.py +3 -1
- package/server/nodes/twitter/_oauth.py +1 -2
- package/server/nodes/twitter/_refresh.py +8 -12
- package/server/nodes/twitter/{twitter_receive.py → twitter_receive/__init__.py} +7 -7
- package/server/nodes/twitter/twitter_receive/icon.svg +1 -0
- package/server/nodes/twitter/twitter_receive/meta.json +3 -0
- package/server/nodes/twitter/{twitter_search.py → twitter_search/__init__.py} +16 -11
- package/server/nodes/twitter/twitter_search/icon.svg +1 -0
- package/server/nodes/twitter/twitter_search/meta.json +3 -0
- package/server/nodes/twitter/{twitter_send.py → twitter_send/__init__.py} +60 -27
- package/server/nodes/twitter/twitter_send/icon.svg +1 -0
- package/server/nodes/twitter/twitter_send/meta.json +3 -0
- package/server/nodes/twitter/{twitter_user.py → twitter_user/__init__.py} +34 -19
- package/server/nodes/twitter/twitter_user/icon.svg +1 -0
- package/server/nodes/twitter/twitter_user/meta.json +3 -0
- package/server/nodes/utility/{console.py → console/__init__.py} +17 -22
- package/server/nodes/utility/console/meta.json +3 -0
- package/server/nodes/utility/{http_request.py → http_request/__init__.py} +9 -6
- package/server/nodes/utility/http_request/meta.json +3 -0
- package/server/nodes/utility/{process_manager.py → process_manager/__init__.py} +10 -6
- package/server/nodes/utility/process_manager/meta.json +3 -0
- package/server/nodes/utility/team_monitor/meta.json +3 -0
- package/server/nodes/utility/{webhook_response.py → webhook_response/__init__.py} +12 -7
- package/server/nodes/utility/webhook_response/meta.json +3 -0
- package/server/nodes/visuals.json +69 -251
- package/server/nodes/whatsapp/__init__.py +24 -0
- package/server/nodes/whatsapp/_base.py +283 -338
- package/server/nodes/whatsapp/_credentials.py +44 -0
- package/server/nodes/whatsapp/_events.py +277 -0
- package/server/nodes/whatsapp/_filters.py +36 -37
- package/server/nodes/whatsapp/_handlers.py +2 -0
- package/server/nodes/whatsapp/_option_loaders.py +1 -3
- package/server/nodes/whatsapp/_refresh.py +13 -18
- package/server/nodes/whatsapp/_runtime.py +9 -6
- package/server/nodes/whatsapp/_service.py +89 -152
- package/server/nodes/whatsapp/meta.json +3 -0
- package/server/nodes/whatsapp/whatsapp_db.py +116 -54
- package/server/nodes/whatsapp/whatsapp_receive.py +30 -13
- package/server/nodes/whatsapp/whatsapp_send.py +60 -37
- package/server/nodes/workflow/{start.py → start/__init__.py} +1 -4
- package/server/nodes/workflow/start/meta.json +3 -0
- package/server/package-lock.json +3 -3
- package/server/package.json +3 -0
- package/server/pyproject.toml +39 -10
- package/server/requirements.txt +3 -5
- package/server/routers/__init__.py +1 -1
- package/server/routers/auth.py +16 -56
- package/server/routers/database.py +27 -50
- package/server/routers/nodejs_compat.py +25 -87
- package/server/routers/schemas.py +66 -2
- package/server/routers/webhook.py +12 -12
- package/server/routers/websocket.py +312 -1716
- package/server/routers/workflow.py +28 -53
- package/server/scripts/smoke_test_skills.py +178 -0
- package/server/services/__init__.py +1 -1
- package/server/services/_supervisor/process.py +9 -3
- package/server/services/_supervisor/registry.py +3 -3
- package/server/services/_supervisor/util.py +1 -1
- package/server/services/agent_team.py +15 -43
- package/server/services/agent_teams/__init__.py +17 -0
- package/server/services/agent_teams/handlers.py +195 -0
- package/server/services/ai.py +853 -1108
- package/server/services/auth.py +10 -34
- package/server/services/chat_client.py +5 -34
- package/server/services/circuit_breaker.py +2 -6
- package/server/services/cli_agent/__init__.py +28 -4
- package/server/services/cli_agent/_cli_auth.py +61 -0
- package/server/services/cli_agent/_handlers.py +24 -183
- package/server/services/cli_agent/config.py +5 -8
- package/server/services/cli_agent/factory.py +168 -22
- package/server/services/cli_agent/jsonl_watcher.py +380 -0
- package/server/services/cli_agent/lockfile.py +9 -2
- package/server/services/cli_agent/mcp_server.py +110 -34
- package/server/services/cli_agent/protocol.py +37 -19
- package/server/services/cli_agent/providers/__init__.py +8 -4
- package/server/services/cli_agent/providers/google_gemini.py +11 -5
- package/server/services/cli_agent/providers/openai_codex.py +34 -34
- package/server/services/cli_agent/service.py +245 -83
- package/server/services/cli_agent/session.py +409 -229
- package/server/services/cli_agent/transports/__init__.py +47 -0
- package/server/services/cli_agent/transports/base.py +111 -0
- package/server/services/cli_agent/transports/posix.py +196 -0
- package/server/services/cli_agent/transports/windows.py +189 -0
- package/server/services/cli_agent/types.py +45 -18
- package/server/services/cli_agent/workflow_tools.py +28 -15
- package/server/services/compaction.py +68 -52
- package/server/services/credential_registry.py +6 -20
- package/server/services/credentials/__init__.py +18 -0
- package/server/services/credentials/handlers.py +196 -0
- package/server/services/deployment/__init__.py +12 -1
- package/server/services/deployment/canary_registry.py +137 -0
- package/server/services/deployment/handlers.py +382 -0
- package/server/services/deployment/manager.py +653 -163
- package/server/services/deployment/poll_registry.py +2 -6
- package/server/services/deployment/state.py +2 -0
- package/server/services/deployment/triggers.py +87 -93
- package/server/services/event_waiter.py +47 -54
- package/server/services/events/__init__.py +11 -0
- package/server/services/events/admin_handlers.py +368 -0
- package/server/services/events/daemon.py +3 -1
- package/server/services/events/dispatch.py +188 -0
- package/server/services/events/envelope.py +264 -45
- package/server/services/events/oauth_lifecycle.py +98 -42
- package/server/services/events/triggers.py +3 -13
- package/server/services/events/verifiers/hmac_basic.py +1 -1
- package/server/services/events/verifiers/standard_webhooks.py +2 -4
- package/server/services/events/webhook.py +2 -3
- package/server/services/example_loader.py +73 -15
- package/server/services/execution/cache.py +36 -76
- package/server/services/execution/conditions.py +7 -20
- package/server/services/execution/dlq.py +20 -24
- package/server/services/execution/executor.py +234 -265
- package/server/services/execution/models.py +40 -46
- package/server/services/execution/recovery.py +23 -46
- package/server/services/handlers/__init__.py +12 -16
- package/server/services/handlers/todo.py +3 -6
- package/server/services/handlers/tools.py +143 -194
- package/server/services/handlers/triggers.py +24 -23
- package/server/services/llm/config.py +10 -1
- package/server/services/llm/factory.py +16 -4
- package/server/services/llm/messages.py +1 -5
- package/server/services/llm/protocol.py +9 -1
- package/server/services/llm/providers/anthropic.py +23 -12
- package/server/services/llm/providers/gemini.py +43 -22
- package/server/services/llm/providers/openai.py +14 -6
- package/server/services/llm/providers/openrouter.py +6 -1
- package/server/services/markdown_formatter.py +1 -2
- package/server/services/memory/__init__.py +2 -2
- package/server/services/memory/jsonl.py +6 -2
- package/server/services/memory/markdown.py +6 -6
- package/server/services/memory/state.py +6 -5
- package/server/services/memory_store.py +8 -12
- package/server/services/model_registry.py +22 -20
- package/server/services/node_executor.py +85 -80
- package/server/services/node_output_schemas.py +4 -7
- package/server/services/node_registry.py +40 -4
- package/server/services/node_spec.py +3 -7
- package/server/services/nodejs_client.py +4 -14
- package/server/services/oauth_utils.py +11 -7
- package/server/services/parameter_resolver.py +30 -36
- package/server/services/plugin/base.py +321 -38
- package/server/services/plugin/connection.py +12 -7
- package/server/services/plugin/credential.py +80 -22
- package/server/services/plugin/edge_walker.py +128 -105
- package/server/services/plugin/identifiers.py +48 -0
- package/server/services/plugin/interceptor.py +1 -1
- package/server/services/plugin/oauth.py +25 -21
- package/server/services/plugin/operation.py +1 -1
- package/server/services/plugin/polling.py +151 -26
- package/server/services/plugin/registry.py +52 -4
- package/server/services/plugin/routing.py +6 -9
- package/server/services/plugin/scaling.py +36 -18
- package/server/services/plugin/service_factories.py +95 -0
- package/server/services/plugin/shutdown_hooks.py +103 -0
- package/server/services/plugin/social_provider_registry.py +80 -0
- package/server/services/plugin/ws.py +2 -1
- package/server/services/pricing.py +26 -40
- package/server/services/pricing_handlers.py +90 -0
- package/server/services/process_service.py +33 -32
- package/server/services/proxy/models.py +15 -9
- package/server/services/proxy/service.py +26 -40
- package/server/services/rlm/adapters.py +43 -40
- package/server/services/rlm/constants.py +9 -9
- package/server/services/rlm/service.py +57 -45
- package/server/services/scheduler.py +8 -39
- package/server/services/settings/__init__.py +16 -0
- package/server/services/settings/handlers.py +275 -0
- package/server/services/skill_loader.py +53 -45
- package/server/services/skill_prompt.py +8 -6
- package/server/services/skills/__init__.py +23 -0
- package/server/services/skills/handlers.py +479 -0
- package/server/services/status_broadcaster.py +314 -291
- package/server/services/temporal/__init__.py +22 -1
- package/server/services/temporal/_handlers.py +65 -0
- package/server/services/temporal/_install.py +158 -0
- package/server/services/temporal/_refresh.py +57 -0
- package/server/services/temporal/_retry_policies.py +85 -0
- package/server/services/temporal/_runtime.py +181 -0
- package/server/services/temporal/_supervised_runtime.py +102 -0
- package/server/services/temporal/activities.py +168 -11
- package/server/services/temporal/agent_activities.py +683 -0
- package/server/services/temporal/agent_workflow.py +601 -0
- package/server/services/temporal/client.py +58 -13
- package/server/services/temporal/executor.py +2 -3
- package/server/services/temporal/plugin_activities.py +37 -2
- package/server/services/temporal/plugin_registry.py +82 -0
- package/server/services/temporal/polling_trigger_workflow.py +267 -0
- package/server/services/temporal/schedules.py +220 -0
- package/server/services/temporal/search_attributes.py +177 -0
- package/server/services/temporal/trigger_listener_workflow.py +378 -0
- package/server/services/temporal/worker.py +111 -18
- package/server/services/temporal/workflow.py +259 -40
- package/server/services/temporal/ws_client.py +22 -11
- package/server/services/text.py +14 -28
- package/server/services/tracked_http.py +29 -49
- package/server/services/user_auth.py +7 -21
- package/server/services/workflow.py +28 -20
- package/server/services/workflow_import.py +351 -0
- package/server/services/workflow_ops.py +4 -0
- package/server/services/workflow_storage/__init__.py +18 -0
- package/server/services/workflow_storage/handlers.py +132 -0
- package/server/services/workflow_validator.py +209 -0
- package/server/services/ws_handler_registry.py +80 -9
- package/server/skills/assistant/agent-builder-skill/SKILL.md +6 -6
- package/server/tests/conftest.py +54 -3
- package/server/tests/credentials/test_auth_service.py +9 -21
- package/server/tests/credentials/test_credential_broadcasts.py +116 -22
- package/server/tests/credentials/test_credentials_database.py +12 -38
- package/server/tests/credentials/test_encryption.py +3 -9
- package/server/tests/credentials/test_google_oauth.py +1 -3
- package/server/tests/credentials/test_oauth_utils.py +31 -38
- package/server/tests/credentials/test_twitter_oauth.py +1 -3
- package/server/tests/credentials/test_websocket_handlers.py +37 -72
- package/server/tests/fixtures/tool_names_snapshot.json +78 -0
- package/server/tests/llm/test_factory.py +12 -4
- package/server/tests/llm/test_providers.py +25 -32
- package/server/tests/llm/test_wiring.py +27 -22
- package/server/tests/nodes/_compat.py +4 -5
- package/server/tests/nodes/_harness.py +31 -24
- package/server/tests/nodes/_mocks.py +2 -6
- package/server/tests/nodes/test_agent_builder.py +43 -35
- package/server/tests/nodes/test_ai_agents.py +29 -24
- package/server/tests/nodes/test_ai_chat_models.py +3 -9
- package/server/tests/nodes/test_ai_tools.py +29 -24
- package/server/tests/nodes/test_android.py +34 -64
- package/server/tests/nodes/test_chat_utility.py +2 -2
- package/server/tests/nodes/test_code_fs_process.py +26 -84
- package/server/tests/nodes/test_document.py +23 -47
- package/server/tests/nodes/test_email.py +88 -51
- package/server/tests/nodes/test_google_workspace.py +26 -20
- package/server/tests/nodes/test_http_proxy.py +43 -89
- package/server/tests/nodes/test_search.py +3 -9
- package/server/tests/nodes/test_specialized_agents.py +58 -162
- package/server/tests/nodes/test_stripe_plugin.py +25 -5
- package/server/tests/nodes/test_telegram_social.py +33 -37
- package/server/tests/nodes/test_twitter.py +59 -150
- package/server/tests/nodes/test_web_automation.py +21 -51
- package/server/tests/nodes/test_whatsapp.py +13 -19
- package/server/tests/nodes/test_workflow_triggers.py +16 -45
- package/server/tests/services/cli_agent/test_claude_session_events.py +201 -0
- package/server/tests/services/cli_agent/test_jsonl_watcher.py +190 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +67 -29
- package/server/tests/services/cli_agent/test_providers.py +236 -47
- package/server/tests/services/cli_agent/test_service.py +9 -7
- package/server/tests/services/memory/test_jsonl.py +30 -25
- package/server/tests/services/test_events.py +26 -7
- package/server/tests/services/test_identifiers.py +122 -0
- package/server/tests/services/test_process_lifecycle.py +129 -0
- package/server/tests/services/test_supervisor.py +0 -1
- package/server/tests/temporal/__init__.py +0 -0
- package/server/tests/temporal/test_agent_workflow.py +215 -0
- package/server/tests/temporal/test_dispatch.py +231 -0
- package/server/tests/test_admin_handlers.py +394 -0
- package/server/tests/test_auto_skill.py +4 -2
- package/server/tests/test_canary_registry.py +310 -0
- package/server/tests/test_chat_trigger_canary_producer.py +101 -0
- package/server/tests/test_cloudevents_node_parameters.py +129 -0
- package/server/tests/test_credential_icon.py +115 -0
- package/server/tests/test_cron_canary.py +511 -0
- package/server/tests/test_deployment_canary_listener.py +692 -0
- package/server/tests/test_event_framework_phase_a.py +537 -0
- package/server/tests/test_no_raw_prints.py +131 -0
- package/server/tests/test_node_spec.py +196 -103
- package/server/tests/test_parameter_resolver.py +20 -20
- package/server/tests/test_plugin_contract.py +76 -49
- package/server/tests/test_plugin_helpers.py +0 -1
- package/server/tests/test_plugin_self_containment.py +40 -47
- package/server/tests/test_polling_trigger_workflow.py +572 -0
- package/server/tests/test_retry_policies.py +146 -0
- package/server/tests/test_service_factories.py +168 -0
- package/server/tests/test_shutdown_hooks.py +199 -0
- package/server/tests/test_social_provider_registry.py +177 -0
- package/server/tests/test_status_broadcasts.py +214 -63
- package/server/tests/test_task_trigger_canary_producer.py +131 -0
- package/server/tests/test_telegram_trigger_canary_producer.py +113 -0
- package/server/tests/test_tool_registry.py +110 -0
- package/server/tests/test_trigger_listener_workflow.py +365 -0
- package/server/tests/test_whatsapp_trigger_canary_producer.py +164 -0
- package/server/tests/test_workflow_ops.py +1 -3
- package/server/tests/test_workflow_validator.py +791 -0
- package/server/uv.lock +3539 -0
- package/client/dist/assets/index-DQ0nwhec.js +0 -257
- package/client/src/assets/icons/apify/index.ts +0 -19
- package/client/src/assets/icons/browser/index.ts +0 -17
- package/client/src/assets/icons/email/index.ts +0 -22
- package/client/src/assets/icons/google/index.ts +0 -34
- package/client/src/assets/icons/llm/deepseek.svg +0 -1
- package/client/src/assets/icons/llm/index.ts +0 -18
- package/client/src/assets/icons/llm/kimi.svg +0 -1
- package/client/src/assets/icons/llm/mistral.svg +0 -1
- package/client/src/assets/icons/search/index.ts +0 -28
- package/client/src/assets/icons/telegram/index.ts +0 -19
- package/machina/buildenv.py +0 -44
- package/machina/cli.py +0 -55
- package/machina/commands/__init__.py +0 -1
- package/machina/commands/clean.py +0 -80
- package/machina/commands/daemon.py +0 -150
- package/machina/config.py +0 -93
- package/machina/platform_.py +0 -37
- package/machina/pyproject.toml +0 -33
- package/server/nodes/agent/deep_agent.py +0 -103
- package/server/services/agents/__init__.py +0 -9
- package/server/services/agents/adapters.py +0 -199
- package/server/services/agents/constants.py +0 -10
- package/server/services/agents/service.py +0 -297
- package/server/services/cli_agent/providers/anthropic_claude.py +0 -419
- /package/{machina → cli}/README.md +0 -0
- /package/{machina → cli}/__init__.py +0 -0
- /package/{client/src/assets/icons/apify → server/credentials/icons}/apify.svg +0 -0
- /package/{client/src/assets/icons/search/brave.svg → server/credentials/icons/brave_search.svg} +0 -0
- /package/{client/src/assets/icons/email/read.svg → server/credentials/icons/email_himalaya.svg} +0 -0
- /package/{client/src/assets/icons/search → server/credentials/icons}/perplexity.svg +0 -0
- /package/{client/src/assets/icons/search/google.svg → server/credentials/icons/serper.svg} +0 -0
- /package/{client/src/assets → server/credentials}/icons/stripe.svg +0 -0
- /package/{client/src/assets/icons/twitter/x.svg → server/credentials/icons/twitter.svg} +0 -0
- /package/{client/src/assets/icons/browser/chrome.svg → server/nodes/browser/browser/icon.svg} +0 -0
- /package/{client/src/assets/icons/chat/chat.svg → server/nodes/chat/chat_history/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/javascript.svg → server/nodes/code/javascript_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/python.svg → server/nodes/code/python_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/typescript.svg → server/nodes/code/typescript_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/email/receive.svg → server/nodes/email/email_receive/icon.svg} +0 -0
- /package/{client/src/assets/icons/email/send.svg → server/nodes/email/email_send/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/calendar.svg → server/nodes/google/calendar/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/contacts.svg → server/nodes/google/contacts/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/drive.svg → server/nodes/google/drive/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/gmail.svg → server/nodes/google/gmail/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/sheets.svg → server/nodes/google/sheets/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/tasks.svg → server/nodes/google/tasks/icon.svg} +0 -0
- /package/{client/src/assets/icons/search/duckduckgo.svg → server/nodes/search/duckduckgo_search/icon.svg} +0 -0
- /package/{client/src/assets/icons/social/social.svg → server/nodes/social/social_receive/icon.svg} +0 -0
- /package/{client/src/assets/icons/telegram/telegram.svg → server/nodes/telegram/icon.svg} +0 -0
- /package/server/nodes/utility/{team_monitor.py → team_monitor/__init__.py} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-db.svg → server/nodes/whatsapp/icon_whatsappDb.svg} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-receive.svg → server/nodes/whatsapp/icon_whatsappReceive.svg} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-send.svg → server/nodes/whatsapp/icon_whatsappSend.svg} +0 -0
- /package/{client/src/assets/icons → server/nodes}/whatsapp/whatsapp.svg +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""PTY transports for driving interactive CLI agents (claude) cross-platform.
|
|
2
|
+
|
|
3
|
+
Public surface:
|
|
4
|
+
|
|
5
|
+
- ``PtyTransport`` / ``PtyHandle`` Protocols (in :mod:`.base`).
|
|
6
|
+
- ``get_pty_transport()`` factory picks the right backend for the
|
|
7
|
+
current platform — ``ptyprocess`` on POSIX, ``pywinpty>=3.0.3`` on
|
|
8
|
+
Windows. Both run in-process behind the same Protocol so the rest
|
|
9
|
+
of ``services/cli_agent/`` is platform-agnostic.
|
|
10
|
+
|
|
11
|
+
If ``pywinpty`` / ``ptyprocess`` stability ever becomes an issue, the
|
|
12
|
+
deferred upgrade path is an out-of-process Python pty-host subprocess
|
|
13
|
+
(VSCode's ``ptyHostMain.ts`` pattern, in Python) — the Protocol
|
|
14
|
+
boundary makes that swap cheap. See
|
|
15
|
+
``docs-internal/claude_code_interactive_mode.md`` for the topology
|
|
16
|
+
rationale.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
from .base import PtyHandle, PtyTransport
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"PtyHandle",
|
|
27
|
+
"PtyTransport",
|
|
28
|
+
"get_pty_transport",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_pty_transport() -> PtyTransport:
|
|
33
|
+
"""Return the appropriate ``PtyTransport`` for the current platform.
|
|
34
|
+
|
|
35
|
+
Lazy-imports the backend module so the heavy native dep (ptyprocess
|
|
36
|
+
on POSIX, pywinpty on Windows) is only loaded when something
|
|
37
|
+
actually needs to drive a PTY — keeps ``services.cli_agent``
|
|
38
|
+
importable in unit-test environments where the native dep may be
|
|
39
|
+
missing.
|
|
40
|
+
"""
|
|
41
|
+
if sys.platform == "win32":
|
|
42
|
+
from .windows import WindowsPtyTransport
|
|
43
|
+
|
|
44
|
+
return WindowsPtyTransport()
|
|
45
|
+
from .posix import PosixPtyTransport
|
|
46
|
+
|
|
47
|
+
return PosixPtyTransport()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Structural Protocols for PTY transports.
|
|
2
|
+
|
|
3
|
+
Two interfaces:
|
|
4
|
+
|
|
5
|
+
- ``PtyTransport`` — factory for spawning. One per process; obtained
|
|
6
|
+
from :func:`services.cli_agent.transports.get_pty_transport`.
|
|
7
|
+
- ``PtyHandle`` — one live PTY-attached subprocess. Owns the PTY
|
|
8
|
+
pair (master + slave), the spawned process, and the lifecycle
|
|
9
|
+
primitives (write, kill, alive-check) the session pool needs.
|
|
10
|
+
|
|
11
|
+
The handles are intentionally minimal — they expose only what the
|
|
12
|
+
``ClaudeSessionPool`` and ``AICliSession`` need to drive an interactive
|
|
13
|
+
``claude`` while reading events from the on-disk session JSONL. We
|
|
14
|
+
never read PTY stdout in the steady state (the TUI's ANSI output is
|
|
15
|
+
discarded), so ``read``/``recv`` are deliberately absent — adding them
|
|
16
|
+
would invite the same TUI-scraping rabbit hole NousResearch's Hermes
|
|
17
|
+
Agent is in.
|
|
18
|
+
|
|
19
|
+
The Protocols are intentionally not declared ``@runtime_checkable``:
|
|
20
|
+
``isinstance(x, PtyTransport)`` would pass any object with the right
|
|
21
|
+
method names but is misleading for Protocols carrying behavioural
|
|
22
|
+
contracts (lifecycle, threading guarantees). Static type-checkers
|
|
23
|
+
still enforce structural conformance at the factory call site.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import signal
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Dict, List, Protocol
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PtyHandle(Protocol):
|
|
34
|
+
"""One live PTY-attached subprocess.
|
|
35
|
+
|
|
36
|
+
Implementations:
|
|
37
|
+
- :class:`services.cli_agent.transports.posix.PosixPtyHandle` —
|
|
38
|
+
wraps ``ptyprocess.PtyProcess``.
|
|
39
|
+
- :class:`services.cli_agent.transports.windows.WindowsPtyHandle` —
|
|
40
|
+
wraps ``pywinpty.PTY``.
|
|
41
|
+
|
|
42
|
+
Thread-safety: not promised. The session pool serialises access
|
|
43
|
+
under a per-key ``asyncio.Lock`` so concurrent writes / kills don't
|
|
44
|
+
race. Backends should still treat ``write`` as a single point-in-time
|
|
45
|
+
operation (no internal buffering across calls).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def pid(self) -> int:
|
|
50
|
+
"""OS pid of the spawned subprocess. Used by ``Job Object``
|
|
51
|
+
enrollment and the operator log."""
|
|
52
|
+
|
|
53
|
+
def is_alive(self) -> bool:
|
|
54
|
+
"""``True`` while the subprocess is running, ``False`` after it
|
|
55
|
+
exits or is killed. Cheap to call (no syscall on the hot path
|
|
56
|
+
when possible)."""
|
|
57
|
+
|
|
58
|
+
async def write(self, data: bytes) -> None:
|
|
59
|
+
"""Write raw bytes to the PTY master.
|
|
60
|
+
|
|
61
|
+
Used both for the user's prompt and for slash commands
|
|
62
|
+
(``/clear``, ``/compact``). Callers append the right line
|
|
63
|
+
terminator — typically ``b"\\r"`` for interactive mode (claude's
|
|
64
|
+
Ink TUI listens for Enter, not stdin-close; see
|
|
65
|
+
`claude-code#15553 <https://github.com/anthropics/claude-code/issues/15553>`_).
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
async def kill(self, signal_: int = signal.SIGTERM) -> None:
|
|
69
|
+
"""Terminate the subprocess.
|
|
70
|
+
|
|
71
|
+
First-stage ``SIGTERM``; backends may escalate to ``SIGKILL``
|
|
72
|
+
after a grace window if the process doesn't exit on its own.
|
|
73
|
+
Windows ignores ``signal_`` and uses
|
|
74
|
+
``TerminateProcess``/``CTRL_BREAK_EVENT`` semantics depending
|
|
75
|
+
on what the pywinpty handle supports.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class PtyTransport(Protocol):
|
|
80
|
+
"""Factory for spawning interactive subprocesses inside a PTY.
|
|
81
|
+
|
|
82
|
+
Implementations:
|
|
83
|
+
- :class:`services.cli_agent.transports.posix.PosixPtyTransport`
|
|
84
|
+
(uses ``ptyprocess``).
|
|
85
|
+
- :class:`services.cli_agent.transports.windows.WindowsPtyTransport`
|
|
86
|
+
(uses ``pywinpty>=3.0.3``).
|
|
87
|
+
|
|
88
|
+
There is one transport per process; ``services.cli_agent.transports
|
|
89
|
+
.get_pty_transport()`` picks the right one for the current platform.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
async def spawn(
|
|
93
|
+
self,
|
|
94
|
+
argv: List[str],
|
|
95
|
+
*,
|
|
96
|
+
cwd: Path,
|
|
97
|
+
env: Dict[str, str],
|
|
98
|
+
) -> PtyHandle:
|
|
99
|
+
"""Spawn ``argv`` inside a fresh PTY pair under ``cwd`` with
|
|
100
|
+
the given ``env``.
|
|
101
|
+
|
|
102
|
+
Returns a live :class:`PtyHandle`. Raises ``FileNotFoundError``
|
|
103
|
+
if ``argv[0]`` isn't executable, ``OSError`` for PTY allocation
|
|
104
|
+
failures, and ``RuntimeError`` for backend-specific errors
|
|
105
|
+
(e.g. ConPTY not available on a too-old Windows build).
|
|
106
|
+
|
|
107
|
+
The handle is registered with the process tree supervisor
|
|
108
|
+
(:mod:`machina.tree` on Windows, ``os.setsid`` on POSIX) so the
|
|
109
|
+
child is reaped on host shutdown — see the existing
|
|
110
|
+
``BaseProcessSupervisor`` patterns.
|
|
111
|
+
"""
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""POSIX ``PtyTransport`` backed by ``ptyprocess``.
|
|
2
|
+
|
|
3
|
+
``ptyprocess.PtyProcess.spawn(...)`` wraps ``os.forkpty()`` and returns
|
|
4
|
+
a PtyProcess that owns the master fd. The child runs in its own
|
|
5
|
+
session (``os.setsid()`` is the default), so SIGTERM-then-SIGKILL on the
|
|
6
|
+
child pid reaps the whole TUI process group. We bridge ptyprocess's sync
|
|
7
|
+
API to asyncio with ``loop.run_in_executor`` only at the spawn/kill
|
|
8
|
+
boundary — writes are single fast syscalls and run on the event-loop
|
|
9
|
+
thread.
|
|
10
|
+
|
|
11
|
+
We do NOT register an ``loop.add_reader`` on the master fd: in the new
|
|
12
|
+
architecture the on-disk session JSONL is the protocol surface; PTY
|
|
13
|
+
stdout is rendered TUI we never look at. Keeping the read side empty
|
|
14
|
+
means no buffering races between fd-readiness and JSONL-watcher events.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import os
|
|
21
|
+
import signal
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
from core.logging import get_logger
|
|
27
|
+
|
|
28
|
+
from .base import PtyHandle, PtyTransport
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
# How long to wait between SIGTERM and SIGKILL when killing a child.
|
|
33
|
+
# Matches ``BaseProcessSupervisor.terminate_grace_seconds`` so the two
|
|
34
|
+
# lifecycle layers agree on grace.
|
|
35
|
+
_TERMINATE_GRACE_SECONDS = 5.0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PosixPtyHandle:
|
|
39
|
+
"""ptyprocess-backed handle for one live ``claude`` subprocess."""
|
|
40
|
+
|
|
41
|
+
__slots__ = ("_proc", "_exited")
|
|
42
|
+
|
|
43
|
+
def __init__(self, proc: object) -> None:
|
|
44
|
+
# ``proc`` is a ``ptyprocess.PtyProcess`` but we keep it typed
|
|
45
|
+
# as ``object`` so the import remains lazy in :meth:`spawn`.
|
|
46
|
+
self._proc = proc
|
|
47
|
+
self._exited = False
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def pid(self) -> int:
|
|
51
|
+
return int(getattr(self._proc, "pid", -1))
|
|
52
|
+
|
|
53
|
+
def is_alive(self) -> bool:
|
|
54
|
+
if self._exited:
|
|
55
|
+
return False
|
|
56
|
+
try:
|
|
57
|
+
alive = bool(self._proc.isalive()) # type: ignore[attr-defined]
|
|
58
|
+
except Exception:
|
|
59
|
+
return False
|
|
60
|
+
if not alive:
|
|
61
|
+
self._exited = True
|
|
62
|
+
return alive
|
|
63
|
+
|
|
64
|
+
async def write(self, data: bytes) -> None:
|
|
65
|
+
# ptyprocess's write() is a single ``os.write`` call; fast
|
|
66
|
+
# enough to run on the event-loop thread.
|
|
67
|
+
if not data:
|
|
68
|
+
return
|
|
69
|
+
try:
|
|
70
|
+
self._proc.write(data) # type: ignore[attr-defined]
|
|
71
|
+
except OSError as exc:
|
|
72
|
+
# EIO = the slave end closed (process exited). Surface as a
|
|
73
|
+
# clean ConnectionError so callers can distinguish from a
|
|
74
|
+
# genuine permission / FD-leak bug.
|
|
75
|
+
raise ConnectionError(f"PTY write failed (pid={self.pid}): {exc}") from exc
|
|
76
|
+
|
|
77
|
+
async def kill(self, signal_: int = signal.SIGTERM) -> None:
|
|
78
|
+
"""SIGTERM, wait briefly, escalate to SIGKILL if still alive.
|
|
79
|
+
|
|
80
|
+
Matches ``BaseProcessSupervisor.terminate_then_kill`` behaviour
|
|
81
|
+
and the grace window from the existing process-tree supervisor.
|
|
82
|
+
"""
|
|
83
|
+
if self._exited:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
loop = asyncio.get_running_loop()
|
|
87
|
+
|
|
88
|
+
# First-stage signal. ptyprocess exposes terminate(force=False)
|
|
89
|
+
# for SIGTERM and terminate(force=True) for SIGKILL. We want the
|
|
90
|
+
# SIGTERM-then-SIGKILL cascade with our own grace window so we
|
|
91
|
+
# call signal helpers directly.
|
|
92
|
+
try:
|
|
93
|
+
await loop.run_in_executor(
|
|
94
|
+
None,
|
|
95
|
+
self._proc.kill,
|
|
96
|
+
signal_, # type: ignore[attr-defined]
|
|
97
|
+
)
|
|
98
|
+
except (ProcessLookupError, OSError) as exc:
|
|
99
|
+
logger.debug("PTY first-stage signal raced exit: %s", exc)
|
|
100
|
+
self._exited = True
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Wait up to grace for graceful exit.
|
|
104
|
+
deadline = time.monotonic() + _TERMINATE_GRACE_SECONDS
|
|
105
|
+
while time.monotonic() < deadline:
|
|
106
|
+
if not self.is_alive():
|
|
107
|
+
self._exited = True
|
|
108
|
+
return
|
|
109
|
+
await asyncio.sleep(0.05)
|
|
110
|
+
|
|
111
|
+
# Escalate. ``kill(SIGKILL)`` is the documented force-quit path.
|
|
112
|
+
try:
|
|
113
|
+
await loop.run_in_executor(
|
|
114
|
+
None,
|
|
115
|
+
self._proc.kill,
|
|
116
|
+
signal.SIGKILL, # type: ignore[attr-defined]
|
|
117
|
+
)
|
|
118
|
+
except (ProcessLookupError, OSError):
|
|
119
|
+
pass
|
|
120
|
+
self._exited = True
|
|
121
|
+
|
|
122
|
+
# Drain the ptyprocess so the OS-level zombie is reaped on next
|
|
123
|
+
# alive-check. No-op if already reaped.
|
|
124
|
+
try:
|
|
125
|
+
await loop.run_in_executor(None, self._proc.close, True) # type: ignore[attr-defined]
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class PosixPtyTransport(PtyTransport):
|
|
131
|
+
"""Spawn factory using ``ptyprocess``.
|
|
132
|
+
|
|
133
|
+
Lazy-imports ``ptyprocess`` so test environments missing the dep
|
|
134
|
+
can still import :mod:`services.cli_agent.transports` (the factory
|
|
135
|
+
only loads this backend on POSIX). Raises ``RuntimeError`` if the
|
|
136
|
+
dep is genuinely missing at spawn time.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(self) -> None:
|
|
140
|
+
# Cache the imported class on first use so we don't repeat the
|
|
141
|
+
# try/except on every spawn.
|
|
142
|
+
self._pty_process_cls: Optional[object] = None
|
|
143
|
+
|
|
144
|
+
def _load_ptyprocess(self) -> object:
|
|
145
|
+
if self._pty_process_cls is not None:
|
|
146
|
+
return self._pty_process_cls
|
|
147
|
+
try:
|
|
148
|
+
from ptyprocess import PtyProcess # type: ignore[import-not-found]
|
|
149
|
+
except ImportError as exc:
|
|
150
|
+
raise RuntimeError(
|
|
151
|
+
"PosixPtyTransport requires the 'ptyprocess' package. "
|
|
152
|
+
"Install it (e.g. `pip install ptyprocess>=0.7.0`) — it's "
|
|
153
|
+
"declared in server/pyproject.toml with the marker "
|
|
154
|
+
"`sys_platform != 'win32'`."
|
|
155
|
+
) from exc
|
|
156
|
+
self._pty_process_cls = PtyProcess
|
|
157
|
+
return PtyProcess
|
|
158
|
+
|
|
159
|
+
async def spawn(
|
|
160
|
+
self,
|
|
161
|
+
argv: List[str],
|
|
162
|
+
*,
|
|
163
|
+
cwd: Path,
|
|
164
|
+
env: Dict[str, str],
|
|
165
|
+
) -> PtyHandle:
|
|
166
|
+
if not argv:
|
|
167
|
+
raise ValueError("PosixPtyTransport.spawn: empty argv")
|
|
168
|
+
|
|
169
|
+
PtyProcess = self._load_ptyprocess() # noqa: N806 — class name
|
|
170
|
+
|
|
171
|
+
binary = argv[0]
|
|
172
|
+
if not os.path.exists(binary):
|
|
173
|
+
# Mirror BaseProcessSupervisor's pre-spawn check so the
|
|
174
|
+
# error matches the existing operator-log shape.
|
|
175
|
+
raise FileNotFoundError(f"PTY binary not found: {binary}")
|
|
176
|
+
|
|
177
|
+
loop = asyncio.get_running_loop()
|
|
178
|
+
proc = await loop.run_in_executor(
|
|
179
|
+
None,
|
|
180
|
+
lambda: PtyProcess.spawn( # type: ignore[attr-defined]
|
|
181
|
+
argv,
|
|
182
|
+
cwd=str(cwd),
|
|
183
|
+
env=env,
|
|
184
|
+
# 80x24 default matches what Ink/most TUI libraries
|
|
185
|
+
# expect on a non-terminal-attached spawn. We don't
|
|
186
|
+
# render anywhere, so the dimensions are cosmetic.
|
|
187
|
+
dimensions=(24, 80),
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
logger.info(
|
|
191
|
+
"[PtyTransport posix] spawned pid=%s cwd=%s argv0=%s",
|
|
192
|
+
getattr(proc, "pid", "?"),
|
|
193
|
+
cwd,
|
|
194
|
+
binary,
|
|
195
|
+
)
|
|
196
|
+
return PosixPtyHandle(proc)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Windows ``PtyTransport`` backed by ``pywinpty>=3.0.3``.
|
|
2
|
+
|
|
3
|
+
``pywinpty`` v3.x is Rust-backed via PyO3 + winpty-rs + Maturin. Mirrors
|
|
4
|
+
ptyprocess's high-level API closely (``PtyProcess.spawn(argv, cwd=,
|
|
5
|
+
env=, dimensions=)``, ``write``, ``isalive``, ``kill``, ``close``) so
|
|
6
|
+
this file's shape mirrors :mod:`.posix`. The pywinpty maintainers ship
|
|
7
|
+
prebuilt wheels for Python 3.9-3.14 including free-threading, so
|
|
8
|
+
end-users don't need a C++ build toolchain.
|
|
9
|
+
|
|
10
|
+
Used in production by Jupyter Lab terminals and Spyder's IPython
|
|
11
|
+
console — same production maturity bar as ptyprocess on POSIX. See
|
|
12
|
+
the upgrade-path note in :mod:`services.cli_agent.transports` for the
|
|
13
|
+
out-of-process pty-host fallback if pywinpty stability ever degrades.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import os
|
|
20
|
+
import signal
|
|
21
|
+
import time
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Dict, List, Optional
|
|
24
|
+
|
|
25
|
+
from core.logging import get_logger
|
|
26
|
+
|
|
27
|
+
from .base import PtyHandle, PtyTransport
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
# Match POSIX backend's grace window so the two lifecycle layers agree.
|
|
32
|
+
_TERMINATE_GRACE_SECONDS = 5.0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WindowsPtyHandle:
|
|
36
|
+
"""pywinpty-backed handle for one live ``claude`` subprocess."""
|
|
37
|
+
|
|
38
|
+
__slots__ = ("_proc", "_exited")
|
|
39
|
+
|
|
40
|
+
def __init__(self, proc: object) -> None:
|
|
41
|
+
self._proc = proc
|
|
42
|
+
self._exited = False
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def pid(self) -> int:
|
|
46
|
+
return int(getattr(self._proc, "pid", -1))
|
|
47
|
+
|
|
48
|
+
def is_alive(self) -> bool:
|
|
49
|
+
if self._exited:
|
|
50
|
+
return False
|
|
51
|
+
try:
|
|
52
|
+
alive = bool(self._proc.isalive()) # type: ignore[attr-defined]
|
|
53
|
+
except Exception:
|
|
54
|
+
return False
|
|
55
|
+
if not alive:
|
|
56
|
+
self._exited = True
|
|
57
|
+
return alive
|
|
58
|
+
|
|
59
|
+
async def write(self, data: bytes) -> None:
|
|
60
|
+
if not data:
|
|
61
|
+
return
|
|
62
|
+
# pywinpty v3's ``PtyProcess.write`` is str-only — the underlying
|
|
63
|
+
# Rust core's ``self.pty.write(s)`` raises
|
|
64
|
+
# ``TypeError: argument 'to_write': 'bytes' object cannot be cast
|
|
65
|
+
# as 'str'`` on bytes. POSIX ``ptyprocess`` is bytes-native; the
|
|
66
|
+
# ``PtyHandle`` Protocol picks bytes for that reason. We decode
|
|
67
|
+
# at this boundary so the rest of the stack stays bytes-only.
|
|
68
|
+
# Defer the actual WriteFile to a worker thread because ConPTY
|
|
69
|
+
# can briefly block under back-pressure
|
|
70
|
+
# (https://github.com/microsoft/node-pty/issues/388).
|
|
71
|
+
try:
|
|
72
|
+
text = data.decode("utf-8")
|
|
73
|
+
except UnicodeDecodeError as exc:
|
|
74
|
+
raise ConnectionError(f"PTY write failed (pid={self.pid}): non-UTF-8 payload: {exc}") from exc
|
|
75
|
+
loop = asyncio.get_running_loop()
|
|
76
|
+
try:
|
|
77
|
+
await loop.run_in_executor(
|
|
78
|
+
None,
|
|
79
|
+
self._proc.write,
|
|
80
|
+
text, # type: ignore[attr-defined]
|
|
81
|
+
)
|
|
82
|
+
except OSError as exc:
|
|
83
|
+
raise ConnectionError(f"PTY write failed (pid={self.pid}): {exc}") from exc
|
|
84
|
+
|
|
85
|
+
async def kill(self, signal_: int = signal.SIGTERM) -> None:
|
|
86
|
+
"""First-stage WM_CLOSE / SIGTERM equivalent, wait briefly,
|
|
87
|
+
escalate to TerminateProcess if still alive.
|
|
88
|
+
|
|
89
|
+
Windows doesn't have POSIX signals; pywinpty maps the
|
|
90
|
+
``kill(signal_)`` call to the closest analogue (TerminateProcess
|
|
91
|
+
with the exit code, or ``CTRL_BREAK_EVENT`` when the process
|
|
92
|
+
was spawned into its own console group). ``force=True`` is the
|
|
93
|
+
unconditional escalation.
|
|
94
|
+
"""
|
|
95
|
+
if self._exited:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
loop = asyncio.get_running_loop()
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
await loop.run_in_executor(
|
|
102
|
+
None,
|
|
103
|
+
self._proc.kill,
|
|
104
|
+
signal_, # type: ignore[attr-defined]
|
|
105
|
+
)
|
|
106
|
+
except (ProcessLookupError, OSError) as exc:
|
|
107
|
+
logger.debug("ConPTY first-stage signal raced exit: %s", exc)
|
|
108
|
+
self._exited = True
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
deadline = time.monotonic() + _TERMINATE_GRACE_SECONDS
|
|
112
|
+
while time.monotonic() < deadline:
|
|
113
|
+
if not self.is_alive():
|
|
114
|
+
self._exited = True
|
|
115
|
+
return
|
|
116
|
+
await asyncio.sleep(0.05)
|
|
117
|
+
|
|
118
|
+
# Escalate. ``close(force=True)`` calls TerminateProcess on the
|
|
119
|
+
# child and frees the ConPTY handle.
|
|
120
|
+
try:
|
|
121
|
+
await loop.run_in_executor(None, self._proc.close, True) # type: ignore[attr-defined]
|
|
122
|
+
except (ProcessLookupError, OSError):
|
|
123
|
+
pass
|
|
124
|
+
self._exited = True
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class WindowsPtyTransport(PtyTransport):
|
|
128
|
+
"""Spawn factory using ``pywinpty>=3.0.3``.
|
|
129
|
+
|
|
130
|
+
Lazy-imports ``winpty`` (the importable package name for pywinpty)
|
|
131
|
+
so test environments missing the dep can still import this module
|
|
132
|
+
on non-Windows; the factory in
|
|
133
|
+
:mod:`services.cli_agent.transports` only loads this backend when
|
|
134
|
+
``sys.platform == 'win32'``.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self) -> None:
|
|
138
|
+
self._pty_process_cls: Optional[object] = None
|
|
139
|
+
|
|
140
|
+
def _load_winpty(self) -> object:
|
|
141
|
+
if self._pty_process_cls is not None:
|
|
142
|
+
return self._pty_process_cls
|
|
143
|
+
try:
|
|
144
|
+
from winpty import PtyProcess # type: ignore[import-not-found]
|
|
145
|
+
except ImportError as exc:
|
|
146
|
+
raise RuntimeError(
|
|
147
|
+
"WindowsPtyTransport requires 'pywinpty>=3.0.3'. "
|
|
148
|
+
"Install it (e.g. `pip install pywinpty>=3.0.3`) — it's "
|
|
149
|
+
"declared in server/pyproject.toml with the marker "
|
|
150
|
+
"`sys_platform == 'win32'`. Prebuilt wheels are "
|
|
151
|
+
"available for Python 3.9-3.14; no C++ toolchain "
|
|
152
|
+
"required."
|
|
153
|
+
) from exc
|
|
154
|
+
self._pty_process_cls = PtyProcess
|
|
155
|
+
return PtyProcess
|
|
156
|
+
|
|
157
|
+
async def spawn(
|
|
158
|
+
self,
|
|
159
|
+
argv: List[str],
|
|
160
|
+
*,
|
|
161
|
+
cwd: Path,
|
|
162
|
+
env: Dict[str, str],
|
|
163
|
+
) -> PtyHandle:
|
|
164
|
+
if not argv:
|
|
165
|
+
raise ValueError("WindowsPtyTransport.spawn: empty argv")
|
|
166
|
+
|
|
167
|
+
PtyProcess = self._load_winpty() # noqa: N806
|
|
168
|
+
|
|
169
|
+
binary = argv[0]
|
|
170
|
+
if not os.path.exists(binary):
|
|
171
|
+
raise FileNotFoundError(f"PTY binary not found: {binary}")
|
|
172
|
+
|
|
173
|
+
loop = asyncio.get_running_loop()
|
|
174
|
+
proc = await loop.run_in_executor(
|
|
175
|
+
None,
|
|
176
|
+
lambda: PtyProcess.spawn( # type: ignore[attr-defined]
|
|
177
|
+
argv,
|
|
178
|
+
cwd=str(cwd),
|
|
179
|
+
env=env,
|
|
180
|
+
dimensions=(24, 80),
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
logger.info(
|
|
184
|
+
"[PtyTransport windows] spawned pid=%s cwd=%s argv0=%s",
|
|
185
|
+
getattr(proc, "pid", "?"),
|
|
186
|
+
cwd,
|
|
187
|
+
binary,
|
|
188
|
+
)
|
|
189
|
+
return WindowsPtyHandle(proc)
|