diff --git a/navi/tools/ssh_exec.py b/navi/tools/ssh_exec.py index d856636..e2a6dd7 100644 --- a/navi/tools/ssh_exec.py +++ b/navi/tools/ssh_exec.py @@ -169,14 +169,11 @@ connect_kwargs = self._resolve(params) if connect_kwargs is None: - return ToolResult( - success=False, - output=( - "No SSH target specified. Provide 'host' (and optionally 'username', " - "'password', 'key_path'), or a named 'connection' from ssh_hosts.json." - ), - error="no_target", + msg = ( + "No SSH target specified. Provide 'host' (and optionally 'username', " + "'password', 'key_path'), or a named 'connection' from ssh_hosts.json." ) + return ToolResult(success=False, output=msg, error=msg) session_id = current_session_id.get() host = connect_kwargs["host"] @@ -207,38 +204,35 @@ if result.stderr: output_parts.append(f"[stderr]\n{result.stderr}") + output_text = "\n".join(output_parts) or "(no output)" success = result.exit_status == 0 return ToolResult( success=success, - output="\n".join(output_parts) or "(no output)", + output=output_text, metadata={"exit_status": result.exit_status, "host": connect_kwargs.get("host")}, - error=None if success else f"Exit status {result.exit_status}", + error=None if success else f"Exit status {result.exit_status}\n{output_text}", ) except (asyncssh.DisconnectError, asyncssh.ConnectionLost, EOFError) as e: _evict(key) if attempt == 0: continue # retry with fresh connection - return ToolResult(success=False, output=f"SSH disconnected: {e}", error=str(e)) + msg = f"SSH disconnected: {e}" + return ToolResult(success=False, output=msg, error=msg) except asyncssh.PermissionDenied: _evict(key) - return ToolResult( - success=False, - output="SSH permission denied. Check username and password/key.", - error="permission_denied", - ) + msg = "SSH permission denied. Check username and password/key." + return ToolResult(success=False, output=msg, error=msg) except (TimeoutError, asyncio.TimeoutError): - return ToolResult( - success=False, - output=f"SSH command timed out after {timeout}s", - error="timeout", - ) + msg = f"SSH command timed out after {timeout}s" + return ToolResult(success=False, output=msg, error=msg) except Exception as e: _evict(key) - return ToolResult(success=False, output=f"SSH error: {e}", error=str(e)) + msg = f"SSH error: {e}" + return ToolResult(success=False, output=msg, error=msg) # Should not be reached return ToolResult(success=False, output="SSH: unexpected retry exhaustion", error="retry_failed") @@ -256,6 +250,8 @@ cfg[k] = params[k] if params.get("key_path"): cfg["client_keys"] = [params["key_path"]] + # Skip host key verification by default (same as ad-hoc) + cfg.setdefault("known_hosts", "none") return self._build_kwargs(cfg) # Direct params @@ -272,7 +268,6 @@ cfg["port"] = params["port"] if params.get("key_path"): cfg["client_keys"] = [params["key_path"]] - # Skip host key verification by default for ad-hoc connections cfg.setdefault("known_hosts", "none") return self._build_kwargs(cfg) diff --git a/navi/tools/switch_profile.py b/navi/tools/switch_profile.py index 78b4c4e..6e326e4 100644 --- a/navi/tools/switch_profile.py +++ b/navi/tools/switch_profile.py @@ -1,6 +1,5 @@ """Built-in tool for switching the active agent profile mid-session.""" -from navi.core.events import ProfileSwitched from navi.tools.base import Tool, ToolResult, current_event_sink, current_session_id @@ -60,6 +59,7 @@ # Notify the client immediately so it can update the UI. sink = current_event_sink.get() if sink is not None: + from navi.core.events import ProfileSwitched await sink.put(ProfileSwitched(profile_id=profile_id, profile_name=profile.name)) return ToolResult(