恢复策划 Agent 实时事件订阅(876529e66 误删)

- apps/ai-game-creator-shell/src/App.tsx 重新订阅 `design-agent-update`:本轮正文与工具提示回到 transient reply,`reasoningText` 回到 `planningV2Reasoning`,`view` 回到 applyDesignAgentViewAfterTransient;Rust 侧一直照旧发射这些事件,前端却不再监听
- 补回 designAgentEventSubscriptionReadyRef 的建/解函数与 designAgentEventSubscriptionResolveRef,让 `executeDesignAgentTurn` 等待监听器挂好再发起回合,开局事件不丢
- 新增 designAgentReasoningTurnRef:回合结束(designAgentTurnRef 清空)后仍认本轮 reasoning,最后一条思考不会被视图落盘前的空隙吃掉;切项目时随 resetChatState 清空
This commit is contained in:
2026-09-21 12:21:51 +08:00
parent 317c8557f4
commit ba3bd59f24
+121
View File
@@ -33,6 +33,7 @@ import type {
ChatMessage,
DesignAgentInput,
DesignClarificationRequest,
DesignEvent,
DesignView,
InitLocalProjectResult,
LauncherImportedAttachment,
@@ -102,6 +103,10 @@ import {
setAgcPluginProjectPath,
startAvailableAgcPlugin,
} from './services/pluginHost';
import {
canSubscribeTauriEvents,
subscribeTauriEvent,
} from './services/tauriEventSubscription';
import type { HomeCreationType } from './view/home';
import {
type ProjectAgentResultSummary,
@@ -243,6 +248,20 @@ export function App({
const designAgentEventSubscriptionReadyRef = useRef<Promise<void> | null>(
null,
);
const designAgentEventSubscriptionResolveRef = useRef<(() => void) | null>(
null,
);
/**
* 本轮策划回合的思考归属。
*
* 与 `designAgentTurnRef` 分开:那个记录回合本身,回合结束就清空;这个只用来判定
* 「这条 reasoning 事件是不是本轮的」,回合结束后仍保留,最后一条思考不会在视图
* 落盘前的空隙里被丢掉。切项目时随其它聊天状态一起清空。
*/
const designAgentReasoningTurnRef = useRef<{
projectPath: string;
clientTurnId: string;
} | null>(null);
// 项目开发工作台的普通项目固定使用 DirectProject,立项策划项目走策划聊天;两条
// 链路各自独立,不通过兼容分支互相承载。
const directProjectMode = !planningV2Active;
@@ -419,6 +438,22 @@ export function App({
return designAgentEventSubscriptionReadyRef.current ?? Promise.resolve();
}
function createDesignAgentEventSubscriptionReady() {
if (!designAgentEventSubscriptionReadyRef.current) {
designAgentEventSubscriptionReadyRef.current = new Promise<void>(
(resolve) => {
designAgentEventSubscriptionResolveRef.current = resolve;
},
);
}
return designAgentEventSubscriptionReadyRef.current;
}
function resolveDesignAgentEventSubscriptionReady() {
designAgentEventSubscriptionResolveRef.current?.();
designAgentEventSubscriptionResolveRef.current = null;
}
useEffect(() => {
const timer = window.setInterval(() => {
const target = planningV2TransientReplyTargetRef.current;
@@ -442,6 +477,83 @@ export function App({
return () => window.clearInterval(timer);
}, []);
/**
* 策划 Agent 的实时事件流:本轮流式正文、思考过程和回合中途的视图都靠它推给界面。
*
* 订阅建立是异步的,而回合由一个 invoke 发起;`designAgentEventSubscriptionReady()`
* 让回合等监听器挂好再开始,避免开头几个事件丢掉。事件只认当前项目;有在跑的回合时
* 还要认本轮 `clientTurnId`,迟到的上一轮事件不会画到这一轮上。
*/
useEffect(() => {
const ready = createDesignAgentEventSubscriptionReady();
if (!canSubscribeTauriEvents() || !planningV2Active) {
resolveDesignAgentEventSubscriptionReady();
return () => {
if (designAgentEventSubscriptionReadyRef.current === ready) {
designAgentEventSubscriptionReadyRef.current = null;
createDesignAgentEventSubscriptionReady();
}
};
}
let cleanup: (() => void) | null = null;
let disposed = false;
void subscribeTauriEvent<DesignEvent>('design-agent-update', (event) => {
const payload = event.payload;
const tracked = designAgentTurnRef.current;
if (
payload.projectPath !== localProjectPathRef.current ||
(tracked && payload.clientTurnId !== tracked.clientTurnId)
) {
return;
}
if (
(payload.kind === 'text' || payload.kind === 'tool') &&
payload.text
) {
setPlanningV2TransientReplyTarget(payload.text);
}
if (payload.reasoningText != null) {
const reasoningTurn = designAgentReasoningTurnRef.current;
if (
reasoningTurn?.projectPath === payload.projectPath &&
reasoningTurn.clientTurnId === payload.clientTurnId
) {
setPlanningV2Reasoning(payload.reasoningText);
}
}
if (payload.view) {
applyDesignAgentViewAfterTransient(
payload.view,
payload.projectPath,
payload.clientTurnId,
);
}
})
.then((unlisten) => {
resolveDesignAgentEventSubscriptionReady();
if (disposed) {
unlisten();
return;
}
cleanup = unlisten;
})
.catch(() => {
resolveDesignAgentEventSubscriptionReady();
});
return () => {
disposed = true;
cleanup?.();
resolveDesignAgentEventSubscriptionReady();
if (designAgentEventSubscriptionReadyRef.current === ready) {
designAgentEventSubscriptionReadyRef.current = null;
createDesignAgentEventSubscriptionReady();
}
};
// 订阅只跟着「是否走策划 Agent」这条链路;回调里的项目/回合判据都走 refs,
// 把它们写进依赖会在每轮回复时重订事件。
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [planningV2Active]);
function applyPlanningV2CommandResult(
result: PlanningSessionCommandResultV2,
clientTurnId?: string,
@@ -712,6 +824,10 @@ export function App({
projectPath: nextProjectPath,
clientTurnId,
};
designAgentReasoningTurnRef.current = {
projectPath: nextProjectPath,
clientTurnId,
};
designAgentPendingViewRef.current = null;
await designAgentEventSubscriptionReady();
setChatAgentBusy(true);
@@ -1582,6 +1698,7 @@ export function App({
planningV2ActiveRef.current = planningStartMode;
designAgentLaneRef.current = planningStartMode;
designAgentTurnRef.current = null;
designAgentReasoningTurnRef.current = null;
setDesignAgentView(null);
}
@@ -2689,6 +2806,10 @@ export function App({
projectPath: nextProjectPath,
clientTurnId,
};
designAgentReasoningTurnRef.current = {
projectPath: nextProjectPath,
clientTurnId,
};
designAgentPendingViewRef.current = null;
setPlanningV2TransientReplyTarget('');
setPlanningV2Reasoning('');