373 lines
14 KiB
Markdown
373 lines
14 KiB
Markdown
# 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<string, unknown> | null;
|
||
clarification_code?: string | null;
|
||
clarification_candidates?: Array<Record<string, unknown>>;
|
||
answer_mode?: string | null;
|
||
grounding_status?: string | null;
|
||
policy_version?: string | null;
|
||
citations?: Array<Record<string, unknown>>;
|
||
sources_used?: Array<Record<string, unknown>>;
|
||
evidence_pack?: Record<string, unknown> | null;
|
||
```
|
||
|
||
Also add:
|
||
|
||
```ts
|
||
async getQASessionState(sessionId: number): Promise<QASessionStateReplay>
|
||
```
|
||
|
||
- [ ] **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
|
||
<QAConversationStatusBar
|
||
currentSubject={sessionState.last_subject}
|
||
effectiveSourcePolicy={lastRouteDecision?.effective_source_policy}
|
||
pendingClarification={sessionState.pending_clarification}
|
||
onClearContext={startNewChat}
|
||
/>
|
||
```
|
||
|
||
- [ ] **Step 4: Implement the per-answer meta card**
|
||
|
||
For each message, render a compact card that surfaces:
|
||
|
||
```tsx
|
||
<QAResponseMetaCard
|
||
routeDecision={msg.route_decision}
|
||
groundingStatus={msg.grounding_status}
|
||
sourcesUsed={msg.sources_used}
|
||
evidencePack={msg.evidence_pack}
|
||
/>
|
||
```
|
||
|
||
- [ ] **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
|
||
<Badge variant="outline">{item.source_work_item?.plan_status || "draft_pending_confirm"}</Badge>
|
||
<div>当前模式:需求模式</div>
|
||
<div>下一步:补信息 / 选 A-B-C / 草稿确认</div>
|
||
```
|
||
|
||
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
|