解耦设计Agent与策划V2状态
为 Design Agent 增加独立 active、瞬态回复和 reasoning 状态 保留策划 V2 会话自身的状态与消息动画链路 统一项目总控轮询使用两条策划 lane 的合并状态 移除 Design Agent 对旧 planning lane ref 的共享依赖
This commit is contained in:
@@ -584,13 +584,15 @@ export function App({
|
||||
onMakeGameFromApprovedGdd,
|
||||
}: AppProps = {}) {
|
||||
const { setTitle: setWindowTitle } = useWindowChrome();
|
||||
const [planningV2Active, setPlanningV2Active] = useState(planningStartMode);
|
||||
const planningV2ActiveRef = useRef(planningStartMode);
|
||||
const [planningV2Active, setPlanningV2Active] = useState(false);
|
||||
const planningV2ActiveRef = useRef(false);
|
||||
const [designAgentActive, setDesignAgentActive] = useState(planningStartMode);
|
||||
const designAgentActiveRef = useRef(planningStartMode);
|
||||
planningV2ActiveRef.current = planningV2Active;
|
||||
designAgentActiveRef.current = designAgentActive;
|
||||
const [designAgentView, setDesignAgentView] = useState<DesignView | null>(
|
||||
null,
|
||||
);
|
||||
const designAgentLaneRef = useRef(planningStartMode);
|
||||
const designAgentTurnRef = useRef<{
|
||||
projectPath: string;
|
||||
clientTurnId: string;
|
||||
@@ -609,7 +611,9 @@ export function App({
|
||||
projectSupervisorOnly &&
|
||||
!supervisorChatOnly &&
|
||||
!planningStartMode &&
|
||||
!planningV2Active;
|
||||
!planningV2Active &&
|
||||
!designAgentActive;
|
||||
const planningLaneActive = planningV2Active || designAgentActive;
|
||||
const [devMode] = useState(() =>
|
||||
projectSupervisorOnly ? false : isDeveloperMode(),
|
||||
);
|
||||
@@ -826,12 +830,16 @@ export function App({
|
||||
useState('');
|
||||
const planningV2TransientReplyTargetRef = useRef('');
|
||||
const planningV2VisibleReplyRef = useRef('');
|
||||
const [designAgentTransientReply, setDesignAgentTransientReplyVisible] =
|
||||
useState('');
|
||||
const designAgentTransientReplyTargetRef = useRef('');
|
||||
const designAgentVisibleReplyRef = useRef('');
|
||||
const designAgentPendingViewRef = useRef<{
|
||||
clientTurnId: string;
|
||||
projectPath: string;
|
||||
view: DesignView;
|
||||
} | null>(null);
|
||||
const [planningV2Reasoning, setPlanningV2Reasoning] = useState('');
|
||||
const [designAgentReasoning, setDesignAgentReasoning] = useState('');
|
||||
const designAgentReasoningTurnRef = useRef<{
|
||||
projectPath: string;
|
||||
clientTurnId: string;
|
||||
@@ -864,6 +872,14 @@ export function App({
|
||||
}
|
||||
}
|
||||
|
||||
function setDesignAgentTransientReplyTarget(next: string) {
|
||||
designAgentTransientReplyTargetRef.current = next;
|
||||
if (!next) {
|
||||
designAgentVisibleReplyRef.current = '';
|
||||
setDesignAgentTransientReplyVisible('');
|
||||
}
|
||||
}
|
||||
|
||||
function designAgentEventSubscriptionReady() {
|
||||
// 业务动作只读取当前订阅代次;清理与下一次 effect 建立之间不能创建
|
||||
// 一个没有订阅 effect 接管的悬挂 promise。
|
||||
@@ -909,12 +925,37 @@ export function App({
|
||||
return () => window.clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setInterval(() => {
|
||||
const target = designAgentTransientReplyTargetRef.current;
|
||||
setDesignAgentTransientReplyVisible((current) => {
|
||||
if (!target) {
|
||||
designAgentVisibleReplyRef.current = '';
|
||||
return '';
|
||||
}
|
||||
const prefix = target.startsWith(current) ? current : '';
|
||||
if (prefix === target) {
|
||||
designAgentVisibleReplyRef.current = target;
|
||||
return target;
|
||||
}
|
||||
const remaining = target.length - prefix.length;
|
||||
const step = remaining > 160 ? 4 : remaining > 48 ? 2 : 1;
|
||||
const next = target.slice(0, prefix.length + step);
|
||||
designAgentVisibleReplyRef.current = next;
|
||||
return next;
|
||||
});
|
||||
}, 50);
|
||||
return () => window.clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
function applyPlanningV2CommandResult(
|
||||
result: PlanningSessionCommandResultV2,
|
||||
clientTurnId?: string,
|
||||
) {
|
||||
planningV2ActiveRef.current = true;
|
||||
setPlanningV2Active(true);
|
||||
designAgentActiveRef.current = false;
|
||||
setDesignAgentActive(false);
|
||||
planningV2SessionRef.current = result;
|
||||
setPlanningV2Session(result);
|
||||
projectSupervisorSessionIdRef.current = result.session.sessionId;
|
||||
@@ -1004,13 +1045,15 @@ export function App({
|
||||
setPlanningV2Session(null);
|
||||
setPlanGddState(null);
|
||||
if (planningStartMode) {
|
||||
planningV2ActiveRef.current = true;
|
||||
setPlanningV2Active(true);
|
||||
designAgentLaneRef.current = true;
|
||||
planningV2ActiveRef.current = false;
|
||||
setPlanningV2Active(false);
|
||||
designAgentActiveRef.current = true;
|
||||
setDesignAgentActive(true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
designAgentLaneRef.current = false;
|
||||
designAgentActiveRef.current = false;
|
||||
setDesignAgentActive(false);
|
||||
setDesignAgentView(null);
|
||||
applyPlanningV2CommandResult(result);
|
||||
return result;
|
||||
@@ -1039,10 +1082,9 @@ export function App({
|
||||
}
|
||||
|
||||
function applyDesignView(view: DesignView, projectPath: string) {
|
||||
designAgentLaneRef.current = true;
|
||||
designAgentActiveRef.current = true;
|
||||
setDesignAgentActive(true);
|
||||
setDesignAgentView(view);
|
||||
planningV2ActiveRef.current = true;
|
||||
setPlanningV2Active(true);
|
||||
setPlanGddError(view.session.lastError);
|
||||
setChatAgentBusy(view.running);
|
||||
const conversation = designMessagesToChat(view);
|
||||
@@ -1056,8 +1098,8 @@ export function App({
|
||||
const pendingTurnId = designAgentPendingViewRef.current?.clientTurnId;
|
||||
designAgentPendingViewRef.current = null;
|
||||
applyDesignView(view, projectPath);
|
||||
setPlanningV2Reasoning('');
|
||||
setPlanningV2TransientReplyTarget('');
|
||||
setDesignAgentReasoning('');
|
||||
setDesignAgentTransientReplyTarget('');
|
||||
if (designAgentTurnRef.current?.clientTurnId === pendingTurnId) {
|
||||
designAgentTurnRef.current = null;
|
||||
}
|
||||
@@ -1068,7 +1110,7 @@ export function App({
|
||||
projectPath: string,
|
||||
clientTurnId: string,
|
||||
) {
|
||||
let target = planningV2TransientReplyTargetRef.current;
|
||||
let target = designAgentTransientReplyTargetRef.current;
|
||||
const tracked = designAgentTurnRef.current;
|
||||
if (!target.trim() && !view.running) {
|
||||
const latestAssistantText = [...view.messages]
|
||||
@@ -1076,7 +1118,7 @@ export function App({
|
||||
.find((message) => message.role !== 'user' && message.text.trim())
|
||||
?.text.trim();
|
||||
if (latestAssistantText) {
|
||||
setPlanningV2TransientReplyTarget(latestAssistantText);
|
||||
setDesignAgentTransientReplyTarget(latestAssistantText);
|
||||
target = latestAssistantText;
|
||||
}
|
||||
}
|
||||
@@ -1084,7 +1126,7 @@ export function App({
|
||||
!view.running &&
|
||||
tracked?.clientTurnId === clientTurnId &&
|
||||
target.trim() &&
|
||||
planningV2VisibleReplyRef.current !== target
|
||||
designAgentVisibleReplyRef.current !== target
|
||||
) {
|
||||
designAgentPendingViewRef.current = {
|
||||
clientTurnId,
|
||||
@@ -1102,8 +1144,8 @@ export function App({
|
||||
if (!pending) {
|
||||
return;
|
||||
}
|
||||
const target = planningV2TransientReplyTargetRef.current;
|
||||
if (target && planningV2VisibleReplyRef.current !== target) {
|
||||
const target = designAgentTransientReplyTargetRef.current;
|
||||
if (target && designAgentVisibleReplyRef.current !== target) {
|
||||
return;
|
||||
}
|
||||
commitDesignAgentView(pending.view, pending.projectPath);
|
||||
@@ -1155,8 +1197,8 @@ export function App({
|
||||
await designAgentEventSubscriptionReady();
|
||||
setChatAgentBusy(true);
|
||||
setProjectSupervisorRuntimeError('');
|
||||
setPlanningV2TransientReplyTarget('');
|
||||
setPlanningV2Reasoning('');
|
||||
setDesignAgentTransientReplyTarget('');
|
||||
setDesignAgentReasoning('');
|
||||
try {
|
||||
const view = await invoke<DesignView>('continue_design_agent_session', {
|
||||
projectPath: nextProjectPath,
|
||||
@@ -1180,7 +1222,7 @@ export function App({
|
||||
} finally {
|
||||
if (!designAgentPendingViewRef.current) {
|
||||
designAgentTurnRef.current = null;
|
||||
setPlanningV2TransientReplyTarget('');
|
||||
setDesignAgentTransientReplyTarget('');
|
||||
}
|
||||
setChatAgentBusy(false);
|
||||
}
|
||||
@@ -1642,10 +1684,11 @@ export function App({
|
||||
setPlanningV2TransientReplyTarget('');
|
||||
designAgentPendingViewRef.current = null;
|
||||
designAgentReasoningTurnRef.current = null;
|
||||
setPlanningV2Reasoning('');
|
||||
setPlanningV2Active(planningStartMode);
|
||||
planningV2ActiveRef.current = planningStartMode;
|
||||
designAgentLaneRef.current = planningStartMode;
|
||||
setDesignAgentReasoning('');
|
||||
setPlanningV2Active(false);
|
||||
planningV2ActiveRef.current = false;
|
||||
setDesignAgentActive(planningStartMode);
|
||||
designAgentActiveRef.current = planningStartMode;
|
||||
designAgentTurnRef.current = null;
|
||||
setDesignAgentView(null);
|
||||
}
|
||||
@@ -1912,7 +1955,7 @@ export function App({
|
||||
if (
|
||||
!canSubscribeTauriEvents() ||
|
||||
directCodexProductRuntime ||
|
||||
planningV2Active
|
||||
planningLaneActive
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -2015,7 +2058,7 @@ export function App({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
directCodexProductRuntime,
|
||||
planningV2Active,
|
||||
planningLaneActive,
|
||||
refreshManifest,
|
||||
updateProjectSupervisorResponseStream,
|
||||
updateProjectSupervisorRuntime,
|
||||
@@ -2023,7 +2066,7 @@ export function App({
|
||||
|
||||
useEffect(() => {
|
||||
const ready = createDesignAgentEventSubscriptionReady();
|
||||
if (!canSubscribeTauriEvents() || !planningV2Active) {
|
||||
if (!canSubscribeTauriEvents() || !designAgentActive) {
|
||||
resolveDesignAgentEventSubscriptionReady();
|
||||
return () => {
|
||||
if (designAgentEventSubscriptionReadyRef.current === ready) {
|
||||
@@ -2044,7 +2087,7 @@ export function App({
|
||||
return;
|
||||
}
|
||||
if (payload.kind === 'text' && payload.text != null) {
|
||||
setPlanningV2TransientReplyTarget(payload.text);
|
||||
setDesignAgentTransientReplyTarget(payload.text);
|
||||
}
|
||||
if (payload.reasoningText != null) {
|
||||
const reasoningTurn = designAgentReasoningTurnRef.current;
|
||||
@@ -2054,11 +2097,11 @@ export function App({
|
||||
reasoningTurn.clientTurnId === payload.clientTurnId
|
||||
) {
|
||||
reasoningTurn.text = payload.reasoningText;
|
||||
setPlanningV2Reasoning(payload.reasoningText);
|
||||
setDesignAgentReasoning(payload.reasoningText);
|
||||
}
|
||||
}
|
||||
if (payload.kind === 'tool' && payload.text) {
|
||||
setPlanningV2TransientReplyTarget(payload.text);
|
||||
setDesignAgentTransientReplyTarget(payload.text);
|
||||
}
|
||||
if (payload.view) {
|
||||
applyDesignAgentViewAfterTransient(
|
||||
@@ -2091,7 +2134,7 @@ export function App({
|
||||
// applyDesignView 读的是 refs 和当前项目路径,
|
||||
// 把它写进依赖会在每轮回复时重订事件。
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [planningV2Active]);
|
||||
}, [designAgentActive]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canSubscribeTauriEvents()) {
|
||||
@@ -2140,7 +2183,7 @@ export function App({
|
||||
if (
|
||||
!invoke ||
|
||||
directCodexProductRuntime ||
|
||||
planningV2Active ||
|
||||
planningLaneActive ||
|
||||
!nextProjectPath ||
|
||||
!sessionId ||
|
||||
!trackedRunId ||
|
||||
@@ -2236,7 +2279,7 @@ export function App({
|
||||
}, [
|
||||
localProject?.projectPath,
|
||||
directCodexProductRuntime,
|
||||
planningV2Active,
|
||||
planningLaneActive,
|
||||
projectSupervisorSessionId,
|
||||
projectSupervisorRuntime?.phase,
|
||||
projectSupervisorRuntime?.runId,
|
||||
@@ -2251,7 +2294,7 @@ export function App({
|
||||
if (
|
||||
!projectSupervisorOnly ||
|
||||
directCodexProductRuntime ||
|
||||
planningV2Active ||
|
||||
planningLaneActive ||
|
||||
!invoke ||
|
||||
!nextProjectPath ||
|
||||
!supervisorRunId
|
||||
@@ -2332,7 +2375,7 @@ export function App({
|
||||
}, [
|
||||
localProject?.projectPath,
|
||||
directCodexProductRuntime,
|
||||
planningV2Active,
|
||||
planningLaneActive,
|
||||
projectSupervisorOnly,
|
||||
projectSupervisorRuntime?.phase,
|
||||
projectSupervisorRuntime?.runId,
|
||||
@@ -2344,7 +2387,7 @@ export function App({
|
||||
(!supervisorChatOnly &&
|
||||
!(
|
||||
projectSupervisorOnly &&
|
||||
(directCodexProductRuntime || planningV2Active)
|
||||
(directCodexProductRuntime || planningLaneActive)
|
||||
)) ||
|
||||
!supervisorChatShouldFollowLatestRef.current
|
||||
) {
|
||||
@@ -2364,7 +2407,7 @@ export function App({
|
||||
directCodexTransientReply,
|
||||
directCodexTransientReplyUpdatedAt,
|
||||
directCodexProductRuntime,
|
||||
planningV2Active,
|
||||
planningLaneActive,
|
||||
projectSupervisorOnly,
|
||||
supervisorChatOnly,
|
||||
]);
|
||||
@@ -3333,14 +3376,16 @@ export function App({
|
||||
) {
|
||||
return;
|
||||
}
|
||||
planningV2ActiveRef.current = true;
|
||||
setPlanningV2Active(true);
|
||||
planningV2ActiveRef.current = Boolean(planningV2);
|
||||
setPlanningV2Active(Boolean(planningV2));
|
||||
if (planningV2) {
|
||||
designAgentLaneRef.current = false;
|
||||
designAgentActiveRef.current = false;
|
||||
setDesignAgentActive(false);
|
||||
setDesignAgentView(null);
|
||||
applyPlanningV2CommandResult(planningV2);
|
||||
} else {
|
||||
designAgentLaneRef.current = true;
|
||||
designAgentActiveRef.current = true;
|
||||
setDesignAgentActive(true);
|
||||
planningV2SessionRef.current = null;
|
||||
setPlanningV2Session(null);
|
||||
setPlanGddState(null);
|
||||
@@ -6138,7 +6183,6 @@ export function App({
|
||||
setChatAgentBusy(true);
|
||||
setProjectSupervisorRuntimeError('');
|
||||
setPlanningV2TransientReplyTarget('');
|
||||
setPlanningV2Reasoning('');
|
||||
try {
|
||||
const result = currentSessionId
|
||||
? await invoke<PlanningSessionCommandResultV2>(
|
||||
@@ -6204,13 +6248,17 @@ export function App({
|
||||
directPolicyChecked = false,
|
||||
references,
|
||||
}: ExecuteChatAgentReplyInput) {
|
||||
if (planningV2ActiveRef.current || planningStartMode) {
|
||||
if (
|
||||
planningV2ActiveRef.current ||
|
||||
designAgentActiveRef.current ||
|
||||
planningStartMode
|
||||
) {
|
||||
const nextProjectPath = resolveChatProjectPath(localProject);
|
||||
if (!nextProjectPath) {
|
||||
setProjectSupervisorRuntimeError('请先初始化本地项目');
|
||||
return;
|
||||
}
|
||||
if (designAgentLaneRef.current) {
|
||||
if (designAgentActiveRef.current) {
|
||||
await executeDesignAgentTurn(
|
||||
nextProjectPath,
|
||||
{ type: 'message', text: prompt },
|
||||
@@ -7074,7 +7122,7 @@ export function App({
|
||||
answers: Record<string, string>,
|
||||
) {
|
||||
const nextProjectPath = resolveChatProjectPath(localProject);
|
||||
if (planningV2ActiveRef.current || planningStartMode) {
|
||||
if (planningV2ActiveRef.current) {
|
||||
const current = planningV2SessionRef.current;
|
||||
const currentQuestion = current?.session.currentQuestion;
|
||||
const answer = Object.values(answers)
|
||||
@@ -11774,7 +11822,7 @@ export function App({
|
||||
(agent.runtimeStatus !== null || agent.hasRecentEvidence),
|
||||
);
|
||||
const useDesignAgentSurface =
|
||||
Boolean(designAgentView) || (planningStartMode && !planningV2Session);
|
||||
Boolean(designAgentView) || (designAgentActive && !planningV2Session);
|
||||
|
||||
if (projectSupervisorOnly && supervisorChatOnly) {
|
||||
const supervisorProjectPath =
|
||||
@@ -11807,11 +11855,13 @@ export function App({
|
||||
runtimeConfigOpen={runtimeConfigOpen}
|
||||
runtimeError={projectSupervisorRuntimeError}
|
||||
transientReply={
|
||||
planningV2Active
|
||||
? planningV2TransientReply
|
||||
: directCodexProductRuntime
|
||||
? directCodexTransientReply
|
||||
: projectSupervisorTransientReply
|
||||
designAgentActive
|
||||
? designAgentTransientReply
|
||||
: planningV2Active
|
||||
? planningV2TransientReply
|
||||
: directCodexProductRuntime
|
||||
? directCodexTransientReply
|
||||
: projectSupervisorTransientReply
|
||||
}
|
||||
hasConversationControls={projectSupervisorHasConversationControls}
|
||||
hiddenConversationCount={hiddenConversationCount}
|
||||
@@ -11857,14 +11907,16 @@ export function App({
|
||||
pendingCommand={directCodexProductRuntime ? pendingCommand : null}
|
||||
projectPath={localProject?.projectPath ?? projectPath}
|
||||
transientReply={
|
||||
planningV2Active
|
||||
? planningV2TransientReply
|
||||
: directCodexProductRuntime
|
||||
? directCodexTransientReply
|
||||
: projectSupervisorTransientReply
|
||||
designAgentActive
|
||||
? designAgentTransientReply
|
||||
: planningV2Active
|
||||
? planningV2TransientReply
|
||||
: directCodexProductRuntime
|
||||
? directCodexTransientReply
|
||||
: projectSupervisorTransientReply
|
||||
}
|
||||
showDesignReasoning={planningV2Active}
|
||||
designReasoning={planningV2Reasoning}
|
||||
showDesignReasoning={designAgentActive}
|
||||
designReasoning={designAgentReasoning}
|
||||
designReasoningEntries={
|
||||
useDesignAgentSurface ? (designAgentView?.reasoningEntries ?? []) : []
|
||||
}
|
||||
@@ -11880,7 +11932,7 @@ export function App({
|
||||
planGddError={planGddError}
|
||||
onPlanGddRefresh={() => void hydratePlanGddState()}
|
||||
onPlanGddDecision={decidePlanGdd}
|
||||
planningLane={planningV2Active}
|
||||
planningLane={planningLaneActive}
|
||||
designView={useDesignAgentSurface ? designAgentView : null}
|
||||
onDesignApprove={
|
||||
useDesignAgentSurface
|
||||
@@ -11901,8 +11953,8 @@ export function App({
|
||||
text: '',
|
||||
};
|
||||
designAgentPendingViewRef.current = null;
|
||||
setPlanningV2TransientReplyTarget('');
|
||||
setPlanningV2Reasoning('');
|
||||
setDesignAgentTransientReplyTarget('');
|
||||
setDesignAgentReasoning('');
|
||||
setChatAgentBusy(true);
|
||||
setPlanGddDecisionBusy(true);
|
||||
void designAgentEventSubscriptionReady()
|
||||
@@ -11951,7 +12003,7 @@ export function App({
|
||||
}
|
||||
if (!designAgentPendingViewRef.current) {
|
||||
designAgentTurnRef.current = null;
|
||||
setPlanningV2TransientReplyTarget('');
|
||||
setDesignAgentTransientReplyTarget('');
|
||||
}
|
||||
setChatAgentBusy(false);
|
||||
setPlanGddDecisionBusy(false);
|
||||
|
||||
Reference in New Issue
Block a user