praisonai-code 0.0.1__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_code/__init__.py +17 -0
- praisonai_code/cli/__init__.py +12 -0
- praisonai_code/cli/_forward_shim.py +10 -0
- praisonai_code/cli/_paths.py +88 -0
- praisonai_code/cli/_warnings.py +58 -0
- praisonai_code/cli/app.py +757 -0
- praisonai_code/cli/approval_backend.py +272 -0
- praisonai_code/cli/branding.py +94 -0
- praisonai_code/cli/commands/__init__.py +114 -0
- praisonai_code/cli/commands/acp.py +80 -0
- praisonai_code/cli/commands/agent.py +116 -0
- praisonai_code/cli/commands/agents.py +80 -0
- praisonai_code/cli/commands/app.py +139 -0
- praisonai_code/cli/commands/attach.py +95 -0
- praisonai_code/cli/commands/audit.py +102 -0
- praisonai_code/cli/commands/auth.py +508 -0
- praisonai_code/cli/commands/batch.py +848 -0
- praisonai_code/cli/commands/benchmark.py +286 -0
- praisonai_code/cli/commands/browser.py +299 -0
- praisonai_code/cli/commands/call.py +45 -0
- praisonai_code/cli/commands/chat.py +332 -0
- praisonai_code/cli/commands/checkpoint.py +170 -0
- praisonai_code/cli/commands/code.py +276 -0
- praisonai_code/cli/commands/command.py +114 -0
- praisonai_code/cli/commands/commit.py +47 -0
- praisonai_code/cli/commands/completion.py +333 -0
- praisonai_code/cli/commands/config.py +681 -0
- praisonai_code/cli/commands/context.py +414 -0
- praisonai_code/cli/commands/daemon.py +203 -0
- praisonai_code/cli/commands/debug.py +142 -0
- praisonai_code/cli/commands/deploy.py +71 -0
- praisonai_code/cli/commands/diag.py +55 -0
- praisonai_code/cli/commands/docs.py +1575 -0
- praisonai_code/cli/commands/doctor.py +332 -0
- praisonai_code/cli/commands/endpoints.py +51 -0
- praisonai_code/cli/commands/environment.py +179 -0
- praisonai_code/cli/commands/eval.py +131 -0
- praisonai_code/cli/commands/examples.py +953 -0
- praisonai_code/cli/commands/flow.py +436 -0
- praisonai_code/cli/commands/github.py +752 -0
- praisonai_code/cli/commands/hooks.py +74 -0
- praisonai_code/cli/commands/init.py +174 -0
- praisonai_code/cli/commands/knowledge.py +440 -0
- praisonai_code/cli/commands/langextract.py +120 -0
- praisonai_code/cli/commands/langfuse.py +984 -0
- praisonai_code/cli/commands/loop.py +211 -0
- praisonai_code/cli/commands/lsp.py +112 -0
- praisonai_code/cli/commands/managed.py +659 -0
- praisonai_code/cli/commands/mcp.py +763 -0
- praisonai_code/cli/commands/memory.py +298 -0
- praisonai_code/cli/commands/models.py +264 -0
- praisonai_code/cli/commands/n8n.py +326 -0
- praisonai_code/cli/commands/obs.py +19 -0
- praisonai_code/cli/commands/package.py +76 -0
- praisonai_code/cli/commands/paths.py +106 -0
- praisonai_code/cli/commands/permissions.py +272 -0
- praisonai_code/cli/commands/plugins.py +609 -0
- praisonai_code/cli/commands/port.py +530 -0
- praisonai_code/cli/commands/profile.py +466 -0
- praisonai_code/cli/commands/publish.py +193 -0
- praisonai_code/cli/commands/rag.py +913 -0
- praisonai_code/cli/commands/realtime.py +52 -0
- praisonai_code/cli/commands/recipe.py +684 -0
- praisonai_code/cli/commands/registry.py +59 -0
- praisonai_code/cli/commands/replay.py +830 -0
- praisonai_code/cli/commands/research.py +49 -0
- praisonai_code/cli/commands/retrieval.py +377 -0
- praisonai_code/cli/commands/rules.py +71 -0
- praisonai_code/cli/commands/run.py +1573 -0
- praisonai_code/cli/commands/sandbox.py +371 -0
- praisonai_code/cli/commands/schedule.py +529 -0
- praisonai_code/cli/commands/serve.py +690 -0
- praisonai_code/cli/commands/session.py +450 -0
- praisonai_code/cli/commands/setup.py +174 -0
- praisonai_code/cli/commands/skills.py +545 -0
- praisonai_code/cli/commands/standardise.py +711 -0
- praisonai_code/cli/commands/templates.py +54 -0
- praisonai_code/cli/commands/test.py +558 -0
- praisonai_code/cli/commands/todo.py +74 -0
- praisonai_code/cli/commands/tools.py +205 -0
- praisonai_code/cli/commands/traces.py +145 -0
- praisonai_code/cli/commands/tracker.py +852 -0
- praisonai_code/cli/commands/train.py +613 -0
- praisonai_code/cli/commands/ui.py +172 -0
- praisonai_code/cli/commands/up.py +354 -0
- praisonai_code/cli/commands/validate.py +291 -0
- praisonai_code/cli/commands/version.py +101 -0
- praisonai_code/cli/commands/workflow.py +97 -0
- praisonai_code/cli/config_loader.py +437 -0
- praisonai_code/cli/configuration/__init__.py +27 -0
- praisonai_code/cli/configuration/config.schema.json +57 -0
- praisonai_code/cli/configuration/credentials.py +446 -0
- praisonai_code/cli/configuration/loader.py +364 -0
- praisonai_code/cli/configuration/model_resolver.py +161 -0
- praisonai_code/cli/configuration/oauth.py +389 -0
- praisonai_code/cli/configuration/paths.py +224 -0
- praisonai_code/cli/configuration/resolver.py +687 -0
- praisonai_code/cli/configuration/schema.py +317 -0
- praisonai_code/cli/execution/__init__.py +99 -0
- praisonai_code/cli/execution/core.py +208 -0
- praisonai_code/cli/execution/profiler.py +898 -0
- praisonai_code/cli/execution/request.py +85 -0
- praisonai_code/cli/execution/result.py +74 -0
- praisonai_code/cli/fallback_schema.py +416 -0
- praisonai_code/cli/features/__init__.py +278 -0
- praisonai_code/cli/features/_endpoint_registry.py +64 -0
- praisonai_code/cli/features/_search_registry.py +43 -0
- praisonai_code/cli/features/acp.py +236 -0
- praisonai_code/cli/features/action_orchestrator.py +576 -0
- praisonai_code/cli/features/agent_scheduler.py +773 -0
- praisonai_code/cli/features/agent_tools.py +603 -0
- praisonai_code/cli/features/agents.py +397 -0
- praisonai_code/cli/features/at_mentions.py +471 -0
- praisonai_code/cli/features/audit_cli.py +270 -0
- praisonai_code/cli/features/auto_memory.py +182 -0
- praisonai_code/cli/features/auto_mode.py +552 -0
- praisonai_code/cli/features/autonomy_mode.py +546 -0
- praisonai_code/cli/features/background.py +356 -0
- praisonai_code/cli/features/base.py +168 -0
- praisonai_code/cli/features/benchmark.py +1462 -0
- praisonai_code/cli/features/capabilities.py +1326 -0
- praisonai_code/cli/features/checkpoints.py +345 -0
- praisonai_code/cli/features/cli_profiler.py +335 -0
- praisonai_code/cli/features/code_intelligence.py +666 -0
- praisonai_code/cli/features/compaction.py +294 -0
- praisonai_code/cli/features/compare.py +534 -0
- praisonai_code/cli/features/config_hierarchy.py +366 -0
- praisonai_code/cli/features/context_manager.py +597 -0
- praisonai_code/cli/features/cost_tracker.py +514 -0
- praisonai_code/cli/features/csv_test_runner.py +736 -0
- praisonai_code/cli/features/custom_definitions.py +790 -0
- praisonai_code/cli/features/debug.py +810 -0
- praisonai_code/cli/features/deploy.py +605 -0
- praisonai_code/cli/features/diag.py +289 -0
- praisonai_code/cli/features/display_jsonl.py +173 -0
- praisonai_code/cli/features/doctor/__init__.py +63 -0
- praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
- praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
- praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
- praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
- praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
- praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
- praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
- praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
- praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
- praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
- praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai_code/cli/features/doctor/engine.py +266 -0
- praisonai_code/cli/features/doctor/formatters.py +377 -0
- praisonai_code/cli/features/doctor/handler.py +564 -0
- praisonai_code/cli/features/doctor/models.py +276 -0
- praisonai_code/cli/features/doctor/registry.py +239 -0
- praisonai_code/cli/features/endpoints.py +1016 -0
- praisonai_code/cli/features/eval.py +559 -0
- praisonai_code/cli/features/examples.py +707 -0
- praisonai_code/cli/features/external_agents.py +231 -0
- praisonai_code/cli/features/fast_context.py +410 -0
- praisonai_code/cli/features/file_history.py +320 -0
- praisonai_code/cli/features/flow_display.py +566 -0
- praisonai_code/cli/features/git_attribution.py +159 -0
- praisonai_code/cli/features/git_integration.py +651 -0
- praisonai_code/cli/features/guardrail.py +171 -0
- praisonai_code/cli/features/handoff.py +252 -0
- praisonai_code/cli/features/hooks.py +583 -0
- praisonai_code/cli/features/hybrid_workflow.py +391 -0
- praisonai_code/cli/features/image.py +384 -0
- praisonai_code/cli/features/interactive_core_headless.py +450 -0
- praisonai_code/cli/features/interactive_runtime.py +600 -0
- praisonai_code/cli/features/interactive_test_harness.py +537 -0
- praisonai_code/cli/features/interactive_tools.py +428 -0
- praisonai_code/cli/features/interactive_tui.py +603 -0
- praisonai_code/cli/features/job_workflow.py +906 -0
- praisonai_code/cli/features/jobs.py +632 -0
- praisonai_code/cli/features/knowledge.py +531 -0
- praisonai_code/cli/features/knowledge_cli.py +438 -0
- praisonai_code/cli/features/lite.py +244 -0
- praisonai_code/cli/features/logs.py +200 -0
- praisonai_code/cli/features/lsp_cli.py +225 -0
- praisonai_code/cli/features/lsp_diagnostics.py +185 -0
- praisonai_code/cli/features/mcp.py +344 -0
- praisonai_code/cli/features/message_queue.py +587 -0
- praisonai_code/cli/features/metrics.py +210 -0
- praisonai_code/cli/features/migrate.py +1329 -0
- praisonai_code/cli/features/migration_flow.py +463 -0
- praisonai_code/cli/features/migration_spec.py +276 -0
- praisonai_code/cli/features/n8n.py +703 -0
- praisonai_code/cli/features/observability.py +293 -0
- praisonai_code/cli/features/ollama.py +361 -0
- praisonai_code/cli/features/output_modes.py +155 -0
- praisonai_code/cli/features/output_style.py +273 -0
- praisonai_code/cli/features/package.py +631 -0
- praisonai_code/cli/features/performance.py +308 -0
- praisonai_code/cli/features/persistence.py +636 -0
- praisonai_code/cli/features/profiler/__init__.py +81 -0
- praisonai_code/cli/features/profiler/core.py +558 -0
- praisonai_code/cli/features/profiler/optimizations.py +652 -0
- praisonai_code/cli/features/profiler/suite.py +386 -0
- praisonai_code/cli/features/queue/__init__.py +73 -0
- praisonai_code/cli/features/queue/manager.py +435 -0
- praisonai_code/cli/features/queue/models.py +289 -0
- praisonai_code/cli/features/queue/persistence.py +564 -0
- praisonai_code/cli/features/queue/scheduler.py +529 -0
- praisonai_code/cli/features/queue/worker.py +400 -0
- praisonai_code/cli/features/recipe.py +2187 -0
- praisonai_code/cli/features/recipe_creator.py +996 -0
- praisonai_code/cli/features/recipe_optimizer.py +1364 -0
- praisonai_code/cli/features/recipe_prompts.py +226 -0
- praisonai_code/cli/features/registry.py +229 -0
- praisonai_code/cli/features/repo_map.py +860 -0
- praisonai_code/cli/features/router.py +466 -0
- praisonai_code/cli/features/safe_shell.py +427 -0
- praisonai_code/cli/features/sandbox_cli.py +283 -0
- praisonai_code/cli/features/sandbox_executor.py +536 -0
- praisonai_code/cli/features/sdk_knowledge.py +500 -0
- praisonai_code/cli/features/session.py +222 -0
- praisonai_code/cli/features/session_checkpoints.py +208 -0
- praisonai_code/cli/features/setup/__init__.py +9 -0
- praisonai_code/cli/features/setup/handler.py +355 -0
- praisonai_code/cli/features/setup/templates.py +62 -0
- praisonai_code/cli/features/skills.py +940 -0
- praisonai_code/cli/features/slash_commands.py +692 -0
- praisonai_code/cli/features/telemetry.py +179 -0
- praisonai_code/cli/features/templates.py +1390 -0
- praisonai_code/cli/features/thinking.py +343 -0
- praisonai_code/cli/features/todo.py +334 -0
- praisonai_code/cli/features/tools.py +680 -0
- praisonai_code/cli/features/tui/__init__.py +83 -0
- praisonai_code/cli/features/tui/app.py +871 -0
- praisonai_code/cli/features/tui/cli.py +580 -0
- praisonai_code/cli/features/tui/config.py +150 -0
- praisonai_code/cli/features/tui/debug.py +526 -0
- praisonai_code/cli/features/tui/events.py +99 -0
- praisonai_code/cli/features/tui/mock_provider.py +328 -0
- praisonai_code/cli/features/tui/orchestrator.py +652 -0
- praisonai_code/cli/features/tui/screens/__init__.py +50 -0
- praisonai_code/cli/features/tui/screens/help.py +157 -0
- praisonai_code/cli/features/tui/screens/main.py +568 -0
- praisonai_code/cli/features/tui/screens/queue.py +174 -0
- praisonai_code/cli/features/tui/screens/session.py +124 -0
- praisonai_code/cli/features/tui/screens/settings.py +148 -0
- praisonai_code/cli/features/tui/session_store.py +198 -0
- praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
- praisonai_code/cli/features/tui/widgets/chat.py +263 -0
- praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
- praisonai_code/cli/features/tui/widgets/composer.py +292 -0
- praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
- praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
- praisonai_code/cli/features/tui/widgets/status.py +181 -0
- praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
- praisonai_code/cli/features/wizard.py +289 -0
- praisonai_code/cli/features/workflow.py +802 -0
- praisonai_code/cli/features/yaml_utils.py +321 -0
- praisonai_code/cli/interactive/__init__.py +48 -0
- praisonai_code/cli/interactive/async_tui.py +1218 -0
- praisonai_code/cli/interactive/config.py +139 -0
- praisonai_code/cli/interactive/core.py +618 -0
- praisonai_code/cli/interactive/events.py +131 -0
- praisonai_code/cli/interactive/frontends/__init__.py +31 -0
- praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
- praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
- praisonai_code/cli/interactive/praison_io.py +502 -0
- praisonai_code/cli/interactive/repl.py +297 -0
- praisonai_code/cli/interactive/split_tui.py +456 -0
- praisonai_code/cli/interactive/tui_app.py +457 -0
- praisonai_code/cli/langfuse_client.py +360 -0
- praisonai_code/cli/main.py +7421 -0
- praisonai_code/cli/output/__init__.py +25 -0
- praisonai_code/cli/output/console.py +456 -0
- praisonai_code/cli/output/event_bridge.py +191 -0
- praisonai_code/cli/schedule_cli.py +54 -0
- praisonai_code/cli/schema_provider.py +23 -0
- praisonai_code/cli/session/__init__.py +16 -0
- praisonai_code/cli/session/resume.py +148 -0
- praisonai_code/cli/session/unified.py +548 -0
- praisonai_code/cli/state/__init__.py +31 -0
- praisonai_code/cli/state/identifiers.py +161 -0
- praisonai_code/cli/state/project_sessions.py +383 -0
- praisonai_code/cli/state/sessions.py +390 -0
- praisonai_code/cli/ui/__init__.py +160 -0
- praisonai_code/cli/ui/config.py +46 -0
- praisonai_code/cli/ui/events.py +61 -0
- praisonai_code/cli/ui/mg_backend.py +342 -0
- praisonai_code/cli/ui/plain.py +133 -0
- praisonai_code/cli/ui/rich_backend.py +162 -0
- praisonai_code/cli/unified_schema.py +655 -0
- praisonai_code/cli/utils/env_utils.py +126 -0
- praisonai_code/cli/utils/project.py +131 -0
- praisonai_code/cli_backends/__init__.py +73 -0
- praisonai_code/cli_backends/claude.py +373 -0
- praisonai_code/cli_backends/registry.py +113 -0
- praisonai_code/runtime/__init__.py +36 -0
- praisonai_code/runtime/__main__.py +81 -0
- praisonai_code/runtime/client.py +131 -0
- praisonai_code/runtime/descriptor.py +209 -0
- praisonai_code/runtime/server.py +356 -0
- praisonai_code-0.0.1.dist-info/METADATA +80 -0
- praisonai_code-0.0.1.dist-info/RECORD +309 -0
- praisonai_code-0.0.1.dist-info/WHEEL +5 -0
- praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1573 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Run command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides agent execution commands.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, Optional, List
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from ..output.console import get_output_controller
|
|
12
|
+
from ..state.identifiers import get_current_context
|
|
13
|
+
from ..configuration.resolver import resolve_config
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(help="Run agents")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _framework_help() -> str:
|
|
19
|
+
try:
|
|
20
|
+
from praisonai.framework_adapters.registry import framework_option_help
|
|
21
|
+
return framework_option_help()
|
|
22
|
+
except ImportError:
|
|
23
|
+
return "Framework: praisonai, crewai, autogen"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_FRAMEWORK_HELP = _framework_help()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _parse_permissions(allow: Optional[List[str]], deny: Optional[List[str]], permissions_file: Optional[str], default: Optional[str]) -> Optional[dict]:
|
|
30
|
+
"""Parse permission flags into a config dict.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
allow: Pattern to allow
|
|
34
|
+
deny: Pattern to deny
|
|
35
|
+
permissions_file: Path to permissions file (YAML or JSON)
|
|
36
|
+
default: Default action (allow/deny/ask)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dict mapping patterns to actions, or None if no permissions specified
|
|
40
|
+
"""
|
|
41
|
+
if not any([allow, deny, permissions_file, default]):
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
import json
|
|
45
|
+
import yaml
|
|
46
|
+
|
|
47
|
+
config = {}
|
|
48
|
+
|
|
49
|
+
# Load from file if provided
|
|
50
|
+
if permissions_file:
|
|
51
|
+
try:
|
|
52
|
+
with open(permissions_file, 'r') as f:
|
|
53
|
+
if permissions_file.endswith('.json'):
|
|
54
|
+
file_config = json.load(f)
|
|
55
|
+
else:
|
|
56
|
+
file_config = yaml.safe_load(f)
|
|
57
|
+
if isinstance(file_config, dict):
|
|
58
|
+
config.update(file_config)
|
|
59
|
+
except (IOError, json.JSONDecodeError, yaml.YAMLError) as e:
|
|
60
|
+
from ..output.console import get_output_controller
|
|
61
|
+
get_output_controller().print_warning(f"Failed to load permissions file: {e}")
|
|
62
|
+
|
|
63
|
+
# Add CLI patterns (override file config)
|
|
64
|
+
if allow:
|
|
65
|
+
for pattern in allow:
|
|
66
|
+
config[pattern] = "allow"
|
|
67
|
+
if deny:
|
|
68
|
+
for pattern in deny:
|
|
69
|
+
config[pattern] = "deny"
|
|
70
|
+
|
|
71
|
+
# Add default pattern if specified
|
|
72
|
+
if default and default in ("allow", "deny", "ask"):
|
|
73
|
+
config["*"] = default
|
|
74
|
+
|
|
75
|
+
return config if config else None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _mcp_server_to_command(server: dict) -> Optional[tuple]:
|
|
79
|
+
"""Convert a resolved MCP server config entry to a (command, env) pair.
|
|
80
|
+
|
|
81
|
+
Supports the project ``config.yaml`` schema where a server is declared as
|
|
82
|
+
either ``command: ["npx", "-y", "@playwright/mcp"]`` (list) or
|
|
83
|
+
``command: "npx -y @playwright/mcp"`` (string), with optional ``args`` and
|
|
84
|
+
``env`` keys. Remote servers and disabled servers return None.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Tuple of (command_string, env_string) suitable for ``args.mcp`` /
|
|
88
|
+
``args.mcp_env``, or None if the server cannot be expressed that way.
|
|
89
|
+
"""
|
|
90
|
+
if not isinstance(server, dict):
|
|
91
|
+
return None
|
|
92
|
+
if server.get("enabled") is False:
|
|
93
|
+
return None
|
|
94
|
+
# Only local (stdio) servers map to the command-string CLI path.
|
|
95
|
+
if server.get("type") == "remote" or server.get("url"):
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
command = server.get("command")
|
|
99
|
+
args = server.get("args") or []
|
|
100
|
+
|
|
101
|
+
import shlex
|
|
102
|
+
|
|
103
|
+
if isinstance(command, list):
|
|
104
|
+
parts = [str(p) for p in command]
|
|
105
|
+
elif isinstance(command, str) and command:
|
|
106
|
+
parts = shlex.split(command)
|
|
107
|
+
else:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
if args:
|
|
111
|
+
parts = parts + [str(a) for a in args]
|
|
112
|
+
if not parts:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
# Quote each token so values with spaces/metacharacters survive the
|
|
116
|
+
# downstream ``shlex.split`` in the MCP handler.
|
|
117
|
+
command_str = " ".join(shlex.quote(part) for part in parts)
|
|
118
|
+
|
|
119
|
+
env = server.get("env") or {}
|
|
120
|
+
env_str = None
|
|
121
|
+
if isinstance(env, dict) and env:
|
|
122
|
+
# The downstream parser splits env on commas, so a comma inside a value
|
|
123
|
+
# would corrupt it. Skip such entries with a warning rather than
|
|
124
|
+
# silently producing wrong env vars.
|
|
125
|
+
safe_pairs = []
|
|
126
|
+
for k, v in env.items():
|
|
127
|
+
v_str = str(v)
|
|
128
|
+
if "," in v_str:
|
|
129
|
+
import warnings
|
|
130
|
+
warnings.warn(
|
|
131
|
+
f"MCP env var '{k}' contains a comma and cannot be passed "
|
|
132
|
+
"via the command-string CLI path; skipping it.",
|
|
133
|
+
stacklevel=2,
|
|
134
|
+
)
|
|
135
|
+
continue
|
|
136
|
+
safe_pairs.append(f"{k}={v_str}")
|
|
137
|
+
if safe_pairs:
|
|
138
|
+
env_str = ",".join(safe_pairs)
|
|
139
|
+
|
|
140
|
+
return command_str, env_str
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _collect_mcp_servers_from_config(config) -> List[dict]:
|
|
144
|
+
"""Collect all enabled MCP servers from resolved config.
|
|
145
|
+
|
|
146
|
+
Returns a list of normalized server dicts (both local stdio and remote
|
|
147
|
+
URL servers). The structured list is handed to the core MCP client, which
|
|
148
|
+
already supports stdio, SSE, HTTP-stream and websocket transports — so a
|
|
149
|
+
project that declares multiple and/or remote servers gets every enabled
|
|
150
|
+
one wired, not just the first stdio one.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
List of server config dicts (possibly empty).
|
|
154
|
+
"""
|
|
155
|
+
mcp = getattr(config, "mcp", None) or {}
|
|
156
|
+
if not isinstance(mcp, dict):
|
|
157
|
+
return []
|
|
158
|
+
servers = mcp.get("servers") or {}
|
|
159
|
+
if not isinstance(servers, dict):
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
collected: List[dict] = []
|
|
163
|
+
for name, server in servers.items():
|
|
164
|
+
if not isinstance(server, dict):
|
|
165
|
+
continue
|
|
166
|
+
if server.get("enabled") is False:
|
|
167
|
+
continue
|
|
168
|
+
entry = dict(server)
|
|
169
|
+
entry.setdefault("name", name)
|
|
170
|
+
collected.append(entry)
|
|
171
|
+
return collected
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _build_mcp_tools(
|
|
175
|
+
mcp: Optional[str],
|
|
176
|
+
mcp_env: Optional[str],
|
|
177
|
+
mcp_servers: Optional[List[dict]],
|
|
178
|
+
verbose: bool = False,
|
|
179
|
+
) -> list:
|
|
180
|
+
"""Build aggregated MCP tools from a command string and/or structured list.
|
|
181
|
+
|
|
182
|
+
Both the single ``--mcp`` command string (ad-hoc) and every enabled server
|
|
183
|
+
from project config (local stdio *and* remote/URL) are wired, so the run
|
|
184
|
+
path exposes all configured MCP servers to the agent — not just the first.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
A flat list of MCP tool callables (possibly empty).
|
|
188
|
+
"""
|
|
189
|
+
tools: list = []
|
|
190
|
+
try:
|
|
191
|
+
from praisonai.cli.features.mcp import MCPHandler
|
|
192
|
+
except ImportError:
|
|
193
|
+
return tools
|
|
194
|
+
|
|
195
|
+
handler = MCPHandler(verbose=verbose)
|
|
196
|
+
|
|
197
|
+
if mcp:
|
|
198
|
+
mcp_instance = handler.create_mcp_tools(mcp, mcp_env)
|
|
199
|
+
if mcp_instance:
|
|
200
|
+
tools.extend(list(mcp_instance))
|
|
201
|
+
|
|
202
|
+
for server in mcp_servers or []:
|
|
203
|
+
mcp_instance = handler.create_mcp_from_server(server)
|
|
204
|
+
if mcp_instance:
|
|
205
|
+
tools.extend(list(mcp_instance))
|
|
206
|
+
|
|
207
|
+
return tools
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _permissions_from_config(config) -> Optional[dict]:
|
|
211
|
+
"""Convert a resolved ``permissions`` config section to a pattern->action dict.
|
|
212
|
+
|
|
213
|
+
Supports both the rule-list form::
|
|
214
|
+
|
|
215
|
+
permissions:
|
|
216
|
+
default: ask
|
|
217
|
+
rules:
|
|
218
|
+
- { pattern: "bash:git *", action: allow }
|
|
219
|
+
|
|
220
|
+
and a flat ``pattern: action`` mapping. The flat mapping mirrors the format
|
|
221
|
+
produced by ``_parse_permissions`` (used by ``--allow``/``--deny`` and
|
|
222
|
+
``--permissions``), so both paths converge on the same structure.
|
|
223
|
+
"""
|
|
224
|
+
permissions = getattr(config, "permissions", None) or {}
|
|
225
|
+
if not isinstance(permissions, dict) or not permissions:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
result: dict = {}
|
|
229
|
+
|
|
230
|
+
rules = permissions.get("rules")
|
|
231
|
+
if isinstance(rules, list):
|
|
232
|
+
for rule in rules:
|
|
233
|
+
if isinstance(rule, dict):
|
|
234
|
+
pattern = rule.get("pattern")
|
|
235
|
+
action = rule.get("action")
|
|
236
|
+
if pattern and action in ("allow", "deny", "ask"):
|
|
237
|
+
result[pattern] = action
|
|
238
|
+
|
|
239
|
+
default = permissions.get("default")
|
|
240
|
+
if default in ("allow", "deny", "ask"):
|
|
241
|
+
result["*"] = default
|
|
242
|
+
|
|
243
|
+
# Allow a flat pattern->action mapping alongside the structured form.
|
|
244
|
+
for key, value in permissions.items():
|
|
245
|
+
if key in ("rules", "default"):
|
|
246
|
+
continue
|
|
247
|
+
if isinstance(value, str) and value in ("allow", "deny", "ask"):
|
|
248
|
+
result[key] = value
|
|
249
|
+
|
|
250
|
+
return result or None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _apply_config_defaults(
|
|
254
|
+
mcp: Optional[str],
|
|
255
|
+
mcp_env: Optional[str],
|
|
256
|
+
permissions_config: Optional[dict],
|
|
257
|
+
) -> tuple:
|
|
258
|
+
"""Layer resolved project config under explicit CLI flags.
|
|
259
|
+
|
|
260
|
+
MCP servers and permission policy declared in ``.praisonai/config.yaml`` are
|
|
261
|
+
surfaced here so any ``praisonai run`` in the directory inherits them. CLI
|
|
262
|
+
flags always take precedence (they are passed in already-resolved and only
|
|
263
|
+
config-derived values fill the gaps).
|
|
264
|
+
|
|
265
|
+
All enabled MCP servers from config — multiple local stdio servers *and*
|
|
266
|
+
remote/URL servers — are collected into a structured list so the run path
|
|
267
|
+
can wire every one of them, not just the first stdio server.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Tuple of (mcp, mcp_env, permissions_config, mcp_servers) with config
|
|
271
|
+
defaults applied. ``mcp_servers`` is a (possibly empty) list of
|
|
272
|
+
structured server config dicts.
|
|
273
|
+
"""
|
|
274
|
+
mcp_servers: List[dict] = []
|
|
275
|
+
try:
|
|
276
|
+
config = resolve_config()
|
|
277
|
+
except (ValueError, OSError):
|
|
278
|
+
return mcp, mcp_env, permissions_config, mcp_servers
|
|
279
|
+
|
|
280
|
+
# MCP: collect all enabled servers (local + remote) from config. The
|
|
281
|
+
# explicit single-string ``--mcp`` flag, when given, is wired separately
|
|
282
|
+
# via ``args.mcp`` and merged alongside these by the run handler.
|
|
283
|
+
mcp_servers = _collect_mcp_servers_from_config(config)
|
|
284
|
+
|
|
285
|
+
# Permissions: merge config rules underneath CLI-provided rules.
|
|
286
|
+
config_perms = _permissions_from_config(config)
|
|
287
|
+
if config_perms:
|
|
288
|
+
if permissions_config:
|
|
289
|
+
merged = dict(config_perms)
|
|
290
|
+
merged.update(permissions_config) # CLI flags override config
|
|
291
|
+
permissions_config = merged
|
|
292
|
+
else:
|
|
293
|
+
permissions_config = config_perms
|
|
294
|
+
|
|
295
|
+
return mcp, mcp_env, permissions_config, mcp_servers
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _checkpoints_auto_enabled() -> bool:
|
|
299
|
+
"""Whether automatic run-checkpointing is enabled via project config.
|
|
300
|
+
|
|
301
|
+
Reads the ``checkpoints.auto`` key from the resolved project config
|
|
302
|
+
(stored under ``extra``). Defaults to ``True`` so interactive terminal
|
|
303
|
+
runs get an automatic safety net; opt out with ``--no-checkpoint`` or
|
|
304
|
+
``checkpoints: {auto: false}``.
|
|
305
|
+
"""
|
|
306
|
+
try:
|
|
307
|
+
config = resolve_config()
|
|
308
|
+
except (ValueError, OSError):
|
|
309
|
+
return True
|
|
310
|
+
|
|
311
|
+
checkpoints = (getattr(config, "extra", None) or {}).get("checkpoints")
|
|
312
|
+
if isinstance(checkpoints, dict) and "auto" in checkpoints:
|
|
313
|
+
return bool(checkpoints["auto"])
|
|
314
|
+
return True
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _checkpoints_storage_dir() -> Optional[str]:
|
|
318
|
+
"""Return a configured ``checkpoints.storage_dir`` (or ``None``).
|
|
319
|
+
|
|
320
|
+
Keeps ``praisonai run`` auto-checkpoints and restores reading from the same
|
|
321
|
+
store the rest of the CLI (``code --checkpoints``, ``praisonai checkpoint``)
|
|
322
|
+
uses when ``checkpoints.storage_dir`` is configured.
|
|
323
|
+
"""
|
|
324
|
+
try:
|
|
325
|
+
config = resolve_config()
|
|
326
|
+
except (ValueError, OSError):
|
|
327
|
+
return None
|
|
328
|
+
checkpoints = (getattr(config, "extra", None) or {}).get("checkpoints")
|
|
329
|
+
if isinstance(checkpoints, dict):
|
|
330
|
+
return checkpoints.get("storage_dir")
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _auto_checkpoint(label: str, *, no_checkpoint: bool, workspace_dir: Optional[str] = None) -> None:
|
|
335
|
+
"""Create an automatic checkpoint of the workspace before a run.
|
|
336
|
+
|
|
337
|
+
``workspace_dir`` defaults to the current directory but should be the
|
|
338
|
+
directory of the project being run (e.g. the directory containing a target
|
|
339
|
+
YAML file) so the checkpoint protects the files the run will actually
|
|
340
|
+
touch.
|
|
341
|
+
|
|
342
|
+
Best-effort and quiet: any failure (e.g. protected path, no git, no
|
|
343
|
+
changes) is swallowed so a checkpoint problem never blocks the run and the
|
|
344
|
+
auto-checkpoint never leaks into machine-readable run output.
|
|
345
|
+
"""
|
|
346
|
+
if no_checkpoint or not _checkpoints_auto_enabled():
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
import asyncio
|
|
350
|
+
import os
|
|
351
|
+
|
|
352
|
+
output = get_output_controller()
|
|
353
|
+
try:
|
|
354
|
+
from praisonai.cli.features.checkpoints import CheckpointsHandler
|
|
355
|
+
|
|
356
|
+
handler = CheckpointsHandler(
|
|
357
|
+
workspace_dir=workspace_dir or os.getcwd(),
|
|
358
|
+
storage_dir=_checkpoints_storage_dir(),
|
|
359
|
+
)
|
|
360
|
+
asyncio.run(handler.save(label, allow_empty=False, quiet=True))
|
|
361
|
+
except Exception as e: # pragma: no cover - defensive, never block the run
|
|
362
|
+
if getattr(output, "is_verbose", False):
|
|
363
|
+
output.print_info(f"Auto-checkpoint skipped: {e}")
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _restore_checkpoint(ref: str, workspace_dir: Optional[str] = None) -> None:
|
|
367
|
+
"""Restore the workspace to a checkpoint reference ('last' or an id)."""
|
|
368
|
+
import asyncio
|
|
369
|
+
import os
|
|
370
|
+
|
|
371
|
+
from praisonai.cli.features.checkpoints import CheckpointsHandler
|
|
372
|
+
|
|
373
|
+
handler = CheckpointsHandler(
|
|
374
|
+
workspace_dir=workspace_dir or os.getcwd(),
|
|
375
|
+
storage_dir=_checkpoints_storage_dir(),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
async def _run() -> bool:
|
|
379
|
+
service = await handler._get_service()
|
|
380
|
+
checkpoints = await service.list_checkpoints(limit=100)
|
|
381
|
+
if not checkpoints:
|
|
382
|
+
handler._print_error("No checkpoints found to restore")
|
|
383
|
+
return False
|
|
384
|
+
if ref in ("last", "latest"):
|
|
385
|
+
target = checkpoints[0].id
|
|
386
|
+
else:
|
|
387
|
+
# Exact id/short_id first, then a unique prefix; reject ambiguous
|
|
388
|
+
# prefixes so we never restore the wrong workspace.
|
|
389
|
+
exact = [cp.id for cp in checkpoints if cp.id == ref or cp.short_id == ref]
|
|
390
|
+
if exact:
|
|
391
|
+
target = exact[0]
|
|
392
|
+
else:
|
|
393
|
+
prefix_matches = [cp.id for cp in checkpoints if cp.id.startswith(ref)]
|
|
394
|
+
if len(prefix_matches) == 1:
|
|
395
|
+
target = prefix_matches[0]
|
|
396
|
+
elif len(prefix_matches) > 1:
|
|
397
|
+
handler._print_error(f"Ambiguous checkpoint reference: {ref}")
|
|
398
|
+
return False
|
|
399
|
+
else:
|
|
400
|
+
handler._print_error(f"No checkpoint found for: {ref}")
|
|
401
|
+
return False
|
|
402
|
+
return await handler.restore(target)
|
|
403
|
+
|
|
404
|
+
if not asyncio.run(_run()):
|
|
405
|
+
raise typer.Exit(1)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _try_attach_runtime(
|
|
409
|
+
prompt: str,
|
|
410
|
+
*,
|
|
411
|
+
model: Optional[str],
|
|
412
|
+
output_mode: Optional[str],
|
|
413
|
+
session_id: Optional[str],
|
|
414
|
+
) -> bool:
|
|
415
|
+
"""Forward a plain prompt to a warm runtime when one is running.
|
|
416
|
+
|
|
417
|
+
Returns True when the request was handled by the warm runtime (so the caller
|
|
418
|
+
should skip in-process execution), or False to fall back to the normal
|
|
419
|
+
in-process path. Any runtime error is treated as a transparent fall-back.
|
|
420
|
+
|
|
421
|
+
Only the simple text path is attached: structured ``actions``/``json`` output
|
|
422
|
+
modes and session continuity stay in-process to preserve their behaviour.
|
|
423
|
+
"""
|
|
424
|
+
# Structured output modes have richer in-process event bridging; don't attach.
|
|
425
|
+
if output_mode in ("actions", "json", "stream", "stream-json"):
|
|
426
|
+
return False
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
from praisonai.runtime import get_runtime_descriptor, RuntimeClient, RuntimeUnavailable
|
|
430
|
+
except ImportError:
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
# Require a version-compatible runtime: a stale (major-mismatched) server is
|
|
434
|
+
# not attached to, so the cold in-process path runs instead of silently
|
|
435
|
+
# talking to an incompatible runtime.
|
|
436
|
+
descriptor = get_runtime_descriptor(require_compatible=True)
|
|
437
|
+
if descriptor is None:
|
|
438
|
+
return False
|
|
439
|
+
|
|
440
|
+
output = get_output_controller()
|
|
441
|
+
try:
|
|
442
|
+
client = RuntimeClient(descriptor)
|
|
443
|
+
result = client.run(prompt, model=model, session_id=session_id)
|
|
444
|
+
except RuntimeUnavailable:
|
|
445
|
+
# Runtime went away mid-flight; fall back to in-process execution.
|
|
446
|
+
return False
|
|
447
|
+
|
|
448
|
+
output.emit_result(
|
|
449
|
+
message="Prompt completed",
|
|
450
|
+
data={"result": str(result) if result else None},
|
|
451
|
+
)
|
|
452
|
+
if result and not output.is_json_mode:
|
|
453
|
+
print(result)
|
|
454
|
+
return True
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@app.callback(invoke_without_command=True)
|
|
458
|
+
def run_main(
|
|
459
|
+
ctx: typer.Context,
|
|
460
|
+
target: Optional[str] = typer.Argument(None, help="Agent file or prompt"),
|
|
461
|
+
model: Optional[str] = typer.Option(None, "--model", "-m", help="LLM model to use"),
|
|
462
|
+
framework: Optional[str] = typer.Option(None, "--framework", "-f", help=_FRAMEWORK_HELP),
|
|
463
|
+
interactive: bool = typer.Option(False, "--interactive", "-i", help="Interactive mode"),
|
|
464
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
|
|
465
|
+
stream: bool = typer.Option(False, "--stream/--no-stream", help="Stream output (default: off for production use)"),
|
|
466
|
+
trace: bool = typer.Option(False, "--trace", help="Enable tracing"),
|
|
467
|
+
memory: bool = typer.Option(False, "--memory", help="Enable memory"),
|
|
468
|
+
tools: Optional[str] = typer.Option(None, "--tools", "-t", help="Tools file path"),
|
|
469
|
+
toolset: Optional[str] = typer.Option(None, "--toolset", help="Named toolset groups (comma-separated, e.g., web,files)"),
|
|
470
|
+
max_tokens: int = typer.Option(16000, "--max-tokens", help="Maximum output tokens"),
|
|
471
|
+
profile: bool = typer.Option(False, "--profile", help="Enable CLI profiling (timing breakdown)"),
|
|
472
|
+
profile_deep: bool = typer.Option(False, "--profile-deep", help="Enable deep profiling (cProfile stats, higher overhead)"),
|
|
473
|
+
output_mode: Optional[str] = typer.Option(None, "--output", "-o", help="Output mode: silent (default), actions, verbose, json, stream"),
|
|
474
|
+
approval: Optional[str] = typer.Option(None, "--approval", help="Approval backend: console, slack, telegram, discord, webhook, http, agent, auto, none"),
|
|
475
|
+
approve_all_tools: bool = typer.Option(False, "--approve-all-tools", help="Require approval for ALL tool calls, not just dangerous tools"),
|
|
476
|
+
approval_timeout: Optional[str] = typer.Option(None, "--approval-timeout", help="Seconds to wait for approval. Use 'none' for indefinite wait"),
|
|
477
|
+
no_rules: bool = typer.Option(False, "--no-rules", help="Disable auto-injection of project instruction files"),
|
|
478
|
+
# Permission flags for CI-safe declarative policies
|
|
479
|
+
allow: Optional[List[str]] = typer.Option(None, "--allow", help="Permission pattern to allow (e.g., 'read:*', 'bash:git *'). Can be repeated."),
|
|
480
|
+
deny: Optional[List[str]] = typer.Option(None, "--deny", help="Permission pattern to deny (e.g., 'bash:rm *'). Can be repeated."),
|
|
481
|
+
permissions: Optional[str] = typer.Option(None, "--permissions", help="Permission file path (YAML or JSON) with allow/deny rules"),
|
|
482
|
+
permission_default: Optional[str] = typer.Option(None, "--permission-default", help="Default action for unmatched patterns: allow, deny, ask (default: ask)"),
|
|
483
|
+
# Session continuity options
|
|
484
|
+
continue_session: bool = typer.Option(False, "--continue", "-c", help="Continue the most recent session for this project"),
|
|
485
|
+
session: Optional[str] = typer.Option(None, "--session", "-s", help="Resume a specific session ID"),
|
|
486
|
+
fork: bool = typer.Option(False, "--fork", help="Fork from the specified session (requires --session)"),
|
|
487
|
+
no_save: bool = typer.Option(False, "--no-save", help="Don't auto-save session after execution"),
|
|
488
|
+
# Custom definitions
|
|
489
|
+
agent: Optional[str] = typer.Option(None, "--agent", "-a", help="Use a named custom agent"),
|
|
490
|
+
command: Optional[str] = typer.Option(None, "--command", help="Execute a named custom command"),
|
|
491
|
+
# Reasoning effort
|
|
492
|
+
thinking: Optional[str] = typer.Option(None, "--thinking", help="Reasoning effort (off, minimal, low, medium, high)"),
|
|
493
|
+
# Checkpoint / rewind
|
|
494
|
+
no_checkpoint: bool = typer.Option(False, "--no-checkpoint", help="Disable automatic file checkpoint before the run"),
|
|
495
|
+
restore: Optional[str] = typer.Option(None, "--restore", help="Restore the workspace to a checkpoint id (or 'last') and exit"),
|
|
496
|
+
# Warm-runtime live session: tag this run so other terminals can `attach`.
|
|
497
|
+
attach: Optional[str] = typer.Option(None, "--attach", help="Run on the warm runtime under this session id so other terminals can observe it via `praisonai attach <id>`"),
|
|
498
|
+
):
|
|
499
|
+
"""
|
|
500
|
+
Run agents from a file or prompt.
|
|
501
|
+
|
|
502
|
+
Examples:
|
|
503
|
+
praisonai run agents.yaml
|
|
504
|
+
praisonai run "What is the weather?"
|
|
505
|
+
praisonai run "What is the weather?" --continue
|
|
506
|
+
praisonai run "Add tests" --session abc123
|
|
507
|
+
praisonai run agents.yaml --interactive
|
|
508
|
+
praisonai run "What is 2+2?" --profile
|
|
509
|
+
"""
|
|
510
|
+
output = get_output_controller()
|
|
511
|
+
_ = get_current_context() # Initialize context
|
|
512
|
+
|
|
513
|
+
# Rewind: restore the workspace to a prior checkpoint and exit. Handled
|
|
514
|
+
# before any execution so `praisonai run --restore last` is a pure undo.
|
|
515
|
+
if restore:
|
|
516
|
+
_restore_checkpoint(restore)
|
|
517
|
+
return
|
|
518
|
+
|
|
519
|
+
# Validate --thinking and resolve it to the core thinking_budget up front so
|
|
520
|
+
# an unknown value fails closed before any execution (consistent with the
|
|
521
|
+
# `code` command and MODE_RULES validation on custom agents).
|
|
522
|
+
from praisonai.cli.features.thinking import thinking_to_budget
|
|
523
|
+
try:
|
|
524
|
+
thinking_budget = thinking_to_budget(thinking)
|
|
525
|
+
except ValueError as exc:
|
|
526
|
+
output.print_error(str(exc))
|
|
527
|
+
raise typer.Exit(1)
|
|
528
|
+
|
|
529
|
+
# Early credential check before any processing
|
|
530
|
+
if target: # Only check if we actually have something to run
|
|
531
|
+
from praisonai.llm.credentials import is_configured
|
|
532
|
+
import sys
|
|
533
|
+
|
|
534
|
+
# Check if credentials are configured (use model if provided, else check general)
|
|
535
|
+
if not is_configured(model):
|
|
536
|
+
# In non-interactive mode, show clear error
|
|
537
|
+
if not sys.stdin.isatty() or output.is_json_mode:
|
|
538
|
+
output.print_error(
|
|
539
|
+
"No API key configured. Run: praisonai setup\n"
|
|
540
|
+
"or set environment variables like OPENAI_API_KEY"
|
|
541
|
+
)
|
|
542
|
+
raise typer.Exit(1)
|
|
543
|
+
|
|
544
|
+
# In interactive mode, offer to run setup
|
|
545
|
+
typer.echo(f"No API key configured{f' for model {model}' if model else ''}.")
|
|
546
|
+
run_setup = typer.confirm("Would you like to run the setup wizard now?")
|
|
547
|
+
|
|
548
|
+
if run_setup:
|
|
549
|
+
from praisonai.cli.commands.setup import _run_setup
|
|
550
|
+
exit_code = _run_setup(
|
|
551
|
+
non_interactive=False,
|
|
552
|
+
provider=None,
|
|
553
|
+
api_key=None,
|
|
554
|
+
model=None
|
|
555
|
+
)
|
|
556
|
+
if exit_code != 0:
|
|
557
|
+
output.print_error("Setup failed. Exiting.")
|
|
558
|
+
raise typer.Exit(exit_code)
|
|
559
|
+
|
|
560
|
+
output.print_success("Setup complete! Continuing with your run...")
|
|
561
|
+
# Re-check after setup
|
|
562
|
+
if not is_configured(model):
|
|
563
|
+
output.print_error("Setup completed but credentials still not detected.")
|
|
564
|
+
raise typer.Exit(1)
|
|
565
|
+
else:
|
|
566
|
+
output.print_info(
|
|
567
|
+
"To configure credentials:\n"
|
|
568
|
+
" - Run: praisonai setup\n"
|
|
569
|
+
" - Or set environment variables like OPENAI_API_KEY"
|
|
570
|
+
)
|
|
571
|
+
raise typer.Exit(0)
|
|
572
|
+
|
|
573
|
+
# Resolve configuration if model not explicitly provided
|
|
574
|
+
if model is None:
|
|
575
|
+
try:
|
|
576
|
+
config = resolve_config()
|
|
577
|
+
if config.agent.model:
|
|
578
|
+
model = config.agent.model
|
|
579
|
+
if verbose:
|
|
580
|
+
output.print_info(f"Using model from config: {model}")
|
|
581
|
+
except (ValueError, OSError) as e:
|
|
582
|
+
# Continue if config resolution fails, but log in verbose mode
|
|
583
|
+
if verbose:
|
|
584
|
+
output.print_info(f"Skipping config-based model fallback: {e}")
|
|
585
|
+
|
|
586
|
+
# Validate session options
|
|
587
|
+
if fork and not session:
|
|
588
|
+
output.print_error("--fork requires --session to specify which session to fork from")
|
|
589
|
+
raise typer.Exit(1)
|
|
590
|
+
|
|
591
|
+
if continue_session and session:
|
|
592
|
+
output.print_error("Cannot use both --continue and --session together")
|
|
593
|
+
raise typer.Exit(1)
|
|
594
|
+
|
|
595
|
+
# --attach tags a warm-runtime run so other terminals can observe it, but
|
|
596
|
+
# only the direct-prompt path forwards to the warm runtime. Reject it up
|
|
597
|
+
# front on profile/custom-agent/custom-command flows so users never
|
|
598
|
+
# pass --attach and silently get a session that produces no events.
|
|
599
|
+
if attach and (agent or command or profile or profile_deep):
|
|
600
|
+
output.print_error("--attach is only supported for direct prompt runs")
|
|
601
|
+
raise typer.Exit(1)
|
|
602
|
+
|
|
603
|
+
# Handle custom agent or command
|
|
604
|
+
if agent:
|
|
605
|
+
from praisonai.cli.features.custom_definitions import load_agent_from_name
|
|
606
|
+
agent_config = load_agent_from_name(agent)
|
|
607
|
+
if not agent_config:
|
|
608
|
+
output.print_error(f"Agent '{agent}' not found")
|
|
609
|
+
raise typer.Exit(1)
|
|
610
|
+
|
|
611
|
+
# Invocation-level permission flags override per-agent definition.
|
|
612
|
+
invocation_permissions = _parse_permissions(allow, deny, permissions, permission_default)
|
|
613
|
+
|
|
614
|
+
# Run with custom agent
|
|
615
|
+
_run_custom_agent(
|
|
616
|
+
agent_config,
|
|
617
|
+
target or "", # Use target as the prompt if provided
|
|
618
|
+
model=model,
|
|
619
|
+
verbose=verbose,
|
|
620
|
+
stream=stream,
|
|
621
|
+
trace=trace,
|
|
622
|
+
memory=memory,
|
|
623
|
+
tools=tools,
|
|
624
|
+
toolset=toolset,
|
|
625
|
+
max_tokens=max_tokens,
|
|
626
|
+
output_mode=output_mode,
|
|
627
|
+
approval=approval,
|
|
628
|
+
approve_all_tools=approve_all_tools,
|
|
629
|
+
approval_timeout=approval_timeout,
|
|
630
|
+
invocation_permissions=invocation_permissions,
|
|
631
|
+
continue_session=continue_session,
|
|
632
|
+
session=session,
|
|
633
|
+
fork=fork,
|
|
634
|
+
no_save=no_save,
|
|
635
|
+
thinking_budget=thinking_budget,
|
|
636
|
+
)
|
|
637
|
+
return
|
|
638
|
+
|
|
639
|
+
if command:
|
|
640
|
+
from praisonai.cli.features.custom_definitions import (
|
|
641
|
+
ShellSubstitutionError,
|
|
642
|
+
interpolate_command_template,
|
|
643
|
+
)
|
|
644
|
+
try:
|
|
645
|
+
prompt = interpolate_command_template(command, target or "")
|
|
646
|
+
except ShellSubstitutionError as exc:
|
|
647
|
+
output.print_error(str(exc))
|
|
648
|
+
raise typer.Exit(1)
|
|
649
|
+
if not prompt:
|
|
650
|
+
output.print_error(f"Command '{command}' not found")
|
|
651
|
+
raise typer.Exit(1)
|
|
652
|
+
|
|
653
|
+
# Run the interpolated command as a prompt
|
|
654
|
+
permissions_config = _parse_permissions(allow, deny, permissions, permission_default)
|
|
655
|
+
mcp_command, mcp_env, permissions_config, mcp_servers = _apply_config_defaults(
|
|
656
|
+
None, None, permissions_config
|
|
657
|
+
)
|
|
658
|
+
_run_prompt(
|
|
659
|
+
prompt,
|
|
660
|
+
model=model,
|
|
661
|
+
verbose=verbose,
|
|
662
|
+
stream=stream,
|
|
663
|
+
trace=trace,
|
|
664
|
+
memory=memory,
|
|
665
|
+
tools=tools,
|
|
666
|
+
toolset=toolset,
|
|
667
|
+
max_tokens=max_tokens,
|
|
668
|
+
output_mode=output_mode,
|
|
669
|
+
approval=approval,
|
|
670
|
+
approve_all_tools=approve_all_tools,
|
|
671
|
+
approval_timeout=approval_timeout,
|
|
672
|
+
no_rules=no_rules,
|
|
673
|
+
permissions_config=permissions_config,
|
|
674
|
+
mcp=mcp_command,
|
|
675
|
+
mcp_env=mcp_env,
|
|
676
|
+
mcp_servers=mcp_servers,
|
|
677
|
+
continue_session=continue_session,
|
|
678
|
+
session=session,
|
|
679
|
+
fork=fork,
|
|
680
|
+
no_save=no_save,
|
|
681
|
+
thinking_budget=thinking_budget,
|
|
682
|
+
)
|
|
683
|
+
return
|
|
684
|
+
|
|
685
|
+
if not target:
|
|
686
|
+
output.print_panel(
|
|
687
|
+
"Run agents from a file or prompt.\n\n"
|
|
688
|
+
"Usage:\n"
|
|
689
|
+
" praisonai run agents.yaml\n"
|
|
690
|
+
" praisonai run \"What is the weather?\"\n"
|
|
691
|
+
" praisonai run \"What is the weather?\" --continue\n"
|
|
692
|
+
" praisonai run \"Add tests\" --session abc123\n"
|
|
693
|
+
" praisonai run agents.yaml --interactive\n"
|
|
694
|
+
" praisonai run --agent researcher \"Find info on X\"\n"
|
|
695
|
+
" praisonai run --command summarize \"Long text here\"\n\n"
|
|
696
|
+
"Options:\n"
|
|
697
|
+
" --model, -m LLM model to use\n"
|
|
698
|
+
f" --framework, -f {_FRAMEWORK_HELP}\n"
|
|
699
|
+
" --interactive, -i Interactive mode\n"
|
|
700
|
+
" --verbose, -v Verbose output\n"
|
|
701
|
+
" --trace Enable tracing\n"
|
|
702
|
+
" --memory Enable memory\n"
|
|
703
|
+
" --continue, -c Continue the most recent session\n"
|
|
704
|
+
" --session, -s Resume a specific session ID\n"
|
|
705
|
+
" --fork Fork from specified session\n"
|
|
706
|
+
" --no-save Don't auto-save session\n"
|
|
707
|
+
" --agent, -a Use a named custom agent\n"
|
|
708
|
+
" --command Execute a named custom command",
|
|
709
|
+
title="Run Command"
|
|
710
|
+
)
|
|
711
|
+
return
|
|
712
|
+
|
|
713
|
+
# Emit start event
|
|
714
|
+
from ..output.event_bridge import SCHEMA_VERSION
|
|
715
|
+
output.emit_start(
|
|
716
|
+
message=f"Starting run: {target[:50]}..." if len(target) > 50 else f"Starting run: {target}",
|
|
717
|
+
data={
|
|
718
|
+
"schema_version": SCHEMA_VERSION,
|
|
719
|
+
"target": target,
|
|
720
|
+
"model": model,
|
|
721
|
+
"framework": framework,
|
|
722
|
+
}
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
# Check if target is a file or prompt
|
|
726
|
+
import os
|
|
727
|
+
is_file = os.path.exists(target) and (target.endswith('.yaml') or target.endswith('.yml'))
|
|
728
|
+
|
|
729
|
+
# Only the direct-prompt path forwards to the warm runtime, so reject
|
|
730
|
+
# --attach on file execution rather than letting it run with no observable
|
|
731
|
+
# session (the attach client would wait forever for events).
|
|
732
|
+
if attach and is_file:
|
|
733
|
+
output.print_error("--attach is only supported for direct prompt runs")
|
|
734
|
+
raise typer.Exit(1)
|
|
735
|
+
|
|
736
|
+
# Auto-checkpoint before file-based runs so a bad turn can be rewound with
|
|
737
|
+
# `praisonai run --restore last`. Scoped to YAML-file runs (which mutate
|
|
738
|
+
# project files) and snapshotted against the file's own directory so the
|
|
739
|
+
# checkpoint protects the right workspace. Plain-prompt runs don't touch
|
|
740
|
+
# project files, so they skip checkpointing (avoiding spurious "no changes"
|
|
741
|
+
# noise). Best-effort and gated by config (`checkpoints.auto`, default on)
|
|
742
|
+
# and `--no-checkpoint`.
|
|
743
|
+
if is_file:
|
|
744
|
+
from ..state.identifiers import get_current_context as _get_ctx
|
|
745
|
+
_run_id = getattr(_get_ctx(), "run_id", None)
|
|
746
|
+
_auto_checkpoint(
|
|
747
|
+
f"run:{_run_id}" if _run_id else "auto checkpoint before run",
|
|
748
|
+
no_checkpoint=no_checkpoint,
|
|
749
|
+
workspace_dir=os.path.dirname(os.path.abspath(target)) or None,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
# Handle profiling
|
|
753
|
+
if profile or profile_deep:
|
|
754
|
+
if is_file:
|
|
755
|
+
# Profiling for YAML file execution
|
|
756
|
+
_run_from_file_profiled(
|
|
757
|
+
target,
|
|
758
|
+
model=model,
|
|
759
|
+
framework=framework,
|
|
760
|
+
verbose=verbose,
|
|
761
|
+
profile_deep=profile_deep,
|
|
762
|
+
continue_session=continue_session,
|
|
763
|
+
session=session,
|
|
764
|
+
fork=fork,
|
|
765
|
+
no_save=no_save,
|
|
766
|
+
)
|
|
767
|
+
else:
|
|
768
|
+
# Profiling for direct prompt
|
|
769
|
+
_run_prompt_profiled(
|
|
770
|
+
target,
|
|
771
|
+
model=model,
|
|
772
|
+
verbose=verbose,
|
|
773
|
+
profile_deep=profile_deep,
|
|
774
|
+
continue_session=continue_session,
|
|
775
|
+
session=session,
|
|
776
|
+
fork=fork,
|
|
777
|
+
no_save=no_save,
|
|
778
|
+
)
|
|
779
|
+
return
|
|
780
|
+
|
|
781
|
+
if is_file:
|
|
782
|
+
# Run from file
|
|
783
|
+
_run_from_file(
|
|
784
|
+
target,
|
|
785
|
+
model=model,
|
|
786
|
+
framework=framework,
|
|
787
|
+
interactive=interactive,
|
|
788
|
+
verbose=verbose,
|
|
789
|
+
stream=stream,
|
|
790
|
+
trace=trace,
|
|
791
|
+
memory=memory,
|
|
792
|
+
tools=tools,
|
|
793
|
+
max_tokens=max_tokens,
|
|
794
|
+
output_mode=output_mode,
|
|
795
|
+
continue_session=continue_session,
|
|
796
|
+
session=session,
|
|
797
|
+
fork=fork,
|
|
798
|
+
no_save=no_save,
|
|
799
|
+
)
|
|
800
|
+
else:
|
|
801
|
+
# Run as prompt
|
|
802
|
+
permissions_config = _parse_permissions(allow, deny, permissions, permission_default)
|
|
803
|
+
mcp_command, mcp_env, permissions_config, mcp_servers = _apply_config_defaults(
|
|
804
|
+
None, None, permissions_config
|
|
805
|
+
)
|
|
806
|
+
_run_prompt(
|
|
807
|
+
target,
|
|
808
|
+
model=model,
|
|
809
|
+
verbose=verbose,
|
|
810
|
+
stream=stream,
|
|
811
|
+
trace=trace,
|
|
812
|
+
memory=memory,
|
|
813
|
+
tools=tools,
|
|
814
|
+
toolset=toolset,
|
|
815
|
+
max_tokens=max_tokens,
|
|
816
|
+
output_mode=output_mode,
|
|
817
|
+
approval=approval,
|
|
818
|
+
approve_all_tools=approve_all_tools,
|
|
819
|
+
approval_timeout=approval_timeout,
|
|
820
|
+
no_rules=no_rules,
|
|
821
|
+
permissions_config=permissions_config,
|
|
822
|
+
mcp=mcp_command,
|
|
823
|
+
mcp_env=mcp_env,
|
|
824
|
+
mcp_servers=mcp_servers,
|
|
825
|
+
continue_session=continue_session,
|
|
826
|
+
session=session,
|
|
827
|
+
fork=fork,
|
|
828
|
+
no_save=no_save,
|
|
829
|
+
attach_session=attach,
|
|
830
|
+
thinking_budget=thinking_budget,
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _run_from_file(
|
|
835
|
+
file_path: str,
|
|
836
|
+
model: Optional[str] = None,
|
|
837
|
+
framework: Optional[str] = None,
|
|
838
|
+
interactive: bool = False,
|
|
839
|
+
verbose: bool = False,
|
|
840
|
+
stream: bool = True,
|
|
841
|
+
trace: bool = False,
|
|
842
|
+
memory: bool = False,
|
|
843
|
+
tools: Optional[str] = None,
|
|
844
|
+
max_tokens: int = 16000,
|
|
845
|
+
output_mode: Optional[str] = None,
|
|
846
|
+
continue_session: bool = False,
|
|
847
|
+
session: Optional[str] = None,
|
|
848
|
+
fork: bool = False,
|
|
849
|
+
no_save: bool = False,
|
|
850
|
+
):
|
|
851
|
+
"""Run agents from a YAML file."""
|
|
852
|
+
output = get_output_controller()
|
|
853
|
+
|
|
854
|
+
# Note: Credential check already done in run_main() entry point
|
|
855
|
+
|
|
856
|
+
try:
|
|
857
|
+
# Use existing PraisonAI class
|
|
858
|
+
from praisonai.cli.main import PraisonAI
|
|
859
|
+
|
|
860
|
+
praison = PraisonAI(
|
|
861
|
+
agent_file=file_path,
|
|
862
|
+
framework=framework or "praisonai",
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
# Set model if provided
|
|
866
|
+
if model:
|
|
867
|
+
praison.config_list[0]['model'] = model
|
|
868
|
+
|
|
869
|
+
# Handle session continuity for YAML files
|
|
870
|
+
session_id = None
|
|
871
|
+
auto_save_name = None
|
|
872
|
+
|
|
873
|
+
if continue_session or session or fork:
|
|
874
|
+
from ..state.project_sessions import find_last_session, session_exists_anywhere
|
|
875
|
+
|
|
876
|
+
if continue_session:
|
|
877
|
+
# Find last session for this project
|
|
878
|
+
session_id = find_last_session()
|
|
879
|
+
if session_id:
|
|
880
|
+
output.print_info(f"Continuing session: {session_id}")
|
|
881
|
+
else:
|
|
882
|
+
output.print_warning("No previous sessions found. Starting new session.")
|
|
883
|
+
|
|
884
|
+
elif session:
|
|
885
|
+
# Use specific session
|
|
886
|
+
if session_exists_anywhere(session):
|
|
887
|
+
session_id = session
|
|
888
|
+
output.print_info(f"Resuming session: {session_id}")
|
|
889
|
+
else:
|
|
890
|
+
output.print_error(f"Session not found: {session}")
|
|
891
|
+
raise typer.Exit(1)
|
|
892
|
+
|
|
893
|
+
# Handle forking
|
|
894
|
+
if fork:
|
|
895
|
+
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
|
|
896
|
+
from ..utils.project import get_project_sessions_dir
|
|
897
|
+
|
|
898
|
+
# Create hierarchical store for forking
|
|
899
|
+
hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
|
|
900
|
+
forked_session_id = hierarchical_store.fork_session(session_id)
|
|
901
|
+
session_id = forked_session_id
|
|
902
|
+
output.print_info(f"Forked session {session} -> {forked_session_id}")
|
|
903
|
+
|
|
904
|
+
# Enable auto-save if not disabled
|
|
905
|
+
if not no_save:
|
|
906
|
+
import uuid
|
|
907
|
+
auto_save_name = session_id or "session-" + str(uuid.uuid4())[:8]
|
|
908
|
+
|
|
909
|
+
# Create args-like object for session configuration
|
|
910
|
+
if session_id or auto_save_name:
|
|
911
|
+
class Args:
|
|
912
|
+
pass
|
|
913
|
+
|
|
914
|
+
args = Args()
|
|
915
|
+
args.auto_save = auto_save_name
|
|
916
|
+
args.resume_session = session_id
|
|
917
|
+
args.cli_project_sessions = bool(session_id or auto_save_name)
|
|
918
|
+
|
|
919
|
+
praison.args = args
|
|
920
|
+
|
|
921
|
+
# Run
|
|
922
|
+
result = praison.run()
|
|
923
|
+
|
|
924
|
+
_record_session_usage(session_id or auto_save_name, model, output)
|
|
925
|
+
output.emit_result(
|
|
926
|
+
message="Run completed",
|
|
927
|
+
data={"result": str(result) if result else None}
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
if result:
|
|
931
|
+
if not output.is_json_mode:
|
|
932
|
+
output.print_success("Run completed")
|
|
933
|
+
|
|
934
|
+
except Exception as e:
|
|
935
|
+
output.emit_error(message=str(e))
|
|
936
|
+
output.print_error(str(e))
|
|
937
|
+
raise typer.Exit(1)
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def _run_prompt(
|
|
941
|
+
prompt: str,
|
|
942
|
+
model: Optional[str] = None,
|
|
943
|
+
verbose: bool = False,
|
|
944
|
+
stream: bool = True,
|
|
945
|
+
trace: bool = False,
|
|
946
|
+
memory: bool = False,
|
|
947
|
+
tools: Optional[str] = None,
|
|
948
|
+
toolset: Optional[str] = None,
|
|
949
|
+
max_tokens: int = 16000,
|
|
950
|
+
output_mode: Optional[str] = None,
|
|
951
|
+
approval: Optional[str] = None,
|
|
952
|
+
approve_all_tools: bool = False,
|
|
953
|
+
approval_timeout: Optional[str] = None,
|
|
954
|
+
no_rules: bool = False,
|
|
955
|
+
permissions_config: Optional[dict] = None,
|
|
956
|
+
mcp: Optional[str] = None,
|
|
957
|
+
mcp_env: Optional[str] = None,
|
|
958
|
+
mcp_servers: Optional[List[dict]] = None,
|
|
959
|
+
continue_session: bool = False,
|
|
960
|
+
session: Optional[str] = None,
|
|
961
|
+
fork: bool = False,
|
|
962
|
+
no_save: bool = False,
|
|
963
|
+
attach_session: Optional[str] = None,
|
|
964
|
+
thinking_budget: Optional[int] = None,
|
|
965
|
+
):
|
|
966
|
+
"""Run a direct prompt."""
|
|
967
|
+
output = get_output_controller()
|
|
968
|
+
|
|
969
|
+
# Note: Credential check already done in run_main() entry point
|
|
970
|
+
|
|
971
|
+
try:
|
|
972
|
+
# Handle session continuity first (before any execution mode)
|
|
973
|
+
from praisonai.cli.main import PraisonAI
|
|
974
|
+
|
|
975
|
+
praison = PraisonAI()
|
|
976
|
+
|
|
977
|
+
if model:
|
|
978
|
+
praison.config_list[0]['model'] = model
|
|
979
|
+
|
|
980
|
+
# Handle session continuity
|
|
981
|
+
session_id = None
|
|
982
|
+
auto_save_name = None
|
|
983
|
+
|
|
984
|
+
if continue_session or session or fork:
|
|
985
|
+
from ..state.project_sessions import find_last_session, session_exists_anywhere
|
|
986
|
+
|
|
987
|
+
if continue_session:
|
|
988
|
+
# Find last session for this project
|
|
989
|
+
session_id = find_last_session()
|
|
990
|
+
if session_id:
|
|
991
|
+
output.print_info(f"Continuing session: {session_id}")
|
|
992
|
+
else:
|
|
993
|
+
output.print_warning("No previous sessions found. Starting new session.")
|
|
994
|
+
|
|
995
|
+
elif session:
|
|
996
|
+
# Use specific session
|
|
997
|
+
if session_exists_anywhere(session):
|
|
998
|
+
session_id = session
|
|
999
|
+
output.print_info(f"Resuming session: {session_id}")
|
|
1000
|
+
else:
|
|
1001
|
+
output.print_error(f"Session not found: {session}")
|
|
1002
|
+
raise typer.Exit(1)
|
|
1003
|
+
|
|
1004
|
+
# Handle forking
|
|
1005
|
+
if fork:
|
|
1006
|
+
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
|
|
1007
|
+
from ..utils.project import get_project_sessions_dir
|
|
1008
|
+
|
|
1009
|
+
# Create hierarchical store for forking
|
|
1010
|
+
hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
|
|
1011
|
+
forked_session_id = hierarchical_store.fork_session(session_id)
|
|
1012
|
+
session_id = forked_session_id
|
|
1013
|
+
output.print_info(f"Forked session {session} -> {forked_session_id}")
|
|
1014
|
+
|
|
1015
|
+
# Enable auto-save if not disabled (for all runs, not just session continuity)
|
|
1016
|
+
if not no_save:
|
|
1017
|
+
import uuid
|
|
1018
|
+
auto_save_name = session_id or "session-" + str(uuid.uuid4())[:8]
|
|
1019
|
+
|
|
1020
|
+
# Detect-and-attach: if a warm runtime is running, forward the plain
|
|
1021
|
+
# prompt to it (skipping per-invocation cold-start) and fall back to
|
|
1022
|
+
# in-process execution otherwise. Only the simple text path attaches;
|
|
1023
|
+
# per-invocation tool/approval/memory overrides stay in-process so their
|
|
1024
|
+
# behaviour is preserved exactly.
|
|
1025
|
+
# Session continuity/forking is handled in-process; the warm runtime does
|
|
1026
|
+
# not carry session state, so any explicit session flag stays local.
|
|
1027
|
+
# Default auto-save also stays in-process until the warm path can persist
|
|
1028
|
+
# sessions the same way as the normal run path.
|
|
1029
|
+
# An explicit --thinking budget is a per-invocation override (like tools/
|
|
1030
|
+
# approval/memory), so it stays in-process: the warm runtime reuses a
|
|
1031
|
+
# cached agent and does not carry a per-call thinking budget, so attaching
|
|
1032
|
+
# would silently drop the requested setting.
|
|
1033
|
+
runtime_eligible = no_save and thinking_budget is None and not any([
|
|
1034
|
+
mcp, mcp_servers, tools, toolset, approval, approve_all_tools,
|
|
1035
|
+
memory, permissions_config, continue_session, session, fork,
|
|
1036
|
+
])
|
|
1037
|
+
# When --attach <id> is given, tag the warm-runtime run with that id so
|
|
1038
|
+
# other terminals (`praisonai attach <id>`) observe its live events.
|
|
1039
|
+
runtime_session_id = attach_session or session_id
|
|
1040
|
+
if runtime_eligible and _try_attach_runtime(
|
|
1041
|
+
prompt,
|
|
1042
|
+
model=model,
|
|
1043
|
+
output_mode=output_mode,
|
|
1044
|
+
session_id=runtime_session_id,
|
|
1045
|
+
):
|
|
1046
|
+
return
|
|
1047
|
+
|
|
1048
|
+
if output_mode == "actions":
|
|
1049
|
+
from praisonaiagents import Agent
|
|
1050
|
+
from ..state.project_sessions import build_cli_memory_config, apply_cli_session_continuity
|
|
1051
|
+
|
|
1052
|
+
agent_config = {
|
|
1053
|
+
"name": "RunAgent",
|
|
1054
|
+
"role": "Assistant",
|
|
1055
|
+
"goal": "Complete the task",
|
|
1056
|
+
"output": "actions", # Use actions preset
|
|
1057
|
+
}
|
|
1058
|
+
if model:
|
|
1059
|
+
agent_config["llm"] = model
|
|
1060
|
+
|
|
1061
|
+
# Resolve approval backend if specified
|
|
1062
|
+
if approval:
|
|
1063
|
+
from praisonai.cli.features.approval import resolve_approval_config
|
|
1064
|
+
agent_config["approval"] = resolve_approval_config(
|
|
1065
|
+
approval, all_tools=approve_all_tools, timeout=approval_timeout,
|
|
1066
|
+
permissions_config=permissions_config,
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
# Add session support to Agent if needed
|
|
1070
|
+
from ..utils.project import build_cli_memory_config, apply_cli_session_continuity
|
|
1071
|
+
memory_cfg = build_cli_memory_config(session_id=session_id, auto_save=auto_save_name)
|
|
1072
|
+
if memory_cfg is not None:
|
|
1073
|
+
agent_config["memory"] = memory_cfg
|
|
1074
|
+
|
|
1075
|
+
# Wire all configured MCP servers (ad-hoc --mcp + config local/remote).
|
|
1076
|
+
if mcp or mcp_servers:
|
|
1077
|
+
mcp_tools = _build_mcp_tools(mcp, mcp_env, mcp_servers, verbose=verbose)
|
|
1078
|
+
if mcp_tools:
|
|
1079
|
+
agent_config["tools"] = list(agent_config.get("tools", [])) + mcp_tools
|
|
1080
|
+
|
|
1081
|
+
agent = Agent(**agent_config)
|
|
1082
|
+
# Reasoning effort applied via the property setter (not a
|
|
1083
|
+
# constructor kwarg) so defaults are unchanged when omitted.
|
|
1084
|
+
if thinking_budget is not None:
|
|
1085
|
+
agent.thinking_budget = thinking_budget
|
|
1086
|
+
if session_id or auto_save_name:
|
|
1087
|
+
apply_cli_session_continuity(agent, session_id or auto_save_name, auto_save=auto_save_name)
|
|
1088
|
+
|
|
1089
|
+
# Bridge per-step agent events into the structured output stream
|
|
1090
|
+
# so `--output stream-json` surfaces tool/text events, not just
|
|
1091
|
+
# start/result. No-op in non-JSON modes.
|
|
1092
|
+
from ..output.event_bridge import attach_bridge, detach_bridge
|
|
1093
|
+
bridge = attach_bridge(agent, output)
|
|
1094
|
+
if bridge is not None:
|
|
1095
|
+
bridge.emit_agent_message(agent_config.get("name"))
|
|
1096
|
+
try:
|
|
1097
|
+
result = agent.start(prompt)
|
|
1098
|
+
finally:
|
|
1099
|
+
detach_bridge(agent, bridge)
|
|
1100
|
+
|
|
1101
|
+
if bridge is not None:
|
|
1102
|
+
bridge.emit_run_result(result, ok=True)
|
|
1103
|
+
_record_session_usage(session_id or auto_save_name, model, output)
|
|
1104
|
+
output.emit_result(
|
|
1105
|
+
message="Prompt completed",
|
|
1106
|
+
data={"result": str(result) if result else None}
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
# Don't print result again - actions mode already shows output
|
|
1110
|
+
return
|
|
1111
|
+
|
|
1112
|
+
# Use handle_direct_prompt for other modes
|
|
1113
|
+
|
|
1114
|
+
# Create args-like object for handle_direct_prompt
|
|
1115
|
+
class Args:
|
|
1116
|
+
pass
|
|
1117
|
+
|
|
1118
|
+
args = Args()
|
|
1119
|
+
args.llm = model
|
|
1120
|
+
args.verbose = verbose
|
|
1121
|
+
args.memory = memory
|
|
1122
|
+
args.tools = tools
|
|
1123
|
+
args.toolset = toolset
|
|
1124
|
+
args.max_tokens = max_tokens
|
|
1125
|
+
args.web_search = False
|
|
1126
|
+
args.web_fetch = False
|
|
1127
|
+
args.prompt_caching = False
|
|
1128
|
+
args.planning = False
|
|
1129
|
+
args.planning_tools = None
|
|
1130
|
+
args.planning_reasoning = False
|
|
1131
|
+
args.auto_approve_plan = False
|
|
1132
|
+
args.final_agent = None
|
|
1133
|
+
args.user_id = None
|
|
1134
|
+
# Enable session features based on flags
|
|
1135
|
+
args.auto_save = auto_save_name
|
|
1136
|
+
args.history = None
|
|
1137
|
+
args.resume_session = session_id
|
|
1138
|
+
args.cli_project_sessions = bool(session_id or auto_save_name)
|
|
1139
|
+
args.include_rules = None if no_rules else "auto"
|
|
1140
|
+
args.no_rules = no_rules
|
|
1141
|
+
args.workflow = None
|
|
1142
|
+
args.workflow_var = None
|
|
1143
|
+
args.claude_memory = False
|
|
1144
|
+
args.guardrail = None
|
|
1145
|
+
args.metrics = False
|
|
1146
|
+
args.image = None
|
|
1147
|
+
args.image_generate = False
|
|
1148
|
+
args.telemetry = False
|
|
1149
|
+
args.mcp = mcp
|
|
1150
|
+
args.mcp_env = mcp_env
|
|
1151
|
+
args.mcp_servers = mcp_servers or None
|
|
1152
|
+
args.fast_context = None
|
|
1153
|
+
args.handoff = None
|
|
1154
|
+
args.auto_memory = False
|
|
1155
|
+
args.todo = False
|
|
1156
|
+
args.router = False
|
|
1157
|
+
args.router_provider = None
|
|
1158
|
+
args.query_rewrite = False
|
|
1159
|
+
args.rewrite_tools = None
|
|
1160
|
+
args.expand_prompt = False
|
|
1161
|
+
args.expand_tools = None
|
|
1162
|
+
args.no_tools = False
|
|
1163
|
+
args.approval = approval
|
|
1164
|
+
args.thinking_budget = thinking_budget
|
|
1165
|
+
|
|
1166
|
+
praison.args = args
|
|
1167
|
+
|
|
1168
|
+
result = praison.handle_direct_prompt(prompt)
|
|
1169
|
+
|
|
1170
|
+
_record_session_usage(session_id or auto_save_name, model, output)
|
|
1171
|
+
output.emit_result(
|
|
1172
|
+
message="Prompt completed",
|
|
1173
|
+
data={"result": str(result) if result else None}
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
if result and not output.is_json_mode:
|
|
1177
|
+
print(result)
|
|
1178
|
+
|
|
1179
|
+
except typer.Exit:
|
|
1180
|
+
raise
|
|
1181
|
+
except Exception as e:
|
|
1182
|
+
from ..output.event_bridge import StreamEventBridge
|
|
1183
|
+
StreamEventBridge(output).emit_run_error(str(e))
|
|
1184
|
+
output.emit_error(message=str(e))
|
|
1185
|
+
output.print_error(str(e))
|
|
1186
|
+
raise typer.Exit(1)
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
def _record_session_usage(session_id, model, output) -> None:
|
|
1190
|
+
"""Accumulate this run's token/cost usage into the active session and show
|
|
1191
|
+
a compact running total footer (Issue #2421).
|
|
1192
|
+
|
|
1193
|
+
Best-effort: never let usage accounting break a completed run. Stays quiet
|
|
1194
|
+
in JSON mode so machine-readable output is unaffected.
|
|
1195
|
+
"""
|
|
1196
|
+
if not session_id:
|
|
1197
|
+
return
|
|
1198
|
+
try:
|
|
1199
|
+
from ..state.project_sessions import (
|
|
1200
|
+
accumulate_session_usage,
|
|
1201
|
+
format_usage_footer,
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
usage = accumulate_session_usage(session_id, model=model)
|
|
1205
|
+
except Exception:
|
|
1206
|
+
return
|
|
1207
|
+
|
|
1208
|
+
if not usage or not usage.get("total_tokens"):
|
|
1209
|
+
return
|
|
1210
|
+
if output is not None and getattr(output, "is_json_mode", False):
|
|
1211
|
+
return
|
|
1212
|
+
try:
|
|
1213
|
+
footer = format_usage_footer(usage)
|
|
1214
|
+
if output is not None:
|
|
1215
|
+
output.print_info(footer)
|
|
1216
|
+
else:
|
|
1217
|
+
typer.echo(footer)
|
|
1218
|
+
except Exception:
|
|
1219
|
+
pass
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
def _run_from_file_profiled(
|
|
1223
|
+
file_path: str,
|
|
1224
|
+
model: Optional[str] = None,
|
|
1225
|
+
framework: Optional[str] = None,
|
|
1226
|
+
verbose: bool = False,
|
|
1227
|
+
profile_deep: bool = False,
|
|
1228
|
+
continue_session: bool = False,
|
|
1229
|
+
session: Optional[str] = None,
|
|
1230
|
+
fork: bool = False,
|
|
1231
|
+
no_save: bool = False,
|
|
1232
|
+
):
|
|
1233
|
+
"""Run agents from a YAML file with profiling enabled."""
|
|
1234
|
+
from praisonai.cli.features.cli_profiler import (
|
|
1235
|
+
CLIProfileConfig,
|
|
1236
|
+
CLIProfiler,
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
config = CLIProfileConfig(enabled=True, deep=profile_deep)
|
|
1240
|
+
profiler = CLIProfiler(config)
|
|
1241
|
+
|
|
1242
|
+
if profile_deep:
|
|
1243
|
+
typer.echo("⚠️ Deep profiling enabled - this adds significant overhead", err=True)
|
|
1244
|
+
|
|
1245
|
+
profiler.start()
|
|
1246
|
+
|
|
1247
|
+
# Import phase
|
|
1248
|
+
profiler.mark_import_start()
|
|
1249
|
+
try:
|
|
1250
|
+
from praisonai.cli.main import PraisonAI
|
|
1251
|
+
except ImportError as e:
|
|
1252
|
+
typer.echo(f"Error: {e}", err=True)
|
|
1253
|
+
raise typer.Exit(1)
|
|
1254
|
+
profiler.mark_import_end()
|
|
1255
|
+
|
|
1256
|
+
# Agent initialization phase
|
|
1257
|
+
profiler.mark_init_start()
|
|
1258
|
+
praison = PraisonAI(
|
|
1259
|
+
agent_file=file_path,
|
|
1260
|
+
framework=framework or "praisonai",
|
|
1261
|
+
)
|
|
1262
|
+
if model:
|
|
1263
|
+
praison.config_list[0]['model'] = model
|
|
1264
|
+
|
|
1265
|
+
# Apply session continuity if requested
|
|
1266
|
+
session_id = None
|
|
1267
|
+
auto_save_name = None
|
|
1268
|
+
|
|
1269
|
+
if continue_session or session or fork:
|
|
1270
|
+
from ..state.project_sessions import find_last_session, session_exists_anywhere
|
|
1271
|
+
|
|
1272
|
+
if continue_session:
|
|
1273
|
+
session_id = find_last_session()
|
|
1274
|
+
if not session_id:
|
|
1275
|
+
typer.echo("Warning: No previous sessions found. Starting new session.", err=True)
|
|
1276
|
+
elif session:
|
|
1277
|
+
if session_exists_anywhere(session):
|
|
1278
|
+
session_id = session
|
|
1279
|
+
|
|
1280
|
+
if fork:
|
|
1281
|
+
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
|
|
1282
|
+
from ..utils.project import get_project_sessions_dir
|
|
1283
|
+
|
|
1284
|
+
hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
|
|
1285
|
+
forked_session_id = hierarchical_store.fork_session(session_id)
|
|
1286
|
+
session_id = forked_session_id
|
|
1287
|
+
else:
|
|
1288
|
+
typer.echo(f"Error: Session not found: {session}", err=True)
|
|
1289
|
+
raise typer.Exit(1)
|
|
1290
|
+
|
|
1291
|
+
if not no_save:
|
|
1292
|
+
import uuid
|
|
1293
|
+
auto_save_name = session_id or "session-" + str(uuid.uuid4())[:8]
|
|
1294
|
+
if session_id or auto_save_name:
|
|
1295
|
+
class Args:
|
|
1296
|
+
pass
|
|
1297
|
+
|
|
1298
|
+
args = Args()
|
|
1299
|
+
args.auto_save = auto_save_name
|
|
1300
|
+
args.resume_session = session_id
|
|
1301
|
+
args.cli_project_sessions = bool(session_id or auto_save_name)
|
|
1302
|
+
|
|
1303
|
+
praison.args = args
|
|
1304
|
+
|
|
1305
|
+
profiler.mark_init_end()
|
|
1306
|
+
|
|
1307
|
+
# Execution phase
|
|
1308
|
+
profiler.mark_exec_start()
|
|
1309
|
+
result = praison.run()
|
|
1310
|
+
profiler.mark_exec_end()
|
|
1311
|
+
|
|
1312
|
+
_record_session_usage(session_id or auto_save_name, model, None)
|
|
1313
|
+
|
|
1314
|
+
profiler.stop()
|
|
1315
|
+
|
|
1316
|
+
# Print result
|
|
1317
|
+
if result:
|
|
1318
|
+
print(result)
|
|
1319
|
+
|
|
1320
|
+
# Print profiling report
|
|
1321
|
+
profiler.print_report()
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
def _run_custom_agent(
|
|
1325
|
+
agent_config: Dict[str, Any],
|
|
1326
|
+
prompt: str,
|
|
1327
|
+
model: Optional[str] = None,
|
|
1328
|
+
verbose: bool = False,
|
|
1329
|
+
stream: bool = True,
|
|
1330
|
+
trace: bool = False,
|
|
1331
|
+
memory: bool = False,
|
|
1332
|
+
tools: Optional[str] = None,
|
|
1333
|
+
toolset: Optional[str] = None,
|
|
1334
|
+
max_tokens: int = 16000,
|
|
1335
|
+
output_mode: Optional[str] = None,
|
|
1336
|
+
approval: Optional[str] = None,
|
|
1337
|
+
approve_all_tools: bool = False,
|
|
1338
|
+
approval_timeout: Optional[str] = None,
|
|
1339
|
+
invocation_permissions: Optional[dict] = None,
|
|
1340
|
+
continue_session: bool = False,
|
|
1341
|
+
session: Optional[str] = None,
|
|
1342
|
+
fork: bool = False,
|
|
1343
|
+
no_save: bool = False,
|
|
1344
|
+
thinking_budget: Optional[int] = None,
|
|
1345
|
+
):
|
|
1346
|
+
"""Run a custom agent definition."""
|
|
1347
|
+
output = get_output_controller()
|
|
1348
|
+
|
|
1349
|
+
try:
|
|
1350
|
+
from praisonaiagents import Agent
|
|
1351
|
+
|
|
1352
|
+
# Override model if specified
|
|
1353
|
+
if model:
|
|
1354
|
+
agent_config["llm"] = model
|
|
1355
|
+
|
|
1356
|
+
# Add verbose flag
|
|
1357
|
+
if verbose:
|
|
1358
|
+
agent_config["verbose"] = verbose
|
|
1359
|
+
|
|
1360
|
+
# Resolve per-agent permissions (from definition) layered with
|
|
1361
|
+
# invocation flags. Precedence: invocation flags > agent definition.
|
|
1362
|
+
agent_permissions = agent_config.pop("permissions", None) or {}
|
|
1363
|
+
merged_permissions = dict(agent_permissions)
|
|
1364
|
+
if invocation_permissions:
|
|
1365
|
+
merged_permissions.update(invocation_permissions)
|
|
1366
|
+
|
|
1367
|
+
if merged_permissions:
|
|
1368
|
+
from praisonai.cli.features.approval import resolve_approval_config
|
|
1369
|
+
# Preserve promptable `ask` rules: only fall back to non-interactive
|
|
1370
|
+
# when there is no interactive approval path. An explicit --approval
|
|
1371
|
+
# flag or any `ask` rule keeps the backend interactive so the user
|
|
1372
|
+
# can be prompted (e.g. the `review` preset's "ask before shell").
|
|
1373
|
+
has_ask_rules = any(
|
|
1374
|
+
str(action).strip().lower() == "ask"
|
|
1375
|
+
for action in merged_permissions.values()
|
|
1376
|
+
)
|
|
1377
|
+
# Default to a console backend so deny/ask rules are enforced even
|
|
1378
|
+
# when no explicit --approval flag is passed.
|
|
1379
|
+
agent_config["approval"] = resolve_approval_config(
|
|
1380
|
+
approval or "console",
|
|
1381
|
+
all_tools=approve_all_tools,
|
|
1382
|
+
timeout=approval_timeout,
|
|
1383
|
+
non_interactive=approval is None and not has_ask_rules,
|
|
1384
|
+
permissions_config=merged_permissions,
|
|
1385
|
+
)
|
|
1386
|
+
elif approval:
|
|
1387
|
+
from praisonai.cli.features.approval import resolve_approval_config
|
|
1388
|
+
agent_config["approval"] = resolve_approval_config(
|
|
1389
|
+
approval,
|
|
1390
|
+
all_tools=approve_all_tools,
|
|
1391
|
+
timeout=approval_timeout,
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
# Handle session continuity
|
|
1395
|
+
session_id = None
|
|
1396
|
+
auto_save_name = None
|
|
1397
|
+
|
|
1398
|
+
if continue_session or session or fork:
|
|
1399
|
+
from ..state.project_sessions import find_last_session, session_exists_anywhere
|
|
1400
|
+
|
|
1401
|
+
if continue_session:
|
|
1402
|
+
session_id = find_last_session()
|
|
1403
|
+
if session_id:
|
|
1404
|
+
output.print_info(f"Continuing session: {session_id}")
|
|
1405
|
+
else:
|
|
1406
|
+
output.print_warning("No previous sessions found. Starting new session.")
|
|
1407
|
+
|
|
1408
|
+
elif session:
|
|
1409
|
+
if session_exists_anywhere(session):
|
|
1410
|
+
session_id = session
|
|
1411
|
+
output.print_info(f"Resuming session: {session_id}")
|
|
1412
|
+
else:
|
|
1413
|
+
output.print_error(f"Session not found: {session}")
|
|
1414
|
+
raise typer.Exit(1)
|
|
1415
|
+
|
|
1416
|
+
if fork:
|
|
1417
|
+
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
|
|
1418
|
+
from ..utils.project import get_project_sessions_dir
|
|
1419
|
+
|
|
1420
|
+
hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
|
|
1421
|
+
forked_session_id = hierarchical_store.fork_session(session_id)
|
|
1422
|
+
session_id = forked_session_id
|
|
1423
|
+
output.print_info(f"Forked session {session} -> {forked_session_id}")
|
|
1424
|
+
|
|
1425
|
+
if not no_save:
|
|
1426
|
+
import uuid
|
|
1427
|
+
auto_save_name = session_id or "session-" + str(uuid.uuid4())[:8]
|
|
1428
|
+
|
|
1429
|
+
# Add session support to agent config
|
|
1430
|
+
if session_id:
|
|
1431
|
+
agent_config["resume_session"] = session_id
|
|
1432
|
+
if auto_save_name:
|
|
1433
|
+
agent_config["auto_save"] = auto_save_name
|
|
1434
|
+
|
|
1435
|
+
# Create and run agent
|
|
1436
|
+
agent = Agent(**agent_config)
|
|
1437
|
+
# Reasoning effort applied via the property setter (not a constructor
|
|
1438
|
+
# kwarg) so defaults are unchanged when --thinking is omitted.
|
|
1439
|
+
if thinking_budget is not None:
|
|
1440
|
+
agent.thinking_budget = thinking_budget
|
|
1441
|
+
|
|
1442
|
+
# Bridge per-step agent events into the structured output stream.
|
|
1443
|
+
from ..output.event_bridge import attach_bridge, detach_bridge
|
|
1444
|
+
bridge = attach_bridge(agent, output)
|
|
1445
|
+
if bridge is not None:
|
|
1446
|
+
bridge.emit_agent_message(agent_config.get("name"))
|
|
1447
|
+
try:
|
|
1448
|
+
result = agent.start(prompt)
|
|
1449
|
+
finally:
|
|
1450
|
+
detach_bridge(agent, bridge)
|
|
1451
|
+
|
|
1452
|
+
if bridge is not None:
|
|
1453
|
+
bridge.emit_run_result(result, ok=True)
|
|
1454
|
+
_record_session_usage(session_id or auto_save_name, model, output)
|
|
1455
|
+
output.emit_result(
|
|
1456
|
+
message="Agent completed",
|
|
1457
|
+
data={"result": str(result) if result else None}
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
if result and not output.is_json_mode:
|
|
1461
|
+
print(result)
|
|
1462
|
+
|
|
1463
|
+
except typer.Exit:
|
|
1464
|
+
raise
|
|
1465
|
+
except Exception as e:
|
|
1466
|
+
from ..output.event_bridge import StreamEventBridge
|
|
1467
|
+
StreamEventBridge(output).emit_run_error(str(e))
|
|
1468
|
+
output.emit_error(message=str(e))
|
|
1469
|
+
output.print_error(str(e))
|
|
1470
|
+
raise typer.Exit(1)
|
|
1471
|
+
|
|
1472
|
+
|
|
1473
|
+
def _run_prompt_profiled(
|
|
1474
|
+
prompt: str,
|
|
1475
|
+
model: Optional[str] = None,
|
|
1476
|
+
verbose: bool = False,
|
|
1477
|
+
profile_deep: bool = False,
|
|
1478
|
+
continue_session: bool = False,
|
|
1479
|
+
session: Optional[str] = None,
|
|
1480
|
+
fork: bool = False,
|
|
1481
|
+
no_save: bool = False,
|
|
1482
|
+
):
|
|
1483
|
+
"""Run a direct prompt with profiling enabled."""
|
|
1484
|
+
from praisonai.cli.features.cli_profiler import (
|
|
1485
|
+
CLIProfileConfig,
|
|
1486
|
+
CLIProfiler,
|
|
1487
|
+
)
|
|
1488
|
+
|
|
1489
|
+
config = CLIProfileConfig(enabled=True, deep=profile_deep)
|
|
1490
|
+
profiler = CLIProfiler(config)
|
|
1491
|
+
|
|
1492
|
+
if profile_deep:
|
|
1493
|
+
typer.echo("⚠️ Deep profiling enabled - this adds significant overhead", err=True)
|
|
1494
|
+
|
|
1495
|
+
profiler.start()
|
|
1496
|
+
|
|
1497
|
+
# Import phase
|
|
1498
|
+
profiler.mark_import_start()
|
|
1499
|
+
try:
|
|
1500
|
+
from praisonaiagents import Agent
|
|
1501
|
+
except ImportError:
|
|
1502
|
+
typer.echo("Error: praisonaiagents not installed", err=True)
|
|
1503
|
+
raise typer.Exit(1)
|
|
1504
|
+
profiler.mark_import_end()
|
|
1505
|
+
|
|
1506
|
+
# Agent initialization phase
|
|
1507
|
+
profiler.mark_init_start()
|
|
1508
|
+
agent_config = {
|
|
1509
|
+
"name": "RunAgent",
|
|
1510
|
+
"role": "Assistant",
|
|
1511
|
+
"goal": "Complete the task",
|
|
1512
|
+
"verbose": verbose,
|
|
1513
|
+
}
|
|
1514
|
+
if model:
|
|
1515
|
+
agent_config["llm"] = model
|
|
1516
|
+
|
|
1517
|
+
# Apply session continuity if requested
|
|
1518
|
+
session_id = None
|
|
1519
|
+
auto_save_name = None
|
|
1520
|
+
|
|
1521
|
+
if continue_session or session or fork:
|
|
1522
|
+
from ..state.project_sessions import find_last_session, session_exists_anywhere
|
|
1523
|
+
|
|
1524
|
+
if continue_session:
|
|
1525
|
+
session_id = find_last_session()
|
|
1526
|
+
if not session_id:
|
|
1527
|
+
typer.echo("Warning: No previous sessions found. Starting new session.", err=True)
|
|
1528
|
+
elif session:
|
|
1529
|
+
if session_exists_anywhere(session):
|
|
1530
|
+
session_id = session
|
|
1531
|
+
|
|
1532
|
+
if fork:
|
|
1533
|
+
from praisonaiagents.session.hierarchy import HierarchicalSessionStore
|
|
1534
|
+
from ..utils.project import get_project_sessions_dir
|
|
1535
|
+
|
|
1536
|
+
hierarchical_store = HierarchicalSessionStore(str(get_project_sessions_dir()))
|
|
1537
|
+
forked_session_id = hierarchical_store.fork_session(session_id)
|
|
1538
|
+
session_id = forked_session_id
|
|
1539
|
+
else:
|
|
1540
|
+
typer.echo(f"Error: Session not found: {session}", err=True)
|
|
1541
|
+
raise typer.Exit(1)
|
|
1542
|
+
|
|
1543
|
+
if not no_save:
|
|
1544
|
+
import uuid
|
|
1545
|
+
auto_save_name = session_id or "session-" + str(uuid.uuid4())[:8]
|
|
1546
|
+
if session_id or auto_save_name:
|
|
1547
|
+
from ..state.project_sessions import build_cli_memory_config, apply_cli_session_continuity
|
|
1548
|
+
|
|
1549
|
+
memory_cfg = build_cli_memory_config(session_id, auto_save_name)
|
|
1550
|
+
if memory_cfg is not None:
|
|
1551
|
+
agent_config["memory"] = memory_cfg
|
|
1552
|
+
|
|
1553
|
+
agent = Agent(**agent_config)
|
|
1554
|
+
if session_id or auto_save_name:
|
|
1555
|
+
apply_cli_session_continuity(agent, session_id or auto_save_name, auto_save=auto_save_name)
|
|
1556
|
+
|
|
1557
|
+
profiler.mark_init_end()
|
|
1558
|
+
|
|
1559
|
+
# Execution phase
|
|
1560
|
+
profiler.mark_exec_start()
|
|
1561
|
+
response = agent.start(prompt)
|
|
1562
|
+
profiler.mark_exec_end()
|
|
1563
|
+
|
|
1564
|
+
_record_session_usage(session_id or auto_save_name, model, None)
|
|
1565
|
+
|
|
1566
|
+
profiler.stop()
|
|
1567
|
+
|
|
1568
|
+
# Print response
|
|
1569
|
+
if response:
|
|
1570
|
+
print(response)
|
|
1571
|
+
|
|
1572
|
+
# Print profiling report
|
|
1573
|
+
profiler.print_report()
|