|
11 | 11 | from pathlib import Path |
12 | 12 | from typing import Any, Optional |
13 | 13 |
|
| 14 | +from litellm import Message |
| 15 | + |
14 | 16 | from agent.config import Config |
15 | 17 | from agent.context_manager.manager import ContextManager |
16 | 18 | from agent.messaging.gateway import NotificationGateway |
@@ -62,6 +64,7 @@ class OpType(Enum): |
62 | 64 | INTERRUPT = "interrupt" |
63 | 65 | UNDO = "undo" |
64 | 66 | COMPACT = "compact" |
| 67 | + NEW = "new" |
65 | 68 | RESUME = "resume" |
66 | 69 | SHUTDOWN = "shutdown" |
67 | 70 |
|
@@ -376,6 +379,82 @@ def increment_turn(self) -> None: |
376 | 379 | """Increment turn counter (called after each user interaction)""" |
377 | 380 | self.turn_count += 1 |
378 | 381 |
|
| 382 | + def start_new_conversation(self) -> dict[str, Any]: |
| 383 | + """Rotate this runtime into a fresh conversation. |
| 384 | +
|
| 385 | + The tool router, model/config choices, user identity, and external |
| 386 | + resources stay attached to the CLI process. Conversation-specific state |
| 387 | + gets reset so later saves do not merge with the prior chat. Warm runtime |
| 388 | + resources such as the sandbox, in-flight job tracking, and probed |
| 389 | + model-effort cache are deliberately preserved. |
| 390 | + """ |
| 391 | + previous_session_id = self.session_id |
| 392 | + previous_turn_count = self.turn_count |
| 393 | + previous_message_count = len(self.context_manager.items) |
| 394 | + previous_non_system_count = sum( |
| 395 | + 1 |
| 396 | + for item in self.context_manager.items |
| 397 | + if getattr(item, "role", None) != "system" |
| 398 | + ) |
| 399 | + |
| 400 | + saved_path: str | None = None |
| 401 | + if self.config.save_sessions and previous_non_system_count: |
| 402 | + saved_path = self.save_and_upload_detached(self.config.session_dataset_repo) |
| 403 | + |
| 404 | + from agent.tools.plan_tool import reset_current_plan |
| 405 | + |
| 406 | + self.current_plan = [] |
| 407 | + reset_current_plan() |
| 408 | + |
| 409 | + system_msg = self._fresh_system_message() |
| 410 | + self.context_manager.items = [system_msg] if system_msg is not None else [] |
| 411 | + self.context_manager.running_context_usage = 0 |
| 412 | + |
| 413 | + self.session_id = str(uuid.uuid4()) |
| 414 | + self.session_start_time = datetime.now().isoformat() |
| 415 | + self.turn_count = 0 |
| 416 | + self.last_auto_save_turn = 0 |
| 417 | + self.logged_events = [] |
| 418 | + self._local_save_path = None |
| 419 | + self._last_heartbeat_ts = None |
| 420 | + self.pending_approval = None |
| 421 | + self.auto_approval_estimated_spend_usd = 0.0 |
| 422 | + self.reset_cancel() |
| 423 | + |
| 424 | + # Previous-session metadata is intentionally included for event |
| 425 | + # consumers and telemetry, even though the CLI currently prints only |
| 426 | + # the optional save path. |
| 427 | + return { |
| 428 | + "session_id": self.session_id, |
| 429 | + "previous_session_id": previous_session_id, |
| 430 | + "previous_turn_count": previous_turn_count, |
| 431 | + "previous_message_count": previous_message_count, |
| 432 | + "saved_path": saved_path, |
| 433 | + } |
| 434 | + |
| 435 | + def _fresh_system_message(self) -> Message | None: |
| 436 | + existing = ( |
| 437 | + self.context_manager.items[0] |
| 438 | + if self.context_manager.items |
| 439 | + and getattr(self.context_manager.items[0], "role", None) == "system" |
| 440 | + else None |
| 441 | + ) |
| 442 | + refresh = getattr(self.context_manager, "refresh_system_prompt", None) |
| 443 | + if refresh is None: |
| 444 | + return existing |
| 445 | + try: |
| 446 | + tool_specs = ( |
| 447 | + self.tool_router.get_tool_specs_for_llm() if self.tool_router else [] |
| 448 | + ) |
| 449 | + return refresh( |
| 450 | + tool_specs=tool_specs, |
| 451 | + hf_token=self.hf_token, |
| 452 | + local_mode=self.local_mode, |
| 453 | + ) |
| 454 | + except Exception as e: |
| 455 | + logger.warning("Failed to refresh system prompt for new chat: %s", e) |
| 456 | + return existing |
| 457 | + |
379 | 458 | async def auto_save_if_needed(self) -> None: |
380 | 459 | """Check if auto-save should trigger and save if so (completely non-blocking)""" |
381 | 460 | if not self.config.save_sessions: |
|
0 commit comments