diff --git a/docs/superpowers/plans/2026-09-08-dialogue-driven-content-and-hierarchical-intent.md b/docs/superpowers/plans/2026-09-08-dialogue-driven-content-and-hierarchical-intent.md new file mode 100644 index 00000000..36bd7b1e --- /dev/null +++ b/docs/superpowers/plans/2026-09-08-dialogue-driven-content-and-hierarchical-intent.md @@ -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; + 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`,则渲染 ``。 + +- [ ] **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" +```