From c1a8ca6481aa4b5e923d18a67a5052cc1bc10762 Mon Sep 17 00:00:00 2001 From: Linghong Date: Sat, 22 Aug 2026 07:53:15 +0000 Subject: [PATCH] =?UTF-8?q?GDD=20=E5=AE=A1=E6=89=B9=E5=8D=A1=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E8=A2=AB=E4=B8=A4=E5=BC=A0=E4=BC=AA=E5=8D=A1=E5=8E=8B?= =?UTF-8?q?=E4=BD=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 立项策划提交 Fast GDD 后,界面上同时冒出三样东西:一句「待回答问题未能读取,请稍后 重试」、一张 plan.submit_gdd 的拒绝/确认卡,以及被压在下面的 GddApprovalCard。前两样 都是伪的。 plan.submit_gdd 的 pending 是审批卡的载体,不是等待批准的动作——planning/pending.json 的 submission.pendingActionId 指向的正是它,交互面是审批卡的批准/修改/退回。它和 user.input_request 是同一性质,而且运行时策略明确拒绝把它转成通用确认 pending (「Runtime-owned create-only 提交」),那个确认按钮点下去必然失败。所以把载体判据从只认 user.input_request 扩成两个工具都认,并改名 agentRuntimePendingActionHasDedicatedCard。 同时补上第四个渲染面:协作 Agent 列表也无条件把 pendingToolAction 画成通用确认卡,上一次 只改了开发者面板、总控概览卡和聊天视图三处。 「待回答问题未能读取」则是另一回事:审批等待复用了 waiting-for-user-input 这个 phase,但 它没有 userInputRequest,只看 phase 就会把一次正常的等待报成读取失败。给 ProjectSupervisorRuntimePanel 加一个 planGddAwaitingDecision 入参,由两个调用处按 planGddState.pendingApproval 传下来,等审批时不再报这句错。 新增用例同时断言三条:审批未决时不出现「待回答问题未能读取」、不出现 plan.submit_gdd 确认卡、审批卡的批准按钮在位。两条判据都做过 A/B——各自关掉后对应的伪卡都会冒出来。 Co-Authored-By: Claude Opus 5 --- .../src/features/agent-runtime/model.ts | 34 +++++++---- .../src/features/agent-runtime/panels.tsx | 36 ++++++++---- .../ProjectSupervisorView.tsx | 5 +- .../ProjectWorkspaceChatPane.tsx | 1 + .../tests/appSurface/plan-gdd.suite.ts | 57 +++++++++++++++++++ 5 files changed, 111 insertions(+), 22 deletions(-) diff --git a/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts b/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts index 157788f74..600e7c6dc 100644 --- a/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts +++ b/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts @@ -858,20 +858,34 @@ export function agentRuntimeNeedsUserInput( } export const AGENT_RUNTIME_USER_INPUT_REQUEST_TOOL = 'user.input_request'; +export const AGENT_RUNTIME_PLAN_SUBMIT_GDD_TOOL = 'plan.submit_gdd'; -// `user.input_request` 的 pending 不是等待批准的动作,而是问题本身的载体:子 Agent 以 -// needs-user-input 终态退出后,Runtime 在 parent-wake 屏障处按信封原文构造这个 pending -// (`ensure_static_delegate_user_input_wait_at_locked`),同一份问题再投影成 -// `userInputRequest`。它也从来不在 `GAME_CREATION_APP_COMMANDS` 的 confirm 集合里, -// 没有任何「确认/拒绝」语义。 +// 有两个 pending 动作不是「等待批准的动作」,而是某张专用卡的载体:Runtime 把内容停在 +// pending 上,真正的交互面是另一个组件。 // -// 通用待确认卡按 `tool` + `inputSummary` 渲染,套到它身上就是把同一个请求画两遍——标题 -// 是原始工具名,副标题是 `questionCount=… · questionsSha256=…` 这种给日志看的取证摘要。 -// 真正的交互面是问答卡,所以这里统一把通用确认面判掉。 -export function agentRuntimePendingActionIsUserInputCarrier( +// - `user.input_request`:子 Agent 以 needs-user-input 终态退出后,Runtime 在 parent-wake +// 屏障处按信封原文构造这个 pending(`ensure_static_delegate_user_input_wait_at_locked`), +// 同一份问题再投影成 `userInputRequest`,交互面是问答卡。 +// - `plan.submit_gdd`:策划子 Agent 提交 Fast GDD 后,`planning/pending.json` 的 +// `submission.pendingActionId` 指向的正是这个 pending,交互面是 `GddApprovalCard` +// 的批准 / 修改 / 退回。 +// +// 两者都没有「确认 / 拒绝」语义:`user.input_request` 从来不在 +// `GAME_CREATION_APP_COMMANDS` 的 confirm 集合里;`plan.submit_gdd` 更是被运行时策略明确 +// 拒绝转成通用确认 pending(「Runtime-owned create-only 提交」),点确认必然失败。 +// +// 通用待确认卡按 `tool` + `inputSummary` 渲染,套到它们身上就是把同一个请求画两遍——标题 +// 是原始工具名,副标题是 `questionCount=… · questionsSha256=…` 这种给日志看的取证摘要, +// 还会把真正该看的那张卡压在下面。所以这里统一把通用确认面判掉。 +const AGENT_RUNTIME_DEDICATED_CARD_TOOLS = new Set([ + AGENT_RUNTIME_USER_INPUT_REQUEST_TOOL, + AGENT_RUNTIME_PLAN_SUBMIT_GDD_TOOL, +]); + +export function agentRuntimePendingActionHasDedicatedCard( action: AgentRuntimePendingToolActionSummary | null | undefined, ) { - return action?.tool === AGENT_RUNTIME_USER_INPUT_REQUEST_TOOL; + return Boolean(action && AGENT_RUNTIME_DEDICATED_CARD_TOOLS.has(action.tool)); } export function matchingAgentRuntimeForSteer( diff --git a/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx b/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx index 3c9ac8944..02ab29137 100644 --- a/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx +++ b/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx @@ -29,7 +29,7 @@ import { agentRuntimeCanRetry, agentRuntimeNeedsUserInput, agentRuntimeNextStepFromPhase, - agentRuntimePendingActionIsUserInputCarrier, + agentRuntimePendingActionHasDedicatedCard, agentRuntimePlanStepText, agentRuntimeWaitingOnFromPhase, createAgentRuntimeUserInputResponseId, @@ -399,18 +399,18 @@ export function AgentRuntimeStatusPanel({ !agentGoalStatusIsPaused(runtime.phase) && !pendingToolAction && Boolean(onRetryRuntimeTask); - const pendingActionIsUserInputCarrier = - agentRuntimePendingActionIsUserInputCarrier(pendingToolAction); + const pendingActionHasDedicatedCard = + agentRuntimePendingActionHasDedicatedCard(pendingToolAction); const canConfirm = Boolean(runtime.runId) && Boolean(pendingToolAction?.actionId) && - !pendingActionIsUserInputCarrier && + !pendingActionHasDedicatedCard && agentRuntimeCanConfirm(runtime.status) && Boolean(onConfirmRuntimeTask); const canReject = Boolean(runtime.runId) && Boolean(pendingToolAction?.actionId) && - !pendingActionIsUserInputCarrier && + !pendingActionHasDedicatedCard && agentRuntimeCanConfirm(runtime.status) && Boolean(onRejectRuntimeTask); const canCompact = @@ -675,6 +675,7 @@ export function ProjectSupervisorRuntimePanel({ runtimeByAgentId, controlBusy, readOnly = false, + planGddAwaitingDecision = false, professionalResultsByAgentId, onToolAction, onSupervisorRetry, @@ -687,6 +688,15 @@ export function ProjectSupervisorRuntimePanel({ runtimeByAgentId: Record; controlBusy: boolean; readOnly?: boolean; + /** + * `GddApprovalCard` 是否正在等用户做决定(`planning/pending.json` 有 + * `awaiting_decision` 的 pending)。 + * + * 立项策划的审批等待复用了 `waiting-for-user-input` 这个 phase,但它没有 + * `userInputRequest`——真正的交互面是审批卡。只看 phase 的话这里会退到「待回答问题未能 + * 读取」那句错误,把一次正常的等待报成读取失败,还压在审批卡上面。 + */ + planGddAwaitingDecision?: boolean; professionalResultsByAgentId: Record< string, ProjectAgentResultSummary | undefined @@ -781,8 +791,8 @@ export function ProjectSupervisorRuntimePanel({ const pendingActionPresentation = pendingToolAction ? projectSupervisorPendingActionPresentation(pendingToolAction) : null; - const pendingActionIsUserInputCarrier = - agentRuntimePendingActionIsUserInputCarrier(pendingToolAction); + const pendingActionHasDedicatedCard = + agentRuntimePendingActionHasDedicatedCard(pendingToolAction); const userInputRequest = runtime?.userInputRequest ?? null; const needsUserInput = agentRuntimeNeedsUserInput(runtime); const needsSupervisorReconciliation = Boolean( @@ -960,7 +970,7 @@ export function ProjectSupervisorRuntimePanel({ ) : null} {pendingToolAction && pendingActionPresentation && - !pendingActionIsUserInputCarrier && + !pendingActionHasDedicatedCard && !readOnly && !needsSupervisorReconciliation ? (
- ) : needsUserInput && !readOnly ? ( + ) : needsUserInput && !planGddAwaitingDecision && !readOnly ? (

待回答问题未能读取,请稍后重试。

@@ -1295,7 +1309,7 @@ export function ProjectSupervisorRuntimeControls({ ) => void | Promise; }) { const pendingToolAction = runtime?.pendingToolAction ?? null; - const confirmableToolAction = agentRuntimePendingActionIsUserInputCarrier( + const confirmableToolAction = agentRuntimePendingActionHasDedicatedCard( pendingToolAction, ) ? null diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx index 0dba8a087..e7851d9f5 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx @@ -150,7 +150,10 @@ export function ProjectSupervisorView({ ) : null}
{directCodex ? null : ( - + )} {pendingCommand ? (
diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx index 13ff681c2..ff15f9b28 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx @@ -866,6 +866,7 @@ export function ProjectWorkspaceChatPane({ error={projectSupervisorRuntimeError} runtimeByAgentId={agentRuntimeById} controlBusy={chatAgentBusy} + planGddAwaitingDecision={Boolean(planGddState?.pendingApproval)} professionalResultsByAgentId={professionalAgentResultsById} onToolAction={handleProjectSupervisorToolAction} onSupervisorRetry={handleProjectSupervisorRetry} diff --git a/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts index f2f36ccfc..881bc3577 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts @@ -174,4 +174,61 @@ export function registerPlanGddApprovalTests() { expect(textarea.value).toBe('战斗节奏太慢,请压缩到三个回合'); expect(harness.planGddDecisionCalls).toHaveLength(0); }); + + it('shows only the approval card while the GDD decision is outstanding', async () => { + // 审批等待复用了 waiting-for-user-input 这个 phase,但它没有 userInputRequest;而 + // 策划子 Agent 的 plan.submit_gdd pending 正是 planning/pending.json 里 + // submission.pendingActionId 指向的那一个,交互面就是审批卡本身。两处都套通用面的话, + // 用户会同时看到一句「待回答问题未能读取」和一张点了必然失败的 plan.submit_gdd + // 确认卡,把真正该看的审批卡压在下面。 + const supervisorRunId = 'plan-root-awaiting-approval-run'; + let collaboratingRuntimes: Array> = []; + const harness = createProjectSupervisorRuntimeHarness({ + expectedRunProfile: 'standard', + initialRuntime: { + runId: supervisorRunId, + source: 'project-supervisor-plan', + runProfile: 'standard', + status: 'running', + phase: 'waiting-for-user-input', + currentTask: '剧情向恋爱养成游戏', + waitingOn: '用户在审批卡选择批准、修改或退回', + nextStep: '等待 decide_game_creator_plan_gdd', + updatedAt: 8000, + }, + runtimeMapLoader: async () => collaboratingRuntimes, + }); + collaboratingRuntimes = [ + harness.runtimeState({ + agentId: 'project-planning', + taskId: 'project-planning', + sessionId: 'agent-session-project-planning', + runId: 'delegated-delegation-344abab06ded0da1cf572fa3', + source: 'agent-delegate', + parentAgentId: 'project-supervisor', + parentRunId: supervisorRunId, + delegationId: 'delegation-344abab06ded0da1cf572fa3', + runProfile: 'standard', + status: 'idle', + phase: 'completed', + currentTask: '完成立项策划并给出 Fast GDD', + pendingToolAction: { + actionId: 'action-0123456789abcdef01234567', + actionFingerprint: 'a'.repeat(64), + tool: 'plan.submit_gdd', + inputSummary: null, + reason: '提交完整原创 MVP Fast GDD。', + requestedAt: 8000, + }, + updatedAt: 8000, + }), + ]; + harness.setPlanGddState(createPlanGddStateView()); + await mountApprovalCard(harness); + + expect(screen.queryByText('待回答问题未能读取,请稍后重试。')).toBeNull(); + expect(screen.queryByText(/plan\.submit_gdd/)).toBeNull(); + expect(screen.queryByLabelText('立项策划 Agent待确认动作')).toBeNull(); + expect(screen.getByRole('button', { name: '批准 v1' })).not.toBeNull(); + }); }