修复策划 Agent 推理展示生命周期

按项目与回合绑定 reasoning 事件

保留回合结束后的思考过程并在新回合清理

补充默认折叠的展开收起交互与定向测试

更新推理与正文分离里程碑进度
This commit is contained in:
2026-09-14 14:47:33 +08:00
parent f7c8b9f217
commit 9f40f044a5
5 changed files with 119 additions and 4 deletions
+25 -1
View File
@@ -833,6 +833,11 @@ export function App({
view: DesignView;
} | null>(null);
const [planningV2Reasoning, setPlanningV2Reasoning] = useState('');
const designAgentReasoningTurnRef = useRef<{
projectPath: string;
clientTurnId: string;
text: string;
} | null>(null);
const planningV2TurnRef = useRef<{
projectPath: string;
clientTurnId: string;
@@ -1125,6 +1130,11 @@ export function App({
projectPath: nextProjectPath,
clientTurnId,
};
designAgentReasoningTurnRef.current = {
projectPath: nextProjectPath,
clientTurnId,
text: '',
};
designAgentPendingViewRef.current = null;
await designAgentEventSubscriptionReady();
setChatAgentBusy(true);
@@ -1648,6 +1658,7 @@ export function App({
setPlanningV2Session(null);
setPlanningV2TransientReplyTarget('');
designAgentPendingViewRef.current = null;
designAgentReasoningTurnRef.current = null;
setPlanningV2Reasoning('');
setPlanningV2Active(planningStartMode);
planningV2ActiveRef.current = planningStartMode;
@@ -2108,7 +2119,15 @@ export function App({
setPlanningV2TransientReplyTarget(payload.text);
}
if (payload.reasoningText != null) {
setPlanningV2Reasoning(payload.reasoningText);
const reasoningTurn = designAgentReasoningTurnRef.current;
if (
reasoningTurn &&
reasoningTurn.projectPath === payload.projectPath &&
reasoningTurn.clientTurnId === payload.clientTurnId
) {
reasoningTurn.text = payload.reasoningText;
setPlanningV2Reasoning(payload.reasoningText);
}
}
if (payload.kind === 'tool' && payload.text) {
setPlanningV2TransientReplyTarget(payload.text);
@@ -11936,6 +11955,11 @@ export function App({
projectPath: nextProjectPath,
clientTurnId,
};
designAgentReasoningTurnRef.current = {
projectPath: nextProjectPath,
clientTurnId,
text: '',
};
designAgentPendingViewRef.current = null;
setPlanningV2TransientReplyTarget('');
setPlanningV2Reasoning('');
@@ -263,8 +263,11 @@ export function ProjectSupervisorView({
</div>
))}
{designReasoning ? (
<details className="design-agent-reasoning">
<summary></summary>
<details
className="design-agent-reasoning"
aria-label="策划 Agent 思考过程"
>
<summary></summary>
<pre>{designReasoning}</pre>
</details>
) : null}
@@ -7,6 +7,7 @@ import {
React,
render,
screen,
setComposerText,
waitFor,
} from './harness';
@@ -55,6 +56,24 @@ function designClarificationView() {
};
}
function designConversationView() {
return {
session: {
sessionId: 'design-session-reasoning',
projectId: 'local-project-draft',
currentPhase: 'concept',
approvedPhases: [],
pendingApproval: null,
pendingClarification: null,
turnIndex: 1,
lastError: null,
},
messages: [],
running: false,
canRetry: false,
};
}
export function registerDesignAgentSurfaceTests() {
it('hydrates an existing design session and decides approval through design commands', async () => {
const harness = createProjectSupervisorRuntimeHarness({
@@ -141,4 +160,54 @@ export function registerDesignAgentSurfaceTests() {
).toBe(true);
});
});
it('keeps the current turn reasoning after completion and supports collapse/expand', async () => {
const harness = createProjectSupervisorRuntimeHarness({
designAgentView: designConversationView(),
designAgentContinueView: designConversationView(),
});
window.__TAURI__ = {
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
window.history.pushState({}, '', '/');
render(
React.createElement(App, {
initialProjectPath: harness.projectPath,
orchestrationMode: 'single-supervisor',
planningStartMode: true,
projectSupervisorOnly: true,
}),
);
const input = await screen.findByLabelText('项目需求');
await setComposerText(input, '请给出核心玩法方案');
fireEvent.submit(input.closest('form') as HTMLFormElement);
await waitFor(() => {
expect(
harness.invoke.mock.calls.some(
([command]) => command === 'continue_design_agent_session',
),
).toBe(true);
});
const continueCall = [...harness.invoke.mock.calls]
.reverse()
.find(([command]) => command === 'continue_design_agent_session');
const clientTurnId = String(
(continueCall?.[1] as { clientTurnId?: string }).clientTurnId,
);
harness.emitDesignAgentEvent({
projectPath: harness.projectPath,
clientTurnId,
kind: 'reasoning',
reasoningText: '先分析需求,再组织方案。',
});
const summary = await screen.findByText('思考过程(点击展开)');
const details = summary.closest('details') as HTMLDetailsElement;
expect(details.open).toBe(false);
fireEvent.click(summary);
expect(details.open).toBe(true);
expect(screen.getByText('先分析需求,再组织方案。')).not.toBeNull();
});
}
@@ -741,6 +741,9 @@ function createProjectSupervisorRuntimeHarness({
};
}) => void)
| null = null;
let designAgentUpdateHandler:
| ((event: { payload: Record<string, unknown> }) => void)
| null = null;
const conversationRecord = (
role: 'user' | 'assistant',
@@ -1113,6 +1116,10 @@ function createProjectSupervisorRuntimeHarness({
if (eventName === 'game-creator-agent-progress') {
progressHandler = handler as unknown as typeof progressHandler;
}
if (eventName === 'design-agent-update') {
designAgentUpdateHandler =
handler as unknown as typeof designAgentUpdateHandler;
}
return () => {
if (runtimeUpdateHandler === handler) {
runtimeUpdateHandler = null;
@@ -1123,6 +1130,9 @@ function createProjectSupervisorRuntimeHarness({
if (progressHandler === handler) {
progressHandler = null;
}
if (designAgentUpdateHandler === handler) {
designAgentUpdateHandler = null;
}
};
},
);
@@ -1149,6 +1159,9 @@ function createProjectSupervisorRuntimeHarness({
setPlanningV2Result(state: Record<string, unknown> | null) {
currentPlanningV2Result = state;
},
emitDesignAgentEvent(payload: Record<string, unknown>) {
designAgentUpdateHandler?.({ payload });
},
setPlanningV2StartResult(state: Record<string, unknown> | null) {
currentPlanningV2StartResult = state;
},