This commit is contained in:
2026-06-09 21:19:30 +08:00
parent 8b41ff9360
commit 0484e79978
66 changed files with 4043 additions and 533 deletions
+4 -1
View File
@@ -176,6 +176,9 @@ func newACPIntegrationSuite(t *testing.T) *acpIntegrationSuite {
ShutdownGrace: 5 * time.Second,
}
sup := acp.NewSupervisor(acpRepo, auditRec, disp, nil, nil, nil, enc, supCfg, log)
permSvc := acp.NewPermissionService(acpRepo, auditRec, 0, log)
sup.SetPermissionService(permSvc)
permSvc.SetRelayLocator(sup)
// Minimal stubs for SessionService dependencies — sufficient for branch=main
// path (no wtSvc / wsRepo calls) and tests that exercise quota/conflict logic.
@@ -192,7 +195,7 @@ func newACPIntegrationSuite(t *testing.T) *acpIntegrationSuite {
r.Use(middleware.RequestID)
user.NewHandler(userSvc).Mount(r)
acp.NewHandler(
akSvc, sessSvc, sup, acpRepo,
akSvc, sessSvc, sup, acpRepo, permSvc,
userSvc,
acpUserAdminAdapter{svc: userSvc},
enc,
+22 -2
View File
@@ -32,7 +32,6 @@ import (
"github.com/yan1h/agent-coding-workflow/internal/acp"
"github.com/yan1h/agent-coding-workflow/internal/audit"
"github.com/yan1h/agent-coding-workflow/internal/chat"
mcp "github.com/yan1h/agent-coding-workflow/internal/mcp"
"github.com/yan1h/agent-coding-workflow/internal/config"
"github.com/yan1h/agent-coding-workflow/internal/infra/clock"
"github.com/yan1h/agent-coding-workflow/internal/infra/crypto"
@@ -45,6 +44,7 @@ import (
"github.com/yan1h/agent-coding-workflow/internal/jobs"
"github.com/yan1h/agent-coding-workflow/internal/jobs/handlers"
"github.com/yan1h/agent-coding-workflow/internal/jobs/runners"
mcp "github.com/yan1h/agent-coding-workflow/internal/mcp"
"github.com/yan1h/agent-coding-workflow/internal/project"
httpx "github.com/yan1h/agent-coding-workflow/internal/transport/http"
"github.com/yan1h/agent-coding-workflow/internal/transport/http/middleware"
@@ -300,6 +300,11 @@ func New(ctx context.Context, cfg *config.Config, dist embed.FS) (*App, error) {
"session_id", sess.ID, "err", rerr.Error())
}
}
// 该会话已被标 crashed,其挂起的权限请求也应一并标 expired,避免悬挂。
if _, rerr := acpRepo.ExpirePendingPermissionRequestsBySession(ctx, sess.ID); rerr != nil {
log.Warn("acp.startup_reaper.expire_permissions_failed",
"session_id", sess.ID, "err", rerr.Error())
}
uid := sess.UserID
if rerr := auditRec.Record(ctx, audit.Entry{
UserID: &uid, Action: "acp.session.aborted_on_restart",
@@ -369,6 +374,11 @@ func New(ctx context.Context, cfg *config.Config, dist embed.FS) (*App, error) {
},
log,
)
// 权限审批服务:与 supervisor 互相依赖,构造后回填(SetPermissionService /
// SetRelayLocator),再注入 handler。
acpPermSvc := acp.NewPermissionService(acpRepo, auditRec, cfg.Acp.PermissionTimeout, log)
acpSup.SetPermissionService(acpPermSvc)
acpPermSvc.SetRelayLocator(acpSup)
sessSvc := acp.NewSessionService(
acpRepo, wsSvc, wtSvc, wsRepo,
acpProjAccess, acpSup, auditRec,
@@ -380,7 +390,7 @@ func New(ctx context.Context, cfg *config.Config, dist embed.FS) (*App, error) {
},
mcpTokenSvc,
)
acp.NewHandler(akSvc, sessSvc, acpSup, acpRepo, userSvc, userAdminAdapter{svc: userSvc}, enc, acp.WSConfig{
acp.NewHandler(akSvc, sessSvc, acpSup, acpRepo, acpPermSvc, userSvc, userAdminAdapter{svc: userSvc}, enc, acp.WSConfig{
PingInterval: cfg.Acp.WSPingInterval,
PongTimeout: cfg.Acp.WSPongTimeout,
SendBuffer: cfg.Acp.WSSendBuffer,
@@ -543,6 +553,16 @@ func New(ctx context.Context, cfg *config.Config, dist embed.FS) (*App, error) {
}
}
// 注册 system 作用域的 prompt 模板为 MCP prompts(spec)。需在 bootstrap 之后
// 执行以保证存在 admin;失败仅 warn,不阻断启动。以第一个 admin 的视角加载模板。
if admins, err := userSvc.ListAdmins(ctx); err != nil {
log.Warn("mcp register_system_prompts: list admins failed", "err", err.Error())
} else if len(admins) > 0 {
if err := mcp.RegisterSystemPrompts(mcpServer, mcpDeps, admins[0].ID); err != nil {
log.Warn("mcp register_system_prompts failed", "err", err.Error())
}
}
srv := &http.Server{
Addr: cfg.HTTP.Addr,
Handler: r,
+9 -9
View File
@@ -47,9 +47,9 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
tcpg "github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/yan1h/agent-coding-workflow/internal/audit"
@@ -327,9 +327,9 @@ func (s *mcpIntegrationSuite) seedAdditionalProject(t *testing.T, ownerID uuid.U
t.Helper()
caller := project.Caller{UserID: ownerID, IsAdmin: ownerID == s.adminID}
p, err := s.projectSvc.Create(s.ctx, caller, project.CreateProjectInput{
Slug: slug,
Name: slug,
Visibility: project.VisibilityInternal,
Slug: slug,
Name: slug,
Visibility: project.VisibilityInternal,
})
require.NoError(t, err)
return p.ID
@@ -342,9 +342,9 @@ func (s *mcpIntegrationSuite) seedPrivateProjectFor(t *testing.T, ownerID uuid.U
slug := fmt.Sprintf("private-%s", uuid.New().String()[:8])
caller := project.Caller{UserID: ownerID, IsAdmin: false}
p, err := s.projectSvc.Create(s.ctx, caller, project.CreateProjectInput{
Slug: slug,
Name: "Private " + slug,
Visibility: project.VisibilityPrivate,
Slug: slug,
Name: "Private " + slug,
Visibility: project.VisibilityPrivate,
})
require.NoError(t, err)
return p.ID, p.Slug
@@ -390,8 +390,8 @@ func (s *mcpIntegrationSuite) queryAuditMetadata(t *testing.T, action string) ma
// bearerRoundTripper injects an Authorization: Bearer header into every request.
type bearerRoundTripper struct {
inner http.RoundTripper
token string
inner http.RoundTripper
token string
}
func (rt *bearerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {