Analysis stays read-only, and a revoked project member cannot confirm an old proposal. Co-authored-by: Cursor <cursoragent@cursor.com>
84 lines
3.1 KiB
Go
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
|
|
}
|