from app.schemas.pmflow_ai_agent_runtime import ( PMFlowAIAgentActionCandidateLedgerEntry, PMFlowAIAgentClaim, PMFlowAIAgentCriticResult, PMFlowAIAgentGoalFrame, PMFlowAIAgentStepProposal, PMFlowAIAgentWorkspace, ) from app.services.pmflow_ai_agent_runtime.agent_answer_runtime import build_agent_terminal_answer_state from app.services.pmflow_ai_agent_runtime.agent_deliverable_runtime import PMFlowAIAgentDeliverableRuntime from app.services.pmflow_ai_agent_runtime.agent_payload_gate import build_agent_answer_canvas def test_goal_authored_project_launch_affordance_reaches_customer_canvas() -> None: question = "我想做一个帮助独立开发者管理产品验证和变现推进的工具" workspace = PMFlowAIAgentWorkspace( current_goal=question, goal_frame=PMFlowAIAgentGoalFrame( user_goal=question, desired_output="给出可执行的产品推进蓝图,并能进入 PMFlow 下一步确认链。", source_policy="internal_customer_safe", freshness_policy="not_required", answer_objective={"user_success": "拿到可落地的 MVP 推进方向"}, dynamic_rubric=[ { "criterion": "回答必须覆盖目标用户、MVP 边界、验证动作和 PMFlow 承接动作", "why_it_matters": "用户要从想法进入执行", "evidence_expectation": "受控假设即可", } ], deliverable_shape={ "primary_shape": "产品执行蓝图", "must_include": ["MVP 边界", "验证动作", "PMFlow 下一步"], "artifact_slots": [ { "slot_key": "mvp_scope", "display_goal": "明确首版 MVP 的边界", "must_cover": ["核心闭环", "先不做什么"], "depth": "standard", } ], "pmflow_system_actions": [ { "capability_key": "project_launch_draft", "title": "生成项目草案待确认", "summary": "本轮方向已经可以进入 Project Launch 生成结构化草案。", "input_seed": question, "completion_criteria": "看到可审查的草案后再决定是否创建项目。", } ], }, ), claim_ledger=[ PMFlowAIAgentClaim( claim_type="controlled_hypothesis_basis", claim_text="可以先把这个方向收敛成一个待验证的产品执行蓝图,再进入 PMFlow 草案确认链。", customer_value="用户可以从想法进入结构化推进。", confidence="medium", allowed_to_state=True, relevance_to_user_goal="direct", claim_kind="directional_judgement", ) ], ) terminal = build_agent_terminal_answer_state( question=question, workspace=workspace, proposal=PMFlowAIAgentStepProposal(action="answer"), observation=None, critic_result=PMFlowAIAgentCriticResult(ready_to_answer=True, retry_would_change_state=False, recover_or_continue="compose_answer"), ) assert terminal.action_candidates candidate = terminal.action_candidates[0] assert candidate.action_type == "create_draft" assert candidate.safety == "prepare_only" assert candidate.requires_confirmation is True assert candidate.href == "/project-launch?source=pmflow-ai" assert candidate.action_payload["input"] == question assert candidate.action_payload["launch_seed"]["packet_version"] == "pmflow_project_launch_seed.v1" assert candidate.action_payload["launch_seed"]["source_contract"] == "same_understanding_materialization" assert candidate.action_payload["launch_seed"]["materialization_policy"]["do_not_reinterpret_as_new_idea"] is True assert candidate.action_payload["launch_seed"]["customer_safe_claims"][0]["claim_text"].startswith("可以先把这个方向") assert len(candidate.href) <= 300 deliverable = PMFlowAIAgentDeliverableRuntime().build( question=question, workspace=workspace, proposal=PMFlowAIAgentStepProposal(action="answer"), observation=None, critic_result=PMFlowAIAgentCriticResult(ready_to_answer=True, retry_would_change_state=False, recover_or_continue="compose_answer"), terminal_state=terminal, answer_text="这个方向可以先做成一个围绕验证和变现推进的 MVP,并进入 PMFlow 生成项目草案待确认。", ) canvas = build_agent_answer_canvas(answer=deliverable.answer_text, workspace=workspace, terminal_state=terminal, deliverable_packet=deliverable) assert deliverable.artifacts assert deliverable.artifacts[0].artifact_kind == "product_blueprint" assert deliverable.artifacts[0].slots[0]["title"] == "明确首版 MVP 的边界" assert canvas["artifacts"][0]["artifact_kind"] == "product_blueprint" assert canvas["artifacts"][0]["slots"][0]["title"] == "明确首版 MVP 的边界" assert canvas["action_candidates"][0]["action_type"] == "create_draft" assert canvas["action_candidates"][0]["href"] == "/project-launch?source=pmflow-ai" assert canvas["action_candidates"][0]["action_payload"]["input"] == question assert canvas["action_candidates"][0]["action_payload"]["launch_seed"]["idea_input"] == question def test_goal_authored_project_launch_action_wins_over_llm_generated_href() -> None: question = "我想做个AI漫剧的工具" workspace = PMFlowAIAgentWorkspace( current_goal=question, goal_frame=PMFlowAIAgentGoalFrame( user_goal=question, desired_output="生成可确认的项目草案入口。", deliverable_shape={ "pmflow_system_actions": [ { "capability_key": "project_launch_draft", "title": "启动“AI漫剧工具”项目草案", "summary": "进入 Project Launch 生成待确认草案。", "input_seed": question, } ] }, ), action_candidate_ledger=[ PMFlowAIAgentActionCandidateLedgerEntry( title="启动“AI漫剧工具”项目草案", summary="模型建议进入项目草案。", action_type="navigate", href="/pmflow/项目列表/new?seed=ai_comic_tool_draft", safety="prepare_only", ) ], ) terminal = build_agent_terminal_answer_state( question=question, workspace=workspace, proposal=PMFlowAIAgentStepProposal(action="answer"), observation=None, critic_result=PMFlowAIAgentCriticResult(ready_to_answer=True, retry_would_change_state=False, recover_or_continue="compose_answer"), ) deliverable = PMFlowAIAgentDeliverableRuntime().build( question=question, workspace=workspace, proposal=PMFlowAIAgentStepProposal(action="answer"), observation=None, critic_result=PMFlowAIAgentCriticResult(ready_to_answer=True, retry_would_change_state=False, recover_or_continue="compose_answer"), terminal_state=terminal, answer_text="可以进入 PMFlow 生成项目草案待确认。", ) canvas = build_agent_answer_canvas(answer=deliverable.answer_text, workspace=workspace, terminal_state=terminal, deliverable_packet=deliverable) assert canvas["action_candidates"][0]["action_type"] == "create_draft" assert canvas["action_candidates"][0]["href"] == "/project-launch?source=pmflow-ai" assert canvas["action_candidates"][0]["action_payload"]["input"] == question assert canvas["action_candidates"][0]["action_payload"]["launch_seed"]["source"] == "space_ai_turn"