feat(pmflow-os): connect execution capability gate
This commit is contained in:
parent
edf18cb15f
commit
42944928a1
|
|
@ -765,113 +765,85 @@ commit gate 判定:
|
||||||
|
|
||||||
当前已完成:
|
当前已完成:
|
||||||
|
|
||||||
**P8:Review Decision Loop 剩余消费面收口。**
|
**main-flow-d4-execution-runtime-capability:Execution Capability Graph 接入开发执行页。**
|
||||||
|
|
||||||
原因:
|
原因:
|
||||||
|
|
||||||
- Main Flow 已把 `reviewerRouting` 从“只读建议”推进到 `review request -> reviewer decision -> approval audit`,并复用 `PMFlowApproval` 形成审计记录。
|
- `/execution-bridge` 已从字段调试页推进到产品化开发执行工作面,并进一步消费 `execution-capabilities`,把 `AI 直接执行 / AI 执行 + 用户验收 / AI 执行 + 技术审查 / 需要人类先补判断 / 当前不可执行` 变成首屏执行模式判定。
|
||||||
- Space 已把待审/已请求/已决策事项纳入 `/space` 的个人下一步队列,Account 已补 review decision / high-risk approval 的页面内提醒偏好合同。
|
- PMFlow OS store 已把 `GET /pmflow-os/projects/{project_id}/execution-capabilities` 纳入共享状态,执行交接除正式 Task gate 外,也会在 reviewer 缺失时阻断 handoff。
|
||||||
- P8 集成仍不新增战略页面,不改 router/nav,不接真实通知中心;继续使用既有 `/change-review / /team-layer / /space / /me` 表面。
|
- `/execution-bridge` 已展示“本次执行结果”汇总,把运行端、代码证据、测试证据、阻塞与下一步收敛成验收前判断,不把缺证据状态表达成发布成功。
|
||||||
|
|
||||||
### 7.1 P8 并行建议
|
### 7.1 下一步推荐 Packet:`main-flow-d5-execution-acceptance-loop`
|
||||||
|
|
||||||
P8 可以三条 lane 并行,但共享高冲突文件必须由 Main Flow 或 Shell / Integration 串行处理。
|
状态:建议下一步。
|
||||||
|
|
||||||
#### Main Flow Packet:`main-flow-p8-review-decision-loop`
|
|
||||||
|
|
||||||
状态:已完成。
|
|
||||||
|
|
||||||
目标:
|
目标:
|
||||||
|
|
||||||
- 把 reviewerRouting 从“只读建议”推进到“审查决策闭环”。
|
- 把 `execution_result_protocol_v1` 回写后的结果接到“用户验收 / 技术审查 / 发布门禁”状态流。
|
||||||
- 支持对待审 ChangeSet 请求审查、记录 reviewer decision、形成 `PMFlowApproval` 审计记录。
|
- 区分 `待回写 / 执行中 / 执行受阻 / 待用户验收 / 待技术审查 / 已验收`,并让 Space next action 在验收后能收敛。
|
||||||
- 让 `ChangeReviewPage / TeamLayerPage` 展示审查状态、审查意见、高风险 approval gate 和下一步动作。
|
- 技术审查模式必须保留 reviewer gate;agent / IDE / Local Bridge 回写测试通过,不等于自动完成。
|
||||||
- reviewer decision 只影响审查/approval 状态,不自动 commit ChangeSet。
|
- 通过验收后再让 Release/Growth 门禁读取稳定状态,缺 PR、测试或阻塞证据时继续 fail-closed。
|
||||||
- high-risk ChangeItem 必须继续遵守 C2 approval gate。
|
|
||||||
|
|
||||||
建议写范围:
|
建议写范围:
|
||||||
|
|
||||||
- `backend/app/api/pmflow_os_api/`
|
- `backend/app/api/pmflow_os_api/`
|
||||||
- `backend/app/services/pmflow_change_review_service.py`
|
|
||||||
- `backend/app/services/pmflow_os_projection_service.py`
|
- `backend/app/services/pmflow_os_projection_service.py`
|
||||||
- `backend/app/schemas/pmflow_os.py`
|
- `backend/app/schemas/pmflow_os.py`
|
||||||
- `backend/tests/test_pmflow_change_review_api.py`
|
|
||||||
- `backend/tests/test_pmflow_os_projection_api.py`
|
- `backend/tests/test_pmflow_os_projection_api.py`
|
||||||
- `frontend/src/api/pmflowOs.ts`
|
- `frontend/src/api/pmflowOs.ts`
|
||||||
- `frontend/src/features/pmflow-os/store.tsx`
|
- `frontend/src/features/pmflow-os/store.tsx`
|
||||||
- `frontend/src/features/pmflow-os/store.api.test.tsx`
|
- `frontend/src/features/pmflow-os/store.api.test.tsx`
|
||||||
- `frontend/src/features/pmflow-os/types.ts`
|
- `frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx`
|
||||||
- `frontend/src/features/pmflow-os/pages/ChangeReviewPage.tsx`
|
- `frontend/src/features/pmflow-os/pages/ReleaseGrowthPage.tsx`
|
||||||
- `frontend/src/features/pmflow-os/pages/TeamLayerPage.tsx`
|
- 必要文档
|
||||||
- 相关 Main Flow 文档
|
|
||||||
|
|
||||||
禁止:
|
禁止:
|
||||||
|
|
||||||
- 不新增 migration,优先复用 `PMFlowApproval`。
|
- 不执行本机命令、不推送、不发布。
|
||||||
- 不新增 route/nav。
|
- 不自动创建正式任务。
|
||||||
- 不改 Space / Account。
|
- 不绕过 Project Memory commit 后才能执行的边界。
|
||||||
- 不自动 commit ChangeSet。
|
- 不把测试通过自动等同于用户验收或技术审查通过。
|
||||||
- 不接真实邮件、桌面推送或系统通知中心。
|
|
||||||
|
|
||||||
#### Space Packet:`space-p7-review-attention-queue`
|
|
||||||
|
|
||||||
状态:已完成。
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 把 Team Review 的待审事项接入 `/space` 的“我的下一步”队列。
|
|
||||||
- 只读消费 `truth-state.reviewerRouting`;如果 Main Flow P8 已完成,再只读消费 `reviewDecision / approvalState`。
|
|
||||||
- 展示“我需要审什么 / 哪些审查卡住了项目”,所有动作仍只跳主流程。
|
|
||||||
|
|
||||||
建议写范围:
|
|
||||||
|
|
||||||
- `frontend/src/api/space.ts`
|
|
||||||
- `frontend/src/features/personal-space/`
|
|
||||||
- `frontend/src/features/personal-space/PersonalSpacePage.test.tsx`
|
|
||||||
- Space 相关文档
|
|
||||||
|
|
||||||
禁止:
|
|
||||||
|
|
||||||
- 不写 ChangeSet、`PMFlowApproval`、ProgressEvent、ReleaseValidationRun、FeedbackIngest。
|
|
||||||
- 不改 PMFlow OS、Account、router 或 bottom nav。
|
|
||||||
- 不虚构 reviewer 或待审项。
|
|
||||||
|
|
||||||
#### Account Settings Packet:`account-p8-review-decision-preference`
|
|
||||||
|
|
||||||
状态:已完成。
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 扩展 Account Settings 的提醒偏好合同,增加 review decision / reviewer request / high-risk approval 相关偏好。
|
|
||||||
- 仍然只控制页面内提醒强度,不接真实通知中心。
|
|
||||||
- 偏好关闭只能静默强调,不得隐藏真实 reviewerRouting、approval、evidence 或 audit state。
|
|
||||||
|
|
||||||
建议写范围:
|
|
||||||
|
|
||||||
- `frontend/src/api/accountSettings.ts`
|
|
||||||
- `frontend/src/features/account-settings/AccountSettingsPanel.tsx`
|
|
||||||
- `frontend/src/features/account-settings/accountSettings.test.ts`
|
|
||||||
- Account 相关文档
|
|
||||||
|
|
||||||
禁止:
|
|
||||||
|
|
||||||
- 不改 PMFlow OS、Space、router 或 backend schema。
|
|
||||||
- 不把偏好当业务权限。
|
|
||||||
|
|
||||||
### 7.2 P8 Integration Owner 收口
|
|
||||||
|
|
||||||
状态:已完成。
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 检查 Main Flow / Space / Account 三个 Packet 的 `git diff --name-only` 是否越界。
|
|
||||||
- 统一处理共享文档、API 索引和状态台账冲突。
|
|
||||||
- 确认 P8 仍不新增战略页面,仍使用既有 `/change-review / /team-layer / /space / /me` 表面。
|
|
||||||
- 跑 Main Flow / Space / Account focused tests、全量 typecheck/build,并在后端变更后重启 backend 确认 healthy。
|
|
||||||
- 更新 `## 8. 进度台账`、`CODE_REFERENCE_INDEX.md`、`ARCHITECTURE_INVENTORY.md` 与必要的 `CLAUDE.md`。
|
|
||||||
|
|
||||||
## 8. 进度台账
|
## 8. 进度台账
|
||||||
|
|
||||||
|
### 2026-05-05 / main-flow-d4-execution-runtime-capability / implemented
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- PMFlow OS store 新增真实 `execution-capabilities` 查询状态,`refetchApiState` 会同步刷新 truth-state、execution-summary 与 execution-capabilities。
|
||||||
|
- `createExecutionHandoff` 在正式任务 gate 之外新增 reviewer/capability gate:当技术审查责任缺失时阻止真实 handoff,提示先回团队层或待确认中心补齐审查责任。
|
||||||
|
- `/execution-bridge` 首屏新增“执行模式判定”,消费 Execution Capability Graph 展示 `AI 直接执行 / AI 执行 + 用户验收 / AI 执行 + 技术审查 / 需要人类先补判断 / 当前不可执行`。
|
||||||
|
- `/execution-bridge` 新增“本次执行结果”汇总,把运行端、代码证据、测试证据、阻塞与下一步收敛为验收前判断;缺证据时保持待回写,不进入发布成功表达。
|
||||||
|
- 执行交接主按钮改为能力 gate:候选/草稿任务、待确认范围、缺 reviewer 会阻塞;缺 repo 时只允许生成交接草案;缺 Prompt Package 时提示生成上下文包。
|
||||||
|
- AI 副驾从泛审查文案升级为执行裁决建议,只基于 execution-summary、execution-capabilities、progress event 与 release gate,不调用新 LLM,不编造 PR/测试/代码质量。
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `frontend/src/features/pmflow-os/store.tsx`
|
||||||
|
- `frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx`
|
||||||
|
- `frontend/src/features/pmflow-os/store.api.test.tsx`
|
||||||
|
- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md`
|
||||||
|
- `docs/architecture/CODE_REFERENCE_INDEX.md`
|
||||||
|
- `docs/architecture/ARCHITECTURE_INVENTORY.md`
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
- `cd frontend && npx tsc --noEmit --pretty false --incremental false` 通过。
|
||||||
|
- `cd frontend && npm run test -- src/features/pmflow-os/store.api.test.tsx -t "execution capability|technical review|execution bridge"` 通过:2 passed,32 skipped。
|
||||||
|
- `cd frontend && npm run test -- src/features/pmflow-os` 通过:35 passed;仍有既有 React Router future flag warning。
|
||||||
|
- `cd frontend && npm run build` 通过。
|
||||||
|
- Playwright / Chromium 打开 `http://localhost:5176/execution-bridge?demo=1`,桌面 1440x1100 与移动 390x844 均能渲染“执行模式判定 / 本次执行结果 / 执行交接”模块;截图输出到 `output/playwright/execution-bridge-d4-desktop.png` 与 `output/playwright/execution-bridge-d4-mobile.png`。
|
||||||
|
- 本轮只修改前端和文档,未修改后端代码、Schema 或 migration,因此未执行后端编译、数据库迁移或热重启。
|
||||||
|
|
||||||
|
未完成或风险:
|
||||||
|
|
||||||
|
- 当前未接真实 Deep Agents runtime,也不执行本机命令;Execution Bridge 仍只做上下文包、handoff 合同与结果回写展示。
|
||||||
|
- 浏览器实测中本地无后端认证导致既有 `GET /api/ai-readiness/project-launch` 401,这属于项目壳 AI readiness 本地环境问题,不影响本页模块渲染结论。
|
||||||
|
|
||||||
|
下一步建议:
|
||||||
|
|
||||||
|
- 进入 `main-flow-d5-execution-acceptance-loop`:把执行结果回写接到用户验收 / 技术审查 / 发布门禁状态流,避免测试通过后仍停留在“待回写”或被误认为自动完成。
|
||||||
|
|
||||||
### 2026-05-05 / main-flow-d3-execution-bridge-productization / implemented
|
### 2026-05-05 / main-flow-d3-execution-bridge-productization / implemented
|
||||||
|
|
||||||
完成内容:
|
完成内容:
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,7 @@
|
||||||
- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 `sendMessage` 会写 thread message 并触发后端真实 AI extraction;显式 `proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功;AI extraction 失败显示“结构化失败/待补充”,不创建模板 ChangeSet。
|
- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 `sendMessage` 会写 thread message 并触发后端真实 AI extraction;显式 `proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功;AI extraction 失败显示“结构化失败/待补充”,不创建模板 ChangeSet。
|
||||||
- `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` 进入时通过 `origin=change_review / returnTo=change_review / changeSetId / itemId` 保留审查上下文,主动作返回待确认队列,补充证据才回指挥台;已 committed 的轮次显示为结构化写入快照并转向项目记忆或新变更链路。`ChangeReviewPage.tsx` 是决策与待确认中心,真实模式下顶部识别统计、待确认列表与右侧 AI 副驾评估都只消费后端 `changeSets / changeItems / evidence`,没有真实待审项时显示空态,禁止用 demo 审查项补位;确认写入 Project Memory 前,`pending_review / accepted / edited / deferred` ChangeItem 均可继续 accept/edit/defer,只有 `committed / skipped` 或关闭轮次不可再操作;`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`、`latestValidationRun` 与 `releaseSignals`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作、发布验证记录和发布后 feedback/revenue/ROI/compliance/recommendation 信号,缺少发布后信号时显示 empty/warning,不执行发布命令、不展示 mock 发布结果;页面可展示 recommendation quality grouping,把真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不创建正式任务;`ChangeReviewPage.tsx` 会展示 `iteration_recommendation_group` evidence 的来源组、feedback 数、recommendation 数、风险、影响和审查优先级;同时读取 Account notification preference helper,用 `releaseGate` 偏好控制发布门禁强调提醒,用 `failureAlert` 偏好控制失败强调提醒,偏好关闭时只静默提醒表达强度,不隐藏 release evidence、阻塞原因、审计状态、validation run 或 releaseSignals;静态发布/增长原型只保留在显式 demo 模式。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,Release Growth P1 已接入 execution evidence gate,Release Validation P1 已接入 `POST /release-validation-runs` 创建 `ReleaseValidationRun` 快照,Release Feedback P1 已接入 `POST /release-feedback-ingests` 写入 `FeedbackIngest` 并生成 `IterationRecommendation`,Iteration Loop P1 已接入 `POST /iteration-recommendations/{recommendation_id}/change-set-drafts` 把 recommendation 回流到 Change Review,Iteration Loop P2 已接入 `POST /iteration-recommendation-groups/{group_key}/change-set-drafts` 并在 `releaseSignals.recommendationGroups` 暴露分组/去重/风险/影响合同;真实 AI extraction 已接入独立 `change_review` protected scene、`pmflow_change_review_extraction_v1` 运行合同、`PROMPT_PMFLOW_CHANGE_REVIEW_EXTRACTION` Prompt 资产与后端 `llm_service.generate_structured_object`,成功时生成 open ChangeSet、pending_review ChangeItems 与 thread message evidence,失败时 fail-closed,不把 mock 当真实失败 fallback,也不复用 Project Launch draft prompt/function。`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` 表达“未确认不得写入”的局部提示。
|
- `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` 进入时通过 `origin=change_review / returnTo=change_review / changeSetId / itemId` 保留审查上下文,主动作返回待确认队列,补充证据才回指挥台;已 committed 的轮次显示为结构化写入快照并转向项目记忆或新变更链路。`ChangeReviewPage.tsx` 是决策与待确认中心,真实模式下顶部识别统计、待确认列表与右侧 AI 副驾评估都只消费后端 `changeSets / changeItems / evidence`,没有真实待审项时显示空态,禁止用 demo 审查项补位;确认写入 Project Memory 前,`pending_review / accepted / edited / deferred` ChangeItem 均可继续 accept/edit/defer,只有 `committed / skipped` 或关闭轮次不可再操作;`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`、`latestValidationRun` 与 `releaseSignals`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作、发布验证记录和发布后 feedback/revenue/ROI/compliance/recommendation 信号,缺少发布后信号时显示 empty/warning,不执行发布命令、不展示 mock 发布结果;页面可展示 recommendation quality grouping,把真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不创建正式任务;`ChangeReviewPage.tsx` 会展示 `iteration_recommendation_group` evidence 的来源组、feedback 数、recommendation 数、风险、影响和审查优先级;同时读取 Account notification preference helper,用 `releaseGate` 偏好控制发布门禁强调提醒,用 `failureAlert` 偏好控制失败强调提醒,偏好关闭时只静默提醒表达强度,不隐藏 release evidence、阻塞原因、审计状态、validation run 或 releaseSignals;静态发布/增长原型只保留在显式 demo 模式。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,Release Growth P1 已接入 execution evidence gate,Release Validation P1 已接入 `POST /release-validation-runs` 创建 `ReleaseValidationRun` 快照,Release Feedback P1 已接入 `POST /release-feedback-ingests` 写入 `FeedbackIngest` 并生成 `IterationRecommendation`,Iteration Loop P1 已接入 `POST /iteration-recommendations/{recommendation_id}/change-set-drafts` 把 recommendation 回流到 Change Review,Iteration Loop P2 已接入 `POST /iteration-recommendation-groups/{group_key}/change-set-drafts` 并在 `releaseSignals.recommendationGroups` 暴露分组/去重/风险/影响合同;真实 AI extraction 已接入独立 `change_review` protected scene、`pmflow_change_review_extraction_v1` 运行合同、`PROMPT_PMFLOW_CHANGE_REVIEW_EXTRACTION` Prompt 资产与后端 `llm_service.generate_structured_object`,成功时生成 open ChangeSet、pending_review ChangeItems 与 thread message evidence,失败时 fail-closed,不把 mock 当真实失败 fallback,也不复用 Project Launch draft prompt/function。`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` 表达“未确认不得写入”的局部提示。
|
||||||
- Execution Result Protocol P1 将 Execution Bridge 的执行结果回写收敛为 `execution_result_protocol_v1`:`backend/app/schemas/pmflow_os.py` 接收 `execution_status` 与 `blocker_summary`,`backend/app/services/pmflow_os_projection_service.py` 写入 `ProgressEvent.payload.protocol_version` 与 `execution_result` envelope,同时保留 `branch_name / commit_sha / pr_number / test_summary / workflow_state` flat 字段兼容现有投影;`frontend/src/api/pmflowOs.ts` 和 `ExecutionBridgePage.tsx` 支持手动记录 status/blocker。该链路只记录 Codex/IDE/Local Bridge/GitHub agent 已产生的结果,不执行本机命令、不推送、不发布、不自动创建正式任务;`execution-summary`、`releaseGate` 与 Team Feed 均从 `ProgressEvent` 只读消费。
|
- Execution Result Protocol P1 将 Execution Bridge 的执行结果回写收敛为 `execution_result_protocol_v1`:`backend/app/schemas/pmflow_os.py` 接收 `execution_status` 与 `blocker_summary`,`backend/app/services/pmflow_os_projection_service.py` 写入 `ProgressEvent.payload.protocol_version` 与 `execution_result` envelope,同时保留 `branch_name / commit_sha / pr_number / test_summary / workflow_state` flat 字段兼容现有投影;`frontend/src/api/pmflowOs.ts` 和 `ExecutionBridgePage.tsx` 支持手动记录 status/blocker。该链路只记录 Codex/IDE/Local Bridge/GitHub agent 已产生的结果,不执行本机命令、不推送、不发布、不自动创建正式任务;`execution-summary`、`releaseGate` 与 Team Feed 均从 `ProgressEvent` 只读消费。
|
||||||
|
- Main Flow D4 已把 Execution Capability Graph 接入 `/execution-bridge`:`frontend/src/features/pmflow-os/store.tsx` 现在读取并刷新真实 `GET /pmflow-os/projects/{project_id}/execution-capabilities`,`ExecutionBridgePage.tsx` 首屏展示执行模式判定、本次执行结果、执行链路、上下文包、回写视图和 AI 执行裁决建议;handoff 主按钮受正式 Task、待确认范围、repo/prompt package 与 reviewer gate 约束,技术审查责任缺失时 store 会阻止真实 handoff,缺 repo 时只允许表达交接草案。该层仍只做能力判定、交接合同和结果回写展示,不执行本机命令、不触发真实 agent、不自动验收、不自动发布。
|
||||||
- Project Knowledge Hub 读模型由 `GET /pmflow-os/projects/{project_id}/project-memory/overview`、`items`、`items/{item_id}`、`assets`、`context-packs`、`writebacks` 承载,后端在 `backend/app/services/pmflow_os_projection_service.py` 从 `truth_nodes / tasks / semantic_facts / pmflow_evidence_links / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items` 聚合首页雷达、全部记忆、项目资产、上下文包和写入版本记录。前端对应 `ProjectMemoryPage.tsx`、`ProjectMemoryCollectionPages.tsx` 与 `projectMemoryUtils.ts`:列表时间使用短格式,抽屉显示完整时间和北京时间;首页卡片与二级列表单条点击打开右侧详情抽屉,展示来源证据、影响对象和 AI 执行动作跳转;全部记忆支持类型、阶段、来源、状态、搜索筛选。该读模型只读,资产可自动索引,进入项目真相层仍必须经过 Change Review 或可信规则确认。
|
- Project Knowledge Hub 读模型由 `GET /pmflow-os/projects/{project_id}/project-memory/overview`、`items`、`items/{item_id}`、`assets`、`context-packs`、`writebacks` 承载,后端在 `backend/app/services/pmflow_os_projection_service.py` 从 `truth_nodes / tasks / semantic_facts / pmflow_evidence_links / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items` 聚合首页雷达、全部记忆、项目资产、上下文包和写入版本记录。前端对应 `ProjectMemoryPage.tsx`、`ProjectMemoryCollectionPages.tsx` 与 `projectMemoryUtils.ts`:列表时间使用短格式,抽屉显示完整时间和北京时间;首页卡片与二级列表单条点击打开右侧详情抽屉,展示来源证据、影响对象和 AI 执行动作跳转;全部记忆支持类型、阶段、来源、状态、搜索筛选。该读模型只读,资产可自动索引,进入项目真相层仍必须经过 Change Review 或可信规则确认。
|
||||||
- `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,并在通知偏好区展示页面内提醒消费说明、提醒强度和证据展示配置。`frontend/src/api/accountSettings.ts` 是前端合同层;有 token 时优先调用 `backend/app/api/account_settings_api/`,失败时回落浏览器本机暂存;P2 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,将四类通知偏好映射为本地 UI 的待确认、Execution Bridge、Release/Growth 和失败提醒显示/静默状态;P3 新增 `getReleaseGateNotificationPreference(settings, event)`,把 Release/Growth 的 `release_gate_* / release_validation_*` 事件映射到 `releaseGate` 偏好,把 `failed_event` 映射到 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 把发布后 `feedback_received / revenue_signal / compliance_blocked` 映射到 `releaseGate` 偏好,把 `iteration_recommendation / next_change_review` 映射到 `pendingConfirmation` 偏好和 `change_review` surface,并返回 `spaceReadable / evidencePolicy / reminderPolicy` 供 Main Flow 与 Space 消费;P7 将通知偏好扩展为 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`,并新增 `getAccountReminderConsumption(settings, event)` 覆盖 `change_review_high_risk / iteration_draft_ready / release_blocked / execution_stalled`,输出 `notificationTransport=in_app_only / evidencePolicy=preserve_business_evidence / auditPolicy=preserve_audit_state`;P8 新增 `getReviewDecisionNotificationPreference(settings, event)` 覆盖 `review_requested / review_approved / review_changes_requested / review_rejected / high_risk_approval_required`,继续使用 `pendingConfirmation` 偏好和 `change_review` surface,并声明 reviewerRouting、approval、evidence、audit state 均必须保留。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、reviewerRouting、approval、recommendation、ChangeSet、执行状态或审计状态。`backend/app/schemas/account_settings.py` 定义账号偏好合同,`backend/app/services/account_settings_service.py` 当前复用既有 `integrations` 表按 `tenant_id + user_id` 保存 `account_settings_preferences` 配置包,`backend/app/api/router.py` 已挂载该 API;未改用户表、OAuth/JWT/auth core 或 PMFlow OS truth store。
|
- `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,并在通知偏好区展示页面内提醒消费说明、提醒强度和证据展示配置。`frontend/src/api/accountSettings.ts` 是前端合同层;有 token 时优先调用 `backend/app/api/account_settings_api/`,失败时回落浏览器本机暂存;P2 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,将四类通知偏好映射为本地 UI 的待确认、Execution Bridge、Release/Growth 和失败提醒显示/静默状态;P3 新增 `getReleaseGateNotificationPreference(settings, event)`,把 Release/Growth 的 `release_gate_* / release_validation_*` 事件映射到 `releaseGate` 偏好,把 `failed_event` 映射到 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 把发布后 `feedback_received / revenue_signal / compliance_blocked` 映射到 `releaseGate` 偏好,把 `iteration_recommendation / next_change_review` 映射到 `pendingConfirmation` 偏好和 `change_review` surface,并返回 `spaceReadable / evidencePolicy / reminderPolicy` 供 Main Flow 与 Space 消费;P7 将通知偏好扩展为 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`,并新增 `getAccountReminderConsumption(settings, event)` 覆盖 `change_review_high_risk / iteration_draft_ready / release_blocked / execution_stalled`,输出 `notificationTransport=in_app_only / evidencePolicy=preserve_business_evidence / auditPolicy=preserve_audit_state`;P8 新增 `getReviewDecisionNotificationPreference(settings, event)` 覆盖 `review_requested / review_approved / review_changes_requested / review_rejected / high_risk_approval_required`,继续使用 `pendingConfirmation` 偏好和 `change_review` surface,并声明 reviewerRouting、approval、evidence、audit state 均必须保留。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、reviewerRouting、approval、recommendation、ChangeSet、执行状态或审计状态。`backend/app/schemas/account_settings.py` 定义账号偏好合同,`backend/app/services/account_settings_service.py` 当前复用既有 `integrations` 表按 `tenant_id + user_id` 保存 `account_settings_preferences` 配置包,`backend/app/api/router.py` 已挂载该 API;未改用户表、OAuth/JWT/auth core 或 PMFlow OS truth store。
|
||||||
- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 `sendMessage` 会写 thread message 并触发后端真实 AI extraction;显式 `proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、AI feedback、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest 与 iteration recommendation -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;AI feedback 写入 `pmflow_ai_feedback_events`,在 `truth-state.aiFeedback` 恢复当前用户选择,并由后端按 `prompt_key` 生成待复核 `JudgeRun / OptimizerCandidate` 质量优化候选,用户改回赞同时挂起此前候选,Prompt Asset quality summary 会聚合 PMFlow 反馈与优化候选;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功;AI extraction 失败显示“结构化失败/待补充”,不创建模板 ChangeSet。
|
- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 `sendMessage` 会写 thread message 并触发后端真实 AI extraction;显式 `proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、AI feedback、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest 与 iteration recommendation -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;AI feedback 写入 `pmflow_ai_feedback_events`,在 `truth-state.aiFeedback` 恢复当前用户选择,并由后端按 `prompt_key` 生成待复核 `JudgeRun / OptimizerCandidate` 质量优化候选,用户改回赞同时挂起此前候选,Prompt Asset quality summary 会聚合 PMFlow 反馈与优化候选;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功;AI extraction 失败显示“结构化失败/待补充”,不创建模板 ChangeSet。
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -17,6 +17,7 @@ import {
|
||||||
Send,
|
Send,
|
||||||
Server,
|
Server,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
|
ShieldCheck,
|
||||||
Terminal,
|
Terminal,
|
||||||
TestTube2,
|
TestTube2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
@ -41,6 +42,11 @@ import {
|
||||||
PacketBlock,
|
PacketBlock,
|
||||||
} from "@/features/pmflow-os/components/ExecutionBridgeCards";
|
} from "@/features/pmflow-os/components/ExecutionBridgeCards";
|
||||||
import { usePMFlowOsStore } from "@/features/pmflow-os/store";
|
import { usePMFlowOsStore } from "@/features/pmflow-os/store";
|
||||||
|
import type {
|
||||||
|
PMFlowExecutionCapabilityMode,
|
||||||
|
PMFlowExecutionCapabilitySnapshot,
|
||||||
|
PMFlowExecutionModeCapability,
|
||||||
|
} from "@/features/pmflow-os/types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const STATUS_LABEL: Record<string, string> = {
|
const STATUS_LABEL: Record<string, string> = {
|
||||||
|
|
@ -86,6 +92,7 @@ const TAB_ITEMS: Array<{ key: ExecutionTabKey; label: string }> = [
|
||||||
|
|
||||||
type ExecutionTabKey = "changes" | "terminal" | "commits" | "pr" | "tests" | "writeback";
|
type ExecutionTabKey = "changes" | "terminal" | "commits" | "pr" | "tests" | "writeback";
|
||||||
type PipelineTone = "done" | "current" | "waiting" | "blocked";
|
type PipelineTone = "done" | "current" | "waiting" | "blocked";
|
||||||
|
type CapabilityTone = "ready" | "partial" | "blocked" | "unknown";
|
||||||
|
|
||||||
function isFormalDevTask(task?: PMFlowDevTask | null) {
|
function isFormalDevTask(task?: PMFlowDevTask | null) {
|
||||||
const status = String(task?.status || "").toLowerCase();
|
const status = String(task?.status || "").toLowerCase();
|
||||||
|
|
@ -96,6 +103,122 @@ function isFormalDevTask(task?: PMFlowDevTask | null) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function primaryExecutionMode(capabilities?: PMFlowExecutionCapabilitySnapshot | null): PMFlowExecutionModeCapability | null {
|
||||||
|
if (!capabilities?.executionModes.length) return null;
|
||||||
|
const order: PMFlowExecutionCapabilityMode[] = [
|
||||||
|
"ai_with_technical_review",
|
||||||
|
"ai_direct",
|
||||||
|
"ai_with_user_acceptance",
|
||||||
|
"needs_human_judgement",
|
||||||
|
"blocked",
|
||||||
|
];
|
||||||
|
return order
|
||||||
|
.map((mode) => capabilities.executionModes.find((item) => item.mode === mode && item.status === "ready"))
|
||||||
|
.find(Boolean) || capabilities.executionModes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHandoffGate(summary: PMFlowExecutionSummary, capabilities?: PMFlowExecutionCapabilitySnapshot | null) {
|
||||||
|
const task =
|
||||||
|
summary.activeDevTasks.find((item) => item.id === summary.execution.taskId) || summary.activeDevTasks[0] || null;
|
||||||
|
const formalTask = isFormalDevTask(task) ? task : null;
|
||||||
|
const repo = task?.code_link?.repo || summary.repoBindings[0] || null;
|
||||||
|
const technicalReviewMode = capabilities?.executionModes.find((item) => item.mode === "ai_with_technical_review");
|
||||||
|
const reviewerMissing =
|
||||||
|
technicalReviewMode?.status === "blocked" || Boolean(capabilities?.warnings.some((item) => item.includes("reviewer_missing")));
|
||||||
|
const pendingConfirmations = Number(capabilities?.personalReadiness.waitingForUser.pendingConfirmations ?? 0);
|
||||||
|
const prompt = summary.promptPackage;
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
formalTask,
|
||||||
|
repo,
|
||||||
|
prompt,
|
||||||
|
canCreate: false,
|
||||||
|
actionLabel: "缺少正式任务",
|
||||||
|
reason: "暂无正式开发任务,先从项目记忆形成可执行任务。",
|
||||||
|
mode: primaryExecutionMode(capabilities),
|
||||||
|
requiresTechnicalReview: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!formalTask) {
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
formalTask,
|
||||||
|
repo,
|
||||||
|
prompt,
|
||||||
|
canCreate: false,
|
||||||
|
actionLabel: "等待写入项目记忆",
|
||||||
|
reason: "当前任务仍是候选或草稿,需要先在待确认中心写入项目记忆。",
|
||||||
|
mode: primaryExecutionMode(capabilities),
|
||||||
|
requiresTechnicalReview: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (reviewerMissing) {
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
formalTask,
|
||||||
|
repo,
|
||||||
|
prompt,
|
||||||
|
canCreate: false,
|
||||||
|
actionLabel: "先补审查责任",
|
||||||
|
reason: "当前执行模式需要技术审查责任,但 reviewer 尚未明确;请先回到团队层或待确认中心补齐审查责任。",
|
||||||
|
mode: technicalReviewMode || primaryExecutionMode(capabilities),
|
||||||
|
requiresTechnicalReview: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pendingConfirmations > 0) {
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
formalTask,
|
||||||
|
repo,
|
||||||
|
prompt,
|
||||||
|
canCreate: false,
|
||||||
|
actionLabel: "先处理待确认",
|
||||||
|
reason: "当前项目仍有 AI 候选变更待确认,先完成 Change Review,避免执行包基于未确认范围扩散。",
|
||||||
|
mode: primaryExecutionMode(capabilities),
|
||||||
|
requiresTechnicalReview: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!repo) {
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
formalTask,
|
||||||
|
repo,
|
||||||
|
prompt,
|
||||||
|
canCreate: true,
|
||||||
|
actionLabel: "生成交接草案",
|
||||||
|
reason: "可以生成执行交接草案,但仓库落点仍需补齐;这不会声称已经推送到真实代码环境。",
|
||||||
|
mode: primaryExecutionMode(capabilities),
|
||||||
|
requiresTechnicalReview: technicalReviewMode?.status === "ready",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!prompt) {
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
formalTask,
|
||||||
|
repo,
|
||||||
|
prompt,
|
||||||
|
canCreate: true,
|
||||||
|
actionLabel: "生成上下文包",
|
||||||
|
reason: "正式任务与仓库已就绪,下一步生成开发上下文包供运行端领取。",
|
||||||
|
mode: primaryExecutionMode(capabilities),
|
||||||
|
requiresTechnicalReview: technicalReviewMode?.status === "ready",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
formalTask,
|
||||||
|
repo,
|
||||||
|
prompt,
|
||||||
|
canCreate: true,
|
||||||
|
actionLabel: "发起执行交接",
|
||||||
|
reason: "复用当前上下文包,并记录新的执行交接起点;代码执行仍在外部运行端完成。",
|
||||||
|
mode: primaryExecutionMode(capabilities),
|
||||||
|
requiresTechnicalReview: technicalReviewMode?.status === "ready",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function ExecutionBridgePage() {
|
export default function ExecutionBridgePage() {
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
|
|
@ -103,7 +226,11 @@ export default function ExecutionBridgePage() {
|
||||||
executionSummary,
|
executionSummary,
|
||||||
executionSummaryStatus,
|
executionSummaryStatus,
|
||||||
executionSummaryError,
|
executionSummaryError,
|
||||||
|
executionCapabilities,
|
||||||
|
executionCapabilitiesStatus,
|
||||||
|
executionCapabilitiesError,
|
||||||
refetchExecutionSummary,
|
refetchExecutionSummary,
|
||||||
|
refetchExecutionCapabilities,
|
||||||
createExecutionHandoff,
|
createExecutionHandoff,
|
||||||
consumeExecutionHandoff,
|
consumeExecutionHandoff,
|
||||||
recordExecutionProgress,
|
recordExecutionProgress,
|
||||||
|
|
@ -111,6 +238,8 @@ export default function ExecutionBridgePage() {
|
||||||
} = usePMFlowOsStore();
|
} = usePMFlowOsStore();
|
||||||
const demoSummary = dataSourceMode === "demo" ? createDemoExecutionSummary(state) : null;
|
const demoSummary = dataSourceMode === "demo" ? createDemoExecutionSummary(state) : null;
|
||||||
const summary = executionSummary || demoSummary;
|
const summary = executionSummary || demoSummary;
|
||||||
|
const demoCapabilities = dataSourceMode === "demo" && summary ? createDemoExecutionCapabilities(summary) : null;
|
||||||
|
const capabilities = executionCapabilities || demoCapabilities;
|
||||||
const isDemo = dataSourceMode === "demo";
|
const isDemo = dataSourceMode === "demo";
|
||||||
const isLoading = dataSourceMode === "api" && executionSummaryStatus === "loading";
|
const isLoading = dataSourceMode === "api" && executionSummaryStatus === "loading";
|
||||||
const error = dataSourceMode === "api" ? executionSummaryError : null;
|
const error = dataSourceMode === "api" ? executionSummaryError : null;
|
||||||
|
|
@ -132,10 +261,13 @@ export default function ExecutionBridgePage() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center gap-2 rounded-lg border border-[#dfe7f3] bg-white px-3 py-2 text-sm font-semibold text-[#2463EB] transition hover:bg-[#f5f8ff]"
|
className="inline-flex items-center gap-2 rounded-lg border border-[#dfe7f3] bg-white px-3 py-2 text-sm font-semibold text-[#2463EB] transition hover:bg-[#f5f8ff]"
|
||||||
onClick={refetchExecutionSummary}
|
onClick={() => {
|
||||||
|
refetchExecutionSummary();
|
||||||
|
refetchExecutionCapabilities();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-4 w-4" />
|
<RefreshCw className="h-4 w-4" />
|
||||||
刷新执行摘要
|
刷新执行态势
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -154,12 +286,19 @@ export default function ExecutionBridgePage() {
|
||||||
|
|
||||||
{summary ? (
|
{summary ? (
|
||||||
<>
|
<>
|
||||||
|
<ExecutionCapabilityPanel
|
||||||
|
capabilities={capabilities}
|
||||||
|
status={dataSourceMode === "api" ? executionCapabilitiesStatus : "success"}
|
||||||
|
error={dataSourceMode === "api" ? executionCapabilitiesError : null}
|
||||||
|
/>
|
||||||
<ExecutionPipelinePanel summary={summary} />
|
<ExecutionPipelinePanel summary={summary} />
|
||||||
<ExecutionWarningList warnings={summary.warnings} />
|
<ExecutionWarningList warnings={summary.warnings} />
|
||||||
|
<ExecutionResultSummaryPanel summary={summary} capabilities={capabilities} />
|
||||||
|
|
||||||
<div className="grid gap-4 min-[1440px]:grid-cols-[360px_minmax(0,1fr)_340px]">
|
<div className="grid gap-4 min-[1440px]:grid-cols-[360px_minmax(0,1fr)_340px]">
|
||||||
<ContextPackagePanel
|
<ContextPackagePanel
|
||||||
summary={summary}
|
summary={summary}
|
||||||
|
capabilities={capabilities}
|
||||||
onCreateHandoff={isDemo ? undefined : createExecutionHandoff}
|
onCreateHandoff={isDemo ? undefined : createExecutionHandoff}
|
||||||
onConsumeHandoff={isDemo ? undefined : consumeExecutionHandoff}
|
onConsumeHandoff={isDemo ? undefined : consumeExecutionHandoff}
|
||||||
onDemoPush={isDemo ? advanceExecution : undefined}
|
onDemoPush={isDemo ? advanceExecution : undefined}
|
||||||
|
|
@ -168,7 +307,7 @@ export default function ExecutionBridgePage() {
|
||||||
summary={summary}
|
summary={summary}
|
||||||
onRecordProgress={isDemo ? undefined : recordExecutionProgress}
|
onRecordProgress={isDemo ? undefined : recordExecutionProgress}
|
||||||
/>
|
/>
|
||||||
<AICopilotReviewPanel summary={summary} />
|
<AICopilotReviewPanel summary={summary} capabilities={capabilities} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ExecutionProgressPanel summary={summary} />
|
<ExecutionProgressPanel summary={summary} />
|
||||||
|
|
@ -178,6 +317,127 @@ export default function ExecutionBridgePage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ExecutionCapabilityPanel({
|
||||||
|
capabilities,
|
||||||
|
status,
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
capabilities?: PMFlowExecutionCapabilitySnapshot | null;
|
||||||
|
status: "idle" | "loading" | "success" | "error";
|
||||||
|
error?: string | null;
|
||||||
|
}) {
|
||||||
|
if (status === "loading") {
|
||||||
|
return <ExecutionEmptyState title="正在读取执行能力图谱" summary="执行桥正在同步 AI、工具、人类审查和容量信号,不会先用静态技能表冒充判断。" />;
|
||||||
|
}
|
||||||
|
if (status === "error") {
|
||||||
|
return <ExecutionEmptyState title="执行能力图谱同步失败" summary={error || "执行能力信号暂未同步,可先查看执行摘要和回写证据。"} />;
|
||||||
|
}
|
||||||
|
if (!capabilities) {
|
||||||
|
return <ExecutionEmptyState title="缺少执行能力图谱" summary="当前没有 execution-capabilities 快照,执行桥只展示执行摘要,不做模式裁决。" />;
|
||||||
|
}
|
||||||
|
const primary = primaryExecutionMode(capabilities);
|
||||||
|
const readiness = capabilities.personalReadiness;
|
||||||
|
return (
|
||||||
|
<section className="rounded-lg border border-[#dbe6ff] bg-white px-5 py-4 shadow-[0_1px_2px_rgba(15,23,42,0.03)]">
|
||||||
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-base font-semibold text-slate-900">
|
||||||
|
<ShieldCheck className="h-4 w-4 text-[#2463EB]" />
|
||||||
|
执行模式判定
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-sm leading-6 text-slate-500">
|
||||||
|
这里把正式任务、工具连接、审查责任和容量信号收束成执行模式;缺条件时只能阻塞或生成草案。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<StatusChip label={primary?.label || "未判定"} tone={modeTone(primary?.status)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 grid gap-3 lg:grid-cols-[minmax(0,1.35fr)_minmax(280px,0.65fr)]">
|
||||||
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-5">
|
||||||
|
{capabilities.executionModes.map((mode) => (
|
||||||
|
<ExecutionModeCard key={mode.mode} mode={mode} active={mode.mode === primary?.mode} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<aside className="rounded-lg border border-[#e8eef8] bg-[#fbfdff] p-4">
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<h3 className="text-sm font-semibold text-slate-900">我的执行准备度</h3>
|
||||||
|
<StatusChip label={readinessLabel(readiness.status)} tone={modeTone(readiness.status)} />
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-sm leading-6 text-slate-600">{readiness.recommendedNextStep}</p>
|
||||||
|
<div className="mt-3 grid grid-cols-3 gap-2">
|
||||||
|
<ExecutionMetric label="待确认" value={`${readiness.waitingForUser.pendingConfirmations ?? 0}`} />
|
||||||
|
<ExecutionMetric label="待我审" value={`${readiness.waitingForUser.pendingReviews ?? 0}`} />
|
||||||
|
<ExecutionMetric label="待验收" value={`${readiness.waitingForUser.waitingAcceptance ?? 0}`} />
|
||||||
|
</div>
|
||||||
|
{readiness.blockers.length ? (
|
||||||
|
<ul className="mt-3 space-y-1.5">
|
||||||
|
{readiness.blockers.slice(0, 3).map((blocker) => (
|
||||||
|
<li key={blocker} className="text-xs leading-5 text-amber-700">
|
||||||
|
{blocker}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExecutionModeCard({ mode, active }: { mode: PMFlowExecutionModeCapability; active: boolean }) {
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg border p-3",
|
||||||
|
active ? "border-[#bfd4ff] bg-[#f5f8ff]" : "border-[#e8eef8] bg-white",
|
||||||
|
mode.status === "blocked" && "border-rose-200 bg-rose-50"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<StatusChip label={readinessLabel(mode.status)} tone={modeTone(mode.status)} />
|
||||||
|
{active ? <span className="text-[11px] font-semibold text-[#2463EB]">当前建议</span> : null}
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 text-sm font-semibold text-slate-900">{mode.label}</div>
|
||||||
|
<p className="mt-1 text-xs leading-5 text-slate-500">{mode.reason}</p>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExecutionResultSummaryPanel({
|
||||||
|
summary,
|
||||||
|
capabilities,
|
||||||
|
}: {
|
||||||
|
summary: PMFlowExecutionSummary;
|
||||||
|
capabilities?: PMFlowExecutionCapabilitySnapshot | null;
|
||||||
|
}) {
|
||||||
|
const result = deriveExecutionResult(summary, capabilities);
|
||||||
|
return (
|
||||||
|
<section className="rounded-lg border border-[#dbe6ff] bg-white px-5 py-4 shadow-[0_1px_2px_rgba(15,23,42,0.03)]">
|
||||||
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-base font-semibold text-slate-900">
|
||||||
|
<ClipboardCheck className="h-4 w-4 text-[#2463EB]" />
|
||||||
|
本次执行结果
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-sm leading-6 text-slate-500">
|
||||||
|
汇总运行端、PR、测试、阻塞和验收信号;缺证据时停在待回写,不进入发布成功表达。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<StatusChip label={result.statusLabel} tone={result.tone} />
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||||
|
<ExecutionMetric label="运行端" value={result.runtimeLabel} />
|
||||||
|
<ExecutionMetric label="代码证据" value={result.codeEvidenceLabel} />
|
||||||
|
<ExecutionMetric label="测试证据" value={result.testEvidenceLabel} tone={result.testTone === "risk" ? "warning" : "default"} />
|
||||||
|
<ExecutionMetric label="下一步" value={result.nextAction} tone={result.tone === "risk" ? "warning" : "default"} />
|
||||||
|
</div>
|
||||||
|
{result.blocker ? (
|
||||||
|
<div className="mt-4 rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-sm leading-6 text-rose-700">{result.blocker}</div>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ExecutionPipelinePanel({ summary }: { summary: PMFlowExecutionSummary }) {
|
function ExecutionPipelinePanel({ summary }: { summary: PMFlowExecutionSummary }) {
|
||||||
const counts = summary.devSync;
|
const counts = summary.devSync;
|
||||||
const steps = buildPipelineSteps(summary);
|
const steps = buildPipelineSteps(summary);
|
||||||
|
|
@ -262,11 +522,13 @@ function PipelineStepCard({
|
||||||
|
|
||||||
function ContextPackagePanel({
|
function ContextPackagePanel({
|
||||||
summary,
|
summary,
|
||||||
|
capabilities,
|
||||||
onCreateHandoff,
|
onCreateHandoff,
|
||||||
onConsumeHandoff,
|
onConsumeHandoff,
|
||||||
onDemoPush,
|
onDemoPush,
|
||||||
}: {
|
}: {
|
||||||
summary: PMFlowExecutionSummary;
|
summary: PMFlowExecutionSummary;
|
||||||
|
capabilities?: PMFlowExecutionCapabilitySnapshot | null;
|
||||||
onCreateHandoff?: (payload: {
|
onCreateHandoff?: (payload: {
|
||||||
task_id: string;
|
task_id: string;
|
||||||
repo_binding_id?: string | null;
|
repo_binding_id?: string | null;
|
||||||
|
|
@ -277,20 +539,12 @@ function ContextPackagePanel({
|
||||||
onDemoPush?: () => void;
|
onDemoPush?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const prompt = summary.promptPackage;
|
const prompt = summary.promptPackage;
|
||||||
const handoffTask =
|
const handoffGate = buildHandoffGate(summary, capabilities);
|
||||||
summary.activeDevTasks.find((task) => task.id === summary.execution.taskId) || summary.activeDevTasks[0] || null;
|
const handoffTask = handoffGate.task;
|
||||||
const formalHandoffTask = isFormalDevTask(handoffTask) ? handoffTask : null;
|
const formalHandoffTask = handoffGate.formalTask;
|
||||||
const handoffRepo = handoffTask?.code_link?.repo || summary.repoBindings[0] || null;
|
const handoffRepo = handoffGate.repo;
|
||||||
const canCreateHandoff = Boolean(formalHandoffTask);
|
const canCreateHandoff = handoffGate.canCreate;
|
||||||
const handoffNextStep = !handoffTask
|
const handoffNextStep = handoffGate.reason;
|
||||||
? "暂无正式开发任务,先从项目记忆生成可执行任务。"
|
|
||||||
: !formalHandoffTask
|
|
||||||
? "当前任务仍是候选或草稿,需要先在待确认中心写入项目记忆。"
|
|
||||||
: !handoffRepo
|
|
||||||
? "可以生成交接草案,但仓库落点仍需补齐。"
|
|
||||||
: !prompt
|
|
||||||
? "生成开发上下文包后,运行端才能领取任务。"
|
|
||||||
: "复用当前上下文包,并记录新的执行交接起点。";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="rounded-lg border border-[#e2e9f5] bg-white shadow-[0_1px_2px_rgba(15,23,42,0.03)]">
|
<aside className="rounded-lg border border-[#e2e9f5] bg-white shadow-[0_1px_2px_rgba(15,23,42,0.03)]">
|
||||||
|
|
@ -350,8 +604,14 @@ function ContextPackagePanel({
|
||||||
<FieldRow label="任务" value={handoffTask?.title || "缺少正式任务"} />
|
<FieldRow label="任务" value={handoffTask?.title || "缺少正式任务"} />
|
||||||
<FieldRow label="状态" value={taskStatusLabel(handoffTask?.status || "missing")} />
|
<FieldRow label="状态" value={taskStatusLabel(handoffTask?.status || "missing")} />
|
||||||
<FieldRow label="仓库" value={handoffRepo?.full_name || "未绑定仓库"} />
|
<FieldRow label="仓库" value={handoffRepo?.full_name || "未绑定仓库"} />
|
||||||
<FieldRow label="运行端" value="本地桥" />
|
<FieldRow label="执行模式" value={handoffGate.mode?.label || "未同步"} />
|
||||||
|
<FieldRow label="运行端" value="本地桥 / Codex / GitHub Agent" />
|
||||||
</dl>
|
</dl>
|
||||||
|
{handoffGate.requiresTechnicalReview ? (
|
||||||
|
<div className="mt-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
|
||||||
|
该模式需要技术审查责任,运行端结果回写后仍需 reviewer 或用户验收,不能直接视为完成。
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -362,7 +622,7 @@ function ContextPackagePanel({
|
||||||
)}
|
)}
|
||||||
disabled={!canCreateHandoff}
|
disabled={!canCreateHandoff}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!formalHandoffTask) return;
|
if (!formalHandoffTask || !canCreateHandoff) return;
|
||||||
onCreateHandoff({
|
onCreateHandoff({
|
||||||
task_id: formalHandoffTask.id,
|
task_id: formalHandoffTask.id,
|
||||||
repo_binding_id: handoffRepo?.id || null,
|
repo_binding_id: handoffRepo?.id || null,
|
||||||
|
|
@ -372,7 +632,7 @@ function ContextPackagePanel({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Rocket className="h-4 w-4" />
|
<Rocket className="h-4 w-4" />
|
||||||
{handoffRepo ? "发起执行交接" : "生成交接草案"}
|
{handoffGate.actionLabel}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -769,9 +1029,16 @@ function WritebackInput({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AICopilotReviewPanel({ summary }: { summary: PMFlowExecutionSummary }) {
|
function AICopilotReviewPanel({
|
||||||
const insights = buildReviewInsights(summary);
|
summary,
|
||||||
const nextActions = buildNextActions(summary);
|
capabilities,
|
||||||
|
}: {
|
||||||
|
summary: PMFlowExecutionSummary;
|
||||||
|
capabilities?: PMFlowExecutionCapabilitySnapshot | null;
|
||||||
|
}) {
|
||||||
|
const insights = buildReviewInsights(summary, capabilities);
|
||||||
|
const nextActions = buildNextActions(summary, capabilities);
|
||||||
|
const result = deriveExecutionResult(summary, capabilities);
|
||||||
return (
|
return (
|
||||||
<aside className="rounded-lg border border-[#e2e9f5] bg-white shadow-[0_1px_2px_rgba(15,23,42,0.03)]">
|
<aside className="rounded-lg border border-[#e2e9f5] bg-white shadow-[0_1px_2px_rgba(15,23,42,0.03)]">
|
||||||
<div className="flex items-center justify-between border-b border-[#edf2f8] px-5 py-4">
|
<div className="flex items-center justify-between border-b border-[#edf2f8] px-5 py-4">
|
||||||
|
|
@ -783,7 +1050,7 @@ function AICopilotReviewPanel({ summary }: { summary: PMFlowExecutionSummary })
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4 p-5">
|
<div className="space-y-4 p-5">
|
||||||
<section className="rounded-lg border border-[#e8eef8] bg-[#fbfdff] p-4">
|
<section className="rounded-lg border border-[#e8eef8] bg-[#fbfdff] p-4">
|
||||||
<h3 className="text-sm font-semibold text-slate-900">审查重点</h3>
|
<h3 className="text-sm font-semibold text-slate-900">执行裁决重点</h3>
|
||||||
<div className="mt-3 space-y-3">
|
<div className="mt-3 space-y-3">
|
||||||
{insights.map((item) => (
|
{insights.map((item) => (
|
||||||
<div key={item.label} className="flex items-start justify-between gap-3">
|
<div key={item.label} className="flex items-start justify-between gap-3">
|
||||||
|
|
@ -799,7 +1066,10 @@ function AICopilotReviewPanel({ summary }: { summary: PMFlowExecutionSummary })
|
||||||
|
|
||||||
<section className="rounded-lg border border-[#e8eef8] bg-white p-4">
|
<section className="rounded-lg border border-[#e8eef8] bg-white p-4">
|
||||||
<h3 className="text-sm font-semibold text-slate-900">风险结论</h3>
|
<h3 className="text-sm font-semibold text-slate-900">风险结论</h3>
|
||||||
<p className="mt-2 text-sm leading-6 text-slate-500">{buildRiskConclusion(summary)}</p>
|
<p className="mt-2 text-sm leading-6 text-slate-500">{buildRiskConclusion(summary, capabilities)}</p>
|
||||||
|
<div className="mt-3 rounded-lg border border-[#e8eef8] bg-[#fbfdff] px-3 py-2 text-xs leading-5 text-slate-500">
|
||||||
|
当前结果:{result.statusLabel};下一步:{result.nextAction}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="rounded-lg border border-[#e8eef8] bg-white p-4">
|
<section className="rounded-lg border border-[#e8eef8] bg-white p-4">
|
||||||
|
|
@ -1012,21 +1282,30 @@ function buildPipelineSteps(summary: PMFlowExecutionSummary) {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildReviewInsights(summary: PMFlowExecutionSummary) {
|
function buildReviewInsights(summary: PMFlowExecutionSummary, capabilities?: PMFlowExecutionCapabilitySnapshot | null) {
|
||||||
const hasPrompt = Boolean(summary.promptPackage);
|
const hasPrompt = Boolean(summary.promptPackage);
|
||||||
const hasTask = summary.activeDevTasks.length > 0;
|
const hasTask = summary.activeDevTasks.length > 0;
|
||||||
const hasTest = summary.recentProgressEvents.some((event) => String(event.event_type).includes("test"));
|
const hasTest = summary.recentProgressEvents.some((event) => String(event.event_type).includes("test"));
|
||||||
const hasFailure = summary.execution.status === "failed" || summary.recentProgressEvents.some((event) => ["test_failed", "failed", "blocked"].includes(String(event.event_type)));
|
const hasFailure = summary.execution.status === "failed" || summary.recentProgressEvents.some((event) => ["test_failed", "failed", "blocked"].includes(String(event.event_type)));
|
||||||
const hasPr = summary.recentProgressEvents.some((event) => String(event.event_type).includes("pr") || event.pr_number);
|
const hasPr = summary.recentProgressEvents.some((event) => String(event.event_type).includes("pr") || event.pr_number);
|
||||||
|
const primary = primaryExecutionMode(capabilities);
|
||||||
|
const technicalReviewMode = capabilities?.executionModes.find((item) => item.mode === "ai_with_technical_review");
|
||||||
|
const waitingAcceptance = Number(capabilities?.personalReadiness.waitingForUser.waitingAcceptance ?? 0);
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: "需求覆盖",
|
label: "执行模式",
|
||||||
summary: hasPrompt && hasTask ? "任务、上下文包和项目记忆已形成可追溯关系。" : "还缺正式任务或上下文包,暂不能判断需求覆盖。",
|
summary: primary ? `${primary.label}:${primary.reason}` : "执行能力图谱未同步,只能依据执行摘要判断。",
|
||||||
|
status: primary ? readinessLabel(primary.status) : "未同步",
|
||||||
|
tone: primary ? modeTone(primary.status) : "muted" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "上下文覆盖",
|
||||||
|
summary: hasPrompt && hasTask ? "正式任务、上下文包和项目记忆已形成可追溯关系。" : "还缺正式任务或上下文包,暂不能判断需求覆盖。",
|
||||||
status: hasPrompt && hasTask ? "已通过" : "待补齐",
|
status: hasPrompt && hasTask ? "已通过" : "待补齐",
|
||||||
tone: hasPrompt && hasTask ? "success" as const : "warning" as const,
|
tone: hasPrompt && hasTask ? "success" as const : "warning" as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "代码质量",
|
label: "代码证据",
|
||||||
summary: hasPr ? "PR 或提交证据已回流,可进入代码审查。" : "暂无 PR 或提交证据,代码质量只能等待运行端回写。",
|
summary: hasPr ? "PR 或提交证据已回流,可进入代码审查。" : "暂无 PR 或提交证据,代码质量只能等待运行端回写。",
|
||||||
status: hasPr ? "可审查" : "待回写",
|
status: hasPr ? "可审查" : "待回写",
|
||||||
tone: hasPr ? "brand" as const : "muted" as const,
|
tone: hasPr ? "brand" as const : "muted" as const,
|
||||||
|
|
@ -1043,17 +1322,32 @@ function buildReviewInsights(summary: PMFlowExecutionSummary) {
|
||||||
status: summary.recentProgressEvents.length ? "已回写" : "等待中",
|
status: summary.recentProgressEvents.length ? "已回写" : "等待中",
|
||||||
tone: summary.recentProgressEvents.length ? "success" as const : "muted" as const,
|
tone: summary.recentProgressEvents.length ? "success" as const : "muted" as const,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "验收责任",
|
||||||
|
summary:
|
||||||
|
technicalReviewMode?.status === "ready"
|
||||||
|
? "当前更适合 AI 执行后绑定技术审查,不能把 agent 结果直接视为完成。"
|
||||||
|
: waitingAcceptance > 0
|
||||||
|
? "已有执行结果等待用户验收,先处理验收再进入发布门禁。"
|
||||||
|
: "暂未出现明确技术审查或用户验收阻塞。",
|
||||||
|
status: technicalReviewMode?.status === "ready" || waitingAcceptance > 0 ? "需处理" : "正常",
|
||||||
|
tone: technicalReviewMode?.status === "ready" || waitingAcceptance > 0 ? "warning" as const : "success" as const,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRiskConclusion(summary: PMFlowExecutionSummary) {
|
function buildRiskConclusion(summary: PMFlowExecutionSummary, capabilities?: PMFlowExecutionCapabilitySnapshot | null) {
|
||||||
|
const technicalReviewMode = capabilities?.executionModes.find((item) => item.mode === "ai_with_technical_review");
|
||||||
|
if (technicalReviewMode?.status === "blocked") return "当前执行需要技术审查责任,但 reviewer 尚未明确,不能发起真实执行交接。";
|
||||||
if (summary.execution.status === "failed") return "当前执行链路存在失败事件,发布门禁应保持阻塞,先定位失败原因并补充测试证据。";
|
if (summary.execution.status === "failed") return "当前执行链路存在失败事件,发布门禁应保持阻塞,先定位失败原因并补充测试证据。";
|
||||||
if (summary.warnings.length) return "当前主要风险来自执行准备缺口,补齐正式任务、仓库、上下文包和进展回写后再推进到 PR 或发布判断。";
|
if (summary.warnings.length) return "当前主要风险来自执行准备缺口,补齐正式任务、仓库、上下文包和进展回写后再推进到 PR 或发布判断。";
|
||||||
if (summary.execution.status === "synced") return "执行结果已回写,可进入验收或发布门禁判断,但仍需要保留 PR、测试和回滚证据。";
|
if (summary.execution.status === "synced") return "执行结果已回写,可进入验收或发布门禁判断,但仍需要保留 PR、测试和回滚证据。";
|
||||||
return "执行链路正在推进,建议持续观察运行端回写,并在 PR 与测试证据完整后再进入发布候选。";
|
return "执行链路正在推进,建议持续观察运行端回写,并在 PR 与测试证据完整后再进入发布候选。";
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildNextActions(summary: PMFlowExecutionSummary) {
|
function buildNextActions(summary: PMFlowExecutionSummary, capabilities?: PMFlowExecutionCapabilitySnapshot | null) {
|
||||||
|
const gate = buildHandoffGate(summary, capabilities);
|
||||||
|
if (!gate.canCreate && gate.actionLabel !== "发起执行交接") return [gate.reason, "按执行模式判定先补齐缺口,再回到执行桥继续交接。"];
|
||||||
if (!summary.activeDevTasks.length) return ["先从项目记忆形成正式开发任务。", "回到项目工作台确认任务来源和验收标准。"];
|
if (!summary.activeDevTasks.length) return ["先从项目记忆形成正式开发任务。", "回到项目工作台确认任务来源和验收标准。"];
|
||||||
if (!summary.repoBindings.length) return ["绑定代码仓库或确认 GitHub App 连接状态。", "仓库就绪后重新生成执行交接包。"];
|
if (!summary.repoBindings.length) return ["绑定代码仓库或确认 GitHub App 连接状态。", "仓库就绪后重新生成执行交接包。"];
|
||||||
if (!summary.promptPackage) return ["生成开发上下文包。", "确认上下文包包含需求、约束、验收标准和风险。"];
|
if (!summary.promptPackage) return ["生成开发上下文包。", "确认上下文包包含需求、约束、验收标准和风险。"];
|
||||||
|
|
@ -1062,6 +1356,96 @@ function buildNextActions(summary: PMFlowExecutionSummary) {
|
||||||
return ["继续观察 PR、测试和验收回写。", "证据完整后进入发布与增长门禁。"];
|
return ["继续观察 PR、测试和验收回写。", "证据完整后进入发布与增长门禁。"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deriveExecutionResult(summary: PMFlowExecutionSummary, capabilities?: PMFlowExecutionCapabilitySnapshot | null) {
|
||||||
|
const events = summary.recentProgressEvents;
|
||||||
|
const hasFailure = summary.execution.status === "failed" || events.some((event) => ["test_failed", "failed", "blocked"].includes(String(event.event_type)));
|
||||||
|
const hasAccepted = events.some((event) => String(event.event_type) === "accepted");
|
||||||
|
const hasTestPassed = events.some((event) => ["test_passed", "checks_passed"].includes(String(event.event_type)));
|
||||||
|
const hasPr = events.some((event) => String(event.event_type).includes("pr") || event.pr_number);
|
||||||
|
const hasCommit = events.some((event) => String(event.event_type).includes("commit") || event.commit_sha);
|
||||||
|
const lastEvent = events[0];
|
||||||
|
const runtimeLabel = sourceLabel(lastEvent?.source) || "等待运行端领取";
|
||||||
|
const technicalReviewMode = capabilities?.executionModes.find((item) => item.mode === "ai_with_technical_review");
|
||||||
|
const needsTechnicalReview = technicalReviewMode?.status === "ready";
|
||||||
|
const blocker = events.find((event) => ["test_failed", "failed", "blocked"].includes(String(event.event_type)))?.summary || null;
|
||||||
|
|
||||||
|
if (hasFailure) {
|
||||||
|
return {
|
||||||
|
statusLabel: "执行受阻",
|
||||||
|
tone: "risk" as const,
|
||||||
|
runtimeLabel,
|
||||||
|
codeEvidenceLabel: hasPr ? "PR 已回流" : hasCommit ? "提交已回流" : "缺代码证据",
|
||||||
|
testEvidenceLabel: summary.execution.testSummary && summary.execution.testSummary !== "pending" ? normalizePending(summary.execution.testSummary) : "失败或阻塞",
|
||||||
|
testTone: "risk" as const,
|
||||||
|
nextAction: "修复后回写",
|
||||||
|
blocker: blocker || "存在失败或阻塞事件,需要先补充原因、影响和下一步。",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasAccepted) {
|
||||||
|
return {
|
||||||
|
statusLabel: "已验收",
|
||||||
|
tone: "success" as const,
|
||||||
|
runtimeLabel,
|
||||||
|
codeEvidenceLabel: hasPr ? "PR 已回流" : hasCommit ? "提交已回流" : "缺代码证据",
|
||||||
|
testEvidenceLabel: hasTestPassed ? "测试通过" : "测试证据不足",
|
||||||
|
testTone: hasTestPassed ? "success" as const : "warning" as const,
|
||||||
|
nextAction: "进入发布门禁",
|
||||||
|
blocker: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hasTestPassed || summary.execution.status === "synced") {
|
||||||
|
return {
|
||||||
|
statusLabel: needsTechnicalReview ? "待技术审查" : "待用户验收",
|
||||||
|
tone: "warning" as const,
|
||||||
|
runtimeLabel,
|
||||||
|
codeEvidenceLabel: hasPr ? "PR 已回流" : hasCommit ? "提交已回流" : "缺代码证据",
|
||||||
|
testEvidenceLabel: hasTestPassed ? "测试通过" : normalizePending(summary.execution.testSummary),
|
||||||
|
testTone: hasTestPassed ? "success" as const : "warning" as const,
|
||||||
|
nextAction: needsTechnicalReview ? "技术审查" : "用户验收",
|
||||||
|
blocker: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (events.length) {
|
||||||
|
return {
|
||||||
|
statusLabel: "执行中",
|
||||||
|
tone: "brand" as const,
|
||||||
|
runtimeLabel,
|
||||||
|
codeEvidenceLabel: hasPr ? "PR 已回流" : hasCommit ? "提交已回流" : "等待 PR/提交",
|
||||||
|
testEvidenceLabel: "等待测试",
|
||||||
|
testTone: "warning" as const,
|
||||||
|
nextAction: "继续回写证据",
|
||||||
|
blocker: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
statusLabel: "待回写",
|
||||||
|
tone: "muted" as const,
|
||||||
|
runtimeLabel: "未领取",
|
||||||
|
codeEvidenceLabel: "缺代码证据",
|
||||||
|
testEvidenceLabel: "缺测试证据",
|
||||||
|
testTone: "warning" as const,
|
||||||
|
nextAction: "发起交接",
|
||||||
|
blocker: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readinessLabel(status?: CapabilityTone | string | null) {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
ready: "就绪",
|
||||||
|
partial: "待补齐",
|
||||||
|
blocked: "阻塞",
|
||||||
|
unknown: "未知",
|
||||||
|
};
|
||||||
|
return status ? map[status] || status : "未知";
|
||||||
|
}
|
||||||
|
|
||||||
|
function modeTone(status?: CapabilityTone | string | null): "brand" | "success" | "warning" | "risk" | "muted" {
|
||||||
|
if (status === "ready") return "success";
|
||||||
|
if (status === "partial") return "warning";
|
||||||
|
if (status === "blocked") return "risk";
|
||||||
|
return "muted";
|
||||||
|
}
|
||||||
|
|
||||||
function eventIcon(eventType: string) {
|
function eventIcon(eventType: string) {
|
||||||
const lower = eventType.toLowerCase();
|
const lower = eventType.toLowerCase();
|
||||||
if (lower.includes("pr") || lower.includes("pull")) return <GitPullRequest className="h-4 w-4 text-[#2463EB]" />;
|
if (lower.includes("pr") || lower.includes("pull")) return <GitPullRequest className="h-4 w-4 text-[#2463EB]" />;
|
||||||
|
|
@ -1329,3 +1713,79 @@ function createDemoExecutionSummary(state: ReturnType<typeof usePMFlowOsStore>["
|
||||||
warnings: [],
|
warnings: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createDemoExecutionCapabilities(summary: PMFlowExecutionSummary): PMFlowExecutionCapabilitySnapshot {
|
||||||
|
const hasRepo = summary.repoBindings.length > 0;
|
||||||
|
const hasPrompt = Boolean(summary.promptPackage);
|
||||||
|
const hasProgress = summary.recentProgressEvents.length > 0;
|
||||||
|
return {
|
||||||
|
projectId: summary.projectId,
|
||||||
|
executionModes: [
|
||||||
|
{
|
||||||
|
mode: "ai_direct",
|
||||||
|
label: "AI 直接执行",
|
||||||
|
status: hasRepo && hasPrompt ? "ready" : "partial",
|
||||||
|
reason: "演示模式下仅展示执行模式,不触发真实运行端。",
|
||||||
|
targetHref: `/execution-bridge?projectId=${summary.projectId}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: "ai_with_user_acceptance",
|
||||||
|
label: "AI 执行 + 用户验收",
|
||||||
|
status: hasRepo ? "ready" : "blocked",
|
||||||
|
reason: "适合低风险改动,执行结果仍需要用户验收。",
|
||||||
|
targetHref: `/execution-bridge?projectId=${summary.projectId}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: "ai_with_technical_review",
|
||||||
|
label: "AI 执行 + 技术审查",
|
||||||
|
status: "partial",
|
||||||
|
reason: "涉及架构、权限、数据或发布门禁时需要技术审查。",
|
||||||
|
targetHref: `/team-layer?projectId=${summary.projectId}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: "needs_human_judgement",
|
||||||
|
label: "需要人类先补判断",
|
||||||
|
status: "partial",
|
||||||
|
reason: "目标、范围或验收标准不清时先回到待确认中心。",
|
||||||
|
targetHref: `/change-review?projectId=${summary.projectId}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: "blocked",
|
||||||
|
label: "当前不可执行",
|
||||||
|
status: hasRepo && hasPrompt && hasProgress ? "unknown" : "ready",
|
||||||
|
reason: "缺上下文包、运行端领取或回写证据时,必须显式阻塞。",
|
||||||
|
targetHref: `/execution-bridge?projectId=${summary.projectId}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
personalReadiness: {
|
||||||
|
status: hasRepo && hasPrompt ? "ready" : "partial",
|
||||||
|
toolReadiness: [
|
||||||
|
{
|
||||||
|
key: "repo",
|
||||||
|
label: "Repo 连接",
|
||||||
|
status: hasRepo ? "ready" : "blocked",
|
||||||
|
summary: hasRepo ? "演示仓库已就绪。" : "缺少 repo。",
|
||||||
|
href: `/execution-bridge?projectId=${summary.projectId}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "prompt_package",
|
||||||
|
label: "执行 Prompt Package",
|
||||||
|
status: hasPrompt ? "ready" : "partial",
|
||||||
|
summary: hasPrompt ? "已有演示上下文包。" : "尚未生成上下文包。",
|
||||||
|
href: `/execution-bridge?projectId=${summary.projectId}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
waitingForUser: {
|
||||||
|
pendingConfirmations: 0,
|
||||||
|
pendingReviews: 0,
|
||||||
|
waitingAcceptance: hasProgress ? 1 : 0,
|
||||||
|
},
|
||||||
|
blockers: hasPrompt ? [] : ["尚未生成上下文包。"],
|
||||||
|
recommendedNextStep: hasPrompt ? "可以推进演示执行状态。" : "先生成或确认执行上下文包。",
|
||||||
|
targetHref: `/execution-bridge?projectId=${summary.projectId}`,
|
||||||
|
},
|
||||||
|
teamSignals: [],
|
||||||
|
adaptiveCapacitySummary: null,
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import {
|
||||||
import { createInitialPMFlowState } from "@/features/pmflow-os/mockData";
|
import { createInitialPMFlowState } from "@/features/pmflow-os/mockData";
|
||||||
import CommandCenterPage from "@/features/pmflow-os/pages/CommandCenterPage";
|
import CommandCenterPage from "@/features/pmflow-os/pages/CommandCenterPage";
|
||||||
import ChangeReviewPage from "@/features/pmflow-os/pages/ChangeReviewPage";
|
import ChangeReviewPage from "@/features/pmflow-os/pages/ChangeReviewPage";
|
||||||
|
import ExecutionBridgePage from "@/features/pmflow-os/pages/ExecutionBridgePage";
|
||||||
import StructuredExtractionPage from "@/features/pmflow-os/pages/StructuredExtractionPage";
|
import StructuredExtractionPage from "@/features/pmflow-os/pages/StructuredExtractionPage";
|
||||||
import TeamLayerPage from "@/features/pmflow-os/pages/TeamLayerPage";
|
import TeamLayerPage from "@/features/pmflow-os/pages/TeamLayerPage";
|
||||||
import WorkGraphPage from "@/features/pmflow-os/pages/WorkGraphPage";
|
import WorkGraphPage from "@/features/pmflow-os/pages/WorkGraphPage";
|
||||||
|
|
@ -704,6 +705,72 @@ describe("PMFlowOsProvider real change review actions", () => {
|
||||||
expect(screen.queryByText(/Team Layer/)).not.toBeInTheDocument();
|
expect(screen.queryByText(/Team Layer/)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders execution capability mode and result summary on execution bridge", async () => {
|
||||||
|
getExecutionCapabilitiesMock.mockResolvedValueOnce({
|
||||||
|
projectId: "123",
|
||||||
|
executionModes: [
|
||||||
|
{
|
||||||
|
mode: "ai_direct",
|
||||||
|
label: "AI 直接执行",
|
||||||
|
status: "ready",
|
||||||
|
reason: "正式任务、仓库和上下文包已就绪。",
|
||||||
|
targetHref: "/execution-bridge?projectId=123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: "ai_with_user_acceptance",
|
||||||
|
label: "AI 执行 + 用户验收",
|
||||||
|
status: "ready",
|
||||||
|
reason: "执行后需要用户验收。",
|
||||||
|
targetHref: "/execution-bridge?projectId=123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: "ai_with_technical_review",
|
||||||
|
label: "AI 执行 + 技术审查",
|
||||||
|
status: "partial",
|
||||||
|
reason: "高风险时需要 reviewer。",
|
||||||
|
targetHref: "/team-layer?projectId=123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: "needs_human_judgement",
|
||||||
|
label: "需要人类先补判断",
|
||||||
|
status: "partial",
|
||||||
|
reason: "范围不清时先回待确认。",
|
||||||
|
targetHref: "/change-review?projectId=123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: "blocked",
|
||||||
|
label: "当前不可执行",
|
||||||
|
status: "unknown",
|
||||||
|
reason: "缺条件才阻塞。",
|
||||||
|
targetHref: "/execution-bridge?projectId=123",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
personalReadiness: {
|
||||||
|
status: "ready",
|
||||||
|
toolReadiness: [],
|
||||||
|
waitingForUser: { pendingConfirmations: 0, pendingReviews: 0, waitingAcceptance: 0 },
|
||||||
|
blockers: [],
|
||||||
|
recommendedNextStep: "可以继续生成或验收 AI 执行包。",
|
||||||
|
targetHref: "/execution-bridge?projectId=123",
|
||||||
|
},
|
||||||
|
teamSignals: [],
|
||||||
|
warnings: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ExecutionBridgePage />
|
||||||
|
</MemoryRouter>,
|
||||||
|
{ wrapper: wrapper() }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await screen.findByText("执行模式判定")).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText("AI 直接执行").length).toBeGreaterThan(0);
|
||||||
|
expect(screen.getByText("本次执行结果")).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText("发起执行交接").length).toBeGreaterThan(0);
|
||||||
|
expect(screen.getByText("正式任务、仓库和上下文包已就绪。")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("renders Change Review from real projection and does not show demo fallback rows", async () => {
|
it("renders Change Review from real projection and does not show demo fallback rows", async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
|
|
@ -1198,6 +1265,44 @@ describe("PMFlowOsProvider real change review actions", () => {
|
||||||
await waitFor(() => expect(result.current.dataSourceError).toMatch(/正式任务/));
|
await waitFor(() => expect(result.current.dataSourceError).toMatch(/正式任务/));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("blocks execution handoff when technical review responsibility is missing", async () => {
|
||||||
|
getExecutionCapabilitiesMock.mockResolvedValueOnce({
|
||||||
|
projectId: "123",
|
||||||
|
executionModes: [
|
||||||
|
{
|
||||||
|
mode: "ai_with_technical_review",
|
||||||
|
label: "AI 执行 + 技术审查",
|
||||||
|
status: "blocked",
|
||||||
|
reason: "缺少 reviewer。",
|
||||||
|
targetHref: "/team-layer?projectId=123",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
personalReadiness: {
|
||||||
|
status: "partial",
|
||||||
|
toolReadiness: [],
|
||||||
|
waitingForUser: { pendingConfirmations: 0, pendingReviews: 0, waitingAcceptance: 0 },
|
||||||
|
blockers: [],
|
||||||
|
recommendedNextStep: "先补齐 reviewer。",
|
||||||
|
targetHref: "/team-layer?projectId=123",
|
||||||
|
},
|
||||||
|
teamSignals: [],
|
||||||
|
warnings: ["reviewer_missing: 存在待审 ChangeSet 缺少 reviewer/owner。"],
|
||||||
|
});
|
||||||
|
const { result } = renderHook(() => usePMFlowOsStore(), { wrapper: wrapper() });
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.executionCapabilitiesStatus).toBe("success"));
|
||||||
|
act(() =>
|
||||||
|
result.current.createExecutionHandoff({
|
||||||
|
task_id: "task-99",
|
||||||
|
repo_binding_id: "601",
|
||||||
|
runtime_target: "local_bridge",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(createExecutionHandoffMock).not.toHaveBeenCalled();
|
||||||
|
await waitFor(() => expect(result.current.dataSourceError).toMatch(/技术审查责任/));
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps reviewer routing empty when payload omits it", async () => {
|
it("keeps reviewer routing empty when payload omits it", async () => {
|
||||||
const state = realState();
|
const state = realState();
|
||||||
delete state.reviewerRouting;
|
delete state.reviewerRouting;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
createPMFlowThreadMessage,
|
createPMFlowThreadMessage,
|
||||||
deferPMFlowChangeItem,
|
deferPMFlowChangeItem,
|
||||||
editPMFlowChangeItem,
|
editPMFlowChangeItem,
|
||||||
|
getPMFlowExecutionCapabilities,
|
||||||
getPMFlowExecutionSummary,
|
getPMFlowExecutionSummary,
|
||||||
getPMFlowTruthState,
|
getPMFlowTruthState,
|
||||||
type PMFlowExecutionHandoffRequest,
|
type PMFlowExecutionHandoffRequest,
|
||||||
|
|
@ -33,6 +34,7 @@ import { createInitialPMFlowState } from "@/features/pmflow-os/mockData";
|
||||||
import type {
|
import type {
|
||||||
ChangeSet,
|
ChangeSet,
|
||||||
PMFlowAIFeedbackEvent,
|
PMFlowAIFeedbackEvent,
|
||||||
|
PMFlowExecutionCapabilitySnapshot,
|
||||||
PMFlowExecutionStatus,
|
PMFlowExecutionStatus,
|
||||||
ProjectTruthState,
|
ProjectTruthState,
|
||||||
Release,
|
Release,
|
||||||
|
|
@ -646,8 +648,12 @@ interface PMFlowOsStoreValue {
|
||||||
executionSummary: PMFlowExecutionSummary | null;
|
executionSummary: PMFlowExecutionSummary | null;
|
||||||
executionSummaryStatus: "idle" | "loading" | "success" | "error";
|
executionSummaryStatus: "idle" | "loading" | "success" | "error";
|
||||||
executionSummaryError: string | null;
|
executionSummaryError: string | null;
|
||||||
|
executionCapabilities: PMFlowExecutionCapabilitySnapshot | null;
|
||||||
|
executionCapabilitiesStatus: "idle" | "loading" | "success" | "error";
|
||||||
|
executionCapabilitiesError: string | null;
|
||||||
refetchTruthState: () => void;
|
refetchTruthState: () => void;
|
||||||
refetchExecutionSummary: () => void;
|
refetchExecutionSummary: () => void;
|
||||||
|
refetchExecutionCapabilities: () => void;
|
||||||
sendMessage: (content: string) => void;
|
sendMessage: (content: string) => void;
|
||||||
generateExtraction: (proposedItems?: PMFlowProposedChangeItem[]) => void;
|
generateExtraction: (proposedItems?: PMFlowProposedChangeItem[]) => void;
|
||||||
skipExtractionRound: (summary?: string) => void;
|
skipExtractionRound: (summary?: string) => void;
|
||||||
|
|
@ -704,6 +710,11 @@ export function PMFlowOsProvider({ children }: { children: ReactNode }) {
|
||||||
queryFn: () => getPMFlowExecutionSummary(dataSource.projectId || ""),
|
queryFn: () => getPMFlowExecutionSummary(dataSource.projectId || ""),
|
||||||
enabled: dataSource.mode === "api" && Boolean(dataSource.projectId),
|
enabled: dataSource.mode === "api" && Boolean(dataSource.projectId),
|
||||||
});
|
});
|
||||||
|
const executionCapabilitiesQuery = useQuery({
|
||||||
|
queryKey: ["pmflow-os", "execution-capabilities", dataSource.projectId],
|
||||||
|
queryFn: () => getPMFlowExecutionCapabilities(dataSource.projectId || ""),
|
||||||
|
enabled: dataSource.mode === "api" && Boolean(dataSource.projectId),
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReadError(null);
|
setReadError(null);
|
||||||
|
|
@ -763,8 +774,10 @@ export function PMFlowOsProvider({ children }: { children: ReactNode }) {
|
||||||
if (dataSource.mode !== "api") return;
|
if (dataSource.mode !== "api") return;
|
||||||
void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "truth-state", dataSource.projectId] });
|
void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "truth-state", dataSource.projectId] });
|
||||||
void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "execution-summary", dataSource.projectId] });
|
void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "execution-summary", dataSource.projectId] });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "execution-capabilities", dataSource.projectId] });
|
||||||
void truthStateQuery.refetch();
|
void truthStateQuery.refetch();
|
||||||
void executionSummaryQuery.refetch();
|
void executionSummaryQuery.refetch();
|
||||||
|
void executionCapabilitiesQuery.refetch();
|
||||||
};
|
};
|
||||||
const runApiAction = (
|
const runApiAction = (
|
||||||
action: () => Promise<unknown> | unknown,
|
action: () => Promise<unknown> | unknown,
|
||||||
|
|
@ -791,7 +804,7 @@ export function PMFlowOsProvider({ children }: { children: ReactNode }) {
|
||||||
dataSourceStatus:
|
dataSourceStatus:
|
||||||
dataSource.mode === "empty"
|
dataSource.mode === "empty"
|
||||||
? "idle"
|
? "idle"
|
||||||
: truthStateQuery.isLoading || truthStateQuery.isFetching || executionSummaryQuery.isFetching
|
: truthStateQuery.isLoading || truthStateQuery.isFetching || executionSummaryQuery.isFetching || executionCapabilitiesQuery.isFetching
|
||||||
? "loading"
|
? "loading"
|
||||||
: readError
|
: readError
|
||||||
? "error"
|
? "error"
|
||||||
|
|
@ -816,7 +829,26 @@ export function PMFlowOsProvider({ children }: { children: ReactNode }) {
|
||||||
: executionSummaryQuery.error instanceof Error
|
: executionSummaryQuery.error instanceof Error
|
||||||
? executionSummaryQuery.error.message
|
? executionSummaryQuery.error.message
|
||||||
: executionSummaryQuery.error
|
: executionSummaryQuery.error
|
||||||
? "PMFlow OS 执行摘要读取失败"
|
? "PMFlow OS 执行摘要读取失败"
|
||||||
|
: null,
|
||||||
|
executionCapabilities: dataSource.mode === "api" ? executionCapabilitiesQuery.data ?? null : null,
|
||||||
|
executionCapabilitiesStatus:
|
||||||
|
dataSource.mode === "empty"
|
||||||
|
? "idle"
|
||||||
|
: dataSource.mode === "demo"
|
||||||
|
? "success"
|
||||||
|
: executionCapabilitiesQuery.isLoading
|
||||||
|
? "loading"
|
||||||
|
: executionCapabilitiesQuery.error
|
||||||
|
? "error"
|
||||||
|
: "success",
|
||||||
|
executionCapabilitiesError:
|
||||||
|
dataSource.mode !== "api"
|
||||||
|
? null
|
||||||
|
: executionCapabilitiesQuery.error instanceof Error
|
||||||
|
? executionCapabilitiesQuery.error.message
|
||||||
|
: executionCapabilitiesQuery.error
|
||||||
|
? "PMFlow OS 执行能力读取失败"
|
||||||
: null,
|
: null,
|
||||||
refetchTruthState: () => {
|
refetchTruthState: () => {
|
||||||
if (dataSource.mode === "api") refetchApiState();
|
if (dataSource.mode === "api") refetchApiState();
|
||||||
|
|
@ -826,6 +858,11 @@ export function PMFlowOsProvider({ children }: { children: ReactNode }) {
|
||||||
void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "execution-summary", dataSource.projectId] });
|
void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "execution-summary", dataSource.projectId] });
|
||||||
void executionSummaryQuery.refetch();
|
void executionSummaryQuery.refetch();
|
||||||
},
|
},
|
||||||
|
refetchExecutionCapabilities: () => {
|
||||||
|
if (dataSource.mode !== "api") return;
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "execution-capabilities", dataSource.projectId] });
|
||||||
|
void executionCapabilitiesQuery.refetch();
|
||||||
|
},
|
||||||
sendMessage: (content) => {
|
sendMessage: (content) => {
|
||||||
if (dataSource.mode === "api" && dataSource.projectId) {
|
if (dataSource.mode === "api" && dataSource.projectId) {
|
||||||
setReadError(null);
|
setReadError(null);
|
||||||
|
|
@ -988,6 +1025,13 @@ export function PMFlowOsProvider({ children }: { children: ReactNode }) {
|
||||||
setReadError("执行交接只能针对已写入项目记忆后的正式任务;候选任务或待确认变更不能直接分派执行。");
|
setReadError("执行交接只能针对已写入项目记忆后的正式任务;候选任务或待确认变更不能直接分派执行。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const capability = executionCapabilitiesQuery.data;
|
||||||
|
const technicalReviewMode = capability?.executionModes.find((item) => item.mode === "ai_with_technical_review");
|
||||||
|
const reviewerMissing = technicalReviewMode?.status === "blocked" || capability?.warnings.some((item) => item.includes("reviewer_missing"));
|
||||||
|
if (reviewerMissing) {
|
||||||
|
setReadError("当前执行模式需要技术审查责任,但 reviewer 尚未明确;请先回到团队层或待确认中心补齐审查责任。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
runApiAction(() => createPMFlowExecutionHandoff(dataSource.projectId || "", payload));
|
runApiAction(() => createPMFlowExecutionHandoff(dataSource.projectId || "", payload));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue