import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { acceptPMFlowChangeItem, commitPMFlowChangeSet, consumePMFlowExecutionHandoff, createPMFlowExecutionHandoff, createPMFlowExecutionProgressEvent, createPMFlowExtraction, createPMFlowIterationRecommendationGroupChangeSetDraft, createPMFlowIterationRecommendationChangeSetDraft, createPMFlowReleaseFeedbackIngest, createPMFlowReleaseValidationRun, createPMFlowThreadMessage, deferPMFlowChangeItem, editPMFlowChangeItem, getPMFlowExecutionSummary, getPMFlowTruthState, type PMFlowExecutionHandoffRequest, type PMFlowExecutionProgressEventRequest, type PMFlowExecutionSummary, type PMFlowIterationRecommendationChangeSetDraftRequest, type PMFlowIterationRecommendationGroupChangeSetDraftRequest, type PMFlowProposedChangeItem, type PMFlowReleaseFeedbackIngestRequest, type PMFlowReleaseValidationRunRequest, } from "@/api/pmflowOs"; import { createInitialPMFlowState } from "@/features/pmflow-os/mockData"; import type { ChangeSet, PMFlowExecutionStatus, ProjectTruthState, Release, } from "@/features/pmflow-os/types"; function nextId(prefix: string) { return `${prefix}-${Math.random().toString(36).slice(2, 8)}`; } function nowLabel() { const now = new Date(); return `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`; } function addFeed(state: ProjectTruthState, title: string, summary: string, kind: ProjectTruthState["feed"][number]["kind"]) { state.feed = [ { id: nextId("feed"), kind, title, summary, timestamp: nowLabel(), }, ...state.feed, ]; } function createEmptyPMFlowState(projectId: string | null, label = "未选择项目"): ProjectTruthState { const id = projectId || "unselected-project"; const threadId = `thread-${id}`; return { loopStage: "command_idle", lastWritebackAt: null, project: { id, name: label, goal: projectId ? "正在读取后端项目真相投影。" : "URL 缺少 projectId,未进入真实项目读取。", stage: "idea", threadId, }, thread: { id: threadId, title: "PMFlow OS 只读项目线程", projectId: id, objective: projectId ? "从后端读取真实 ProjectTruthState。" : "等待选择真实项目。", extractionStatus: "idle", messageIds: [], }, messages: [], changeSets: [], changeItems: [], evidence: [], requirements: [], decisions: [], tasks: [], risks: [], modelDecisions: [], testRuns: [], releases: [], metricSignals: [], approvals: [], feed: [], teamInbox: { product: [], engineering: [], qa: [], growth: [], }, activeChangeSetId: null, selectedTaskId: null, execution: { taskId: null, status: "queued", prompt: "", commitLog: [], prStatus: "pending", testSummary: "pending", writebackStatus: "pending", }, }; } function parsePMFlowOsDataSource() { const params = new URLSearchParams(window.location.search); const projectId = params.get("projectId") || params.get("project_id"); const demo = params.get("demo") === "1" || params.get("dataSource") === "demo"; if (demo) { return { mode: "demo" as const, projectId }; } if (projectId && projectId.trim()) { return { mode: "api" as const, projectId: projectId.trim() }; } return { mode: "empty" as const, projectId: null }; } function cloneState(state: ProjectTruthState): ProjectTruthState { return JSON.parse(JSON.stringify(state)) as ProjectTruthState; } function executionStatusLabel(status: PMFlowExecutionStatus) { const labelMap: Record = { queued: "待推送", pushed: "已推送", coding: "开发中", pr_open: "PR 审查中", testing: "测试中", synced: "已回写", failed: "失败", }; return labelMap[status]; } export function sendMessageMutation(state: ProjectTruthState, content: string): ProjectTruthState { const next = cloneState(state); next.loopStage = "ai_extracting"; const messageId = nextId("msg"); next.messages.push({ id: messageId, role: "user", author: "你", content, createdAt: nowLabel(), status: "sent", }); next.thread.messageIds.push(messageId); next.thread.extractionStatus = "loading"; next.messages.push({ id: nextId("msg"), role: "assistant", author: "PMFlow", content: "正在拆分本轮结构化对象和高风险变更…", createdAt: nowLabel(), status: "analyzing", }); return next; } export function generateExtractionMutation(state: ProjectTruthState): ProjectTruthState { const next = cloneState(state); next.loopStage = "extraction_ready"; const changeSetId = nextId("cs"); const requirementId = nextId("req"); const riskId = nextId("risk"); const decisionId = nextId("decision"); const taskId = nextId("task"); const conversationEvidenceId = nextId("ev"); const lastUserMessage = [...next.messages].reverse().find((message) => message.role === "user"); next.evidence.push({ id: conversationEvidenceId, kind: "conversation", title: "最新对话证据", excerpt: lastUserMessage?.content || "用户提出新的产品推进要求。", sourceLabel: "实时线程", }); const items = [ { id: nextId("chg"), changeSetId, group: "new_object", objectType: "Requirement", objectId: requirementId, title: "新增需求:对话压缩成单轮确认", summary: "把首次确认动作收敛到单轮,减少用户来回修改成本。", diff: "新增需求目标为单轮确认。", confidence: 0.92, riskLevel: "medium", status: "pending_review", evidenceIds: [conversationEvidenceId], }, { id: nextId("chg"), changeSetId, group: "risk", objectType: "Risk", objectId: riskId, title: "新增风险:提取过快导致范围漂移", summary: "如果不经过变更审查,结构化对象会偏离用户真正想推进的内容。", diff: "新增缓解策略为回写前强制审查。", confidence: 0.88, riskLevel: "high", status: "pending_review", evidenceIds: [conversationEvidenceId], }, { id: nextId("chg"), changeSetId, group: "decision", objectType: "Decision", objectId: decisionId, title: "待确认决策:先锁主链,再扩支线能力", summary: "本轮以主链闭环为目标,不新增支线配置面。", diff: "决策优先级改为主链优先于广度。", confidence: 0.84, riskLevel: "medium", status: "pending_review", evidenceIds: [conversationEvidenceId], }, { id: nextId("chg"), changeSetId, group: "field_update", objectType: "Task", objectId: taskId, title: "新增任务:对话后立即生成变更包", summary: "对话结束后自动带出变更摘要、证据和操作按钮。", diff: "新增任务验收标准为 1 秒内可见变更包。", confidence: 0.91, riskLevel: "medium", status: "pending_review", evidenceIds: [conversationEvidenceId], }, ] as ProjectTruthState["changeItems"]; const changeSet: ChangeSet = { id: changeSetId, threadId: next.thread.id, summary: "最新一轮对话已生成 4 个结构化变更,等待变更审查。", headline: "新一轮主链沉淀", createdAt: nowLabel(), status: "open", itemIds: items.map((item) => item.id), }; next.changeItems = [...items, ...next.changeItems]; next.changeSets = [changeSet, ...next.changeSets]; next.activeChangeSetId = changeSetId; next.thread.extractionStatus = "ready"; next.messages = next.messages.map((message) => message.status === "analyzing" ? { ...message, status: "resolved", linkedChangeSetId: changeSetId, content: "本轮已生成新的变更审查包,可以直接确认、修改或暂缓。", } : message ); addFeed(next, "新变更集已生成", "对话已沉淀为可审查的结构化变更。", "extraction"); return next; } export function openReviewMutation(state: ProjectTruthState, changeSetId: string): ProjectTruthState { const next = cloneState(state); next.loopStage = "review_open"; next.activeChangeSetId = changeSetId; return next; } export function acceptChangeMutation(state: ProjectTruthState, changeId: string): ProjectTruthState { const next = cloneState(state); next.changeItems = next.changeItems.map((item) => item.id === changeId ? { ...item, status: "accepted" } : item ); addFeed(next, "变更已接受", "结构化更新已进入已确认阶段,等待入图。", "review"); return next; } export function editChangeMutation(state: ProjectTruthState, changeId: string, diff: string): ProjectTruthState { const next = cloneState(state); next.changeItems = next.changeItems.map((item) => item.id === changeId ? { ...item, diff, summary: diff, status: "edited", } : item ); addFeed(next, "变更已修改", "变更包已切换为已修改状态。", "review"); return next; } export function deferChangeMutation(state: ProjectTruthState, changeId: string): ProjectTruthState { const next = cloneState(state); next.changeItems = next.changeItems.map((item) => item.id === changeId ? { ...item, status: "deferred" } : item ); addFeed(next, "变更已暂缓", "该变更保留可见,但不会进入项目真相层。", "review"); return next; } export function promoteAcceptedToGraphMutation(state: ProjectTruthState, changeSetId?: string | null): ProjectTruthState { const next = cloneState(state); next.loopStage = "writeback_success"; next.lastWritebackAt = nowLabel(); const targetId = changeSetId || next.activeChangeSetId; const items = next.changeItems.filter((item) => item.changeSetId === targetId && item.status === "accepted"); items.forEach((item) => { if (item.objectType === "Requirement" && !next.requirements.find((entry) => entry.id === item.objectId)) { next.requirements.unshift({ id: item.objectId, title: item.title.replace("新增需求:", ""), summary: item.summary, stage: "planning", status: "confirmed", linkedTaskIds: [], }); } if (item.objectType === "Decision" && !next.decisions.find((entry) => entry.id === item.objectId)) { next.decisions.unshift({ id: item.objectId, title: item.title.replace("待确认决策:", ""), summary: item.summary, stage: "planning", status: "confirmed", owner: "产品负责人", }); } if (item.objectType === "Risk" && !next.risks.find((entry) => entry.id === item.objectId)) { next.risks.unshift({ id: item.objectId, title: item.title.replace("新增风险:", ""), summary: item.summary, stage: "planning", status: "confirmed", level: item.riskLevel, mitigation: "继续保留变更审查,并前置高风险确认。", }); } if (item.objectType === "Task" && !next.tasks.find((entry) => entry.id === item.objectId)) { next.tasks.unshift({ id: item.objectId, title: item.title.replace("新增任务:", ""), summary: item.summary, stage: "development", status: "draft", assignee: "工程智能体", acceptanceCriteria: ["变更包可见", "结构化对象能进入执行桥"], }); } }); next.changeSets = next.changeSets.map((changeSet) => changeSet.id === targetId ? { ...changeSet, status: "reviewed" } : changeSet ); addFeed(next, "已确认变更已入图", "需求、决策、风险和任务已同步到项目真相层。", "review"); return next; } function executionStep(status: PMFlowExecutionStatus): PMFlowExecutionStatus { switch (status) { case "queued": return "pushed"; case "pushed": return "coding"; case "coding": return "pr_open"; case "pr_open": return "testing"; case "testing": return "synced"; default: return status; } } export function pushTaskToExecutionMutation(state: ProjectTruthState, taskId: string): ProjectTruthState { const next = cloneState(state); next.selectedTaskId = taskId; next.execution.taskId = taskId; next.execution.status = "queued"; next.execution.prompt = "把当前需求、风险、决策组装成开发智能体可消费的上下文包。"; next.execution.commitLog = []; next.execution.prStatus = "尚未推送"; next.execution.testSummary = "等待执行"; next.execution.writebackStatus = "等待执行回写"; next.tasks = next.tasks.map((task) => task.id === taskId ? { ...task, status: "in_progress", stage: "development" } : task ); addFeed(next, "任务已进入执行桥", "上下文包已准备推送到开发智能体。", "execution"); return next; } export function advanceExecutionMutation(state: ProjectTruthState): ProjectTruthState { const next = cloneState(state); const current = next.execution.status; const task = next.tasks.find((entry) => entry.id === next.execution.taskId); const nextStatus = executionStep(current); next.execution.status = nextStatus; if (nextStatus === "pushed") { next.execution.commitLog = ["推送:已发送 PMFlow 上下文包到开发智能体"]; next.execution.prStatus = "上下文包已送达"; } if (nextStatus === "coding") { next.execution.commitLog = [...next.execution.commitLog, "功能:重构主线程的对话审查链路"]; next.execution.prStatus = "智能体开发中"; } if (nextStatus === "pr_open") { next.execution.commitLog = [...next.execution.commitLog, "重构:显化证据与确认状态"]; next.execution.prStatus = "PR #207 已打开"; } if (nextStatus === "testing") { next.execution.testSummary = "正在覆盖指挥台、变更审查、项目真相层和执行桥的冒烟测试。"; } if (task) { next.tasks = next.tasks.map((entry) => entry.id === task.id ? { ...entry, status: nextStatus === "synced" ? "done" : "in_progress", } : entry ); } addFeed(next, "执行状态前进", `当前执行状态已推进到${executionStatusLabel(nextStatus)}。`, "execution"); return next; } export function writeBackExecutionMutation( state: ProjectTruthState, outcome: "passed" | "failed" ): ProjectTruthState { const next = cloneState(state); const taskId = next.execution.taskId; next.execution.status = outcome === "passed" ? "synced" : "failed"; next.execution.testSummary = outcome === "passed" ? "冒烟测试 10/10 通过,准备回写。" : "风险路径失败,等待人工修复。"; next.execution.writebackStatus = outcome === "passed" ? "代码、PR、测试结果已回写到项目真相层。" : "测试失败已回写,发布闸门自动阻塞。"; next.testRuns.unshift({ id: nextId("test"), title: `执行桥测试${outcome === "passed" ? "通过" : "失败"}`, summary: next.execution.testSummary, stage: "testing", status: outcome === "passed" ? "done" : "blocked", result: outcome, linkedTaskId: taskId, }); next.tasks = next.tasks.map((task) => task.id === taskId ? { ...task, status: outcome === "passed" ? "done" : "blocked", stage: outcome === "passed" ? "testing" : "development", } : task ); addFeed( next, outcome === "passed" ? "测试通过并已回写" : "测试失败并已回写", next.execution.writebackStatus, "test" ); return next; } export function publishReleaseMutation(state: ProjectTruthState): ProjectTruthState { const next = cloneState(state); next.project.stage = "release"; next.releases = next.releases.map((release: Release, index) => index === 0 ? { ...release, status: "live", gateItems: release.gateItems.map((item) => ({ ...item, passed: true })), } : release ); addFeed(next, "版本已发布", "发布门禁全部通过,项目进入发布观察。", "release"); return next; } export function ingestGrowthSignalMutation(state: ProjectTruthState): ProjectTruthState { const next = cloneState(state); next.project.stage = "growth"; next.metricSignals.unshift({ id: nextId("metric"), title: "变更审查接受率", summary: "高风险变更在审查后的接受率稳定,说明主链表达更清晰。", stage: "growth", status: "confirmed", value: "72%", direction: "up", }); next.decisions.unshift({ id: nextId("decision"), title: "下一轮继续降低审查认知成本", summary: "增长反馈表明变更审查有效,下一轮优化可读性而不是新增流程。", stage: "growth", status: "confirmed", owner: "增长负责人", }); addFeed(next, "增长信号已入图", "上线反馈已回流到项目真相层并生成下一轮建议。", "growth"); return next; } interface PMFlowOsStoreValue { state: ProjectTruthState; dataSourceMode: "api" | "demo" | "empty"; dataSourceStatus: "idle" | "loading" | "success" | "error"; dataSourceError: string | null; projectionWarnings: string[]; executionSummary: PMFlowExecutionSummary | null; executionSummaryStatus: "idle" | "loading" | "success" | "error"; executionSummaryError: string | null; refetchTruthState: () => void; refetchExecutionSummary: () => void; sendMessage: (content: string) => void; generateExtraction: (proposedItems?: PMFlowProposedChangeItem[]) => void; setLoopStage: (stage: ProjectTruthState["loopStage"]) => void; openReview: (changeSetId: string) => void; acceptChange: (changeId: string) => void; editChange: (changeId: string, diff: string) => void; deferChange: (changeId: string) => void; promoteAcceptedToGraph: (changeSetId?: string | null) => void; pushTaskToExecution: (taskId: string) => void; createExecutionHandoff: (payload: PMFlowExecutionHandoffRequest) => void; consumeExecutionHandoff: (promptPackageId: string) => void; recordExecutionProgress: (payload: PMFlowExecutionProgressEventRequest) => void; createReleaseValidationRun: (payload: PMFlowReleaseValidationRunRequest) => void; createReleaseFeedbackIngest: (payload: PMFlowReleaseFeedbackIngestRequest) => void; createIterationRecommendationChangeSetDraft: ( recommendationId: string, payload?: PMFlowIterationRecommendationChangeSetDraftRequest ) => void; createIterationRecommendationGroupChangeSetDraft: ( groupKey: string, payload?: PMFlowIterationRecommendationGroupChangeSetDraftRequest ) => void; advanceExecution: () => void; writeBackExecution: (outcome: "passed" | "failed") => void; publishRelease: () => void; ingestGrowthSignal: () => void; reset: () => void; } const PMFlowOsStoreContext = createContext(null); export function PMFlowOsProvider({ children }: { children: ReactNode }) { const queryClient = useQueryClient(); const dataSource = useMemo(() => parsePMFlowOsDataSource(), []); const [state, setState] = useState(() => dataSource.mode === "demo" ? createInitialPMFlowState() : createEmptyPMFlowState(dataSource.projectId, dataSource.mode === "api" ? "正在读取真实项目" : "未选择项目") ); const [readError, setReadError] = useState(null); const [projectionWarnings, setProjectionWarnings] = useState([]); const truthStateQuery = useQuery({ queryKey: ["pmflow-os", "truth-state", dataSource.projectId], queryFn: () => getPMFlowTruthState(dataSource.projectId || ""), enabled: dataSource.mode === "api" && Boolean(dataSource.projectId), }); const executionSummaryQuery = useQuery({ queryKey: ["pmflow-os", "execution-summary", dataSource.projectId], queryFn: () => getPMFlowExecutionSummary(dataSource.projectId || ""), enabled: dataSource.mode === "api" && Boolean(dataSource.projectId), }); useEffect(() => { if (dataSource.mode !== "api") return; if (!truthStateQuery.data) return; setReadError(null); setState(truthStateQuery.data); const rawWarnings = (truthStateQuery.data as ProjectTruthState & { projectionWarnings?: string[] }).projectionWarnings; setProjectionWarnings(Array.isArray(rawWarnings) ? rawWarnings : []); }, [dataSource.mode, truthStateQuery.data]); useEffect(() => { if (dataSource.mode !== "api") return; if (!executionSummaryQuery.data) return; setReadError(null); setState((current) => ({ ...current, selectedTaskId: executionSummaryQuery.data.execution.taskId, execution: executionSummaryQuery.data.execution, })); }, [dataSource.mode, executionSummaryQuery.data]); useEffect(() => { if (dataSource.mode !== "api") return; if (!truthStateQuery.error) return; const message = truthStateQuery.error instanceof Error ? truthStateQuery.error.message : "PMFlow OS 真实 API 读取失败"; setReadError(message); setState(createEmptyPMFlowState(dataSource.projectId, "真实读取失败")); setProjectionWarnings([]); }, [dataSource.mode, dataSource.projectId, truthStateQuery.error]); useEffect(() => { if (dataSource.mode !== "demo") return; if (state.thread.extractionStatus !== "loading") return; const timer = window.setTimeout(() => { setState((current) => generateExtractionMutation(current)); }, 700); return () => window.clearTimeout(timer); }, [dataSource.mode, state.thread.extractionStatus]); const mutateDemoOnly = (mutation: (current: ProjectTruthState) => ProjectTruthState) => { if (dataSource.mode !== "demo") return; setState(mutation); }; const refetchApiState = () => { if (dataSource.mode !== "api") return; void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "truth-state", dataSource.projectId] }); void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "execution-summary", dataSource.projectId] }); void truthStateQuery.refetch(); void executionSummaryQuery.refetch(); }; const runApiAction = (action: () => Promise) => { if (dataSource.mode !== "api") return; setReadError(null); void action() .then(() => refetchApiState()) .catch((error) => { const message = error instanceof Error ? error.message : "PMFlow OS 真实 API 操作失败"; setReadError(message); }); }; return ( { if (dataSource.mode === "api") refetchApiState(); }, refetchExecutionSummary: () => { if (dataSource.mode !== "api") return; void queryClient.invalidateQueries({ queryKey: ["pmflow-os", "execution-summary", dataSource.projectId] }); void executionSummaryQuery.refetch(); }, sendMessage: (content) => { if (dataSource.mode === "api" && dataSource.projectId) { runApiAction(() => createPMFlowThreadMessage(dataSource.projectId || "", state.thread.id, { content, source: "web", attachments: [], }) ); return; } mutateDemoOnly((current) => sendMessageMutation(current, content)); }, generateExtraction: (proposedItems) => { if (dataSource.mode === "api" && dataSource.projectId) { if (!proposedItems?.length) { setReadError("结构化提取候选将在下一阶段 AI 提取开放;当前真实模式需要 proposed_items。"); return; } const lastMessage = [...state.messages].reverse().find((message) => message.role === "user"); if (!lastMessage) { setReadError("缺少可用于提取的项目线程消息。"); return; } runApiAction(() => createPMFlowExtraction(dataSource.projectId || "", { thread_id: state.thread.id, message_id: lastMessage.id, scope: "latest_message", proposed_items: proposedItems, }) ); return; } mutateDemoOnly((current) => generateExtractionMutation(current)); }, setLoopStage: (stage) => mutateDemoOnly((current) => ({ ...current, loopStage: stage })), openReview: (changeSetId) => mutateDemoOnly((current) => openReviewMutation(current, changeSetId)), acceptChange: (changeId) => { if (dataSource.mode === "api") { runApiAction(() => acceptPMFlowChangeItem(changeId)); return; } mutateDemoOnly((current) => acceptChangeMutation(current, changeId)); }, editChange: (changeId, diff) => { if (dataSource.mode === "api") { runApiAction(() => editPMFlowChangeItem(changeId, { summary: diff, diff, payload_patch: { summary: diff }, reason: "用户修改" })); return; } mutateDemoOnly((current) => editChangeMutation(current, changeId, diff)); }, deferChange: (changeId) => { if (dataSource.mode === "api") { runApiAction(() => deferPMFlowChangeItem(changeId, "用户暂缓")); return; } mutateDemoOnly((current) => deferChangeMutation(current, changeId)); }, promoteAcceptedToGraph: (changeSetId) => { if (dataSource.mode === "api") { const targetChangeSetId = changeSetId || state.activeChangeSetId; if (!targetChangeSetId) { setReadError("缺少可提交的 ChangeSet,无法写入项目记忆。"); return; } runApiAction(() => commitPMFlowChangeSet(targetChangeSetId, { mode: "atomic", note: "用户确认写入项目记忆", }) ); return; } mutateDemoOnly((current) => promoteAcceptedToGraphMutation(current, changeSetId)); }, pushTaskToExecution: (taskId) => mutateDemoOnly((current) => pushTaskToExecutionMutation(current, taskId)), createExecutionHandoff: (payload) => { if (dataSource.mode === "api" && dataSource.projectId) { runApiAction(() => createPMFlowExecutionHandoff(dataSource.projectId || "", payload)); return; } mutateDemoOnly((current) => pushTaskToExecutionMutation(current, payload.task_id)); }, consumeExecutionHandoff: (promptPackageId) => { if (dataSource.mode === "api" && dataSource.projectId) { runApiAction(() => consumePMFlowExecutionHandoff(dataSource.projectId || "", promptPackageId, { runtime_target: "codex", consumer_label: "Codex", note: "PMFlow Execution Bridge 确认交接包已被运行端领取", }) ); return; } mutateDemoOnly((current) => advanceExecutionMutation(current)); }, recordExecutionProgress: (payload) => { if (dataSource.mode === "api" && dataSource.projectId) { runApiAction(() => createPMFlowExecutionProgressEvent(dataSource.projectId || "", payload)); return; } mutateDemoOnly((current) => advanceExecutionMutation(current)); }, createReleaseValidationRun: (payload) => { if (dataSource.mode === "api" && dataSource.projectId) { runApiAction(() => createPMFlowReleaseValidationRun(dataSource.projectId || "", payload)); } }, createReleaseFeedbackIngest: (payload) => { if (dataSource.mode === "api" && dataSource.projectId) { runApiAction(() => createPMFlowReleaseFeedbackIngest(dataSource.projectId || "", payload)); return; } mutateDemoOnly((current) => ingestGrowthSignalMutation(current)); }, createIterationRecommendationChangeSetDraft: (recommendationId, payload) => { if (dataSource.mode === "api" && dataSource.projectId) { runApiAction(() => createPMFlowIterationRecommendationChangeSetDraft(dataSource.projectId || "", recommendationId, { note: payload?.note || "ReleaseGrowthPage 生成下一轮待确认变更;不自动 commit。", }) ); return; } mutateDemoOnly((current) => ingestGrowthSignalMutation(current)); }, createIterationRecommendationGroupChangeSetDraft: (groupKey, payload) => { if (dataSource.mode === "api" && dataSource.projectId) { runApiAction(() => createPMFlowIterationRecommendationGroupChangeSetDraft(dataSource.projectId || "", groupKey, { recommendation_ids: payload?.recommendation_ids || [], note: payload?.note || "ReleaseGrowthPage 批量生成同组下一轮待确认 ChangeSet draft;不自动 commit。", }) ); return; } mutateDemoOnly((current) => ingestGrowthSignalMutation(current)); }, advanceExecution: () => mutateDemoOnly((current) => advanceExecutionMutation(current)), writeBackExecution: (outcome) => mutateDemoOnly((current) => writeBackExecutionMutation(current, outcome)), publishRelease: () => mutateDemoOnly((current) => publishReleaseMutation(current)), ingestGrowthSignal: () => mutateDemoOnly((current) => ingestGrowthSignalMutation(current)), reset: () => setState( dataSource.mode === "demo" ? createInitialPMFlowState() : createEmptyPMFlowState(dataSource.projectId, dataSource.mode === "api" ? "正在读取真实项目" : "未选择项目") ), }} > {children} ); } export function usePMFlowOsStore() { const value = useContext(PMFlowOsStoreContext); if (!value) { throw new Error("usePMFlowOsStore must be used within PMFlowOsProvider"); } return value; }