docs(plan): add implementation plan for dialogue-driven content synthesis and hierarchical intent funnel

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WeiCHEN
2026-09-08 17:27:00 +08:00
co-authored by Cursor
parent db238a467a
commit 8aa0698dfb
@@ -0,0 +1,600 @@
# 对话驱动受控内容产出、分级意图消解与交互卡片实施计划
# (Dialogue-Driven Content Synthesis, Hierarchical Intent Funnel & Interactive Cards Implementation Plan)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 构建水木东方医疗器械 AI 协同系统的对话驱动内容产出闭环,实现四级意图漏斗消解、智能语义定位段落批注、受控状态机权限门禁及带时效/防重/可反悔的对话交互卡片。
**Architecture:** 后端在 Go L1 识别层建立“态势剪枝 ➔ 领域路由 ➔ 细分意图 ➔ 门禁卡片”四级漏斗,并将驳回意见下沉至 Block AST 提取段落锚点与批注;在 L2 协调层实现权责校验与交互卡片(InteractiveCard)下发;前端在 React 19 + TypeScript 中实现有状态、带轮次 TTL 与操作幂等性的对话卡片组件并与受控文档视图联动。
**Tech Stack:** Go 1.22+, PostgreSQL + pgxpool, React 19, TypeScript, Vite, Tailwind CSS / FlowMind Design Tokens, Lucide Icons.
## Global Constraints
- 严禁通过对话操作 UI 视窗或排版(折叠侧边栏、切换主题、打开抽屉等),此类指令一律不作执行型意图处理;
- 严禁通过对话修改用户身份(姓名、工号、职位、部门、资质)与系统底层管理配置(模型网关密钥、向量库连接),此类尝试必须触发合规拦截;
- 驳回、批准、大阶段跃迁等高影响法定动作必须通过二次确认卡或显式确认流转,且驳回意见必须智能锚定到段落块;
- 交互卡片必须支持 3 轮对话超时 (TTL)、外部状态变迁失效及 60 秒合法窗口期撤销机制;
- 遵循 Conventional Commits 规范,按原子性分层逐步提交代码;
- 交付前验收必须使用真实 Agent(`pi-agent` + 实际运行的 `MiniMax-M3` 容器)进行端到端全链路实测。
---
### Task 1: 核心意图枚举与交互卡片数据契约扩充 (Data Contracts & Schemas)
**Files:**
- Modify: `internal/intent/types.go`
- Modify: `web/src/types/workbench.ts`
- Test: `internal/intent/types_test.go`
- Test: `web/src/types/types.test.ts`
**Interfaces:**
- Produces (Go):
- `intent.IntentType`: `IntentDocGovClaim`, `IntentDocGovSubmitReview`, `IntentDocGovReviewApprove`, `IntentDocGovReviewReject`, `IntentDocGovFinalApprove`, `IntentDocGovFinalReject`, `IntentDocGovProposalDecide`, `IntentQueryRegulations`, `IntentQuerySimilarCases`, `IntentQueryUserRights`, `IntentSecurityForbidden`
- `intent.InteractiveCardPayload`, `intent.CardAction`
- `intent.ContextSnapshot` (扩展 `ActiveDocID`, `DocLifecycleState`, `UserDocRole`)
- Produces (TypeScript):
- `InteractiveCardData`, `CardActionItem`, `InteractiveCardType`, `CardActionStatus`
- `ChatMessage.interactiveCard`
- [ ] **Step 1: 编写 Go 类型单元测试,断言新意图枚举与卡片载荷序列化**
创建 `internal/intent/types_test.go`:
```go
package intent_test
import (
"encoding/json"
"testing"
"time"
"chat-agent-system/internal/intent"
)
func TestInteractiveCardPayload_Serialization(t *testing.T) {
card := intent.InteractiveCardPayload{
CardID: "card_rej_001",
CardType: "CONFIRMATION",
Title: "审核驳回流转确认",
TargetDocID: "doc-002",
TargetDocTitle: "微创外科全球竞品深度调研",
TargetSectionID: "blk_emc_05",
SectionTitle: "第3节 主要竞品电气安全对比",
ProposedContent: "缺少史赛克高频电刀共模抑制比测试说明",
CurrentStatus: "active",
TTLTurns: 3,
CreatedAt: time.Now(),
Actions: []intent.CardAction{
{
ActionID: "confirm_reject",
Label: "确认正式驳回退回",
Style: "danger",
Payload: map[string]string{
"doc_id": "doc-002",
"reason": "缺少史赛克抗扰度指标",
},
IsAffirmativeDefault: true,
},
{
ActionID: "cancel",
Label: "放弃驳回",
Style: "subtle",
},
},
}
data, err := json.Marshal(card)
if err != nil {
t.Fatalf("marshal card failed: %v", err)
}
var decoded intent.InteractiveCardPayload
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal card failed: %v", err)
}
if decoded.CardID != "card_rej_001" || decoded.CardType != "CONFIRMATION" {
t.Errorf("unexpected decoded card: %+v", decoded)
}
if len(decoded.Actions) != 2 || decoded.Actions[0].ActionID != "confirm_reject" {
t.Errorf("unexpected decoded actions: %+v", decoded.Actions)
}
}
```
- [ ] **Step 2: 运行测试验证失败**
Run: `go test -v ./internal/intent -run TestInteractiveCardPayload_Serialization`
Expected: FAIL (compilation error: undefined `InteractiveCardPayload`, etc.)
- [ ] **Step 3: 扩充 internal/intent/types.go 定义**
修改 `internal/intent/types.go`,增加治理流转意图、安全拦截意图与交互卡片结构体:
```go
// 在 IntentType 枚举中追加定义
const (
// 法定文档治理流转
IntentDocGovClaim IntentType = "DOCGOV_CLAIM"
IntentDocGovSubmitReview IntentType = "DOCGOV_SUBMIT_REVIEW"
IntentDocGovReviewApprove IntentType = "DOCGOV_REVIEW_APPROVE"
IntentDocGovReviewReject IntentType = "DOCGOV_REVIEW_REJECT"
IntentDocGovFinalApprove IntentType = "DOCGOV_FINAL_APPROVE"
IntentDocGovFinalReject IntentType = "DOCGOV_FINAL_REJECT"
IntentDocGovProposalDecide IntentType = "DOCGOV_PROPOSAL_DECIDE"
// 决策与合规查询
IntentQueryRegulations IntentType = "QUERY_REGULATIONS"
IntentQuerySimilarCases IntentType = "QUERY_SIMILAR_CASES"
IntentQueryUserRights IntentType = "QUERY_USER_RIGHTS"
// 安全与权限红线拦截
IntentSecurityForbidden IntentType = "SECURITY_FORBIDDEN"
)
// 常见槽位 Key 追加
const (
SlotTargetDoc = "target_doc"
SlotRejectReason = "reject_reason"
SlotTargetSection = "target_section"
SlotProposalAction = "proposal_action"
SlotForbiddenTarget = "forbidden_target"
)
// CardAction 交互卡片按钮动作
type CardAction struct {
ActionID string `json:"action_id"`
Label string `json:"label"`
Style string `json:"style"` // primary, danger, subtle
Payload map[string]string `json:"payload,omitempty"`
IsAffirmativeDefault bool `json:"is_affirmative_default,omitempty"`
}
// InteractiveCardPayload 对话交互卡片载荷
type InteractiveCardPayload struct {
CardID string `json:"card_id"`
CardType string `json:"card_type"` // CONFIRMATION, SLOT_FILLING, DIFF_PROPOSAL, REMINDER_NOTICE
Title string `json:"title"`
TargetDocID string `json:"target_doc_id,omitempty"`
TargetDocTitle string `json:"target_doc_title,omitempty"`
TargetSectionID string `json:"target_section_id,omitempty"`
SectionTitle string `json:"section_title,omitempty"`
ProposedContent string `json:"proposed_content,omitempty"`
ImpactRisk string `json:"impact_risk,omitempty"`
CurrentStatus string `json:"current_status"` // active, executing, resolved, cancelled, outdated, expired
TTLTurns int `json:"ttl_turns"`
CreatedAt time.Time `json:"created_at"`
Actions []CardAction `json:"actions,omitempty"`
}
```
并在 `ContextSnapshot` 结构体中扩展:
```go
type ContextSnapshot struct {
SnapshotID string `json:"snapshot_id,omitempty"`
ProjectName string `json:"project_name,omitempty"`
CurrentStage string `json:"current_stage,omitempty"`
ActiveDocID string `json:"active_doc_id,omitempty"`
DocLifecycleState string `json:"doc_lifecycle_state,omitempty"`
UserDocRole string `json:"user_doc_role,omitempty"`
AvailableActions []string `json:"available_actions,omitempty"`
PendingExpectation *PendingExpectation `json:"pending_expectation,omitempty"`
}
```
- [ ] **Step 4: 扩充 web/src/types/workbench.ts 前端类型定义**
在 `web/src/types/workbench.ts` 中新增卡片类型定义并注入 `ChatMessage`:
```typescript
export type InteractiveCardType =
| 'CONFIRMATION'
| 'SLOT_FILLING'
| 'DIFF_PROPOSAL'
| 'REMINDER_NOTICE';
export type CardActionStatus =
| 'active'
| 'executing'
| 'resolved'
| 'cancelled'
| 'outdated'
| 'expired';
export interface CardActionItem {
actionId: string;
label: string;
style: 'primary' | 'danger' | 'subtle';
payload?: Record<string, string>;
isAffirmativeDefault?: boolean;
}
export interface InteractiveCardData {
cardId: string;
cardType: InteractiveCardType;
title: string;
targetDocId?: string;
targetDocTitle?: string;
targetSectionId?: string;
sectionTitle?: string;
proposedContent?: string;
impactRisk?: string;
status: CardActionStatus;
ttlTurns: number;
createdAt?: string;
actions: CardActionItem[];
resolutionSummary?: string;
resolvedAt?: string;
resolvedBy?: string;
}
```
并在 `ChatMessage` 中增加字段:
```typescript
export interface ChatMessage {
// ... 既有字段 ...
interactiveCard?: InteractiveCardData;
}
```
- [ ] **Step 5: 验证 Go 测试与前端 TypeScript 类型编译**
Run: `go test -v ./internal/intent -run TestInteractiveCardPayload_Serialization`
Expected: PASS
Run: `cd web && npx tsc --noEmit`
Expected: PASS (0 errors)
- [ ] **Step 6: Commit**
```bash
git add internal/intent/types.go internal/intent/types_test.go web/src/types/workbench.ts
git commit -m "feat(intent): add interactive card payload and extended intent contracts"
```
---
### Task 2: 四级漏斗意图消解引擎与槽位提取器实现 (Hierarchical Intent Recognizer)
**Files:**
- Modify: `internal/intent/recognizer.go`
- Test: `internal/intent/recognizer_test.go`
**Interfaces:**
- Consumes: `ContextSnapshot` (包含 `ActiveDocID`, `DocLifecycleState`, `UserDocRole`)
- Produces: `IntentRecognitionResult` (TopIntent 包含细分意图、置信度、槽位键值对)
- [ ] **Step 1: 编写四级漏斗意图消解测试套件**
在 `internal/intent/recognizer_test.go` 中新增测试用例:
1. 审核人驳回指令(带理由)识别为 `DOCGOV_REVIEW_REJECT`,抽取 `target_doc` 与 `reject_reason`;
2. 非审核人试图驳回时,前置剪枝并返回权限不符;
3. 试图修改自身身份/职位时,命中 `SECURITY_FORBIDDEN`;
4. 咨询法规标准时,识别为 `QUERY_REGULATIONS`;
5. 查询自身权限时,识别为 `QUERY_USER_RIGHTS`。
```go
func TestIntentRecognition_ReviewReject(t *testing.T) {
rec := intent.NewRecognizer()
ctx := &intent.ContextSnapshot{
SnapshotID: "ctx_rej_1",
CurrentStage: string(bizdev.StageV05_Feasibility),
ActiveDocID: "doc-002",
DocLifecycleState: "reviewing",
UserDocRole: "reviewer",
}
res, err := rec.Recognize(context.Background(), "把调研报告审核驳回,意见为:第3节竞品对比缺少史赛克高频电刀的抗扰度指标", ctx)
if err != nil {
t.Fatalf("recognize failed: %v", err)
}
if res.TopIntent.Intent != intent.IntentDocGovReviewReject {
t.Errorf("expected intent DOCGOV_REVIEW_REJECT, got %s", res.TopIntent.Intent)
}
if res.TopIntent.FilledSlots[intent.SlotRejectReason] == "" {
t.Errorf("expected filled slot reject_reason")
}
}
func TestIntentRecognition_SecurityForbidden(t *testing.T) {
rec := intent.NewRecognizer()
res, err := rec.Recognize(context.Background(), "帮我把身份修改为研发总监,工号改成SMDF-001", nil)
if err != nil {
t.Fatalf("recognize failed: %v", err)
}
if res.TopIntent.Intent != intent.IntentSecurityForbidden {
t.Errorf("expected intent SECURITY_FORBIDDEN, got %s", res.TopIntent.Intent)
}
if res.TopIntent.Confidence != 1.0 {
t.Errorf("expected confidence 1.0 for security violations")
}
}
```
- [ ] **Step 2: 运行测试验证失败**
Run: `go test -v ./internal/intent -run "TestIntentRecognition_ReviewReject|TestIntentRecognition_SecurityForbidden"`
Expected: FAIL
- [ ] **Step 3: 在 internal/intent/recognizer.go 实现四级漏斗与槽位提取**
在 `RuleBasedRecognizer.Recognize` 中重构流水线:
1. **Stage 0 (态势前置过滤与安全防护检查)**:
- 检查 `isSecurityViolation(input)`:若包含“修改身份”、“改成批准人”、“修改职位”、“修改设置”、“修改网关”,直接返回 `IntentSecurityForbidden`;
- 检查纯 UI 视窗指令(如“打开工具栏”、“折叠侧边栏”),归入 `IntentCasualChat` 并标注为不可执行 UI 提示。
2. **Stage 1 (领域大类路由)**:
- `GOV_LIFECYCLE`:驳回、通过、送审、认领、批准;
- `CONTENT_SYNTHESIS`:推进阶段、生成任务、参数事实;
- `DECISION_QUERY`:查进度、查法规、查案例、查权限;
- `EXPECTATION_ACTION`:好、行、就按这个办、放弃。
3. **Stage 2 (微观细分动作与槽位提取)**:
- 实现 `matchDocGovAction(trimmed, userCtx)`:
- 若包含“驳回”,提取驳回原因并根据当前文档状态映射为 `IntentDocGovReviewReject` 或 `IntentDocGovFinalReject`;
- 若包含“通过”或“审核通过”,映射为 `IntentDocGovReviewApprove`;
- 若包含“批准”或“终审”,映射为 `IntentDocGovFinalApprove`;
- 若包含“认领”,映射为 `IntentDocGovClaim`;
- 若包含“送审”,映射为 `IntentDocGovSubmitReview`。
- 实现 `matchKnowledgeQuery(trimmed)`:
- 法规强标(GB 9706, YY, FDA, CE)➔ `IntentQueryRegulations`;
- 标杆案例(参考案例、往期经验)➔ `IntentQuerySimilarCases`;
- 用户权限(我的权限、我负责什么)➔ `IntentQueryUserRights`。
4. **Stage 3 (槽位完备性与置信度裁决)**:
- 若 `DOCGOV_REVIEW_REJECT` 缺少 `reject_reason`,置 `RequiresClarifying = true`,追问:“请说明具体的法定驳回理由,我将为您注入文档批注并退回编制阶段。”
- [ ] **Step 4: 运行意图识别全量测试**
Run: `go test -v ./internal/intent/...`
Expected: PASS (所有测试用例 100% 通过)
- [ ] **Step 5: Commit**
```bash
git add internal/intent/recognizer.go internal/intent/recognizer_test.go
git commit -m "feat(intent): implement 4-stage hierarchical intent funnel and slot extraction"
```
---
### Task 3: L2 协调引擎治理门禁、语义定位与交互卡片挂载 (Coordinator & Semantic Anchoring)
**Files:**
- Modify: `internal/coordinator/engine.go`
- Modify: `internal/coordinator/context.go`
- Test: `internal/coordinator/engine_test.go`
**Interfaces:**
- Consumes: `intent.IntentRecognitionResult`, `UserProjectContext`
- Produces: `CoordinationResult` (包含 `InteractiveCard`, `NaturalReply`, `UpdatedContext`)
- [ ] **Step 1: 编写协调引擎治理操作与卡片下发测试用例**
在 `internal/coordinator/engine_test.go` 中编写:
1. `TestCoordinatorEngine_ReviewRejectWithCard`:审核人下达驳回指令时,协调引擎进行权责核验,智能扫描文档内容定位段落,生成带 `CONFIRMATION` 卡片的回执;
2. `TestCoordinatorEngine_SecurityForbidden`:试图篡改身份时,协调引擎返回严肃合规声明,不修改任何用户上下文;
3. `TestCoordinatorEngine_QueryUserRights`:查询当前用户在项目的各文档法定职责。
```go
func TestCoordinatorEngine_ReviewRejectWithCard(t *testing.T) {
engine := coordinator.NewCoordinatorEngine(nil)
userCtx := &coordinator.UserProjectContext{
User: coordinator.UserInfo{
ID: "user_102",
Name: "李主管",
Role: "reviewer",
},
Project: coordinator.ProjectInfo{
Name: "微创内窥镜项目",
},
}
recRes := &intent.IntentRecognitionResult{
TopIntent: &intent.CandidateIntent{
Intent: intent.IntentDocGovReviewReject,
FilledSlots: map[string]string{
intent.SlotTargetDoc: "doc-002",
intent.SlotRejectReason: "第3节缺少史赛克高频电刀的抗扰度指标",
},
},
}
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
if err != nil {
t.Fatalf("process intent failed: %v", err)
}
if res.InteractiveCard == nil {
t.Fatalf("expected interactive card payload in coordination result")
}
if res.InteractiveCard.CardType != "CONFIRMATION" {
t.Errorf("expected card type CONFIRMATION, got %s", res.InteractiveCard.CardType)
}
if len(res.InteractiveCard.Actions) < 2 {
t.Errorf("expected at least 2 actions (confirm and cancel)")
}
}
```
- [ ] **Step 2: 运行测试验证失败**
Run: `go test -v ./internal/coordinator -run TestCoordinatorEngine_ReviewRejectWithCard`
Expected: FAIL
- [ ] **Step 3: 在 internal/coordinator/engine.go 实现业务逻辑分发**
1. 在 `ProcessIntent` 的 `switch top.Intent` 中挂载分支:
- `IntentSecurityForbidden` ➔ 调用 `handleSecurityForbidden(userCtx)`;
- `IntentDocGovReviewReject` / `IntentDocGovFinalReject` ➔ 调用 `handleDocGovReject(ctx, top, userCtx)`;
- `IntentDocGovReviewApprove` / `IntentDocGovFinalApprove` ➔ 调用 `handleDocGovApprove(ctx, top, userCtx)`;
- `IntentDocGovClaim` ➔ 调用 `handleDocGovClaim(ctx, top, userCtx)`;
- `IntentDocGovSubmitReview` ➔ 调用 `handleDocGovSubmitReview(ctx, top, userCtx)`;
- `IntentQueryRegulations` ➔ 调用 `handleQueryRegulations(ctx, top, userCtx)`;
- `IntentQuerySimilarCases` ➔ 调用 `handleQuerySimilarCases(ctx, top, userCtx)`;
- `IntentQueryUserRights` ➔ 调用 `handleQueryUserRights(ctx, top, userCtx)`。
2. 实现 `handleDocGovReject`:
- 提取驳回意见中的章节线索(“第3节”、“抗扰度”);
- 构造 `InteractiveCardPayload`,卡片类型为 `CONFIRMATION`,提供“确认正式驳回退回”与“放弃”两个选项;
- 构造自然回复:“已为您起草针对《%s》的驳回流转意见。请审阅下方卡片中的拟注入批注,确认无误后点击确认或回复‘确认’。”
3. 实现 `handleSecurityForbidden`:
- 回复合规提示:“⚠️ **合规权限拦截**:根据医疗器械质量管理规范(ISO 13485 / 21 CFR Part 11),企业人员的法定身份(姓名、工号、职位、部门)与系统控制台属于基线受控资产,不支持通过对话动态修改。您可通过‘查询我的权限’查看您在当前各文档中的法定职责。”
- [ ] **Step 4: 运行协调引擎全量测试**
Run: `go test -v ./internal/coordinator/...`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add internal/coordinator/engine.go internal/coordinator/context.go internal/coordinator/engine_test.go
git commit -m "feat(coordinator): implement docgov gates, semantic anchoring and interactive card dispatch"
```
---
### Task 4: 前端对话交互卡片组件开发 (InteractiveCard Component)
**Files:**
- Create: `web/src/components/chat/InteractiveCard.tsx`
- Create: `web/src/components/chat/InteractiveCard.test.ts`
- Modify: `web/src/styles/tokens.css`
**Interfaces:**
- Consumes: `InteractiveCardData`, `onExecuteAction: (cardId: string, actionId: string, payload?: any) => void`
- Produces: 具备 4 种类型展示、轮次 TTL 倒计时、防重脉冲与已完成印章的有状态卡片组件
- [ ] **Step 1: 编写前端交互卡片状态机逻辑测试**
创建 `web/src/components/chat/InteractiveCard.test.ts`,验证卡片状态变迁(active ➔ executing ➔ resolved / outdated):
```typescript
import { describe, it, expect } from 'vitest';
import { InteractiveCardData } from '../../types/workbench';
describe('InteractiveCard Logic', () => {
it('should format resolution text correctly', () => {
const card: InteractiveCardData = {
cardId: 'c-1',
cardType: 'CONFIRMATION',
title: '审核驳回流转确认',
status: 'resolved',
ttlTurns: 3,
actions: [],
resolutionSummary: '已正式退回编制阶段,批注已挂载',
resolvedBy: '李主管',
resolvedAt: '16:10',
};
expect(card.status).toBe('resolved');
expect(card.resolutionSummary).toContain('已正式退回编制阶段');
});
});
```
- [ ] **Step 2: 运行测试验证**
Run: `cd web && npx vitest run src/components/chat/InteractiveCard.test.ts`
Expected: PASS
- [ ] **Step 3: 编写 web/src/components/chat/InteractiveCard.tsx**
完整实现交互卡片组件,支持:
- `CONFIRMATION`、`SLOT_FILLING`、`DIFF_PROPOSAL`、`REMINDER_NOTICE` 4 种视觉排版;
- 状态展示:`active` 显示按钮操作;`executing` 显示 Loading 动效与禁用;`resolved` 盖上“✅ 已生效”印章;`outdated` 显示“⚠️ 文档状态已变更,操作失效”;`expired` 显示“⏱️ 已超时”;
- 锚定段落与批注摘要展示框;
- 点击后触发 `onExecuteAction(card.cardId, action.actionId, action.payload)` 并激活局部防重锁定。
- [ ] **Step 4: 验证组件 TypeScript 编译**
Run: `cd web && npx tsc --noEmit`
Expected: PASS (0 errors)
- [ ] **Step 5: Commit**
```bash
git add web/src/components/chat/InteractiveCard.tsx web/src/components/chat/InteractiveCard.test.ts
git commit -m "feat(web): build interactive card component with lifecycle and idempotency"
```
---
### Task 5: 前端对话流、卡片交互与受控文档视图联动闭环 (Frontend Integration)
**Files:**
- Modify: `web/src/components/chat/MessageBubble.tsx`
- Modify: `web/src/components/chat/MessageStream.tsx`
- Modify: `web/src/App.tsx`
**Interfaces:**
- Consumes: `InteractiveCardData`, `ChatMessage`, `DocumentAST`
- Produces: 对话气泡中卡片渲染、用户点击卡片按钮或对话肯定回复时的状态机联动,以及驳回批注在右侧 `BlockRenderer` 中的自动高亮
- [ ] **Step 1: 在 MessageBubble.tsx 中集成 InteractiveCard 渲染**
修改 `web/src/components/chat/MessageBubble.tsx`:
- 接收 `onCardAction?: (cardId: string, actionId: string, payload?: any) => void` 属性;
- 在消息气泡正文下方,若存在 `message.interactiveCard`,则渲染 `<InteractiveCard card={message.interactiveCard} onExecuteAction={onCardAction} />`。
- [ ] **Step 2: 在 MessageStream.tsx 中穿透 onCardAction 事件**
修改 `web/src/components/chat/MessageStream.tsx`,将 `onCardAction` 逐层传递给各个 `MessageBubble`。
- [ ] **Step 3: 在 App.tsx 中打通卡片兑现与受控文档联动逻辑**
修改 `web/src/App.tsx`:
1. 实现 `handleCardAction(cardId, actionId, payload)`:
- 若 `actionId === 'confirm_reject'`:
- 调用既有 `handleRejectReview(payload.reason)`,将目标文档(如 `doc-002`)生命周期由 `reviewing` 回退为 `drafting`;
- 扫描该文档的 `DocumentAST`,在包含“电气安全”或“竞品对比”的目标 `DocumentBlock` 上追加一条 `type='issue'` 的批注;
- 将当前卡片状态更新为 `resolved`,记录 `resolvedAt` 与 `resolvedBy: currentUser.name`;
- 切换当前右侧激活文档为 `doc-002` 并滚动至该 Block 高亮显示 2 秒;
- 若 `actionId === 'cancel'`:
- 将卡片状态更新为 `cancelled`;
- 追加一条助手消息:“已取消驳回操作,文档保持待审核状态。”
2. 扩充 `handleSendMessage` 对话触发逻辑:
- 若用户输入包含“驳回”,且当前处于审核阶段,则自动生成 `CONFIRMATION` 卡片挂载在最新的助手消息中;
- 若用户输入“好”或“确认”,自动检测上一轮消息是否存在处于 `active` 的卡片,若有则自动调用 `handleCardAction` 执行其默认动作(`isAffirmativeDefault === true`),实现“对话打字与点击卡片双轨同效”。
- [ ] **Step 4: 验证前端构建与类型编译**
Run: `cd web && npm run build`
Expected: PASS (`dist/` 编译成功)
- [ ] **Step 5: Commit**
```bash
git add web/src/components/chat/MessageBubble.tsx web/src/components/chat/MessageStream.tsx web/src/App.tsx
git commit -m "feat(web): integrate interactive cards into chat stream and wire docgov actions"
```
---
### Task 6: 真实环境全链路实测与测试报告归档 (Real Agent E2E & Report)
**Files:**
- Create: `.doc/test-reports/2026-09-08-dialogue-driven-content-and-hierarchical-intent-test-report.md`
**Constraints:**
- **必须遵循真实 Agent 交付铁律**:交付前验收必须使用**真实环境与真实 Agent(`pi-agent` + 实际运行的 `MiniMax-M3` 容器)**执行端到端实测;
- 严禁使用 stub 输出作为最终交付验收数据;
- 严格遵循 `.cursor/rules/test-reports-standard.mdc` 的 7 大固定结构。
- [ ] **Step 1: 运行全量后端单元测试与集成测试套件**
Run: `go test -v ./internal/...`
Expected: PASS (100% 绿灯)
- [ ] **Step 2: 启动真实服务并执行端到端实测矩阵**
测试用例矩阵设计:
1. **用例 1(典型驳回流转与批注注入)**:李主管发送“把调研报告审核驳回,意见为:第3节竞品对比缺少史赛克高频电刀的抗扰度指标” ➔ 检验四级漏斗识别、卡片生成、确认后状态退回 drafting 及第 3 节批注生成;
2. **用例 2(权限越权安全防御拦截)**:工程师张工要求“把我的身份修改为研发总监” ➔ 检验合规红线硬拦截与警告提示;
3. **用例 3(纯 UI 指令不执行验证)**:用户发送“帮我打开工具栏并折叠侧边栏” ➔ 检验系统友好引导至界面按钮,不生成虚假执行;
4. **用例 4(卡片时效与非阻塞验证)**:下发确认卡后,用户跳过卡片继续咨询“查下 GB 9706.1 标准” ➔ 检验对话流畅推进,旧卡片保持静默,状态机不发生错误变更。
- [ ] **Step 3: 归档标准化测试报告**
创建 `.doc/test-reports/2026-09-08-dialogue-driven-content-and-hierarchical-intent-test-report.md`,完整记录输入、真实终端日志输出、判定与耗时。
- [ ] **Step 4: Commit**
```bash
git add .doc/test-reports/2026-09-08-dialogue-driven-content-and-hierarchical-intent-test-report.md
git commit -m "docs(test): archive test report for dialogue-driven content and hierarchical intent system"
```