2026-04-13 17:17:13 +08:00
|
|
|
package runtime
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-04-19 11:40:17 +08:00
|
|
|
"fmt"
|
2026-04-13 17:17:13 +08:00
|
|
|
"strings"
|
|
|
|
|
|
2026-08-21 00:41:07 +08:00
|
|
|
applicationruntime "code.tczkiot.com/wlw/ai-agent/internal/ai/application/runtime"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/ai/runtime/graphs"
|
|
|
|
|
svc "code.tczkiot.com/wlw/ai-agent/internal/services"
|
2026-04-13 17:17:13 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type replyInterruptService struct{}
|
|
|
|
|
|
|
|
|
|
func newReplyInterruptService() *replyInterruptService {
|
|
|
|
|
return &replyInterruptService{}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-19 11:38:05 +08:00
|
|
|
func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owner *aiReplyService, replyCtx aiReplyContext) error {
|
2026-04-19 11:40:17 +08:00
|
|
|
if replyCtx.PendingInterrupt == nil {
|
|
|
|
|
return fmt.Errorf("pending interrupt is required")
|
2026-04-13 17:17:13 +08:00
|
|
|
}
|
2026-04-19 11:19:51 +08:00
|
|
|
summary, err := owner.executor.ResumePendingInterrupt(ctx, runtimeReplyResumeInput{
|
2026-04-19 11:38:05 +08:00
|
|
|
Conversation: replyCtx.Conversation,
|
|
|
|
|
Message: replyCtx.Message,
|
|
|
|
|
AIAgent: replyCtx.AIAgent,
|
|
|
|
|
PendingInterrupt: replyCtx.PendingInterrupt,
|
2026-04-19 11:19:51 +08:00
|
|
|
})
|
2026-04-19 11:38:05 +08:00
|
|
|
replyCtx.setSummary(summary)
|
2026-04-13 17:17:13 +08:00
|
|
|
if err != nil {
|
|
|
|
|
if isCheckpointMissingError(err) {
|
|
|
|
|
summary = expiredInterruptSummary()
|
2026-04-19 11:38:05 +08:00
|
|
|
replyCtx.setSummary(summary)
|
2026-04-19 11:19:51 +08:00
|
|
|
replyMessage, expireErr := owner.commit.CommitAIReply(replyCommitInput{
|
2026-06-24 17:49:39 +08:00
|
|
|
Conversation: replyCtx.Conversation,
|
|
|
|
|
Message: replyCtx.Message,
|
|
|
|
|
AIAgent: replyCtx.AIAgent,
|
|
|
|
|
ReplyText: summary.ReplyText,
|
|
|
|
|
ClientPrefix: "ai_interrupt_expired",
|
|
|
|
|
WorkflowRunID: summary.WorkflowRunID,
|
2026-04-19 11:19:51 +08:00
|
|
|
})
|
2026-04-13 17:17:13 +08:00
|
|
|
if expireErr != nil {
|
|
|
|
|
return expireErr
|
|
|
|
|
}
|
|
|
|
|
lastResumeMessageID := int64(0)
|
|
|
|
|
if replyMessage != nil {
|
|
|
|
|
lastResumeMessageID = replyMessage.ID
|
|
|
|
|
}
|
2026-04-19 11:38:05 +08:00
|
|
|
if expireMarkErr := svc.ConversationInterruptService.MarkExpired(replyCtx.PendingInterrupt.ID, lastResumeMessageID); expireMarkErr != nil {
|
2026-04-13 17:17:13 +08:00
|
|
|
return expireMarkErr
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if summary != nil && summary.Interrupted {
|
2026-04-19 11:38:05 +08:00
|
|
|
return s.HandleInterruptedResume(owner, replyCtx, summary)
|
2026-04-13 17:17:13 +08:00
|
|
|
}
|
|
|
|
|
if summary != nil && strings.TrimSpace(summary.ReplyText) != "" {
|
2026-04-19 11:19:51 +08:00
|
|
|
replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{
|
2026-06-24 17:49:39 +08:00
|
|
|
Conversation: replyCtx.Conversation,
|
|
|
|
|
Message: replyCtx.Message,
|
|
|
|
|
AIAgent: replyCtx.AIAgent,
|
|
|
|
|
ReplyText: summary.ReplyText,
|
|
|
|
|
ClientPrefix: "ai_resume",
|
|
|
|
|
WorkflowRunID: summary.WorkflowRunID,
|
2026-04-19 11:19:51 +08:00
|
|
|
})
|
2026-04-13 17:17:13 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
replyMessageID := int64(0)
|
|
|
|
|
if replyMessage != nil {
|
|
|
|
|
replyMessageID = replyMessage.ID
|
|
|
|
|
}
|
|
|
|
|
if graphs.IsCancellationReply(summary.ReplyText) {
|
2026-04-19 11:38:05 +08:00
|
|
|
return svc.ConversationInterruptService.MarkCancelled(replyCtx.PendingInterrupt.ID, replyMessageID)
|
2026-04-13 17:17:13 +08:00
|
|
|
}
|
2026-04-19 11:38:05 +08:00
|
|
|
return svc.ConversationInterruptService.MarkResolved(replyCtx.PendingInterrupt.ID, replyMessageID)
|
2026-04-13 17:17:13 +08:00
|
|
|
}
|
2026-04-19 11:38:05 +08:00
|
|
|
return svc.ConversationInterruptService.MarkResolved(replyCtx.PendingInterrupt.ID, 0)
|
2026-04-13 17:17:13 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-27 23:29:02 +08:00
|
|
|
func (s *replyInterruptService) HandleInterruptedSummary(owner *aiReplyService, replyCtx aiReplyContext, summary *applicationruntime.RunResult) error {
|
2026-04-19 11:38:05 +08:00
|
|
|
pending := buildConversationInterrupt(replyCtx.Conversation, replyCtx.Message, replyCtx.AIAgent, summary)
|
2026-07-25 12:04:06 +08:00
|
|
|
if pending != nil && pending.AgentRunID > 0 {
|
|
|
|
|
pending.AgentStepID = svc.AgentRunService.GetLatestStepID(pending.AgentRunID)
|
|
|
|
|
}
|
2026-04-13 17:17:13 +08:00
|
|
|
if err := svc.ConversationInterruptService.CreateOrUpdatePending(pending); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
pending = svc.ConversationInterruptService.GetByCheckPointID(summary.CheckPointID)
|
|
|
|
|
replyText := resolveInterruptPrompt(summary)
|
2026-04-19 11:19:51 +08:00
|
|
|
replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{
|
2026-06-24 17:49:39 +08:00
|
|
|
Conversation: replyCtx.Conversation,
|
|
|
|
|
Message: replyCtx.Message,
|
|
|
|
|
AIAgent: replyCtx.AIAgent,
|
|
|
|
|
ReplyText: replyText,
|
|
|
|
|
ClientPrefix: "ai_interrupt",
|
|
|
|
|
WorkflowRunID: summary.WorkflowRunID,
|
2026-04-19 11:19:51 +08:00
|
|
|
})
|
2026-04-13 17:17:13 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if replyMessage != nil && pending != nil {
|
|
|
|
|
return svc.ConversationInterruptService.MarkPendingAgain(pending.ID, pending.InterruptID, replyText, replyMessage.ID)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-27 23:29:02 +08:00
|
|
|
func (s *replyInterruptService) HandleInterruptedResume(owner *aiReplyService, replyCtx aiReplyContext, summary *applicationruntime.RunResult) error {
|
2026-04-19 11:40:17 +08:00
|
|
|
if replyCtx.PendingInterrupt == nil {
|
|
|
|
|
return fmt.Errorf("pending interrupt is required")
|
2026-04-13 17:17:13 +08:00
|
|
|
}
|
|
|
|
|
replyText := resolveInterruptPrompt(summary)
|
2026-04-19 11:19:51 +08:00
|
|
|
replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{
|
2026-06-24 17:49:39 +08:00
|
|
|
Conversation: replyCtx.Conversation,
|
|
|
|
|
Message: replyCtx.Message,
|
|
|
|
|
AIAgent: replyCtx.AIAgent,
|
|
|
|
|
ReplyText: replyText,
|
|
|
|
|
ClientPrefix: "ai_interrupt_resume",
|
|
|
|
|
WorkflowRunID: summary.WorkflowRunID,
|
2026-04-19 11:19:51 +08:00
|
|
|
})
|
2026-04-13 17:17:13 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if replyMessage != nil {
|
2026-04-19 11:38:05 +08:00
|
|
|
return svc.ConversationInterruptService.MarkPendingAgain(replyCtx.PendingInterrupt.ID, firstInterruptID(summary), replyText, replyMessage.ID)
|
2026-04-13 17:17:13 +08:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|