feat: Agent CLI 与终端审计回放全面优化

- Agent CLI: 多轮对话、SSH 长连接、流式推理、Evolver 按用户隔离、历史侧栏
- 终端审计: 回放播放器、命令书签、筛选分页导出、命令输出统计、录制落盘修复
- 修复 session_commands 表迁移、Kill 竞态导致录制丢失、无录制会话回放提示
- 审计历史页只读(移除删除),移除回放弹框录制搜索
This commit is contained in:
Logan committed 2026-07-01 12:13:53 +08:00
1 parent 9a3dd304f7
commit 1acb0c49af
81 files changed
+3916 -1655

No files matched your search

+83 -22
View File
@@ -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() {}