diff --git a/src/praisonai-agents/praisonaiagents/agent/memory_mixin.py b/src/praisonai-agents/praisonaiagents/agent/memory_mixin.py index 02e32f89e..9626c8a4a 100644 --- a/src/praisonai-agents/praisonaiagents/agent/memory_mixin.py +++ b/src/praisonai-agents/praisonaiagents/agent/memory_mixin.py @@ -317,8 +317,9 @@ def _init_session_store(self): return try: - from ..session import get_default_session_store - self._session_store = get_default_session_store() + if self._session_store is None: + from ..session import get_default_session_store + self._session_store = get_default_session_store() # Restore chat history from previous session history = self._session_store.get_chat_history(self._session_id) diff --git a/src/praisonai/praisonai/cli/commands/run.py b/src/praisonai/praisonai/cli/commands/run.py index 1d0b7db12..776eaf3ca 100644 --- a/src/praisonai/praisonai/cli/commands/run.py +++ b/src/praisonai/praisonai/cli/commands/run.py @@ -256,6 +256,7 @@ class Args: args = Args() args.auto_save = auto_save_name args.resume_session = session_id + args.cli_project_sessions = bool(session_id or auto_save_name) praison.args = args @@ -349,10 +350,9 @@ def _run_prompt( if not no_save: import uuid auto_save_name = session_id or "session-" + str(uuid.uuid4())[:8] - - # If output_mode is "actions", use direct Agent with actions preset if output_mode == "actions": from praisonaiagents import Agent + from ..state.project_sessions import build_cli_memory_config, apply_cli_session_continuity agent_config = { "name": "RunAgent", @@ -370,13 +370,13 @@ 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 + memory_cfg = build_cli_memory_config(session_id, auto_save_name) + if memory_cfg is not None: + agent_config["memory"] = memory_cfg agent = Agent(**agent_config) + if session_id or auto_save_name: + apply_cli_session_continuity(agent, session_id or auto_save_name, auto_save=auto_save_name) result = agent.start(prompt) output.emit_result( @@ -413,6 +413,7 @@ class Args: args.auto_save = auto_save_name args.history = None args.resume_session = session_id + args.cli_project_sessions = bool(session_id or auto_save_name) args.include_rules = None if no_rules else "auto" args.no_rules = no_rules args.workflow = None @@ -440,44 +441,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( @@ -536,6 +499,22 @@ def _run_from_file_profiled( ) if model: praison.config_list[0]['model'] = model + + # Apply session continuity if requested + session_id, auto_save_name = resolve_session_params( + continue_session, session, fork, no_save + ) + if session_id or auto_save_name: + class Args: + pass + + args = Args() + args.auto_save = auto_save_name + args.resume_session = session_id + args.cli_project_sessions = bool(session_id or auto_save_name) + + praison.args = args + profiler.mark_init_end() # Execution phase @@ -597,7 +576,21 @@ def _run_prompt_profiled( if model: agent_config["llm"] = model + # Apply session continuity if requested + session_id, auto_save_name = resolve_session_params( + continue_session, session, fork, no_save + ) + if session_id or auto_save_name: + from ..state.project_sessions import build_cli_memory_config, apply_cli_session_continuity + + memory_cfg = build_cli_memory_config(session_id, auto_save_name) + if memory_cfg is not None: + agent_config["memory"] = memory_cfg + agent = Agent(**agent_config) + if session_id or auto_save_name: + apply_cli_session_continuity(agent, session_id or auto_save_name, auto_save=auto_save_name) + profiler.mark_init_end() # Execution phase diff --git a/src/praisonai/praisonai/cli/main.py b/src/praisonai/praisonai/cli/main.py index 370498d8b..86ceb9511 100644 --- a/src/praisonai/praisonai/cli/main.py +++ b/src/praisonai/praisonai/cli/main.py @@ -362,6 +362,7 @@ def main(self): original_agent_file = self.agent_file # Parse args - this returns both args and unknown_args + preserved_args = getattr(self, 'args', None) parse_result = self.parse_args() if isinstance(parse_result, tuple): args, unknown_args = parse_result @@ -369,6 +370,12 @@ def main(self): args = parse_result unknown_args = [] + # Preserve project session flags set by ``praison run`` before parse_args() + if preserved_args and getattr(preserved_args, 'cli_project_sessions', False): + for attr in ('auto_save', 'resume_session', 'cli_project_sessions'): + if hasattr(preserved_args, attr): + setattr(args, attr, getattr(preserved_args, attr)) + # Store args for use in handle_direct_prompt self.args = args invocation_cmd = "praisonai" @@ -4243,6 +4250,15 @@ def _extract_cli_config_for_yaml(self): handoff_detect_cycles = getattr(self.args, 'handoff_detect_cycles', None) if handoff_detect_cycles is not None: cli_config['handoff_detect_cycles'] = handoff_detect_cycles + + if getattr(self.args, 'cli_project_sessions', False): + from .state.project_sessions import build_cli_memory_config + memory_cfg = build_cli_memory_config( + getattr(self.args, 'resume_session', None), + getattr(self.args, 'auto_save', None), + ) + if memory_cfg is not None: + cli_config['memory'] = memory_cfg return cli_config @@ -4509,6 +4525,15 @@ def handle_direct_prompt(self, prompt): else: agent_config["memory"] = True print("[bold cyan]Memory enabled - agent will remember context across sessions[/bold cyan]") + elif getattr(self.args, 'cli_project_sessions', False) and ( + getattr(self.args, 'resume_session', None) or getattr(self.args, 'auto_save', None) + ): + from .state.project_sessions import build_cli_memory_config + agent_config["memory"] = build_cli_memory_config( + getattr(self.args, 'resume_session', None), + getattr(self.args, 'auto_save', None), + ) + print(f"[bold cyan]Project session enabled - session '{agent_config['memory'].auto_save}'[/bold cyan]") elif getattr(self.args, 'auto_save', None): from praisonaiagents import MemoryConfig agent_config["memory"] = MemoryConfig(auto_save=self.args.auto_save) @@ -4792,6 +4817,13 @@ def level_based_approve(function_name, arguments, risk_level): flow.display_workflow_start("Direct Prompt", ["DirectAgent"]) agent = PraisonAgent(**agent_config) + + if hasattr(self, 'args') and getattr(self.args, 'cli_project_sessions', False): + session_id = getattr(self.args, 'resume_session', None) or getattr(self.args, 'auto_save', None) + auto_save = getattr(self.args, 'auto_save', None) + if session_id: + from .state.project_sessions import apply_cli_session_continuity + apply_cli_session_continuity(agent, session_id, auto_save=auto_save) # AutoRag - Automatic RAG retrieval decision if hasattr(self, 'args') and getattr(self.args, 'auto_rag', False): diff --git a/src/praisonai/praisonai/cli/state/project_sessions.py b/src/praisonai/praisonai/cli/state/project_sessions.py index afdd0e370..2d20ba27d 100644 --- a/src/praisonai/praisonai/cli/state/project_sessions.py +++ b/src/praisonai/praisonai/cli/state/project_sessions.py @@ -98,4 +98,40 @@ 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() \ No newline at end of file + return store.get_last_session_id() + + +def build_cli_memory_config( + session_id: Optional[str] = None, + auto_save: Optional[str] = None, +): + """Build MemoryConfig for ``praison run`` project-scoped session continuity.""" + if not session_id and not auto_save: + return None + + from praisonaiagents import MemoryConfig + + sid = session_id or auto_save + return MemoryConfig(session_id=sid, auto_save=auto_save, history=True) + + +def apply_cli_session_continuity(agent, session_id: str, project_path: Optional[str] = None, auto_save: Optional[str] = None) -> None: + """Wire an agent to the project session store and restore prior history.""" + store = get_project_session_store(project_path) + agent._session_store = store + agent._session_id = session_id + agent._history_enabled = True + agent._history_session_id = session_id + if auto_save is not None: + agent.auto_save = auto_save + + history = store.get_chat_history(session_id) + if history and not agent.chat_history: + for msg in history: + agent.chat_history.append({ + "role": msg["role"], + "content": msg["content"], + }) + agent._auto_save_last_index = len(agent.chat_history) + + agent._session_store_initialized = True \ No newline at end of file diff --git a/src/praisonai/tests/unit/cli/test_project_session_continuity.py b/src/praisonai/tests/unit/cli/test_project_session_continuity.py new file mode 100644 index 000000000..dbd0d0340 --- /dev/null +++ b/src/praisonai/tests/unit/cli/test_project_session_continuity.py @@ -0,0 +1,57 @@ +"""Tests for praison run project-scoped session continuity.""" + +from praisonai.cli.state.project_sessions import ( + apply_cli_session_continuity, + build_cli_memory_config, + get_project_session_store, +) +from praisonaiagents import Agent + + +def test_build_cli_memory_config_enables_history(): + cfg = build_cli_memory_config(session_id="sess-1", auto_save="sess-1") + assert cfg is not None + assert cfg.session_id == "sess-1" + assert cfg.auto_save == "sess-1" + assert cfg.history is True + + +def test_apply_cli_session_continuity_restores_project_history(): + store = get_project_session_store() + session_id = "continuity-unit-test" + store.clear_session(session_id) + store.add_user_message(session_id, "remember this") + store.add_assistant_message(session_id, "acknowledged") + + agent = Agent(name="RunAgent", memory=build_cli_memory_config(session_id, session_id)) + apply_cli_session_continuity(agent, session_id) + + 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 this" + + agent.chat_history.append({"role": "user", "content": "follow-up"}) + agent.chat_history.append({"role": "assistant", "content": "reply"}) + agent._auto_save_session() + + saved = store.get_chat_history(session_id) + assert len(saved) == 4 + assert saved[-1]["content"] == "reply" + + store.clear_session(session_id) + + +def test_injected_session_store_not_overwritten(): + store = get_project_session_store() + session_id = "store-injection-test" + store.clear_session(session_id) + + agent = Agent(name="RunAgent", memory=build_cli_memory_config(session_id, session_id)) + apply_cli_session_continuity(agent, session_id) + injected_dir = agent._session_store.session_dir + + agent._init_session_store() + assert agent._session_store.session_dir == injected_dir + + store.clear_session(session_id)