Files
Genarrative/apps/ai-game-creator-shell/src/features/app-shell/useDeveloperAgentPanel.ts
T
suzmii 6618207b5b 修复运行模块切换版本报 listeners[eventId].handlerId:事件订阅改为自建登记 + 幂等注销
- 新增 apps/ai-game-creator-shell/src/services/tauriEventSubscription.ts:AGC 唯一的事件订阅入口。真实 WebView 内用 plugin:event|listen + transformCallback 自建登记,注销时先 unregisterCallback(handlerId)(callbacks.delete,幂等、缺条目也不抛)再发 plugin:event|unlisten,并对重复注销去重,注销失败只在 console.warn 显式记录;非原生环境沿用注入的 event.listen(两参调用形状不变),未接桥接时返回空操作。tauri 2.11 的注销脚本会先读注册表条目再摘回调,而条目由注册 eval 异步写入,与 IPC 返回无序,这一层自建登记正好绕开该竞态(上游 tauri-apps/tauri#15799 / #15800,2.12 起脚本自带判空,升级后可删掉 internals 分支)。
- App.tsx:5 处事件订阅(game-creator-direct-turn-update / agent-progress / agent-runtime-update / planning-session-v2-stream / manifest-invalidated)与角色 Agent 流式回复监听改用订阅入口,守卫由 window.__TAURI__?.event?.listen 换成 canSubscribeTauriEvents()。
- features/app-shell/useDeveloperAgentPanel.ts:Agent Runtime 与角色 Agent 流式回复两处订阅改用订阅入口。
- services/errorReportingBridge.ts:error-report-updated 订阅改用订阅入口,不再直接依赖库内 listen。
- components/AppUpdateNotice.tsx:更新下载进度订阅改用订阅入口。
- components/WindowChrome.tsx:窗口尺寸监听不再走 nativeWindow.onResized,改为订阅 tauri://resize(限定当前窗口),避开库内注销竞态。
- 新增 tests/tauriEventFake.ts:与 tauri 2.11.3 等价的 Tauri 事件替身(注册表条目由注册 eval 异步写入、库内注销脚本读缺失条目即抛并留痕、全局桥 event.listen 按库内实现返回会读条目的注销函数)。
- 新增 tests/tauriEventSubscription.test.ts(7 例):注册 eval 未落地就注销、重复注销只摘一次、注册落地后能投递且注销后不再投递、真实 WebView 不走库内注销脚本、无 internals 时回落注入桥接、无桥接时空操作。
- 新增 tests/runVersionSwitchEventSubscription.test.tsx(2 例):真实 launcher + 运行模块连续切换两次版本(断言 start_local_game_preview 被调用)、卸载后后端订阅与 JS 回调都不泄漏、切换版本后清单失效事件仍能送达并重读清单。
- docs/project-memory/shared-memory/pitfalls.md:记录该竞态的成因、触发面、处理取舍与变异验证结论。
2026-09-12 17:23:22 +08:00

1893 lines
61 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
type FormEvent,
type UIEvent,
useEffect,
useLayoutEffect,
} from 'react';
import { AGENT_CHAT_SCROLL_BOTTOM_THRESHOLD } from '../../app/constants';
import { useEscapeToClose } from '../../app/dialogs';
import { resolveTauriInvoke } from '../../app/tauri';
import type {
AgentChatPendingRuntimeRun,
AgentConversationSessionListResult,
AgentConversationSessionRecord,
AgentGoalRecord,
AgentRuntimeResult,
AgentRuntimeSteerResult,
GameCreatorAgentRuntimeUpdateEvent,
GameCreatorChatAgentReply,
GameCreatorLlmConfigStatus,
GameCreatorRoleAgentChatStreamEvent,
LauncherAgentChatAgent,
LocalConversationResult,
TauriInvoke,
} from '../../app/types';
import {
canSubscribeTauriEvents,
subscribeTauriEvent,
} from '../../services/tauriEventSubscription';
import { type LauncherView } from '../../view/layout';
import {
agentGoalStatusIsTerminal,
agentRuntimeConversationStatus,
agentRuntimeNeedsUserInput,
agentRuntimeStartedRunId,
agentRuntimeStartStatus,
agentRuntimeStateFromResult,
agentRuntimeSteerStatus,
createAgentChatRunId,
createLocalConversationDraftMessage,
isAgentRuntimeTerminalState,
isMissingAgentGoalCommandError,
isMissingAgentRuntimeResumeCommandError,
isMissingAgentSessionCommandError,
matchingAgentRuntimeForSteer,
mergeAgentGoalRecordFromRuntime,
normalizeAgentRuntimeState,
} from '../agent-runtime';
import {
formatAgentLlmConfigWarning,
formatCodexRuntimeCapabilities,
llmStatusForAgentCard,
} from '../project-summary/agentPresentation';
import {
isAbsoluteProjectPath,
projectPathHasControlCharacter,
} from '../project-summary/projectSummary';
import { createDeveloperAgentControls } from './developerAgentControls';
import { useDeveloperAgentState } from './useDeveloperAgentState';
export function useDeveloperAgentPanel(launcherView: LauncherView) {
const state = useDeveloperAgentState();
const {
launcherAgentChatAgents,
agentChatProjectPath,
setAgentChatProjectPath,
agentChatSelectedAgentId,
setAgentChatSelectedAgentId,
agentChatMessages,
setAgentChatMessages,
agentChatSessions,
setAgentChatSessions,
agentChatSelectedSessionId,
setAgentChatSelectedSessionId,
agentChatActiveSessionId,
setAgentChatActiveSessionId,
agentChatLegacySessionMode,
setAgentChatLegacySessionMode,
agentChatConversationPath,
setAgentChatConversationPath,
agentChatSessionStatus,
setAgentChatSessionStatus,
agentChatInput,
setAgentChatInput,
agentChatInteractionMode,
setAgentChatInteractionMode,
agentChatRunSubmitMode,
setAgentChatRunSubmitMode,
agentChatReplyPhase,
setAgentChatReplyPhase,
agentChatStatus,
setAgentChatStatus,
agentChatBusy,
setAgentChatBusy,
agentChatMessagesRef,
agentChatShouldFollowLatestRef,
agentChatBackgroundBusy,
setAgentChatBackgroundBusy,
agentChatPendingRuntimeRun,
setAgentChatPendingRuntimeRunState,
agentChatLlmConfigStatus,
setAgentChatLlmConfigStatus,
agentChatLlmStatus,
setAgentChatLlmStatus,
agentChatRuntime,
setAgentChatRuntime,
agentChatActiveRuntime,
setAgentChatActiveRuntime,
agentChatRuntimeError,
setAgentChatRuntimeError,
agentChatGoal,
setAgentChatGoal,
agentChatGoalError,
setAgentChatGoalError,
agentChatGoalDialog,
setAgentChatGoalDialog,
agentChatGoalDialogError,
setAgentChatGoalDialogError,
agentChatResumeConfirmation,
setAgentChatResumeConfirmation,
agentChatLoadVersionRef,
agentChatRuntimeResumeProjectPathRef,
agentChatProjectPathRef,
agentChatSelectedAgentIdRef,
agentChatSelectedSessionIdRef,
agentChatActiveSessionIdRef,
agentChatPendingRuntimeRunRef,
agentChatRuntimeSyncingRunIdsRef,
agentChatRuntimeSyncConversationRef,
} = state;
useEffect(() => {
setAgentChatRunSubmitMode('steer');
}, [
agentChatSelectedAgentId,
agentChatSelectedSessionId,
setAgentChatRunSubmitMode,
]);
function setAgentChatPendingRuntimeRun(
pendingRun: AgentChatPendingRuntimeRun | null,
) {
agentChatPendingRuntimeRunRef.current = pendingRun;
setAgentChatPendingRuntimeRunState(pendingRun);
}
async function syncAgentChatConversationAfterRuntime(
invoke: TauriInvoke,
pendingRun: AgentChatPendingRuntimeRun,
) {
let latestResult: LocalConversationResult | null = null;
for (let attempt = 0; attempt < 4; attempt += 1) {
if (
agentChatProjectPathRef.current.trim() !== pendingRun.projectPath ||
agentChatSelectedAgentIdRef.current !== pendingRun.agentId ||
(pendingRun.sessionId !== null &&
agentChatSelectedSessionIdRef.current !== pendingRun.sessionId)
) {
return;
}
try {
latestResult = await invoke<LocalConversationResult>(
'read_local_conversation',
{
projectPath: pendingRun.projectPath,
agentId: pendingRun.agentId,
...(pendingRun.sessionId
? { sessionId: pendingRun.sessionId }
: {}),
},
);
} catch (error) {
if (attempt === 3) {
setAgentChatStatus(
`Agent 已结束,但同步对话失败:${
error instanceof Error ? error.message : String(error)
}`,
);
break;
}
}
const newMessages =
latestResult?.messages.slice(pendingRun.messageCount) ?? [];
if (
newMessages.some((message) => message.role === 'assistant') ||
attempt === 3
) {
break;
}
await new Promise<void>((resolve) => {
window.setTimeout(resolve, 40);
});
}
if (
latestResult &&
agentChatProjectPathRef.current.trim() === pendingRun.projectPath &&
agentChatSelectedAgentIdRef.current === pendingRun.agentId &&
(pendingRun.sessionId === null ||
agentChatSelectedSessionIdRef.current === pendingRun.sessionId)
) {
setAgentChatMessages(latestResult.messages);
setAgentChatConversationPath(latestResult.path);
updateAgentChatSessionMessageCount(
pendingRun.sessionId,
latestResult.messages.length,
);
setAgentChatStatus(
`已同步 ${latestResult.messages.length} 条:${latestResult.path}`,
);
}
if (pendingRun.sessionId) {
try {
const goal = await invoke<AgentGoalRecord | null>(
'read_game_creator_agent_goal',
{
projectPath: pendingRun.projectPath,
agentId: pendingRun.agentId,
sessionId: pendingRun.sessionId,
},
);
if (
agentChatProjectPathRef.current.trim() === pendingRun.projectPath &&
agentChatSelectedAgentIdRef.current === pendingRun.agentId &&
agentChatSelectedSessionIdRef.current === pendingRun.sessionId
) {
setAgentChatGoal(goal);
setAgentChatGoalError('');
}
} catch (error) {
if (
!isMissingAgentGoalCommandError(error) &&
agentChatProjectPathRef.current.trim() === pendingRun.projectPath &&
agentChatSelectedAgentIdRef.current === pendingRun.agentId &&
agentChatSelectedSessionIdRef.current === pendingRun.sessionId
) {
setAgentChatGoalError(
`Goal 状态读取失败:${
error instanceof Error ? error.message : String(error)
}`,
);
}
}
}
if (agentChatPendingRuntimeRunRef.current?.runId === pendingRun.runId) {
setAgentChatPendingRuntimeRun(null);
}
}
agentChatRuntimeSyncConversationRef.current =
syncAgentChatConversationAfterRuntime;
useLayoutEffect(() => {
const messageList = agentChatMessagesRef.current;
if (messageList && agentChatShouldFollowLatestRef.current) {
messageList.scrollTop = messageList.scrollHeight;
}
}, [
agentChatMessages,
agentChatPendingRuntimeRun,
agentChatReplyPhase,
agentChatStatus,
agentChatMessagesRef,
agentChatShouldFollowLatestRef,
]);
function handleAgentChatMessagesScroll(event: UIEvent<HTMLDivElement>) {
const messageList = event.currentTarget;
const distanceFromBottom =
messageList.scrollHeight -
messageList.scrollTop -
messageList.clientHeight;
agentChatShouldFollowLatestRef.current =
distanceFromBottom <= AGENT_CHAT_SCROLL_BOTTOM_THRESHOLD;
}
useEffect(() => {
const invoke = resolveTauriInvoke();
// WorkspaceLauncher stays mounted while users work in a project. The
// legacy developer Agent Runtime listener must therefore exist only on
// the explicit developer Agent chat surface, never behind direct Codex
// project workbenches.
if (
launcherView !== 'agent-chat' ||
!canSubscribeTauriEvents() ||
!invoke
) {
return;
}
let cleanup: (() => void) | null = null;
let disposed = false;
void subscribeTauriEvent<GameCreatorAgentRuntimeUpdateEvent>(
'game-creator-agent-runtime-update',
(event) => {
const payload = event.payload;
if (
payload.projectPath !== agentChatProjectPathRef.current ||
payload.agentId !== agentChatSelectedAgentIdRef.current
) {
return;
}
const runtimeState = agentRuntimeStateFromResult(payload.runtime);
if (
agentChatActiveSessionIdRef.current === null ||
payload.runtime.state.sessionId ===
agentChatActiveSessionIdRef.current
) {
setAgentChatActiveRuntime((current) =>
agentRuntimeStateFromResult(payload.runtime, current),
);
}
if (
agentChatSelectedSessionIdRef.current !== null &&
payload.runtime.state.sessionId !==
agentChatSelectedSessionIdRef.current
) {
return;
}
setAgentChatRuntime((current) =>
agentRuntimeStateFromResult(payload.runtime, current),
);
setAgentChatGoal((current) =>
mergeAgentGoalRecordFromRuntime(current, runtimeState),
);
setAgentChatRuntimeError('');
const pendingRun = agentChatPendingRuntimeRunRef.current;
if (
!pendingRun ||
pendingRun.projectPath !== payload.projectPath ||
pendingRun.agentId !== payload.agentId ||
pendingRun.runId !== payload.runId ||
(pendingRun.sessionId !== null &&
pendingRun.sessionId !== payload.runtime.state.sessionId)
) {
return;
}
setAgentChatStatus(agentRuntimeConversationStatus(runtimeState));
if (
isAgentRuntimeTerminalState(runtimeState) &&
!agentChatRuntimeSyncingRunIdsRef.current.has(pendingRun.runId)
) {
const syncConversation = agentChatRuntimeSyncConversationRef.current;
if (!syncConversation) {
return;
}
agentChatRuntimeSyncingRunIdsRef.current.add(pendingRun.runId);
void syncConversation(invoke, pendingRun).finally(() => {
agentChatRuntimeSyncingRunIdsRef.current.delete(pendingRun.runId);
});
}
},
)
.then((unlisten) => {
if (disposed) {
unlisten();
return;
}
cleanup = unlisten;
})
.catch((error) => {
if (disposed) {
return;
}
setAgentChatRuntimeError(
`Runtime 订阅不可用:${
error instanceof Error ? error.message : String(error)
}`,
);
});
return () => {
disposed = true;
cleanup?.();
};
}, [
agentChatActiveSessionIdRef,
agentChatPendingRuntimeRunRef,
agentChatProjectPathRef,
agentChatRuntimeSyncConversationRef,
agentChatRuntimeSyncingRunIdsRef,
agentChatSelectedAgentIdRef,
agentChatSelectedSessionIdRef,
launcherView,
setAgentChatActiveRuntime,
setAgentChatGoal,
setAgentChatRuntime,
setAgentChatRuntimeError,
setAgentChatStatus,
]);
useEffect(() => {
const invoke = resolveTauriInvoke();
const pendingRun = agentChatPendingRuntimeRun;
if (!invoke || !pendingRun) {
return;
}
let disposed = false;
let inFlight = false;
const pollRuntime = async () => {
if (disposed || inFlight) {
return;
}
inFlight = true;
try {
const runtime = await invoke<AgentRuntimeResult>(
'read_game_creator_agent_runtime',
{
projectPath: pendingRun.projectPath,
agentId: pendingRun.agentId,
...(pendingRun.sessionId
? { sessionId: pendingRun.sessionId }
: {}),
},
);
if (
disposed ||
agentChatPendingRuntimeRunRef.current?.runId !== pendingRun.runId ||
agentChatProjectPathRef.current.trim() !== pendingRun.projectPath ||
agentChatSelectedAgentIdRef.current !== pendingRun.agentId
) {
return;
}
const runtimeState = agentRuntimeStateFromResult(runtime);
const tracksPendingRun =
runtimeState.runId === pendingRun.runId ||
(runtimeState.recentTasks ?? []).some(
(task) => task.runId === pendingRun.runId,
);
if (!tracksPendingRun) {
return;
}
if (
pendingRun.sessionId !== null &&
runtimeState.sessionId !== pendingRun.sessionId
) {
return;
}
setAgentChatRuntime((current) =>
normalizeAgentRuntimeState(runtimeState, current),
);
setAgentChatGoal((current) =>
mergeAgentGoalRecordFromRuntime(current, runtimeState),
);
if (
agentChatActiveSessionIdRef.current === null ||
runtimeState.sessionId === agentChatActiveSessionIdRef.current
) {
setAgentChatActiveRuntime((current) =>
normalizeAgentRuntimeState(runtimeState, current),
);
}
setAgentChatRuntimeError('');
setAgentChatStatus(agentRuntimeConversationStatus(runtimeState));
if (
isAgentRuntimeTerminalState(runtimeState) &&
!agentChatRuntimeSyncingRunIdsRef.current.has(pendingRun.runId)
) {
const syncConversation = agentChatRuntimeSyncConversationRef.current;
if (!syncConversation) {
return;
}
agentChatRuntimeSyncingRunIdsRef.current.add(pendingRun.runId);
void syncConversation(invoke, pendingRun).finally(() => {
agentChatRuntimeSyncingRunIdsRef.current.delete(pendingRun.runId);
});
}
} catch (error) {
if (!disposed) {
setAgentChatRuntimeError(
`Runtime 状态刷新失败:${
error instanceof Error ? error.message : String(error)
}`,
);
}
} finally {
inFlight = false;
}
};
void pollRuntime();
const timer = window.setInterval(() => {
void pollRuntime();
}, 750);
return () => {
disposed = true;
window.clearInterval(timer);
};
}, [
agentChatActiveSessionIdRef,
agentChatPendingRuntimeRun,
agentChatPendingRuntimeRunRef,
agentChatProjectPathRef,
agentChatRuntimeSyncConversationRef,
agentChatRuntimeSyncingRunIdsRef,
agentChatSelectedAgentIdRef,
setAgentChatActiveRuntime,
setAgentChatGoal,
setAgentChatRuntime,
setAgentChatRuntimeError,
setAgentChatStatus,
]);
useEffect(() => {
if (launcherView !== 'agent-chat') {
return;
}
void loadAgentChatLlmStatus();
// Reload only when the active launcher selection changes.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [launcherView, agentChatSelectedAgentId]);
function validateAgentChatProjectPath(projectPath = agentChatProjectPath) {
const trimmedProjectPath = projectPath.trim();
if (!trimmedProjectPath || !isAbsoluteProjectPath(trimmedProjectPath)) {
setAgentChatStatus('请提供项目绝对路径');
return null;
}
if (projectPathHasControlCharacter(trimmedProjectPath)) {
setAgentChatStatus('项目目录不能包含控制字符');
return null;
}
return trimmedProjectPath;
}
function selectedLauncherAgentChatAgent(
agentId = agentChatSelectedAgentId,
): LauncherAgentChatAgent | null {
return (
launcherAgentChatAgents.find((agent) => agent.id === agentId) ??
launcherAgentChatAgents[0] ??
null
);
}
function selectedLauncherAgentChatSession(
sessionId = agentChatSelectedSessionId,
): AgentConversationSessionRecord | null {
if (!sessionId) {
return null;
}
return (
agentChatSessions.find((session) => session.sessionId === sessionId) ??
null
);
}
function agentChatSessionInvokeArgs(sessionId: string | null) {
return sessionId ? { sessionId } : {};
}
function resetAgentChatSessionView() {
agentChatShouldFollowLatestRef.current = true;
setAgentChatReplyPhase('idle');
setAgentChatPendingRuntimeRun(null);
setAgentChatRunSubmitMode('steer');
setAgentChatSessions([]);
setAgentChatSelectedSessionId(null);
setAgentChatActiveSessionId(null);
setAgentChatLegacySessionMode(false);
setAgentChatActiveRuntime(null);
setAgentChatRuntime(null);
setAgentChatRuntimeError('');
setAgentChatGoal(null);
setAgentChatGoalError('');
setAgentChatGoalDialog(null);
setAgentChatGoalDialogError('');
setAgentChatConversationPath('');
setAgentChatSessionStatus('');
setAgentChatMessages([]);
}
function updateAgentChatSessionMessageCount(
sessionId: string | null,
messageCount: number,
) {
if (!sessionId) {
return;
}
setAgentChatSessions((sessions) =>
sessions.map((session) =>
session.sessionId === sessionId
? { ...session, messageCount, updatedAt: Date.now() }
: session,
),
);
}
function getCurrentAgentChatLlmWarning(
agent = selectedLauncherAgentChatAgent(),
) {
if (!agent) {
return null;
}
return formatAgentLlmConfigWarning(agentChatLlmConfigStatus, agent);
}
async function loadAgentChatLlmStatus() {
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatLlmConfigStatus(null);
setAgentChatLlmStatus('需要在 Tauri App 内检查 LLM 配置');
return;
}
setAgentChatLlmStatus('正在检查 LLM 配置');
try {
const status = await invoke<GameCreatorLlmConfigStatus>(
'check_game_creator_llm_config',
);
setAgentChatLlmConfigStatus(status);
const agent = selectedLauncherAgentChatAgent();
const agentStatus = agent ? llmStatusForAgentCard(status, agent) : null;
if (!agentStatus) {
setAgentChatLlmStatus('当前 Agent 智能服务状态不可用');
return;
}
setAgentChatLlmStatus(
agentStatus.configured
? `当前 Agent 智能服务已连接;${formatCodexRuntimeCapabilities(agentStatus)}`
: `当前 Agent 智能服务未就绪,请登录或重新登录后重试;${formatCodexRuntimeCapabilities(agentStatus)}`,
);
} catch (error) {
setAgentChatLlmConfigStatus(null);
setAgentChatLlmStatus(
`LLM 状态读取失败:${error instanceof Error ? error.message : String(error)}`,
);
}
}
function cancelAgentChatRuntimeResume() {
setAgentChatResumeConfirmation(null);
setAgentChatStatus('已取消恢复 Agent Runtime 任务');
}
async function confirmAgentChatRuntimeResume() {
const pending = agentChatResumeConfirmation;
const invoke = resolveTauriInvoke();
if (!pending || !invoke) {
return;
}
setAgentChatResumeConfirmation(null);
setAgentChatStatus('正在恢复 Agent Runtime 任务');
try {
await invoke<AgentRuntimeResult[]>(
'confirm_resume_game_creator_agent_runtime_tasks',
{ projectPath: pending.projectPath },
);
if (agentChatProjectPathRef.current.trim() !== pending.projectPath) {
return;
}
agentChatRuntimeResumeProjectPathRef.current = pending.projectPath;
await loadAgentChatConversation(
agentChatSelectedAgentIdRef.current,
pending.projectPath,
agentChatSelectedSessionIdRef.current,
);
setAgentChatStatus('已确认恢复 Agent Runtime 任务');
} catch (error) {
if (agentChatProjectPathRef.current.trim() !== pending.projectPath) {
return;
}
agentChatRuntimeResumeProjectPathRef.current = null;
setAgentChatStatus(
`Agent Runtime 恢复失败:${
error instanceof Error ? error.message : String(error)
}`,
);
}
}
async function resumeAgentChatRuntimeTasksIfNeeded(
invoke: TauriInvoke,
projectPathForChat: string,
) {
if (agentChatRuntimeResumeProjectPathRef.current === projectPathForChat) {
return '';
}
try {
await invoke<AgentRuntimeResult[]>(
'resume_game_creator_agent_runtime_tasks',
{ projectPath: projectPathForChat },
);
agentChatRuntimeResumeProjectPathRef.current = projectPathForChat;
return '';
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (isMissingAgentRuntimeResumeCommandError(error)) {
agentChatRuntimeResumeProjectPathRef.current = projectPathForChat;
return '';
}
if (message.includes('项目权限策略要求用户确认:agent.resume')) {
agentChatRuntimeResumeProjectPathRef.current = projectPathForChat;
setAgentChatResumeConfirmation({
projectPath: projectPathForChat,
detail: `恢复 ${projectPathForChat} 中未完成的 Agent Runtime 任务`,
});
return ';等待确认恢复 Agent Runtime 任务';
}
if (message.includes('项目权限策略拒绝执行:agent.resume')) {
agentChatRuntimeResumeProjectPathRef.current = projectPathForChat;
return ';项目策略禁止恢复 Agent Runtime 任务';
}
agentChatRuntimeResumeProjectPathRef.current = null;
return `Agent Runtime 恢复检查失败:${message}`;
}
}
useEscapeToClose(
cancelAgentChatRuntimeResume,
agentChatResumeConfirmation !== null,
);
function closeAgentChatGoalDialog() {
if (agentChatBackgroundBusy) {
return;
}
setAgentChatGoalDialog(null);
setAgentChatGoalDialogError('');
}
useEscapeToClose(
closeAgentChatGoalDialog,
agentChatGoalDialog !== null && !agentChatBackgroundBusy,
);
async function handleOpenProjectSupervisorChatWindow() {
if (agentChatBusy || agentChatBackgroundBusy) {
return;
}
const projectPathForChat = validateAgentChatProjectPath();
if (!projectPathForChat) {
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
setAgentChatStatus('正在打开项目总控对话');
try {
await invoke('open_project_supervisor_chat_window', {
projectPath: projectPathForChat,
});
setAgentChatStatus('已打开项目总控对话');
} catch (error) {
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
}
}
async function handleAgentChatPickProjectDirectory() {
if (agentChatBusy || agentChatBackgroundBusy) {
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
setAgentChatStatus('正在选择');
try {
const selectedPath = await invoke<string | null>(
'pick_local_project_directory',
agentChatProjectPath.trim()
? { initialPath: agentChatProjectPath.trim() }
: undefined,
);
if (!selectedPath) {
setAgentChatStatus('已取消');
return;
}
setAgentChatStatus('已选择项目目录');
resetAgentChatSessionView();
agentChatRuntimeResumeProjectPathRef.current = null;
setAgentChatResumeConfirmation(null);
setAgentChatProjectPath(selectedPath);
void loadAgentChatConversation(agentChatSelectedAgentId, selectedPath);
} catch (error) {
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
}
}
async function loadAgentChatConversation(
agentId = agentChatSelectedAgentId,
projectPath = agentChatProjectPath,
requestedSessionId?: string | null,
knownSessions?: AgentConversationSessionListResult | null,
) {
const projectPathForChat = validateAgentChatProjectPath(projectPath);
const agent = selectedLauncherAgentChatAgent(agentId);
if (!projectPathForChat || !agent) {
return;
}
agentChatShouldFollowLatestRef.current = true;
const pendingRun = agentChatPendingRuntimeRunRef.current;
if (
pendingRun &&
(pendingRun.projectPath !== projectPathForChat ||
pendingRun.agentId !== agent.id ||
(requestedSessionId !== undefined &&
pendingRun.sessionId !== requestedSessionId))
) {
setAgentChatPendingRuntimeRun(null);
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
const loadVersion = agentChatLoadVersionRef.current + 1;
agentChatLoadVersionRef.current = loadVersion;
setAgentChatBusy(true);
setAgentChatGoal(null);
setAgentChatGoalError('');
setAgentChatStatus('正在读取');
try {
const runtimeResumeStatus = await resumeAgentChatRuntimeTasksIfNeeded(
invoke,
projectPathForChat,
);
if (agentChatLoadVersionRef.current !== loadVersion) {
return;
}
let sessionList = knownSessions;
let sessionListError = '';
if (sessionList === undefined) {
try {
const candidate = await invoke<AgentConversationSessionListResult>(
'list_game_creator_agent_sessions',
{
projectPath: projectPathForChat,
agentId: agent.id,
},
);
if (candidate === undefined || candidate === null) {
sessionList = null;
sessionListError = '当前客户端后端不支持多会话命令';
} else if (!Array.isArray(candidate.sessions)) {
setAgentChatSessionStatus('Agent 会话列表返回格式无效');
setAgentChatStatus('Agent 会话列表返回格式无效,已停止读取');
return;
} else {
sessionList = candidate;
}
} catch (error) {
if (!isMissingAgentSessionCommandError(error)) {
const message =
error instanceof Error ? error.message : String(error);
setAgentChatSessionStatus(`Agent 会话列表读取失败:${message}`);
setAgentChatStatus(`Agent 会话列表读取失败:${message}`);
return;
}
sessionList = null;
sessionListError = '当前客户端后端不支持多会话命令';
}
}
if (agentChatLoadVersionRef.current !== loadVersion) {
return;
}
let sessionId = requestedSessionId ?? null;
let resolvedActiveSessionId = agentChatActiveSessionId;
if (sessionList) {
const requestedExists = sessionList.sessions.some(
(session) => session.sessionId === sessionId,
);
if (!requestedExists) {
sessionId =
sessionList.activeSessionId ||
sessionList.sessions.find((session) => session.archivedAt === null)
?.sessionId ||
sessionList.sessions[0]?.sessionId ||
null;
}
setAgentChatSessions(sessionList.sessions);
setAgentChatSelectedSessionId(sessionId);
resolvedActiveSessionId = sessionList.activeSessionId || null;
setAgentChatActiveSessionId(resolvedActiveSessionId);
setAgentChatLegacySessionMode(false);
setAgentChatSessionStatus(
sessionList.sessions.length > 0
? `共 ${sessionList.sessions.length} 个会话`
: '暂无会话',
);
} else {
sessionId = null;
resolvedActiveSessionId = null;
setAgentChatSessions([]);
setAgentChatSelectedSessionId(null);
setAgentChatActiveSessionId(null);
setAgentChatLegacySessionMode(true);
setAgentChatSessionStatus(
sessionListError
? `会话列表不可用,已使用旧版单会话:${sessionListError}`
: '已使用旧版单会话',
);
}
const selectedSessionIsActive =
sessionId === resolvedActiveSessionId ||
(sessionId === null && resolvedActiveSessionId === null);
setAgentChatRuntime(null);
setAgentChatRuntimeError('');
if (sessionId) {
try {
const goal = await invoke<AgentGoalRecord | null>(
'read_game_creator_agent_goal',
{
projectPath: projectPathForChat,
agentId: agent.id,
sessionId,
},
);
if (agentChatLoadVersionRef.current !== loadVersion) {
return;
}
setAgentChatGoal(goal);
setAgentChatGoalError('');
} catch (error) {
if (agentChatLoadVersionRef.current !== loadVersion) {
return;
}
setAgentChatGoal(null);
setAgentChatGoalError(
isMissingAgentGoalCommandError(error)
? ''
: `Goal 状态读取失败:${
error instanceof Error ? error.message : String(error)
}`,
);
}
}
const result = await invoke<LocalConversationResult>(
'read_local_conversation',
{
projectPath: projectPathForChat,
agentId: agent.id,
...agentChatSessionInvokeArgs(sessionId),
},
);
if (agentChatLoadVersionRef.current !== loadVersion) {
return;
}
setAgentChatMessages(result.messages);
updateAgentChatSessionMessageCount(sessionId, result.messages.length);
setAgentChatConversationPath(result.path);
try {
const runtime = await invoke<AgentRuntimeResult>(
'read_game_creator_agent_runtime',
{
projectPath: projectPathForChat,
agentId: agent.id,
...agentChatSessionInvokeArgs(sessionId),
},
);
if (agentChatLoadVersionRef.current === loadVersion) {
const runtimeState = agentRuntimeStateFromResult(runtime);
setAgentChatRuntime(runtimeState);
if (selectedSessionIsActive) {
setAgentChatActiveRuntime(runtimeState);
}
setAgentChatGoal((current) =>
mergeAgentGoalRecordFromRuntime(current, runtimeState),
);
setAgentChatRuntimeError('');
}
} catch (error) {
if (agentChatLoadVersionRef.current === loadVersion) {
setAgentChatRuntime(null);
if (selectedSessionIsActive) {
setAgentChatActiveRuntime(null);
}
setAgentChatRuntimeError(
error instanceof Error ? error.message : String(error),
);
}
}
setAgentChatStatus(
`已读取 ${result.messages.length} 条:${result.path}${runtimeResumeStatus}`,
);
} catch (error) {
if (agentChatLoadVersionRef.current !== loadVersion) {
return;
}
setAgentChatMessages([]);
setAgentChatConversationPath('');
setAgentChatGoal(null);
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
} finally {
if (agentChatLoadVersionRef.current === loadVersion) {
setAgentChatBusy(false);
}
}
}
async function handleAgentChatSelectSession(
session: AgentConversationSessionRecord,
) {
const projectPathForChat = validateAgentChatProjectPath();
const agent = selectedLauncherAgentChatAgent();
if (
!projectPathForChat ||
!agent ||
agentChatBusy ||
agentChatBackgroundBusy
) {
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
if (session.archivedAt !== null) {
await loadAgentChatConversation(
agent.id,
projectPathForChat,
session.sessionId,
{
path: '',
agentId: agent.id,
activeSessionId:
agentChatActiveSessionId ??
agentChatSessions.find((candidate) => candidate.archivedAt === null)
?.sessionId ??
session.sessionId,
sessions: agentChatSessions,
},
);
return;
}
if (
session.sessionId === agentChatSelectedSessionId ||
session.sessionId === agentChatActiveSessionId
) {
await loadAgentChatConversation(
agent.id,
projectPathForChat,
session.sessionId,
{
path: '',
agentId: agent.id,
activeSessionId: session.sessionId,
sessions: agentChatSessions,
},
);
return;
}
setAgentChatBusy(true);
setAgentChatStatus('正在切换会话');
try {
const result = await invoke<AgentConversationSessionListResult>(
'set_active_game_creator_agent_session',
{
projectPath: projectPathForChat,
agentId: agent.id,
sessionId: session.sessionId,
},
);
await loadAgentChatConversation(
agent.id,
projectPathForChat,
session.sessionId,
result,
);
} catch (error) {
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
setAgentChatBusy(false);
}
}
async function handleAgentChatCreateSession() {
const projectPathForChat = validateAgentChatProjectPath();
const agent = selectedLauncherAgentChatAgent();
if (
!projectPathForChat ||
!agent ||
agentChatBusy ||
agentChatBackgroundBusy
) {
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
setAgentChatBusy(true);
setAgentChatStatus('正在新建会话');
try {
const result = await invoke<AgentConversationSessionListResult>(
'create_game_creator_agent_session',
{
projectPath: projectPathForChat,
agentId: agent.id,
title: '',
},
);
await loadAgentChatConversation(
agent.id,
projectPathForChat,
result.activeSessionId,
result,
);
} catch (error) {
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
setAgentChatBusy(false);
}
}
async function handleAgentChatForkSession() {
const projectPathForChat = validateAgentChatProjectPath();
const agent = selectedLauncherAgentChatAgent();
const sourceSession = selectedLauncherAgentChatSession();
if (
!projectPathForChat ||
!agent ||
!sourceSession ||
agentChatBusy ||
agentChatBackgroundBusy
) {
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
setAgentChatBusy(true);
setAgentChatStatus('正在分叉会话');
try {
const result = await invoke<AgentConversationSessionListResult>(
'fork_game_creator_agent_session',
{
projectPath: projectPathForChat,
agentId: agent.id,
sourceSessionId: sourceSession.sessionId,
title: '',
},
);
await loadAgentChatConversation(
agent.id,
projectPathForChat,
result.activeSessionId,
result,
);
setAgentChatStatus(`已从“${sourceSession.title}”分叉新会话`);
} catch (error) {
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
setAgentChatBusy(false);
}
}
async function handleAgentChatArchiveSession() {
const projectPathForChat = validateAgentChatProjectPath();
const agent = selectedLauncherAgentChatAgent();
const session = selectedLauncherAgentChatSession();
if (
!projectPathForChat ||
!agent ||
!session ||
session.legacy ||
session.archivedAt !== null ||
agentChatBusy ||
agentChatBackgroundBusy
) {
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
setAgentChatBusy(true);
setAgentChatStatus('正在归档会话');
try {
const result = await invoke<AgentConversationSessionListResult>(
'archive_game_creator_agent_session',
{
projectPath: projectPathForChat,
agentId: agent.id,
sessionId: session.sessionId,
},
);
await loadAgentChatConversation(
agent.id,
projectPathForChat,
result.activeSessionId,
result,
);
} catch (error) {
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
setAgentChatBusy(false);
}
}
async function handleAgentChatSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const projectPathForChat = validateAgentChatProjectPath();
const agent = selectedLauncherAgentChatAgent();
const sessionIdForChat = agentChatSelectedSessionId;
const selectedSession = selectedLauncherAgentChatSession(sessionIdForChat);
const content = agentChatInput.trim();
if (agentRuntimeNeedsUserInput(agentChatRuntime)) {
setAgentChatStatus('请先回答 Agent 当前的澄清问题');
return;
}
if (
!projectPathForChat ||
!agent ||
!content ||
agentChatBusy ||
agentChatBackgroundBusy
) {
return;
}
if (selectedSession && selectedSession.archivedAt !== null) {
setAgentChatStatus('已归档会话只能查看,请先新建或切换到活动会话');
return;
}
const llmWarning = getCurrentAgentChatLlmWarning(agent);
if (llmWarning) {
setAgentChatStatus(llmWarning);
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
const saveVersion = agentChatLoadVersionRef.current + 1;
agentChatLoadVersionRef.current = saveVersion;
agentChatShouldFollowLatestRef.current = true;
setAgentChatBusy(true);
setAgentChatReplyPhase('saving-user');
setAgentChatInput('');
setAgentChatStatus('正在保存用户消息');
let savedUserResult: LocalConversationResult | null = null;
let stopStreamListen: (() => void) | null = null;
let streamListenDisposed = false;
let streamListenReady = false;
let streamListenUnavailable = false;
let streamFrameId: number | null = null;
let pendingStreamDraftText = '';
let pendingStreamFinishReason: string | null = null;
const streamDraftUpdatedAt = Date.now();
try {
savedUserResult = await invoke<LocalConversationResult>(
'append_local_conversation_message',
{
projectPath: projectPathForChat,
agentId: agent.id,
...agentChatSessionInvokeArgs(sessionIdForChat),
message: {
role: 'user',
content,
agentId: null,
},
},
);
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
const savedUserMessages = savedUserResult.messages;
setAgentChatMessages(savedUserMessages);
updateAgentChatSessionMessageCount(
sessionIdForChat,
savedUserMessages.length,
);
setAgentChatReplyPhase('connecting');
setAgentChatStatus('正在连接 Agent LLM');
const streamRunId = createAgentChatRunId('launcher-agent-chat');
if (canSubscribeTauriEvents()) {
try {
stopStreamListen =
await subscribeTauriEvent<GameCreatorRoleAgentChatStreamEvent>(
'game-creator-role-agent-chat-stream',
(event) => {
const payload = event.payload;
if (
payload.projectPath !== projectPathForChat ||
payload.agentId !== agent.id ||
payload.runId !== streamRunId ||
agentChatLoadVersionRef.current !== saveVersion
) {
return;
}
if (payload.runtimeState && payload.status !== 'delta') {
setAgentChatRuntime((current) =>
normalizeAgentRuntimeState(payload.runtimeState!, current),
);
setAgentChatActiveRuntime((current) =>
normalizeAgentRuntimeState(payload.runtimeState!, current),
);
setAgentChatRuntimeError('');
}
if (payload.status === 'started') {
setAgentChatReplyPhase('waiting-first-content');
setAgentChatStatus(
payload.runtimeSummary
? `已连接 Agent LLM${payload.runtimeSummary}`
: '已连接 Agent LLM,正在等待首个回复片段',
);
return;
}
if (payload.status === 'delta') {
setAgentChatReplyPhase('streaming');
const draftText =
payload.accumulatedText || payload.deltaText;
if (draftText) {
pendingStreamDraftText = draftText;
}
pendingStreamFinishReason = payload.finishReason ?? null;
if (streamFrameId === null) {
streamFrameId = window.requestAnimationFrame(() => {
streamFrameId = null;
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
if (pendingStreamDraftText) {
setAgentChatMessages([
...savedUserMessages,
createLocalConversationDraftMessage(
pendingStreamDraftText,
streamDraftUpdatedAt,
),
]);
}
setAgentChatStatus(
pendingStreamFinishReason
? `Agent 回复结束:${pendingStreamFinishReason}`
: '正在接收 Agent 回复',
);
});
}
return;
}
if (payload.status === 'completed') {
if (streamFrameId !== null) {
window.cancelAnimationFrame(streamFrameId);
streamFrameId = null;
}
setAgentChatReplyPhase('saving-reply');
setAgentChatStatus(
payload.runtimeSummary ?? 'Agent 回复完成,正在保存',
);
return;
}
if (payload.status === 'failed') {
if (streamFrameId !== null) {
window.cancelAnimationFrame(streamFrameId);
streamFrameId = null;
}
setAgentChatReplyPhase('saving-reply');
setAgentChatStatus(
payload.runtimeSummary ??
'Agent 流式回复失败,正在记录错误',
);
}
},
);
streamListenReady = true;
if (streamListenDisposed) {
stopStreamListen();
stopStreamListen = null;
}
} catch {
streamListenUnavailable = true;
setAgentChatStatus('实时状态不可用,正在使用普通回复模式');
}
}
setAgentChatStatus(
streamListenReady
? '已连接 Agent LLM,正在等待首个回复片段'
: streamListenUnavailable
? '实时状态不可用,正在使用普通回复模式'
: '正在等待 Agent LLM 回复',
);
setAgentChatReplyPhase('waiting-first-content');
let reply: GameCreatorChatAgentReply;
if (streamListenReady) {
try {
reply = await invoke<GameCreatorChatAgentReply>(
'chat_with_game_creator_role_agent_stream',
{
projectPath: projectPathForChat,
agentId: agent.id,
prompt: content,
runId: streamRunId,
...agentChatSessionInvokeArgs(sessionIdForChat),
},
);
} catch (error) {
if (pendingStreamDraftText && pendingStreamFinishReason) {
reply = { replyText: pendingStreamDraftText };
setAgentChatStatus(
`Agent 回复已结束(${pendingStreamFinishReason}),正在保存已接收内容`,
);
} else {
throw error;
}
}
} else {
reply = await invoke<GameCreatorChatAgentReply>(
'chat_with_game_creator_role_agent',
{
projectPath: projectPathForChat,
agentId: agent.id,
prompt: content,
...agentChatSessionInvokeArgs(sessionIdForChat),
},
);
}
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
try {
const runtime = await invoke<AgentRuntimeResult>(
'read_game_creator_agent_runtime',
{
projectPath: projectPathForChat,
agentId: agent.id,
...agentChatSessionInvokeArgs(sessionIdForChat),
},
);
if (agentChatLoadVersionRef.current === saveVersion) {
const runtimeState = agentRuntimeStateFromResult(runtime);
setAgentChatRuntime(runtimeState);
setAgentChatActiveRuntime(runtimeState);
setAgentChatGoal((current) =>
mergeAgentGoalRecordFromRuntime(current, runtimeState),
);
setAgentChatRuntimeError('');
}
} catch (error) {
if (agentChatLoadVersionRef.current === saveVersion) {
setAgentChatRuntimeError(
error instanceof Error ? error.message : String(error),
);
}
}
if (streamFrameId !== null) {
window.cancelAnimationFrame(streamFrameId);
streamFrameId = null;
}
setAgentChatReplyPhase('saving-reply');
setAgentChatStatus('正在保存 Agent 回复');
setAgentChatMessages([
...savedUserMessages,
createLocalConversationDraftMessage(
reply.replyText,
streamDraftUpdatedAt,
),
]);
const assistantResult = await invoke<LocalConversationResult>(
'append_local_conversation_message',
{
projectPath: projectPathForChat,
agentId: agent.id,
...agentChatSessionInvokeArgs(sessionIdForChat),
message: {
role: 'assistant',
content: reply.replyText,
agentId: null,
},
},
);
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
setAgentChatMessages(assistantResult.messages);
updateAgentChatSessionMessageCount(
sessionIdForChat,
assistantResult.messages.length,
);
setAgentChatStatus(
`已保存 ${assistantResult.messages.length} 条:${assistantResult.path}`,
);
} catch (error) {
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
if (savedUserResult) {
const message = `已保存用户消息;Agent 回复失败:${
error instanceof Error ? error.message : String(error)
}`;
try {
const errorResult = await invoke<LocalConversationResult>(
'append_local_conversation_message',
{
projectPath: projectPathForChat,
agentId: agent.id,
...agentChatSessionInvokeArgs(sessionIdForChat),
message: {
role: 'assistant',
content: message,
agentId: null,
},
},
);
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
setAgentChatMessages(errorResult.messages);
updateAgentChatSessionMessageCount(
sessionIdForChat,
errorResult.messages.length,
);
} catch {
setAgentChatMessages([
...savedUserResult.messages,
{
schemaVersion: 'game-creator-conversation.v1',
role: 'assistant',
content: message,
agentId: null,
updatedAt: Date.now(),
},
]);
}
setAgentChatStatus(message);
} else {
setAgentChatInput(content);
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
}
} finally {
streamListenDisposed = true;
stopStreamListen?.();
if (streamFrameId !== null) {
window.cancelAnimationFrame(streamFrameId);
}
if (agentChatLoadVersionRef.current === saveVersion) {
setAgentChatReplyPhase('idle');
setAgentChatBusy(false);
}
}
}
async function handleAgentChatStartBackgroundTask() {
const projectPathForChat = validateAgentChatProjectPath();
const agent = selectedLauncherAgentChatAgent();
const sessionIdForTask = agentChatSelectedSessionId;
const selectedSession = selectedLauncherAgentChatSession(sessionIdForTask);
const content = agentChatInput.trim();
if (agentRuntimeNeedsUserInput(agentChatRuntime)) {
setAgentChatStatus('请先回答 Agent 当前的澄清问题');
return;
}
if (
!projectPathForChat ||
!agent ||
!content ||
agentChatBusy ||
agentChatBackgroundBusy
) {
return;
}
if (selectedSession && selectedSession.archivedAt !== null) {
setAgentChatStatus('已归档会话不能启动任务,请先新建或切换会话');
return;
}
const llmWarning = getCurrentAgentChatLlmWarning(agent);
if (llmWarning) {
setAgentChatStatus(llmWarning);
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
const saveVersion = agentChatLoadVersionRef.current + 1;
agentChatLoadVersionRef.current = saveVersion;
agentChatShouldFollowLatestRef.current = true;
const steerRuntime =
agentChatRunSubmitMode === 'steer'
? matchingAgentRuntimeForSteer(
[agentChatRuntime, agentChatActiveRuntime],
agent.id,
sessionIdForTask,
)
: null;
const requestedRunId =
steerRuntime?.runId ?? createAgentChatRunId('launcher-agent-task');
let pendingRunId = requestedRunId;
const previousPendingRun = agentChatPendingRuntimeRunRef.current;
setAgentChatBackgroundBusy(true);
if (steerRuntime) {
setAgentChatStatus(`正在向当前 Run 追加指令:${steerRuntime.runId}`);
} else {
setAgentChatInput('');
setAgentChatPendingRuntimeRun({
projectPath: projectPathForChat,
agentId: agent.id,
sessionId: sessionIdForTask,
runId: requestedRunId,
messageCount: agentChatMessages.length,
});
setAgentChatStatus('正在启动 Agent 后台任务');
}
try {
let runtime: AgentRuntimeResult;
let successStatus: string;
if (steerRuntime) {
const steer = await invoke<AgentRuntimeSteerResult>(
'steer_game_creator_agent_runtime_task',
{
projectPath: projectPathForChat,
agentId: agent.id,
sessionId: steerRuntime.sessionId,
runId: steerRuntime.runId,
steerId: createAgentChatRunId('launcher-agent-steer'),
instruction: content,
},
);
runtime = steer.runtime;
successStatus = agentRuntimeSteerStatus(steer);
} else {
runtime = await invoke<AgentRuntimeResult>(
'start_game_creator_agent_runtime_task',
{
projectPath: projectPathForChat,
agentId: agent.id,
task: content,
runId: requestedRunId,
...agentChatSessionInvokeArgs(sessionIdForTask),
},
);
successStatus = agentRuntimeStartStatus(runtime);
}
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
const runtimeState = agentRuntimeStateFromResult(runtime);
pendingRunId = steerRuntime
? runtimeState.runId
: agentRuntimeStartedRunId(runtime, requestedRunId);
setAgentChatInput('');
setAgentChatRunSubmitMode('steer');
setAgentChatPendingRuntimeRun({
projectPath: projectPathForChat,
agentId: agent.id,
sessionId: sessionIdForTask,
runId: pendingRunId,
messageCount: agentChatMessages.length,
});
setAgentChatRuntime(runtimeState);
setAgentChatActiveRuntime(runtimeState);
setAgentChatGoal((current) =>
mergeAgentGoalRecordFromRuntime(current, runtimeState),
);
setAgentChatRuntimeError('');
try {
const conversation = await invoke<LocalConversationResult>(
'read_local_conversation',
{
projectPath: projectPathForChat,
agentId: agent.id,
...agentChatSessionInvokeArgs(sessionIdForTask),
},
);
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
setAgentChatMessages(conversation.messages);
if (agentChatPendingRuntimeRunRef.current?.runId === pendingRunId) {
setAgentChatPendingRuntimeRun({
...agentChatPendingRuntimeRunRef.current,
messageCount: conversation.messages.length,
});
}
setAgentChatConversationPath(conversation.path);
updateAgentChatSessionMessageCount(
sessionIdForTask,
conversation.messages.length,
);
} catch (error) {
if (agentChatLoadVersionRef.current === saveVersion) {
setAgentChatRuntimeError(
`对话刷新失败:${
error instanceof Error ? error.message : String(error)
}`,
);
}
}
setAgentChatStatus(successStatus);
} catch (error) {
if (agentChatLoadVersionRef.current !== saveVersion) {
return;
}
if (
!steerRuntime &&
agentChatPendingRuntimeRunRef.current?.runId === pendingRunId
) {
setAgentChatPendingRuntimeRun(previousPendingRun);
}
setAgentChatInput(content);
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
} finally {
if (agentChatLoadVersionRef.current === saveVersion) {
setAgentChatBackgroundBusy(false);
}
}
}
const {
openAgentChatGoalDialog,
handleAgentChatGoalDialogSubmit,
handleAgentChatGoalControl,
handleAgentChatCompactContext,
handleAgentChatCancelRuntimeTask,
handleAgentChatRetryRuntimeTask,
handleAgentChatConfirmRuntimeTask,
handleAgentChatRejectRuntimeTask,
handleAgentChatSubmitUserInput,
} = createDeveloperAgentControls({
state,
validateAgentChatProjectPath,
selectedLauncherAgentChatAgent,
selectedLauncherAgentChatSession,
agentChatSessionInvokeArgs,
updateAgentChatSessionMessageCount,
getCurrentAgentChatLlmWarning,
setAgentChatPendingRuntimeRun,
});
const currentAgentChatAgent = selectedLauncherAgentChatAgent();
const currentAgentChatSession = selectedLauncherAgentChatSession();
const currentAgentChatSessionArchived =
currentAgentChatSession?.archivedAt !== null &&
currentAgentChatSession?.archivedAt !== undefined;
const currentAgentChatRuntimeMatchesGoal = Boolean(
agentChatRuntime?.goalId &&
(!agentChatGoal || agentChatRuntime.goalId === agentChatGoal.goalId),
);
const currentAgentChatGoalStatus =
(currentAgentChatRuntimeMatchesGoal
? agentChatRuntime?.goalStatus
: null) ??
agentChatGoal?.status ??
null;
const currentAgentChatGoalDialogMode: 'create' | 'edit' =
!agentChatGoal || agentGoalStatusIsTerminal(currentAgentChatGoalStatus)
? 'create'
: 'edit';
const currentAgentChatSessionMutationBlocked = Boolean(
agentChatActiveRuntime &&
([
'running',
'pending',
'waiting-for-confirmation',
'waiting-for-user-input',
'cancelling',
'finalizing',
'pausing',
'paused',
].includes(agentChatActiveRuntime.status) ||
['needs-reconciliation', 'pausing', 'paused'].includes(
agentChatActiveRuntime.phase,
) ||
['pause-requested', 'paused', 'clearing'].includes(
agentChatActiveRuntime.goalStatus ?? '',
) ||
(agentChatActiveRuntime.taskQueue?.pending ?? 0) > 0 ||
(agentChatActiveRuntime.taskQueue?.running ?? 0) > 0 ||
(agentChatActiveRuntime.taskQueue?.waitingForConfirmation ?? 0) > 0 ||
(agentChatActiveRuntime.taskQueue?.waitingForUserInput ?? 0) > 0 ||
(agentChatActiveRuntime.taskQueue?.paused ?? 0) > 0),
);
const currentAgentChatActiveSessions = agentChatSessions.filter(
(session) => session.archivedAt === null,
);
const currentAgentChatArchivedSessions = agentChatSessions.filter(
(session) => session.archivedAt !== null,
);
const currentAgentChatLlmWarning = getCurrentAgentChatLlmWarning(
currentAgentChatAgent,
);
const currentAgentChatNeedsUserInput =
agentRuntimeNeedsUserInput(agentChatRuntime);
const currentAgentChatSteerRuntime = currentAgentChatAgent
? matchingAgentRuntimeForSteer(
[agentChatRuntime, agentChatActiveRuntime],
currentAgentChatAgent.id,
agentChatSelectedSessionId,
)
: null;
const currentAgentChatWaiting =
agentChatReplyPhase !== 'idle' || agentChatPendingRuntimeRun !== null;
const currentAgentChatWaitingStatus =
agentChatPendingRuntimeRun &&
agentChatRuntime?.runId === agentChatPendingRuntimeRun.runId
? agentRuntimeConversationStatus(agentChatRuntime)
: agentChatStatus;
const currentAgentChatWaitingDetail = agentChatPendingRuntimeRun
? '任务仍在运行,状态和最终回复会自动更新'
: '请求仍在进行中,收到回复后会立即显示';
return {
launcherAgentChatAgents,
agentChatProjectPath,
setAgentChatProjectPath,
agentChatSelectedAgentId,
setAgentChatSelectedAgentId,
agentChatMessages,
agentChatSessions,
agentChatSelectedSessionId,
agentChatActiveSessionId,
agentChatLegacySessionMode,
agentChatConversationPath,
agentChatSessionStatus,
agentChatInput,
setAgentChatInput,
agentChatInteractionMode,
setAgentChatInteractionMode,
agentChatRunSubmitMode,
setAgentChatRunSubmitMode,
agentChatLlmStatus,
agentChatStatus,
setAgentChatStatus,
agentChatBusy,
agentChatMessagesRef,
agentChatBackgroundBusy,
agentChatRuntime,
agentChatRuntimeError,
agentChatGoal,
agentChatGoalError,
agentChatGoalDialog,
setAgentChatGoalDialog,
agentChatGoalDialogError,
agentChatResumeConfirmation,
setAgentChatResumeConfirmation,
agentChatLoadVersionRef,
agentChatRuntimeResumeProjectPathRef,
handleAgentChatMessagesScroll,
loadAgentChatLlmStatus,
cancelAgentChatRuntimeResume,
confirmAgentChatRuntimeResume,
closeAgentChatGoalDialog,
handleOpenProjectSupervisorChatWindow,
handleAgentChatPickProjectDirectory,
loadAgentChatConversation,
resetAgentChatSessionView,
handleAgentChatSelectSession,
handleAgentChatCreateSession,
handleAgentChatForkSession,
handleAgentChatArchiveSession,
handleAgentChatSubmit,
handleAgentChatStartBackgroundTask,
openAgentChatGoalDialog,
handleAgentChatGoalDialogSubmit,
handleAgentChatGoalControl,
handleAgentChatCompactContext,
handleAgentChatCancelRuntimeTask,
handleAgentChatRetryRuntimeTask,
handleAgentChatConfirmRuntimeTask,
handleAgentChatRejectRuntimeTask,
handleAgentChatSubmitUserInput,
currentAgentChatAgent,
currentAgentChatSession,
currentAgentChatSessionArchived,
currentAgentChatGoalStatus,
currentAgentChatGoalDialogMode,
currentAgentChatSessionMutationBlocked,
currentAgentChatActiveSessions,
currentAgentChatArchivedSessions,
currentAgentChatLlmWarning,
currentAgentChatNeedsUserInput,
currentAgentChatSteerRuntime,
currentAgentChatWaiting,
currentAgentChatWaitingStatus,
currentAgentChatWaitingDetail,
};
}
export type DeveloperAgentPanelController = ReturnType<
typeof useDeveloperAgentPanel
>;