Files
Genarrative/apps/ai-game-creator-shell/src/features/app-shell/useDeveloperAgentPanel.ts
T
suzmii 68dfd2c24f 补齐AGC流式输出与受控联网搜索
增加 DirectProject 流式可见文本投影和敏感内容过滤

接入受控 web search 工具及参数、网络边界测试

补充配置迁移和运行时回归覆盖
2026-08-30 14:22:03 +08:00

1908 lines
62 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 { 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,
formatCodexAgentModeLabel,
formatCodexRuntimeCapabilities,
isCodexAgentMode,
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 listen = window.__TAURI__?.event?.listen;
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' || !listen || !invoke) {
return;
}
let cleanup: (() => void) | null = null;
let disposed = false;
void listen<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 的 LLM 路由');
return;
}
setAgentChatLlmStatus(
isCodexAgentMode(agentStatus.agentMode)
? agentStatus.configured
? `当前 Agent 已检测到 ${formatCodexAgentModeLabel(
agentStatus.agentMode,
)}${formatCodexRuntimeCapabilities(agentStatus)};登录与网络将在首次调用时验证`
: `当前 Agent ${formatCodexAgentModeLabel(
agentStatus.agentMode,
)} 未就绪;${formatCodexRuntimeCapabilities(agentStatus)}${
agentStatus.error ?? 'Codex CLI 不可用'
}`
: agentStatus.configured
? `当前 Agent LLM 已配置:${agentStatus.model ?? '未命名模型'}${
agentStatus.reasoningEffort
? `,推理 ${agentStatus.reasoningEffort}`
: ''
},联网检索 ${agentStatus.webSearchEnabled ? '开启' : '关闭'}API Key ${
agentStatus.apiKeyPresent ? '已读取' : '未读取'
}`
: `当前 Agent LLM 未就绪:${
agentStatus.error ?? '缺少 API Key 或模型配置'
}${
agentStatus.reasoningEffort
? `(推理 ${agentStatus.reasoningEffort}`
: ''
},联网检索 ${agentStatus.webSearchEnabled ? '开启' : '关闭'}`,
);
} 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');
const listen = window.__TAURI__?.event?.listen;
if (listen) {
try {
stopStreamListen = await listen<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
>;