Files
weichenandCursor 687f930f40 feat(trace): deliver recheck and impact notices in chat
A new recheck mark is posted into the actor's existing project session, and a downstream-impact event does the same. Asking about what a change affects still resolves to impact analysis.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 05:19:43 +00:00

92 lines
3.0 KiB
Go

package docnotify
import (
"context"
"encoding/json"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/events"
"chat-agent-system/internal/notification"
"github.com/jackc/pgx/v5"
)
// EventConsumer is the durable notify worker. Closures remain the unique writer until live cutover.
type EventConsumer struct {
Notify *notification.Service
Docs DocumentLookup
Chat *ctxmgr.ContextManager
Env string
}
func (c *EventConsumer) HandleLease(ctx context.Context, lease events.Lease, tx pgx.Tx) error {
if c == nil || c.Notify == nil {
return events.ErrUnknownConsumer
}
principal := events.ServicePrincipal{
ID: "notify-" + c.Env, Environment: c.Env, Consumer: events.ConsumerNotify,
Actions: []string{"notify.emit"},
}
if err := events.AuthorizePrincipal(principal, lease.Environment, lease.ProjectID, "notify.emit"); err != nil {
return events.ErrQuarantine
}
kind, title, body := notifyCopy(lease.EventName)
if kind == "" {
return nil
}
docID := lease.Envelope.AggregateID
var payload map[string]any
_ = json.Unmarshal(lease.Envelope.Payload, &payload)
if payload != nil {
if id, ok := payload["doc_id"].(string); ok && id != "" {
docID = id
}
}
var recipient int64
projectID := lease.ProjectID
if c.Docs != nil && docID != "" {
doc, err := c.Docs.GetDocument(ctx, docID)
if err != nil || doc == nil {
return events.ErrQuarantine
}
if doc.ProjectID != lease.ProjectID {
return events.ErrQuarantine
}
recipient = doc.AuthorID
projectID = doc.ProjectID
}
if recipient <= 0 {
return events.ErrQuarantine
}
p := notification.DocumentEventPayload{ProjectID: projectID, DocID: docID}
if err := c.Notify.EmitVisibleDocumentEvent(ctx, tx, lease.EventID, kind, title, body, recipient, p); err != nil {
return err
}
if lease.EventName == events.EventDownstreamImpact && c.Chat != nil {
impacted, _ := payload["impacted_doc_id"].(string)
_ = PostProjectNotice(ctx, c.Chat, projectID, recipient, downstreamChatBody(docID, impacted), nil)
}
return nil
}
func IntendedNotifyDigest(eventID, kind string, recipient int64) string {
return events.VisibleOutcomeDigest(eventID, kind, recipient)
}
func notifyCopy(eventName string) (kind, title, body string) {
switch eventName {
case events.EventCandidateReady:
return notification.TypeDocAgentCandidateReady, "智能体变更待确认", "文档有高风险智能体候选变更,请确认后生效。"
case events.EventVersionCommitted:
return notification.TypeDocAgentVersionCommitted, "智能体版本已生效", "新的智能体文档版本已提交。"
case events.EventDownstreamImpact:
return notification.TypeDocDownstreamImpact, "下游文档受影响", "上游文档变更,请复核关联文档。"
case events.EventChangeReportDegraded:
return notification.TypeDocChangeReportDegraded, "变更报告未完整评估", "变更报告降级,受控文档暂不可送审。"
case events.EventApproved:
return notification.TypeDocApproved, "文档已批准", "文档已完成终审批准。"
default:
return "", "", ""
}
}