From 6122a1d88143d41d994cb7d5df7d646f764d8668 Mon Sep 17 00:00:00 2001 From: mlogclub Date: Tue, 23 Jun 2026 23:22:18 +0800 Subject: [PATCH] refactor: remove AgentRunLog related code and components - Deleted AgentRunLogResponse struct from skill_response.go. - Removed agent_run_log_repository.go and agent_run_log_service.go files. - Eliminated AgentRunLogDetailDialog component and its related logic from the dashboard. - Removed agent run log fetching functions from admin API. - Updated navigation and localization files to remove references to agent run logs. --- cmd/generator/generator.go | 1 - internal/ai/runtime/reply_helpers_test.go | 108 +---- internal/ai/runtime/reply_runlog_service.go | 407 ---------------- .../ai/runtime/reply_runlog_service_test.go | 57 --- internal/ai/runtime/reply_service.go | 2 - internal/ai/runtime/reply_service_test.go | 57 +-- internal/ai/runtime/reply_trigger_service.go | 13 - internal/ai/runtime/runtime_reply_executor.go | 2 +- .../ai/runtime/runtime_trace_extractors.go | 105 +++++ internal/bootstrap/routes.go | 5 - internal/bootstrap/server.go | 1 - internal/builders/agent_run_log_builder.go | 94 ---- .../dashboard/agent_run_log_handler.go | 66 --- internal/models/models.go | 31 -- internal/pkg/dto/response/skill_response.go | 35 -- .../repositories/agent_run_log_repository.go | 91 ---- internal/services/agent_run_log_service.go | 86 ---- .../agent-run-logs/_components/detail.tsx | 440 ------------------ web/app/dashboard/agent-run-logs/page.tsx | 368 --------------- web/lib/api/admin.ts | 46 -- web/lib/navigation.tsx | 7 - web/messages/en-US.json | 65 --- web/messages/zh-CN.json | 65 --- 23 files changed, 126 insertions(+), 2026 deletions(-) delete mode 100644 internal/ai/runtime/reply_runlog_service.go delete mode 100644 internal/ai/runtime/reply_runlog_service_test.go create mode 100644 internal/ai/runtime/runtime_trace_extractors.go delete mode 100644 internal/builders/agent_run_log_builder.go delete mode 100644 internal/handlers/dashboard/agent_run_log_handler.go delete mode 100644 internal/repositories/agent_run_log_repository.go delete mode 100644 internal/services/agent_run_log_service.go delete mode 100644 web/app/dashboard/agent-run-logs/_components/detail.tsx delete mode 100644 web/app/dashboard/agent-run-logs/page.tsx diff --git a/cmd/generator/generator.go b/cmd/generator/generator.go index e0640cb..5abaedc 100644 --- a/cmd/generator/generator.go +++ b/cmd/generator/generator.go @@ -58,7 +58,6 @@ func main() { codegen.GetGenerateStruct(&models.AIConfig{}), codegen.GetGenerateStruct(&models.SkillDefinition{}), codegen.GetGenerateStruct(&models.SkillRunLog{}), - codegen.GetGenerateStruct(&models.AgentRunLog{}), codegen.GetGenerateStruct(&models.SystemConfig{}), ) diff --git a/internal/ai/runtime/reply_helpers_test.go b/internal/ai/runtime/reply_helpers_test.go index a22786e..30ac319 100644 --- a/internal/ai/runtime/reply_helpers_test.go +++ b/internal/ai/runtime/reply_helpers_test.go @@ -1,7 +1,6 @@ package runtime import ( - "strings" "testing" applicationruntime "agent-desk/internal/ai/application/runtime" @@ -9,64 +8,33 @@ import ( "agent-desk/internal/pkg/toolx" ) -func TestSummaryPrimaryToolCodePrefersToolSearchTarget(t *testing.T) { - summary := &applicationruntime.Summary{ - InvokedToolCodes: []string{toolx.BuiltinToolSearch.Code}, - TraceData: `{ - "toolSearch": { - "items": [ - {"targetToolCode":"mcp/server/tool_a"} - ] - } - }`, +func TestRuntimeTraceFinalAction(t *testing.T) { + if got := runtimeTraceFinalAction(&applicationruntime.Summary{Status: "completed", ReplyText: "ok"}); got != "reply" { + t.Fatalf("expected reply final action, got %q", got) } - - if got := summaryPrimaryToolCode(summary); got != "mcp/server/tool_a" { - t.Fatalf("unexpected primary tool code: %q", got) + if got := runtimeTraceFinalAction(&applicationruntime.Summary{Status: "completed"}); got != "completed" { + t.Fatalf("expected completed final action, got %q", got) } -} - -func TestToRunLogFinalAction(t *testing.T) { - if got := toRunLogFinalAction(&applicationruntime.Summary{WorkflowVersionID: 66, ReplyText: "ok"}); got != "workflow_reply" { - t.Fatalf("expected workflow_reply final action, got %q", got) - } - - if got := toRunLogFinalAction(&applicationruntime.Summary{PlannedSkillID: 44, ReplyText: "ok"}); got != "skill" { - t.Fatalf("expected skill final action, got %q", got) - } - - graphSummary := &applicationruntime.Summary{ - ReplyText: "ok", - TraceData: `{ - "graphTools": { - "items": [ - {"toolCode":"` + toolx.GraphAnalyzeConversation.Code + `"} - ] - } - }`, - } - if got := toRunLogFinalAction(graphSummary); got != "graph" { - t.Fatalf("expected graph final action, got %q", got) - } - - if got := toRunLogFinalAction(&applicationruntime.Summary{Status: "fallback"}); got != "fallback" { + if got := runtimeTraceFinalAction(&applicationruntime.Summary{Status: "fallback"}); got != "fallback" { t.Fatalf("expected fallback final action, got %q", got) } } -func TestBuildRunLogPlanUsesWorkflowSummary(t *testing.T) { - plannedAction, plannedToolCode, planReason := buildRunLogPlan(&applicationruntime.Summary{ - WorkflowVersionID: 66, - Status: "completed", - }) - if plannedAction != "workflow" { - t.Fatalf("expected workflow planned action, got %q", plannedAction) +func TestExtractRuntimeToolTraces(t *testing.T) { + summary := &applicationruntime.Summary{ + TraceData: `{ + "toolSearch": {"items": [{"targetToolCode":"mcp/server/tool_a"}]}, + "graphTools": {"items": [{"toolCode":"` + toolx.GraphAnalyzeConversation.Code + `"}]} + }`, } - if plannedToolCode != "workflow/66" { - t.Fatalf("expected workflow planned tool code, got %q", plannedToolCode) + if got := extractToolSearchTrace(summary); got == "" { + t.Fatalf("expected tool search trace") } - if planReason == "" { - t.Fatalf("expected plan reason") + if got := extractGraphToolTrace(summary); got == "" { + t.Fatalf("expected graph tool trace") + } + if got := firstGraphToolCode(summary); got != toolx.GraphAnalyzeConversation.Code { + t.Fatalf("unexpected graph tool code: %q", got) } } @@ -103,44 +71,6 @@ func TestBuildConversationInterruptStoresWorkflowCheckpointData(t *testing.T) { } } -func TestGraphPlanReason(t *testing.T) { - summary := &applicationruntime.Summary{ - TraceData: `{ - "graphTools": { - "items": [ - { - "toolCode":"` + toolx.GraphTriageServiceRequest.Code + `", - "recommendedAction":"create_ticket", - "ticketDraftReady": true - } - ] - } - }`, - } - got := graphPlanReason(summary) - if !strings.Contains(got, "create_ticket") || !strings.Contains(got, "ready ticket draft") { - t.Fatalf("unexpected graph plan reason: %q", got) - } -} - -func TestExtractHandoffReason(t *testing.T) { - summary := &applicationruntime.Summary{ - TraceData: `{ - "graphTools": { - "items": [ - { - "toolCode":"` + toolx.GraphHandoffConversation.Code + `", - "arguments":{"reason":" 用户明确要求人工处理 "} - } - ] - } - }`, - } - if got := extractHandoffReason(summary); got != "用户明确要求人工处理" { - t.Fatalf("unexpected handoff reason: %q", got) - } -} - type fakeErr string func (e fakeErr) Error() string { diff --git a/internal/ai/runtime/reply_runlog_service.go b/internal/ai/runtime/reply_runlog_service.go deleted file mode 100644 index a088ca9..0000000 --- a/internal/ai/runtime/reply_runlog_service.go +++ /dev/null @@ -1,407 +0,0 @@ -package runtime - -import ( - "encoding/json" - "log/slog" - "strconv" - "strings" - "time" - - applicationruntime "agent-desk/internal/ai/application/runtime" - "agent-desk/internal/models" - "agent-desk/internal/pkg/toolx" - svc "agent-desk/internal/services" -) - -func newReplyRunLogService() *replyRunLogService { - return &replyRunLogService{} -} - -type replyRunLogService struct{} - -type replyRunLogInput struct { - StartedAt time.Time - Message models.Message - Conversation models.Conversation - AIAgent models.AIAgent - Question string - RunErr error - Trace *aiReplyTraceData - Summary *applicationruntime.Summary -} - -func (s *replyRunLogService) Write(input replyRunLogInput) { - errorMessage := "" - if input.RunErr != nil { - errorMessage = input.RunErr.Error() - } else if input.Summary != nil && strings.TrimSpace(input.Summary.ErrorMessage) != "" { - errorMessage = strings.TrimSpace(input.Summary.ErrorMessage) - } - traceData := buildAIReplyTraceData(input.Trace) - plannedAction, plannedToolCode, planReason := buildRunLogPlan(input.Summary) - logItem := &models.AgentRunLog{ - ConversationID: input.Conversation.ID, - MessageID: input.Message.ID, - RequestID: input.Message.RequestID, - AIAgentID: input.AIAgent.ID, - AIConfigID: input.AIAgent.AIConfigID, - UserMessage: strings.TrimSpace(input.Question), - PlannedAction: plannedAction, - PlannedSkillID: summaryPlannedSkillID(input.Summary), - PlannedSkillName: strings.TrimSpace(summaryPlannedSkillName(input.Summary)), - SkillRouteTrace: strings.TrimSpace(summarySkillRouteTrace(input.Summary)), - ToolSearchTrace: extractToolSearchTrace(input.Summary), - GraphToolTrace: extractGraphToolTrace(input.Summary), - GraphToolCode: firstGraphToolCode(input.Summary), - HandoffReason: extractHandoffReason(input.Summary), - PlannedToolCode: plannedToolCode, - PlanReason: planReason, - InterruptType: firstInterruptType(input.Summary), - ResumeSource: runLogResumeSource(input.Trace), - FinalAction: toRunLogFinalAction(input.Summary), - FinalStatus: runLogFinalStatus(input.Summary), - ReplyText: buildRunLogReplyText(input.Summary), - ErrorMessage: errorMessage, - LatencyMs: time.Since(input.StartedAt).Milliseconds(), - TraceData: traceData, - CreatedAt: time.Now(), - } - if err := svc.AgentRunLogService.Create(logItem); err != nil { - slog.Warn("create agent run log failed", - "requestId", input.Message.RequestID, - "message_id", input.Message.ID, - "conversation_id", logItem.ConversationID, - "ai_agent_id", input.AIAgent.ID, - "error", err) - } -} - -func buildAIReplyTraceData(trace *aiReplyTraceData) string { - if trace == nil { - return "" - } - data, err := json.Marshal(trace) - if err != nil { - return "" - } - return string(data) -} - -func buildRunLogPlan(summary *applicationruntime.Summary) (plannedAction, plannedToolCode, planReason string) { - if summary == nil { - return "", "", "" - } - if isWorkflowSummary(summary) { - return "workflow", workflowPlannedToolCode(summary), "workflow executed" - } - if summaryPlannedSkillID(summary) > 0 { - reason := strings.TrimSpace(summary.PlanReason) - if reason == "" { - reason = "skill_selected" - } - return "skill", "", reason - } - if strings.TrimSpace(summary.Status) == "expired" { - return "interrupt", "", "pending interrupt checkpoint expired" - } - if summary.Interrupted { - if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" { - reason := graphPlanReason(summary) - if reason == "" { - reason = "graph tool interrupted and is waiting for user confirmation" - } - return "graph", graphToolCode, reason - } - return "tool", summaryPrimaryToolCode(summary), "agent interrupted and is waiting for user confirmation" - } - if len(summary.InvokedToolCodes) > 0 { - if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" { - reason := graphPlanReason(summary) - if reason == "" { - reason = "agent invoked graph tool" - } - return "graph", graphToolCode, reason - } - toolCode := summaryPrimaryToolCode(summary) - reason := "agent invoked MCP tool" - if toolCode != "" && toolCode != firstInvokedToolCode(summary) { - reason = "agent invoked dynamic tool via tool_search" - } - return "tool", toolCode, reason - } - if strings.TrimSpace(summary.ReplyText) != "" { - return "reply", "", "agent replied directly" - } - if strings.TrimSpace(summary.ErrorMessage) != "" { - return "error", "", "runtime execution failed" - } - return "fallback", "", "runtime produced empty reply" -} - -func toRunLogFinalAction(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - if isWorkflowSummary(summary) { - return workflowFinalAction(summary) - } - if summaryPlannedSkillID(summary) > 0 && strings.TrimSpace(summary.ReplyText) != "" { - return "skill" - } - if graphToolCode := firstGraphToolCode(summary); graphToolCode != "" && strings.TrimSpace(summary.ReplyText) != "" { - return "graph" - } - switch strings.TrimSpace(summary.Status) { - case "completed": - return "reply" - case "fallback": - return "fallback" - case "error": - return "error" - case "interrupted": - return "interrupted" - case "expired": - return "expired" - default: - return strings.TrimSpace(summary.Status) - } -} - -func isWorkflowSummary(summary *applicationruntime.Summary) bool { - return summary != nil && summary.WorkflowVersionID > 0 -} - -func workflowPlannedToolCode(summary *applicationruntime.Summary) string { - if summary == nil || summary.WorkflowVersionID <= 0 { - return "" - } - return "workflow/" + strconv.FormatInt(summary.WorkflowVersionID, 10) -} - -func workflowFinalAction(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - switch strings.TrimSpace(summary.Status) { - case "interrupted": - return "workflow_interrupted" - case "error": - return "workflow_error" - case "expired": - return "workflow_expired" - } - if strings.TrimSpace(summary.ReplyText) != "" { - return "workflow_reply" - } - return "workflow_completed" -} - -func buildRunLogReplyText(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - return strings.TrimSpace(summary.ReplyText) -} - -func summaryPlannedSkillID(summary *applicationruntime.Summary) int64 { - if summary == nil { - return 0 - } - return summary.PlannedSkillID -} - -func summaryPlannedSkillName(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - return strings.TrimSpace(summary.PlannedSkillName) -} - -func summarySkillRouteTrace(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - return strings.TrimSpace(summary.SkillRouteTrace) -} - -func runLogResumeSource(trace *aiReplyTraceData) string { - if trace == nil { - return "" - } - return strings.TrimSpace(trace.ResumeSource) -} - -func runLogFinalStatus(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - return strings.TrimSpace(summary.Status) -} - -func summaryPrimaryToolCode(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - toolCode := firstInvokedToolCode(summary) - if toolCode != toolx.BuiltinToolSearch.Code { - return toolCode - } - if targetToolCode := firstToolSearchTargetToolCode(summary); targetToolCode != "" { - return targetToolCode - } - return toolCode -} - -func extractToolSearchTrace(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - trace := parseRuntimeTraceData(summary.TraceData) - if len(trace.ToolSearch.Items) == 0 { - return "" - } - buf, err := json.Marshal(trace.ToolSearch) - if err != nil { - return "" - } - return string(buf) -} - -func extractGraphToolTrace(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - trace := parseRuntimeTraceData(summary.TraceData) - if len(trace.GraphTools.Items) == 0 { - return "" - } - buf, err := json.Marshal(trace.GraphTools) - if err != nil { - return "" - } - return string(buf) -} - -func firstToolSearchTargetToolCode(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - trace := parseRuntimeTraceData(summary.TraceData) - for _, item := range trace.ToolSearch.Items { - toolCode := strings.TrimSpace(item.TargetToolCode) - if toolCode != "" { - return toolCode - } - if len(item.CandidateToolCodes) == 1 { - toolCode = strings.TrimSpace(item.CandidateToolCodes[0]) - if toolCode != "" { - return toolCode - } - } - } - return "" -} - -func firstGraphToolCode(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - trace := parseRuntimeTraceData(summary.TraceData) - for _, item := range trace.GraphTools.Items { - toolCode := strings.TrimSpace(item.ToolCode) - if toolCode != "" { - return toolCode - } - } - return "" -} - -func extractHandoffReason(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - trace := parseRuntimeTraceData(summary.TraceData) - for _, item := range trace.GraphTools.Items { - if strings.TrimSpace(item.ToolCode) != toolx.GraphHandoffConversation.Code { - continue - } - if len(item.Arguments) == 0 { - return "" - } - var args runtimeTraceHandoffArguments - if err := json.Unmarshal(item.Arguments, &args); err != nil { - return "" - } - return strings.TrimSpace(args.Reason) - } - return "" -} - -func graphPlanReason(summary *applicationruntime.Summary) string { - if summary == nil { - return "" - } - trace := parseRuntimeTraceData(summary.TraceData) - for _, item := range trace.GraphTools.Items { - toolCode := strings.TrimSpace(item.ToolCode) - switch toolCode { - case toolx.GraphTriageServiceRequest.Code: - recommendedAction := strings.TrimSpace(item.RecommendedAction) - if recommendedAction == "" { - return "graph tool triaged service request" - } - if item.TicketDraftReady { - return "graph tool triaged service request: " + recommendedAction + " with ready ticket draft" - } - return "graph tool triaged service request: " + recommendedAction - case toolx.GraphAnalyzeConversation.Code: - recommendedAction := strings.TrimSpace(item.RecommendedAction) - riskLevel := strings.TrimSpace(item.RiskLevel) - switch { - case recommendedAction != "" && riskLevel != "": - return "graph tool analyzed conversation: " + recommendedAction + " (" + riskLevel + " risk)" - case recommendedAction != "": - return "graph tool analyzed conversation: " + recommendedAction - case riskLevel != "": - return "graph tool analyzed conversation (" + riskLevel + " risk)" - default: - return "graph tool analyzed conversation" - } - } - } - return "" -} - -type runtimeTraceProjection struct { - ToolSearch struct { - Items []struct { - TargetToolCode string `json:"targetToolCode"` - CandidateToolCodes []string `json:"candidateToolCodes"` - } `json:"items"` - } `json:"toolSearch"` - GraphTools struct { - Items []struct { - ToolCode string `json:"toolCode"` - Arguments json.RawMessage `json:"arguments"` - RecommendedAction string `json:"recommendedAction"` - RiskLevel string `json:"riskLevel"` - TicketDraftReady bool `json:"ticketDraftReady"` - } `json:"items"` - } `json:"graphTools"` -} - -type runtimeTraceHandoffArguments struct { - Reason string `json:"reason"` -} - -func parseRuntimeTraceData(raw string) runtimeTraceProjection { - raw = strings.TrimSpace(raw) - if raw == "" { - return runtimeTraceProjection{} - } - var trace runtimeTraceProjection - if err := json.Unmarshal([]byte(raw), &trace); err != nil { - return runtimeTraceProjection{} - } - return trace -} diff --git a/internal/ai/runtime/reply_runlog_service_test.go b/internal/ai/runtime/reply_runlog_service_test.go deleted file mode 100644 index a971916..0000000 --- a/internal/ai/runtime/reply_runlog_service_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package runtime - -import ( - "strings" - "testing" - "time" - - "agent-desk/internal/models" - "agent-desk/internal/pkg/enums" - - "github.com/glebarez/sqlite" - "github.com/mlogclub/simple/sqls" - "gorm.io/gorm" - "gorm.io/gorm/schema" -) - -func TestReplyRunLogStoresRequestID(t *testing.T) { - dbName := "reply_runlog_trace_test_" + strings.NewReplacer("/", "_").Replace(t.Name()) - db, err := gorm.Open(sqlite.Open("file:"+dbName+"?mode=memory&cache=shared"), &gorm.Config{ - NamingStrategy: schema.NamingStrategy{ - TablePrefix: "t_", - SingularTable: true, - }, - }) - if err != nil { - t.Fatalf("open sqlite db: %v", err) - } - sqlDB, err := db.DB() - if err != nil { - t.Fatalf("get sqlite db: %v", err) - } - t.Cleanup(func() { - if err := sqlDB.Close(); err != nil { - t.Fatalf("close sqlite db: %v", err) - } - }) - if err := db.AutoMigrate(&models.AgentRunLog{}); err != nil { - t.Fatalf("auto migrate: %v", err) - } - sqls.SetDB(db) - - newReplyRunLogService().Write(replyRunLogInput{ - StartedAt: time.Now(), - Message: models.Message{ID: 22, RequestID: "trace-123", SenderType: enums.IMSenderTypeCustomer, Content: "hello"}, - Conversation: models.Conversation{ID: 11}, - AIAgent: models.AIAgent{ID: 33, AIConfigID: 44}, - Question: "hello", - }) - - var item models.AgentRunLog - if err := db.First(&item).Error; err != nil { - t.Fatalf("find run log: %v", err) - } - if item.RequestID != "trace-123" { - t.Fatalf("RequestID=%q want %q", item.RequestID, "trace-123") - } -} diff --git a/internal/ai/runtime/reply_service.go b/internal/ai/runtime/reply_service.go index 3997caf..41fe3c7 100644 --- a/internal/ai/runtime/reply_service.go +++ b/internal/ai/runtime/reply_service.go @@ -19,7 +19,6 @@ func newAIReplyService() *aiReplyService { executor: newRuntimeReplyExecutor(), interrupts: newReplyInterruptService(), commit: newReplyCommitService(), - runlog: newReplyRunLogService(), } } @@ -28,7 +27,6 @@ type aiReplyService struct { executor *runtimeReplyExecutor interrupts *replyInterruptService commit *replyCommitService - runlog *replyRunLogService } func firstInvokedToolCode(summary *applicationruntime.Summary) string { diff --git a/internal/ai/runtime/reply_service_test.go b/internal/ai/runtime/reply_service_test.go index 07cdab6..7ec35c4 100644 --- a/internal/ai/runtime/reply_service_test.go +++ b/internal/ai/runtime/reply_service_test.go @@ -4,11 +4,9 @@ import ( "testing" "time" + applicationruntime "agent-desk/internal/ai/application/runtime" "agent-desk/internal/models" "agent-desk/internal/pkg/enums" - "agent-desk/internal/pkg/toolx" - - applicationruntime "agent-desk/internal/ai/application/runtime" ) func TestReplyEligibilityCanReply(t *testing.T) { @@ -70,59 +68,6 @@ func TestResolveReplyTimeout(t *testing.T) { } } -func TestBuildRunLogPlan(t *testing.T) { - summary := &applicationruntime.Summary{ - PlannedSkillID: 44, - PlanReason: "manual", - } - action, toolCode, reason := buildRunLogPlan(summary) - if action != "skill" || toolCode != "" || reason != "manual" { - t.Fatalf("unexpected skill plan result: action=%q toolCode=%q reason=%q", action, toolCode, reason) - } - - summary = &applicationruntime.Summary{ - Interrupted: true, - TraceData: `{ - "graphTools": { - "items": [ - { - "toolCode": "` + toolx.GraphTriageServiceRequest.Code + `", - "recommendedAction": "create_ticket", - "ticketDraftReady": true - } - ] - } - }`, - } - action, toolCode, reason = buildRunLogPlan(summary) - if action != "graph" || toolCode != toolx.GraphTriageServiceRequest.Code || reason == "" { - t.Fatalf("unexpected graph interrupt result: action=%q toolCode=%q reason=%q", action, toolCode, reason) - } - - summary = &applicationruntime.Summary{ - InvokedToolCodes: []string{toolx.BuiltinToolSearch.Code}, - TraceData: `{ - "toolSearch": { - "items": [ - { - "targetToolCode": "mcp/test/search" - } - ] - } - }`, - } - action, toolCode, reason = buildRunLogPlan(summary) - if action != "tool" || toolCode != "mcp/test/search" || reason != "agent invoked dynamic tool via tool_search" { - t.Fatalf("unexpected dynamic tool result: action=%q toolCode=%q reason=%q", action, toolCode, reason) - } - - summary = &applicationruntime.Summary{ReplyText: "done"} - action, toolCode, reason = buildRunLogPlan(summary) - if action != "reply" || toolCode != "" || reason != "agent replied directly" { - t.Fatalf("unexpected reply result: action=%q toolCode=%q reason=%q", action, toolCode, reason) - } -} - func TestResolveInterruptPrompt(t *testing.T) { summary := &applicationruntime.Summary{ Interrupts: []applicationruntime.InterruptContextSummary{ diff --git a/internal/ai/runtime/reply_trigger_service.go b/internal/ai/runtime/reply_trigger_service.go index 538c74d..370c1ac 100644 --- a/internal/ai/runtime/reply_trigger_service.go +++ b/internal/ai/runtime/reply_trigger_service.go @@ -45,7 +45,6 @@ func (s *aiReplyService) TriggerReplyAsync(conversation models.Conversation, mes } func (s *aiReplyService) TriggerReply(ctx context.Context, conversation models.Conversation, message models.Message, aiAgent models.AIAgent) (retErr error) { - startedAt := time.Now() trace := &aiReplyTraceData{Status: "started"} var summary *applicationruntime.Summary replyCtx := aiReplyContext{ @@ -61,18 +60,6 @@ func (s *aiReplyService) TriggerReply(ctx context.Context, conversation models.C if s.eligibility != nil && !s.eligibility.CanReply(conversation, message, aiAgent) { return nil } - defer func() { - s.runlog.Write(replyRunLogInput{ - StartedAt: startedAt, - Message: message, - Conversation: conversation, - AIAgent: aiAgent, - Question: message.Content, - RunErr: retErr, - Trace: trace, - Summary: summary, - }) - }() if pendingInterrupt := svc.ConversationInterruptService.FindLatestPendingByConversationID(conversation.ID); pendingInterrupt != nil { replyCtx.PendingInterrupt = pendingInterrupt return s.resumePendingInterrupt(ctx, replyCtx) diff --git a/internal/ai/runtime/runtime_reply_executor.go b/internal/ai/runtime/runtime_reply_executor.go index 67b6bd3..828bba3 100644 --- a/internal/ai/runtime/runtime_reply_executor.go +++ b/internal/ai/runtime/runtime_reply_executor.go @@ -95,7 +95,7 @@ func (e *runtimeReplyExecutor) fillTraceFromSummary(trace *aiReplyTraceData, sum return } trace.Status = "runtime_prepared" - trace.FinalAction = toRunLogFinalAction(summary) + trace.FinalAction = runtimeTraceFinalAction(summary) if summary != nil && strings.TrimSpace(summary.TraceData) != "" { trace.Runtime = json.RawMessage(summary.TraceData) } diff --git a/internal/ai/runtime/runtime_trace_extractors.go b/internal/ai/runtime/runtime_trace_extractors.go new file mode 100644 index 0000000..9280392 --- /dev/null +++ b/internal/ai/runtime/runtime_trace_extractors.go @@ -0,0 +1,105 @@ +package runtime + +import ( + "encoding/json" + "strings" + + applicationruntime "agent-desk/internal/ai/application/runtime" +) + +func runtimeTraceFinalAction(summary *applicationruntime.Summary) string { + if summary == nil { + return "" + } + switch strings.TrimSpace(summary.Status) { + case "completed": + if strings.TrimSpace(summary.ReplyText) != "" { + return "reply" + } + return "completed" + case "fallback": + return "fallback" + case "error": + return "error" + case "interrupted": + return "interrupted" + case "expired": + return "expired" + default: + return strings.TrimSpace(summary.Status) + } +} + +func extractToolSearchTrace(summary *applicationruntime.Summary) string { + if summary == nil { + return "" + } + trace := parseRuntimeTraceData(summary.TraceData) + if len(trace.ToolSearch.Items) == 0 { + return "" + } + buf, err := json.Marshal(trace.ToolSearch) + if err != nil { + return "" + } + return string(buf) +} + +func extractGraphToolTrace(summary *applicationruntime.Summary) string { + if summary == nil { + return "" + } + trace := parseRuntimeTraceData(summary.TraceData) + if len(trace.GraphTools.Items) == 0 { + return "" + } + buf, err := json.Marshal(trace.GraphTools) + if err != nil { + return "" + } + return string(buf) +} + +func firstGraphToolCode(summary *applicationruntime.Summary) string { + if summary == nil { + return "" + } + trace := parseRuntimeTraceData(summary.TraceData) + for _, item := range trace.GraphTools.Items { + toolCode := strings.TrimSpace(item.ToolCode) + if toolCode != "" { + return toolCode + } + } + return "" +} + +type runtimeTraceProjection struct { + ToolSearch struct { + Items []struct { + TargetToolCode string `json:"targetToolCode"` + CandidateToolCodes []string `json:"candidateToolCodes"` + } `json:"items"` + } `json:"toolSearch"` + GraphTools struct { + Items []struct { + ToolCode string `json:"toolCode"` + Arguments json.RawMessage `json:"arguments"` + RecommendedAction string `json:"recommendedAction"` + RiskLevel string `json:"riskLevel"` + TicketDraftReady bool `json:"ticketDraftReady"` + } `json:"items"` + } `json:"graphTools"` +} + +func parseRuntimeTraceData(raw string) runtimeTraceProjection { + raw = strings.TrimSpace(raw) + if raw == "" { + return runtimeTraceProjection{} + } + var trace runtimeTraceProjection + if err := json.Unmarshal([]byte(raw), &trace); err != nil { + return runtimeTraceProjection{} + } + return trace +} diff --git a/internal/bootstrap/routes.go b/internal/bootstrap/routes.go index 27f2c20..5f427ac 100644 --- a/internal/bootstrap/routes.go +++ b/internal/bootstrap/routes.go @@ -308,11 +308,6 @@ func registerDashboardKnowledgeRetrieveLogRoutes(group *gin.RouterGroup) { group.Any("/list", dashboard.KnowledgeRetrieveLogAnyList) } -func registerDashboardAgentRunLogRoutes(group *gin.RouterGroup) { - group.GET("/:id", dashboard.AgentRunLogGetBy) - group.Any("/list", dashboard.AgentRunLogAnyList) -} - func registerDashboardSkillDefinitionRoutes(group *gin.RouterGroup) { group.GET("/:id", dashboard.SkillDefinitionGetBy) group.POST("/create", dashboard.SkillDefinitionPostCreate) diff --git a/internal/bootstrap/server.go b/internal/bootstrap/server.go index ba43f95..11c38ce 100644 --- a/internal/bootstrap/server.go +++ b/internal/bootstrap/server.go @@ -195,7 +195,6 @@ func addRouter(app *gin.Engine) { registerDashboardKnowledgeFAQRoutes(dashboardGroup.Group("/knowledge-faq")) registerDashboardKnowledgeRetrieveRoutes(dashboardGroup.Group("/knowledge-retrieve")) registerDashboardKnowledgeRetrieveLogRoutes(dashboardGroup.Group("/knowledge-retrieve-log")) - registerDashboardAgentRunLogRoutes(dashboardGroup.Group("/agent-run-log")) registerDashboardSkillDefinitionRoutes(dashboardGroup.Group("/skill-definition")) registerDashboardMCPRoutes(dashboardGroup.Group("/mcp")) diff --git a/internal/builders/agent_run_log_builder.go b/internal/builders/agent_run_log_builder.go deleted file mode 100644 index fc10e9f..0000000 --- a/internal/builders/agent_run_log_builder.go +++ /dev/null @@ -1,94 +0,0 @@ -package builders - -import ( - "agent-desk/internal/models" - "agent-desk/internal/pkg/dto/response" - "encoding/json" - "strings" -) - -func BuildAgentRunLog(item *models.AgentRunLog) response.AgentRunLogResponse { - if item == nil { - return response.AgentRunLogResponse{} - } - hitlStatus, hitlStatusName, hitlSummary := resolveAgentRunLogHITL(item) - recommendedAction, riskLevel, ticketDraftReady := resolveGraphOutcome(item.GraphToolTrace) - return response.AgentRunLogResponse{ - ID: item.ID, - ConversationID: item.ConversationID, - MessageID: item.MessageID, - RequestID: item.RequestID, - AIAgentID: item.AIAgentID, - AIConfigID: item.AIConfigID, - UserMessage: item.UserMessage, - PlannedAction: item.PlannedAction, - PlannedSkillID: item.PlannedSkillID, - PlannedSkillName: item.PlannedSkillName, - SkillRouteTrace: item.SkillRouteTrace, - ToolSearchTrace: item.ToolSearchTrace, - GraphToolTrace: item.GraphToolTrace, - GraphToolCode: item.GraphToolCode, - RecommendedAction: recommendedAction, - RiskLevel: riskLevel, - TicketDraftReady: ticketDraftReady, - HandoffReason: item.HandoffReason, - PlannedToolCode: item.PlannedToolCode, - PlanReason: item.PlanReason, - InterruptType: item.InterruptType, - ResumeSource: item.ResumeSource, - HitlStatus: hitlStatus, - HitlStatusName: hitlStatusName, - HitlSummary: hitlSummary, - FinalAction: item.FinalAction, - FinalStatus: item.FinalStatus, - ReplyText: item.ReplyText, - ErrorMessage: item.ErrorMessage, - LatencyMs: item.LatencyMs, - TraceData: item.TraceData, - CreatedAt: item.CreatedAt.Format("2006-01-02 15:04:05"), - } -} - -func resolveGraphOutcome(raw string) (recommendedAction, riskLevel string, ticketDraftReady bool) { - raw = strings.TrimSpace(raw) - if raw == "" { - return "", "", false - } - var payload struct { - Items []struct { - RecommendedAction string `json:"recommendedAction"` - RiskLevel string `json:"riskLevel"` - TicketDraftReady bool `json:"ticketDraftReady"` - } `json:"items"` - } - if err := json.Unmarshal([]byte(raw), &payload); err != nil { - return "", "", false - } - for _, item := range payload.Items { - if strings.TrimSpace(item.RecommendedAction) != "" || strings.TrimSpace(item.RiskLevel) != "" || item.TicketDraftReady { - return strings.TrimSpace(item.RecommendedAction), strings.TrimSpace(item.RiskLevel), item.TicketDraftReady - } - } - return "", "", false -} - -func resolveAgentRunLogHITL(item *models.AgentRunLog) (status, statusName, summary string) { - if item == nil { - return "", "", "" - } - replyText := strings.TrimSpace(item.ReplyText) - switch { - case strings.TrimSpace(item.FinalStatus) == "interrupted": - return "pending", "等待确认", "Graph Tool 已发起确认,正在等待用户回复。" - case strings.TrimSpace(item.FinalStatus) == "expired": - return "expired", "已过期", "确认 checkpoint 已失效,需要重新发起。" - case strings.Contains(replyText, "已取消本次工单创建") || strings.Contains(replyText, "已取消本次转人工"): - return "cancelled", "已取消", "用户已明确取消,本次确认流程已终止。" - case strings.TrimSpace(item.ResumeSource) != "": - return "confirmed", "已确认", "用户确认后已恢复执行,并完成后续流程。" - case strings.TrimSpace(item.InterruptType) != "": - return "triggered", "已触发", "本次运行涉及确认式 HITL 流程。" - default: - return "", "", "" - } -} diff --git a/internal/handlers/dashboard/agent_run_log_handler.go b/internal/handlers/dashboard/agent_run_log_handler.go deleted file mode 100644 index 3bb2d84..0000000 --- a/internal/handlers/dashboard/agent_run_log_handler.go +++ /dev/null @@ -1,66 +0,0 @@ -package dashboard - -import ( - "agent-desk/internal/builders" - "agent-desk/internal/pkg/constants" - "agent-desk/internal/pkg/dto/response" - "agent-desk/internal/pkg/httpx" - "agent-desk/internal/services" - - "agent-desk/internal/pkg/httpx/params" - - "github.com/gin-gonic/gin" - "github.com/mlogclub/simple/web" -) - -func AgentRunLogAnyList(ctx *gin.Context) { - if _, err := services.AuthService.RequirePermission(ctx, constants.PermissionConversationView); err != nil { - httpx.WriteJSON(ctx, err) - return - } - - cnd := params.NewPagedSqlCnd(ctx, - params.QueryFilter{ParamName: "conversationId"}, - params.QueryFilter{ParamName: "messageId"}, - params.QueryFilter{ParamName: "requestId"}, - params.QueryFilter{ParamName: "aiAgentId"}, - params.QueryFilter{ParamName: "plannedAction"}, - params.QueryFilter{ParamName: "plannedSkillId"}, - params.QueryFilter{ParamName: "graphToolCode"}, - params.QueryFilter{ParamName: "interruptType"}, - params.QueryFilter{ParamName: "resumeSource"}, - params.QueryFilter{ParamName: "finalStatus"}, - params.QueryFilter{ParamName: "handoffReason", Op: params.Like}, - params.QueryFilter{ParamName: "finalAction"}, - params.QueryFilter{ParamName: "userMessage", Op: params.Like}, - ).Desc("id") - if hitlStatus, _ := params.Get(ctx, "hitlStatus"); hitlStatus != "" && hitlStatus != "all" { - cnd = services.AgentRunLogService.ApplyHITLStatusFilter(cnd, hitlStatus) - } - queryParams := params.NewQueryParams(ctx) - queryParams.Cnd = *cnd - list, paging := services.AgentRunLogService.FindPageByParams(queryParams) - results := make([]response.AgentRunLogResponse, 0, len(list)) - for _, item := range list { - results = append(results, builders.BuildAgentRunLog(&item)) - } - httpx.WriteJSON(ctx, &web.PageResult{Results: results, Page: paging}) -} - -func AgentRunLogGetBy(ctx *gin.Context) { - id, ok := httpx.GetPathInt64(ctx, "id") - if !ok { - return - } - if _, err := services.AuthService.RequirePermission(ctx, constants.PermissionConversationView); err != nil { - httpx.WriteJSON(ctx, err) - return - } - - item := services.AgentRunLogService.Get(id) - if item == nil { - httpx.WriteJSON(ctx, httpx.JsonErrorMsg(ctx, "error.e0013")) - return - } - httpx.WriteJSON(ctx, builders.BuildAgentRunLog(item)) -} diff --git a/internal/models/models.go b/internal/models/models.go index d19d326..c169806 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -57,7 +57,6 @@ var Models = []any{ &KnowledgeFeedback{}, &SkillDefinition{}, &SkillRunLog{}, - &AgentRunLog{}, &AIWorkflow{}, &AIWorkflowVersion{}, &AIWorkflowRun{}, @@ -912,36 +911,6 @@ type SkillRunLog struct { CreatedAt time.Time `gorm:"type:datetime;not null;index"` // CreatedAt 为运行日志创建时间。 } -// AgentRunLog 表示一次客服 Agent 自动运行的总链路日志。 -type AgentRunLog struct { - ID int64 `gorm:"primaryKey;autoIncrement"` - ConversationID int64 `gorm:"type:bigint;not null;default:0;index"` - MessageID int64 `gorm:"type:bigint;not null;default:0;index"` - RequestID string `gorm:"type:varchar(128);not null;default:'';index"` - AIAgentID int64 `gorm:"type:bigint;not null;default:0;index"` - AIConfigID int64 `gorm:"type:bigint;not null;default:0;index"` - UserMessage string `gorm:"type:longtext"` - PlannedAction string `gorm:"type:varchar(30);not null;default:'';index"` - PlannedSkillID int64 `gorm:"type:bigint;not null;default:0;index"` - PlannedSkillName string `gorm:"type:varchar(100);not null;default:''"` - SkillRouteTrace string `gorm:"type:text"` - ToolSearchTrace string `gorm:"type:text"` - GraphToolTrace string `gorm:"type:text"` - GraphToolCode string `gorm:"type:varchar(200);not null;default:'';index"` - HandoffReason string `gorm:"type:varchar(500);not null;default:''"` - PlannedToolCode string `gorm:"type:varchar(200);not null;default:'';index"` - PlanReason string `gorm:"type:varchar(500);not null;default:''"` - InterruptType string `gorm:"type:varchar(50);not null;default:'';index"` - ResumeSource string `gorm:"type:varchar(50);not null;default:'';index"` - FinalAction string `gorm:"type:varchar(30);not null;default:'';index"` - FinalStatus string `gorm:"type:varchar(30);not null;default:'';index"` - ReplyText string `gorm:"type:longtext"` - ErrorMessage string `gorm:"type:text"` - LatencyMs int64 `gorm:"type:bigint;not null;default:0"` - TraceData string `gorm:"type:text"` - CreatedAt time.Time `gorm:"type:datetime;not null;index"` -} - // ConversationInterrupt 表示会话级待恢复中断记录。 type ConversationInterrupt struct { ID int64 `gorm:"primaryKey;autoIncrement"` diff --git a/internal/pkg/dto/response/skill_response.go b/internal/pkg/dto/response/skill_response.go index f7e1d03..494ceb9 100644 --- a/internal/pkg/dto/response/skill_response.go +++ b/internal/pkg/dto/response/skill_response.go @@ -38,38 +38,3 @@ type SkillDebugRunResponse struct { ConversationID int64 `json:"conversationId"` AIAgentID int64 `json:"aiAgentId"` } - -type AgentRunLogResponse struct { - ID int64 `json:"id"` - ConversationID int64 `json:"conversationId"` - MessageID int64 `json:"messageId"` - RequestID string `json:"requestId"` - AIAgentID int64 `json:"aiAgentId"` - AIConfigID int64 `json:"aiConfigId"` - UserMessage string `json:"userMessage"` - PlannedAction string `json:"plannedAction"` - PlannedSkillID int64 `json:"plannedSkillId"` - PlannedSkillName string `json:"plannedSkillName"` - SkillRouteTrace string `json:"skillRouteTrace"` - ToolSearchTrace string `json:"toolSearchTrace"` - GraphToolTrace string `json:"graphToolTrace"` - GraphToolCode string `json:"graphToolCode"` - RecommendedAction string `json:"recommendedAction"` - RiskLevel string `json:"riskLevel"` - TicketDraftReady bool `json:"ticketDraftReady"` - HandoffReason string `json:"handoffReason"` - PlannedToolCode string `json:"plannedToolCode"` - PlanReason string `json:"planReason"` - InterruptType string `json:"interruptType"` - ResumeSource string `json:"resumeSource"` - HitlStatus string `json:"hitlStatus"` - HitlStatusName string `json:"hitlStatusName"` - HitlSummary string `json:"hitlSummary"` - FinalAction string `json:"finalAction"` - FinalStatus string `json:"finalStatus"` - ReplyText string `json:"replyText"` - ErrorMessage string `json:"errorMessage"` - LatencyMs int64 `json:"latencyMs"` - TraceData string `json:"traceData"` - CreatedAt string `json:"createdAt"` -} diff --git a/internal/repositories/agent_run_log_repository.go b/internal/repositories/agent_run_log_repository.go deleted file mode 100644 index 9eab4f5..0000000 --- a/internal/repositories/agent_run_log_repository.go +++ /dev/null @@ -1,91 +0,0 @@ -package repositories - -import ( - "agent-desk/internal/models" - - "agent-desk/internal/pkg/httpx/params" - - "github.com/mlogclub/simple/sqls" - "gorm.io/gorm" -) - -var AgentRunLogRepository = newAgentRunLogRepository() - -func newAgentRunLogRepository() *agentRunLogRepository { - return &agentRunLogRepository{} -} - -type agentRunLogRepository struct{} - -func (r *agentRunLogRepository) Get(db *gorm.DB, id int64) *models.AgentRunLog { - ret := &models.AgentRunLog{} - if err := db.First(ret, "id = ?", id).Error; err != nil { - return nil - } - return ret -} - -func (r *agentRunLogRepository) Take(db *gorm.DB, where ...interface{}) *models.AgentRunLog { - ret := &models.AgentRunLog{} - if err := db.Take(ret, where...).Error; err != nil { - return nil - } - return ret -} - -func (r *agentRunLogRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.AgentRunLog) { - cnd.Find(db, &list) - return -} - -func (r *agentRunLogRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.AgentRunLog { - ret := &models.AgentRunLog{} - if err := cnd.FindOne(db, &ret); err != nil { - return nil - } - return ret -} - -func (r *agentRunLogRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.AgentRunLog, paging *sqls.Paging) { - return r.FindPageByCnd(db, ¶ms.Cnd) -} - -func (r *agentRunLogRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.AgentRunLog, paging *sqls.Paging) { - cnd.Find(db, &list) - count := cnd.Count(db, &models.AgentRunLog{}) - - paging = &sqls.Paging{ - Page: cnd.Paging.Page, - Limit: cnd.Paging.Limit, - Total: count, - } - return -} - -func (r *agentRunLogRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 { - return cnd.Count(db, &models.AgentRunLog{}) -} - -func (r *agentRunLogRepository) Create(db *gorm.DB, t *models.AgentRunLog) (err error) { - err = db.Create(t).Error - return -} - -func (r *agentRunLogRepository) Update(db *gorm.DB, t *models.AgentRunLog) (err error) { - err = db.Save(t).Error - return -} - -func (r *agentRunLogRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) { - err = db.Model(&models.AgentRunLog{}).Where("id = ?", id).Updates(columns).Error - return -} - -func (r *agentRunLogRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) { - err = db.Model(&models.AgentRunLog{}).Where("id = ?", id).UpdateColumn(name, value).Error - return -} - -func (r *agentRunLogRepository) Delete(db *gorm.DB, id int64) { - db.Delete(&models.AgentRunLog{}, "id = ?", id) -} diff --git a/internal/services/agent_run_log_service.go b/internal/services/agent_run_log_service.go deleted file mode 100644 index 7a69a15..0000000 --- a/internal/services/agent_run_log_service.go +++ /dev/null @@ -1,86 +0,0 @@ -package services - -import ( - "agent-desk/internal/models" - "agent-desk/internal/repositories" - "strings" - - "agent-desk/internal/pkg/httpx/params" - - "github.com/mlogclub/simple/sqls" -) - -var AgentRunLogService = newAgentRunLogService() - -func newAgentRunLogService() *agentRunLogService { - return &agentRunLogService{} -} - -type agentRunLogService struct{} - -func (s *agentRunLogService) Get(id int64) *models.AgentRunLog { - return repositories.AgentRunLogRepository.Get(sqls.DB(), id) -} - -func (s *agentRunLogService) Take(where ...interface{}) *models.AgentRunLog { - return repositories.AgentRunLogRepository.Take(sqls.DB(), where...) -} - -func (s *agentRunLogService) Find(cnd *sqls.Cnd) []models.AgentRunLog { - return repositories.AgentRunLogRepository.Find(sqls.DB(), cnd) -} - -func (s *agentRunLogService) FindOne(cnd *sqls.Cnd) *models.AgentRunLog { - return repositories.AgentRunLogRepository.FindOne(sqls.DB(), cnd) -} - -func (s *agentRunLogService) FindPageByParams(params *params.QueryParams) (list []models.AgentRunLog, paging *sqls.Paging) { - return repositories.AgentRunLogRepository.FindPageByParams(sqls.DB(), params) -} - -func (s *agentRunLogService) FindPageByCnd(cnd *sqls.Cnd) (list []models.AgentRunLog, paging *sqls.Paging) { - return repositories.AgentRunLogRepository.FindPageByCnd(sqls.DB(), cnd) -} - -func (s *agentRunLogService) Count(cnd *sqls.Cnd) int64 { - return repositories.AgentRunLogRepository.Count(sqls.DB(), cnd) -} - -func (s *agentRunLogService) Create(t *models.AgentRunLog) error { - return repositories.AgentRunLogRepository.Create(sqls.DB(), t) -} - -func (s *agentRunLogService) Update(t *models.AgentRunLog) error { - return repositories.AgentRunLogRepository.Update(sqls.DB(), t) -} - -func (s *agentRunLogService) Updates(id int64, columns map[string]interface{}) error { - return repositories.AgentRunLogRepository.Updates(sqls.DB(), id, columns) -} - -func (s *agentRunLogService) UpdateColumn(id int64, name string, value interface{}) error { - return repositories.AgentRunLogRepository.UpdateColumn(sqls.DB(), id, name, value) -} - -func (s *agentRunLogService) Delete(id int64) { - repositories.AgentRunLogRepository.Delete(sqls.DB(), id) -} - -func (s *agentRunLogService) ApplyHITLStatusFilter(cnd *sqls.Cnd, hitlStatus string) *sqls.Cnd { - if cnd == nil { - cnd = sqls.NewCnd() - } - switch strings.TrimSpace(hitlStatus) { - case "pending": - cnd.Eq("final_status", "interrupted") - case "expired": - cnd.Eq("final_status", "expired") - case "cancelled": - cnd.Where("(reply_text LIKE ? OR reply_text LIKE ?)", "%已取消本次工单创建%", "%已取消本次转人工%") - case "confirmed": - cnd.Where("resume_source <> ''") - case "triggered": - cnd.Where("interrupt_type <> ''") - } - return cnd -} diff --git a/web/app/dashboard/agent-run-logs/_components/detail.tsx b/web/app/dashboard/agent-run-logs/_components/detail.tsx deleted file mode 100644 index d265317..0000000 --- a/web/app/dashboard/agent-run-logs/_components/detail.tsx +++ /dev/null @@ -1,440 +0,0 @@ -"use client" - -import { useEffect, useMemo, useState, type ReactNode } from "react" -import { BotMessageSquareIcon, WorkflowIcon } from "lucide-react" -import { toast } from "sonner" - -import { ImMessageHTML } from "@/components/im-message-html" -import { JsonTreeViewer } from "@/components/json-tree-viewer" -import { ProjectDialog } from "@/components/project-dialog" -import { Button } from "@/components/ui/button" -import { fetchAgentRunLog, type AgentRunLog } from "@/lib/api/admin" -import { useI18n } from "@/i18n/provider" -import { formatDateTime } from "@/lib/utils" - -type AgentRunLogDetailDialogProps = { - open: boolean - logId: number | null - onOpenChange: (open: boolean) => void -} - -type TFunction = (key: string, values?: Record) => string - -export function AgentRunLogDetailDialog({ - open, - logId, - onOpenChange, -}: AgentRunLogDetailDialogProps) { - const t = useI18n() - const [loading, setLoading] = useState(false) - const [activeLog, setActiveLog] = useState(null) - - useEffect(() => { - if (!open || !logId) { - return - } - - let cancelled = false - const currentLogId = logId - - async function loadDetail() { - setLoading(true) - try { - const data = await fetchAgentRunLog(currentLogId) - if (!cancelled) { - setActiveLog(data) - } - } catch (error) { - if (!cancelled) { - toast.error(error instanceof Error ? error.message : t("agentRunLog.loadDetailFailed")) - onOpenChange(false) - } - } finally { - if (!cancelled) { - setLoading(false) - } - } - } - - void loadDetail() - - return () => { - cancelled = true - } - }, [logId, onOpenChange, open, t]) - - useEffect(() => { - if (open) { - return - } - setLoading(false) - setActiveLog(null) - }, [open]) - - const activeTraceData = useMemo( - () => safeParseJSON(activeLog?.traceData ?? ""), - [activeLog?.traceData] - ) - const activeToolSearchTrace = useMemo( - () => safeParseJSON(activeLog?.toolSearchTrace ?? ""), - [activeLog?.toolSearchTrace] - ) - const activeGraphToolTrace = useMemo( - () => safeParseJSON(activeLog?.graphToolTrace ?? ""), - [activeLog?.graphToolTrace] - ) - - return ( - - - {t("agentRunLog.detailTitle")} - - } - description={t("agentRunLog.detailDescription")} - size="xl" - allowFullscreen - defaultFullscreen - bodyClassName="min-h-0" - footer={ - - } - > - {loading ? ( -
{t("agentRunLog.loading")}
- ) : activeLog ? ( - <> - - - - - - - - - } - title={t("agentRunLog.userMessage")} - value={activeLog.userMessage} - renderAsHtml - /> - } - title={t("agentRunLog.botReply")} - value={activeLog.replyText} - /> - - - - ) : ( -
{t("agentRunLog.notFound")}
- )} -
- ) -} - -function getHitlStatusLabel(status: string | undefined, t: TFunction) { - switch (status) { - case "pending": - return t("agentRunLog.hitlPending") - case "confirmed": - return t("agentRunLog.hitlConfirmed") - case "cancelled": - return t("agentRunLog.hitlCancelled") - case "expired": - return t("agentRunLog.hitlExpired") - case "triggered": - return t("agentRunLog.hitlTriggered") - default: - return "" - } -} - -function getHitlSummary(status: string | undefined, t: TFunction) { - switch (status) { - case "pending": - return t("agentRunLog.hitlPendingSummary") - case "confirmed": - return t("agentRunLog.hitlConfirmedSummary") - case "cancelled": - return t("agentRunLog.hitlCancelledSummary") - case "expired": - return t("agentRunLog.hitlExpiredSummary") - case "triggered": - return t("agentRunLog.hitlTriggeredSummary") - default: - return "" - } -} - -function safeParseJSON(value: string) { - if (!value.trim()) { - return null - } - try { - return JSON.parse(value) - } catch { - return null - } -} - -function MetaStrip({ - items, -}: { - items: Array<{ label: string; value: string }> -}) { - return ( -
-
- {items.map((item) => ( -
- {item.label} - {item.value} -
- ))} -
-
- ) -} - -function InfoBlock({ title, lines }: { title: string; lines: string[] }) { - return ( -
-
{title}
-
- {lines.map((line) => ( -
{line}
- ))} -
-
- ) -} - -function TextBlock({ - title, - value, - icon, - tone = "default", - renderAsHtml = false, -}: { - title: string - value?: string - icon?: ReactNode - tone?: "default" | "danger" - renderAsHtml?: boolean -}) { - const normalizedValue = value?.trim() || "" - const html = useMemo(() => { - if (!renderAsHtml || !normalizedValue) { - return "" - } - return sanitizeRichHTML(normalizedValue) - }, [normalizedValue, renderAsHtml]) - - return ( -
-
- {icon} - {title} -
- {renderAsHtml && normalizedValue ? ( - - ) : ( -
- {normalizedValue || "-"} -
- )} -
- ) -} - -function JsonBlock({ - title, - jsonValue, - fallbackValue, -}: { - title: string - jsonValue: unknown - fallbackValue?: string -}) { - const normalizedFallback = fallbackValue?.trim() || "" - - return ( -
-
{title}
- {jsonValue ? ( - - ) : ( -
- {normalizedFallback || "-"} -
- )} -
- ) -} - -function sanitizeRichHTML(value: string) { - if (typeof window === "undefined") { - return value - } - - const doc = new DOMParser().parseFromString(value, "text/html") - const allowedTags = new Set([ - "a", - "b", - "blockquote", - "br", - "code", - "div", - "em", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "hr", - "img", - "li", - "ol", - "p", - "pre", - "span", - "strong", - "table", - "tbody", - "td", - "th", - "thead", - "tr", - "u", - "ul", - ]) - const allowedAttrs = new Set([ - "alt", - "class", - "colspan", - "href", - "rel", - "rowspan", - "src", - "target", - "title", - ]) - const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT) - const elements: Element[] = [] - - while (walker.nextNode()) { - elements.push(walker.currentNode as Element) - } - - for (const element of elements) { - const tag = element.tagName.toLowerCase() - if (!allowedTags.has(tag)) { - element.replaceWith(...Array.from(element.childNodes)) - continue - } - - for (const attr of Array.from(element.attributes)) { - const name = attr.name.toLowerCase() - const attrValue = attr.value.trim() - if (name.startsWith("on") || !allowedAttrs.has(name)) { - element.removeAttribute(attr.name) - continue - } - if ((name === "href" || name === "src") && !isSafeURL(attrValue)) { - element.removeAttribute(attr.name) - } - } - - if (tag === "a") { - element.setAttribute("target", "_blank") - element.setAttribute("rel", "noreferrer noopener") - } - } - - return doc.body.innerHTML -} - -function isSafeURL(value: string) { - if (!value) { - return false - } - if (value.startsWith("/")) { - return true - } - if (value.startsWith("data:image/")) { - return true - } - try { - const url = new URL(value, window.location.origin) - return ["http:", "https:"].includes(url.protocol) - } catch { - return false - } -} diff --git a/web/app/dashboard/agent-run-logs/page.tsx b/web/app/dashboard/agent-run-logs/page.tsx deleted file mode 100644 index be8cf7e..0000000 --- a/web/app/dashboard/agent-run-logs/page.tsx +++ /dev/null @@ -1,368 +0,0 @@ -"use client" - -import { useEffect, useMemo, useState } from "react" -import { SearchIcon } from "lucide-react" -import { toast } from "sonner" - -import { DashboardListPage } from "@/components/dashboard/list" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { AgentRunLogDetailDialog } from "./_components/detail" -import { - fetchAgentRunLogs, - fetchAIAgentsAll, - type AIAgent, - type AgentRunLog, -} from "@/lib/api/admin" -import { useI18n } from "@/i18n/provider" -import { formatDateTime } from "@/lib/utils" - -type TFunction = (key: string, values?: Record) => string - -function getActionOptions(t: TFunction) { - return [ - { value: "all", label: t("agentRunLog.allActions") }, - { value: "rag", label: "RAG" }, - { value: "skill", label: "Skill" }, - { value: "tool", label: "Tool" }, - { value: "graph", label: "Graph" }, - { value: "handoff", label: t("agentRunLog.handoff") }, - { value: "reply", label: t("agentRunLog.reply") }, - { value: "fallback", label: t("agentRunLog.fallback") }, - ] -} - -function getFinalStatusOptions(t: TFunction) { - return [ - { value: "all", label: t("agentRunLog.allStatus") }, - { value: "completed", label: "completed" }, - { value: "interrupted", label: "interrupted" }, - { value: "expired", label: "expired" }, - { value: "error", label: "error" }, - { value: "fallback", label: "fallback" }, - ] -} - -function getHitlStatusOptions(t: TFunction) { - return [ - { value: "all", label: t("agentRunLog.allHitl") }, - { value: "pending", label: t("agentRunLog.hitlPending") }, - { value: "confirmed", label: t("agentRunLog.hitlConfirmed") }, - { value: "cancelled", label: t("agentRunLog.hitlCancelled") }, - { value: "expired", label: t("agentRunLog.hitlExpired") }, - { value: "triggered", label: t("agentRunLog.hitlTriggered") }, - ] -} - -function getHitlStatusLabel(status: string | undefined, t: TFunction) { - switch (status) { - case "pending": - return t("agentRunLog.hitlPending") - case "confirmed": - return t("agentRunLog.hitlConfirmed") - case "cancelled": - return t("agentRunLog.hitlCancelled") - case "expired": - return t("agentRunLog.hitlExpired") - case "triggered": - return t("agentRunLog.hitlTriggered") - default: - return "" - } -} - -function actionBadgeVariant(action: string) { - switch (action) { - case "handoff": - return "destructive" as const - case "skill": - return "default" as const - case "tool": - return "default" as const - case "graph": - return "default" as const - case "rag": - return "secondary" as const - case "fallback": - return "outline" as const - default: - return "secondary" as const - } -} - -export default function DashboardAgentRunLogsPage() { - const t = useI18n() - const [detailOpen, setDetailOpen] = useState(false) - const [activeLogId, setActiveLogId] = useState(null) - const [aiAgents, setAiAgents] = useState([]) - const actionOptions = useMemo(() => getActionOptions(t), [t]) - const finalStatusOptions = useMemo(() => getFinalStatusOptions(t), [t]) - const hitlStatusOptions = useMemo(() => getHitlStatusOptions(t), [t]) - - const aiAgentOptions = useMemo( - () => [ - { value: "all", label: t("agentRunLog.allAgents") }, - ...aiAgents.map((item) => ({ - value: String(item.id), - label: item.name, - })), - ], - [aiAgents, t] - ) - - useEffect(() => { - async function loadAIAgents() { - try { - const data = await fetchAIAgentsAll() - setAiAgents(data) - } catch (error) { - toast.error(error instanceof Error ? error.message : t("agentRunLog.loadAgentsFailed")) - } - } - void loadAIAgents() - }, [t]) - - return ( - <> - - filters={[ - { - name: "userMessage", - label: t("agentRunLog.filterUserMessage"), - placeholder: t("agentRunLog.filterUserMessage"), - defaultValue: "", - trim: true, - className: "min-w-0", - inputClassName: "pl-9", - icon: , - }, - { - name: "plannedAction", - label: t("agentRunLog.plannedAction"), - type: "select", - defaultValue: "all", - allValue: "all", - options: actionOptions, - placeholder: t("agentRunLog.plannedAction"), - searchPlaceholder: t("agentRunLog.searchAction"), - emptyText: t("agentRunLog.emptyAction"), - className: "min-w-0", - }, - { - name: "finalAction", - label: t("agentRunLog.finalAction"), - type: "select", - defaultValue: "all", - allValue: "all", - options: actionOptions, - placeholder: t("agentRunLog.finalAction"), - searchPlaceholder: t("agentRunLog.searchAction"), - emptyText: t("agentRunLog.emptyAction"), - className: "min-w-0", - }, - { - name: "finalStatus", - label: t("agentRunLog.finalStatus"), - type: "select", - defaultValue: "all", - allValue: "all", - options: finalStatusOptions, - placeholder: t("agentRunLog.finalStatus"), - searchPlaceholder: t("agentRunLog.searchStatus"), - emptyText: t("agentRunLog.emptyStatus"), - className: "min-w-0", - }, - { - name: "hitlStatus", - label: t("agentRunLog.hitlStatus"), - type: "select", - defaultValue: "all", - allValue: "all", - options: hitlStatusOptions, - placeholder: t("agentRunLog.hitlStatus"), - searchPlaceholder: t("agentRunLog.searchHitl"), - emptyText: t("agentRunLog.emptyStatus"), - className: "min-w-0", - }, - { - name: "aiAgentId", - label: t("agentRunLog.selectAgent"), - type: "select", - defaultValue: "all", - allValue: "all", - options: aiAgentOptions, - placeholder: t("agentRunLog.selectAgent"), - searchPlaceholder: t("agentRunLog.searchAgent"), - emptyText: t("agentRunLog.emptyAgent"), - className: "min-w-0", - }, - ]} - fetchList={fetchAgentRunLogs} - renderContent={({ result, loading }) => - !loading && result.results.length === 0 ? ( -
- {t("agentRunLog.emptyRows")} -
- ) : ( -
-
-
{t("agentRunLog.time")}
-
{t("agentRunLog.userMessage")}
-
{t("agentRunLog.plannedAction")}
-
{t("agentRunLog.skillTool")}
-
{t("agentRunLog.finalStatus")}
-
{t("agentRunLog.duration")}
-
{t("agentRunLog.actions")}
-
- -
- {result.results.map((item) => ( -
-
- {formatDateTime(item.createdAt)} -
- -
- - {item.errorMessage ? ( -
{item.errorMessage}
- ) : null} -
- -
- - {item.plannedAction || "-"} - -
- -
- {item.plannedSkillId || item.graphToolCode || item.plannedToolCode ? ( -
-
- {item.plannedSkillName || - (item.plannedSkillId - ? `Skill #${item.plannedSkillId}` - : "") || - item.graphToolCode || - item.plannedToolCode} -
- {item.plannedSkillId ? ( -
- Skill #{item.plannedSkillId} -
- ) : item.handoffReason ? ( -
- {t("agentRunLog.handoffReason", { reason: item.handoffReason })} -
- ) : item.recommendedAction ? ( -
- {t("agentRunLog.routingRecommendation", { action: item.recommendedAction })} - {item.riskLevel ? ` / ${item.riskLevel} risk` : ""} - {item.ticketDraftReady ? ` / ${t("agentRunLog.draftReady")}` : ""} -
- ) : null} -
- ) : ( - - - )} -
- -
-
- - {item.finalAction || "-"} - -
- {getHitlStatusLabel(item.hitlStatus, t) - ? `${getHitlStatusLabel(item.hitlStatus, t)} / ${item.finalStatus || "-"}` - : item.finalStatus || "-"} -
-
-
- -
- {item.latencyMs} ms -
- -
- -
-
- ))} -
-
- ) - } - labels={{ - refresh: t("agentRunLog.refresh"), - query: t("agentRunLog.query"), - loading: t("agentRunLog.loadingRows"), - empty: t("agentRunLog.emptyRows"), - loadFailed: t("agentRunLog.loadFailed"), - }} - /> - { - setDetailOpen(open) - if (!open) { - setActiveLogId(null) - } - }} - /> - - ) -} - -function UserMessagePreview({ value, t }: { value?: string; t: TFunction }) { - const preview = useMemo(() => summarizeUserMessage(value, t), [value, t]) - - return ( -
- {preview} -
- ) -} - -function summarizeUserMessage(value: string | undefined, t: TFunction) { - const normalized = value?.trim() - if (!normalized) { - return "-" - } - const text = extractTextFromHTML(normalized).replace(/\s+/g, " ").trim() - if (text) { - return text - } - if (containsHTML(normalized)) { - if (/]/i.test(normalized)) { - return t("agentRunLog.imageMessage") - } - return t("agentRunLog.richMessage") - } - return normalized -} - -function containsHTML(value: string) { - return /<[^>]+>/.test(value) -} - -function extractTextFromHTML(value: string) { - if (typeof window === "undefined") { - return value - } - const doc = new DOMParser().parseFromString(value, "text/html") - return doc.body.textContent || "" -} diff --git a/web/lib/api/admin.ts b/web/lib/api/admin.ts index 506e6e6..ebb2ab1 100644 --- a/web/lib/api/admin.ts +++ b/web/lib/api/admin.ts @@ -511,40 +511,6 @@ export type MCPToolCallResult = { structuredContent?: unknown } -export type AgentRunLog = { - id: number - conversationId: number - messageId: number - aiAgentId: number - aiConfigId: number - userMessage: string - plannedAction: string - plannedSkillId: number - plannedSkillName: string - skillRouteTrace: string - toolSearchTrace: string - graphToolTrace: string - graphToolCode: string - recommendedAction: string - riskLevel: string - ticketDraftReady: boolean - handoffReason: string - plannedToolCode: string - planReason: string - interruptType: string - resumeSource: string - hitlStatus: string - hitlStatusName: string - hitlSummary: string - finalAction: string - finalStatus: string - replyText: string - errorMessage: string - latencyMs: number - traceData: string - createdAt: string -} - export type AIWorkflowNodeRun = { id: number workflowRunId: number @@ -1130,18 +1096,6 @@ export function updateSkillDefinition(payload: UpdateSkillDefinitionPayload) { }) } -export function fetchAgentRunLogs( - query?: Record -) { - return request>( - `/api/dashboard/agent-run-log/list${toQueryString(query)}` - ) -} - -export function fetchAgentRunLog(id: number) { - return request(`/api/dashboard/agent-run-log/${id}`) -} - export function fetchAIWorkflowRuns( query?: Record ) { diff --git a/web/lib/navigation.tsx b/web/lib/navigation.tsx index e5f6abe..324ab98 100644 --- a/web/lib/navigation.tsx +++ b/web/lib/navigation.tsx @@ -1,5 +1,4 @@ import { - ActivitySquareIcon, BotMessageSquareIcon, BrainCircuitIcon, Building2Icon, @@ -205,12 +204,6 @@ export const dashboardNavSections: DashboardNavSectionConfig[] = [ icon: , requiredPermission: "mcp.view", }, - { - titleKey: "nav.agentRunLogs", - url: "/dashboard/agent-run-logs", - icon: , - requiredPermission: "conversation.view", - }, ], }, { diff --git a/web/messages/en-US.json b/web/messages/en-US.json index 9091697..481e340 100644 --- a/web/messages/en-US.json +++ b/web/messages/en-US.json @@ -1697,70 +1697,6 @@ "resumeCompleted": "Resume completed", "resumeReply": "Resume Reply" }, - "agentRunLog": { - "allActions": "All actions", - "handoff": "Handoff", - "reply": "Reply", - "fallback": "Fallback", - "allStatus": "All statuses", - "allHitl": "All HITL", - "hitlPending": "Pending approval", - "hitlConfirmed": "Confirmed", - "hitlCancelled": "Cancelled", - "hitlExpired": "Expired", - "hitlTriggered": "Triggered", - "hitlPendingSummary": "A Graph Tool has requested confirmation and is waiting for the user.", - "hitlConfirmedSummary": "The user confirmed the action, execution resumed, and the follow-up flow completed.", - "hitlCancelledSummary": "The user cancelled the action, so the confirmation flow was stopped.", - "hitlExpiredSummary": "The confirmation checkpoint has expired and needs to be started again.", - "hitlTriggeredSummary": "This run involved a confirmation-based HITL flow.", - "allAgents": "All agents", - "loadFailed": "Could not load agent run logs.", - "loadAgentsFailed": "Could not load AI agents.", - "refresh": "Refresh", - "filterUserMessage": "Filter by user message", - "plannedAction": "Planned action", - "finalAction": "Final action", - "finalStatus": "Final status", - "hitlStatus": "HITL status", - "selectAgent": "Select agent", - "searchAction": "Search actions", - "emptyAction": "No matching actions", - "searchStatus": "Search statuses", - "emptyStatus": "No matching statuses", - "searchHitl": "Search HITL status", - "searchAgent": "Search agents", - "emptyAgent": "No agents found", - "query": "Search", - "emptyRows": "No agent run logs yet", - "time": "Time", - "userMessage": "User Message", - "skillTool": "Skill / Tool", - "duration": "Duration", - "actions": "Actions", - "handoffReason": "Handoff reason: {reason}", - "routingRecommendation": "Routing recommendation: {action}", - "draftReady": "draft ready", - "detail": "Details", - "imageMessage": "[Image]", - "richMessage": "[Rich text message]", - "loadDetailFailed": "Could not load log details.", - "detailTitle": "Agent Run Details", - "detailDescription": "Inspect planner selection, final action, reply content, and errors.", - "close": "Close", - "loading": "Loading...", - "logId": "Log ID", - "conversationId": "Conversation ID", - "messageId": "Message ID", - "planningStage": "Planning Stage", - "executionResult": "Execution Result", - "dynamicTools": "Dynamic Tool Selection", - "graphToolCall": "Graph Tool Call", - "botReply": "Bot Reply", - "errorMessage": "Error Message", - "trace": "Trace", - "notFound": "No details found" - }, "knowledge": { "document": "Documents", "faq": "FAQ", @@ -2339,7 +2275,6 @@ "aiWorkflows": "AI Workflows", "skillDefinition": "Skills", "mcp": "MCP tools", - "agentRunLogs": "Run Logs", "system": "System", "users": "Users", "roles": "Roles", diff --git a/web/messages/zh-CN.json b/web/messages/zh-CN.json index c770916..567ea22 100644 --- a/web/messages/zh-CN.json +++ b/web/messages/zh-CN.json @@ -1698,70 +1698,6 @@ "resumeCompleted": "已恢复完成", "resumeReply": "恢复回复" }, - "agentRunLog": { - "allActions": "全部动作", - "handoff": "转人工", - "reply": "回复", - "fallback": "兜底", - "allStatus": "全部状态", - "allHitl": "全部 HITL", - "hitlPending": "等待确认", - "hitlConfirmed": "已确认", - "hitlCancelled": "已取消", - "hitlExpired": "已过期", - "hitlTriggered": "已触发", - "hitlPendingSummary": "Graph Tool 已发起确认,正在等待用户回复。", - "hitlConfirmedSummary": "用户确认后已恢复执行,并完成后续流程。", - "hitlCancelledSummary": "用户已明确取消,本次确认流程已终止。", - "hitlExpiredSummary": "确认 checkpoint 已失效,需要重新发起。", - "hitlTriggeredSummary": "本次运行涉及确认式 HITL 流程。", - "allAgents": "全部 Agent", - "loadFailed": "加载 Agent 运行日志失败", - "loadAgentsFailed": "加载 AI Agent 列表失败", - "refresh": "刷新", - "filterUserMessage": "按用户问题筛选", - "plannedAction": "规划动作", - "finalAction": "最终动作", - "finalStatus": "最终状态", - "hitlStatus": "HITL 状态", - "selectAgent": "选择 Agent", - "searchAction": "搜索动作", - "emptyAction": "未找到动作", - "searchStatus": "搜索状态", - "emptyStatus": "未找到状态", - "searchHitl": "搜索 HITL 状态", - "searchAgent": "搜索 Agent", - "emptyAgent": "未找到 Agent", - "query": "查询", - "emptyRows": "暂无 Agent 运行日志", - "time": "时间", - "userMessage": "用户问题", - "skillTool": "Skill / Tool", - "duration": "耗时", - "actions": "操作", - "handoffReason": "转人工原因:{reason}", - "routingRecommendation": "分流建议:{action}", - "draftReady": "草稿已就绪", - "detail": "详情", - "imageMessage": "[图片]", - "richMessage": "[富文本消息]", - "loadDetailFailed": "加载日志详情失败", - "detailTitle": "Agent 运行详情", - "detailDescription": "查看 planner 选择、最终动作、回复内容与错误信息。", - "close": "关闭", - "loading": "加载中...", - "logId": "日志ID", - "conversationId": "会话ID", - "messageId": "消息ID", - "planningStage": "规划阶段", - "executionResult": "执行结果", - "dynamicTools": "动态工具选择", - "graphToolCall": "Graph Tool 调用", - "botReply": "机器人回复", - "errorMessage": "错误信息", - "trace": "链路 Trace", - "notFound": "未找到详情数据" - }, "knowledge": { "document": "文档", "faq": "FAQ", @@ -2339,7 +2275,6 @@ "aiWorkflows": "AI流程", "skillDefinition": "Skills", "mcp": "MCP tools", - "agentRunLogs": "运行日志", "system": "系统管理", "users": "用户管理", "roles": "角色管理",