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
|
@@ -1,12 +1,30 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Registry + factory for `AICliProvider` instances.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Plugins call :func:`register_provider` from their package
|
|
4
|
+
``__init__.py`` so the framework can build an instance by provider
|
|
5
|
+
name without importing from the plugin folder directly (which would
|
|
6
|
+
be a ``services → nodes`` layering violation — forbidden by the
|
|
7
|
+
plugin-folder pattern in
|
|
8
|
+
[docs-internal/plugin_system.md](../../../../docs-internal/plugin_system.md)).
|
|
9
|
+
|
|
10
|
+
The pattern matches the six existing per-plugin registries
|
|
11
|
+
(``ws_handler_registry``, ``register_router``, etc.) — see
|
|
12
|
+
:func:`services.ws_handler_registry.register_ws_handlers` for the
|
|
13
|
+
template. Provider registration is idempotent: a second
|
|
14
|
+
``register_provider("claude", AnthropicClaudeProvider)`` is a no-op
|
|
15
|
+
when the binding already matches; mismatching bindings raise.
|
|
16
|
+
|
|
17
|
+
Usage from a plugin's ``__init__.py``::
|
|
18
|
+
|
|
19
|
+
from services.cli_agent.factory import register_provider
|
|
20
|
+
from ._provider import AnthropicClaudeProvider
|
|
21
|
+
register_provider("claude", AnthropicClaudeProvider)
|
|
6
22
|
"""
|
|
7
23
|
|
|
8
24
|
from __future__ import annotations
|
|
9
25
|
|
|
26
|
+
from typing import Any, Callable, Dict, Optional, Type, Union
|
|
27
|
+
|
|
10
28
|
from core.logging import get_logger
|
|
11
29
|
|
|
12
30
|
from services.cli_agent.protocol import AICliProvider
|
|
@@ -14,35 +32,163 @@ from services.cli_agent.protocol import AICliProvider
|
|
|
14
32
|
logger = get_logger(__name__)
|
|
15
33
|
|
|
16
34
|
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
# Bindings populated by plugin folders on import. ``Union[Type, Callable]``
|
|
36
|
+
# because some plugins may want a zero-arg factory function instead of a
|
|
37
|
+
# class — both shapes are valid as long as calling them returns an
|
|
38
|
+
# ``AICliProvider``-conformant instance.
|
|
39
|
+
_PROVIDER_REGISTRY: Dict[str, Union[Type[AICliProvider], Callable[[], AICliProvider]]] = {}
|
|
40
|
+
|
|
41
|
+
# Per-provider session pool factories. Pool implementations are
|
|
42
|
+
# provider-specific (claude uses subprocess + stream-json pooling; codex
|
|
43
|
+
# / gemini don't) and live in the plugin folder per the canonical
|
|
44
|
+
# layout. This registry lets the framework's pool dispatcher
|
|
45
|
+
# (``service.py::_run_pooled_turn``) look up the right pool by
|
|
46
|
+
# provider name without importing from the plugin folder directly.
|
|
47
|
+
_POOL_REGISTRY: Dict[str, Callable[[], Any]] = {}
|
|
48
|
+
|
|
49
|
+
# Per-provider skill materialisers. Only claude implements one today —
|
|
50
|
+
# it writes ``SKILL.md`` trees under ``<workspace>/.claude/skills/``
|
|
51
|
+
# per the Anthropic Skills spec (code.claude.com/docs/en/skills).
|
|
52
|
+
# Codex / Gemini don't honor SKILL.md so they don't register here.
|
|
53
|
+
# The framework's session bootstrap (``session.py:_pre_spawn``)
|
|
54
|
+
# looks up by provider name; ``None`` means no-op (skip materialise).
|
|
55
|
+
#
|
|
56
|
+
# Signature: ``async (workspace_dir, skill_names, *, previous_skill_names,
|
|
57
|
+
# log_label) -> tuple[int, int]`` — (added_count, removed_count).
|
|
58
|
+
_SKILL_MATERIALISER_REGISTRY: Dict[str, Callable[..., Any]] = {}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def register_provider(
|
|
62
|
+
name: str,
|
|
63
|
+
provider_factory: Union[Type[AICliProvider], Callable[[], AICliProvider]],
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Bind a provider name to its class / factory.
|
|
66
|
+
|
|
67
|
+
Called from the plugin folder's ``__init__.py`` on import. Idempotent
|
|
68
|
+
when the binding is unchanged; raises ``ValueError`` on conflict so
|
|
69
|
+
accidental double-registration with different classes is caught
|
|
70
|
+
instead of silently shadowing.
|
|
71
|
+
"""
|
|
72
|
+
existing = _PROVIDER_REGISTRY.get(name)
|
|
73
|
+
if existing is not None and existing is not provider_factory:
|
|
74
|
+
raise ValueError(f"CLI provider {name!r} already registered with {existing!r}; " f"refusing to overwrite with {provider_factory!r}")
|
|
75
|
+
_PROVIDER_REGISTRY[name] = provider_factory
|
|
76
|
+
logger.debug("[cli_agent] registered provider %r -> %r", name, provider_factory)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def unregister_provider(name: str) -> None:
|
|
80
|
+
"""Drop a provider binding. Used by tests."""
|
|
81
|
+
_PROVIDER_REGISTRY.pop(name, None)
|
|
19
82
|
|
|
20
83
|
|
|
21
84
|
def create_cli_provider(name: str) -> AICliProvider:
|
|
22
85
|
"""Build a CLI provider by name.
|
|
23
86
|
|
|
24
87
|
Raises:
|
|
25
|
-
NotImplementedError: for
|
|
88
|
+
NotImplementedError: for declared-but-deferred providers
|
|
89
|
+
(currently ``gemini``).
|
|
26
90
|
ValueError: for unknown names.
|
|
27
91
|
"""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
if name == "codex":
|
|
33
|
-
from services.cli_agent.providers.openai_codex import OpenAICodexProvider
|
|
34
|
-
return OpenAICodexProvider()
|
|
92
|
+
factory = _PROVIDER_REGISTRY.get(name)
|
|
93
|
+
if factory is not None:
|
|
94
|
+
return factory()
|
|
35
95
|
|
|
96
|
+
# ``gemini`` is intentionally listed as a known-but-unsupported name
|
|
97
|
+
# so the frontend dropdown can show it greyed-out while the v2
|
|
98
|
+
# implementation is in flight. Surface a clean ``NotImplementedError``
|
|
99
|
+
# so factory consumers can detect the deferred state.
|
|
36
100
|
if name == "gemini":
|
|
37
|
-
|
|
38
|
-
# the deferred state and offer the user an actionable hint.
|
|
39
|
-
raise NotImplementedError(
|
|
40
|
-
"gemini provider deferred to v2. Use 'claude' or 'codex' in v1."
|
|
41
|
-
)
|
|
101
|
+
raise NotImplementedError("gemini provider deferred to v2. Use 'claude' or 'codex' in v1.")
|
|
42
102
|
|
|
43
|
-
|
|
103
|
+
registered = sorted(_PROVIDER_REGISTRY.keys())
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"Unknown CLI provider: {name!r}. " f"Registered: {registered}. " f"Did the plugin's ``__init__.py`` call register_provider()?"
|
|
106
|
+
)
|
|
44
107
|
|
|
45
108
|
|
|
46
109
|
def is_supported(name: str) -> bool:
|
|
47
|
-
"""True if the provider is
|
|
48
|
-
return name in
|
|
110
|
+
"""True if the provider is registered (plugin imported successfully)."""
|
|
111
|
+
return name in _PROVIDER_REGISTRY
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def registered_provider_names() -> frozenset[str]:
|
|
115
|
+
"""Frozen snapshot of currently-registered provider names."""
|
|
116
|
+
return frozenset(_PROVIDER_REGISTRY.keys())
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Session-pool registry — paired with provider registry above
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def register_session_pool(
|
|
125
|
+
provider_name: str,
|
|
126
|
+
pool_getter: Callable[[], Any],
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Bind a provider to a callable returning its session pool singleton.
|
|
129
|
+
|
|
130
|
+
Called from the plugin folder's ``__init__.py`` on import. The
|
|
131
|
+
pool object must expose ``acquire``, ``send_turn``, ``release``,
|
|
132
|
+
``clear``, ``terminate``, ``shutdown_all``, and ``start_reaper`` —
|
|
133
|
+
the surface ``services.cli_agent.service._run_pooled_turn`` uses.
|
|
134
|
+
|
|
135
|
+
Idempotent on identical binding; raises ``ValueError`` on conflict
|
|
136
|
+
so accidental double-registration with different getters is caught.
|
|
137
|
+
"""
|
|
138
|
+
existing = _POOL_REGISTRY.get(provider_name)
|
|
139
|
+
if existing is not None and existing is not pool_getter:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"Session pool for {provider_name!r} already registered " f"({existing!r}); refusing to overwrite with {pool_getter!r}"
|
|
142
|
+
)
|
|
143
|
+
_POOL_REGISTRY[provider_name] = pool_getter
|
|
144
|
+
logger.debug(
|
|
145
|
+
"[cli_agent] registered session pool %r -> %r",
|
|
146
|
+
provider_name,
|
|
147
|
+
pool_getter,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_session_pool(provider_name: str) -> Optional[Any]:
|
|
152
|
+
"""Return the session pool singleton for ``provider_name`` or ``None``.
|
|
153
|
+
|
|
154
|
+
Calls the registered getter (provider-side) so the pool is lazily
|
|
155
|
+
instantiated on first request.
|
|
156
|
+
"""
|
|
157
|
+
getter = _POOL_REGISTRY.get(provider_name)
|
|
158
|
+
if getter is None:
|
|
159
|
+
return None
|
|
160
|
+
return getter()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# Skill-materialiser registry
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def register_skill_materialiser(
|
|
169
|
+
provider_name: str,
|
|
170
|
+
materialiser: Callable[..., Any],
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Bind a provider to its skill materialiser (claude only today).
|
|
173
|
+
|
|
174
|
+
Called from the plugin folder's ``__init__.py`` on import. The
|
|
175
|
+
materialiser must be an async callable with signature
|
|
176
|
+
``(workspace_dir, skill_names, *, previous_skill_names, log_label)
|
|
177
|
+
-> tuple[int, int]`` (added_count, removed_count).
|
|
178
|
+
"""
|
|
179
|
+
existing = _SKILL_MATERIALISER_REGISTRY.get(provider_name)
|
|
180
|
+
if existing is not None and existing is not materialiser:
|
|
181
|
+
raise ValueError(
|
|
182
|
+
f"Skill materialiser for {provider_name!r} already registered " f"({existing!r}); refusing to overwrite with {materialiser!r}"
|
|
183
|
+
)
|
|
184
|
+
_SKILL_MATERIALISER_REGISTRY[provider_name] = materialiser
|
|
185
|
+
logger.debug(
|
|
186
|
+
"[cli_agent] registered skill materialiser %r -> %r",
|
|
187
|
+
provider_name,
|
|
188
|
+
materialiser,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_skill_materialiser(provider_name: str) -> Optional[Callable[..., Any]]:
|
|
193
|
+
"""Return the registered materialiser or ``None`` (= skip materialise)."""
|
|
194
|
+
return _SKILL_MATERIALISER_REGISTRY.get(provider_name)
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""On-disk JSONL watchers — protocol surface for interactive-mode claude.
|
|
2
|
+
|
|
3
|
+
Claude writes its session transcript to
|
|
4
|
+
``<CLAUDE_CONFIG_DIR>/projects/<project_key>/<session_uuid>.jsonl`` —
|
|
5
|
+
same shape ``-p`` used to write to stdout (Claude Code CHANGELOG
|
|
6
|
+
2.1.101 / 2.1.126 confirms the shared writer). In interactive mode we
|
|
7
|
+
keep the TUI alive in a PTY and read events off disk; this module
|
|
8
|
+
provides the two watchers ``AICliSession`` + ``ClaudeSessionPool`` need:
|
|
9
|
+
|
|
10
|
+
- :class:`JsonlWatcher` tails one specific JSONL file and dispatches
|
|
11
|
+
each new line as a parsed event. Drop-in replacement for the old
|
|
12
|
+
``_consume_stdout`` NDJSON parser.
|
|
13
|
+
|
|
14
|
+
- :class:`JsonlDirWatcher` watches a directory for *new* JSONL files
|
|
15
|
+
appearing — the mechanism the session pool uses to capture the
|
|
16
|
+
new session UUID after sending ``/clear`` (which mints a fresh
|
|
17
|
+
UUID, not an in-place clear; see issue `claude-code#32871
|
|
18
|
+
<https://github.com/anthropics/claude-code/issues/32871>`_).
|
|
19
|
+
|
|
20
|
+
Both use a simple poll loop (default 100 ms / 250 ms) rather than
|
|
21
|
+
``watchdog``'s OS-native APIs (inotify/FSEvents/ReadDirectoryChangesW).
|
|
22
|
+
Polling is good enough for our latency target (UI updates were already
|
|
23
|
+
at the same cadence under the old stdout path), more predictable
|
|
24
|
+
under high-frequency append, and avoids the well-known watchdog races
|
|
25
|
+
on Windows under rapid file creation.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import asyncio
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, Set
|
|
35
|
+
|
|
36
|
+
from core.logging import get_logger
|
|
37
|
+
|
|
38
|
+
logger = get_logger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
EventHandler = Callable[[Dict[str, Any]], Awaitable[None]]
|
|
42
|
+
"""Async callback fired once per parsed JSONL line."""
|
|
43
|
+
|
|
44
|
+
FileHandler = Callable[[Path], Awaitable[None]]
|
|
45
|
+
"""Async callback fired once per newly-detected .jsonl file."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class JsonlWatcher:
|
|
49
|
+
"""Tail a specific JSONL file and dispatch new lines as events.
|
|
50
|
+
|
|
51
|
+
Replaces the old NDJSON-on-stdout consumer. Once :meth:`start` is
|
|
52
|
+
called, every newline-terminated chunk appended to ``path`` is read,
|
|
53
|
+
parsed with :func:`json.loads`, and handed to ``on_event``. Garbage
|
|
54
|
+
(unparseable lines, partial writes during an append) is silently
|
|
55
|
+
dropped — matches the old ``AnthropicClaudeProvider.parse_event``
|
|
56
|
+
return-None-on-error semantics.
|
|
57
|
+
|
|
58
|
+
The watcher is cancellation-safe: :meth:`stop` cancels the
|
|
59
|
+
background task and closes the open file handle. Calling
|
|
60
|
+
:meth:`stop` on an already-stopped watcher is a no-op.
|
|
61
|
+
|
|
62
|
+
Initial position: by default we seek to the END of the file at
|
|
63
|
+
:meth:`start` so we only see new appends. Pass ``start_from_end=False``
|
|
64
|
+
to replay the entire file (useful for picking up the result event
|
|
65
|
+
of a turn that completed while we were spawning).
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
__slots__ = (
|
|
69
|
+
"_path",
|
|
70
|
+
"_on_event",
|
|
71
|
+
"_poll_interval",
|
|
72
|
+
"_start_from_end",
|
|
73
|
+
"_task",
|
|
74
|
+
"_stopped",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
path: Path,
|
|
80
|
+
on_event: EventHandler,
|
|
81
|
+
*,
|
|
82
|
+
poll_interval: float = 0.1,
|
|
83
|
+
start_from_end: bool = False,
|
|
84
|
+
) -> None:
|
|
85
|
+
self._path = Path(path)
|
|
86
|
+
self._on_event = on_event
|
|
87
|
+
self._poll_interval = max(0.01, float(poll_interval))
|
|
88
|
+
self._start_from_end = bool(start_from_end)
|
|
89
|
+
self._task: Optional[asyncio.Task[None]] = None
|
|
90
|
+
self._stopped = asyncio.Event()
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def path(self) -> Path:
|
|
94
|
+
return self._path
|
|
95
|
+
|
|
96
|
+
async def start(self) -> None:
|
|
97
|
+
"""Spawn the tail-f task. Returns immediately."""
|
|
98
|
+
if self._task is not None and not self._task.done():
|
|
99
|
+
return # idempotent
|
|
100
|
+
self._stopped.clear()
|
|
101
|
+
self._task = asyncio.create_task(
|
|
102
|
+
self._run(),
|
|
103
|
+
name=f"JsonlWatcher({self._path.name})",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def stop(self) -> None:
|
|
107
|
+
"""Cancel the tail-f task and wait for it to settle."""
|
|
108
|
+
self._stopped.set()
|
|
109
|
+
if self._task is None:
|
|
110
|
+
return
|
|
111
|
+
if not self._task.done():
|
|
112
|
+
self._task.cancel()
|
|
113
|
+
try:
|
|
114
|
+
await self._task
|
|
115
|
+
except asyncio.CancelledError:
|
|
116
|
+
pass
|
|
117
|
+
except Exception as exc: # pragma: no cover — defensive
|
|
118
|
+
logger.debug(
|
|
119
|
+
"[JsonlWatcher] task exit error path=%s exc=%s",
|
|
120
|
+
self._path.name,
|
|
121
|
+
exc,
|
|
122
|
+
)
|
|
123
|
+
self._task = None
|
|
124
|
+
|
|
125
|
+
async def _run(self) -> None:
|
|
126
|
+
# Wait for the file to appear. Claude writes the JSONL on first
|
|
127
|
+
# turn output; for first-spawn runs we may start watching
|
|
128
|
+
# before it exists.
|
|
129
|
+
while not self._path.exists():
|
|
130
|
+
if self._stopped.is_set():
|
|
131
|
+
return
|
|
132
|
+
await asyncio.sleep(self._poll_interval)
|
|
133
|
+
|
|
134
|
+
# Open in binary mode so partial UTF-8 sequences at the read
|
|
135
|
+
# boundary don't blow up — we accumulate bytes and decode on
|
|
136
|
+
# the newline split.
|
|
137
|
+
try:
|
|
138
|
+
handle = self._path.open("rb")
|
|
139
|
+
except OSError as exc:
|
|
140
|
+
logger.warning(
|
|
141
|
+
"[JsonlWatcher] open failed path=%s exc=%s",
|
|
142
|
+
self._path,
|
|
143
|
+
exc,
|
|
144
|
+
)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
buf = b""
|
|
148
|
+
try:
|
|
149
|
+
if self._start_from_end:
|
|
150
|
+
handle.seek(0, os.SEEK_END)
|
|
151
|
+
|
|
152
|
+
while not self._stopped.is_set():
|
|
153
|
+
chunk = handle.read()
|
|
154
|
+
if chunk:
|
|
155
|
+
buf += chunk
|
|
156
|
+
while b"\n" in buf:
|
|
157
|
+
raw, buf = buf.split(b"\n", 1)
|
|
158
|
+
await self._dispatch_line(raw)
|
|
159
|
+
else:
|
|
160
|
+
# No new data — sleep and try again. Use a small
|
|
161
|
+
# interval so latency stays comparable to stdout
|
|
162
|
+
# streaming.
|
|
163
|
+
try:
|
|
164
|
+
await asyncio.wait_for(
|
|
165
|
+
self._stopped.wait(),
|
|
166
|
+
timeout=self._poll_interval,
|
|
167
|
+
)
|
|
168
|
+
except asyncio.TimeoutError:
|
|
169
|
+
pass
|
|
170
|
+
except asyncio.CancelledError:
|
|
171
|
+
raise
|
|
172
|
+
except Exception as exc: # pragma: no cover — defensive
|
|
173
|
+
logger.warning(
|
|
174
|
+
"[JsonlWatcher] read loop ended unexpectedly path=%s exc=%s",
|
|
175
|
+
self._path.name,
|
|
176
|
+
exc,
|
|
177
|
+
)
|
|
178
|
+
finally:
|
|
179
|
+
# Flush any final no-newline bytes (claude generally writes
|
|
180
|
+
# newline-terminated lines but be defensive).
|
|
181
|
+
if buf.strip():
|
|
182
|
+
await self._dispatch_line(buf)
|
|
183
|
+
try:
|
|
184
|
+
handle.close()
|
|
185
|
+
except OSError:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
async def _dispatch_line(self, raw: bytes) -> None:
|
|
189
|
+
text = raw.decode("utf-8", errors="replace").strip()
|
|
190
|
+
if not text:
|
|
191
|
+
return
|
|
192
|
+
try:
|
|
193
|
+
event: Dict[str, Any] = json.loads(text)
|
|
194
|
+
except json.JSONDecodeError:
|
|
195
|
+
return # garbage / partial line — drop silently
|
|
196
|
+
try:
|
|
197
|
+
await self._on_event(event)
|
|
198
|
+
except asyncio.CancelledError:
|
|
199
|
+
raise
|
|
200
|
+
except Exception as exc: # pragma: no cover — handler isolation
|
|
201
|
+
logger.warning(
|
|
202
|
+
"[JsonlWatcher] on_event handler raised path=%s exc=%s",
|
|
203
|
+
self._path.name,
|
|
204
|
+
exc,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class JsonlDirWatcher:
|
|
209
|
+
"""Watch a directory for newly-appearing ``.jsonl`` files.
|
|
210
|
+
|
|
211
|
+
Used by :class:`nodes.agent.claude_code_agent._pool.ClaudeSessionPool`
|
|
212
|
+
to capture the new session UUID after sending ``/clear``: claude
|
|
213
|
+
mints a fresh UUID and starts writing to
|
|
214
|
+
``<project_key>/<new_uuid>.jsonl``; the watcher fires the callback
|
|
215
|
+
with that path, the pool reads the UUID off the filename, and
|
|
216
|
+
points its memory bridge at the new file.
|
|
217
|
+
|
|
218
|
+
Detection is "new file" only — we don't fire on appends to
|
|
219
|
+
existing files (that's :class:`JsonlWatcher`'s job). Files present
|
|
220
|
+
at :meth:`start` time are recorded as baseline and never fire the
|
|
221
|
+
callback.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
__slots__ = (
|
|
225
|
+
"_dir",
|
|
226
|
+
"_on_new_file",
|
|
227
|
+
"_poll_interval",
|
|
228
|
+
"_task",
|
|
229
|
+
"_stopped",
|
|
230
|
+
"_baseline",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def __init__(
|
|
234
|
+
self,
|
|
235
|
+
directory: Path,
|
|
236
|
+
on_new_file: FileHandler,
|
|
237
|
+
*,
|
|
238
|
+
poll_interval: float = 0.25,
|
|
239
|
+
) -> None:
|
|
240
|
+
self._dir = Path(directory)
|
|
241
|
+
self._on_new_file = on_new_file
|
|
242
|
+
self._poll_interval = max(0.05, float(poll_interval))
|
|
243
|
+
self._task: Optional[asyncio.Task[None]] = None
|
|
244
|
+
self._stopped = asyncio.Event()
|
|
245
|
+
self._baseline: Set[str] = set()
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def directory(self) -> Path:
|
|
249
|
+
return self._dir
|
|
250
|
+
|
|
251
|
+
async def start(self) -> None:
|
|
252
|
+
"""Snapshot the current ``.jsonl`` files as the baseline, then
|
|
253
|
+
start polling. Files in the baseline never fire the callback."""
|
|
254
|
+
if self._task is not None and not self._task.done():
|
|
255
|
+
return
|
|
256
|
+
self._stopped.clear()
|
|
257
|
+
# Refresh baseline at every start so a re-used watcher across
|
|
258
|
+
# session-pool acquires sees a fresh snapshot.
|
|
259
|
+
self._baseline = self._snapshot()
|
|
260
|
+
self._task = asyncio.create_task(
|
|
261
|
+
self._run(),
|
|
262
|
+
name=f"JsonlDirWatcher({self._dir.name})",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
async def stop(self) -> None:
|
|
266
|
+
self._stopped.set()
|
|
267
|
+
if self._task is None:
|
|
268
|
+
return
|
|
269
|
+
if not self._task.done():
|
|
270
|
+
self._task.cancel()
|
|
271
|
+
try:
|
|
272
|
+
await self._task
|
|
273
|
+
except asyncio.CancelledError:
|
|
274
|
+
pass
|
|
275
|
+
self._task = None
|
|
276
|
+
|
|
277
|
+
def _snapshot(self) -> Set[str]:
|
|
278
|
+
"""Return the set of ``.jsonl`` filenames currently in the
|
|
279
|
+
directory. Empty set if the directory doesn't exist yet."""
|
|
280
|
+
try:
|
|
281
|
+
return {entry.name for entry in self._dir.iterdir() if entry.is_file() and entry.suffix == ".jsonl"}
|
|
282
|
+
except (FileNotFoundError, NotADirectoryError):
|
|
283
|
+
return set()
|
|
284
|
+
except OSError as exc:
|
|
285
|
+
logger.debug(
|
|
286
|
+
"[JsonlDirWatcher] snapshot failed dir=%s exc=%s",
|
|
287
|
+
self._dir,
|
|
288
|
+
exc,
|
|
289
|
+
)
|
|
290
|
+
return set()
|
|
291
|
+
|
|
292
|
+
async def _run(self) -> None:
|
|
293
|
+
# Wait for the directory to appear if it doesn't yet (first run
|
|
294
|
+
# before claude has materialised the projects dir).
|
|
295
|
+
while not self._dir.exists():
|
|
296
|
+
if self._stopped.is_set():
|
|
297
|
+
return
|
|
298
|
+
await asyncio.sleep(self._poll_interval)
|
|
299
|
+
# Note: if the directory appears mid-wait we re-snapshot at
|
|
300
|
+
# next iteration; first new file detected only AFTER the
|
|
301
|
+
# baseline is established, which matches the documented
|
|
302
|
+
# "files at start time are baseline" semantic.
|
|
303
|
+
|
|
304
|
+
while not self._stopped.is_set():
|
|
305
|
+
current = self._snapshot()
|
|
306
|
+
new = current - self._baseline
|
|
307
|
+
if new:
|
|
308
|
+
# Update baseline BEFORE firing the callback so a slow
|
|
309
|
+
# handler doesn't cause the same file to fire twice on
|
|
310
|
+
# the next tick. Order within the new set is undefined;
|
|
311
|
+
# callers shouldn't depend on ordering across multiple
|
|
312
|
+
# additions in one poll interval.
|
|
313
|
+
self._baseline |= new
|
|
314
|
+
for name in new:
|
|
315
|
+
path = self._dir / name
|
|
316
|
+
try:
|
|
317
|
+
await self._on_new_file(path)
|
|
318
|
+
except asyncio.CancelledError:
|
|
319
|
+
raise
|
|
320
|
+
except Exception as exc: # pragma: no cover
|
|
321
|
+
logger.warning(
|
|
322
|
+
"[JsonlDirWatcher] on_new_file handler raised " "dir=%s file=%s exc=%s",
|
|
323
|
+
self._dir.name,
|
|
324
|
+
name,
|
|
325
|
+
exc,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
await asyncio.wait_for(
|
|
330
|
+
self._stopped.wait(),
|
|
331
|
+
timeout=self._poll_interval,
|
|
332
|
+
)
|
|
333
|
+
except asyncio.TimeoutError:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def snapshot_jsonl_sizes(project_dir: Path) -> Dict[str, int]:
|
|
338
|
+
"""Return ``{name: size_bytes}`` for every ``.jsonl`` in ``project_dir``.
|
|
339
|
+
|
|
340
|
+
Used by ``ClaudeSessionPool._wait_for_session_jsonl`` (and the
|
|
341
|
+
equivalent in :class:`AICliSession`) to baseline the project dir
|
|
342
|
+
before spawning ``claude``. Post-spawn the caller polls again and
|
|
343
|
+
picks whichever file is new (absent from baseline) or grew
|
|
344
|
+
(``current_size > baseline_size``) — both fresh spawns and
|
|
345
|
+
``--continue`` spawns produce a size-positive delta.
|
|
346
|
+
|
|
347
|
+
Returns an empty dict if ``project_dir`` doesn't exist yet (first
|
|
348
|
+
spawn under a new cwd). Silently skips entries the filesystem
|
|
349
|
+
refuses to ``stat`` — they're typically transient and the post-spawn
|
|
350
|
+
poll will catch them on the next tick once they settle.
|
|
351
|
+
"""
|
|
352
|
+
sizes: Dict[str, int] = {}
|
|
353
|
+
try:
|
|
354
|
+
entries = list(project_dir.iterdir())
|
|
355
|
+
except (FileNotFoundError, NotADirectoryError):
|
|
356
|
+
return sizes
|
|
357
|
+
except OSError:
|
|
358
|
+
return sizes
|
|
359
|
+
for entry in entries:
|
|
360
|
+
if entry.suffix != ".jsonl":
|
|
361
|
+
continue
|
|
362
|
+
try:
|
|
363
|
+
sizes[entry.name] = entry.stat().st_size
|
|
364
|
+
except OSError:
|
|
365
|
+
continue
|
|
366
|
+
return sizes
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def session_uuid_from_jsonl_path(path: Path) -> Optional[str]:
|
|
370
|
+
"""Extract the session UUID from a JSONL filename, or None if the
|
|
371
|
+
name doesn't match the ``<uuid>.jsonl`` convention.
|
|
372
|
+
|
|
373
|
+
Cheap helper so callers don't need to manually do ``path.stem`` and
|
|
374
|
+
validate. Claude's filenames are RFC-4122 UUIDs; we don't validate
|
|
375
|
+
the UUID format aggressively since claude controls the writer.
|
|
376
|
+
"""
|
|
377
|
+
if path.suffix != ".jsonl":
|
|
378
|
+
return None
|
|
379
|
+
stem = path.stem
|
|
380
|
+
return stem if stem else None
|
|
@@ -25,7 +25,11 @@ logger = logging.getLogger(__name__)
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def _lockfile_path(
|
|
28
|
-
*,
|
|
28
|
+
*,
|
|
29
|
+
ide_lockfile_dir: Path,
|
|
30
|
+
pid: int,
|
|
31
|
+
port: int,
|
|
32
|
+
ide_name: str,
|
|
29
33
|
) -> Path:
|
|
30
34
|
if ide_name == "gemini":
|
|
31
35
|
return ide_lockfile_dir / f"gemini-ide-server-{pid}-{port}.json"
|
|
@@ -51,7 +55,10 @@ def write_ide_lockfile(
|
|
|
51
55
|
"""Write a VSCode-style IDE lockfile (mode 0600 on POSIX)."""
|
|
52
56
|
ide_lockfile_dir.mkdir(parents=True, exist_ok=True)
|
|
53
57
|
path = _lockfile_path(
|
|
54
|
-
ide_lockfile_dir=ide_lockfile_dir,
|
|
58
|
+
ide_lockfile_dir=ide_lockfile_dir,
|
|
59
|
+
pid=pid,
|
|
60
|
+
port=port,
|
|
61
|
+
ide_name=ide_name,
|
|
55
62
|
)
|
|
56
63
|
|
|
57
64
|
# FastMCP's ``streamable_http_app()`` registers the JSON-RPC route at
|