feat(cli): show session titles in status bars
This commit is contained in:
@@ -5271,6 +5271,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
"model_name": model_name,
|
||||
"model_short": model_short,
|
||||
"duration": format_duration_compact(elapsed_seconds),
|
||||
"session_title": self._get_status_bar_session_title(),
|
||||
"prompt_elapsed": self._format_prompt_elapsed(
|
||||
getattr(self, "_prompt_start_time", None),
|
||||
getattr(self, "_prompt_duration", 0.0),
|
||||
@@ -5407,6 +5408,34 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
|
||||
return snapshot
|
||||
|
||||
def _get_status_bar_session_title(self) -> str:
|
||||
"""Return the current title without polling state.db on every repaint."""
|
||||
pending = str(getattr(self, "_pending_title", None) or "").strip()
|
||||
session_id = str(getattr(self, "session_id", "") or "")
|
||||
if pending:
|
||||
self._status_bar_title_session_id = session_id
|
||||
self._status_bar_title_cache = pending
|
||||
self._status_bar_title_checked_at = time.monotonic()
|
||||
return pending
|
||||
|
||||
now = time.monotonic()
|
||||
cached_session_id = getattr(self, "_status_bar_title_session_id", None)
|
||||
checked_at = float(getattr(self, "_status_bar_title_checked_at", 0.0) or 0.0)
|
||||
if cached_session_id == session_id and now - checked_at < 1.5:
|
||||
return str(getattr(self, "_status_bar_title_cache", "") or "")
|
||||
|
||||
title = ""
|
||||
db = getattr(self, "_session_db", None)
|
||||
if db is not None and session_id:
|
||||
try:
|
||||
title = str(db.get_session_title(session_id) or "").strip()
|
||||
except Exception:
|
||||
title = ""
|
||||
self._status_bar_title_session_id = session_id
|
||||
self._status_bar_title_cache = title
|
||||
self._status_bar_title_checked_at = now
|
||||
return title
|
||||
|
||||
@staticmethod
|
||||
def _status_bar_display_width(text: str) -> int:
|
||||
"""Return terminal cell width for status-bar text.
|
||||
@@ -5450,6 +5479,57 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
width += ch_width
|
||||
return "".join(out).rstrip() + ellipsis
|
||||
|
||||
@classmethod
|
||||
def _right_align_status_title(cls, text: str, title: str, width: int) -> str:
|
||||
"""Pin a bounded session-title badge to the far-right status-bar edge."""
|
||||
title = str(title or "").strip()
|
||||
if not title or width < 24:
|
||||
return cls._trim_status_bar_text(text, width)
|
||||
|
||||
title_width = max(6, min(30, width // 3))
|
||||
badge = f" {cls._trim_status_bar_text(title, title_width - 2)} "
|
||||
suffix = f" ─{badge}"
|
||||
left_width = max(0, width - cls._status_bar_display_width(suffix))
|
||||
left = cls._trim_status_bar_text(text.rstrip(), left_width)
|
||||
padding = " " * max(0, left_width - cls._status_bar_display_width(left))
|
||||
return f"{left}{padding}{suffix}"
|
||||
|
||||
@classmethod
|
||||
def _right_align_status_title_fragments(cls, frags, title: str, width: int):
|
||||
"""Styled counterpart to :meth:`_right_align_status_title`."""
|
||||
title = str(title or "").strip()
|
||||
if not title or width < 24:
|
||||
return frags
|
||||
|
||||
title_width = max(6, min(30, width // 3))
|
||||
badge = f" {cls._trim_status_bar_text(title, title_width - 2)} "
|
||||
suffix_width = cls._status_bar_display_width(" ─") + cls._status_bar_display_width(badge)
|
||||
left_width = max(0, width - suffix_width)
|
||||
trimmed = []
|
||||
used = 0
|
||||
for style, value in frags:
|
||||
remaining = left_width - used
|
||||
if remaining <= 0:
|
||||
break
|
||||
value_width = cls._status_bar_display_width(value)
|
||||
if value_width <= remaining:
|
||||
trimmed.append((style, value))
|
||||
used += value_width
|
||||
continue
|
||||
clipped = cls._trim_status_bar_text(value, remaining)
|
||||
if clipped:
|
||||
trimmed.append((style, clipped))
|
||||
used += cls._status_bar_display_width(clipped)
|
||||
break
|
||||
|
||||
if used < left_width:
|
||||
trimmed.append(("class:status-bar-dim", " " * (left_width - used)))
|
||||
trimmed.extend([
|
||||
("class:status-bar-dim", " ─"),
|
||||
("class:status-bar-session-title", badge),
|
||||
])
|
||||
return trimmed
|
||||
|
||||
@staticmethod
|
||||
def _get_tui_terminal_width(default: tuple[int, int] = (80, 24)) -> int:
|
||||
"""Return the live prompt_toolkit width, falling back to ``shutil``.
|
||||
@@ -5934,6 +6014,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
battery_label = snapshot.get("battery_label") or ""
|
||||
battery_prefix = f"{battery_label} │ " if battery_label else ""
|
||||
focus_label = snapshot.get("focus_label") or ""
|
||||
session_title = snapshot.get("session_title") or ""
|
||||
|
||||
yolo_active = self._is_session_yolo_active()
|
||||
goal_segment = self._status_bar_goal_segment(snapshot)
|
||||
@@ -5945,7 +6026,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
text += f" · {focus_label}"
|
||||
if yolo_active:
|
||||
text += " · ⚠ YOLO"
|
||||
return self._trim_status_bar_text(text, width)
|
||||
return self._right_align_status_title(text, session_title, width)
|
||||
if width < 76:
|
||||
parts = [f"⚕ {snapshot['model_short']}", percent_label]
|
||||
if battery_label:
|
||||
@@ -5969,7 +6050,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
parts.append(focus_label)
|
||||
if yolo_active:
|
||||
parts.append("⚠ YOLO")
|
||||
return self._trim_status_bar_text(" · ".join(parts), width)
|
||||
return self._right_align_status_title(" · ".join(parts), session_title, width)
|
||||
|
||||
if snapshot["context_length"]:
|
||||
ctx_total = _format_context_length(snapshot["context_length"])
|
||||
@@ -6006,7 +6087,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
parts.append(focus_label)
|
||||
if yolo_active:
|
||||
parts.append("⚠ YOLO")
|
||||
return self._trim_status_bar_text(" │ ".join(parts), width)
|
||||
return self._right_align_status_title(" │ ".join(parts), session_title, width)
|
||||
except Exception:
|
||||
return f"⚕ {self.model if getattr(self, 'model', None) else 'Hermes'}"
|
||||
|
||||
@@ -6027,6 +6108,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
battery_label = snapshot.get("battery_label") or ""
|
||||
battery_style = self._battery_status_style(snapshot.get("battery_category", "dim"))
|
||||
focus_label = snapshot.get("focus_label") or ""
|
||||
session_title = snapshot.get("session_title") or ""
|
||||
|
||||
if width < 52:
|
||||
frags = [
|
||||
@@ -6177,6 +6259,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
("class:status-bar-dim", " │"),
|
||||
]
|
||||
|
||||
frags = self._right_align_status_title_fragments(frags, session_title, width)
|
||||
|
||||
total_width = sum(self._status_bar_display_width(text) for _, text in frags)
|
||||
if total_width > width:
|
||||
plain_text = "".join(text for _, text in frags)
|
||||
@@ -8297,6 +8381,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
try:
|
||||
self._session_db.set_session_title(self.session_id, sanitized)
|
||||
self._pending_title = None
|
||||
self._status_bar_title_checked_at = 0.0
|
||||
title = sanitized
|
||||
except ValueError as e:
|
||||
_cprint(f" {e} — session started untitled.")
|
||||
@@ -10015,6 +10100,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
# Session exists in DB — set title directly
|
||||
try:
|
||||
if self._session_db.set_session_title(self.session_id, new_title):
|
||||
self._status_bar_title_checked_at = 0.0
|
||||
_cprint(f" Session title set: {new_title}")
|
||||
else:
|
||||
_cprint(" Session not found in database.")
|
||||
@@ -17224,6 +17310,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
'status-bar-bad': 'bg:#1a1a2e #FF8C00 bold',
|
||||
'status-bar-critical': 'bg:#1a1a2e #FF6B6B bold',
|
||||
'status-bar-yolo': 'bg:#1a1a2e #FF4444 bold',
|
||||
'status-bar-session-title': 'bg:#FFD700 #1a1a2e bold',
|
||||
# Bronze horizontal rules around the input area
|
||||
'input-rule': '#CD7F32',
|
||||
# Clipboard image attachment badges
|
||||
|
||||
Reference in New Issue
Block a user