From 033ef70ac951568f94e629a847efe90bc199601f Mon Sep 17 00:00:00 2001 From: weichen Date: Sat, 12 Sep 2026 10:24:45 +0000 Subject: [PATCH] fix(squad): propagate AgentTimeout to pi-agent step execution E2E v2 still hit 30min runner timeout because runOneStep ignored ExecuteOptions.Timeout; inject shared KB config in pi-agent E2E test. Co-authored-by: Cursor --- ...-12-piagent-e2e-bizdev-abac-test-report.md | 57 +++++++------------ internal/server/piagent_e2e_test.go | 44 ++++++++++++++ internal/squad/quality_gate.go | 2 +- internal/squad/workflow.go | 10 +++- 4 files changed, 74 insertions(+), 39 deletions(-) diff --git a/.doc/test-reports/2026-09-12-piagent-e2e-bizdev-abac-test-report.md b/.doc/test-reports/2026-09-12-piagent-e2e-bizdev-abac-test-report.md index a2920244..e00ff70a 100644 --- a/.doc/test-reports/2026-09-12-piagent-e2e-bizdev-abac-test-report.md +++ b/.doc/test-reports/2026-09-12-piagent-e2e-bizdev-abac-test-report.md @@ -41,7 +41,26 @@ Phase 4 完成后,交付验收需满足两项缺口: ## 五、 实际测试结果与判定 (Actual Results & Evaluation) -### 用例 1:pi-agent 全链路 E2E(第二次运行,含 auth 修复) +### 用例 1a:pi-agent 全链路 E2E v2(60m AgentTimeout 配置,2026-09-12 09:50 UTC) + +- **Input**: `go test -count=1 -timeout 2h -v -run TestPiAgent_FullLifecycle ./internal/server/...` +- **Actual Output**(节选): + ``` + [kb] 无法连接到 PostgreSQL 知识库 (postgres://:@:0/...), 将降级运行在离线模式 + ✓ [0. 环境校验] Health检查正常: Env=dev, AgentBackend=pi-agent, SharedKBPort=0 + ⏱ [步骤 1 对话完成] 累计耗时 3m0s + ExecuteStage V0.1 failed: step step_01_organize failed: Execution timed out after 1800000ms + --- FAIL: TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E (1980.29s) + ``` +- **Verdict**: **FAIL** +- **Duration**: 1980s(约 33min) +- **根因**: + 1. E2E 未注入 `KB_DB_*`,知识库离线降级; + 2. `squad/workflow.go:runOneStep` 硬编码 `Timeout: 30*time.Minute`,未传递 `ExecuteOptions.Timeout`,导致 pi-agent runner 仍收到 1800000ms(30min)而非 60min。 + +**已修复(待 v3 复验)**:`mergePiAgentE2EKbConfig` 注入共享 KB;`runOneStep` 透传 `stepTimeout`。 + +### 用例 1b:pi-agent 全链路 E2E(历史 PASS,含 auth 修复) - **Input**: `go test ./internal/server/ -run TestPiAgent_FullLifecycle -timeout 1h -v` - **Actual Output**(节选): @@ -106,43 +125,11 @@ ok chat-agent-system/internal/bizdev 0.003s TestEnsureTaskStageAccess_De ## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) -**结论**:两项交付目标均已达成,可合并入 dev 阶段节点。 +**结论**:bizdev ABAC、Blended 上下文过滤与 PG 治理链已验收通过;pi-agent E2E v2 因 30min 硬编码超时 **FAIL**,修复已落地,待 v3 复验。 | 交付项 | 状态 | |--------|------| -| pi-agent 1h E2E 全链路 | ✅ PASS(676s) | -| bizdev Agent 上下文 ABAC | ✅ 已实现并注入 server | -| 多角色 docgov Bearer 鉴权 | ✅ E2E 验证通过 | - -### 用例 3:BlendedSquadContext 阶段 ABAC 过滤 - -- **Input**: `go test ./internal/bizdev/... -run TestBuildBlendedSquadContextForUser_FiltersByStage -v` -- **Actual Output**: - ``` - --- PASS: TestBuildBlendedSquadContextForUser_FiltersByStage (0.00s) - PASS - ``` -- **Verdict**: **PASS**(里程碑决策按 `stage_key` 过滤,仅保留当前允许阶段) - -### 用例 4:Phase 5 PG 治理全链路(activate → approve → advance) - -- **Input**: `go test ./internal/server/ -run TestWorkflowGovernance_PG -v` -- **Actual Output**: - ``` - --- PASS: TestWorkflowGovernance_PG_ActivateApproveAdvanceChain (0.36s) - PASS - ok chat-agent-system/internal/server 0.367s - ``` -- **Verdict**: **PASS**(含 stage_ready 通知、访客 403 ABAC、创建者阶段推进) -- **Duration**: ~0.36s - -## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) - -**结论**:pi-agent E2E、bizdev ABAC、Blended 上下文过滤与 PG 治理链均已验收通过。 - -| 交付项 | 状态 | -|--------|------| -| pi-agent 1h E2E 全链路 | ✅ PASS(676s) | +| pi-agent 1h E2E 全链路 | ⚠️ v2 FAIL(1980s);历史 PASS(676s);v3 待跑 | | bizdev Agent 上下文 ABAC | ✅ | | BuildBlendedSquadContext ABAC | ✅ | | PG activate→approve→advance | ✅ | diff --git a/internal/server/piagent_e2e_test.go b/internal/server/piagent_e2e_test.go index ca0d5c5f..641dc59d 100644 --- a/internal/server/piagent_e2e_test.go +++ b/internal/server/piagent_e2e_test.go @@ -16,10 +16,44 @@ import ( "chat-agent-system/internal/config" "chat-agent-system/internal/ctxmgr" "chat-agent-system/internal/docgov" + "chat-agent-system/internal/kb" "chat-agent-system/internal/server" "chat-agent-system/internal/squad" ) +// mergePiAgentE2EKbConfig 从 .env / 环境变量注入共享知识库与向量检索配置(E2E 必须走真实 KB,不可 stub)。 +func mergePiAgentE2EKbConfig(cfg *config.Config) { + envCfg, err := config.Load("") + if err == nil && envCfg != nil { + cfg.KbDbHost = envCfg.KbDbHost + cfg.KbDbPort = envCfg.KbDbPort + cfg.KbDbUser = envCfg.KbDbUser + cfg.KbDbPassword = envCfg.KbDbPassword + cfg.KbDbName = envCfg.KbDbName + cfg.EmbeddingEndpointURL = envCfg.EmbeddingEndpointURL + cfg.EmbeddingAPIKey = envCfg.EmbeddingAPIKey + cfg.EmbeddingModel = envCfg.EmbeddingModel + cfg.RerankerEndpointURL = envCfg.RerankerEndpointURL + cfg.RerankerAPIKey = envCfg.RerankerAPIKey + cfg.RerankerModel = envCfg.RerankerModel + } + if cfg.KbDbHost == "" { + cfg.KbDbHost = "127.0.0.1" + } + if cfg.KbDbPort == 0 { + cfg.KbDbPort = 5432 + } + if cfg.KbDbUser == "" { + cfg.KbDbUser = "regkb" + } + if cfg.KbDbPassword == "" { + cfg.KbDbPassword = "shared_kb_secret_pass" + } + if cfg.KbDbName == "" { + cfg.KbDbName = "regulations" + } +} + // TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E // 贯穿:新建项目 -> 角色权限配置 -> 分角色材料补充与真实对话 -> 自由非标任务执行 -> 工作流推进 -> 审核驳回/再批 -> 数据库落盘与全量审计追溯 // @@ -50,6 +84,7 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { DataDir: t.TempDir(), AllowInMemory: true, } + mergePiAgentE2EKbConfig(cfg) svc := bizdev.MustNewService(t, cfg) srv := server.NewServer(cfg, svc) @@ -76,6 +111,15 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { respHealth.Body.Close() t.Logf("✓ [0. 环境校验] Health检查正常: Env=%s, AgentBackend=%s, SharedKBPort=%d", healthData.Env, healthData.AgentBackend, healthData.SharedKBPort) + kbClient := kb.NewClient(cfg) + defer kbClient.Close() + if stats, err := kbClient.Stats(ctx); err != nil { + t.Logf("⚠ [0. 知识库] 未连接共享 KB(%s:%d/%s): %v", cfg.KbDbHost, cfg.KbDbPort, cfg.KbDbName, err) + } else if reg, ok := stats[kb.DomainRegulatory]; ok { + t.Logf("✓ [0. 知识库] 已连接共享 KB: documents=%d chunks=%d", reg.DocumentCount, reg.ChunkCount) + } else { + t.Logf("✓ [0. 知识库] 已连接共享 KB") + } // 3. 项目基本信息与三级角色定义 projectID := int64(time.Now().UnixNano()%900000 + 100000) diff --git a/internal/squad/quality_gate.go b/internal/squad/quality_gate.go index f8fed19e..44dd98e2 100644 --- a/internal/squad/quality_gate.go +++ b/internal/squad/quality_gate.go @@ -312,7 +312,7 @@ func (q *QualityGate) EnsureQualityOrRepair( _ = os.WriteFile(repairTaskPath, []byte(b.String()), 0o644) // 执行修复步骤(治理入账:轨迹 + 预算) - runErr := driver.runOneStep(ctx, wsDir, repairStep, backend, opts.StepNotify, nil, &[]string{}, ledger) + runErr := driver.runOneStep(ctx, wsDir, repairStep, backend, opts.Timeout, opts.StepNotify, nil, &[]string{}, ledger) if runErr != nil { break } diff --git a/internal/squad/workflow.go b/internal/squad/workflow.go index d3644018..a38a379c 100644 --- a/internal/squad/workflow.go +++ b/internal/squad/workflow.go @@ -263,7 +263,7 @@ func (d *WorkflowDriver) Execute(ctx context.Context, wsDir string, backend agen wg.Add(1) go func() { defer wg.Done() - if err := d.runOneStep(runCtx, wsDir, sCopy, backend, opts.StepNotify, &mu, &producedFiles, ledger); err != nil { + if err := d.runOneStep(runCtx, wsDir, sCopy, backend, timeout, opts.StepNotify, &mu, &producedFiles, ledger); err != nil { mu.Lock() if groupErr == nil { groupErr = err @@ -325,7 +325,7 @@ func (d *WorkflowDriver) Execute(ctx context.Context, wsDir string, backend agen } // 治理:受控迁移至执行态(legacy 重跑场景直接放行) markStepExecuting(wf, step.ID) - if err := d.runOneStep(runCtx, wsDir, step, backend, opts.StepNotify, &mu, &producedFiles, ledger); err != nil { + if err := d.runOneStep(runCtx, wsDir, step, backend, timeout, opts.StepNotify, &mu, &producedFiles, ledger); err != nil { wf.Status = "failed" _ = d.SaveWorkflow(wsDir, wf) _ = d.updateWorkspaceStatus(wsDir, "failed") @@ -403,6 +403,7 @@ func (d *WorkflowDriver) runOneStep( wsDir string, step StepSpec, backend agent.Backend, + stepTimeout time.Duration, notify func(StepEvent), mu *sync.Mutex, producedFiles *[]string, @@ -491,12 +492,15 @@ func (d *WorkflowDriver) runOneStep( prevContent = string(b) } + if stepTimeout <= 0 { + stepTimeout = 30 * time.Minute + } req := agent.Request{ WorkDir: subDir, Prompt: string(taskData), Instructions: string(agentsData), Targets: targets, - Timeout: 30 * time.Minute, + Timeout: stepTimeout, } res, err := backend.Execute(ctx, req)