diff --git a/pentestagent/interface/tui.py b/pentestagent/interface/tui.py index 370f81c..2840fea 100644 --- a/pentestagent/interface/tui.py +++ b/pentestagent/interface/tui.py @@ -1228,6 +1228,9 @@ class PentestAgentTUI(App): with Horizontal(id="main-container"): # Chat area (left side) with Vertical(id="chat-area"): + # Persistent header shown above the chat scroll so operator + # always sees runtime/mode/target information. + yield Static("", id="header") yield ScrollableContainer(id="chat-scroll") yield StatusBar(id="status-bar") with Horizontal(id="input-container"): @@ -1380,15 +1383,28 @@ class PentestAgentTUI(App): tools_str += f", +{len(self.all_tools) - 5} more" runtime_str = "Docker" if self.use_docker else "Local" - self._add_system( - f"+ PentestAgent ready\n" - f" Model: {self.model} | Tools: {len(self.all_tools)} | MCP: {mcp_server_count} | RAG: {rag_doc_count}\n" - f" Runtime: {runtime_str} | Mode: Assist (use /agent or /crew for autonomous modes)" - ) + # Update persistent header instead of adding an ad-hoc system + # message to the chat. This keeps the info visible at top. + try: + self._update_header(model_line=( + f"+ PentestAgent ready\n" + f" Model: {self.model} | Tools: {len(self.all_tools)} | MCP: {mcp_server_count} | RAG: {rag_doc_count}\n" + f" Runtime: {runtime_str} | Mode: Assist (use /agent or /crew for autonomous modes)" + )) + except Exception: + # Fallback to previous behavior if header widget isn't available + self._add_system( + f"+ PentestAgent ready\n" + f" Model: {self.model} | Tools: {len(self.all_tools)} | MCP: {mcp_server_count} | RAG: {rag_doc_count}\n" + f" Runtime: {runtime_str} | Mode: Assist (use /agent or /crew for autonomous modes)" + ) - # Show target if provided (but don't auto-start) + # Also update header with target if present if self.target: - self._add_system(f" Target: {self.target}") + try: + self._update_header(target=self.target) + except Exception: + self._add_system(f" Target: {self.target}") except Exception as e: import traceback @@ -2408,6 +2424,39 @@ Be concise. Use the actual data from notes.""" scroll.mount(msg) except Exception: self._add_system(f" Target: {target}") + except Exception as e: + logging.getLogger(__name__).exception("Failed updating in-scroll target display: %s", e) + # Also update the persistent header so the target is always visible + try: + self._update_header(target=target) + except Exception: + pass + + def _update_header(self, model_line: Optional[str] = None, target: Optional[str] = None) -> None: + """Compose and update the persistent header widget.""" + try: + header = self.query_one("#header", Static) + # Build header text from provided pieces or current state + lines: List[str] = [] + if model_line: + lines.append(model_line) + else: + # try to recreate a compact model/runtime line + runtime_str = "Docker" if getattr(self, "use_docker", False) else "Local" + tools_count = len(getattr(self, "all_tools", [])) if hasattr(self, "all_tools") else 0 + lines.append( + f"+ PentestAgent ready\n Model: {getattr(self, 'model', '')} | Tools: {tools_count} | RAG: {getattr(self, 'rag_doc_count', '')}\n Runtime: {runtime_str} | Mode: {getattr(self, '_mode', 'assist')}" + ) + # Ensure target line is present/updated + if target is None: + target = getattr(self, "target", "") + if target: + # append target on its own line + lines.append(f" Target: {target}") + + header.update("\n".join(lines)) + except Exception: + pass except Exception: self._add_system(f" Target: {target}")