feat: Agent CLI 与终端审计回放全面优化
- Agent CLI: 多轮对话、SSH 长连接、流式推理、Evolver 按用户隔离、历史侧栏 - 终端审计: 回放播放器、命令书签、筛选分页导出、命令输出统计、录制落盘修复 - 修复 session_commands 表迁移、Kill 竞态导致录制丢失、无录制会话回放提示 - 审计历史页只读(移除删除),移除回放弹框录制搜索
This commit is contained in:
1 parent
9a3dd304f7
commit
1acb0c49af
81 files changed
+3916
-1655
No files matched your search
@@ -23,8 +23,9 @@ import (
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
EventThought EventType = "thought" // AI 思考/推理文本
|
||||
EventToolCall EventType = "tool_call" // 即将执行的工具(名/参数/风险)
|
||||
EventThought EventType = "thought" // AI 思考/推理文本(流式结束后的完整内容)
|
||||
EventThoughtDelta EventType = "thought_delta" // AI 思考流式增量 token
|
||||
EventToolCall EventType = "tool_call" // 即将执行的工具(名/参数/风险)
|
||||
EventToolResult EventType = "tool_result" // 工具执行结果(输出/成败/耗时)
|
||||
EventProgress EventType = "progress" // 轮次进度 + token
|
||||
EventDone EventType = "done" // 任务完成(最终答案)
|
||||
@@ -50,6 +51,14 @@ type Event struct {
|
||||
// EventCallback 事件回调(controller 注入) // Logan.Li
|
||||
type EventCallback func(Event)
|
||||
|
||||
// RunResult ReAct 执行结果(含对话历史供多轮追问) // Logan.Li
|
||||
type RunResult struct {
|
||||
Success bool
|
||||
Output string
|
||||
History []llm.Message // system 之后的对话历史
|
||||
TotalTokens int
|
||||
}
|
||||
|
||||
// Config Agent 运行配置 // Logan.Li
|
||||
type Config struct {
|
||||
MaxIterations int
|
||||
@@ -60,6 +69,7 @@ type Config struct {
|
||||
OutputMaxLength int
|
||||
MaxContextTokens int
|
||||
MemoryDir string // Evolver 记忆目录
|
||||
RoundOffset int // 轮次偏移(多轮追问时接续上一轮编号) // Logan.Li
|
||||
}
|
||||
|
||||
// DefaultConfig 默认配置 // Logan.Li
|
||||
@@ -112,9 +122,9 @@ func (a *Agent) emit(e Event) {
|
||||
}
|
||||
}
|
||||
|
||||
// Run 执行单次任务(ReAct 循环 + Evolver 集成) // Logan.Li
|
||||
func (a *Agent) Run(ctx context.Context, query, serverInfo string) (*tools.Result, error) {
|
||||
a.totalTokens = 0
|
||||
// Run 执行单次任务(ReAct 循环 + Evolver 集成);prior 为不含 system 的历史对话 // Logan.Li
|
||||
func (a *Agent) Run(ctx context.Context, query, serverInfo string, prior []llm.Message, priorTokens int) (*RunResult, error) {
|
||||
a.totalTokens = priorTokens
|
||||
start := time.Now()
|
||||
|
||||
// Evolver PREDICT:获取历史经验注入 system prompt
|
||||
@@ -127,10 +137,11 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string) (*tools.Resul
|
||||
toolDescs := a.buildToolDescriptions()
|
||||
systemPrompt := prompt.BuildSystemPrompt(serverInfo, toolDescs, evolveCtx)
|
||||
|
||||
messages := []llm.Message{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: query},
|
||||
messages := []llm.Message{{Role: "system", Content: systemPrompt}}
|
||||
if len(prior) > 0 {
|
||||
messages = append(messages, prior...)
|
||||
}
|
||||
messages = append(messages, llm.Message{Role: "user", Content: query})
|
||||
llmTools := a.registry.ToLLMTools()
|
||||
|
||||
toolCallHistory := make(map[string]int) // 循环检测
|
||||
@@ -144,7 +155,7 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string) (*tools.Resul
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.emit(Event{Type: EventProgress, Round: iter + 1})
|
||||
a.emit(Event{Type: EventProgress, Round: a.roundNum(iter)})
|
||||
|
||||
// 自适应检查点
|
||||
if hint := a.checkAdaptiveCheckpoint(iter, toolCallHistory, consecutiveFailures, totalToolCalls); hint != "" {
|
||||
@@ -154,12 +165,17 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string) (*tools.Resul
|
||||
// 裁剪历史
|
||||
trimmed := a.trimMessages(messages)
|
||||
|
||||
// 调用 LLM
|
||||
resp, err := a.llm.Complete(ctx, &llm.CompletionRequest{
|
||||
// 调用 LLM(流式推送思考增量,降低长推理等待感) // Logan.Li
|
||||
round := a.roundNum(iter)
|
||||
resp, err := a.llm.Stream(ctx, &llm.CompletionRequest{
|
||||
Messages: trimmed,
|
||||
Tools: llmTools,
|
||||
Temperature: a.config.Temperature,
|
||||
MaxTokens: a.config.MaxTokens,
|
||||
}, func(delta string) {
|
||||
if strings.TrimSpace(delta) != "" {
|
||||
a.emit(Event{Type: EventThoughtDelta, Round: round, Thought: delta})
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
a.emit(Event{Type: EventError, Message: "LLM 调用失败: " + err.Error()})
|
||||
@@ -169,9 +185,9 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string) (*tools.Resul
|
||||
a.totalTokens += resp.Usage.TotalTokens
|
||||
a.emit(Event{Type: EventProgress, Tokens: a.totalTokens})
|
||||
|
||||
// 推送思考文本
|
||||
// 推送完整思考文本(供持久化与回放) // Logan.Li
|
||||
if strings.TrimSpace(resp.Message.Content) != "" {
|
||||
a.emit(Event{Type: EventThought, Round: iter + 1, Thought: resp.Message.Content})
|
||||
a.emit(Event{Type: EventThought, Round: round, Thought: resp.Message.Content})
|
||||
}
|
||||
messages = append(messages, resp.Message)
|
||||
|
||||
@@ -180,11 +196,14 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string) (*tools.Resul
|
||||
answer := resp.Message.Content
|
||||
a.emit(Event{Type: EventDone, Answer: answer, Tokens: a.totalTokens})
|
||||
a.triggerEvolve(ctx, query, serverInfo, answer, toolRecords, true, start)
|
||||
return &tools.Result{Success: true, Output: answer}, nil
|
||||
return &RunResult{
|
||||
Success: true, Output: answer,
|
||||
History: a.extractHistory(messages), TotalTokens: a.totalTokens,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 处理工具调用
|
||||
observations, records, failInc := a.processToolCalls(ctx, resp.Message.ToolCalls, toolCallHistory, iter+1)
|
||||
observations, records, failInc := a.processToolCalls(ctx, resp.Message.ToolCalls, toolCallHistory, round)
|
||||
toolRecords = append(toolRecords, records...)
|
||||
messages = append(messages, observations...)
|
||||
totalToolCalls += len(resp.Message.ToolCalls)
|
||||
@@ -195,7 +214,9 @@ func (a *Agent) Run(ctx context.Context, query, serverInfo string) (*tools.Resul
|
||||
msg := fmt.Sprintf("达到最大迭代次数(%d),任务未能完成。可能任务过于复杂或需要更多步骤。", a.config.MaxIterations)
|
||||
a.emit(Event{Type: EventError, Message: msg, Tokens: a.totalTokens})
|
||||
a.triggerEvolve(ctx, query, serverInfo, "", toolRecords, false, start)
|
||||
return nil, fmt.Errorf("%s", msg)
|
||||
return &RunResult{
|
||||
Success: false, History: a.extractHistory(messages), TotalTokens: a.totalTokens,
|
||||
}, fmt.Errorf("%s", msg)
|
||||
}
|
||||
|
||||
// processToolCalls 处理工具调用,返回 observation 消息 + 记录 // Logan.Li
|
||||
@@ -264,6 +285,13 @@ func (a *Agent) processToolCalls(ctx context.Context, calls []llm.ToolCall, hist
|
||||
success = false
|
||||
}
|
||||
|
||||
// 超长 ssh 输出自动分析,避免 token 爆炸 // Logan.Li
|
||||
if tc.Function.Name == "ssh_execute" && success && len(output) > a.autoAnalyzeThreshold() {
|
||||
if analyzed := a.autoAnalyzeOutput(ctx, output); analyzed != "" {
|
||||
output = analyzed
|
||||
}
|
||||
}
|
||||
|
||||
// 输出截断
|
||||
if len(output) > a.config.OutputMaxLength {
|
||||
output = output[:a.config.OutputMaxLength] +
|
||||
@@ -313,6 +341,43 @@ func (a *Agent) triggerEvolve(ctx context.Context, query, serverHost, answer str
|
||||
}()
|
||||
}
|
||||
|
||||
// roundNum 计算对外展示的 ReAct 轮次(含多轮追问偏移) // Logan.Li
|
||||
func (a *Agent) roundNum(iter int) int {
|
||||
return a.config.RoundOffset + iter + 1
|
||||
}
|
||||
|
||||
// extractHistory 提取 system 之后的对话历史(供多轮追问) // Logan.Li
|
||||
func (a *Agent) extractHistory(messages []llm.Message) []llm.Message {
|
||||
if len(messages) <= 1 {
|
||||
return nil
|
||||
}
|
||||
out := make([]llm.Message, len(messages)-1)
|
||||
copy(out, messages[1:])
|
||||
return out
|
||||
}
|
||||
|
||||
// autoAnalyzeThreshold 触发自动 analyze_output 的输出长度阈值 // Logan.Li
|
||||
func (a *Agent) autoAnalyzeThreshold() int {
|
||||
t := a.config.OutputMaxLength / 2
|
||||
if t < 4000 {
|
||||
return 4000
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// autoAnalyzeOutput 对超长命令输出自动调用 analyze_output // Logan.Li
|
||||
func (a *Agent) autoAnalyzeOutput(ctx context.Context, output string) string {
|
||||
tool, err := a.registry.Get("analyze_output")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
r, err := tool.Execute(ctx, map[string]interface{}{"output": output})
|
||||
if err != nil || r == nil || !r.Success || strings.TrimSpace(r.Output) == "" {
|
||||
return ""
|
||||
}
|
||||
return r.Output + "\n[系统自动分析超长输出]"
|
||||
}
|
||||
|
||||
// buildToolDescriptions 工具描述文本 // Logan.Li
|
||||
func (a *Agent) buildToolDescriptions() string {
|
||||
var sb strings.Builder
|
||||
@@ -428,9 +493,5 @@ func estimateTokens(s string) int {
|
||||
return len([]rune(s))/3 + 1
|
||||
}
|
||||
|
||||
// Close 释放资源 // Logan.Li
|
||||
func (a *Agent) Close() {
|
||||
if a.registry != nil {
|
||||
// Registry 无 Closer 实现(Web 工具无连接池),保留接口位
|
||||
}
|
||||
}
|
||||
// Close 释放资源(SSH 长连接由 Engine.Close 统一释放) // Logan.Li
|
||||
func (a *Agent) Close() {}
|
||||
Reference in new issue
Block a user