feat(01-03): add /archive command to compress and remove sessions
Archives session directory with tar+pigz to sessions_archive/, terminates any running subprocess first, clears active session if needed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3cc97adcd0
commit
a27ac010ec
2 changed files with 79 additions and 0 deletions
|
|
@ -140,6 +140,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
*Claude Sessions:*
|
*Claude Sessions:*
|
||||||
/new <name> [persona] - Create new Claude session
|
/new <name> [persona] - Create new Claude session
|
||||||
/session <name> - Switch to a session
|
/session <name> - Switch to a session
|
||||||
|
/archive <name> - Archive and remove a session
|
||||||
|
|
||||||
*Status & Monitoring:*
|
*Status & Monitoring:*
|
||||||
/status - Quick service overview
|
/status - Quick service overview
|
||||||
|
|
@ -407,6 +408,38 @@ async def switch_session_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||||
logger.error(f"Error switching session: {e}")
|
logger.error(f"Error switching session: {e}")
|
||||||
await update.message.reply_text(f"Error switching session: {e}")
|
await update.message.reply_text(f"Error switching session: {e}")
|
||||||
|
|
||||||
|
async def archive_session_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Archive a Claude session (compress and remove)."""
|
||||||
|
if not is_authorized(update.effective_user.id):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not context.args:
|
||||||
|
await update.message.reply_text("Usage: /archive <name>")
|
||||||
|
return
|
||||||
|
|
||||||
|
name = context.args[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Terminate subprocess if running
|
||||||
|
if name in subprocesses:
|
||||||
|
if subprocesses[name].is_alive:
|
||||||
|
await subprocesses[name].terminate()
|
||||||
|
del subprocesses[name]
|
||||||
|
|
||||||
|
# Archive the session
|
||||||
|
archive_path = session_manager.archive_session(name)
|
||||||
|
size_mb = archive_path.stat().st_size / (1024 * 1024)
|
||||||
|
await update.message.reply_text(
|
||||||
|
f"Session '{name}' archived ({size_mb:.1f} MB)\n{archive_path.name}"
|
||||||
|
)
|
||||||
|
logger.info(f"Archived session '{name}' to {archive_path}")
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
await update.message.reply_text(str(e))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error archiving session: {e}")
|
||||||
|
await update.message.reply_text(f"Error archiving session: {e}")
|
||||||
|
|
||||||
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Handle free text messages - route to active Claude session."""
|
"""Handle free text messages - route to active Claude session."""
|
||||||
if not is_authorized(update.effective_user.id):
|
if not is_authorized(update.effective_user.id):
|
||||||
|
|
@ -535,6 +568,7 @@ def main():
|
||||||
app.add_handler(CommandHandler("chatid", chatid))
|
app.add_handler(CommandHandler("chatid", chatid))
|
||||||
app.add_handler(CommandHandler("new", new_session))
|
app.add_handler(CommandHandler("new", new_session))
|
||||||
app.add_handler(CommandHandler("session", switch_session_cmd))
|
app.add_handler(CommandHandler("session", switch_session_cmd))
|
||||||
|
app.add_handler(CommandHandler("archive", archive_session_cmd))
|
||||||
app.add_handler(CommandHandler("status", status))
|
app.add_handler(CommandHandler("status", status))
|
||||||
app.add_handler(CommandHandler("pbs", pbs))
|
app.add_handler(CommandHandler("pbs", pbs))
|
||||||
app.add_handler(CommandHandler("pbs_status", pbs_status))
|
app.add_handler(CommandHandler("pbs_status", pbs_status))
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ Directory structure:
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -316,6 +318,49 @@ class SessionManager:
|
||||||
"""
|
"""
|
||||||
return self.base_dir / name
|
return self.base_dir / name
|
||||||
|
|
||||||
|
def archive_session(self, name: str) -> Path:
|
||||||
|
"""
|
||||||
|
Archive a session by compressing it with tar+pigz and removing the original.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Session name to archive
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the archive file
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If session does not exist
|
||||||
|
"""
|
||||||
|
if not self.session_exists(name):
|
||||||
|
raise ValueError(f"Session '{name}' does not exist")
|
||||||
|
|
||||||
|
# Clear active session if archiving the active one
|
||||||
|
if self.active_session == name:
|
||||||
|
self.active_session = None
|
||||||
|
|
||||||
|
# Create archive directory
|
||||||
|
archive_dir = self.base_dir.parent / "sessions_archive"
|
||||||
|
archive_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Build archive filename with timestamp
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
archive_name = f"{name}_{timestamp}.tar.gz"
|
||||||
|
archive_path = archive_dir / archive_name
|
||||||
|
|
||||||
|
# Compress with tar + pigz
|
||||||
|
session_dir = self.base_dir / name
|
||||||
|
subprocess.run(
|
||||||
|
["tar", "--use-compress-program=pigz", "-cf", str(archive_path),
|
||||||
|
"-C", str(self.base_dir), name],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove original session directory
|
||||||
|
shutil.rmtree(session_dir)
|
||||||
|
|
||||||
|
logger.info(f"Archived session '{name}' to {archive_path}")
|
||||||
|
return archive_path
|
||||||
|
|
||||||
def load_persona(self, name: str) -> dict:
|
def load_persona(self, name: str) -> dict:
|
||||||
"""
|
"""
|
||||||
Load persona from library.
|
Load persona from library.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue