Files
weichenandCursor 5e05d966b4 feat(intent): bridge chat into impact analysis and revision proposals
Analysis stays read-only, and a revoked project member cannot confirm an old proposal.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 15:35:07 +00:00

84 lines
3.1 KiB
Go

package coordinator
import (
"context"
"errors"
"strings"
"chat-agent-system/internal/intent"
)
// ErrImpactNeedsConfirm means a revision proposal was requested but not confirmed.
var ErrImpactNeedsConfirm = errors.New("revision proposal needs confirmation")
// ImpactActions is the chat bridge into the trace index.
// Analyze is read-only. Propose must not write controlled content.
type ImpactActions interface {
AnalyzeImpact(ctx context.Context, projectID, userID int64, text string) (string, error)
ProposeRevision(ctx context.Context, projectID, userID int64, text string, confirmed bool) (string, error)
}
// ProjectAccess reports whether the user may still act on the project.
// A revoked member must fail this check even if an older card is confirmed.
type ProjectAccess func(ctx context.Context, projectID, userID int64) error
func (e *CoordinatorEngine) SetProjectAccess(fn ProjectAccess) {
if e != nil {
e.projectAccess = fn
}
}
func (e *CoordinatorEngine) handleImpactAnalyze(ctx context.Context, top *intent.CandidateIntent, userCtx *UserProjectContext, raw string) (*CoordinationResult, error) {
if e == nil || e.impact == nil || top == nil || userCtx == nil {
return &CoordinationResult{
Status: StatusAccepted, Intent: intent.IntentImpactAnalyze,
NaturalReply: "影响分析还没有接到这个环境。", UpdatedContext: userCtx,
}, nil
}
summary, err := e.impact.AnalyzeImpact(ctx, parseDecimalID(userCtx.Project.ID), parseDecimalID(userCtx.User.ID), raw)
if err != nil {
return nil, err
}
return &CoordinationResult{
Status: StatusAccepted, Intent: top.Intent,
NaturalReply: summary, UpdatedContext: userCtx,
}, nil
}
func (e *CoordinatorEngine) handleRevisePropose(ctx context.Context, top *intent.CandidateIntent, userCtx *UserProjectContext, raw string) (*CoordinationResult, error) {
if e == nil || e.impact == nil || top == nil || userCtx == nil {
return &CoordinationResult{
Status: StatusAccepted, Intent: intent.IntentRevisePropose,
NaturalReply: "修订提案还没有接到这个环境。", UpdatedContext: userCtx,
}, nil
}
confirmed := strings.Contains(raw, "确认")
projectID := parseDecimalID(userCtx.Project.ID)
userID := parseDecimalID(userCtx.User.ID)
if confirmed && e.projectAccess != nil {
if err := e.projectAccess(ctx, projectID, userID); err != nil {
return &CoordinationResult{
Status: StatusRejected, Intent: top.Intent,
NaturalReply: "你已不能访问该项目,旧的确认不能再生成修订提案。",
UpdatedContext: userCtx,
}, err
}
}
id, err := e.impact.ProposeRevision(ctx, projectID, userID, raw, confirmed)
if errors.Is(err, ErrImpactNeedsConfirm) {
return &CoordinationResult{
Status: StatusAccepted, Intent: top.Intent,
NaturalReply: "这会生成受控文档的修订提案,不会直接改正文。请回复「确认修订提案」后再执行。",
UpdatedContext: userCtx,
}, nil
}
if err != nil {
return nil, err
}
return &CoordinationResult{
Status: StatusAccepted, Intent: top.Intent,
NaturalReply: "已生成修订提案 " + id + ",等编制人决定后才会进入正文。",
UpdatedContext: userCtx,
}, nil
}