From 541c60a846a05a957a8c3f8191502cf41bd83ce4 Mon Sep 17 00:00:00 2001 From: Christopher Rios Date: Mon, 16 Feb 2026 11:33:00 -0700 Subject: [PATCH 1/3] feat: add direct session resume by name Allow users to resume a session directly by name instead of always opening the interactive picker: /resume # Opens picker (unchanged) /resume auto_session_20260214_175543 # Loads directly (NEW) - session_commands.py: Parse optional session_name argument - cli_runner.py: Handle __AUTOSAVE_LOAD_DIRECT__ marker Closes: user feedback about /resume not working --- code_puppy/cli_runner.py | 47 ++++++++++++++++++++- code_puppy/command_line/session_commands.py | 32 +++++++++++--- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/code_puppy/cli_runner.py b/code_puppy/cli_runner.py index f39f4d3ac..1f515ea7e 100644 --- a/code_puppy/cli_runner.py +++ b/code_puppy/cli_runner.py @@ -642,8 +642,51 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non if command_result is True: continue elif isinstance(command_result, str): - if command_result == "__AUTOSAVE_LOAD__": - # Handle async autosave loading + if command_result.startswith("__AUTOSAVE_LOAD_DIRECT__:"): + # Handle direct session load by name + session_name = command_result.split(":", 1)[1] + try: + from code_puppy.agents.agent_manager import get_current_agent + from code_puppy.config import ( + set_current_autosave_from_session_name, + ) + from code_puppy.messaging import emit_error, emit_success + from code_puppy.session_storage import load_session + + base_dir = Path(AUTOSAVE_DIR) + session_path = base_dir / f"{session_name}.pkl" + + # Check if session exists + if not session_path.exists(): + emit_error(f"❌ Session not found: {session_name}") + emit_info("💡 Use /resume to open the session picker.") + continue + + # Load the session + history = load_session(session_name, base_dir) + + agent = get_current_agent() + agent.set_message_history(history) + + # Set current autosave session + set_current_autosave_from_session_name(session_name) + + total_tokens = sum( + agent.estimate_tokens_for_message(msg) for msg in history + ) + + emit_success( + f"✅ Resumed: {session_name}\n" + f"📊 {len(history)} messages ({total_tokens:,} tokens)" + ) + except Exception as e: + from code_puppy.messaging import emit_error + + emit_error(f"Failed to load session: {e}") + emit_info("💡 Use /resume to open the session picker.") + continue + elif command_result == "__AUTOSAVE_LOAD__": + # Handle async autosave loading (interactive picker) try: # Check if we're in a real interactive terminal # (not pexpect/tests) - interactive picker requires proper TTY diff --git a/code_puppy/command_line/session_commands.py b/code_puppy/command_line/session_commands.py index 0fb69395b..a51937e04 100644 --- a/code_puppy/command_line/session_commands.py +++ b/code_puppy/command_line/session_commands.py @@ -184,15 +184,37 @@ def handle_truncate_command(command: str) -> bool: @register_command( name="autosave_load", - description="Load an autosave session interactively", - usage="/autosave_load", + description="Load an autosave session interactively, or by name", + usage="/autosave_load [session_name]", aliases=["resume"], category="session", + detailed_help=""" + Load an autosave session. + + Commands: + /resume Open interactive session picker + /resume Load session directly by name + + Examples: + /resume + /resume auto_session_20260214_175543 + """, ) def handle_autosave_load_command(command: str) -> bool: - """Load an autosave session.""" - # Return a special marker to indicate we need to run async autosave loading - return "__AUTOSAVE_LOAD__" + """Load an autosave session. + + If no session name is provided, opens interactive picker. + If session name is provided, loads that session directly. + """ + tokens = command.split() + + if len(tokens) == 1: + # No argument = open picker + return "__AUTOSAVE_LOAD__" + + # Session name provided = load directly + session_name = tokens[1] + return f"__AUTOSAVE_LOAD_DIRECT__:{session_name}" @register_command( From 0ef9cc7d1adcc1d21a3cdb9ca4f9a93c6d28b584 Mon Sep 17 00:00:00 2001 From: Christopher Rios Date: Mon, 16 Feb 2026 11:43:29 -0700 Subject: [PATCH 2/3] fix: add path traversal protection for session resume - Validate session_path stays within AUTOSAVE_DIR before loading - Resolve paths to prevent ../../ escape attacks - Fix return type annotation (bool -> bool | str) Addresses CodeRabbit security review feedback --- code_puppy/cli_runner.py | 10 ++++++++-- code_puppy/command_line/session_commands.py | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/code_puppy/cli_runner.py b/code_puppy/cli_runner.py index 1f515ea7e..5ee8e274c 100644 --- a/code_puppy/cli_runner.py +++ b/code_puppy/cli_runner.py @@ -653,8 +653,14 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non from code_puppy.messaging import emit_error, emit_success from code_puppy.session_storage import load_session - base_dir = Path(AUTOSAVE_DIR) - session_path = base_dir / f"{session_name}.pkl" + base_dir = Path(AUTOSAVE_DIR).resolve() + session_path = (base_dir / f"{session_name}.pkl").resolve() + + # Security: Prevent path traversal attacks + if base_dir not in session_path.parents and session_path.parent != base_dir: + emit_error(f"❌ Invalid session name: {session_name}") + emit_info("💡 Use /resume to open the session picker.") + continue # Check if session exists if not session_path.exists(): diff --git a/code_puppy/command_line/session_commands.py b/code_puppy/command_line/session_commands.py index a51937e04..dbe5e87ca 100644 --- a/code_puppy/command_line/session_commands.py +++ b/code_puppy/command_line/session_commands.py @@ -200,11 +200,14 @@ def handle_truncate_command(command: str) -> bool: /resume auto_session_20260214_175543 """, ) -def handle_autosave_load_command(command: str) -> bool: +def handle_autosave_load_command(command: str) -> "bool | str": """Load an autosave session. If no session name is provided, opens interactive picker. If session name is provided, loads that session directly. + + Returns: + "__AUTOSAVE_LOAD__" for picker, "__AUTOSAVE_LOAD_DIRECT__:" for direct load. """ tokens = command.split() From 089bd793e20e415f128fbaed314e6bb73d5d0ca9 Mon Sep 17 00:00:00 2001 From: Christopher Rios Date: Mon, 16 Feb 2026 11:54:44 -0700 Subject: [PATCH 3/3] style: format cli_runner.py with ruff --- code_puppy/cli_runner.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code_puppy/cli_runner.py b/code_puppy/cli_runner.py index 5ee8e274c..afbb4b733 100644 --- a/code_puppy/cli_runner.py +++ b/code_puppy/cli_runner.py @@ -657,7 +657,10 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non session_path = (base_dir / f"{session_name}.pkl").resolve() # Security: Prevent path traversal attacks - if base_dir not in session_path.parents and session_path.parent != base_dir: + if ( + base_dir not in session_path.parents + and session_path.parent != base_dir + ): emit_error(f"❌ Invalid session name: {session_name}") emit_info("💡 Use /resume to open the session picker.") continue