mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
fix(server): suppress on_comment trigger for member-to-member thread replies
When a member replies to another member's comment without @mentioning the assignee agent, the on_comment trigger was incorrectly firing. This is a human-to-human conversation in the thread and should not trigger the assigned agent. Added isReplyToMemberWithoutMentioningAssignee() check: if the parent comment is from a member and the reply doesn't @mention the assignee, the trigger is suppressed. Replying to an agent's comment or explicitly @mentioning the assignee still triggers normally.
This commit is contained in:
@@ -116,6 +116,7 @@ func (h *Handler) CreateComment(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var parentID pgtype.UUID
|
||||
var parentComment *db.Comment
|
||||
if req.ParentID != nil {
|
||||
parentID = parseUUID(*req.ParentID)
|
||||
parent, err := h.Queries.GetComment(r.Context(), parentID)
|
||||
@@ -123,6 +124,7 @@ func (h *Handler) CreateComment(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "invalid parent comment")
|
||||
return
|
||||
}
|
||||
parentComment = &parent
|
||||
}
|
||||
|
||||
// Determine author identity: agent (via X-Agent-ID header) or member.
|
||||
@@ -162,8 +164,11 @@ func (h *Handler) CreateComment(w http.ResponseWriter, r *http.Request) {
|
||||
// Skip when the comment comes from the assigned agent itself to avoid loops.
|
||||
// Also skip when the comment @mentions others but not the assignee agent —
|
||||
// the user is talking to someone else, not requesting work from the assignee.
|
||||
// Also skip when replying to another member's comment without @mentioning
|
||||
// the assignee — the user is having a human-to-human conversation in the thread.
|
||||
if authorType == "member" && h.shouldEnqueueOnComment(r.Context(), issue) &&
|
||||
!h.commentMentionsOthersButNotAssignee(comment.Content, issue) {
|
||||
!h.commentMentionsOthersButNotAssignee(comment.Content, issue) &&
|
||||
!h.isReplyToMemberWithoutMentioningAssignee(comment.Content, parentComment, issue) {
|
||||
// Resolve thread root: if the comment is a reply, agent should reply
|
||||
// to the thread root (matching frontend behavior where all replies
|
||||
// in a thread share the same top-level parent).
|
||||
@@ -203,6 +208,30 @@ func (h *Handler) commentMentionsOthersButNotAssignee(content string, issue db.I
|
||||
return true // Others mentioned but not assignee — suppress trigger
|
||||
}
|
||||
|
||||
// isReplyToMemberWithoutMentioningAssignee returns true if this comment is a
|
||||
// reply to another member's comment and does NOT @mention the assignee agent.
|
||||
// This suppresses the on_comment trigger for human-to-human thread replies —
|
||||
// the user is conversing with another person, not requesting work from the agent.
|
||||
func (h *Handler) isReplyToMemberWithoutMentioningAssignee(content string, parent *db.Comment, issue db.Issue) bool {
|
||||
if parent == nil {
|
||||
return false // Top-level comment — not a reply
|
||||
}
|
||||
if parent.AuthorType != "member" {
|
||||
return false // Replying to an agent — should trigger
|
||||
}
|
||||
// Replying to a member. Only trigger if the assignee agent is explicitly @mentioned.
|
||||
if !issue.AssigneeID.Valid {
|
||||
return true
|
||||
}
|
||||
assigneeID := uuidToString(issue.AssigneeID)
|
||||
for _, m := range util.ParseMentions(content) {
|
||||
if m.ID == assigneeID {
|
||||
return false // Assignee is mentioned — allow trigger
|
||||
}
|
||||
}
|
||||
return true // Reply to member without mentioning assignee — suppress
|
||||
}
|
||||
|
||||
// enqueueMentionedAgentTasks parses @agent mentions from comment content and
|
||||
// enqueues a task for each mentioned agent. Skips self-mentions, agents that
|
||||
// are already the issue's assignee (handled by on_comment), and agents with
|
||||
|
||||
Reference in New Issue
Block a user