merge: fix/stageflow-type-ownership

This commit is contained in:
weichen
2026-09-15 11:13:17 +00:00
30 changed files with 324 additions and 198 deletions
+4 -3
View File
@@ -5,7 +5,7 @@
## 焦点
1. **TRACK-S**: [D7a intent 解绑](./short-term/2026-09-15-intent-bizdev-boundary.md)(D6 已合入。下一波 D5/D4/D7b)
1. **TRACK-S**: [D5 stageflow](./short-term/2026-09-15-stageflow-type-ownership.md)(D6/D7a 已合入。D4 getter 仍开放)
2. **CHAT-INTENT**: [对话意图运行时状态](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md)(双标签 busy 已签:项目 **6275** / `DOC_BUSY` 单 run;不删 `sess-p143-u9` 探针)
3. **CHAT**: [对话历史刷新活验已补](../test-reports/2026-09-13-chat-history-refresh-test-report.md)(项目 6252 闲聊刷新同集 + guest 403;编制任务链仍开放)
@@ -65,7 +65,8 @@
| Cursor | event-e1c-projection-cutover | fix/event-projection-cutover | - | events | done |
| Cursor | event-e2-hook-guard | fix/event-e2-hook-guard | - | events | done |
| Cursor | doccontent-canonical-boundary | fix/doccontent-canonical-boundary | - | docgov | done |
| Cursor | intent-bizdev-boundary | fix/intent-bizdev-boundary | - | intent | in_progress |
| Cursor | intent-bizdev-boundary | fix/intent-bizdev-boundary | - | intent | done |
| Cursor | stageflow-type-ownership | fix/stageflow-type-ownership | - | stageflow | in_progress |
> 新任务开始前登记。`branch` 必须为 `feature/<slug>` 或 `fix/<slug>`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。
@@ -77,7 +78,7 @@
| holder | phase | since | scope | queue |
|--------|-------|-------|-------|-------|
| Cursor | merging | 2026-09-15 | D6 doccontent canonical;mode=off | - |
| Cursor | merging | 2026-09-15 | D5 stageflow types;mode=off | - |
**phase**: `idle` | `merging` | `smoke` | `e2e`
@@ -0,0 +1,40 @@
---
title: "D5 stageflow 类型归属与 D7b chatflow 首批"
status: in_progress
created: 2026-09-15
updated: 2026-09-15
owner: Cursor
branch: fix/stageflow-type-ownership
worktree: "-"
mutex: stageflow
---
# D5 stageflow 类型归属与 D7b chatflow 首批
## 目标
coordinator 生产代码不再 import bizdev;task/stage 请求走 stageflow 类型;chatflow assembler 改用 maturity。
## 范围
- stageflow 类型别名与状态常量
- coordinator 构造改为 StageTaskRunner + ContextServices
- chatflow 去掉 bizdev import
## 非目标
- 不复制行为方法成 DTO(身份别名 + token golden)
- 不删除 bizdev 八个 getter(D4)
- 不搬空 chat 包
## 步骤
1. [x] 类型边界与 coordinator 解绑
2. [x] chatflow assembler
3. [x] 表征测试与 arch-check
4. [x] 报告
## 验收标准
- [x] coordinator 生产 import 无 bizdev
- [x] [S2/S4b 报告](../../test-reports/2026-09-15-stageflow-type-ownership-test-report.md)
+1 -1
View File
@@ -27,7 +27,7 @@
| PI Runner / 配置链 | pi-agent + MiniMax 真实链路 | done | [test piagent](../test-reports/2026-09-13-codex-audit-p0-remediation-test-report.md);[container-env](../test-reports/2026-09-13-container-env-configuration-test-report.md) | — | `pi-agent` |
| 三环境运维 | dev/test/main + 共享 KB | done | [PROJECT.md](../../PROJECT.md) §4;四环境 healthy | — | `infra` |
| CI / E2E | PG 集成 + 真实 PI E2E | partial | [gap-matrix BUG-08](../remediation/gap-matrix.md);[test pg-ci](../test-reports/2026-09-12-pg-ci-soak-e2e-test-report.md);[运行时 UI Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) | CI `TEST_DATABASE_URL` 隔离库仍缺;文档运行时真实 UI 已按 UI_PHASE 分文件且 post-restart 断言最终 A1 正文。`-race` 全包并行 TempDir flake 为基建残留 | `ci` |
| 模块解耦与可靠事件 | 文档可靠性纵向试点、PG 持久事件交付与选择性模块化 | partial | [架构](../system-design/2026-09-14-module-decoupling-architecture.md);[ADR](../adr/2026-09-14-modular-monolith-decoupling.md);[Phase 1](../remediation/2026-09-14-module-decoupling-refactor-plan.md);[Phase 2 审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md);[Phase 2 修订计划](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md);[E0A](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md);[E0B](../test-reports/2026-09-15-event-e0b-dispatcher-pg-test-report.md);[E1A](../test-reports/2026-09-15-event-e1a-notify-cutover-test-report.md);[E1B](../test-reports/2026-09-15-event-e1b-workflow-advance-test-report.md);[E1C](../test-reports/2026-09-15-event-e1c-projection-test-report.md);[E2](../test-reports/2026-09-15-event-e2-hook-guard-test-report.md);[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[D6](../test-reports/2026-09-15-doccontent-canonical-boundary-test-report.md) | E0A–E2 隔离库已过;dispatcher 默认 off。删除闭包 API 须等 live。D6 canonical 已抽出。Track S 其余与 Track P/FAV 另列 | `architecture-docs` |
| 模块解耦与可靠事件 | 文档可靠性纵向试点、PG 持久事件交付与选择性模块化 | partial | [架构](../system-design/2026-09-14-module-decoupling-architecture.md);[ADR](../adr/2026-09-14-modular-monolith-decoupling.md);[Phase 1](../remediation/2026-09-14-module-decoupling-refactor-plan.md);[Phase 2 审阅](../audit/2026-09-15-module-decoupling-phase2-plan-review.md);[Phase 2 修订计划](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md);[E0A](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md);[E0B](../test-reports/2026-09-15-event-e0b-dispatcher-pg-test-report.md);[E1A](../test-reports/2026-09-15-event-e1a-notify-cutover-test-report.md);[E1B](../test-reports/2026-09-15-event-e1b-workflow-advance-test-report.md);[E1C](../test-reports/2026-09-15-event-e1c-projection-test-report.md);[E2](../test-reports/2026-09-15-event-e2-hook-guard-test-report.md);[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[D6](../test-reports/2026-09-15-doccontent-canonical-boundary-test-report.md);[D7a](../test-reports/2026-09-15-intent-bizdev-boundary-test-report.md);[D5](../test-reports/2026-09-15-stageflow-type-ownership-test-report.md) | E0A–E2 隔离库已过;dispatcher 默认 off。D5/D6/D7a/D7b-chatflow 已合入。删除闭包 API 须等 live。D4 getter 原子删除与 Track P 实现、FAV 另列。HTTP/PI 已有独立 remediation(Phase 2 只立项) | `architecture-docs` |
---
+3 -2
View File
@@ -42,11 +42,12 @@ imports:
config: []
coordinator:
- agent
- bizdev
- app/stageflow
- ctxmgr
- docgov
- intent
- kb
- maturity
- projectinstance
- squad
- stream
@@ -83,9 +84,9 @@ imports:
- workflowdef
- workspace
app/chatflow:
- bizdev
- coordinator
- ctxmgr
- maturity
- projectinstance
app/docnotify:
- docgov
@@ -0,0 +1,52 @@
# D5 stageflow 类型归属与 D7b chatflow 首批测试报告
## 一、 测试背景 (Background)
coordinator 通过 bizdev DTO 与 locator 调用编排根。D5 要求 use-case 请求/结果归 stageflow,coordinator 最终不 import bizdev。D7b 允许按用例收紧 import;本批只迁 `app/chatflow` assembler 的阶段常量。
## 二、 测试目标 (Test Objectives)
- coordinator 生产包 import 无 bizdev。
- StageTaskRunner / UseCase 使用 stageflow 类型;token `failed` / `pending_author_confirmation` 不变。
- chatflow assembler 使用 `maturity.StageV01_Triage`。
- 顾问案例推荐仍能通过注入的 ContextManager 填充。
## 三、 测试方案与环境 (Test Plan & Environment)
| 项 | 值 |
|----|-----|
| OS | Linux 7.0.0-31-generic |
| Go | go1.27.1 linux/amd64 |
| 分支 | `fix/stageflow-type-ownership` |
| 层级 | 表征/短测 + arch-check |
## 四、 预期结果 (Expected Results)
既有 ADVANCE_STAGE / pending confirmation / chat session 表征仍过;arch-check 收紧 coordinator 与 chatflow allowlist。
## 五、 实际测试结果与判定 (Actual Results & Evaluation)
### 5.1 表征
- **测试输入 (Input)**:`go test -short -count=1 ./internal/coordinator ./internal/app/stageflow ./internal/app/chatflow ./internal/app ./internal/chat ./internal/bizdev ./internal/archtest ./cmd/bizdev-cli`
- **实际输出 (Actual Output)**:上述包全部 `ok`(cli 2.3s,其余 <0.15s)
- **评价判定 (Evaluation / Verdict)**:**PASS**
- **性能与耗时 (Duration & Latency)**:约 3.6s
### 5.2 arch-check
- **测试输入 (Input)**:`./scripts/arch-check.sh --mode block`
- **实际输出 (Actual Output)**:`arch-check block: production gate passed`
- **评价判定 (Evaluation / Verdict)**:**PASS**
- **性能与耗时 (Duration & Latency)**:~0.12s
## 六、 自动化测试套件汇总 (Test Suite Execution)
| 命令 | 结果 |
|------|------|
| coordinator/stageflow/chatflow/app/chat/bizdev/cli | PASS |
| arch-check | PASS |
## 七、 测试结论与后续优化建议 (Conclusion & Next Steps)
D5 与 D7b **首批 PASS**。类型仍是 bizdev 结构的别名,未机械复制方法。D4 八个 getter 仍在;`internal/chat` 仍 import bizdev 跑任务。下一步:no-new-callers 门禁后原子删除 getter。
+1 -1
View File
@@ -662,7 +662,7 @@ func handleChatMode(
}
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(svc)
engine := coordinator.NewCoordinatorEngine(stageflow.New(svc))
session := chat.NewSession("cli_chat_sess", userCtx, rec, engine)
session.CtxMgr = svc.ContextManager()
+2 -1
View File
@@ -88,7 +88,7 @@ func Assemble(cfg *config.Config, svc *bizdev.Service) (*Runtime, error) {
rec := intent.NewRecognizer()
dialogueBackend := agent.NewBackend(cfg.AgentBackend, cfg)
coordEngine := coordinator.NewCoordinatorEngineWithDialogue(svc, dialogueBackend, cfg.DataDir)
coordEngine := coordinator.NewCoordinatorEngineWithDialogue(nil, dialogueBackend, cfg.DataDir)
pool := svc.Pool()
stores, storeErr := auth.NewStores(context.Background(), pool, cfg)
if storeErr != nil {
@@ -130,6 +130,7 @@ func Assemble(cfg *config.Config, svc *bizdev.Service) (*Runtime, error) {
stages := stageflow.New(svc)
coordEngine.SetStageTaskRunner(stages)
coordEngine.SetContextServices(svc.ContextManager(), svc.KB())
if instSvc != nil && instSvc.Available() {
coordEngine.SetInstanceService(instSvc)
}
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"strings"
"time"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/maturity"
)
// Assembler adapts coordinator.ContextAssembler and ChatLog so prompt context
@@ -43,10 +43,10 @@ func (a *Assembler) Assemble(ctx context.Context, p Principal, req AssembleReque
WorkDir: req.WorkDir,
Project: req.Fallback,
BlackboardFacts: make(map[string]string),
AvailableActions: coordinator.ComputeAvailableActions(bizdev.StageV01_Triage, req.User.ProjectRole),
AvailableActions: coordinator.ComputeAvailableActions(maturity.StageV01_Triage, req.User.ProjectRole),
}
if userCtx.Project.CurrentStage == "" {
userCtx.Project.CurrentStage = bizdev.StageV01_Triage
userCtx.Project.CurrentStage = maturity.StageV01_Triage
}
if userCtx.Project.CurrentWorkflow == "" {
userCtx.Project.CurrentWorkflow = "intake"
+23
View File
@@ -0,0 +1,23 @@
package stageflow
import "chat-agent-system/internal/bizdev"
// Use-case request/result types live at the stageflow boundary.
// They alias bizdev domain structs so mapping stays identity until a later
// contract commit copies fields; characterization tests lock the tokens.
type (
TaskRunRequest = bizdev.TaskRunRequest
TaskRunResult = bizdev.TaskRunResult
StageRunRequest = bizdev.StageRunRequest
StageRunResult = bizdev.StageRunResult
MaterialInput = bizdev.MaterialInput
StageMeta = bizdev.StageMeta
TaskMeta = bizdev.TaskMeta
)
const (
TaskStatusCompleted = bizdev.TaskStatusCompleted
TaskStatusFailed = bizdev.TaskStatusFailed
TaskStatusQualityFailed = bizdev.TaskStatusQualityFailed
TaskStatusPendingAuthorConfirmation = bizdev.TaskStatusPendingAuthorConfirmation
)
+4 -6
View File
@@ -3,8 +3,6 @@ package stageflow
import (
"context"
"errors"
"chat-agent-system/internal/bizdev"
)
var ErrClosed = errors.New("stageflow: executor not configured")
@@ -12,8 +10,8 @@ var ErrClosed = errors.New("stageflow: executor not configured")
// Executor is the unique application owner for task and stage runs.
// HTTP, CLI and coordinator must call this instead of bizdev directly.
type Executor interface {
ExecuteTask(ctx context.Context, req bizdev.TaskRunRequest) (*bizdev.TaskRunResult, error)
ExecuteStage(ctx context.Context, req bizdev.StageRunRequest) (*bizdev.StageRunResult, error)
ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRunResult, error)
ExecuteStage(ctx context.Context, req StageRunRequest) (*StageRunResult, error)
}
// UseCase adapts bizdev orchestration without moving domain tables.
@@ -25,14 +23,14 @@ func New(biz Executor) *UseCase {
return &UseCase{Biz: biz}
}
func (u *UseCase) ExecuteTask(ctx context.Context, req bizdev.TaskRunRequest) (*bizdev.TaskRunResult, error) {
func (u *UseCase) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRunResult, error) {
if u == nil || u.Biz == nil {
return nil, ErrClosed
}
return u.Biz.ExecuteTask(ctx, req)
}
func (u *UseCase) ExecuteStage(ctx context.Context, req bizdev.StageRunRequest) (*bizdev.StageRunResult, error) {
func (u *UseCase) ExecuteStage(ctx context.Context, req StageRunRequest) (*StageRunResult, error) {
if u == nil || u.Biz == nil {
return nil, ErrClosed
}
+10 -1
View File
@@ -23,7 +23,16 @@ func (s *stubBiz) ExecuteStage(_ context.Context, req bizdev.StageRunRequest) (*
return &bizdev.StageRunResult{Success: true, StageTitle: string(req.Stage)}, nil
}
func TestUseCaseDelegates(t *testing.T) {
func TestUseCaseTypeIdentity(t *testing.T) {
var req TaskRunRequest
req.TaskKey = "intake"
if req.TaskKey != "intake" {
t.Fatal("alias lost fields")
}
if TaskStatusFailed != "failed" || TaskStatusPendingAuthorConfirmation != "pending_author_confirmation" {
t.Fatalf("status tokens drifted")
}
}
biz := &stubBiz{}
httpUC := New(biz)
cliUC := New(biz)
@@ -101,6 +101,7 @@ func TestBizDev_CaseRecommendation_FullLink_E2E(t *testing.T) {
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(svc)
engine.SetContextServices(svc.ContextManager(), svc.KB())
userCtx := &coordinator.UserProjectContext{
SnapshotID: "ctx_new_consult",
+4 -4
View File
@@ -22,8 +22,8 @@ import (
"chat-agent-system/internal/agent"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/chat"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/config"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/intent"
)
@@ -101,6 +101,7 @@ func TestLongTaskGovernance_QualityE2E(t *testing.T) {
svc := bizdev.MustNewService(t, cfg)
dialogueBackend := agent.NewBackend("pi-agent", cfg)
engine := coordinator.NewCoordinatorEngineWithDialogue(svc, dialogueBackend, cfg.DataDir)
engine.SetContextServices(svc.ContextManager(), svc.KB())
rec := intent.NewRecognizer()
reportRoot := qualityReportDir(t)
ctx := context.Background()
@@ -112,12 +113,12 @@ func TestLongTaskGovernance_QualityE2E(t *testing.T) {
_ = os.MkdirAll(scenarioADir, 0o755)
userCtx := &coordinator.UserProjectContext{
User: coordinator.UserInfo{ID: "1", Name: "张工", Role: "engineer", ProjectRole: "composer"},
User: coordinator.UserInfo{ID: "1", Name: "张工", Role: "engineer", ProjectRole: "composer"},
Project: coordinator.ProjectInfo{
Name: "掌上无线超声系统",
CustomerName: "华西医院微创神经外科",
DeviceType: "超声软组织手术吸引设备",
IntendedUse: "脑组织空化乳化与同轴吸引",
IntendedUse: "脑组织空化乳化与同轴吸引",
TargetMarket: "中国 NMPA 三类",
CurrentStage: bizdev.StageV01_Triage,
},
@@ -290,4 +291,3 @@ func qualityVerdict(res *bizdev.TaskRunResult) string {
}
return fmt.Sprintf("⚠️ 未完全通过(评分 %.0f,违规: %s)", res.QualityReport.Score, strings.Join(res.QualityReport.Violations, "; "))
}
+1 -4
View File
@@ -278,6 +278,7 @@ func TestChatSession_AdvisorCaseRecommendation(t *testing.T) {
// 2. 发起新会话咨询
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(svc)
engine.SetContextServices(svc.ContextManager(), svc.KB())
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "超声软组织刀研发咨询",
@@ -626,7 +627,3 @@ func TestChatSession_QueryRewrite_EllipsisAndPronoun(t *testing.T) {
t.Errorf("expected pronoun resolution to project name, got: %s", session.UserCtx.RewrittenQuery)
}
}
+5 -5
View File
@@ -5,9 +5,9 @@ import (
"strings"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/maturity"
)
func TestEngine_IntentPlan_QueryThenQuery(t *testing.T) {
@@ -27,7 +27,7 @@ func TestEngine_IntentPlan_QueryThenQuery(t *testing.T) {
userCtx := &coordinator.UserProjectContext{
User: coordinator.UserInfo{ID: "1", Name: "用户"},
Project: coordinator.ProjectInfo{Name: "测试项目", CurrentStage: bizdev.StageV01_Triage},
Project: coordinator.ProjectInfo{Name: "测试项目", CurrentStage: maturity.StageV01_Triage},
BlackboardFacts: map[string]string{},
}
@@ -51,7 +51,7 @@ func TestEngine_ExpectationPlusNew_ExecutesQueryKeepsConfirm(t *testing.T) {
engine := coordinator.NewCoordinatorEngine(nil)
ctx := &intent.ContextSnapshot{
CurrentStage: string(bizdev.StageV01_Triage),
CurrentStage: string(maturity.StageV01_Triage),
PendingExpectation: &intent.PendingExpectation{
ExpectedIntent: intent.IntentAdvanceStage,
AllowAffirmative: true,
@@ -68,8 +68,8 @@ func TestEngine_ExpectationPlusNew_ExecutesQueryKeepsConfirm(t *testing.T) {
}
userCtx := &coordinator.UserProjectContext{
User: coordinator.UserInfo{ID: "1", Name: "用户"},
Project: coordinator.ProjectInfo{Name: "项目", CurrentStage: bizdev.StageV01_Triage},
User: coordinator.UserInfo{ID: "1", Name: "用户"},
Project: coordinator.ProjectInfo{Name: "项目", CurrentStage: maturity.StageV01_Triage},
PendingExpectation: ctx.PendingExpectation,
BlackboardFacts: map[string]string{},
}
+1 -1
View File
@@ -77,7 +77,7 @@ func TestResolveRejectCard_AuthorExecuteDenied(t *testing.T) {
func TestHandleDocGovReject_UnauthorizedAuthor(t *testing.T) {
engine := NewCoordinatorEngine(nil)
userCtx := &UserProjectContext{
User: UserInfo{ID: "u1", Name: "张工", Role: "author", ProjectRole: "author"},
User: UserInfo{ID: "u1", Name: "张工", Role: "author", ProjectRole: "author"},
Project: ProjectInfo{Name: "NeuroPulse"},
}
recRes := &intent.IntentRecognitionResult{
+42 -42
View File
@@ -11,10 +11,10 @@ import (
"sync"
"time"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/kb"
"chat-agent-system/internal/maturity"
"chat-agent-system/internal/squad"
)
@@ -28,18 +28,18 @@ type UserInfo struct {
// ProjectInfo 项目与工作流当前基准
type ProjectInfo struct {
ID string `json:"id"`
Name string `json:"name"`
CustomerName string `json:"customer_name"`
DeviceType string `json:"device_type"`
IntendedUse string `json:"intended_use"`
TargetMarket string `json:"target_market"`
CreatorUserID int64 `json:"creator_user_id,omitempty"`
ActiveWorkflowKey string `json:"active_workflow_key,omitempty"`
WorkflowSelectionVersion int `json:"workflow_selection_version,omitempty"`
CurrentWorkflow string `json:"current_workflow"`
CurrentStage bizdev.MaturityStage `json:"current_stage"`
StageRules []string `json:"stage_rules,omitempty"`
ID string `json:"id"`
Name string `json:"name"`
CustomerName string `json:"customer_name"`
DeviceType string `json:"device_type"`
IntendedUse string `json:"intended_use"`
TargetMarket string `json:"target_market"`
CreatorUserID int64 `json:"creator_user_id,omitempty"`
ActiveWorkflowKey string `json:"active_workflow_key,omitempty"`
WorkflowSelectionVersion int `json:"workflow_selection_version,omitempty"`
CurrentWorkflow string `json:"current_workflow"`
CurrentStage maturity.MaturityStage `json:"current_stage"`
StageRules []string `json:"stage_rules,omitempty"`
}
// UserDocsInfo 用户相关文档清单与认领状态
@@ -78,20 +78,20 @@ type DocumentSummaryReader func(ctx context.Context, target string) (*ReadableDo
// UserProjectContext 用户项目级全量统一上下文 (对话系统与执行系统共同消费)
type UserProjectContext struct {
mu sync.Mutex
Version int64 `json:"version"`
SnapshotID string `json:"snapshot_id"`
AssembledAt time.Time `json:"assembled_at"`
User UserInfo `json:"user"`
Project ProjectInfo `json:"project"`
UserDocs UserDocsInfo `json:"user_docs"`
Pending PendingItems `json:"pending"`
BlackboardFacts map[string]string `json:"blackboard_facts"`
LatestAdvisorBrief *squad.AdvisorBriefing `json:"latest_advisor_brief,omitempty"`
PendingExpectation *intent.PendingExpectation `json:"pending_expectation,omitempty"`
Version int64 `json:"version"`
SnapshotID string `json:"snapshot_id"`
AssembledAt time.Time `json:"assembled_at"`
User UserInfo `json:"user"`
Project ProjectInfo `json:"project"`
UserDocs UserDocsInfo `json:"user_docs"`
Pending PendingItems `json:"pending"`
BlackboardFacts map[string]string `json:"blackboard_facts"`
LatestAdvisorBrief *squad.AdvisorBriefing `json:"latest_advisor_brief,omitempty"`
PendingExpectation *intent.PendingExpectation `json:"pending_expectation,omitempty"`
InteractiveCard *intent.InteractiveCardPayload `json:"interactive_card,omitempty"`
AvailableActions []string `json:"available_actions"`
RecentEvents []string `json:"recent_events,omitempty"`
WorkDir string `json:"work_dir,omitempty"`
AvailableActions []string `json:"available_actions"`
RecentEvents []string `json:"recent_events,omitempty"`
WorkDir string `json:"work_dir,omitempty"`
// RecentDialogue 近轮对话(供 Phase A 对话 LLM 使用,不序列化到快照)
RecentDialogue []DialogueTurn `json:"-"`
// DialogueSummary 早期对话滚动压缩摘要(由 ctxmgr 维护生成,不序列化到快照)
@@ -124,20 +124,20 @@ func NewContextAssembler() *ContextAssembler {
}
// ComputeAvailableActions 根据当前成熟度阶段与角色计算允许执行的操作集合
func ComputeAvailableActions(stage bizdev.MaturityStage, pRole string) []string {
func ComputeAvailableActions(stage maturity.MaturityStage, pRole string) []string {
actions := []string{"QUERY_PROGRESS", "SUPPLEMENT_INFO", "VIEW_DOC"}
switch stage {
case bizdev.StageV01_Triage:
case maturity.StageV01_Triage:
actions = append(actions, "TRIGGER_TASK:intake", "ADVANCE_STAGE:V0.5_feasibility")
case bizdev.StageV05_Feasibility:
case maturity.StageV05_Feasibility:
actions = append(actions,
"TRIGGER_TASK:research",
"TRIGGER_TASK:feasibility_reg",
"TRIGGER_TASK:prelim_design",
"ADVANCE_STAGE:V1.0_quoting",
)
case bizdev.StageV10_Quoting:
case maturity.StageV10_Quoting:
actions = append(actions, "TRIGGER_TASK:cdo_quote", "LOCK_COMMERCIAL")
default:
actions = append(actions, "ADVANCE_STAGE:V0.1_triage")
@@ -147,7 +147,7 @@ func ComputeAvailableActions(stage bizdev.MaturityStage, pRole string) []string
}
// ComputeAvailableActionsWithWorkflow 结合激活工作流、成熟度阶段与角色权限计算可用操作集合
func ComputeAvailableActionsWithWorkflow(stage bizdev.MaturityStage, pRole string, activeWorkflow string, isCreator bool) []string {
func ComputeAvailableActionsWithWorkflow(stage maturity.MaturityStage, pRole string, activeWorkflow string, isCreator bool) []string {
actions := ComputeAvailableActions(stage, pRole)
actions = appendUniqueCapability(actions, "QUERY_WORKFLOW")
if isCreator {
@@ -208,7 +208,7 @@ func (ctx *UserProjectContext) ToSnapshot() *intent.ContextSnapshot {
}
// BuildNextExpectation 根据当前成熟度阶段与项目名构造参谋下一步建议选项草稿
func BuildNextExpectation(stage bizdev.MaturityStage, projectName string) *intent.PendingExpectation {
func BuildNextExpectation(stage maturity.MaturityStage, projectName string) *intent.PendingExpectation {
if projectName == "" {
projectName = "本项目"
}
@@ -217,7 +217,7 @@ func BuildNextExpectation(stage bizdev.MaturityStage, projectName string) *inten
var snippet string
switch stage {
case bizdev.StageV01_Triage:
case maturity.StageV01_Triage:
snippet = fmt.Sprintf("项目「%s」当前处于初始摸底期,基准产品画像已梳理完成。", projectName)
options = []intent.ExpectationOption{
{
@@ -226,7 +226,7 @@ func BuildNextExpectation(stage bizdev.MaturityStage, projectName string) *inten
Description: "自动比选技术路线,并梳理分类路径与型检标准",
Intent: intent.IntentAdvanceStage,
Slots: map[string]string{
intent.SlotTargetStage: string(bizdev.StageV05_Feasibility),
intent.SlotTargetStage: string(maturity.StageV05_Feasibility),
},
},
{
@@ -246,7 +246,7 @@ func BuildNextExpectation(stage bizdev.MaturityStage, projectName string) *inten
},
}
case bizdev.StageV05_Feasibility:
case maturity.StageV05_Feasibility:
snippet = fmt.Sprintf("项目「%s」已完成方案成型与法规对齐,具备进入商务核算的条件。", projectName)
options = []intent.ExpectationOption{
{
@@ -255,7 +255,7 @@ func BuildNextExpectation(stage bizdev.MaturityStage, projectName string) *inten
Description: "生成精细化 CDO 工时与成本核算表,锁定立项商务方案",
Intent: intent.IntentAdvanceStage,
Slots: map[string]string{
intent.SlotTargetStage: string(bizdev.StageV10_Quoting),
intent.SlotTargetStage: string(maturity.StageV10_Quoting),
},
},
{
@@ -275,7 +275,7 @@ func BuildNextExpectation(stage bizdev.MaturityStage, projectName string) *inten
},
}
case bizdev.StageV10_Quoting:
case maturity.StageV10_Quoting:
snippet = fmt.Sprintf("项目「%s」已完成商务锁定,报价核算表已就绪。", projectName)
options = []intent.ExpectationOption{
{
@@ -307,7 +307,7 @@ func BuildNextExpectation(stage bizdev.MaturityStage, projectName string) *inten
Description: "快速沉淀第一版立项画像并为您准备沟通话术",
Intent: intent.IntentAdvanceStage,
Slots: map[string]string{
intent.SlotTargetStage: string(bizdev.StageV01_Triage),
intent.SlotTargetStage: string(maturity.StageV01_Triage),
},
},
}
@@ -381,15 +381,15 @@ func (a *ContextAssembler) AssembleFromWorkspace(workDir string, user UserInfo)
ctx.Project.CurrentWorkflow = ctx.Project.ActiveWorkflowKey
}
if strings.HasPrefix(ctx.Project.CurrentWorkflow, "V0.1") || ctx.Project.CurrentWorkflow == "intake" {
ctx.Project.CurrentStage = bizdev.StageV01_Triage
ctx.Project.CurrentStage = maturity.StageV01_Triage
} else if strings.HasPrefix(ctx.Project.CurrentWorkflow, "V0.5") {
ctx.Project.CurrentStage = bizdev.StageV05_Feasibility
ctx.Project.CurrentStage = maturity.StageV05_Feasibility
} else if strings.HasPrefix(ctx.Project.CurrentWorkflow, "V1.0") {
ctx.Project.CurrentStage = bizdev.StageV10_Quoting
ctx.Project.CurrentStage = maturity.StageV10_Quoting
}
if ctx.Project.CurrentStage == "" {
ctx.Project.CurrentStage = bizdev.StageV01_Triage
ctx.Project.CurrentStage = maturity.StageV01_Triage
}
isCreator := false
+3 -3
View File
@@ -5,13 +5,13 @@ import (
"path/filepath"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/maturity"
)
func TestComputeAvailableActions(t *testing.T) {
actionsV01 := coordinator.ComputeAvailableActions(bizdev.StageV01_Triage, "composer")
actionsV01 := coordinator.ComputeAvailableActions(maturity.StageV01_Triage, "composer")
foundAdvance := false
for _, a := range actionsV01 {
if a == "ADVANCE_STAGE:V0.5_feasibility" {
@@ -22,7 +22,7 @@ func TestComputeAvailableActions(t *testing.T) {
t.Errorf("expected V0.1 actions to contain advance to V0.5: %v", actionsV01)
}
actionsV05 := coordinator.ComputeAvailableActions(bizdev.StageV05_Feasibility, "composer")
actionsV05 := coordinator.ComputeAvailableActions(maturity.StageV05_Feasibility, "composer")
foundAdvanceQuote := false
for _, a := range actionsV05 {
if a == "ADVANCE_STAGE:V1.0_quoting" {
+7 -7
View File
@@ -8,8 +8,8 @@ import (
"testing"
"chat-agent-system/internal/agent"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/maturity"
)
type scriptedDialogueBackend struct {
@@ -49,7 +49,7 @@ func TestDialogueLLM_CasualChatUsesBackend(t *testing.T) {
Project: ProjectInfo{
ID: "p-dialogue-1",
Name: "CUSA-Dialogue-Test",
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
},
BlackboardFacts: map[string]string{},
RecentDialogue: []DialogueTurn{
@@ -128,7 +128,7 @@ func TestDialogueLLM_AmbiguousFallbackUsesBackend(t *testing.T) {
}
engine := NewCoordinatorEngineWithDialogue(nil, backend, t.TempDir())
userCtx := &UserProjectContext{
Project: ProjectInfo{Name: "CUSA", CurrentStage: bizdev.StageV01_Triage},
Project: ProjectInfo{Name: "CUSA", CurrentStage: maturity.StageV01_Triage},
}
rec := &intent.IntentRecognitionResult{
RawInput: "请详细解释压电换能器的逆压电效应与变幅杆阻抗匹配。",
@@ -158,7 +158,7 @@ func TestDialogueLLM_FallbackToTemplateOnBackendFailure(t *testing.T) {
backend := &scriptedDialogueBackend{fail: true}
engine := NewCoordinatorEngineWithDialogue(nil, backend, t.TempDir())
userCtx := &UserProjectContext{
Project: ProjectInfo{Name: "CUSA", CurrentStage: bizdev.StageV01_Triage},
Project: ProjectInfo{Name: "CUSA", CurrentStage: maturity.StageV01_Triage},
}
rec := &intent.IntentRecognitionResult{
RawInput: "你好",
@@ -190,7 +190,7 @@ func TestDialogueLLM_PersonalizedGuidanceAndWarmPersona(t *testing.T) {
User: UserInfo{Name: "张工", Role: "engineer", ProjectRole: "composer"},
Project: ProjectInfo{
Name: "CUSA-Warm",
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
},
PersonalizedGuidance: "【用户个性化工作习惯与偏好指导 (Memory Injection)】\n• 沟通风格:该用户偏好【结构精炼、结论先行】\n• 重点关注:该用户较关心【法规标准合规与审评审批风险】\n",
Pending: PendingItems{
@@ -236,7 +236,7 @@ func TestDialogueLLM_QueryProgressNoBriefUsesBackend(t *testing.T) {
engine := NewCoordinatorEngineWithDialogue(nil, backend, t.TempDir())
userCtx := &UserProjectContext{
User: UserInfo{Name: "张工", ProjectRole: "composer"},
Project: ProjectInfo{Name: "医疗器械研发项目", CurrentStage: bizdev.StageV01_Triage},
Project: ProjectInfo{Name: "医疗器械研发项目", CurrentStage: maturity.StageV01_Triage},
}
rec := &intent.IntentRecognitionResult{
RawInput: "简单分析一下这个项目有什么风险",
@@ -269,7 +269,7 @@ func TestDialogueLLM_QueryProgressNoBriefFallsBackToSynthesizedTemplate(t *testi
backend := &scriptedDialogueBackend{fail: true}
engine := NewCoordinatorEngineWithDialogue(nil, backend, t.TempDir())
userCtx := &UserProjectContext{
Project: ProjectInfo{Name: "医疗器械研发项目", CurrentStage: bizdev.StageV01_Triage},
Project: ProjectInfo{Name: "医疗器械研发项目", CurrentStage: maturity.StageV01_Triage},
}
rec := &intent.IntentRecognitionResult{
RawInput: "简单分析一下这个项目有什么风险",
+33 -28
View File
@@ -13,11 +13,12 @@ import (
"time"
"chat-agent-system/internal/agent"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/app/stageflow"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/kb"
"chat-agent-system/internal/maturity"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/squad"
"chat-agent-system/internal/stream"
@@ -72,19 +73,19 @@ type CoordinationResult struct {
// CoordinatorEngine L2 协调引擎
type CoordinatorEngine struct {
bizdevSvc bizdev.BizdevService
stageTasks StageTaskRunner
instSvc *projectinstance.Service
assembler *ContextAssembler
dialogueBackend agent.Backend
dataDir string
ctxMgr *ctxmgr.ContextManager
kbClient *kb.Client
}
// StageTaskRunner is the application owner for ExecuteTask / ExecuteStage.
// Wired from app/stageflow; tests may leave it nil and fall back to bizdevSvc.
type StageTaskRunner interface {
ExecuteTask(ctx context.Context, req bizdev.TaskRunRequest) (*bizdev.TaskRunResult, error)
ExecuteStage(ctx context.Context, req bizdev.StageRunRequest) (*bizdev.StageRunResult, error)
ExecuteTask(ctx context.Context, req stageflow.TaskRunRequest) (*stageflow.TaskRunResult, error)
ExecuteStage(ctx context.Context, req stageflow.StageRunRequest) (*stageflow.StageRunResult, error)
}
func (e *CoordinatorEngine) SetStageTaskRunner(r StageTaskRunner) {
@@ -97,10 +98,14 @@ func (e *CoordinatorEngine) tasks() StageTaskRunner {
if e == nil {
return nil
}
if e.stageTasks != nil {
return e.stageTasks
return e.stageTasks
}
func (e *CoordinatorEngine) SetContextServices(ctxMgr *ctxmgr.ContextManager, kbClient *kb.Client) {
if e != nil {
e.ctxMgr = ctxMgr
e.kbClient = kbClient
}
return e.bizdevSvc
}
// SetInstanceService 注入项目工作流实例服务(对话推进与实例门禁合流)。
@@ -110,17 +115,17 @@ func (e *CoordinatorEngine) SetInstanceService(svc *projectinstance.Service) {
}
}
func NewCoordinatorEngine(bizdevSvc bizdev.BizdevService) *CoordinatorEngine {
func NewCoordinatorEngine(runner StageTaskRunner) *CoordinatorEngine {
return &CoordinatorEngine{
bizdevSvc: bizdevSvc,
assembler: NewContextAssembler(),
stageTasks: runner,
assembler: NewContextAssembler(),
}
}
// NewCoordinatorEngineWithDialogue 注入对话 LLM 后端(Phase A:CASUAL / AMBIGUOUS)
func NewCoordinatorEngineWithDialogue(bizdevSvc bizdev.BizdevService, dialogueBackend agent.Backend, dataDir string) *CoordinatorEngine {
func NewCoordinatorEngineWithDialogue(runner StageTaskRunner, dialogueBackend agent.Backend, dataDir string) *CoordinatorEngine {
return &CoordinatorEngine{
bizdevSvc: bizdevSvc,
stageTasks: runner,
assembler: NewContextAssembler(),
dialogueBackend: dialogueBackend,
dataDir: dataDir,
@@ -324,10 +329,10 @@ func (e *CoordinatorEngine) ProcessIntent(
// enrichAdvisorBriefWithCases 结合上下文与意图槽位检索相似标杆案例并注入参谋锦囊
func (e *CoordinatorEngine) enrichAdvisorBriefWithCases(ctx context.Context, brief *squad.AdvisorBriefing, userCtx *UserProjectContext, slots map[string]string) {
if brief == nil || e.bizdevSvc == nil {
if brief == nil || e.ctxMgr == nil {
return
}
ctxMgr := e.bizdevSvc.ContextManager()
ctxMgr := e.ctxMgr
if ctxMgr == nil {
return
}
@@ -414,7 +419,7 @@ func (e *CoordinatorEngine) handleAmbiguousConsult(ctx context.Context, cand *in
var sb strings.Builder
sb.WriteString(fmt.Sprintf("完全理解您想再掂量一下。针对「%s」目前的节点,我帮您快速对照一下:\n\n", projName))
if currentStage == bizdev.StageV01_Triage {
if currentStage == maturity.StageV01_Triage {
sb.WriteString("• 若推进 V0.5(方案成型与法规对齐):\n")
sb.WriteString(" 我可帮您梳理初步架构与注册分类路径,并预估型检成本,便于与客户深入沟通;\n")
sb.WriteString("• 若先放一放 / 多做调研:\n")
@@ -761,9 +766,9 @@ func (e *CoordinatorEngine) handleAdvanceStage(ctx context.Context, cand *intent
}
targetStageStr := cand.FilledSlots[intent.SlotTargetStage]
targetStage := bizdev.MaturityStage(targetStageStr)
targetStage := maturity.MaturityStage(targetStageStr)
if targetStage == "" {
targetStage = bizdev.StageV05_Feasibility
targetStage = maturity.StageV05_Feasibility
}
streamer := stream.FromContext(ctx)
@@ -835,7 +840,7 @@ func (e *CoordinatorEngine) handleAdvanceStage(ctx context.Context, cand *intent
}
// 3. 调用底层 L3 调度器执行阶段跃迁
stageRes, err := e.tasks().ExecuteStage(ctx, bizdev.StageRunRequest{
stageRes, err := e.tasks().ExecuteStage(ctx, stageflow.StageRunRequest{
Stage: targetStage,
ProjectName: userCtx.Project.Name,
CustomerName: userCtx.Project.CustomerName,
@@ -929,7 +934,7 @@ func (e *CoordinatorEngine) handleTriggerTask(ctx context.Context, cand *intent.
fmt.Sscanf(userCtx.User.ID, "%d", &userID)
}
taskRes, err := e.tasks().ExecuteTask(ctx, bizdev.TaskRunRequest{
taskRes, err := e.tasks().ExecuteTask(ctx, stageflow.TaskRunRequest{
TaskKey: taskKey,
ProjectName: userCtx.Project.Name,
CustomerName: userCtx.Project.CustomerName,
@@ -999,9 +1004,9 @@ func (e *CoordinatorEngine) handleTriggerTask(ctx context.Context, cand *intent.
// formatTriggerTaskReply builds sync coordinator copy for a TaskRunResult.
// Pending high-risk candidates must not claim "正文已保存 / 已完成".
func formatTriggerTaskReply(taskRes *bizdev.TaskRunResult, outPath string) (reply string, taskStatus string) {
func formatTriggerTaskReply(taskRes *stageflow.TaskRunResult, outPath string) (reply string, taskStatus string) {
if taskRes == nil {
return "任务执行失败。", bizdev.TaskStatusFailed
return "任务执行失败。", stageflow.TaskStatusFailed
}
taskStatus = taskRes.TerminalStatus()
if taskRes.AwaitsAgentConfirmation() {
@@ -1019,7 +1024,7 @@ func formatTriggerTaskReply(taskRes *bizdev.TaskRunResult, outPath string) (repl
return sb.String(), taskStatus
}
func attachTaskRunProjection(res *CoordinationResult, taskRes *bizdev.TaskRunResult) {
func attachTaskRunProjection(res *CoordinationResult, taskRes *stageflow.TaskRunResult) {
if res == nil || taskRes == nil {
return
}
@@ -1072,12 +1077,12 @@ func (e *CoordinatorEngine) handleTriggerAdhocTask(ctx context.Context, cand *in
// 任务简报材料化:作为首要输入材料注入工作区(context/materials_text/task_brief.md),
// 供 organizer 专员在容器内直接读到用户原始诉求(否则首步将报"无材料可整理")
briefMaterial := bizdev.MaterialInput{
briefMaterial := stageflow.MaterialInput{
Filename: "task_brief.md",
Content: []byte(fmt.Sprintf("# 任务简报(用户原始诉求,权威等级:primary_evidence)\n\n%s\n", brief)),
}
taskRes, err := e.tasks().ExecuteTask(ctx, bizdev.TaskRunRequest{
taskRes, err := e.tasks().ExecuteTask(ctx, stageflow.TaskRunRequest{
TaskKey: taskKey,
Title: title,
ProjectName: userCtx.Project.Name,
@@ -1086,7 +1091,7 @@ func (e *CoordinatorEngine) handleTriggerAdhocTask(ctx context.Context, cand *in
IntendedUse: userCtx.Project.IntendedUse,
TargetMarket: userCtx.Project.TargetMarket,
Fields: userCtx.BlackboardFacts,
Materials: []bizdev.MaterialInput{briefMaterial},
Materials: []stageflow.MaterialInput{briefMaterial},
AutoRepair: true,
ProjectID: projectID,
UserID: userID,
@@ -1671,7 +1676,7 @@ func (e *CoordinatorEngine) handleQueryRegulations(ctx context.Context, cand *in
}
var hits []kb.HitItem
if e != nil && e.bizdevSvc != nil && e.bizdevSvc.KB() != nil && strings.TrimSpace(searchQuery) != "" {
if e != nil && e.kbClient != nil && strings.TrimSpace(searchQuery) != "" {
cacheKey := ctxmgr.HashKey("regulatory_search", searchQuery)
cache := ctxmgr.GetGlobalCache()
if cached, ok := cache.Get(ctxmgr.LayerC1Search, cacheKey); ok {
@@ -1679,7 +1684,7 @@ func (e *CoordinatorEngine) handleQueryRegulations(ctx context.Context, cand *in
hits = ch
}
} else {
kbHits, err := e.bizdevSvc.KB().Search(ctx, searchQuery, kb.SearchOptions{
kbHits, err := e.kbClient.Search(ctx, searchQuery, kb.SearchOptions{
Domains: []kb.KnowledgeDomain{kb.DomainRegulatory},
Limit: 5,
})
@@ -5,9 +5,9 @@ import (
"strings"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/maturity"
)
// TestEngine_ClarificationBeforeAmbiguousConsult 缺驳回理由必须返回定向澄清,不得进入参谋模糊咨询
@@ -17,7 +17,7 @@ func TestEngine_ClarificationBeforeAmbiguousConsult(t *testing.T) {
ctx := &intent.ContextSnapshot{
SnapshotID: "ctx_engine_clarify",
CurrentStage: string(bizdev.StageV05_Feasibility),
CurrentStage: string(maturity.StageV05_Feasibility),
ActiveDocID: "doc-002",
DocLifecycleState: "reviewing",
UserDocRole: "reviewer",
@@ -38,7 +38,7 @@ func TestEngine_ClarificationBeforeAmbiguousConsult(t *testing.T) {
User: coordinator.UserInfo{ID: "user_102", Name: "李审核", Role: "reviewer", ProjectRole: "reviewer"},
Project: coordinator.ProjectInfo{
Name: "智能内窥镜项目",
CurrentStage: bizdev.StageV05_Feasibility,
CurrentStage: maturity.StageV05_Feasibility,
},
BlackboardFacts: map[string]string{},
}
@@ -75,8 +75,8 @@ func TestEngine_ClaimQueryMustNotExecuteWrite(t *testing.T) {
}
userCtx := &coordinator.UserProjectContext{
User: coordinator.UserInfo{ID: "user_101", Name: "张工", Role: "engineer", ProjectRole: "composer"},
Project: coordinator.ProjectInfo{Name: "测试项目"},
User: coordinator.UserInfo{ID: "user_101", Name: "张工", Role: "engineer", ProjectRole: "composer"},
Project: coordinator.ProjectInfo{Name: "测试项目"},
BlackboardFacts: map[string]string{},
}
+3 -3
View File
@@ -19,9 +19,9 @@ func TestEngine_RunStatusQuery_WithWorkDir(t *testing.T) {
}
userCtx := &coordinator.UserProjectContext{
User: coordinator.UserInfo{ID: "1", Name: "用户"},
Project: coordinator.ProjectInfo{Name: "项目"},
WorkDir: "/data/dev/ws-123",
User: coordinator.UserInfo{ID: "1", Name: "用户"},
Project: coordinator.ProjectInfo{Name: "项目"},
WorkDir: "/data/dev/ws-123",
BlackboardFacts: map[string]string{},
}
+14 -16
View File
@@ -4,9 +4,9 @@ import (
"context"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/maturity"
"chat-agent-system/internal/squad"
"strings"
)
@@ -75,7 +75,7 @@ func TestCoordinatorEngine_QueryProgress_NoBrief_SubstantiveReply(t *testing.T)
Name: "医疗器械研发项目",
CustomerName: "北京水木东方",
CurrentWorkflow: "intake",
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
},
BlackboardFacts: map[string]string{},
}
@@ -156,7 +156,7 @@ func TestCoordinatorEngine_AdvanceStage(t *testing.T) {
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "超声测试项目",
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
},
User: coordinator.UserInfo{
ProjectRole: "composer",
@@ -167,7 +167,7 @@ func TestCoordinatorEngine_AdvanceStage(t *testing.T) {
TopIntent: &intent.CandidateIntent{
Intent: intent.IntentAdvanceStage,
FilledSlots: map[string]string{
intent.SlotTargetStage: string(bizdev.StageV05_Feasibility),
intent.SlotTargetStage: string(maturity.StageV05_Feasibility),
},
},
}
@@ -180,7 +180,7 @@ func TestCoordinatorEngine_AdvanceStage(t *testing.T) {
if res.Status != coordinator.StatusAccepted {
t.Errorf("expected status ACCEPTED, got %s", res.Status)
}
if userCtx.Project.CurrentStage != bizdev.StageV05_Feasibility {
if userCtx.Project.CurrentStage != maturity.StageV05_Feasibility {
t.Errorf("expected stage V0.5_feasibility, got %s", userCtx.Project.CurrentStage)
}
}
@@ -190,9 +190,9 @@ func TestCoordinatorEngine_DeclineExpectation(t *testing.T) {
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "超声测试项目",
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
},
PendingExpectation: coordinator.BuildNextExpectation(bizdev.StageV01_Triage, "超声测试项目"),
PendingExpectation: coordinator.BuildNextExpectation(maturity.StageV01_Triage, "超声测试项目"),
}
recRes := &intent.IntentRecognitionResult{
@@ -225,7 +225,7 @@ func TestCoordinatorEngine_AmbiguousConsult(t *testing.T) {
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "超声测试项目",
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
},
}
@@ -255,7 +255,7 @@ func TestCoordinatorEngine_AdvanceStageWithPatch(t *testing.T) {
userCtx := &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{
Name: "超声测试项目",
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
},
BlackboardFacts: make(map[string]string),
}
@@ -266,8 +266,8 @@ func TestCoordinatorEngine_AdvanceStageWithPatch(t *testing.T) {
Intent: intent.IntentAdvanceStage,
Branch: intent.BranchExpectationHit,
FilledSlots: map[string]string{
intent.SlotTargetStage: string(bizdev.StageV05_Feasibility),
"target_market": "欧洲CE",
intent.SlotTargetStage: string(maturity.StageV05_Feasibility),
"target_market": "欧洲CE",
},
PatchSlots: map[string]string{
"target_market": "欧洲CE",
@@ -580,9 +580,9 @@ func TestCoordinatorEngine_RejectExpectationAndAffirmativeChain(t *testing.T) {
TopIntent: &intent.CandidateIntent{
Intent: intent.IntentDocGovReviewReject,
FilledSlots: map[string]string{
intent.SlotTargetDoc: "doc_cardio_01",
intent.SlotRejectReason: "第3节缺少血液相容性溶血试验评价指标",
intent.SlotTargetSection: "第3节",
intent.SlotTargetDoc: "doc_cardio_01",
intent.SlotRejectReason: "第3节缺少血液相容性溶血试验评价指标",
intent.SlotTargetSection: "第3节",
},
},
}
@@ -982,5 +982,3 @@ func TestCoordinatorEngine_TestingAnalysis(t *testing.T) {
t.Errorf("reply must not contain commercial triage boilerplate, got: %s", res.NaturalReply)
}
}
+20 -20
View File
@@ -36,9 +36,9 @@ func (e *CoordinatorEngine) handleSwitchWorkflow(_ context.Context, cand *intent
if userCtx.Project.ActiveWorkflowKey == targetWF {
return &CoordinationResult{
Status: StatusAccepted,
Intent: intent.IntentSwitchWorkflow,
NaturalReply: fmt.Sprintf("当前项目已激活工作流「%s」,无需重复切换。", targetWF),
Status: StatusAccepted,
Intent: intent.IntentSwitchWorkflow,
NaturalReply: fmt.Sprintf("当前项目已激活工作流「%s」,无需重复切换。", targetWF),
UpdatedContext: userCtx,
}, nil
}
@@ -83,9 +83,9 @@ func (e *CoordinatorEngine) handleTriggerWorkflow(_ context.Context, cand *inten
targetWF, workflowDisplayName(targetWF),
)
return &CoordinationResult{
Status: StatusAccepted,
Intent: intent.IntentTriggerWorkflow,
NaturalReply: msg,
Status: StatusAccepted,
Intent: intent.IntentTriggerWorkflow,
NaturalReply: msg,
UpdatedContext: userCtx,
}, nil
}
@@ -107,9 +107,9 @@ func (e *CoordinatorEngine) handleQueryWorkflowStatus(_ context.Context, _ *inte
}
}
return &CoordinationResult{
Status: StatusAccepted,
Intent: intent.IntentQueryWorkflowStatus,
NaturalReply: sb.String(),
Status: StatusAccepted,
Intent: intent.IntentQueryWorkflowStatus,
NaturalReply: sb.String(),
UpdatedContext: userCtx,
}, nil
}
@@ -129,9 +129,9 @@ func (e *CoordinatorEngine) handleQueryDocStatus(ctx context.Context, cand *inte
defaultStr(lifecycle, "请在工作台文档面板查看详情"),
)
return &CoordinationResult{
Status: StatusAccepted,
Intent: intent.IntentQueryDocStatus,
NaturalReply: reply,
Status: StatusAccepted,
Intent: intent.IntentQueryDocStatus,
NaturalReply: reply,
UpdatedContext: userCtx,
}, nil
}
@@ -149,18 +149,18 @@ func (e *CoordinatorEngine) handleHelpCapabilities(_ context.Context, _ *intent.
}
sb.WriteString("\n高风险操作(工作流切换、文档审批、阶段推进)需要明确确认后才会执行。")
return &CoordinationResult{
Status: StatusAccepted,
Intent: intent.IntentHelpCapabilities,
NaturalReply: sb.String(),
Status: StatusAccepted,
Intent: intent.IntentHelpCapabilities,
NaturalReply: sb.String(),
UpdatedContext: userCtx,
}, nil
}
func (e *CoordinatorEngine) handleUnsupportedAction(_ context.Context, _ *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
return &CoordinationResult{
Status: StatusAccepted,
Intent: intent.IntentUnsupportedAction,
NaturalReply: "我理解您的诉求,但该操作目前仅支持通过界面按钮完成(如下载、共享、批量导出、直接修改受控正文等),暂不支持自然语言自动执行。",
Status: StatusAccepted,
Intent: intent.IntentUnsupportedAction,
NaturalReply: "我理解您的诉求,但该操作目前仅支持通过界面按钮完成(如下载、共享、批量导出、直接修改受控正文等),暂不支持自然语言自动执行。",
UpdatedContext: userCtx,
}, nil
}
@@ -244,8 +244,8 @@ func buildWorkflowSwitchExpectation(targetWF string, userCtx *UserProjectContext
},
},
Payload: map[string]string{
intent.SlotTargetWorkflow: targetWF,
"selection_version": fmt.Sprintf("%d", userCtx.Project.WorkflowSelectionVersion),
intent.SlotTargetWorkflow: targetWF,
"selection_version": fmt.Sprintf("%d", userCtx.Project.WorkflowSelectionVersion),
},
}
}
+9 -9
View File
@@ -5,9 +5,9 @@ import (
"strings"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/maturity"
)
func TestEngine_SwitchWorkflow_NonCreatorRejected(t *testing.T) {
@@ -24,11 +24,11 @@ func TestEngine_SwitchWorkflow_NonCreatorRejected(t *testing.T) {
userCtx := &coordinator.UserProjectContext{
User: coordinator.UserInfo{ID: "42", Name: "张工", ProjectRole: "composer"},
Project: coordinator.ProjectInfo{
Name: "测试项目",
CreatorUserID: 1,
ActiveWorkflowKey: "main",
Name: "测试项目",
CreatorUserID: 1,
ActiveWorkflowKey: "main",
WorkflowSelectionVersion: 1,
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
},
AvailableActions: []string{"SWITCH_WORKFLOW"},
BlackboardFacts: map[string]string{},
@@ -61,7 +61,7 @@ func TestEngine_QueryWorkflowStatus(t *testing.T) {
Name: "测试项目",
ActiveWorkflowKey: "product_manager",
WorkflowSelectionVersion: 2,
CurrentStage: bizdev.StageV05_Feasibility,
CurrentStage: maturity.StageV05_Feasibility,
},
AvailableActions: []string{"QUERY_WORKFLOW", "TRIGGER_WORKFLOW:product_manager"},
BlackboardFacts: map[string]string{},
@@ -89,10 +89,10 @@ func TestEngine_HelpCapabilities(t *testing.T) {
}
userCtx := &coordinator.UserProjectContext{
User: coordinator.UserInfo{ID: "1", Name: "用户"},
Project: coordinator.ProjectInfo{Name: "项目"},
User: coordinator.UserInfo{ID: "1", Name: "用户"},
Project: coordinator.ProjectInfo{Name: "项目"},
AvailableActions: []string{"QUERY_PROGRESS", "SWITCH_WORKFLOW"},
BlackboardFacts: map[string]string{},
BlackboardFacts: map[string]string{},
}
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
@@ -4,11 +4,12 @@ import (
"context"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/app/stageflow"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/kb"
"chat-agent-system/internal/maturity"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/squad"
"chat-agent-system/internal/storage"
@@ -18,14 +19,14 @@ type countingBizdev struct {
stageCalls int
}
func (c *countingBizdev) ListTasks() []bizdev.TaskMeta { return nil }
func (c *countingBizdev) ListStages() []bizdev.StageMeta { return nil }
func (c *countingBizdev) ExecuteTask(_ context.Context, _ bizdev.TaskRunRequest) (*bizdev.TaskRunResult, error) {
return &bizdev.TaskRunResult{}, nil
func (c *countingBizdev) ListTasks() []stageflow.TaskMeta { return nil }
func (c *countingBizdev) ListStages() []stageflow.StageMeta { return nil }
func (c *countingBizdev) ExecuteTask(_ context.Context, _ stageflow.TaskRunRequest) (*stageflow.TaskRunResult, error) {
return &stageflow.TaskRunResult{}, nil
}
func (c *countingBizdev) ExecuteStage(_ context.Context, _ bizdev.StageRunRequest) (*bizdev.StageRunResult, error) {
func (c *countingBizdev) ExecuteStage(_ context.Context, _ stageflow.StageRunRequest) (*stageflow.StageRunResult, error) {
c.stageCalls++
return &bizdev.StageRunResult{StageTitle: "legacy"}, nil
return &stageflow.StageRunResult{StageTitle: "legacy"}, nil
}
func (c *countingBizdev) InspectWorkspace(_ context.Context, _ string) (*squad.WorkspaceState, error) {
return nil, nil
@@ -58,14 +59,14 @@ func TestDialogueAdvanceStage_LiveInstanceDoesNotUseBizdev(t *testing.T) {
engine.SetInstanceService(instSvc)
userCtx := &UserProjectContext{
Project: ProjectInfo{ID: "55", CreatorUserID: 1, CurrentStage: bizdev.StageV01_Triage},
Project: ProjectInfo{ID: "55", CreatorUserID: 1, CurrentStage: maturity.StageV01_Triage},
User: UserInfo{ID: "1", ProjectRole: "owner"},
}
recRes := &intent.IntentRecognitionResult{
TopIntent: &intent.CandidateIntent{
Intent: intent.IntentAdvanceStage,
FilledSlots: map[string]string{
intent.SlotTargetStage: string(bizdev.StageV05_Feasibility),
intent.SlotTargetStage: string(maturity.StageV05_Feasibility),
},
},
}
+2 -2
View File
@@ -5,11 +5,11 @@ import (
"testing"
"time"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/kb"
"chat-agent-system/internal/maturity"
)
func TestMemoryComposer_HierarchyAndPriority(t *testing.T) {
@@ -33,7 +33,7 @@ func TestMemoryComposer_HierarchyAndPriority(t *testing.T) {
Name: "超声切割止血系统",
DeviceType: "有源手术器械",
TargetMarket: "国内+CE",
CurrentStage: bizdev.StageV01_Triage,
CurrentStage: maturity.StageV01_Triage,
CurrentWorkflow: "立项摸底",
},
TaskState: taskState,
@@ -6,25 +6,26 @@ import (
"testing"
"time"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/app/stageflow"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/kb"
"chat-agent-system/internal/maturity"
"chat-agent-system/internal/squad"
"chat-agent-system/internal/storage"
)
type pendingReplyBizdev struct {
res *bizdev.TaskRunResult
res *stageflow.TaskRunResult
}
func (p *pendingReplyBizdev) ListTasks() []bizdev.TaskMeta { return nil }
func (p *pendingReplyBizdev) ListStages() []bizdev.StageMeta { return nil }
func (p *pendingReplyBizdev) ExecuteTask(_ context.Context, _ bizdev.TaskRunRequest) (*bizdev.TaskRunResult, error) {
func (p *pendingReplyBizdev) ListTasks() []stageflow.TaskMeta { return nil }
func (p *pendingReplyBizdev) ListStages() []stageflow.StageMeta { return nil }
func (p *pendingReplyBizdev) ExecuteTask(_ context.Context, _ stageflow.TaskRunRequest) (*stageflow.TaskRunResult, error) {
return p.res, nil
}
func (p *pendingReplyBizdev) ExecuteStage(_ context.Context, _ bizdev.StageRunRequest) (*bizdev.StageRunResult, error) {
func (p *pendingReplyBizdev) ExecuteStage(_ context.Context, _ stageflow.StageRunRequest) (*stageflow.StageRunResult, error) {
return nil, nil
}
func (p *pendingReplyBizdev) InspectWorkspace(_ context.Context, _ string) (*squad.WorkspaceState, error) {
@@ -39,7 +40,7 @@ func (p *pendingReplyBizdev) DocGov() *docgov.GovernanceEngine { return ni
func (p *pendingReplyBizdev) KB() *kb.Client { return nil }
func TestSyncTriggerTask_PendingConfirmation_NotSavedCompleted(t *testing.T) {
pending := &bizdev.TaskRunResult{
pending := &stageflow.TaskRunResult{
Success: true,
TaskKey: "intake",
ArtifactTitle: "用户初始需求收集表",
@@ -55,7 +56,7 @@ func TestSyncTriggerTask_PendingConfirmation_NotSavedCompleted(t *testing.T) {
eng := NewCoordinatorEngine(&pendingReplyBizdev{res: pending})
userCtx := &UserProjectContext{
User: UserInfo{ID: "1", Name: "u", ProjectRole: "author"},
Project: ProjectInfo{ID: "9", Name: "P", CurrentStage: bizdev.StageV01_Triage},
Project: ProjectInfo{ID: "9", Name: "P", CurrentStage: maturity.StageV01_Triage},
}
cand := &intent.CandidateIntent{
Intent: intent.IntentTriggerTask,
@@ -78,7 +79,7 @@ func TestSyncTriggerTask_PendingConfirmation_NotSavedCompleted(t *testing.T) {
if !strings.Contains(res.NaturalReply, "文档候选已生成") || !strings.Contains(res.NaturalReply, "待编制人确认") {
t.Fatalf("sync reply missing pending wording: %s", res.NaturalReply)
}
if res.TaskStatus != bizdev.TaskStatusPendingAuthorConfirmation {
if res.TaskStatus != stageflow.TaskStatusPendingAuthorConfirmation {
t.Fatalf("TaskStatus=%q", res.TaskStatus)
}
if res.DocID != "doc_proj9_intake" || res.PendingAgentCandidateID != 99 || res.PendingChangeReportID != 12 {
@@ -87,7 +88,7 @@ func TestSyncTriggerTask_PendingConfirmation_NotSavedCompleted(t *testing.T) {
}
func TestSyncTriggerTask_Committed_KeepsSavedWording(t *testing.T) {
ok := &bizdev.TaskRunResult{
ok := &stageflow.TaskRunResult{
Success: true,
TaskKey: "intake",
ArtifactTitle: "用户初始需求收集表",
@@ -98,7 +99,7 @@ func TestSyncTriggerTask_Committed_KeepsSavedWording(t *testing.T) {
eng := NewCoordinatorEngine(&pendingReplyBizdev{res: ok})
userCtx := &UserProjectContext{
User: UserInfo{ID: "1", ProjectRole: "author"},
Project: ProjectInfo{ID: "1", Name: "P", CurrentStage: bizdev.StageV01_Triage},
Project: ProjectInfo{ID: "1", Name: "P", CurrentStage: maturity.StageV01_Triage},
}
cand := &intent.CandidateIntent{
Intent: intent.IntentTriggerTask,
@@ -115,13 +116,13 @@ func TestSyncTriggerTask_Committed_KeepsSavedWording(t *testing.T) {
if !strings.Contains(res.NaturalReply, "正文已保存") {
t.Fatalf("committed sync reply should mention saved body: %s", res.NaturalReply)
}
if res.TaskStatus != bizdev.TaskStatusCompleted {
if res.TaskStatus != stageflow.TaskStatusCompleted {
t.Fatalf("TaskStatus=%q", res.TaskStatus)
}
}
func TestFormatTriggerTaskReply_PendingUsesCompletionSummary(t *testing.T) {
pending := &bizdev.TaskRunResult{
pending := &stageflow.TaskRunResult{
Success: true,
ArtifactTitle: "法规对齐报告",
PendingAgentCandidateID: 1,
@@ -129,7 +130,7 @@ func TestFormatTriggerTaskReply_PendingUsesCompletionSummary(t *testing.T) {
DocRecord: &docgov.DocumentRecord{DocID: "doc_x"},
}
reply, status := formatTriggerTaskReply(pending, "/tmp/out")
if status != bizdev.TaskStatusPendingAuthorConfirmation {
if status != stageflow.TaskStatusPendingAuthorConfirmation {
t.Fatalf("status=%q", status)
}
if strings.Contains(reply, "正文已保存") {
-1
View File
@@ -298,4 +298,3 @@ func sanitizeAdhocTaskKey(title string) string {
}
return "adhoc_" + key
}
+6 -7
View File
@@ -5,8 +5,8 @@ import (
"strings"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/maturity"
)
// ============================================================================
@@ -109,8 +109,8 @@ func TestUpgradeFlow_AttachCooldownAndRedeem(t *testing.T) {
engine := NewCoordinatorEngine(nil)
newCtx := func() *UserProjectContext {
return &UserProjectContext{
User: UserInfo{Name: "张工", ProjectRole: "composer"},
Project: ProjectInfo{Name: "掌上超声项目", CurrentStage: bizdev.StageV01_Triage},
User: UserInfo{Name: "张工", ProjectRole: "composer"},
Project: ProjectInfo{Name: "掌上超声项目", CurrentStage: maturity.StageV01_Triage},
BlackboardFacts: map[string]string{},
}
}
@@ -119,7 +119,7 @@ func TestUpgradeFlow_AttachCooldownAndRedeem(t *testing.T) {
// 1. 模糊分支命中复杂画像 → 初答 + 升级建议挂载
rec := &intent.IntentRecognitionResult{
RawInput: complexInput,
Branch: intent.BranchAmbiguousFallback,
Branch: intent.BranchAmbiguousFallback,
TopIntent: &intent.CandidateIntent{Intent: intent.IntentCasualChat, Confidence: 0.2, Branch: intent.BranchAmbiguousFallback},
}
res, err := engine.ProcessIntent(context.Background(), rec, newCtx())
@@ -187,11 +187,11 @@ func TestUpgradeFlow_NoHintForSimpleInputs(t *testing.T) {
for _, input := range []string{"这个先放放,你觉得呢", "好的知道了", "今天天气不错"} {
userCtx := &UserProjectContext{
User: UserInfo{Name: "张工", ProjectRole: "composer"},
Project: ProjectInfo{Name: "智能输注泵项目", CurrentStage: bizdev.StageV01_Triage},
Project: ProjectInfo{Name: "智能输注泵项目", CurrentStage: maturity.StageV01_Triage},
}
rec := &intent.IntentRecognitionResult{
RawInput: input,
Branch: intent.BranchAmbiguousFallback,
Branch: intent.BranchAmbiguousFallback,
TopIntent: &intent.CandidateIntent{Intent: intent.IntentCasualChat, Confidence: 0.2, Branch: intent.BranchAmbiguousFallback},
}
res, err := engine.ProcessIntent(context.Background(), rec, userCtx)
@@ -206,4 +206,3 @@ func TestUpgradeFlow_NoHintForSimpleInputs(t *testing.T) {
}
}
}