feat(03-02): add /timeout and /sessions commands

- Added timeout_cmd() to set per-session idle timeout (1-120 min range)
  - Shows current timeout when called without args
  - Updates session metadata and existing idle timer
- Added sessions_cmd() to list all sessions with status
  - Shows LIVE (subprocess running) or IDLE (suspended) status
  - Displays persona and relative last-active time (e.g., '2m ago')
  - Marks current active session with arrow
- Registered both commands in main()
- Commands already added to help text in Task 1

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikkel Georgsen 2026-02-04 23:37:30 +00:00
parent 6ebdb4a555
commit 06c52466f2

View file

@ -1025,6 +1025,98 @@ async def model_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(f"Model set to {resolved} for session '{active_session}'.")
logger.info(f"Model changed to {resolved} for session '{active_session}'")
async def timeout_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Set idle timeout for current session."""
if not is_authorized(update.effective_user.id):
return
active_session = session_manager.get_active_session()
if not active_session:
await update.message.reply_text("No active session. Use /new <name> to start one.")
return
if not context.args:
# Show current timeout
timeout_secs = session_manager.get_session_timeout(active_session)
minutes = timeout_secs // 60
await update.message.reply_text(
f"Idle timeout: {minutes} minutes\n\nUsage: /timeout <minutes> (1-120)"
)
return
# Parse and validate timeout value
try:
minutes = int(context.args[0])
if minutes < 1 or minutes > 120:
await update.message.reply_text("Timeout must be between 1 and 120 minutes")
return
except ValueError:
await update.message.reply_text("Invalid number. Usage: /timeout <minutes>")
return
# Convert to seconds and update
timeout_seconds = minutes * 60
session_manager.update_session(active_session, idle_timeout=timeout_seconds)
# Update existing idle timer if present
if active_session in idle_timers:
idle_timers[active_session].timeout_seconds = timeout_seconds
idle_timers[active_session].reset()
await update.message.reply_text(
f"Idle timeout set to {minutes} minutes for session '{active_session}'."
)
logger.info(f"Idle timeout set to {minutes} minutes for session '{active_session}'")
async def sessions_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""List all sessions with status."""
if not is_authorized(update.effective_user.id):
return
sessions = session_manager.list_sessions()
if not sessions:
await update.message.reply_text("No sessions. Use /new <name> to create one.")
return
active_session = session_manager.get_active_session()
def format_relative_time(iso_str):
"""Format ISO timestamp as relative time (e.g., '2m ago')."""
dt = datetime.fromisoformat(iso_str)
delta = datetime.now(timezone.utc) - dt
secs = delta.total_seconds()
if secs < 60:
return "just now"
if secs < 3600:
return f"{int(secs/60)}m ago"
if secs < 86400:
return f"{int(secs/3600)}h ago"
return f"{int(secs/86400)}d ago"
lines = []
for s in sessions:
name = s['name']
persona = s.get('persona', 'default')
# Determine status
if name in subprocesses and subprocesses[name].is_alive:
status = "🟢 LIVE"
elif s.get('status') == 'suspended':
status = "⚪ IDLE"
else:
status = s.get('status', 'unknown').upper()
# Format last active time
last_active = s.get('last_active', '')
rel_time = format_relative_time(last_active) if last_active else "unknown"
# Mark current active session
marker = "" if name == active_session else " "
lines.append(f"{marker}{status} `{name}` ({persona}) - {rel_time}")
await update.message.reply_text("\n".join(lines), parse_mode='Markdown')
async def unknown(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle unknown commands."""
if not is_authorized(update.effective_user.id):
@ -1113,8 +1205,10 @@ def main():
app.add_handler(CommandHandler("chatid", chatid))
app.add_handler(CommandHandler("new", new_session))
app.add_handler(CommandHandler("session", switch_session_cmd))
app.add_handler(CommandHandler("sessions", sessions_cmd))
app.add_handler(CommandHandler("archive", archive_session_cmd))
app.add_handler(CommandHandler("model", model_cmd))
app.add_handler(CommandHandler("timeout", timeout_cmd))
app.add_handler(CommandHandler("status", status))
app.add_handler(CommandHandler("pbs", pbs))
app.add_handler(CommandHandler("pbs_status", pbs_status))