diff --git a/CLAUDE.md b/CLAUDE.md index 14b32687..7f2cbf15 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,7 +134,7 @@ frontend/ - `frontend/src/features/project-launch/` 是新项目启动台独立产品域:`types.ts / useProjectLaunchState.ts` 承载状态合同和真实 API 编排,`mockData.ts` 只在显式 demo 模式下使用;默认路径调用 `POST /pmflow-launch/drafts` 生成可审查草案,确认后调用 `POST /pmflow-launch/drafts/{draft_id}/confirm` 创建真实项目并跳转 `/pmflow-os?projectId=`。`LaunchShell` 桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,主画布仍由 `IntentInputPanel / AIProgressCanvas / HumanReviewCanvas / DraftEditCanvas / FailureStatePanel / SuccessTransitionPanel` 承接,移动端仍由 `MobileLaunchLayout` 承接;`frontend/src/pages/ProjectLaunchPage.tsx` 仅做薄入口。 - `frontend/src/features/personal-space/` 是空间层入口:`PersonalSpacePage.tsx` 承接桌面个人工作面与独立移动单手布局,`frontend/src/api/space.ts` 只读聚合 `projects` summary、PMFlow OS `truth-state`、`execution-summary` 并生成继续推进、待确认、AI 正在推进、草案箱、最近项目摘要;点击项目/确认/草案只跳回 `/pmflow-os?projectId=` 主流程处理,不在 Space 写 Project Memory 或 execution event。`components/SpaceChrome.tsx` 承接空间层左侧栏、顶部条与空间用量组件,并被 `/project-launch` 桌面首次新建壳复用;`frontend/src/pages/PersonalSpacePage.tsx` 仅做薄入口。 - `frontend/src/features/account-settings/` 是 Account Settings 独立产品域:`AccountSettingsPanel.tsx` 承接 `/me` 桌面设置页、移动端分组列表与 bottom sheet,`frontend/src/api/accountSettings.ts` 定义个人资料、默认语言、AI 回答深度、证据展示偏好和四类通知偏好合同,当前首版使用浏览器本地持久化作为前端合同落点,不改 OAuth/JWT/auth core;`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 仅做薄入口并复用既有退出登录 bottom sheet。 -- `frontend/src/features/pmflow-os/` 当前承载新的对话驱动前端产品域:`store.tsx` 提供共享 truth store;真实模式通过 `?projectId=` 调用 `frontend/src/api/pmflowOs.ts` 读取后端 `ProjectTruthState` projection,并调用真实 message、`proposed_items` extraction、ChangeItem `accept/edit/defer` 与 ChangeSet `commit` API,成功后 refetch truth-state;显式 `?demo=1` 才启用 `command_idle -> ai_extracting -> extraction_ready -> review_open -> writeback_success` 本地演示状态机,以及本地 `promoteAcceptedToGraph` 等演示动作。`pages/` 下包含 `CommandCenter / StructuredExtraction / ChangeReview / MemoryWritebackResult / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主页面;`ProjectMemoryPage` 在真实模式消费后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,真实模式下 accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`components/` 下除了 `ProjectStageRail / ThreadComposer / ExtractionRail / ChangeGroupPanel / ObjectGraphSection / ContextPacketPanel / ExecutionTimeline / ReleaseGatePanel / TeamFeedPanel` 之外,新增 `ProjectStructuredLoop / PMFlowOsFrame / AccountControls / FeedbackPrimitives` 等行为组件,用来承接项目指挥台、结构化提取、待确认审查、项目记忆写入结果、项目内面包屑、项目切换器、账号退出登录交互,以及 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet` 这组契约化提示提醒组件;`components/MobileBottomNav.tsx` 是正式应用壳唯一移动底部导航,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`。 +- `frontend/src/features/pmflow-os/` 当前承载新的对话驱动前端产品域:`store.tsx` 提供共享 truth store;真实模式通过 `?projectId=` 调用 `frontend/src/api/pmflowOs.ts` 读取后端 `ProjectTruthState` projection,并调用真实 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet `commit`、Execution Handoff create/consume API,成功后 refetch truth-state / execution-summary;显式 `?demo=1` 才启用 `command_idle -> ai_extracting -> extraction_ready -> review_open -> writeback_success` 本地演示状态机,以及本地 `promoteAcceptedToGraph` 等演示动作。`pages/` 下包含 `CommandCenter / StructuredExtraction / ChangeReview / MemoryWritebackResult / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主页面;`ExecutionBridgePage` 已支持创建 handoff 与“运行端领取确认”,后端只记录 `handoff_created / handoff_consumed` progress event,不执行本机命令;`ProjectMemoryPage` 在真实模式消费后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,真实模式下 accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`components/` 下除了 `ProjectStageRail / ThreadComposer / ExtractionRail / ChangeGroupPanel / ObjectGraphSection / ContextPacketPanel / ExecutionTimeline / ReleaseGatePanel / TeamFeedPanel` 之外,新增 `ProjectStructuredLoop / PMFlowOsFrame / AccountControls / FeedbackPrimitives` 等行为组件,用来承接项目指挥台、结构化提取、待确认审查、项目记忆写入结果、项目内面包屑、项目切换器、账号退出登录交互,以及 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet` 这组契约化提示提醒组件;`components/MobileBottomNav.tsx` 是正式应用壳唯一移动底部导航,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`。 - 旧登录实现 `frontend/src/components/ui/sign-in.tsx` 与 `frontend/src/components/ui/demo.tsx` 已移除,`frontend/src/components/login/` 是当前唯一登录真源。 ### 5.1 `frontend/src/components/sstc/` 目录说明(模块化拆分后) diff --git a/backend/app/api/pmflow_os_api/__init__.py b/backend/app/api/pmflow_os_api/__init__.py index 28b838f6..0b43db00 100644 --- a/backend/app/api/pmflow_os_api/__init__.py +++ b/backend/app/api/pmflow_os_api/__init__.py @@ -13,6 +13,7 @@ from ...schemas.pmflow_os import ( PMFlowChangeItemDeferRequest, PMFlowChangeItemEditRequest, PMFlowChangeSetCommitRequest, + PMFlowExecutionHandoffConsumeRequest, PMFlowExecutionHandoffRequest, PMFlowExtractionCreateRequest, PMFlowThreadMessageCreateRequest, @@ -282,3 +283,40 @@ async def create_pmflow_os_execution_handoff( raise HTTPException(status_code=404, detail="project_not_found") from exc raise return success_response(payload.model_dump(mode="json")) + + +@router.post("/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume") +async def consume_pmflow_os_execution_handoff( + project_id: int, + prompt_package_id: int, + body: PMFlowExecutionHandoffConsumeRequest | None = None, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user), +): + tenant_id = int(current_user.tenant_id) + await _ensure_project_member_or_manager( + db, + tenant_id=tenant_id, + project_id=int(project_id), + current_user=current_user, + ) + service = PMFlowOSProjectionService(db, tenant_id=tenant_id) + try: + payload = await service.consume_execution_handoff( + project_id=int(project_id), + prompt_package_id=int(prompt_package_id), + body=body or PMFlowExecutionHandoffConsumeRequest(), + current_user_id=int(current_user.id), + ) + except ValueError as exc: + code = str(exc) + if code in {"prompt_package_not_found", "execution_handoff_not_found"}: + return JSONResponse(status_code=404, content=error_response(404, code)) + if code == "task_not_found": + return JSONResponse(status_code=404, content=error_response(404, "task_not_found")) + if code == "repo_binding_not_found": + return JSONResponse(status_code=422, content=error_response(422, "repo_binding_not_found")) + if code == "project_not_found": + raise HTTPException(status_code=404, detail="project_not_found") from exc + raise + return success_response(payload.model_dump(mode="json")) diff --git a/backend/app/schemas/pmflow_os.py b/backend/app/schemas/pmflow_os.py index 1f3e9ebd..eedcedfd 100644 --- a/backend/app/schemas/pmflow_os.py +++ b/backend/app/schemas/pmflow_os.py @@ -94,6 +94,12 @@ class PMFlowExecutionHandoffRequest(BaseModel): note: Optional[str] = Field(default=None, max_length=4000) +class PMFlowExecutionHandoffConsumeRequest(BaseModel): + runtime_target: Optional[PMFlowExecutionRuntimeTarget] = None + consumer_label: Optional[str] = Field(default=None, max_length=120) + note: Optional[str] = Field(default=None, max_length=4000) + + class PMFlowChangeSetDetailResponse(BaseModel): changeSet: dict[str, Any] items: list[dict[str, Any]] = Field(default_factory=list) diff --git a/backend/app/services/pmflow_os_projection_service.py b/backend/app/services/pmflow_os_projection_service.py index e4048a7e..e658c13b 100644 --- a/backend/app/services/pmflow_os_projection_service.py +++ b/backend/app/services/pmflow_os_projection_service.py @@ -8,6 +8,7 @@ from typing import Any, Optional from sqlalchemy import and_, or_, select from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm.attributes import flag_modified from ..models import ( FeedbackIngest, @@ -36,6 +37,7 @@ from ..models import ( from ..schemas.pmflow_os import ( ExecutionSummaryProjection, PMFlowDecision, + PMFlowExecutionHandoffConsumeRequest, PMFlowExecutionHandoffRequest, PMFlowExecutionContext, PMFlowFeedEvent, @@ -229,7 +231,7 @@ class PMFlowOSProjectionService: note=body.note, ) else: - payload = self._payload(prompt_package.package_payload) + payload = dict(self._payload(prompt_package.package_payload)) payload["latest_handoff"] = self._handoff_payload( task=task, repo=repo, @@ -238,6 +240,7 @@ class PMFlowOSProjectionService: prompt_package_id=int(prompt_package.id), ) prompt_package.package_payload = payload + flag_modified(prompt_package, "package_payload") event = ProgressEvent( tenant_id=self.tenant_id, @@ -262,6 +265,63 @@ class PMFlowOSProjectionService: await self.db.commit() return await self.get_execution_summary(project_id=project_id, current_user_id=current_user_id) + async def consume_execution_handoff( + self, + *, + project_id: int, + prompt_package_id: int, + body: PMFlowExecutionHandoffConsumeRequest, + current_user_id: Optional[int] = None, + ) -> ExecutionSummaryProjection: + await self._load_project(project_id=project_id) + prompt_package = await self._load_execution_prompt_package( + project_id=project_id, + prompt_package_id=prompt_package_id, + ) + if prompt_package.task_id is None: + raise ValueError("task_not_found") + task = await self._load_project_task(project_id=project_id, task_id=str(prompt_package.task_id)) + payload = dict(self._payload(prompt_package.package_payload)) + latest_handoff = dict(self._payload(payload.get("latest_handoff"))) + runtime_target = body.runtime_target or str(latest_handoff.get("runtime_target") or payload.get("runtime_target") or "manual") + repo_binding_id = str(latest_handoff.get("repo_binding_id") or "").strip() or None + repo = await self._load_project_repo_binding(project_id=project_id, repo_binding_id=repo_binding_id) + consumer_label = (body.consumer_label or runtime_target or "runtime").strip() + consumed_at = now_cn_naive().isoformat() + consumed_payload = self._handoff_consumed_payload( + task=task, + repo=repo, + runtime_target=runtime_target, + note=body.note, + prompt_package_id=int(prompt_package.id), + consumer_label=consumer_label, + consumed_at=consumed_at, + ) + latest_handoff.update(consumed_payload) + payload["latest_handoff"] = latest_handoff + payload.setdefault("handoff_consumptions", []) + if isinstance(payload["handoff_consumptions"], list): + payload["handoff_consumptions"] = [consumed_payload, *payload["handoff_consumptions"][:9]] + prompt_package.package_payload = payload + flag_modified(prompt_package, "package_payload") + + event = ProgressEvent( + tenant_id=self.tenant_id, + task_id=int(task.id), + project_id=int(project_id), + user_id=int(current_user_id) if current_user_id is not None else None, + repo_binding_id=int(repo.id) if repo is not None else None, + event_type="handoff_consumed", + source="pmflow_os", + derived_task_status="coding", + summary=self._handoff_consumed_summary(task=task, runtime_target=runtime_target, consumer_label=consumer_label), + payload=consumed_payload, + occurred_at=now_cn_naive(), + ) + self.db.add(event) + await self.db.commit() + return await self.get_execution_summary(project_id=project_id, current_user_id=current_user_id) + async def _load_project(self, *, project_id: int) -> Project: result = await self.db.execute( select(Project).where(and_(Project.id == int(project_id), Project.tenant_id == self.tenant_id)) @@ -354,6 +414,23 @@ class PMFlowOSProjectionService: ) return result.scalar_one_or_none() + async def _load_execution_prompt_package(self, *, project_id: int, prompt_package_id: int) -> PromptPackage: + result = await self.db.execute( + select(PromptPackage).where( + and_( + PromptPackage.tenant_id == self.tenant_id, + PromptPackage.project_id == int(project_id), + PromptPackage.id == int(prompt_package_id), + ) + ) + ) + prompt_package = result.scalar_one_or_none() + if prompt_package is None: + raise ValueError("prompt_package_not_found") + if prompt_package.package_kind != "execution_handoff": + raise ValueError("execution_handoff_not_found") + return prompt_package + async def _create_execution_prompt_package( self, *, @@ -1265,6 +1342,34 @@ class PMFlowOSProjectionService: target = f"{repo.owner}/{repo.repo_name}" if repo is not None else "未绑定仓库" return f"执行交接已创建:{task.title} -> {runtime_target} / {target}" + def _handoff_consumed_payload( + self, + *, + task: Task, + repo: Optional[RepoBinding], + runtime_target: str, + note: Optional[str], + prompt_package_id: int, + consumer_label: str, + consumed_at: str, + ) -> dict[str, Any]: + return { + "workflow_state": "coding", + "handoff_state": "consumed", + "runtime_target": runtime_target, + "consumer_label": consumer_label, + "consumed_at": consumed_at, + "prompt_package_id": str(prompt_package_id), + "task_id": str(task.id), + "repo_binding_id": None if repo is None else str(repo.id), + "repository_full_name": None if repo is None else f"{repo.owner}/{repo.repo_name}", + "note": note, + "boundary": "PMFlow records handoff pickup only; command execution remains outside backend runtime.", + } + + def _handoff_consumed_summary(self, *, task: Task, runtime_target: str, consumer_label: str) -> str: + return f"执行交接已领取:{task.title} -> {runtime_target} / {consumer_label}" + def _latest_iso(self, values: list[Optional[datetime]]) -> Optional[str]: valid = [value for value in values if value is not None] if not valid: diff --git a/backend/tests/test_pmflow_os_projection_api.py b/backend/tests/test_pmflow_os_projection_api.py index f4bfc9de..311c49c9 100644 --- a/backend/tests/test_pmflow_os_projection_api.py +++ b/backend/tests/test_pmflow_os_projection_api.py @@ -519,6 +519,86 @@ async def test_execution_handoff_without_repo_creates_draft_warning(db_env) -> N assert event.payload["handoff_state"] == "draft" +@pytest.mark.asyncio +async def test_execution_handoff_consume_records_pickup_event(db_env) -> None: + session = db_env + user, project = await _seed_user_project(session) + charter = TruthNode( + tenant_id=1, + node_type="charter", + project_id=project.id, + created_by=user.id, + title="交接领取宪章", + content={"business_goal": "执行交接被运行端领取后可追踪"}, + version=1, + status="aligned", + ) + session.add(charter) + await session.flush() + task = Task( + tenant_id=1, + truth_node_id=charter.id, + project_id=project.id, + assignee_id=user.id, + ai_recommended_assignees=None, + ai_breakdown_payload={"acceptance_criteria": ["领取交接包后进入开发中"]}, + manual_overrides=None, + title="领取执行交接包", + description="Codex 消费 PromptPackage", + required_skill_tag_ids=[], + status="confirmed", + priority="high", + ) + repo = RepoBinding( + tenant_id=1, + project_id=project.id, + provider="github", + owner="pmflow", + repo_name="handoff-consume", + default_branch="main", + installation_id="install-3", + status="active", + ) + session.add_all([task, repo]) + await session.commit() + app = _build_app(session, SimpleNamespace(id=user.id, tenant_id=1, role="MEMBER")) + + async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: + create_response = await client.post( + f"/pmflow-os/projects/{project.id}/execution-handoffs", + json={"task_id": str(task.id), "repo_binding_id": str(repo.id), "runtime_target": "local_bridge"}, + ) + prompt_package_id = create_response.json()["data"]["promptPackage"]["id"] + consume_response = await client.post( + f"/pmflow-os/projects/{project.id}/execution-handoffs/{prompt_package_id}/consume", + json={"runtime_target": "codex", "consumer_label": "Codex", "note": "开始开发"}, + ) + + assert consume_response.status_code == 200 + payload = consume_response.json()["data"] + assert payload["execution"]["status"] == "coding" + assert payload["recentProgressEvents"][0]["event_type"] == "handoff_consumed" + assert payload["recentProgressEvents"][0]["derived_task_status"] == "coding" + assert payload["recentProgressEvents"][0]["repo"]["full_name"] == "pmflow/handoff-consume" + + prompt = ( + await session.execute(select(PromptPackage).where(PromptPackage.id == int(prompt_package_id))) + ).scalar_one() + latest_handoff = prompt.package_payload["latest_handoff"] + assert latest_handoff["handoff_state"] == "consumed" + assert latest_handoff["consumer_label"] == "Codex" + assert latest_handoff["runtime_target"] == "codex" + event_types = [ + row.event_type + for row in ( + await session.execute(select(ProgressEvent).where(ProgressEvent.task_id == task.id).order_by(ProgressEvent.id.asc())) + ) + .scalars() + .all() + ] + assert event_types == ["handoff_created", "handoff_consumed"] + + @pytest.mark.asyncio async def test_execution_handoff_rejects_task_outside_project(db_env) -> None: session = db_env diff --git a/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md b/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md index 312daa6b..c1d1952d 100644 --- a/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md +++ b/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md @@ -807,6 +807,50 @@ P0 公共壳冻结已完成,下一批可以进入 `Batch P1` 三模块并行 ## 8. 进度台账 +### 2026-04-26 / main-flow-p1-execution-handoff-consume / done + +完成内容: + +- 在 D.2 的 `handoff_created` 起点上继续补齐执行交接消费闭环,新增 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`。 +- 后端校验项目成员权限、项目边界与 `PromptPackage(package_kind='execution_handoff')` 边界;消费动作只记录运行端领取,不执行本机命令、不写宿主机配置。 +- `PMFlowOSProjectionService.consume_execution_handoff` 将 `PromptPackage.package_payload.latest_handoff` 更新为 `handoff_state=consumed`,并写入 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`。 +- `execution-summary` 会因最新 `handoff_consumed` 事件把执行状态投影为 `coding`,前端执行桥可显示“运行端领取确认”。 +- 前端 `ExecutionBridgePage` 在真实模式新增“标记为已领取”动作,调用 `consumePMFlowExecutionHandoff` 后 refetch `truth-state / execution-summary`;demo 模式仍只走本地演示推进。 +- 修复 D.2 中 active `PromptPackage.package_payload` 嵌套 JSONB 更新可能未被 SQLAlchemy 标脏的问题,显式使用 `flag_modified`。 + +涉及文件: + +- `backend/app/api/pmflow_os_api/__init__.py` +- `backend/app/services/pmflow_os_projection_service.py` +- `backend/app/schemas/pmflow_os.py` +- `backend/tests/test_pmflow_os_projection_api.py` +- `frontend/src/api/pmflowOs.ts` +- `frontend/src/features/pmflow-os/store.tsx` +- `frontend/src/features/pmflow-os/store.api.test.tsx` +- `frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx` +- `CLAUDE.md` +- `docs/architecture/CODE_REFERENCE_INDEX.md` +- `docs/architecture/ARCHITECTURE_INVENTORY.md` +- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` + +验证: + +- `PYTHONPATH=backend /opt/homebrew/bin/python3.11 -m py_compile backend/app/api/pmflow_os_api/__init__.py backend/app/services/pmflow_os_projection_service.py backend/app/schemas/pmflow_os.py` 通过。 +- `PYTHONPATH=backend /opt/homebrew/bin/python3.11 -m pytest backend/tests/test_pmflow_os_projection_api.py -q` 通过:`10 passed`;仍有既有 `backend/app/schemas/recipe.py` Pydantic V2 class-based config deprecation warning。 +- `cd frontend && npm run test -- src/features/pmflow-os/store.api.test.tsx` 通过:`3 passed`。 +- `cd frontend && npm run test -- src/features/pmflow-os` 通过:`4 passed`。 +- `cd frontend && npm run typecheck` 通过。 +- `cd frontend && npm run build` 通过。 + +未完成或风险: + +- 本轮仍不执行真实命令;Codex / Local Bridge / GitHub agent 的 branch、commit、PR、test 回写仍属于下一步 D.3。 +- 本轮新增后端运行时代码但未执行热部署/重启;当前环境是本地开发工作区,未连接实际部署服务。部署环境需要按实际栈执行 backend restart/reload。 + +下一步建议: + +- 进入 D.3:补 Dev Sync 写回闭环,把运行端执行结果回写为 branch/commit/PR/test progress event,并投影回 `execution-summary / truth-state`。 + ### 2026-04-26 / big-module-integration-p1 / done 完成内容: diff --git a/docs/architecture/ARCHITECTURE_INVENTORY.md b/docs/architecture/ARCHITECTURE_INVENTORY.md index bcb15e29..331bf15c 100644 --- a/docs/architecture/ARCHITECTURE_INVENTORY.md +++ b/docs/architecture/ARCHITECTURE_INVENTORY.md @@ -197,9 +197,9 @@ - `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成继续推进、待确认、AI 正在推进、暂存想法、草案箱、最近项目摘要;项目主动作进入 `/pmflow-os?projectId=`,待确认项进入 `/change-review?projectId=&changeSetId=`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入或 execution event 写入。 - `frontend/src/features/project-launch/types.ts`、`mockData.ts`、`useProjectLaunchState.ts` 是新项目启动台状态层;默认真实模式通过 `frontend/src/api/pmflowLaunch.ts` 生成草案并确认创建真实项目,API 失败进入明确失败态,不静默 fallback 成 mock 成功;`mockData.ts` 仅在显式 `?demo=1` 或 `?dataSource=demo` 下作为演示状态机使用。`components/LaunchShell.tsx` 负责桌面/移动分流,桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,`LaunchCanvases.tsx` 和 `MobileLaunchLayout.tsx` 承接高保真图中的意图输入、AI 工作、人类确认、草案修改、生成失败、创建成功过渡与移动端 bottom sheet 交互。 - `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 首版设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好。`frontend/src/api/accountSettings.ts` 是前端合同与本地持久化层;V1 UI 明确区分 `仅本机暂存 / 已保存到当前账号 / 保存失败`,当前实现只使用本机暂存,不假装云端账号保存;后续如接后端,应新增独立 `account_settings_api`,不能写入 PMFlow OS truth store 或 auth core。 -- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。 -- `frontend/src/features/pmflow-os/store.tsx` 仍保留演示用项目内 AI 结构化闭环状态机:`command_idle / ai_extracting / extraction_ready / review_open / writeback_success`。`frontend/src/features/pmflow-os/pages/StructuredExtractionPage.tsx` 是本轮结构化提取页,`ChangeReviewPage.tsx` 是决策与待确认中心,`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、PR/测试与回写状态页;A3 已接入后端 truth-state,C1 已接入真实 message/extraction/review 状态机,C2 已接入真实 ChangeSet commit,Phase D 已接入真实 execution-summary/dev-sync,D.2 已接入 `POST /execution-handoffs` 创建 PromptPackage + handoff_created progress event;当前仍不调用真实 AI 提取,不把 mock 当真实失败 fallback。`ProjectMemoryPage.tsx` 在真实模式下展示后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx` 提供 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel` 等闭环组件,并复用契约化 `InlineNotice` 表达“未确认不得写入”的局部提示。 -- PMFlow OS 后端入口位于 `backend/app/api/pmflow_os_api/`,projection service 位于 `backend/app/services/pmflow_os_projection_service.py`,Change Review / commit 服务位于 `backend/app/services/pmflow_change_review_service.py`,Pydantic 合同位于 `backend/app/schemas/pmflow_os.py`;当前聚合 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,`Thread / Message` 来自 `pmflow_threads / pmflow_thread_messages`,`ChangeSet / ChangeItem / Approval / EvidenceLink` 来自 `backend/app/models/pmflow_change_review.py` 对应真实表。C1 已实现 message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend/frontend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit` 与真实模式前端接入,默认 atomic,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,校验 task/repo project 边界,生成/复用 `PromptPackage(package_kind='execution_handoff')` 并写 `ProgressEvent(event_type='handoff_created')` 起点,不直接执行本机命令;`ModelDecision / MetricSignal` 在 C2 V1 仍 fail-closed。 +- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。 +- `frontend/src/features/pmflow-os/store.tsx` 仍保留演示用项目内 AI 结构化闭环状态机:`command_idle / ai_extracting / extraction_ready / review_open / writeback_success`。`frontend/src/features/pmflow-os/pages/StructuredExtractionPage.tsx` 是本轮结构化提取页,`ChangeReviewPage.tsx` 是决策与待确认中心,`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;A3 已接入后端 truth-state,C1 已接入真实 message/extraction/review 状态机,C2 已接入真实 ChangeSet commit,Phase D 已接入真实 execution-summary/dev-sync,D.2 已接入 `POST /execution-handoffs` 创建 PromptPackage + handoff_created progress event,P1 已接入 `POST /execution-handoffs/{prompt_package_id}/consume` 创建 handoff_consumed progress event 并把 execution status 投影为 coding;当前仍不调用真实 AI 提取,不把 mock 当真实失败 fallback。`ProjectMemoryPage.tsx` 在真实模式下展示后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx` 提供 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel` 等闭环组件,并复用契约化 `InlineNotice` 表达“未确认不得写入”的局部提示。 +- PMFlow OS 后端入口位于 `backend/app/api/pmflow_os_api/`,projection service 位于 `backend/app/services/pmflow_os_projection_service.py`,Change Review / commit 服务位于 `backend/app/services/pmflow_change_review_service.py`,Pydantic 合同位于 `backend/app/schemas/pmflow_os.py`;当前聚合 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,`Thread / Message` 来自 `pmflow_threads / pmflow_thread_messages`,`ChangeSet / ChangeItem / Approval / EvidenceLink` 来自 `backend/app/models/pmflow_change_review.py` 对应真实表。C1 已实现 message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend/frontend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit` 与真实模式前端接入,默认 atomic,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,校验 task/repo project 边界,生成/复用 `PromptPackage(package_kind='execution_handoff')` 并写 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,校验 execution handoff package 后更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并写 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;两步都不直接执行本机命令;`ModelDecision / MetricSignal` 在 C2 V1 仍 fail-closed。 - PMFlow Launch 后端 Phase B 创建链位于 `backend/app/api/pmflow_launch_api/`、`backend/app/services/pmflow_launch_service.py`、`backend/app/schemas/pmflow_launch.py` 与 `backend/app/models/pmflow_launch.py`;`POST /pmflow-launch/drafts` 只写 `pmflow_launch_drafts`,`POST /pmflow-launch/drafts/{draft_id}/confirm` 在同一事务内写 `projects / user_projects / pmflow_threads / pmflow_thread_messages / truth_nodes / tasks / semantic_facts seed / operation_logs`,并把 `launch_draft_id / thread_id / thread_message_id` 写入正式对象 metadata。 - `frontend/src/features/pmflow-os/components/ChangeReviewGroup.tsx`、`WorkbenchPanel.tsx`、`ExecutionBridgeCards.tsx`、`PMFlowOsFrame.tsx`、`AccountControls.tsx`、`FeedbackPrimitives.tsx` 是高保真原型新增的行为组件层:分别承载变更 diff 审查块、工作台对象卡组、执行桥上下文/日志卡片、全局上下文条与 AI 副驾、账号菜单/退出登录确认交互,以及 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet` 这组提示、提醒、确认与移动端 bottom sheet 基础组件,避免再把这些业务片段内联塞回 `pages/`。 - `frontend/vite.config.ts` 现补了固定 vendor chunk 分组(`framework / state-query / ui-radix / icons / charts / markdown`)与业务 chunk 分组(`ai-steward / sstc / governance`),目标是让登录页与基础导航不再背 AI Steward、SSTC、治理页和图表代码。 diff --git a/docs/architecture/CODE_REFERENCE_INDEX.md b/docs/architecture/CODE_REFERENCE_INDEX.md index df86bb67..514a359c 100644 --- a/docs/architecture/CODE_REFERENCE_INDEX.md +++ b/docs/architecture/CODE_REFERENCE_INDEX.md @@ -89,7 +89,7 @@ - SSTC 负责人推荐接口:参考 `POST /projects/{project_id}/tasks/{task_id}/recommend-assignee` - SSTC 任务更新接口:参考 `PATCH /projects/{project_id}/tasks/{task_id}` - SSTC 发布创建接口:参考 `POST /projects/{project_id}/releases` -- PMFlow OS 真实读路径、C1 Change Review API、C2 backend commit 与 D.2 execution handoff:参考 `backend/app/api/pmflow_os_api/`、`backend/app/services/pmflow_os_projection_service.py`、`backend/app/services/pmflow_change_review_service.py`、`backend/app/schemas/pmflow_os.py`;只读提供 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,并读取 `pmflow_threads / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`;C1 写 API 包括 thread message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit`,默认 `mode=atomic`,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,对已确认任务生成/复用 `PromptPackage` 并创建 `ProgressEvent(event_type='handoff_created')` 起点,不直接执行本机命令 +- PMFlow OS 真实读路径、C1 Change Review API、C2 backend commit 与 Execution Handoff:参考 `backend/app/api/pmflow_os_api/`、`backend/app/services/pmflow_os_projection_service.py`、`backend/app/services/pmflow_change_review_service.py`、`backend/app/schemas/pmflow_os.py`;只读提供 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,并读取 `pmflow_threads / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`;C1 写 API 包括 thread message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit`,默认 `mode=atomic`,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,对已确认任务生成/复用 `PromptPackage` 并创建 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,记录运行端领取、更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并创建 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;两步都不直接执行本机命令 - Project Launch 真实创建链:参考 `backend/app/api/pmflow_launch_api/`、`backend/app/services/pmflow_launch_service.py`、`backend/app/schemas/pmflow_launch.py` 与模型 `backend/app/models/pmflow_launch.py`;接口为 `POST /pmflow-launch/drafts` 与 `POST /pmflow-launch/drafts/{draft_id}/confirm`,draft 只写 `pmflow_launch_drafts`,confirm 在同一事务内创建 `projects / user_projects / pmflow_threads / pmflow_thread_messages / truth_nodes / tasks / semantic_facts seed / operation_logs` - PMFlow Change Review 模型与 migration:参考 `backend/app/models/pmflow_change_review.py` 与 `backend/alembic/versions/20260426_add_pmflow_change_review_tables.py`;只包含 `pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`,本阶段不得新增或依赖 `/commit` @@ -97,7 +97,7 @@ - PMFlow UI 契约真源:先读 `docs/PMFLOW_UI_CONTRACT.md`,再修改 `project-launch`、`personal-space`、`pmflow-os` 相关页面;该文档锁定 Logo、色彩、移动端底部导航、状态标签、AI 操作/用户动作/待确认视觉语法和核心功能 canonical 契约 - PMFlow 大模块并行公共壳:参考 `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 的 `4.4.1-4.4.3` 与 `frontend/src/router/index.tsx`、`frontend/src/main.tsx`、`frontend/src/features/pmflow-os/components/MobileBottomNav.tsx`、`frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx`;`/space` 归 Space lane,`/pmflow-os / /command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route,新增或改动必须由 Shell / Integration owner 串行处理 - PMFlow OS 产品合同真源:先读 `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`,再实现 `pmflow-os` 真实后端接入;该文档锁定 `ProjectTruthState / Thread / ChangeSet / ChangeItem / Evidence / Approval / Project Memory / ExecutionContext` 与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的复用边界和后续 API 草案 -- 新增前端 API 调用:参考 `frontend/src/api/sstc.ts`;Space 工作面只读聚合参考 `frontend/src/api/space.ts`,只消费项目摘要与 PMFlow OS `truth-state / execution-summary`,不写 Project Memory 或 execution event;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowExecutionHandoff`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft` +- 新增前端 API 调用:参考 `frontend/src/api/sstc.ts`;Space 工作面只读聚合参考 `frontend/src/api/space.ts`,只消费项目摘要与 PMFlow OS `truth-state / execution-summary`,不写 Project Memory 或 execution event;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowExecutionHandoff`、`consumePMFlowExecutionHandoff`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft` - AI Steward 页面入口:参考 `frontend/src/pages/ai-steward/` - AI Steward 问答浮窗与上下文显性化组件:参考 `frontend/src/components/ai-steward/FloatingQAChat.tsx`、`frontend/src/components/ai-steward/QAConversationStatusBar.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.tsx` - AI Steward QA contract 前端类型(含 `analysis_packet`):参考 `frontend/src/api/aiSteward.ts` @@ -107,7 +107,7 @@ - 个人空间首页 / 空间层入口前端产品域:参考 `frontend/src/features/personal-space/`、`frontend/src/api/space.ts` 与薄入口 `frontend/src/pages/PersonalSpacePage.tsx`;本地预览路由入口为 `/space`,覆盖继续推进、待我确认、AI 正在推进、暂存想法、草案箱与最近项目,点击项目主动作进入 `/pmflow-os?projectId=`,待确认项进入 `/change-review?projectId=&changeSetId=`;Space 只读聚合项目摘要、PMFlow OS `truth-state / execution-summary`,不直接写 Project Memory、ChangeSet 状态或 execution event;暂存想法后端只读聚合尚未接入时必须显示 empty/warning,不得用 mock 数据冒充真实内容;空间层共享左侧栏、顶部条与空间用量组件在 `frontend/src/features/personal-space/components/SpaceChrome.tsx`,`/project-launch` 桌面首次新建壳也复用该组件 - 新项目启动台 / 首次进入页前端产品域:参考 `frontend/src/features/project-launch/` 与薄入口 `frontend/src/pages/ProjectLaunchPage.tsx`;真实模式默认通过 `frontend/src/api/pmflowLaunch.ts` 调用后端 draft/confirm API,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用 `mockData.ts` 本地演示状态机;本地预览路由入口为 `/project-launch` - Account Settings / `/me` 前端产品域:参考 `frontend/src/features/account-settings/AccountSettingsPanel.tsx`、`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 与 `frontend/src/api/accountSettings.ts`;当前首版覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,移动端使用分组列表与 bottom sheet,账户退出仍复用 `AccountControls.tsx`,不改 OAuth/JWT/auth core;V1 暂为本机持久化,UI 必须明确显示 `仅本机暂存 / 保存失败`,不得假装云端账号保存 -- PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=` 走 React Query 读取真实 `truth-state` 与 `execution-summary`,真实模式下 thread message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff 均调后端并 refetch,不本地 promote 入 Project Memory 或伪造 execution event;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success` +- PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=` 走 React Query 读取真实 `truth-state` 与 `execution-summary`,真实模式下 thread message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume 均调后端并 refetch,不本地 promote 入 Project Memory 或伪造 execution event;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success` - PMFlow OS 产品页入口:参考 `frontend/src/features/pmflow-os/pages/CommandCenterPage.tsx`、`StructuredExtractionPage.tsx`、`ChangeReviewPage.tsx`、`MemoryWritebackResultPage.tsx`、`WorkGraphPage.tsx`、`ExecutionBridgePage.tsx`、`ReleaseGrowthPage.tsx`、`TeamLayerPage.tsx`、`ProjectMemoryPage.tsx`、`AccountPage.tsx` - PMFlow OS 行为组件:参考 `frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx`、`ProjectStageRail.tsx`、`ThreadComposer.tsx`、`ExtractionRail.tsx`、`ChangeGroupPanel.tsx`、`ObjectGraphSection.tsx`、`ContextPacketPanel.tsx`、`ExecutionTimeline.tsx`、`ReleaseGatePanel.tsx`、`TeamFeedPanel.tsx`、`ChangeReviewGroup.tsx`、`WorkbenchPanel.tsx`、`ExecutionBridgeCards.tsx`、`PMFlowOsFrame.tsx`、`AccountControls.tsx`、`FeedbackPrimitives.tsx`、`MobileBottomNav.tsx`;其中 `ProjectStructuredLoop.tsx` 承接 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel`,`PMFlowOsFrame.tsx` 承接项目内面包屑和项目切换器,`AccountControls.tsx` 承接桌面头像菜单、退出登录确认与移动端 bottom sheet,`FeedbackPrimitives.tsx` 承接 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet`,`MobileBottomNav.tsx` 承接正式应用壳移动端固定底部导航 - PMFlow OS 原型状态回归测试:参考 `frontend/src/features/pmflow-os/store.test.ts`;真实 API 动作回归参考 `frontend/src/features/pmflow-os/store.api.test.tsx` diff --git a/frontend/src/api/pmflowOs.ts b/frontend/src/api/pmflowOs.ts index fbd3e53d..8b3839ec 100644 --- a/frontend/src/api/pmflowOs.ts +++ b/frontend/src/api/pmflowOs.ts @@ -31,6 +31,12 @@ export interface PMFlowExecutionHandoffRequest { note?: string | null; } +export interface PMFlowExecutionHandoffConsumeRequest { + runtime_target?: PMFlowExecutionRuntimeTarget | null; + consumer_label?: string | null; + note?: string | null; +} + export interface PMFlowPromptPackage { id: string; project_id: string; @@ -177,6 +183,18 @@ export async function createPMFlowExecutionHandoff( return unwrap(data); } +export async function consumePMFlowExecutionHandoff( + projectId: string, + promptPackageId: string, + payload: PMFlowExecutionHandoffConsumeRequest = {} +): Promise { + const { data } = await api.post>( + `/pmflow-os/projects/${projectId}/execution-handoffs/${promptPackageId}/consume`, + payload + ); + return unwrap(data); +} + export async function createPMFlowThreadMessage( projectId: string, threadId: string, diff --git a/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx b/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx index 82dadf6a..e6cc2045 100644 --- a/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx +++ b/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx @@ -53,6 +53,7 @@ export default function ExecutionBridgePage() { executionSummaryError, refetchExecutionSummary, createExecutionHandoff, + consumeExecutionHandoff, advanceExecution, } = usePMFlowOsStore(); const demoSummary = dataSourceMode === "demo" ? createDemoExecutionSummary(state) : null; @@ -107,6 +108,7 @@ export default function ExecutionBridgePage() { @@ -141,6 +143,7 @@ function ExecutionStatusStrip({ summary }: { summary: PMFlowExecutionSummary }) function ContextPackagePanel({ summary, onCreateHandoff, + onConsumeHandoff, onDemoPush, }: { summary: PMFlowExecutionSummary; @@ -150,6 +153,7 @@ function ContextPackagePanel({ runtime_target: PMFlowExecutionRuntimeTarget; note?: string | null; }) => void; + onConsumeHandoff?: (promptPackageId: string) => void; onDemoPush?: () => void; }) { const prompt = summary.promptPackage; @@ -241,6 +245,28 @@ function ContextPackagePanel({ ) : null} + {prompt && onConsumeHandoff ? ( +
+
+ +
+
运行端领取确认
+

+ 只记录交接包已被 Codex/Local Bridge 等运行端领取,不从浏览器或后端执行本机命令。 +

+ +
+
+
+ ) : null} + {onDemoPush ? (