封堵鉴权旁路与导出 IDOR,DocGov 改服务端身份;对话层 PendingAction 接真实 docgov/taskrun;修复流式租约假死与会话级任务流;加固配置/doc_id/NULL;补齐 slog 与测试;前端 ErrorBoundary 与受控文档重试。 Co-authored-by: Cursor <cursoragent@cursor.com>
165 lines
4.9 KiB
Go
165 lines
4.9 KiB
Go
package chat
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/bizdev"
|
|
"chat-agent-system/internal/coordinator"
|
|
"chat-agent-system/internal/docgov"
|
|
"chat-agent-system/internal/taskrun"
|
|
)
|
|
|
|
// ActionServices 对话层执行 PendingAction 所需的后端服务。
|
|
type ActionServices struct {
|
|
DocGov *docgov.GovernanceEngine
|
|
Bizdev bizdev.BizdevService
|
|
TaskRuns *taskrun.Manager
|
|
}
|
|
|
|
// ActionExecResult 动作执行结果摘要。
|
|
type ActionExecResult struct {
|
|
DocState string
|
|
RunID string
|
|
Message string
|
|
}
|
|
|
|
func parseUserID(userID string) int64 {
|
|
id, _ := strconv.ParseInt(strings.TrimSpace(userID), 10, 64)
|
|
return id
|
|
}
|
|
|
|
func actorName(userCtx *coordinator.UserProjectContext) string {
|
|
if userCtx == nil {
|
|
return "用户"
|
|
}
|
|
if userCtx.User.Name != "" {
|
|
return userCtx.User.Name
|
|
}
|
|
return "用户"
|
|
}
|
|
|
|
func (s *Session) executeAction(ctx context.Context, action *coordinator.PendingAction, userCtx *coordinator.UserProjectContext) (*ActionExecResult, error) {
|
|
if action == nil || s.Actions == nil {
|
|
return nil, fmt.Errorf("action services not configured")
|
|
}
|
|
uid := s.UserID
|
|
if uid <= 0 {
|
|
uid = parseUserID(userCtx.User.ID)
|
|
}
|
|
name := actorName(userCtx)
|
|
|
|
switch action.Kind {
|
|
case "docgov.review_approve":
|
|
if s.Actions.DocGov == nil {
|
|
return nil, fmt.Errorf("docgov engine not available")
|
|
}
|
|
doc, err := s.Actions.DocGov.ReviewApprove(ctx, action.DocID, uid, docgov.RoleReviewer, name, action.Comment)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ActionExecResult{DocState: string(doc.CurrentState), Message: "审核通过"}, nil
|
|
|
|
case "docgov.approver_approve":
|
|
if s.Actions.DocGov == nil {
|
|
return nil, fmt.Errorf("docgov engine not available")
|
|
}
|
|
doc, err := s.Actions.DocGov.ApproverApprove(ctx, action.DocID, uid, docgov.RoleApprover, name, action.Comment)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ActionExecResult{DocState: string(doc.CurrentState), Message: "终审批准"}, nil
|
|
|
|
case "docgov.reject":
|
|
if s.Actions.DocGov == nil {
|
|
return nil, fmt.Errorf("docgov engine not available")
|
|
}
|
|
reason := action.Reason
|
|
if reason == "" {
|
|
reason = "用户确认驳回"
|
|
}
|
|
doc, err := s.Actions.DocGov.ReviewReject(ctx, action.DocID, uid, docgov.RoleReviewer, name, reason)
|
|
if err != nil {
|
|
rollback := action.Rollback
|
|
if rollback == "" {
|
|
rollback = docgov.StateDrafting
|
|
}
|
|
doc, err = s.Actions.DocGov.ApproverReject(ctx, action.DocID, uid, docgov.RoleApprover, name, reason, rollback)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &ActionExecResult{DocState: string(doc.CurrentState), Message: "驳回完成"}, nil
|
|
|
|
case "taskrun.resume", "taskrun.restart":
|
|
if s.Actions.TaskRuns == nil || s.Actions.Bizdev == nil {
|
|
return nil, fmt.Errorf("task run services not available")
|
|
}
|
|
taskKey := action.TaskKey
|
|
if taskKey == "" {
|
|
taskKey = "intake"
|
|
}
|
|
runID := fmt.Sprintf("run_%d", time.Now().UnixNano())
|
|
resumeDir := ""
|
|
if action.Kind == "taskrun.resume" {
|
|
resumeDir = action.WorkDir
|
|
}
|
|
taskReq := bizdev.TaskRunRequest{
|
|
TaskKey: taskKey,
|
|
ProjectName: userCtx.Project.Name,
|
|
UserID: uid,
|
|
ProjectID: s.ProjectID,
|
|
SessionID: s.ID,
|
|
}
|
|
record := &taskrun.RunRecord{
|
|
RunID: runID,
|
|
SessionID: s.ID,
|
|
ProjectID: s.ProjectID,
|
|
UserID: uid,
|
|
TaskKey: taskKey,
|
|
Status: "running",
|
|
}
|
|
created, err := s.Actions.TaskRuns.CreateAndStart(ctx, runID, record, taskReq, bizdev.StageRunRequest{}, resumeDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ActionExecResult{RunID: created.RunID, Message: "任务已启动"}, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unsupported pending action: %s", action.Kind)
|
|
}
|
|
}
|
|
|
|
func renderActionReply(action *coordinator.PendingAction, result *ActionExecResult, execErr error) string {
|
|
docName := action.DocID
|
|
if docName == "" {
|
|
docName = "当前任务"
|
|
}
|
|
if execErr != nil {
|
|
return fmt.Sprintf("操作未能完成:%s", execErr.Error())
|
|
}
|
|
if result == nil {
|
|
return "操作已完成。"
|
|
}
|
|
switch action.Kind {
|
|
case "docgov.review_approve":
|
|
return fmt.Sprintf("已确认审核通过!《%s》已流转至批准环节(当前状态:%s)。", docName, result.DocState)
|
|
case "docgov.approver_approve":
|
|
return fmt.Sprintf("已确认终审批准!《%s》已被法定受控锁定并正式归档生效(当前状态:%s)。", docName, result.DocState)
|
|
case "docgov.reject":
|
|
return fmt.Sprintf("已确认执行驳回操作!《%s》已退回(当前状态:%s),驳回意见已同步下发。", docName, result.DocState)
|
|
case "taskrun.resume":
|
|
return fmt.Sprintf("续跑任务已启动(run_id=%s)。您可在任务面板查看进度。", result.RunID)
|
|
case "taskrun.restart":
|
|
return fmt.Sprintf("任务已重新启动(run_id=%s)。您可在任务面板查看进度。", result.RunID)
|
|
default:
|
|
if result.Message != "" {
|
|
return result.Message
|
|
}
|
|
return "操作已完成。"
|
|
}
|
|
}
|