feat(api): wire document approval hook and project notifications API.

Register workflow approval callback on server start and expose GET /projects/{id}/notifications.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-13 02:50:23 +00:00
committed by weichen
co-authored by Cursor
parent 32b88beee0
commit 4bfe1cb5ad
3 changed files with 85 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
package server
import (
"net/http"
"strconv"
)
func (s *Server) handleListProjectNotifications(w http.ResponseWriter, r *http.Request) {
user, ok := s.requireAuthenticatedUser(w, r)
if !ok {
return
}
if s.notifySvc == nil || !s.notifySvc.Available() {
http.Error(w, "notification service unavailable", http.StatusServiceUnavailable)
return
}
projectID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil || projectID <= 0 {
http.Error(w, "invalid project id", http.StatusBadRequest)
return
}
sinceID, _ := strconv.ParseInt(r.URL.Query().Get("since_id"), 10, 64)
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := s.notifySvc.ListProjectNotifications(r.Context(), user.ID, projectID, sinceID, limit)
if err != nil {
http.Error(w, "list notifications failed: "+err.Error(), http.StatusInternalServerError)
return
}
writeJSONResponse(w, http.StatusOK, map[string]any{
"items": items,
})
}
+5
View File
@@ -19,6 +19,7 @@ import (
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/notification"
"chat-agent-system/internal/observability"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/projectworkflow"
@@ -42,6 +43,7 @@ type Server struct {
authzSvc *authz.Service
wfDefSvc *workflowdef.Service
instSvc *projectinstance.Service
notifySvc *notification.Service
resolver *workspace.Resolver
httpServer *http.Server
}
@@ -97,8 +99,10 @@ func NewServer(cfg *config.Config, svc *bizdev.Service) *Server {
authzSvc: authzSvc,
wfDefSvc: wfDefSvc,
instSvc: instSvc,
notifySvc: notification.NewService(pool),
resolver: workspace.NewResolver(cfg.DataDir, pool),
}
s.registerWorkflowApprovalHook()
s.initTaskRunExecutors()
return s
}
@@ -149,6 +153,7 @@ func (s *Server) Routes() http.Handler {
mux.HandleFunc("GET /api/v1/projects/{id}/workflows/{workflow_key}/runs", s.handleListProjectWorkflowRuns)
mux.HandleFunc("GET /api/v1/projects/{id}/lineage", s.handleGetProjectLineage)
mux.HandleFunc("GET /api/v1/projects/{id}/documents", s.handleListProjectWorkflowDocuments)
mux.HandleFunc("GET /api/v1/projects/{id}/notifications", s.handleListProjectNotifications)
// 3. 对话协同服务 (标准 JSON 阻塞)
mux.HandleFunc("POST /api/v1/chat", s.handleChat)
+45
View File
@@ -0,0 +1,45 @@
package server
import (
"context"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/notification"
)
func (s *Server) registerWorkflowApprovalHook() {
if s == nil || s.svc == nil || s.svc.DocGov() == nil || s.instSvc == nil || !s.instSvc.Available() {
return
}
s.svc.DocGov().AddOnApprovedHook(s.onWorkflowDocumentApproved)
}
func (s *Server) onWorkflowDocumentApproved(ctx context.Context, doc *docgov.DocumentRecord) {
if doc == nil || doc.TaskKey == "" || doc.ProjectID <= 0 || s.instSvc == nil {
return
}
result, err := s.instSvc.HandleDocumentApproved(ctx, doc.ProjectID, doc.TaskKey)
if err != nil || result == nil || !result.StageReady {
return
}
if s.notifySvc == nil || !s.notifySvc.Available() {
return
}
project, err := s.pwSvc.GetProject(ctx, doc.ProjectID, 0)
if err != nil || project == nil || project.CreatorUserID <= 0 {
return
}
_, _ = s.notifySvc.EmitStageReadyToAdvance(ctx, project.CreatorUserID, notification.StageReadyPayload{
ProjectID: doc.ProjectID,
WorkflowInstanceID: result.InstanceID,
StageInstanceID: result.StageInstanceID,
StageKey: result.StageKey,
StageName: result.StageName,
ConfigRevision: result.ConfigRevision,
RequiredDocCount: result.RequiredDocCount,
ApprovedDocCount: result.ApprovedDocCount,
})
}