fix(handler): attribute agent CLI actions to agent identity
When agents use the multica CLI during task execution, their comments, issue updates, and issue creations were attributed to the daemon's user (via JWT) instead of the agent. Pass MULTICA_AGENT_ID env var from the daemon, send X-Agent-ID header from the CLI client, and use it in handlers to set the correct author/actor identity.
This commit is contained in:
parent
663dec52b8
commit
a4c8bbb03c
5 changed files with 52 additions and 10 deletions
|
|
@ -101,10 +101,22 @@ func (h *Handler) CreateComment(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// Determine author identity: agent (via X-Agent-ID header) or member.
|
||||
authorType := "member"
|
||||
authorID := userID
|
||||
if agentID := r.Header.Get("X-Agent-ID"); agentID != "" {
|
||||
// Validate the agent exists in this workspace.
|
||||
agent, err := h.Queries.GetAgent(r.Context(), parseUUID(agentID))
|
||||
if err == nil && uuidToString(agent.WorkspaceID) == uuidToString(issue.WorkspaceID) {
|
||||
authorType = "agent"
|
||||
authorID = agentID
|
||||
}
|
||||
}
|
||||
|
||||
comment, err := h.Queries.CreateComment(r.Context(), db.CreateCommentParams{
|
||||
IssueID: issue.ID,
|
||||
AuthorType: "member",
|
||||
AuthorID: parseUUID(userID),
|
||||
AuthorType: authorType,
|
||||
AuthorID: parseUUID(authorID),
|
||||
Content: req.Content,
|
||||
Type: req.Type,
|
||||
ParentID: parentID,
|
||||
|
|
@ -117,7 +129,7 @@ func (h *Handler) CreateComment(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
resp := commentToResponse(comment)
|
||||
slog.Info("comment created", append(logger.RequestAttrs(r), "comment_id", uuidToString(comment.ID), "issue_id", issueID)...)
|
||||
h.publish(protocol.EventCommentCreated, uuidToString(issue.WorkspaceID), "member", userID, map[string]any{
|
||||
h.publish(protocol.EventCommentCreated, uuidToString(issue.WorkspaceID), authorType, authorID, map[string]any{
|
||||
"comment": resp,
|
||||
"issue_title": issue.Title,
|
||||
"issue_assignee_type": textToPtr(issue.AssigneeType),
|
||||
|
|
@ -126,8 +138,8 @@ func (h *Handler) CreateComment(w http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
|
||||
// If the issue is assigned to an agent with on_comment trigger, enqueue a new task.
|
||||
// The agent will resume its prior session and see this comment.
|
||||
if h.shouldEnqueueOnComment(r.Context(), issue) {
|
||||
// Skip when the comment comes from the assigned agent itself to avoid loops.
|
||||
if authorType == "member" && h.shouldEnqueueOnComment(r.Context(), issue) {
|
||||
if _, err := h.TaskService.EnqueueTaskForIssue(r.Context(), issue); err != nil {
|
||||
slog.Warn("enqueue agent task on comment failed", "issue_id", issueID, "error", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,6 +220,16 @@ func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Determine creator identity: agent (via X-Agent-ID header) or member.
|
||||
creatorType := "member"
|
||||
actualCreatorID := creatorID
|
||||
if agentID := r.Header.Get("X-Agent-ID"); agentID != "" {
|
||||
if agent, err := h.Queries.GetAgent(r.Context(), parseUUID(agentID)); err == nil && uuidToString(agent.WorkspaceID) == workspaceID {
|
||||
creatorType = "agent"
|
||||
actualCreatorID = agentID
|
||||
}
|
||||
}
|
||||
|
||||
issue, err := qtx.CreateIssue(r.Context(), db.CreateIssueParams{
|
||||
WorkspaceID: parseUUID(workspaceID),
|
||||
Title: req.Title,
|
||||
|
|
@ -228,8 +238,8 @@ func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
|
|||
Priority: priority,
|
||||
AssigneeType: assigneeType,
|
||||
AssigneeID: assigneeID,
|
||||
CreatorType: "member",
|
||||
CreatorID: parseUUID(creatorID),
|
||||
CreatorType: creatorType,
|
||||
CreatorID: parseUUID(actualCreatorID),
|
||||
ParentIssueID: parentIssueID,
|
||||
Position: 0,
|
||||
DueDate: dueDate,
|
||||
|
|
@ -249,7 +259,7 @@ func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
|
|||
prefix := h.getIssuePrefix(r.Context(), issue.WorkspaceID)
|
||||
resp := issueToResponse(issue, prefix)
|
||||
slog.Info("issue created", append(logger.RequestAttrs(r), "issue_id", uuidToString(issue.ID), "title", issue.Title, "status", issue.Status, "workspace_id", workspaceID)...)
|
||||
h.publish(protocol.EventIssueCreated, workspaceID, "member", creatorID, map[string]any{"issue": resp})
|
||||
h.publish(protocol.EventIssueCreated, workspaceID, creatorType, actualCreatorID, map[string]any{"issue": resp})
|
||||
|
||||
// Only ready issues in todo are enqueued for agents.
|
||||
if issue.AssigneeType.Valid && issue.AssigneeID.Valid {
|
||||
|
|
@ -371,7 +381,17 @@ func (h *Handler) UpdateIssue(w http.ResponseWriter, r *http.Request) {
|
|||
dueDateChanged := prevDueDate != resp.DueDate && (prevDueDate == nil) != (resp.DueDate == nil) ||
|
||||
(prevDueDate != nil && resp.DueDate != nil && *prevDueDate != *resp.DueDate)
|
||||
|
||||
h.publish(protocol.EventIssueUpdated, workspaceID, "member", userID, map[string]any{
|
||||
// Determine actor identity: agent (via X-Agent-ID header) or member.
|
||||
actorType := "member"
|
||||
actorID := userID
|
||||
if agentID := r.Header.Get("X-Agent-ID"); agentID != "" {
|
||||
if agent, err := h.Queries.GetAgent(r.Context(), parseUUID(agentID)); err == nil && uuidToString(agent.WorkspaceID) == workspaceID {
|
||||
actorType = "agent"
|
||||
actorID = agentID
|
||||
}
|
||||
}
|
||||
|
||||
h.publish(protocol.EventIssueUpdated, workspaceID, actorType, actorID, map[string]any{
|
||||
"issue": resp,
|
||||
"assignee_changed": assigneeChanged,
|
||||
"status_changed": statusChanged,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue