- Agent CLI: 多轮对话、SSH 长连接、流式推理、Evolver 按用户隔离、历史侧栏 - 终端审计: 回放播放器、命令书签、筛选分页导出、命令输出统计、录制落盘修复 - 修复 session_commands 表迁移、Kill 竞态导致录制丢失、无录制会话回放提示 - 审计历史页只读(移除删除),移除回放弹框录制搜索
106 lines
3.4 KiB
Go
106 lines
3.4 KiB
Go
// ssh_execute.go - 远程命令执行工具 // Logan.Li
|
||
// 复用 aiops 既有 pkg/remoteexec(Go 原生 SSH),凭据由 controller 从 model.Server 构建后注入。
|
||
// 任务级复用 PersistentClient 长连接,避免每条命令重建 SSH 握手。
|
||
// 实现 DynamicRiskTool:依据具体命令动态评估风险,决定是否触发用户确认。
|
||
package tools
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"ezops/pkg/remoteexec"
|
||
)
|
||
|
||
// SSHExecuteTool 远程命令执行工具 // Logan.Li
|
||
type SSHExecuteTool struct {
|
||
client *remoteexec.PersistentClient // 任务级 SSH 长连接
|
||
timeout time.Duration // 默认命令超时
|
||
}
|
||
|
||
// NewSSHExecuteTool 创建远程执行工具(内部建立 SSH 长连接) // Logan.Li
|
||
func NewSSHExecuteTool(cfg remoteexec.SSHConfig, timeout time.Duration) (*SSHExecuteTool, error) {
|
||
if timeout <= 0 {
|
||
timeout = 60 * time.Second
|
||
}
|
||
client, err := remoteexec.NewPersistentClient(cfg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("SSH 长连接建立失败: %w", err)
|
||
}
|
||
return &SSHExecuteTool{client: client, timeout: timeout}, nil
|
||
}
|
||
|
||
// Close 释放 SSH 长连接 // Logan.Li
|
||
func (t *SSHExecuteTool) Close() error {
|
||
if t.client == nil {
|
||
return nil
|
||
}
|
||
return t.client.Close()
|
||
}
|
||
|
||
// Name 工具名 // Logan.Li
|
||
func (t *SSHExecuteTool) Name() string { return "ssh_execute" }
|
||
|
||
// Description 给 LLM 的工具说明 // Logan.Li
|
||
func (t *SSHExecuteTool) Description() string {
|
||
return "在远程目标服务器上执行 shell 命令并返回合并的 stdout+stderr 输出与退出码。" +
|
||
"用于排障(查看日志/进程/磁盘/网络)、安装与配置软件、重启服务等。" +
|
||
"高危命令(rm -rf /、mkfs、reboot 等)需用户确认。"
|
||
}
|
||
|
||
// Parameters JSON Schema 参数定义 // Logan.Li
|
||
func (t *SSHExecuteTool) Parameters() map[string]interface{} {
|
||
return map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"command": map[string]interface{}{
|
||
"type": "string",
|
||
"description": "要执行的 shell 命令,例如 df -h、systemctl status nginx、tail -n 100 /var/log/messages",
|
||
},
|
||
"timeout": map[string]interface{}{
|
||
"type": "integer",
|
||
"description": "命令超时秒数(默认 60)",
|
||
},
|
||
},
|
||
"required": []string{"command"},
|
||
}
|
||
}
|
||
|
||
// RiskLevel 静态风险等级(高危) // Logan.Li
|
||
func (t *SSHExecuteTool) RiskLevel() RiskLevel { return RiskHigh }
|
||
|
||
// RiskLevelForArgs 依据命令动态评估风险 // Logan.Li
|
||
func (t *SSHExecuteTool) RiskLevelForArgs(args map[string]interface{}) RiskLevel {
|
||
return AssessBashRisk(StringParam(args, "command"))
|
||
}
|
||
|
||
// Execute 执行远程命令 // Logan.Li
|
||
func (t *SSHExecuteTool) Execute(ctx context.Context, args map[string]interface{}) (*Result, error) {
|
||
command := strings.TrimSpace(StringParam(args, "command"))
|
||
if command == "" {
|
||
return &Result{Success: false, Error: "参数 command 不能为空"}, nil
|
||
}
|
||
|
||
timeout := t.timeout
|
||
if sec := IntParam(args, "timeout"); sec > 0 {
|
||
timeout = time.Duration(sec) * time.Second
|
||
}
|
||
|
||
res, err := t.client.Exec(command, timeout)
|
||
if err != nil {
|
||
return &Result{Success: false, Error: fmt.Sprintf("SSH 连接失败: %v", err)}, nil
|
||
}
|
||
|
||
output := res.Output
|
||
if !strings.HasSuffix(output, "\n") && output != "" {
|
||
output += "\n"
|
||
}
|
||
output += fmt.Sprintf("[exit_code=%d]", res.ExitCode)
|
||
|
||
return &Result{
|
||
Success: res.Success,
|
||
Output: output,
|
||
Error: res.Error,
|
||
}, nil
|
||
} |