# Three-Mode Interaction And Context Upgrade 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:** 让 PMFlow 的三类用户可见模式在同一套交互骨架下可见、可控、可纠偏:`planmode(requirement_plan/draft)` 先做顶层状态显化,`query.answer` 再按 `internal / external` 给出显式来源合同与上下文续写提示。 **Architecture:** 先锁共享基线,不改语义边界:保留 `plan_mode gate -> non-plan query.answer -> internal/external split` 的现有层级,只把后端已经存在的 `route_decision / routing_snapshot / session_state / requirement plan status` 暴露给前端。前端新增一套轻量状态条和回答元信息卡,查询侧挂在 `FloatingQAChat`,planmode 侧挂在 `Inbox` 与 AI PMO handoff 结果,不另起一套聊天壳。 **Tech Stack:** FastAPI, SQLAlchemy 2.0, PMFlow AdaptiveRAG, AI PMO kernel, React 18, TypeScript, Vitest, pytest --- ## Baseline File Map ### Shared Query Contract - Modify: `backend/app/api/rag_ask_contract.py` - 保持 `/rag/ask` 现有包络不变,补齐给前台可消费的 `route_decision / clarification_code / clarification_candidates / answer_mode / grounding_status / policy_version / citations / sources_used / evidence_pack` 规范化输出。 - Modify: `backend/app/services/adaptive_rag_service/mixin_qa_session_list_ops_p1.py` - 让会话详情把每轮回答的 route / evidence / answer mode 元信息带出来,不再只有 `question / answer / sources`。 - Modify: `frontend/src/api/aiSteward.ts` - 前端类型补齐 richer QA payload,并新增 `getQASessionState(sessionId)` 读取 `last_subject / pending_clarification / rewrite_history`。 - Modify: `backend/tests/test_ai_pmo_qa_http_mainline_e2e.py` - 锁 `/rag/ask` HTTP 响应合同。 - Modify: `backend/tests/test_ai_pmo_ingress_mainline.py` - 锁 QA session detail / state replay 合同。 ### Query UI Shell - Create: `frontend/src/components/ai-steward/QAConversationStatusBar.tsx` - 显示当前对象、当前来源模式、待澄清状态、是否仍在继承上一轮上下文。 - Create: `frontend/src/components/ai-steward/QAResponseMetaCard.tsx` - 每条回答下显示 `内部/外部/待澄清`、grounding 状态、来源摘要、推荐下一步。 - Modify: `frontend/src/components/ai-steward/FloatingQAChat.tsx` - 接入 status bar、message-level meta card、route clarify 快捷动作、session state 拉取与“清空上下文/新话题”入口。 - Create: `frontend/src/components/ai-steward/FloatingQAChat.test.tsx` - 锁状态条、route clarify 动作、元信息卡渲染。 ### Plan Mode Surface - Modify: `backend/app/api/ai_pmo_api/helpers_inbox_payload.py` - 给 inbox work item summary 增加 planmode 相关轻量字段,例如 `plan_mode / plan_status / requirement_plan_context_summary`。 - Modify: `backend/app/api/ai_pmo_api/routes_core.py` - 统一 `process-text` 与 `work-items` 输出里的 planmode 相关字段,避免前端靠 `reply_template_key` 猜。 - Modify: `frontend/src/api/aiPmo.ts` - 补齐 AI PMO result / work item summary 的 planmode 类型。 - Modify: `frontend/src/types/inbox.ts` - 让 inbox item view model 能承载 `plan_status / plan_mode / requirement_plan_context_summary`。 - Modify: `frontend/src/components/inbox/InboxListItem.tsx` - 在详情卡里显式展示“当前需求模式状态 / 下一步动作 / 草稿待确认”。 - Modify: `frontend/src/pages/inbox/InboxPage.tsx` - 把 mounted run / work item 映射到统一的 planmode 视图文案。 - Create: `frontend/src/components/inbox/InboxListItem.test.tsx` - 锁 planmode 状态渲染与 CTA 文案。 ### Docs And Inventory - Modify: `CLAUDE.md` - Modify: `docs/architecture/ARCHITECTURE_INVENTORY.md` - Modify: `docs/architecture/CODE_REFERENCE_INDEX.md` - Modify: `docs/architecture/RUNTIME_GUARDRAILS.md` --- ### Task 1: Lock The Shared QA Contract **Files:** - Modify: `backend/tests/test_ai_pmo_qa_http_mainline_e2e.py` - Modify: `backend/tests/test_ai_pmo_ingress_mainline.py` - Modify: `frontend/src/api/aiSteward.ts` - [ ] **Step 1: Write the failing `/rag/ask` contract test** Add an HTTP contract assertion that `/rag/ask` returns: ```python assert payload["data"]["route_decision"]["effective_source_policy"] == "internal" assert payload["data"]["clarification_code"] is None assert payload["data"]["answer_mode"] == "fast_path" assert payload["data"]["evidence_pack"]["summary"]["conclusion"] == "当前主链状态正常。" ``` - [ ] **Step 2: Write the failing session detail/state test** Add sqlite-backed tests that prove: ```python detail = await service.get_qa_session_detail(session_id=session_id, user_id=101) assert detail["messages"][0]["route_decision"]["effective_source_policy"] == "external" assert detail["messages"][0]["grounding_status"] == "insufficient_evidence" state = await service.get_session_state_replay(session_id=session_id, user_id=101) assert state["last_subject"] == "Alpha项目" assert state["pending_clarification"]["kind"] == "query_route_selection" ``` - [ ] **Step 3: Run the focused backend tests to confirm they fail** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m pytest backend/tests/test_ai_pmo_qa_http_mainline_e2e.py -k "rag_ask" -v cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m pytest backend/tests/test_ai_pmo_ingress_mainline.py -k "qa_session_detail or session_state_replay" -v ``` Expected: FAIL because the richer QA/session contract is not exposed yet. ### Task 2: Implement The Shared QA Metadata Baseline **Files:** - Modify: `backend/app/api/rag_ask_contract.py` - Modify: `backend/app/services/adaptive_rag_service/mixin_qa_session_list_ops_p1.py` - Modify: `frontend/src/api/aiSteward.ts` - Test: `backend/tests/test_ai_pmo_qa_http_mainline_e2e.py` - Test: `backend/tests/test_ai_pmo_ingress_mainline.py` - [ ] **Step 1: Normalize `/rag/ask` payload fields** Keep the existing response envelope, but explicitly preserve: ```python payload["route_decision"] = dict(payload.get("route_decision") or {}) payload["clarification_code"] = str(payload.get("clarification_code") or "").strip() or None payload["clarification_candidates"] = _normalize_dict_list(payload.get("clarification_candidates")) ``` - [ ] **Step 2: Extend QA session detail payload** When serializing session messages, add: ```python "route_decision": row.routing_snapshot.get("query_route") if isinstance(row.routing_snapshot, dict) else {}, "answer_mode": row.answer_mode, "grounding_status": row.grounding_status, "policy_version": row.policy_version, "citations": row.citations or [], "sources_used": row.sources_used or [], "evidence_pack": row.evidence_pack or {}, ``` - [ ] **Step 3: Update front-end request/response types** Extend `QAAskResponse` and `QASessionMessage` to accept: ```ts route_decision?: Record | null; clarification_code?: string | null; clarification_candidates?: Array>; answer_mode?: string | null; grounding_status?: string | null; policy_version?: string | null; citations?: Array>; sources_used?: Array>; evidence_pack?: Record | null; ``` Also add: ```ts async getQASessionState(sessionId: number): Promise ``` - [ ] **Step 4: Re-run the focused backend tests** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m pytest backend/tests/test_ai_pmo_qa_http_mainline_e2e.py -k "rag_ask" -v cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m pytest backend/tests/test_ai_pmo_ingress_mainline.py -k "qa_session_detail or session_state_replay" -v ``` Expected: PASS ### Task 3: Build The Query Status Shell **Files:** - Create: `frontend/src/components/ai-steward/QAConversationStatusBar.tsx` - Create: `frontend/src/components/ai-steward/QAResponseMetaCard.tsx` - Modify: `frontend/src/components/ai-steward/FloatingQAChat.tsx` - Create: `frontend/src/components/ai-steward/FloatingQAChat.test.tsx` - Test: `frontend/src/components/ai-steward/FloatingQAChat.test.tsx` - [ ] **Step 1: Write the failing front-end tests** Cover these cases: ```tsx it("shows current subject and source mode in the status bar", ...) it("renders route clarify actions when clarification_code=query_route_clarify", ...) it("renders answer meta card with grounding and source summary", ...) ``` - [ ] **Step 2: Run the front-end test to verify it fails** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/components/ai-steward/FloatingQAChat.test.tsx ``` Expected: FAIL because the new status shell does not exist yet. - [ ] **Step 3: Implement the session-level status bar** Render a compact bar above the message list with: ```tsx ``` - [ ] **Step 4: Implement the per-answer meta card** For each message, render a compact card that surfaces: ```tsx ``` - [ ] **Step 5: Wire route clarify actions** When the last answer returns `clarification_code === "query_route_clarify"`, show two quick actions that submit the existing protocol text: ```tsx onChooseInternal={() => runQuestionRequest({ prompt: "B" })} onChooseExternal={() => runQuestionRequest({ prompt: "A" })} ``` - [ ] **Step 6: Re-run the front-end test** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/components/ai-steward/FloatingQAChat.test.tsx ``` Expected: PASS ### Task 4: Surface Plan Mode In AI PMO And Inbox **Files:** - Modify: `backend/tests/test_ai_pmo_ingress_mainline.py` - Modify: `backend/app/api/ai_pmo_api/helpers_inbox_payload.py` - Modify: `backend/app/api/ai_pmo_api/routes_core.py` - Modify: `frontend/src/api/aiPmo.ts` - Modify: `frontend/src/types/inbox.ts` - Modify: `frontend/src/components/inbox/InboxListItem.tsx` - Modify: `frontend/src/pages/inbox/InboxPage.tsx` - Create: `frontend/src/components/inbox/InboxListItem.test.tsx` - Test: `frontend/src/components/inbox/InboxListItem.test.tsx` - [ ] **Step 1: Write the failing backend test for planmode summary fields** Add coverage that proves work item summary/detail exposes: ```python assert item["plan_mode"] is True assert item["plan_status"] == "collecting" assert item["requirement_plan_context_summary"]["title"] == "换季搭配推荐" ``` - [ ] **Step 2: Run the targeted backend test to verify it fails** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m pytest backend/tests/test_ai_pmo_ingress_mainline.py -k "plan_mode or requirement_plan_context_summary" -v ``` Expected: FAIL because inbox/process-text payloads do not expose these fields yet. - [ ] **Step 3: Serialize planmode summary fields in AI PMO payloads** Normalize a small summary shape instead of leaking full runtime packet: ```python "plan_mode": bool(summary.get("plan_mode")), "plan_status": normalized_plan_status, "requirement_plan_context_summary": { "title": card.get("title"), "goal": card.get("goal"), "missing_info": card.get("missing_info", [])[:3], } ``` - [ ] **Step 4: Update front-end inbox types and rendering** Show: ```tsx {item.source_work_item?.plan_status || "draft_pending_confirm"}
当前模式:需求模式
下一步:补信息 / 选 A-B-C / 草稿确认
``` Keep the existing draft actions intact. - [ ] **Step 5: Write and run the front-end inbox test** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/components/inbox/InboxListItem.test.tsx ``` Expected: FAIL first, then PASS after the new planmode summary UI is wired. ### Task 5: Verification And Docs Sync **Files:** - Modify: `CLAUDE.md` - Modify: `docs/architecture/ARCHITECTURE_INVENTORY.md` - Modify: `docs/architecture/CODE_REFERENCE_INDEX.md` - Modify: `docs/architecture/RUNTIME_GUARDRAILS.md` - [ ] **Step 1: Update docs for the new three-mode front-end contract** Record: - `plan_mode` is still the top-level gate - `internal / external` remain submodes under `query.answer` - `FloatingQAChat` now shows context/source status instead of only answer text - inbox exposes requirement-plan summary state - [ ] **Step 2: Run focused backend verification** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m pytest \ backend/tests/test_ai_pmo_qa_http_mainline_e2e.py \ backend/tests/test_ai_pmo_ingress_mainline.py \ backend/tests/test_adaptive_rag_mainline_contract.py -v ``` Expected: PASS - [ ] **Step 3: Run backend compile verification** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m py_compile \ backend/app/api/rag_ask_contract.py \ backend/app/services/adaptive_rag_service/mixin_qa_session_list_ops_p1.py \ backend/app/api/ai_pmo_api/helpers_inbox_payload.py \ backend/app/api/ai_pmo_api/routes_core.py ``` Expected: exit 0 - [ ] **Step 4: Run front-end verification** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run \ src/components/ai-steward/FloatingQAChat.test.tsx \ src/components/inbox/InboxListItem.test.tsx cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx tsc --noEmit cd /Users/yuuuu/Documents/AIProjectManger/frontend && npm run build ``` Expected: PASS