PraisonAI 3.0.0__py3-none-any.whl
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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Daemon process manager for running schedulers in background.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import signal
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Dict, List
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DaemonManager:
|
|
14
|
+
"""Manages daemon processes for schedulers."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, log_dir: Optional[Path] = None, max_log_size_mb: float = 10.0):
|
|
17
|
+
"""
|
|
18
|
+
Initialize daemon manager.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
log_dir: Directory for log files. Defaults to ~/.praisonai/logs
|
|
22
|
+
max_log_size_mb: Maximum log file size in MB before rotation
|
|
23
|
+
"""
|
|
24
|
+
if log_dir is None:
|
|
25
|
+
home = Path.home()
|
|
26
|
+
log_dir = home / ".praisonai" / "logs"
|
|
27
|
+
|
|
28
|
+
self.log_dir = Path(log_dir)
|
|
29
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
self.max_log_size_bytes = int(max_log_size_mb * 1024 * 1024)
|
|
31
|
+
|
|
32
|
+
def start_daemon(
|
|
33
|
+
self,
|
|
34
|
+
name: str,
|
|
35
|
+
task: str,
|
|
36
|
+
interval: str,
|
|
37
|
+
command: List[str]
|
|
38
|
+
) -> int:
|
|
39
|
+
"""
|
|
40
|
+
Start a daemon process.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name: Daemon name
|
|
44
|
+
task: Task description
|
|
45
|
+
interval: Schedule interval
|
|
46
|
+
command: Command to run as list
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Process ID
|
|
50
|
+
"""
|
|
51
|
+
log_file = self.log_dir / f"{name}.log"
|
|
52
|
+
|
|
53
|
+
# Open log file - use original working approach
|
|
54
|
+
with open(log_file, 'a') as log:
|
|
55
|
+
log.flush()
|
|
56
|
+
|
|
57
|
+
# Start process as daemon
|
|
58
|
+
proc = subprocess.Popen(
|
|
59
|
+
command,
|
|
60
|
+
stdout=log,
|
|
61
|
+
stderr=subprocess.STDOUT,
|
|
62
|
+
stdin=subprocess.DEVNULL,
|
|
63
|
+
start_new_session=True, # Detach from terminal
|
|
64
|
+
cwd=os.getcwd()
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return proc.pid
|
|
68
|
+
|
|
69
|
+
def start_scheduler_daemon(
|
|
70
|
+
self,
|
|
71
|
+
name: str,
|
|
72
|
+
task: Optional[str] = None,
|
|
73
|
+
recipe_name: Optional[str] = None,
|
|
74
|
+
interval: str = "hourly",
|
|
75
|
+
max_cost: Optional[float] = None,
|
|
76
|
+
timeout: Optional[int] = None,
|
|
77
|
+
max_retries: int = 3
|
|
78
|
+
) -> int:
|
|
79
|
+
"""
|
|
80
|
+
Start a PraisonAI scheduler as daemon.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
name: Scheduler name
|
|
84
|
+
task: Task to schedule (mutually exclusive with recipe_name)
|
|
85
|
+
recipe_name: Recipe name to schedule (mutually exclusive with task)
|
|
86
|
+
interval: Schedule interval
|
|
87
|
+
max_cost: Maximum cost budget
|
|
88
|
+
timeout: Timeout per execution
|
|
89
|
+
max_retries: Maximum retry attempts
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Process ID
|
|
93
|
+
"""
|
|
94
|
+
# Build command - use CLI without --daemon flag (original working approach)
|
|
95
|
+
command = [
|
|
96
|
+
sys.executable,
|
|
97
|
+
"-m",
|
|
98
|
+
"praisonai.cli.main",
|
|
99
|
+
"schedule",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
# Add task or recipe
|
|
103
|
+
if recipe_name:
|
|
104
|
+
command.append(recipe_name) # Recipe name will be auto-detected
|
|
105
|
+
elif task:
|
|
106
|
+
command.append(task)
|
|
107
|
+
else:
|
|
108
|
+
raise ValueError("Either task or recipe_name must be provided")
|
|
109
|
+
|
|
110
|
+
command.extend(["--interval", interval, "--max-retries", str(max_retries)])
|
|
111
|
+
|
|
112
|
+
if timeout:
|
|
113
|
+
command.extend(["--timeout", str(timeout)])
|
|
114
|
+
|
|
115
|
+
if max_cost:
|
|
116
|
+
command.extend(["--max-cost", str(max_cost)])
|
|
117
|
+
|
|
118
|
+
display_task = recipe_name or task
|
|
119
|
+
return self.start_daemon(name, display_task, interval, command)
|
|
120
|
+
|
|
121
|
+
def stop_daemon(self, pid: int, timeout: int = 10) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Stop a daemon process gracefully.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
pid: Process ID
|
|
127
|
+
timeout: Timeout in seconds
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if stopped successfully
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
# Try graceful shutdown first (SIGTERM)
|
|
134
|
+
os.kill(pid, signal.SIGTERM)
|
|
135
|
+
|
|
136
|
+
# Wait for process to terminate
|
|
137
|
+
import time
|
|
138
|
+
for _ in range(timeout * 10):
|
|
139
|
+
try:
|
|
140
|
+
os.kill(pid, 0) # Check if still alive
|
|
141
|
+
time.sleep(0.1)
|
|
142
|
+
except (OSError, ProcessLookupError):
|
|
143
|
+
return True # Process terminated
|
|
144
|
+
|
|
145
|
+
# Force kill if still alive
|
|
146
|
+
try:
|
|
147
|
+
os.kill(pid, signal.SIGKILL)
|
|
148
|
+
time.sleep(0.2) # Give it time to die
|
|
149
|
+
except (OSError, ProcessLookupError):
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
except (OSError, ProcessLookupError):
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
def get_status(self, pid: int) -> Optional[Dict]:
|
|
158
|
+
"""
|
|
159
|
+
Get daemon process status.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
pid: Process ID
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Status dictionary or None if not found
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
os.kill(pid, 0) # Check if process exists
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
"pid": pid,
|
|
172
|
+
"is_alive": True
|
|
173
|
+
}
|
|
174
|
+
except (OSError, ProcessLookupError):
|
|
175
|
+
return {
|
|
176
|
+
"pid": pid,
|
|
177
|
+
"is_alive": False
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
def read_logs(self, name: str, lines: int = 50) -> Optional[str]:
|
|
181
|
+
"""
|
|
182
|
+
Read daemon logs.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
name: Daemon name
|
|
186
|
+
lines: Number of lines to read from end
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Log content or None if not found
|
|
190
|
+
"""
|
|
191
|
+
log_file = self.log_dir / f"{name}.log"
|
|
192
|
+
|
|
193
|
+
if not log_file.exists():
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
with open(log_file) as f:
|
|
198
|
+
all_lines = f.readlines()
|
|
199
|
+
return ''.join(all_lines[-lines:])
|
|
200
|
+
except IOError:
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
def rotate_log(self, name: str) -> bool:
|
|
204
|
+
"""
|
|
205
|
+
Rotate log file if it exceeds max size.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
name: Daemon name
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if rotated
|
|
212
|
+
"""
|
|
213
|
+
log_file = self.log_dir / f"{name}.log"
|
|
214
|
+
|
|
215
|
+
if not log_file.exists():
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
if log_file.stat().st_size > self.max_log_size_bytes:
|
|
219
|
+
# Rotate: rename to .log.1, .log.2, etc.
|
|
220
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
221
|
+
rotated_file = self.log_dir / f"{name}.log.{timestamp}"
|
|
222
|
+
log_file.rename(rotated_file)
|
|
223
|
+
return True
|
|
224
|
+
|
|
225
|
+
return False
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scheduler state manager for persistent storage of scheduler processes.
|
|
3
|
+
"""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import signal
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SchedulerStateManager:
|
|
13
|
+
"""Manages persistent state for scheduler processes."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, state_dir: Optional[Path] = None):
|
|
16
|
+
"""
|
|
17
|
+
Initialize state manager.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
state_dir: Directory to store state files. Defaults to ~/.praisonai/schedulers
|
|
21
|
+
"""
|
|
22
|
+
if state_dir is None:
|
|
23
|
+
home = Path.home()
|
|
24
|
+
state_dir = home / ".praisonai" / "schedulers"
|
|
25
|
+
|
|
26
|
+
self.state_dir = Path(state_dir)
|
|
27
|
+
self.state_dir.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
def save_state(self, name: str, state: Dict) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Save scheduler state to JSON file.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
name: Scheduler name
|
|
35
|
+
state: State dictionary to save
|
|
36
|
+
"""
|
|
37
|
+
state_file = self.state_dir / f"{name}.json"
|
|
38
|
+
|
|
39
|
+
with open(state_file, 'w') as f:
|
|
40
|
+
json.dump(state, f, indent=2)
|
|
41
|
+
|
|
42
|
+
def load_state(self, name: str) -> Optional[Dict]:
|
|
43
|
+
"""
|
|
44
|
+
Load scheduler state from JSON file.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
name: Scheduler name
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
State dictionary or None if not found
|
|
51
|
+
"""
|
|
52
|
+
state_file = self.state_dir / f"{name}.json"
|
|
53
|
+
|
|
54
|
+
if not state_file.exists():
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
with open(state_file) as f:
|
|
59
|
+
return json.load(f)
|
|
60
|
+
except (json.JSONDecodeError, IOError):
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def delete_state(self, name: str) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Delete scheduler state file.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
name: Scheduler name
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if deleted, False if not found
|
|
72
|
+
"""
|
|
73
|
+
state_file = self.state_dir / f"{name}.json"
|
|
74
|
+
|
|
75
|
+
if state_file.exists():
|
|
76
|
+
state_file.unlink()
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def list_all(self) -> List[Dict]:
|
|
82
|
+
"""
|
|
83
|
+
List all scheduler states.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List of state dictionaries
|
|
87
|
+
"""
|
|
88
|
+
states = []
|
|
89
|
+
|
|
90
|
+
for state_file in self.state_dir.glob("*.json"):
|
|
91
|
+
try:
|
|
92
|
+
with open(state_file) as f:
|
|
93
|
+
state = json.load(f)
|
|
94
|
+
states.append(state)
|
|
95
|
+
except (json.JSONDecodeError, IOError):
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
return states
|
|
99
|
+
|
|
100
|
+
def generate_unique_name(self, base_name: str = "scheduler") -> str:
|
|
101
|
+
"""
|
|
102
|
+
Generate a unique scheduler name.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
base_name: Base name for the scheduler
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Unique name like "scheduler-0", "scheduler-1", etc.
|
|
109
|
+
"""
|
|
110
|
+
existing_states = self.list_all()
|
|
111
|
+
existing_names = {s.get("name", "") for s in existing_states}
|
|
112
|
+
|
|
113
|
+
counter = 0
|
|
114
|
+
while True:
|
|
115
|
+
name = f"{base_name}-{counter}"
|
|
116
|
+
if name not in existing_names:
|
|
117
|
+
return name
|
|
118
|
+
counter += 1
|
|
119
|
+
|
|
120
|
+
def is_process_alive(self, pid: int) -> bool:
|
|
121
|
+
"""
|
|
122
|
+
Check if a process is still alive.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
pid: Process ID
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
True if process is alive, False otherwise
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
# Send signal 0 to check if process exists
|
|
132
|
+
os.kill(pid, 0)
|
|
133
|
+
return True
|
|
134
|
+
except (OSError, ProcessLookupError):
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def cleanup_dead_processes(self) -> int:
|
|
138
|
+
"""
|
|
139
|
+
Clean up state files for dead processes.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Number of dead processes cleaned up
|
|
143
|
+
"""
|
|
144
|
+
cleaned = 0
|
|
145
|
+
states = self.list_all()
|
|
146
|
+
|
|
147
|
+
for state in states:
|
|
148
|
+
pid = state.get("pid")
|
|
149
|
+
name = state.get("name")
|
|
150
|
+
|
|
151
|
+
if pid and name and not self.is_process_alive(pid):
|
|
152
|
+
self.delete_state(name)
|
|
153
|
+
cleaned += 1
|
|
154
|
+
|
|
155
|
+
return cleaned
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
YAML loader for agent scheduler configuration.
|
|
3
|
+
|
|
4
|
+
Loads agent and schedule configuration from agents.yaml files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, Any, Tuple
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def load_agent_yaml_with_schedule(yaml_path: str) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
16
|
+
"""
|
|
17
|
+
Load agents.yaml file and extract agent and schedule configuration.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
yaml_path: Path to agents.yaml file
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Tuple of (agent_config, schedule_config)
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
FileNotFoundError: If YAML file doesn't exist
|
|
27
|
+
ValueError: If YAML is invalid or missing required fields
|
|
28
|
+
|
|
29
|
+
Example YAML structure:
|
|
30
|
+
framework: praisonai
|
|
31
|
+
|
|
32
|
+
agents:
|
|
33
|
+
- name: "AI News Monitor"
|
|
34
|
+
role: "News Analyst"
|
|
35
|
+
goal: "Monitor AI news"
|
|
36
|
+
instructions: "Search and summarize"
|
|
37
|
+
tools:
|
|
38
|
+
- search_tool
|
|
39
|
+
verbose: true
|
|
40
|
+
|
|
41
|
+
task: "Search for latest AI news"
|
|
42
|
+
|
|
43
|
+
schedule:
|
|
44
|
+
interval: "hourly"
|
|
45
|
+
max_retries: 3
|
|
46
|
+
run_immediately: true
|
|
47
|
+
"""
|
|
48
|
+
yaml_path = Path(yaml_path)
|
|
49
|
+
|
|
50
|
+
if not yaml_path.exists():
|
|
51
|
+
raise FileNotFoundError(f"YAML file not found: {yaml_path}")
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
with open(yaml_path, 'r') as f:
|
|
55
|
+
config = yaml.safe_load(f)
|
|
56
|
+
except yaml.YAMLError as e:
|
|
57
|
+
raise ValueError(f"Invalid YAML file: {e}")
|
|
58
|
+
|
|
59
|
+
if not config:
|
|
60
|
+
raise ValueError("Empty YAML file")
|
|
61
|
+
|
|
62
|
+
# Extract agent configuration
|
|
63
|
+
agent_config = {}
|
|
64
|
+
|
|
65
|
+
# Get agents list (can be single agent or multiple)
|
|
66
|
+
agents = config.get('agents', [])
|
|
67
|
+
if not agents:
|
|
68
|
+
raise ValueError("No agents defined in YAML file")
|
|
69
|
+
|
|
70
|
+
# Use first agent for scheduling (can be extended for multi-agent later)
|
|
71
|
+
if isinstance(agents, list):
|
|
72
|
+
agent_config = agents[0]
|
|
73
|
+
else:
|
|
74
|
+
agent_config = agents
|
|
75
|
+
|
|
76
|
+
# Get task
|
|
77
|
+
task = config.get('task', '')
|
|
78
|
+
if not task:
|
|
79
|
+
# Try to get from agent's tasks
|
|
80
|
+
if 'tasks' in agent_config and agent_config['tasks']:
|
|
81
|
+
first_task = list(agent_config['tasks'].values())[0]
|
|
82
|
+
task = first_task.get('description', '')
|
|
83
|
+
|
|
84
|
+
agent_config['task'] = task
|
|
85
|
+
|
|
86
|
+
# Get framework (default to praisonai)
|
|
87
|
+
agent_config['framework'] = config.get('framework', 'praisonai')
|
|
88
|
+
|
|
89
|
+
# Extract schedule configuration (optional)
|
|
90
|
+
schedule_section = config.get('schedule', {})
|
|
91
|
+
|
|
92
|
+
# Set defaults for schedule if not provided
|
|
93
|
+
schedule_config = {
|
|
94
|
+
'interval': schedule_section.get('interval', 'hourly'),
|
|
95
|
+
'max_retries': schedule_section.get('max_retries', 3),
|
|
96
|
+
'run_immediately': schedule_section.get('run_immediately', False),
|
|
97
|
+
'timeout': schedule_section.get('timeout'), # Optional timeout in seconds
|
|
98
|
+
'max_cost': schedule_section.get('max_cost', 1.00) # Default $1.00 budget limit for safety
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
logger.info(f"Loaded agent '{agent_config.get('name', 'Unknown')}' with schedule interval '{schedule_config['interval']}'")
|
|
102
|
+
|
|
103
|
+
return agent_config, schedule_config
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def create_agent_from_config(agent_config: Dict[str, Any]) -> Any:
|
|
107
|
+
"""
|
|
108
|
+
Create a PraisonAI Agent instance from configuration.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
agent_config: Agent configuration dictionary
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Agent instance
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
ImportError: If praisonaiagents is not installed
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
from praisonaiagents import Agent
|
|
121
|
+
except ImportError:
|
|
122
|
+
raise ImportError("praisonaiagents package is required. Install with: pip install praisonaiagents")
|
|
123
|
+
|
|
124
|
+
# Extract agent parameters
|
|
125
|
+
name = agent_config.get('name', 'Scheduled Agent')
|
|
126
|
+
role = agent_config.get('role', '')
|
|
127
|
+
goal = agent_config.get('goal', '')
|
|
128
|
+
instructions = agent_config.get('instructions', agent_config.get('backstory', ''))
|
|
129
|
+
verbose = agent_config.get('verbose', False)
|
|
130
|
+
|
|
131
|
+
# Handle tools
|
|
132
|
+
tools = []
|
|
133
|
+
tool_names = agent_config.get('tools', [])
|
|
134
|
+
|
|
135
|
+
if tool_names:
|
|
136
|
+
# Try to import tools
|
|
137
|
+
for tool_name in tool_names:
|
|
138
|
+
try:
|
|
139
|
+
# Try common tool imports
|
|
140
|
+
if tool_name == 'search_tool' or tool_name == 'InternetSearchTool':
|
|
141
|
+
try:
|
|
142
|
+
from tools import search_tool
|
|
143
|
+
tools.append(search_tool)
|
|
144
|
+
except ImportError:
|
|
145
|
+
logger.warning(f"Could not import {tool_name}, skipping")
|
|
146
|
+
# Add more tool imports as needed
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.warning(f"Error loading tool {tool_name}: {e}")
|
|
149
|
+
|
|
150
|
+
# Create agent
|
|
151
|
+
agent = Agent(
|
|
152
|
+
name=name,
|
|
153
|
+
role=role,
|
|
154
|
+
goal=goal,
|
|
155
|
+
instructions=instructions,
|
|
156
|
+
tools=tools,
|
|
157
|
+
verbose=verbose
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return agent
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def validate_schedule_config(schedule_config: Dict[str, Any]) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Validate schedule configuration.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
schedule_config: Schedule configuration dictionary
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
ValueError: If configuration is invalid
|
|
172
|
+
"""
|
|
173
|
+
required_fields = ['interval']
|
|
174
|
+
for field in required_fields:
|
|
175
|
+
if field not in schedule_config:
|
|
176
|
+
raise ValueError(f"Missing required schedule field: {field}")
|
|
177
|
+
|
|
178
|
+
# Validate interval format
|
|
179
|
+
interval = schedule_config['interval']
|
|
180
|
+
valid_intervals = ['hourly', 'daily']
|
|
181
|
+
|
|
182
|
+
if interval not in valid_intervals:
|
|
183
|
+
# Check if it's a custom format (*/Nm, */Nh, */Ns, or plain number)
|
|
184
|
+
if not (interval.startswith('*/') or interval.isdigit()):
|
|
185
|
+
raise ValueError(
|
|
186
|
+
f"Invalid interval format: {interval}. "
|
|
187
|
+
f"Use 'hourly', 'daily', '*/30m', '*/6h', or seconds as number"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Validate max_retries
|
|
191
|
+
max_retries = schedule_config.get('max_retries', 3)
|
|
192
|
+
if not isinstance(max_retries, int) or max_retries < 0:
|
|
193
|
+
raise ValueError(f"max_retries must be a non-negative integer, got: {max_retries}")
|