Capture trigger_user HITL expectations from dialogue adjudication (previously dropped at the bizdev call site), project them as chat confirmation cards via taskrun OnTerminal (exactly-once), arm the session PendingExpectation, and handle confirm_context_fact hits through the supplement-info blackboard path. Decline (纠正) guides the user to state the correct fact.
75 lines
2.8 KiB
Go
75 lines
2.8 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/bizdev"
|
|
"chat-agent-system/internal/chat"
|
|
"chat-agent-system/internal/coordinator"
|
|
"chat-agent-system/internal/taskrun"
|
|
)
|
|
|
|
// projectContextConflictCards surfaces dialogue-context conflict confirmation
|
|
// cards (HITL trigger_user route) to the user who triggered the run:
|
|
// 1. a durable assistant message in the run's chat session (history APIs);
|
|
// 2. the first card armed as PendingExpectation on the live session so a
|
|
// follow-up「确认/纠正」hits the expectation branch.
|
|
//
|
|
// The adjudication ledger (context_conflict_adjudications) and the workspace
|
|
// CONFIRMATIONS.md remain the authoritative record; this projection is UX.
|
|
func (s *Server) projectContextConflictCards(ctx context.Context, record *taskrun.RunRecord, result *bizdev.TaskRunResult) {
|
|
if s == nil || record == nil || result == nil || len(result.PendingContextConflicts) == 0 {
|
|
return
|
|
}
|
|
if strings.TrimSpace(record.SessionID) == "" {
|
|
return
|
|
}
|
|
content := renderContextConflictCardMessage(result.PendingContextConflicts)
|
|
|
|
if cm := s.ctxMgr(); cm != nil {
|
|
meta := map[string]any{
|
|
"run_id": record.RunID,
|
|
"project_id": record.ProjectID,
|
|
"client_turn_id": record.ClientTurnID,
|
|
"ctx_conflict_card": true,
|
|
}
|
|
_, _ = cm.RecordUserMessageWithID(ctx, record.SessionID, 0, "assistant", content, "CTX_CONFLICT_CARD", "EXPECTATION_HIT", meta)
|
|
}
|
|
|
|
s.sessionsMu.RLock()
|
|
sess := s.sessions[record.SessionID]
|
|
s.sessionsMu.RUnlock()
|
|
if sess == nil {
|
|
return
|
|
}
|
|
if exp := bizdev.ContextConflictExpectation(result.PendingContextConflicts[0]); exp != nil && sess.UserCtx != nil {
|
|
_ = coordinator.ApplyContextUpdate(sess.UserCtx, coordinator.ContextUpdate{
|
|
ExpectedVersion: sess.UserCtx.Version,
|
|
SetPendingExpectation: true,
|
|
PendingExpectation: exp,
|
|
})
|
|
}
|
|
sess.AppendMessage(chat.Message{
|
|
ID: fmt.Sprintf("msg_%d_cc", time.Now().UnixNano()),
|
|
Role: "assistant",
|
|
Content: content,
|
|
Timestamp: time.Now(),
|
|
})
|
|
}
|
|
|
|
// renderContextConflictCardMessage builds the chat-visible confirmation card
|
|
// text. Reply「确认」accepts the recommended winning value;「纠正」clears the
|
|
// card and the user states the correct fact, which SUPPLEMENT_INFO absorbs.
|
|
func renderContextConflictCardMessage(cards []bizdev.ContextConflictCard) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("⚠️ 检测到项目对话与现有事实存在说法冲突,需要您确认:\n\n")
|
|
for i, c := range cards {
|
|
sb.WriteString(fmt.Sprintf("%d. %s\n 推荐采信:%s = %s\n 依据:%s\n", i+1, c.Prompt, c.FactKey, c.WinningValue, c.Snippet))
|
|
}
|
|
sb.WriteString("\n回复「确认」采信推荐值(将同步项目黑板);回复「纠正」后直接告诉我正确的事实。")
|
|
return sb.String()
|
|
}
|