Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 12 additions & 43 deletions src/praisonai/praisonai/cli/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,20 @@ def _run_prompt(
approval, all_tools=approve_all_tools, timeout=approval_timeout,
)

# Add session support to Agent if needed
if session_id:
agent_config["resume_session"] = session_id
if auto_save_name:
agent_config["auto_save"] = auto_save_name
# Session continuity via MemoryConfig (Agent has no resume_session param)
continuity_id = session_id or auto_save_name
if continuity_id:
from praisonaiagents import MemoryConfig
from ..state.project_sessions import apply_cli_session_continuity
agent_config["memory"] = MemoryConfig(
session_id=continuity_id,
auto_save=auto_save_name or continuity_id,
history=bool(session_id),
)

agent = Agent(**agent_config)
if continuity_id:
apply_cli_session_continuity(agent, continuity_id)
result = agent.start(prompt)

output.emit_result(
Expand Down Expand Up @@ -440,44 +447,6 @@ class Args:

praison.args = args

# If output_mode is "actions", use direct Agent with actions preset
if output_mode == "actions":
from praisonaiagents import Agent

agent_config = {
"name": "RunAgent",
"role": "Assistant",
"goal": "Complete the task",
"output": "actions", # Use actions preset
}
if model:
agent_config["llm"] = model

# Resolve approval backend if specified
if approval:
from praisonai.cli.features.approval import resolve_approval_config
agent_config["approval"] = resolve_approval_config(
approval, all_tools=approve_all_tools, timeout=approval_timeout,
)

# Add session support to Agent if needed
if session_id:
agent_config["resume_session"] = session_id
if auto_save_name:
agent_config["auto_save"] = auto_save_name

agent = Agent(**agent_config)
result = agent.start(prompt)

output.emit_result(
message="Prompt completed",
data={"result": str(result) if result else None}
)

# Don't print result again - actions mode already shows output
return

# Use handle_direct_prompt for other modes
result = praison.handle_direct_prompt(prompt)

output.emit_result(
Expand Down
36 changes: 36 additions & 0 deletions src/praisonai/praisonai/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4513,6 +4513,33 @@ def handle_direct_prompt(self, prompt):
from praisonaiagents import MemoryConfig
agent_config["memory"] = MemoryConfig(auto_save=self.args.auto_save)
print(f"[bold cyan]Auto-save enabled - session will be saved as '{self.args.auto_save}'[/bold cyan]")

resume_session_id = getattr(self.args, 'resume_session', None)
if resume_session_id:
from praisonaiagents import MemoryConfig
save_name = getattr(self.args, 'auto_save', None) or resume_session_id

# Preserve existing memory configuration fields
existing_memory = agent_config.get("memory")
if isinstance(existing_memory, MemoryConfig):
existing_memory.session_id = resume_session_id
existing_memory.auto_save = save_name
existing_memory.history = True
elif isinstance(existing_memory, dict):
# Convert dict to MemoryConfig while preserving fields
existing_memory.update({
"session_id": resume_session_id,
"auto_save": save_name,
"history": True,
})
agent_config["memory"] = MemoryConfig(**existing_memory)
else:
agent_config["memory"] = MemoryConfig(
session_id=resume_session_id,
auto_save=save_name,
history=True,
)
print(f"[bold cyan]Resuming session '{resume_session_id}'[/bold cyan]")

if getattr(self.args, 'history', None):
if agent_config.get("memory") is None:
Expand Down Expand Up @@ -4792,6 +4819,15 @@ def level_based_approve(function_name, arguments, risk_level):
flow.display_workflow_start("Direct Prompt", ["DirectAgent"])

agent = PraisonAgent(**agent_config)

continuity_id = None
if hasattr(self, 'args'):
continuity_id = getattr(self.args, 'resume_session', None) or getattr(
self.args, 'auto_save', None
)
if continuity_id:
from praisonai.cli.state.project_sessions import apply_cli_session_continuity
apply_cli_session_continuity(agent, continuity_id)

# AutoRag - Automatic RAG retrieval decision
if hasattr(self, 'args') and getattr(self.args, 'auto_rag', False):
Expand Down
24 changes: 23 additions & 1 deletion src/praisonai/praisonai/cli/state/project_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,26 @@ def find_last_session(project_path: Optional[str] = None) -> Optional[str]:
Session ID or None if no sessions exist
"""
store = get_project_session_store(project_path)
return store.get_last_session_id()
return store.get_last_session_id()


def apply_cli_session_continuity(agent, session_id: str, project_path: Optional[str] = None) -> None:
"""
Bind an agent to the project-scoped session store and restore prior turns.

CLI ``run --continue/--session`` discovers sessions in the project store but
agents default to the global store unless explicitly wired here.
"""
store = get_project_session_store(project_path)
agent._session_store = store
agent._session_id = session_id
if not getattr(agent, "auto_save", None):
agent.auto_save = session_id

history = store.get_chat_history(session_id)
if history and not agent.chat_history:
agent.chat_history = [
{"role": msg.get("role", "user"), "content": msg.get("content", "")}
for msg in history
]
agent._auto_save_last_index = len(agent.chat_history)
185 changes: 185 additions & 0 deletions src/praisonai/tests/unit/cli/test_run_session_continuity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""Regression tests for CLI run session continuity."""

import tempfile
from pathlib import Path
from types import SimpleNamespace

import pytest

from praisonaiagents import Agent, MemoryConfig


def test_agent_rejects_resume_session_kwarg():
"""Agent must not accept the invalid resume_session parameter."""
with pytest.raises(TypeError, match="resume_session"):
Agent(name="RunAgent", resume_session="abc123")


def test_apply_cli_session_continuity_restores_project_history():
"""Project-scoped history must be restored into the agent chat_history."""
from praisonai.cli.state.project_sessions import (
apply_cli_session_continuity,
get_project_session_store,
)

with tempfile.TemporaryDirectory() as tmp:
project_path = Path(tmp)
store = get_project_session_store(str(project_path))
session_id = "continuity-test-session"
store.add_user_message(session_id, "remember the code is 42")
store.add_assistant_message(session_id, "Got it, the code is 42.")

agent = Agent(name="RunAgent")

apply_cli_session_continuity(agent, session_id, project_path=str(project_path))

assert agent._session_store.session_dir == store.session_dir
assert agent._session_id == session_id
assert len(agent.chat_history) == 2
assert agent.chat_history[0]["content"] == "remember the code is 42"
assert agent._auto_save_last_index == 2


def test_handle_direct_prompt_wires_project_session(monkeypatch):
"""handle_direct_prompt must wire resume_session to the project store."""
from praisonai.cli.main import PraisonAI

captured = {}

class FakeAgent:
def __init__(self, **kwargs):
self.chat_history = []
self._session_store = None
self._session_id = None
self.auto_save = None
self._auto_save_last_index = 0
captured["agent_config"] = kwargs

def start(self, prompt):
captured["prompt"] = prompt
return "ok"

monkeypatch.setattr("praisonaiagents.Agent", FakeAgent)
monkeypatch.setattr(
"praisonai.cli.state.project_sessions.apply_cli_session_continuity",
lambda agent, session_id, project_path=None: captured.setdefault(
"continuity", session_id
),
)
monkeypatch.setattr(
"praisonai.cli.main.PraisonAI._execute_agent_with_budget_handling",
lambda self, agent, method, prompt: agent.start(prompt),
)
monkeypatch.setattr(
"praisonai.cli.main.PraisonAI._resolve_display_mode",
lambda self: "quiet",
)
monkeypatch.setattr(
"praisonai.cli.main.PraisonAI._rewrite_query_if_enabled",
lambda self, prompt: prompt,
)
monkeypatch.setattr(
"praisonai.cli.main.PraisonAI._expand_prompt_if_enabled",
lambda self, prompt: prompt,
)
monkeypatch.setattr("praisonai.cli.main.PRAISONAI_AVAILABLE", True)

praison = PraisonAI()
args = SimpleNamespace(
profile=False,
workflow=None,
no_rules=True,
verbose=0,
llm=None,
memory=False,
auto_save="saved-session",
resume_session="saved-session",
history=None,
tools=None,
toolset=None,
no_tools=True,
web_search=False,
web_fetch=False,
prompt_caching=False,
autonomy=None,
approval=None,
router=False,
metrics=False,
telemetry=False,
mcp=None,
auto_rag=False,
image=None,
image_generate=False,
cli_backend=None,
flow_display=False,
planning=False,
query_rewrite=False,
expand_prompt=False,
tool_timeout=None,
tool_retry_attempts=1,
rewrite_tools=None,
expand_tools=None,
planning_tools=None,
)
praison.args = args

result = praison.handle_direct_prompt("follow up question")

assert result == "ok"
memory = captured["agent_config"]["memory"]
assert isinstance(memory, MemoryConfig)
assert memory.session_id == "saved-session"
assert memory.history is True
assert captured["continuity"] == "saved-session"


def test_run_prompt_actions_mode_wires_session_continuity(monkeypatch):
"""Actions mode must wire resume_session to project session continuity."""
from praisonai.cli.commands.run import _run_prompt

captured = {}

class FakeAgent:
def __init__(self, **kwargs):
self.chat_history = []
self._session_store = None
self._session_id = None
self.auto_save = None
self._auto_save_last_index = 0
captured["agent_config"] = kwargs

def start(self, prompt):
captured["prompt"] = prompt
return "actions complete"

monkeypatch.setattr("praisonaiagents.Agent", FakeAgent)
monkeypatch.setattr(
"praisonai.cli.state.project_sessions.apply_cli_session_continuity",
lambda agent, session_id, project_path=None: captured.setdefault("continuity", session_id),
)

# Mock output controller
class MockOutput:
def emit_result(self, result, status="success", metadata=None):
captured["output_result"] = result

def print_info(self, msg):
pass

def print_warning(self, msg):
pass

monkeypatch.setattr("praisonai.cli.commands.run.get_output_controller", lambda: MockOutput())

result = _run_prompt(
"follow up question",
output_mode="actions",
session="saved-session",
)

memory = captured["agent_config"]["memory"]
assert isinstance(memory, MemoryConfig)
assert memory.session_id == "saved-session"
assert memory.history is True
assert captured["continuity"] == "saved-session"
assert captured["prompt"] == "follow up question"
Loading