立项策划链路底部改成窄条,只留澄清卡和失败恢复,做游戏链路面板原样保留
做方案链路的底部挂的是 ProjectSupervisorRuntimePanel——一块为做游戏链路设计的 面板:十几个专业 Agent、多步计划、逐 Agent 重试。套到策划链路上,子 Agent 永远只 有 project-planning 一个,计划永远一两步,「专业 Agent 协作:1」永远是 1。它把 D11 的「总控 + 委派子 Run」拓扑整个漏给了用户:currentAction 原文(「等待 project-planning 提交 GDD」)、计划 1/2 进度条、五段式紧凑进度、一张把策划子 Run 当成「专业 Agent」的卡,外加同一个状态在顶部 strip / overview / 子 Agent 卡三处各 画一遍。用户的心智模型是在跟一个策划聊天,不是看总控调度。 新组件 PlanningLaneRuntimeStrip 只画真正需要用户动手的两样:澄清问答卡(复用 AgentRuntimeUserInputCard,零改动)和失败 / 待核对后的恢复入口(逻辑照搬原面板, 文案改成「重新启动策划」)。App 层操作错误那一行保留,那是原面板里唯一面向用户 的文字。其余时候返回 null,不占一行——状态由顶部的 PlanGddStageProgress 承担。 原面板在 waiting-for-user-input 却读不到 userInputRequest 时画的「待回答问题未能 读取」一句不搬:策划链路里这个组合出现在子 Run 退出到父 Run 醒来之间的瞬时窗口, 以及审批等待本身,两种都不是读取失败。 切换判据是 run 的 source === 'project-supervisor-plan',抽成 isPlanningLaneRuntime 放到独立模块(react-refresh 不让组件文件导出函数),两个挂载点 ProjectSupervisorView / ProjectWorkspaceChatPane 和顶部 strip 的 active 判定统一 用它。判据故意不看 planGddState:做游戏链路在策划批准后照样带着一份 approved 状 态,但它的总控 run 是 autonomous 源,必须继续拿完整面板。source 非 plan 时面板一 行未动,既有的「keeps Project Supervisor plan progress compact」用例仍在守门。 新增三条 appSurface 用例,都做过 A/B(强制回旧面板即三条全红): - 策划进行中:底部不存在面板、overview、紧凑进度、子 Agent 列表,且 currentAction 原文与「计划 N/M」都不出现。 - 澄清等待:问答卡仍在窄条里。 - run 失败:窄条给出「重新启动策划」,错误文字保留。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+184
@@ -0,0 +1,184 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import type {
|
||||
AgentRuntimeState,
|
||||
AgentRuntimeUserInputRequest,
|
||||
} from '../../app/types';
|
||||
import {
|
||||
agentRuntimeCanRetry,
|
||||
AgentRuntimeUserInputCard,
|
||||
projectRuntimeVisibleError,
|
||||
} from '../agent-runtime';
|
||||
|
||||
type PlanningLaneRuntimeStripProps = {
|
||||
runtime: AgentRuntimeState | null;
|
||||
error: string;
|
||||
controlBusy: boolean;
|
||||
readOnly?: boolean;
|
||||
onSupervisorRetry: (runtime: AgentRuntimeState) => Promise<string>;
|
||||
onUserInput: (
|
||||
request: AgentRuntimeUserInputRequest,
|
||||
responseId: string,
|
||||
answers: Record<string, string>,
|
||||
) => void | Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 立项策划链路下替代 `ProjectSupervisorRuntimePanel` 的窄条。
|
||||
*
|
||||
* 完整面板是为做游戏链路设计的:十几个专业 Agent、多步计划、逐 Agent 重试。套到
|
||||
* 策划链路上,子 Agent 永远只有 `project-planning` 一个,计划永远一两步,「专业
|
||||
* Agent 协作:1」永远是 1——它把 D11 的「总控 + 委派子 Run」拓扑整个漏给了用户,而
|
||||
* 用户的心智模型是在跟一个策划聊天。状态本身由顶部的 `PlanGddStageProgress` 承担。
|
||||
*
|
||||
* 这里只画真正需要用户动手的两样:澄清问答卡,以及失败后的恢复入口。其余时候
|
||||
* 返回 null,不占一行。
|
||||
*
|
||||
* 完整面板在 `waiting-for-user-input` 却读不到 `userInputRequest` 时会画一句
|
||||
* 「待回答问题未能读取」。策划链路里这个组合出现在子 Run 退出到父 Run 醒来之间的
|
||||
* 瞬时窗口,以及审批等待(交互面是审批卡)——两种都不是读取失败,所以这里不画。
|
||||
*/
|
||||
export function PlanningLaneRuntimeStrip({
|
||||
runtime,
|
||||
error,
|
||||
controlBusy,
|
||||
readOnly = false,
|
||||
onSupervisorRetry,
|
||||
onUserInput,
|
||||
}: PlanningLaneRuntimeStripProps) {
|
||||
const [retrySubmitting, setRetrySubmitting] = useState(false);
|
||||
const [retryAccepted, setRetryAccepted] = useState(false);
|
||||
const [retryFeedback, setRetryFeedback] = useState('');
|
||||
useEffect(() => {
|
||||
setRetrySubmitting(false);
|
||||
setRetryAccepted(false);
|
||||
if (runtime?.status === 'failed' || runtime?.phase === 'failed') {
|
||||
setRetryFeedback('');
|
||||
}
|
||||
}, [runtime?.phase, runtime?.runId, runtime?.status]);
|
||||
|
||||
const userInputRequest = readOnly
|
||||
? null
|
||||
: (runtime?.userInputRequest ?? null);
|
||||
const needsReconciliation = Boolean(
|
||||
runtime &&
|
||||
(runtime.status === 'needs-reconciliation' ||
|
||||
runtime.phase === 'needs-reconciliation'),
|
||||
);
|
||||
const stopped = Boolean(
|
||||
runtime &&
|
||||
(runtime.status === 'failed' ||
|
||||
runtime.phase === 'failed' ||
|
||||
((runtime.status === 'cancelled' || runtime.phase === 'cancelled') &&
|
||||
(runtime.taskQueue?.pending ?? 0) === 0)),
|
||||
);
|
||||
const canRetry = Boolean(
|
||||
!readOnly &&
|
||||
runtime &&
|
||||
!runtime.pendingToolAction &&
|
||||
stopped &&
|
||||
agentRuntimeCanRetry(runtime.status),
|
||||
);
|
||||
const showRecovery =
|
||||
!readOnly && runtime && (needsReconciliation || canRetry);
|
||||
// 与完整面板同源:App 层的操作错误(如「请先回答当前的澄清问题」)优先,其次是
|
||||
// run 自己记下的失败原因。这是原面板里唯一真正面向用户的一行文字,照搬。
|
||||
const rawErrorDetail = error || runtime?.error || '';
|
||||
const errorDetail = rawErrorDetail
|
||||
? projectRuntimeVisibleError(rawErrorDetail, '项目总控 Agent', true)
|
||||
: '';
|
||||
|
||||
if (!userInputRequest && !showRecovery && !errorDetail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="agent-runtime-status planning-lane-runtime-strip"
|
||||
aria-label="立项策划运行状态"
|
||||
>
|
||||
{errorDetail ? (
|
||||
<small className="project-runtime-error" role="alert">
|
||||
{errorDetail}
|
||||
</small>
|
||||
) : null}
|
||||
{showRecovery && runtime ? (
|
||||
<div
|
||||
className="project-runtime-recovery"
|
||||
aria-label={
|
||||
needsReconciliation ? '立项策划待核对恢复' : '立项策划失败恢复'
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{needsReconciliation ? (
|
||||
<>
|
||||
本轮工具动作的结果不确定,需要先结束旧任务。
|
||||
<small>不会直接重试,避免重复执行未核对的动作。</small>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
本轮策划已停止。
|
||||
<small>在当前项目重新启动策划,不会新建项目。</small>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={controlBusy || retrySubmitting || retryAccepted}
|
||||
onClick={() => {
|
||||
setRetryFeedback(
|
||||
needsReconciliation
|
||||
? '正在结束待核对的旧任务…'
|
||||
: '正在重新启动策划…',
|
||||
);
|
||||
setRetrySubmitting(true);
|
||||
void onSupervisorRetry(runtime)
|
||||
.then((message) => {
|
||||
setRetryAccepted(true);
|
||||
setRetryFeedback(message);
|
||||
})
|
||||
.catch((retryError) => {
|
||||
setRetryAccepted(false);
|
||||
setRetryFeedback(
|
||||
projectRuntimeVisibleError(
|
||||
retryError instanceof Error
|
||||
? retryError.message
|
||||
: String(retryError),
|
||||
'项目总控 Agent',
|
||||
true,
|
||||
),
|
||||
);
|
||||
})
|
||||
.finally(() => setRetrySubmitting(false));
|
||||
}}
|
||||
>
|
||||
{retrySubmitting
|
||||
? needsReconciliation
|
||||
? '正在结束旧任务…'
|
||||
: '正在重新启动…'
|
||||
: retryAccepted
|
||||
? needsReconciliation
|
||||
? '旧任务结束请求已受理'
|
||||
: '重试已受理'
|
||||
: needsReconciliation
|
||||
? '已核对,结束旧任务'
|
||||
: '重新启动策划'}
|
||||
</button>
|
||||
{retryFeedback ? (
|
||||
<small className="project-runtime-retry-feedback" role="status">
|
||||
{retryFeedback}
|
||||
</small>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{userInputRequest ? (
|
||||
<AgentRuntimeUserInputCard
|
||||
key={`${userInputRequest.requestId}:${userInputRequest.responseId ?? 'pending'}`}
|
||||
request={userInputRequest}
|
||||
controlBusy={controlBusy}
|
||||
onSubmit={onUserInput}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
+15
-4
@@ -29,6 +29,8 @@ import {
|
||||
pendingCommandDetail,
|
||||
pendingCommandTitle,
|
||||
} from './pendingCommandPresentation';
|
||||
import { isPlanningLaneRuntime } from './planningLane';
|
||||
import { PlanningLaneRuntimeStrip } from './PlanningLaneRuntimeStrip';
|
||||
import { resolvePendingCommandProjectPath } from './projectCommandPolicy';
|
||||
|
||||
type RuntimePanelProps = ComponentProps<typeof ProjectSupervisorRuntimePanel>;
|
||||
@@ -100,9 +102,7 @@ export function ProjectSupervisorView({
|
||||
<div className="project-supervisor-conversation">
|
||||
<PlanGddStageProgress
|
||||
state={planGddState}
|
||||
active={
|
||||
runtimePanelProps.runtime?.source === 'project-supervisor-plan'
|
||||
}
|
||||
active={isPlanningLaneRuntime(runtimePanelProps.runtime)}
|
||||
/>
|
||||
<GddApprovalCard
|
||||
state={planGddState}
|
||||
@@ -149,7 +149,18 @@ export function ProjectSupervisorView({
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{directCodex ? null : (
|
||||
{directCodex ? null : isPlanningLaneRuntime(
|
||||
runtimePanelProps.runtime,
|
||||
) ? (
|
||||
<PlanningLaneRuntimeStrip
|
||||
runtime={runtimePanelProps.runtime}
|
||||
error={runtimePanelProps.error}
|
||||
controlBusy={runtimePanelProps.controlBusy}
|
||||
readOnly={runtimePanelProps.readOnly}
|
||||
onSupervisorRetry={runtimePanelProps.onSupervisorRetry}
|
||||
onUserInput={runtimePanelProps.onUserInput}
|
||||
/>
|
||||
) : (
|
||||
<ProjectSupervisorRuntimePanel
|
||||
{...runtimePanelProps}
|
||||
planGddAwaitingDecision={Boolean(planGddState?.pendingApproval)}
|
||||
|
||||
+26
-16
@@ -52,6 +52,8 @@ import {
|
||||
pendingCommandDetail,
|
||||
pendingCommandTitle,
|
||||
} from './pendingCommandPresentation';
|
||||
import { isPlanningLaneRuntime } from './planningLane';
|
||||
import { PlanningLaneRuntimeStrip } from './PlanningLaneRuntimeStrip';
|
||||
import { resolvePendingCommandProjectPath } from './projectCommandPolicy';
|
||||
|
||||
type ProjectWorkspaceChatPaneProps = {
|
||||
@@ -365,9 +367,7 @@ export function ProjectWorkspaceChatPane({
|
||||
</header>
|
||||
<PlanGddStageProgress
|
||||
state={planGddState}
|
||||
active={
|
||||
projectSupervisorRuntime?.source === 'project-supervisor-plan'
|
||||
}
|
||||
active={isPlanningLaneRuntime(projectSupervisorRuntime)}
|
||||
/>
|
||||
<GddApprovalCard
|
||||
state={planGddState}
|
||||
@@ -861,19 +861,29 @@ export function ProjectWorkspaceChatPane({
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<ProjectSupervisorRuntimePanel
|
||||
runtime={projectSupervisorRuntime}
|
||||
error={projectSupervisorRuntimeError}
|
||||
runtimeByAgentId={agentRuntimeById}
|
||||
controlBusy={chatAgentBusy}
|
||||
planGddAwaitingDecision={Boolean(planGddState?.pendingApproval)}
|
||||
professionalResultsByAgentId={professionalAgentResultsById}
|
||||
onToolAction={handleProjectSupervisorToolAction}
|
||||
onSupervisorRetry={handleProjectSupervisorRetry}
|
||||
onProfessionalToolAction={handleProjectProfessionalAgentToolAction}
|
||||
onProfessionalRetry={handleProjectProfessionalAgentRetry}
|
||||
onUserInput={handleProjectSupervisorUserInput}
|
||||
/>
|
||||
{isPlanningLaneRuntime(projectSupervisorRuntime) ? (
|
||||
<PlanningLaneRuntimeStrip
|
||||
runtime={projectSupervisorRuntime}
|
||||
error={projectSupervisorRuntimeError}
|
||||
controlBusy={chatAgentBusy}
|
||||
onSupervisorRetry={handleProjectSupervisorRetry}
|
||||
onUserInput={handleProjectSupervisorUserInput}
|
||||
/>
|
||||
) : (
|
||||
<ProjectSupervisorRuntimePanel
|
||||
runtime={projectSupervisorRuntime}
|
||||
error={projectSupervisorRuntimeError}
|
||||
runtimeByAgentId={agentRuntimeById}
|
||||
controlBusy={chatAgentBusy}
|
||||
planGddAwaitingDecision={Boolean(planGddState?.pendingApproval)}
|
||||
professionalResultsByAgentId={professionalAgentResultsById}
|
||||
onToolAction={handleProjectSupervisorToolAction}
|
||||
onSupervisorRetry={handleProjectSupervisorRetry}
|
||||
onProfessionalToolAction={handleProjectProfessionalAgentToolAction}
|
||||
onProfessionalRetry={handleProjectProfessionalAgentRetry}
|
||||
onUserInput={handleProjectSupervisorUserInput}
|
||||
/>
|
||||
)}
|
||||
{pendingCommand ? (
|
||||
<div className="pending-command">
|
||||
<span>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { AgentRuntimeState } from '../../app/types';
|
||||
|
||||
export const PROJECT_SUPERVISOR_PLAN_SOURCE = 'project-supervisor-plan';
|
||||
|
||||
/**
|
||||
* 当前总控 run 是否属于立项策划链路。
|
||||
*
|
||||
* 判据是 run 的 `source`,不是 `planGddState`:做游戏链路在策划批准之后照样带着
|
||||
* 一份 approved 的策划状态,但它的总控 run 是 autonomous 源,必须继续拿完整面板。
|
||||
*/
|
||||
export function isPlanningLaneRuntime(
|
||||
runtime: AgentRuntimeState | null | undefined,
|
||||
) {
|
||||
return runtime?.source === PROJECT_SUPERVISOR_PLAN_SOURCE;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
agentRuntimeUserInputRequest,
|
||||
createPlanGddStateView,
|
||||
createProjectSupervisorRuntimeHarness,
|
||||
expect,
|
||||
@@ -23,6 +24,49 @@ async function mountApprovalCard(
|
||||
return await screen.findByLabelText('GDD 审批卡');
|
||||
}
|
||||
|
||||
async function mountPlanningSurface(
|
||||
harness: ReturnType<typeof createProjectSupervisorRuntimeHarness>,
|
||||
) {
|
||||
window.__TAURI__ = {
|
||||
core: { invoke: harness.invoke },
|
||||
event: { listen: harness.listen },
|
||||
};
|
||||
renderAppAt('/');
|
||||
await openMainProject(harness.projectPath);
|
||||
return await screen.findByLabelText('立项策划阶段进度');
|
||||
}
|
||||
|
||||
type PlanGddStateView = ReturnType<typeof createPlanGddStateView>;
|
||||
|
||||
/** 策划中、尚未提交 GDD 的状态:没有待审批,也没有可展示的 GDD。 */
|
||||
function draftPlanGddState(
|
||||
sessionOverrides: Partial<NonNullable<PlanGddStateView['session']>> = {},
|
||||
) {
|
||||
const base = createPlanGddStateView();
|
||||
return createPlanGddStateView({
|
||||
state: 'draft',
|
||||
versions: [],
|
||||
displayGdd: null,
|
||||
pendingApproval: null,
|
||||
approvedGddRef: null,
|
||||
session: base.session
|
||||
? { ...base.session, phase: 'collecting', ...sessionOverrides }
|
||||
: null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整总控面板在策划链路下漏给用户的几块:面板本体、currentAction/计划进度、
|
||||
* 五段式紧凑进度、子 Agent 列表。策划链路的断言统一从这里查。
|
||||
*/
|
||||
function expectSupervisorRuntimePanelAbsent() {
|
||||
expect(screen.queryByLabelText('项目总控 Agent 状态')).toBeNull();
|
||||
expect(screen.queryByLabelText('当前工作状态')).toBeNull();
|
||||
expect(screen.queryByLabelText('项目总控 Agent 进度')).toBeNull();
|
||||
expect(screen.queryByLabelText('专业 Agent 实时状态')).toBeNull();
|
||||
expect(screen.queryByText(/专业 Agent 协作:/)).toBeNull();
|
||||
}
|
||||
|
||||
function openReviseDialog() {
|
||||
fireEvent.click(screen.getByRole('button', { name: '修改' }));
|
||||
return screen.getByRole('dialog');
|
||||
@@ -231,4 +275,134 @@ export function registerPlanGddApprovalTests() {
|
||||
expect(screen.queryByLabelText('立项策划 Agent待确认动作')).toBeNull();
|
||||
expect(screen.getByRole('button', { name: '批准 v1' })).not.toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the supervisor runtime panel off the planning lane while the planner is working', async () => {
|
||||
// 完整面板是给做游戏链路的:十几个专业 Agent、多步计划、逐 Agent 重试。策划链路
|
||||
// 只有一个 project-planning 子 Run、一两步计划,面板画出来的全是 D11 拓扑的内部
|
||||
// 记账(currentAction 原文、计划 1/2、「专业 Agent 协作:1」、子 Agent 卡)。
|
||||
// 状态由顶部的阶段进度承担;底部在策划正常进行时什么都不该画。
|
||||
const supervisorRunId = 'plan-root-collecting-run';
|
||||
const harness = createProjectSupervisorRuntimeHarness({
|
||||
expectedRunProfile: 'standard',
|
||||
initialRuntime: {
|
||||
runId: supervisorRunId,
|
||||
source: 'project-supervisor-plan',
|
||||
runProfile: 'standard',
|
||||
status: 'running',
|
||||
phase: 'waiting-for-delegate-receipts',
|
||||
currentTask: '剧情向恋爱养成游戏',
|
||||
currentAction: '等待 project-planning 提交 GDD',
|
||||
waitingOn: '策划子 Run 提交 Fast GDD',
|
||||
nextStep: '读取 GDD 并进入审批',
|
||||
plan: ['委派策划子 Run', '读取 GDD 并审批'],
|
||||
planSteps: [
|
||||
{ step: '委派策划子 Run', status: 'active' },
|
||||
{ step: '读取 GDD 并审批', status: 'pending' },
|
||||
],
|
||||
activePlanStepIndex: 0,
|
||||
updatedAt: 7000,
|
||||
},
|
||||
runtimeMapLoader: async () => [
|
||||
harness.runtimeState({
|
||||
agentId: 'project-planning',
|
||||
taskId: 'project-planning',
|
||||
sessionId: 'agent-session-project-planning',
|
||||
runId: 'delegated-delegation-collecting',
|
||||
source: 'agent-delegate',
|
||||
parentAgentId: 'project-supervisor',
|
||||
parentRunId: supervisorRunId,
|
||||
delegationId: 'delegation-collecting',
|
||||
runProfile: 'standard',
|
||||
status: 'running',
|
||||
phase: 'running',
|
||||
currentTask: '完成立项策划并给出 Fast GDD',
|
||||
currentAction: '整理第 1 轮澄清答案',
|
||||
updatedAt: 7000,
|
||||
}),
|
||||
],
|
||||
});
|
||||
harness.setPlanGddState(draftPlanGddState());
|
||||
const progress = await mountPlanningSurface(harness);
|
||||
|
||||
expect(within(progress).getByText('策划中')).not.toBeNull();
|
||||
expectSupervisorRuntimePanelAbsent();
|
||||
expect(screen.queryByLabelText('立项策划运行状态')).toBeNull();
|
||||
expect(screen.queryByText('等待 project-planning 提交 GDD')).toBeNull();
|
||||
expect(screen.queryByText('整理第 1 轮澄清答案')).toBeNull();
|
||||
expect(screen.queryByText(/计划 \d+\/\d+/)).toBeNull();
|
||||
});
|
||||
|
||||
it('still surfaces the clarification card on the planning lane', async () => {
|
||||
// 澄清卡是策划链路唯一需要用户动手的交互面之一,瘦身不能把它一起收掉。
|
||||
const supervisorRunId = 'plan-root-clarifying-run';
|
||||
const request = agentRuntimeUserInputRequest({
|
||||
agentId: 'project-supervisor',
|
||||
sessionId: 'supervisor-session-active',
|
||||
runId: supervisorRunId,
|
||||
requestId: 'request-plan-round-1',
|
||||
actionId: 'action-plan-round-1',
|
||||
});
|
||||
const harness = createProjectSupervisorRuntimeHarness({
|
||||
expectedRunProfile: 'standard',
|
||||
initialRuntime: {
|
||||
runId: supervisorRunId,
|
||||
source: 'project-supervisor-plan',
|
||||
runProfile: 'standard',
|
||||
status: 'waiting-for-user-input',
|
||||
phase: 'waiting-for-user-input',
|
||||
currentTask: '剧情向恋爱养成游戏',
|
||||
currentAction: '等待用户回答第 1 轮澄清',
|
||||
userInputRequest: request,
|
||||
updatedAt: 7100,
|
||||
},
|
||||
});
|
||||
harness.setPlanGddState(
|
||||
draftPlanGddState({
|
||||
clarificationRound: 0,
|
||||
awaitingAnswerFor: {
|
||||
delegationId: 'delegation-0001',
|
||||
requestId: 'request-plan-round-1',
|
||||
questionId: 'visual_direction',
|
||||
round: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await mountPlanningSurface(harness);
|
||||
|
||||
const strip = await screen.findByLabelText('立项策划运行状态');
|
||||
expect(within(strip).getByLabelText('Needs input')).not.toBeNull();
|
||||
expect(
|
||||
within(strip).getByText('首版角色规范图采用哪种美术方向?'),
|
||||
).not.toBeNull();
|
||||
expectSupervisorRuntimePanelAbsent();
|
||||
expect(screen.queryByText('等待用户回答第 1 轮澄清')).toBeNull();
|
||||
});
|
||||
|
||||
it('offers a restart on the planning lane once the planning run has failed', async () => {
|
||||
// 失败是另一处需要用户动手的时刻。恢复入口跟着搬进窄条,而不是随面板一起消失。
|
||||
const harness = createProjectSupervisorRuntimeHarness({
|
||||
expectedRunProfile: 'standard',
|
||||
initialRuntime: {
|
||||
runId: 'plan-root-failed-run',
|
||||
source: 'project-supervisor-plan',
|
||||
runProfile: 'standard',
|
||||
status: 'failed',
|
||||
phase: 'failed',
|
||||
currentTask: '剧情向恋爱养成游戏',
|
||||
currentAction: '策划子 Run 退出',
|
||||
error: '项目总控 Agent Codex 执行失败,请查看运行详情后重试',
|
||||
updatedAt: 7200,
|
||||
},
|
||||
});
|
||||
harness.setPlanGddState(draftPlanGddState());
|
||||
await mountPlanningSurface(harness);
|
||||
|
||||
const recovery = await screen.findByLabelText('立项策划失败恢复');
|
||||
expect(
|
||||
within(recovery).getByRole('button', { name: '重新启动策划' }),
|
||||
).not.toBeNull();
|
||||
expect(screen.getByRole('alert').textContent).toContain('执行失败');
|
||||
expectSupervisorRuntimePanelAbsent();
|
||||
expect(screen.queryByText('策划子 Run 退出')).toBeNull();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user