feat(workflow): add WorkflowRunID to message handling and related services

This commit is contained in:
mlogclub
2026-06-24 17:49:39 +08:00
parent 05233820fb
commit 0b5a24975c
16 changed files with 513 additions and 39 deletions
+3 -1
View File
@@ -22,6 +22,7 @@ type replyCommitInput struct {
AIAgent models.AIAgent
ReplyText string
ClientPrefix string
WorkflowRunID int64
IncrementRound bool
}
@@ -34,7 +35,7 @@ func (s *replyCommitService) SendAIReply(input replyCommitInput) (*models.Messag
if replyText == "" {
return nil, nil
}
replyMessage, err := svc.MessageService.SendAIMessageWithRequestID(
replyMessage, err := svc.MessageService.SendAIMessageWithRequestIDAndWorkflowRunID(
input.Conversation.ID,
input.AIAgent.ID,
fmt.Sprintf("%s_%d", strings.TrimSpace(input.ClientPrefix), input.Message.ID),
@@ -43,6 +44,7 @@ func (s *replyCommitService) SendAIReply(input replyCommitInput) (*models.Messag
"",
s.buildAIPrincipal(input.AIAgent),
input.Message.RequestID,
input.WorkflowRunID,
)
if err != nil || !input.IncrementRound {
return replyMessage, err
@@ -0,0 +1,120 @@
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 TestReplyCommitStoresWorkflowRunIDOnAIMessage(t *testing.T) {
db := setupReplyCommitTestDB(t)
aiAgent := createReplyCommitTestAIAgent(t, db)
conversation := createReplyCommitTestConversation(t, db, aiAgent.ID)
replyMessage, err := newReplyCommitService().CommitAIReply(replyCommitInput{
Conversation: *conversation,
Message: models.Message{ID: 101, RequestID: "trace-101"},
AIAgent: *aiAgent,
ReplyText: "AI reply",
ClientPrefix: "ai_reply",
WorkflowRunID: 9988,
})
if err != nil {
t.Fatalf("CommitAIReply() error = %v", err)
}
if replyMessage == nil {
t.Fatalf("expected reply message")
}
if replyMessage.WorkflowRunID != 9988 {
t.Fatalf("replyMessage.WorkflowRunID=%d want 9988", replyMessage.WorkflowRunID)
}
var stored models.Message
if err := db.First(&stored, replyMessage.ID).Error; err != nil {
t.Fatalf("find reply message: %v", err)
}
if stored.WorkflowRunID != 9988 {
t.Fatalf("stored.WorkflowRunID=%d want 9988", stored.WorkflowRunID)
}
}
func setupReplyCommitTestDB(t *testing.T) *gorm.DB {
t.Helper()
dbName := "reply_commit_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.AIAgent{},
&models.Channel{},
&models.ChannelMessageOutbox{},
&models.Conversation{},
&models.ConversationReadState{},
&models.ConversationEventLog{},
&models.Message{},
); err != nil {
t.Fatalf("auto migrate: %v", err)
}
sqls.SetDB(db)
return db
}
func createReplyCommitTestAIAgent(t *testing.T, db *gorm.DB) *models.AIAgent {
t.Helper()
now := time.Now()
item := &models.AIAgent{
Name: "reply-agent",
Status: enums.StatusOk,
AuditFields: models.AuditFields{
CreatedAt: now,
UpdatedAt: now,
},
}
if err := db.Create(item).Error; err != nil {
t.Fatalf("create ai agent: %v", err)
}
return item
}
func createReplyCommitTestConversation(t *testing.T, db *gorm.DB, aiAgentID int64) *models.Conversation {
t.Helper()
now := time.Now()
item := &models.Conversation{
CustomerID: 1,
ChannelID: 11,
AIAgentID: aiAgentID,
Status: enums.IMConversationStatusAIServing,
LastActiveAt: now,
AuditFields: models.AuditFields{
CreatedAt: now,
UpdatedAt: now,
},
}
if err := db.Create(item).Error; err != nil {
t.Fatalf("create conversation: %v", err)
}
return item
}
+24 -20
View File
@@ -32,11 +32,12 @@ func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owne
summary = expiredInterruptSummary()
replyCtx.setSummary(summary)
replyMessage, expireErr := owner.commit.CommitAIReply(replyCommitInput{
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: summary.ReplyText,
ClientPrefix: "ai_interrupt_expired",
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: summary.ReplyText,
ClientPrefix: "ai_interrupt_expired",
WorkflowRunID: summary.WorkflowRunID,
})
if expireErr != nil {
return expireErr
@@ -57,11 +58,12 @@ func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owne
}
if summary != nil && strings.TrimSpace(summary.ReplyText) != "" {
replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: summary.ReplyText,
ClientPrefix: "ai_resume",
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: summary.ReplyText,
ClientPrefix: "ai_resume",
WorkflowRunID: summary.WorkflowRunID,
})
if err != nil {
return err
@@ -86,11 +88,12 @@ func (s *replyInterruptService) HandleInterruptedSummary(owner *aiReplyService,
pending = svc.ConversationInterruptService.GetByCheckPointID(summary.CheckPointID)
replyText := resolveInterruptPrompt(summary)
replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: replyText,
ClientPrefix: "ai_interrupt",
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: replyText,
ClientPrefix: "ai_interrupt",
WorkflowRunID: summary.WorkflowRunID,
})
if err != nil {
return err
@@ -107,11 +110,12 @@ func (s *replyInterruptService) HandleInterruptedResume(owner *aiReplyService, r
}
replyText := resolveInterruptPrompt(summary)
replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: replyText,
ClientPrefix: "ai_interrupt_resume",
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: replyText,
ClientPrefix: "ai_interrupt_resume",
WorkflowRunID: summary.WorkflowRunID,
})
if err != nil {
return err
+6 -5
View File
@@ -84,11 +84,12 @@ func (s *aiReplyService) executeReply(ctx context.Context, replyCtx aiReplyConte
}
if summary != nil && strings.TrimSpace(summary.ReplyText) != "" {
_, err := s.commit.CommitAIReply(replyCommitInput{
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: summary.ReplyText,
ClientPrefix: "ai_reply",
Conversation: replyCtx.Conversation,
Message: replyCtx.Message,
AIAgent: replyCtx.AIAgent,
ReplyText: summary.ReplyText,
ClientPrefix: "ai_reply",
WorkflowRunID: summary.WorkflowRunID,
})
if err != nil {
return err