From 0224ef7b9de5654324de906c0a2161cd4a89d341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 21 Sep 2026 11:15:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=20Project=20Supervisor=20?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E9=81=97=E7=95=99=E7=9A=84=20pendingCommand?= =?UTF-8?q?=20=E6=AD=BB=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 App.tsx 里 pendingCommand 状态、确认/取消处理与 21 个 execute* 分发分支 - 删除随该链路一起不可达的 21 个 execute* 实现与 requireChatProjectForUserAction - 删除只服务该链路的 PendingCommand 类型、pendingCommandPresentation.ts 与 resolvePendingCommandProjectPath - PlanningChatView 去掉 pendingCommand 面板相关 props、渲染分支与导入 - rememberCommand.test.ts 只保留现役 helper(path/remember/needsInitializedChatProject/agent 卡片)的用例 --- apps/ai-game-creator-shell/src/App.tsx | 1450 +---------------- apps/ai-game-creator-shell/src/app/types.ts | 90 - .../pendingCommandPresentation.ts | 159 -- .../project-workspace/projectCommandPolicy.ts | 15 - .../planning/PlanningChatView.tsx | 36 - .../tests/rememberCommand.test.ts | 172 +- 6 files changed, 3 insertions(+), 1919 deletions(-) delete mode 100644 apps/ai-game-creator-shell/src/features/project-workspace/pendingCommandPresentation.ts diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index ae832a991..cd6dae9eb 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -27,7 +27,6 @@ import { useEscapeToClose } from './app/dialogs'; import { claimInitialTurnForPage } from './app/initialTurnClaims'; import { resolveTauriInvoke } from './app/tauri'; import type { - AgentRunControlResult, AgentRuntimeResult, AgentRuntimeState, AgentRuntimeUserInputRequest, @@ -35,33 +34,18 @@ import type { DesignAgentInput, DesignClarificationRequest, DesignView, - GenerateLocalGameDraftResult, - ImportCanvasExportResult, InitLocalProjectResult, LauncherImportedAttachment, - LimitedLocalCommandResult, LocalConversationResult, LocalGameProjectRevisionStatus, LocalPreviewResult, - LocalPreviewStatus, - LocalProjectCheckpointResult, - LocalProjectExportPackageResult, LocalProjectFileResult, - LocalProjectIndexResult, LocalProjectKind, - LocalProjectRestoreResult, - MemoryScope, - MemoryWriteMode, - OpenCanvasProjectResult, - PendingCommand, PendingUiConfirmation, PlanGddDecisionAction, PlanGddStateViewV1, - ProjectPermissionPolicy, ProjectPermissionPolicyView, - SyncCanvasProjectAssetsResult, TauriInvoke, - UploadLocalAssetResult, } from './app/types'; import { agentConversationId, @@ -87,33 +71,19 @@ import { } from './features/app-shell/model'; import { WorkspaceLauncherShell } from './features/app-shell/WorkspaceLauncher'; import { - formatAgentRunControlError, projectAgentRuntimeSummaries, summarizeAgentRunCompletionForChat, } from './features/project-summary/agentPresentation'; import { - formatCanvasAssetSource, isAbsoluteProjectPath, projectPathHasControlCharacter, - summarizeProjectCheckpoint, - summarizeProjectExportPackage, - summarizeProjectIndex, - summarizeProjectPolicy, } from './features/project-summary/projectSummary'; -import { - gameDraftStartedMessage, - parseAgentRunTrace, -} from './features/project-workspace/agentRunTrace'; -import { - memoryScopeLabel, - parseRememberInput, -} from './features/project-workspace/memoryCommands'; -import { pendingCommandDetail } from './features/project-workspace/pendingCommandPresentation'; +import { parseAgentRunTrace } from './features/project-workspace/agentRunTrace'; +import { parseRememberInput } from './features/project-workspace/memoryCommands'; import { isAgentTraceFilePath, needsInitializedChatProject, resolveChatProjectPath, - resolvePendingCommandProjectPath, } from './features/project-workspace/projectCommandPolicy'; import type { ResourceReferenceInputHandle } from './features/project-workspace/ResourceReferenceInput'; import type { @@ -189,7 +159,6 @@ function isPersistableDirectCodexConversationMessage(message: ChatMessage) { */ export { AuthenticatedClient } from './app/AuthenticatedClient'; -export type { PendingCommand } from './app/types'; export { deriveAgentStatusCards, summarizeAgentAudit, @@ -199,9 +168,7 @@ export { isAbsoluteProjectPath } from './features/project-summary/projectSummary export { needsInitializedChatProject, parseRememberInput, - pendingCommandDetail, resolveChatProjectPath, - resolvePendingCommandProjectPath, }; export function WorkspaceLauncher(props: WorkspaceLauncherProps) { @@ -977,9 +944,6 @@ export function App({ const [conversationVisibleCount, setConversationVisibleCount] = useState( CONVERSATION_INITIAL_VISIBLE_COUNT, ); - const [pendingCommand, setPendingCommand] = useState( - null, - ); const [pendingUiConfirmation, setPendingUiConfirmation] = useState(null); const [pendingNonEmptyProjectCreate, setPendingNonEmptyProjectCreate] = @@ -1349,13 +1313,6 @@ export function App({ } } - function resolvePermissionLogProjectPath(command: PendingCommand) { - if (command.id === 'project.create') { - return null; - } - return resolveChatProjectPath(localProject); - } - function resolveUiPermissionLogProjectPath( commandId: GameCreationAppCommandDescriptor['id'], ) { @@ -1606,18 +1563,6 @@ export function App({ void executeRunLocalRef.current(true); }, [localProject?.projectPath, onPlayRequestHandled, playRequest]); - function requireChatProjectForUserAction() { - const nextProjectPath = resolveChatProjectPath(localProject); - if (nextProjectPath) { - return nextProjectPath; - } - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - return null; - } - /** * 切换项目作用域时把这条链路自己的聊天状态清干净。 * @@ -2202,335 +2147,6 @@ export function App({ ); } - async function executeGameDraft(prompt: string) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - - try { - setMessages((current) => [ - ...current, - { role: 'assistant', text: gameDraftStartedMessage() }, - ]); - const result = await invoke( - 'generate_local_game_draft', - { projectPath: nextProjectPath, prompt }, - ); - const generatedProjectPath = result.projectPath.trim(); - if ( - !generatedProjectPath || - !isAbsoluteProjectPath(generatedProjectPath) || - projectPathHasControlCharacter(generatedProjectPath) - ) { - const message = '生成结果项目路径无效'; - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - return; - } - setLocalProject({ - projectPath: generatedProjectPath, - manifestPath: `${generatedProjectPath}/.agent/manifest.json`, - manifest: result.manifest, - }); - setManifest(result.manifest); - const completionSummary = - await refreshAgentRunTrace(generatedProjectPath); - - try { - const previewResult = await invoke( - 'start_local_game_preview', - { projectPath: generatedProjectPath }, - ); - updateClientPreview(previewResult); - void refreshManifest(generatedProjectPath); - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: [ - `已保存并在客户端运行视图启动预览:${previewResult.url}`, - completionSummary?.text, - ] - .filter(Boolean) - .join('\n\n'), - draftCommand: completionSummary?.draftCommand, - }, - ]); - } catch (previewError) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: [ - `已保存草案:${result.designPath}。预览启动失败:${ - previewError instanceof Error - ? previewError.message - : String(previewError) - }`, - completionSummary?.text, - ] - .filter(Boolean) - .join('\n\n'), - draftCommand: completionSummary?.draftCommand, - }, - ]); - } - } catch (error) { - void refreshAgentRunTrace(nextProjectPath); - const message = error instanceof Error ? error.message : String(error); - if (isRuntimeConfigMissingError(message)) { - requestRuntimeConfigOpen(); - } - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: message, - }, - ]); - } - } - - function markPendingCommandCanceled(command: PendingCommand) { - if (command.id === 'project.create') { - setWorkspaceStatus('已取消'); - return; - } - if (command.id === 'game.generate_draft') { - return; - } - if ( - command.id === 'game.run_local' || - command.id === 'command.run_limited' - ) { - return; - } - if (command.id === 'project.checkpoint') { - return; - } - if (command.id === 'project.export_package') { - return; - } - if (command.id === 'project.index') { - setWorkspaceStatus('已取消索引项目'); - return; - } - if (command.id === 'project.restore') { - return; - } - if (command.id === 'project.policy_write') { - return; - } - if (command.id === 'preview.start' || command.id === 'preview.open') { - setWorkspaceStatus('已取消预览操作'); - return; - } - if ( - command.id === 'agent.kill' || - command.id === 'agent.retry' || - command.id === 'agent.resume' - ) { - return; - } - if (command.id === 'memory.write') { - return; - } - if (command.id === 'memory.delete') { - return; - } - if (command.id === 'asset.upload') { - return; - } - if (command.id === 'asset.register') { - return; - } - if (command.id === 'canvas.project_open') { - return; - } - if (command.id === 'canvas.project_sync') { - return; - } - if (command.id === 'canvas.asset_import') { - return; - } - if (command.id === 'canvas.asset_generate') { - return; - } - } - - function handlePendingCommandCancel() { - const command = pendingCommand; - setPendingCommand(null); - if (command) { - markPendingCommandCanceled(command); - appendLocalPermissionLog( - resolvePermissionLogProjectPath(command), - 'permission.cancel', - command.id, - ); - } - setMessages((current) => [ - ...current, - { role: 'assistant', text: '已取消。' }, - ]); - } - - async function handlePendingCommandConfirm() { - const command = pendingCommand; - if (!command) { - return; - } - if ( - needsInitializedChatProject(command.id) && - !resolveChatProjectPath(localProject) - ) { - setPendingCommand(null); - appendLocalPermissionLog( - resolvePermissionLogProjectPath(command), - 'permission.cancel', - command.id, - ); - requireChatProjectForUserAction(); - return; - } - if ( - await denyPendingCommandIfNeeded( - command.id, - resolvePermissionLogProjectPath(command), - ) - ) { - setPendingCommand(null); - return; - } - setPendingCommand(null); - appendLocalPermissionLog( - resolvePermissionLogProjectPath(command), - 'permission.confirm', - command.id, - ); - if (command.id === 'asset.upload') { - void executeAssetUpload(command.file); - } else if (command.id === 'asset.register') { - void executeAssetRegister( - resolveChatProjectPath(localProject) ?? '', - command.localPath, - command.kind, - command.mediaType, - 'generated', - '', - '', - '', - true, - ); - } else if (command.id === 'game.run_local') { - void executeRunLocal(true); - } else if (command.id === 'command.run_limited') { - void executeLimitedCommand(command.commandId, true); - } else if (command.id === 'project.create') { - void executeProjectCreate(command.projectPath, true); - } else if (command.id === 'project.checkpoint') { - void executeProjectCheckpoint(true); - } else if (command.id === 'project.export_package') { - void executeProjectExportPackage(true); - } else if (command.id === 'project.index') { - void executeProjectIndex(true); - } else if (command.id === 'project.restore') { - void executeProjectRestore(command.checkpointId, true); - } else if (command.id === 'project.policy_write') { - void executeProjectPolicyWrite(command.policy, true); - } else if (command.id === 'preview.start') { - void executePreviewStart(true); - } else if (command.id === 'preview.open') { - void executePreviewOpen(true); - } else if ( - command.id === 'agent.kill' || - command.id === 'agent.retry' || - command.id === 'agent.resume' - ) { - void executeAgentRunControl( - command.id.slice('agent.'.length), - command.detail, - true, - ); - } else if (command.id === 'memory.write') { - void executeMemoryWrite( - command.scope, - command.content, - command.mode ?? 'append', - ); - } else if (command.id === 'memory.delete') { - void executeMemoryDeleteChat(command.scope); - } else if (command.id === 'canvas.project_open') { - void executeCanvasProjectOpen(command.canvasProjectId, true); - } else if (command.id === 'canvas.project_sync') { - void executeCanvasProjectSync(command.canvasProjectId, true); - } else if (command.id === 'canvas.asset_import') { - void executeCanvasAssetImport( - null, - command.localPath, - command.kind, - command.mediaType, - command.canvasProjectId, - command.canvasAssetId, - command.canvasAssetObjectId ?? '', - true, - ); - } else if (command.id === 'canvas.asset_generate') { - void executeCanvasAssetGenerate(command.prompt, true); - } else if (command.id === 'canvas.export_import') { - void executeCanvasExportImport( - command.exportPath, - command.canvasProjectId, - true, - ); - } else if (command.id === 'game.generate_draft') { - void executeGameDraft(command.prompt); - } - } - - async function executeProjectCreate( - nextProjectPath: string, - announceToChat: boolean, - ) { - const trimmedProjectPath = nextProjectPath.trim(); - const invoke = resolveTauriInvoke(); - if ( - invoke && - trimmedProjectPath && - isAbsoluteProjectPath(trimmedProjectPath) && - !projectPathHasControlCharacter(trimmedProjectPath) - ) { - try { - const nonEmpty = await invoke( - 'is_local_project_directory_non_empty', - { projectPath: trimmedProjectPath }, - ); - if (nonEmpty) { - setPendingNonEmptyProjectCreate({ - projectPath: trimmedProjectPath, - announceToChat, - }); - setWorkspaceStatus('目标文件夹不是空的'); - return; - } - } catch { - // ponytail: stale test doubles and older shells may miss this helper; init still validates. - } - } - await openWorkspace(nextProjectPath, announceToChat); - } - function cancelProjectCreateInNonEmptyFolder() { const pendingCreate = pendingNonEmptyProjectCreate; setPendingNonEmptyProjectCreate(null); @@ -2546,535 +2162,6 @@ export function App({ } } - async function executeProjectIndex(announceToChat: boolean) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : resolveChatProjectPath(localProject); - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'build_local_project_index', - { projectPath: nextProjectPath }, - ); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeProjectIndex(result) }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectCheckpoint(announceToChat: boolean) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath; - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'create_local_project_checkpoint', - { projectPath: nextProjectPath }, - ); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeProjectCheckpoint(result) }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectExportPackage(announceToChat: boolean) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath; - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'export_local_project_package', - { projectPath: nextProjectPath }, - ); - void refreshManifest(nextProjectPath); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: summarizeProjectExportPackage(result), - draftCommand: '/open-project', - draftCommandLabel: '显示目录', - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectRestore( - checkpointId: string, - announceToChat: boolean, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath; - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'restore_local_project_checkpoint', - { projectPath: nextProjectPath, checkpointId }, - ); - void refreshManifest(nextProjectPath); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `已回滚 ${result.restoredCount} 个文件到 ${nextProjectPath},删除 ${result.deletedCount} 个新增文件:${result.checkpointId}`, - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeProjectPolicyWrite( - policy: ProjectPermissionPolicy, - announceToChat: boolean, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath; - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'write_project_permission_policy', - { projectPath: nextProjectPath, policy }, - ); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: summarizeProjectPolicy(result) }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeAgentRunControl( - action: string, - detail: string | undefined, - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - const commandId = - action === 'status' - ? 'agent.run_status' - : (`agent.${action}` as GameCreationAppCommandDescriptor['id']); - - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - commandId, - nextProjectPath, - action === 'status' - ? `查看 ${nextProjectPath} 的最近 Agent run 状态` - : `执行 ${nextProjectPath} 的 ${commandId}`, - action === 'status' - ? '准备查看 Agent run 状态。' - : '准备执行 Agent run 操作。', - () => - void executeAgentRunControl(action, detail, announceToChat, true), - )) - ) { - return; - } - const result = await invoke('control_agent_run', { - projectPath: nextProjectPath, - action, - detail, - }); - if (announceToChat && action === 'status') { - appendLocalPermissionLog( - nextProjectPath, - 'command.auto', - 'agent.run_status', - ); - } - let traceReadQueued = false; - if (action === 'status') { - const policyView = await invoke( - 'read_project_permission_policy', - { projectPath: nextProjectPath }, - ); - if (policyView.policy.confirmCommands.includes('agent.trace_read')) { - requestProjectPolicyConfirmation( - 'agent.trace_read', - nextProjectPath, - `读取 ${nextProjectPath} 的最近 Agent run trace`, - () => void refreshAgentRunTrace(nextProjectPath), - ); - traceReadQueued = true; - } - } - if (!traceReadQueued) { - await refreshAgentRunTrace(nextProjectPath); - } - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: [ - result.message, - `run:${result.runId}`, - `状态:${result.status} / ${result.lifecycleStatus}`, - `下一步:${result.nextStep}`, - `事件:${result.activityPath}`, - `输出:${result.outputPath}`, - `上下文包:${result.contextBundlePath}`, - ].join('\n'), - draftCommand: '/read .agent/output.jsonl', - draftCommandLabel: '读取 Run 输出', - }, - ]); - } - } catch (error) { - const rawMessage = error instanceof Error ? error.message : String(error); - const message = formatAgentRunControlError(action, rawMessage); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executePreviewStart( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = resolveChatProjectPath(localProject); - if (!nextProjectPath) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '请先用 /project 设置本地项目。' }, - ]); - } - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'preview.start', - nextProjectPath, - `启动 ${nextProjectPath}/game/ 并载入客户端运行视图`, - '准备启动本地预览。', - () => void executePreviewStart(true, true), - )) - ) { - return; - } - const result = await invoke( - 'start_local_game_preview', - { projectPath: nextProjectPath }, - ); - updateClientPreview(result); - void refreshManifest(nextProjectPath); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `预览已在客户端运行视图启动:${result.url}`, - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executePreviewOpen( - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : resolveChatProjectPath(localProject); - if (announceToChat && !nextProjectPath) { - return; - } - - try { - if ( - announceToChat && - nextProjectPath && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'preview.open', - nextProjectPath, - `打开 ${nextProjectPath} 的本地预览`, - '准备打开当前本地预览。', - () => void executePreviewOpen(true, true), - )) - ) { - return; - } - const result = await invoke( - 'activate_local_game_preview', - nextProjectPath ? { projectPath: nextProjectPath } : undefined, - ); - if ( - result.status === 'running' && - result.url && - result.port && - result.root - ) { - updateClientPreview({ - url: result.url, - port: result.port, - root: result.root, - }); - } - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: result.url - ? `已切换到客户端运行视图:${result.url}` - : '已切换到客户端运行视图。', - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeMemoryWrite( - scope: MemoryScope, - content: string, - mode: MemoryWriteMode, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - - try { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: - mode === 'append' - ? `已追加${memoryScopeLabel(scope)}记忆。` - : `已保存${memoryScopeLabel(scope)}记忆。`, - }, - ]); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - - async function executeMemoryDeleteChat(scope: MemoryScope) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - - try { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `已删除${memoryScopeLabel(scope)}记忆。` }, - ]); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - async function executeRunLocal(announceToChat: boolean) { const invoke = resolveTauriInvoke(); if (!invoke) { @@ -3114,536 +2201,6 @@ export function App({ } } - async function executeLimitedCommand( - commandId: string, - announceToChat: boolean, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : resolveChatProjectPath(localProject); - if (!nextProjectPath) { - return; - } - - try { - const result = await invoke( - 'run_limited_local_command', - { projectPath: nextProjectPath, commandId }, - ); - const summary = `${result.output}\n日志:${result.logPath}`; - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: summary }, - ]); - } - void refreshManifest(nextProjectPath); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeAssetRegister( - nextProjectPath: string, - localPath: string, - kind: string, - mediaType: string, - sourceKind: string, - canvasProjectId: string, - resourceId: string, - nextAssetObjectId: string, - announceToChat = false, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - - try { - if ( - announceToChat && - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'asset.register', - nextProjectPath, - `登记 ${nextProjectPath}/${localPath}`, - '准备登记项目资产。', - () => - void executeAssetRegister( - nextProjectPath, - localPath, - kind, - mediaType, - sourceKind, - canvasProjectId, - resourceId, - nextAssetObjectId, - true, - true, - ), - )) - ) { - return; - } - const result = await invoke( - 'register_local_asset', - { - projectPath: nextProjectPath, - localPath, - kind, - mediaType, - sourceKind, - canvasProjectId, - resourceId, - assetObjectId: nextAssetObjectId, - taskId: '', - prompt: '', - model: '', - }, - ); - void refreshManifest(nextProjectPath); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `已登记资产:${result.localPath}` }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeCanvasAssetImport( - nextProjectPath: string | null, - localPath: string, - kind: string, - mediaType: string, - canvasProjectId: string, - resourceId: string, - assetObjectId: string, - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const targetProjectPath = announceToChat - ? requireChatProjectForUserAction() - : nextProjectPath; - if (!targetProjectPath) { - return; - } - - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'canvas.asset_import', - targetProjectPath, - `导入 ${targetProjectPath}/${localPath} 的画板来源资产`, - '准备导入画板资产。', - () => - void executeCanvasAssetImport( - nextProjectPath, - localPath, - kind, - mediaType, - canvasProjectId, - resourceId, - assetObjectId, - announceToChat, - true, - ), - )) - ) { - return; - } - const result = await invoke( - 'import_canvas_asset', - { - projectPath: targetProjectPath, - localPath, - kind, - mediaType, - canvasProjectId, - resourceId, - assetObjectId, - taskId: '', - prompt: '', - model: '', - }, - ); - const canvasSource = formatCanvasAssetSource({ - canvasProjectId, - canvasAssetId: resourceId, - canvasAssetObjectId: assetObjectId, - }); - void refreshManifest(targetProjectPath); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `已导入画板资产 ${canvasSource}:${result.localPath}`, - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeCanvasAssetGenerate( - prompt: string, - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath.trim(); - if (!nextProjectPath) { - return; - } - - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'canvas.asset_generate', - nextProjectPath, - `生成 ${nextProjectPath} 的首版美术素材`, - '准备生成首版美术素材。', - () => void executeCanvasAssetGenerate(prompt, announceToChat, true), - )) - ) { - return; - } - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '正在生成首版美术素材。' }, - ]); - } - const result = await invoke( - 'generate_platform_art_asset', - { - projectPath: nextProjectPath, - prompt, - }, - ); - void refreshManifest(nextProjectPath); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `已生成美术素材:${result.localPath}` }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (isRuntimeConfigMissingError(message)) { - requestRuntimeConfigOpen(); - } - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeCanvasProjectSync( - canvasProjectId: string, - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath; - if (!nextProjectPath) { - return; - } - - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'canvas.project_sync', - nextProjectPath, - `同步 ${nextProjectPath} 的画板项目资源:${canvasProjectId}`, - '准备同步画板项目资源。', - () => - void executeCanvasProjectSync( - canvasProjectId, - announceToChat, - true, - ), - )) - ) { - return; - } - const result = await invoke( - 'sync_canvas_project_assets', - { - projectPath: nextProjectPath, - canvasProjectId, - }, - ); - void refreshManifest(nextProjectPath); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `已同步 ${result.importedCount} 个画板资产自 ${result.canvasProjectId}:${result.importRoot}`, - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeCanvasExportImport( - exportPath: string, - canvasProjectId: string, - announceToChat: boolean, - skipPolicyConfirm = false, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - const nextProjectPath = announceToChat - ? requireChatProjectForUserAction() - : projectPath; - if (!nextProjectPath) { - return; - } - - try { - if ( - !skipPolicyConfirm && - (await queueProjectPolicyConfirmationIfNeeded( - invoke, - 'canvas.export_import', - nextProjectPath, - `导入 ${nextProjectPath} 的画板导出包:${exportPath}`, - '准备导入画板导出包。', - () => - void executeCanvasExportImport( - exportPath, - canvasProjectId, - announceToChat, - true, - ), - )) - ) { - return; - } - const result = await invoke( - 'import_canvas_export', - { - projectPath: nextProjectPath, - exportPath, - canvasProjectId, - }, - ); - void refreshManifest(nextProjectPath); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `已导入 ${result.importedCount} 个画板资产自 ${canvasProjectId}:${result.importRoot}`, - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeCanvasProjectOpen( - canvasProjectId: string, - announceToChat: boolean, - ) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - } - return; - } - - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: `正在打开画板:${canvasProjectId}` }, - ]); - } - try { - const result = await invoke( - 'open_canvas_project', - { - canvasProjectId, - }, - ); - if (announceToChat) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: `已打开画板:${result.url}`, - draftCommand: `/sync-canvas-project ${canvasProjectId}`, - draftCommandLabel: '同步此画板', - }, - ]); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if (announceToChat) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: message }, - ]); - } - } - } - - async function executeAssetUpload(file: File) { - const invoke = resolveTauriInvoke(); - if (!invoke) { - setMessages((current) => [ - ...current, - { role: 'assistant', text: '需要在 Tauri App 内运行。' }, - ]); - return; - } - const nextProjectPath = requireChatProjectForUserAction(); - if (!nextProjectPath) { - return; - } - - try { - const bytes = Array.from(new Uint8Array(await file.arrayBuffer())); - const result = await invoke( - 'upload_local_asset', - { - projectPath: nextProjectPath, - fileName: file.name, - mediaType: file.type || 'application/octet-stream', - bytes, - }, - ); - setLocalProject({ - projectPath: nextProjectPath, - manifestPath: result.manifestPath, - manifest, - }); - void refreshManifest(nextProjectPath); - setMessages((current) => [ - ...current, - { role: 'assistant', text: `已保存文件:${result.localPath}` }, - ]); - } catch (error) { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: error instanceof Error ? error.message : String(error), - }, - ]); - } - } - async function loadAgentRunTraceFile( relativePath: string, nextProjectPath = resolveChatProjectPath(localProject) ?? '', @@ -4096,15 +2653,12 @@ export function App({ messagesRef={planningChatMessagesRef} needsUserInput={planningNeedsUserInput} onCancelConfirmation={cancelUiCommandConfirmation} - onCancelPendingCommand={handlePendingCommandCancel} onChatInputChange={handleChatComposerChange} onConfirmConfirmation={confirmUiCommand} - onConfirmPendingCommand={() => void handlePendingCommandConfirm()} onScroll={handlePlanningChatScroll} onShowEarlierMessages={showEarlierConversationMessages} onSubmit={handlePlanningChatSubmit} pendingConfirmation={pendingUiConfirmation} - pendingCommand={pendingCommand} projectPath={localProject?.projectPath ?? projectPath} conversationMessages={messages} transientReply={planningV2TransientReply} diff --git a/apps/ai-game-creator-shell/src/app/types.ts b/apps/ai-game-creator-shell/src/app/types.ts index dd40ab154..88e71bfdb 100644 --- a/apps/ai-game-creator-shell/src/app/types.ts +++ b/apps/ai-game-creator-shell/src/app/types.ts @@ -1186,96 +1186,6 @@ export interface AgentStatusCard { export type AgentTaskGraphState = 'active' | 'carried' | 'ready'; -export type PendingCommand = - | { - id: 'game.generate_draft'; - prompt: string; - } - | { - id: 'game.run_local'; - } - | { - id: 'asset.upload'; - file: File; - } - | { - id: 'asset.register'; - localPath: string; - kind: string; - mediaType: string; - } - | { - id: 'command.run_limited'; - commandId: string; - } - | { - id: 'project.create'; - projectPath: string; - } - | { - id: 'project.checkpoint'; - } - | { - id: 'project.export_package'; - } - | { - id: 'project.index'; - } - | { - id: 'project.restore'; - checkpointId: string; - } - | { - id: 'project.policy_write'; - policy: ProjectPermissionPolicy; - } - | { - id: 'preview.start'; - } - | { - id: 'preview.open'; - } - | { - id: 'agent.kill' | 'agent.retry' | 'agent.resume'; - detail?: string; - } - | { - id: 'memory.write'; - scope: MemoryScope; - content: string; - mode?: MemoryWriteMode; - } - | { - id: 'memory.delete'; - scope: MemoryScope; - } - | { - id: 'canvas.project_open'; - canvasProjectId: string; - } - | { - id: 'canvas.project_sync'; - canvasProjectId: string; - } - | { - id: 'canvas.asset_import'; - localPath: string; - canvasProjectId: string; - canvasAssetId: string; - canvasAssetObjectId?: string; - kind: string; - mediaType: string; - } - | { - id: 'canvas.asset_generate'; - prompt: string; - } - | { - id: 'canvas.export_import'; - exportPath: string; - canvasProjectId: string; - }; - export interface PendingUiConfirmation { commandId: GameCreationAppCommandDescriptor['id']; detail: string; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/pendingCommandPresentation.ts b/apps/ai-game-creator-shell/src/features/project-workspace/pendingCommandPresentation.ts deleted file mode 100644 index f71dd2c22..000000000 --- a/apps/ai-game-creator-shell/src/features/project-workspace/pendingCommandPresentation.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { GAME_CREATION_APP_LIMITED_RUN_COMMANDS } from '../../../../../packages/shared/src/contracts/gameCreationApp'; -import type { PendingCommand } from '../../app/types'; -import { formatAgentPolicySummary } from '../project-summary/agentPresentation'; -import { - formatCanvasAssetSource, - formatProjectPolicyCommandList, -} from '../project-summary/projectSummary'; -import { memoryScopeLabel, memoryScopePath } from './memoryCommands'; - -export function pendingCommandTitle(command: PendingCommand) { - if (command.id === 'asset.upload') { - return `${command.id} · ${command.file.name}`; - } - if (command.id === 'asset.register') { - return `${command.id} · ${command.localPath}`; - } - if (command.id === 'command.run_limited') { - const descriptor = GAME_CREATION_APP_LIMITED_RUN_COMMANDS.find( - (limitedCommand) => limitedCommand.id === command.commandId, - ); - return `${command.id} · ${descriptor?.title ?? command.commandId}`; - } - if (command.id === 'game.run_local') { - return command.id; - } - if (command.id === 'project.create') { - return command.id; - } - if (command.id === 'project.checkpoint') { - return command.id; - } - if (command.id === 'project.export_package') { - return command.id; - } - if (command.id === 'project.index') { - return command.id; - } - if (command.id === 'project.restore') { - return `${command.id} · ${command.checkpointId}`; - } - if (command.id === 'project.policy_write') { - return command.id; - } - if ( - command.id === 'agent.kill' || - command.id === 'agent.retry' || - command.id === 'agent.resume' - ) { - return command.id; - } - if (command.id === 'memory.write' || command.id === 'memory.delete') { - return `${command.id} · ${memoryScopeLabel(command.scope)}`; - } - if (command.id === 'canvas.project_open') { - return `${command.id} · ${command.canvasProjectId}`; - } - if (command.id === 'canvas.project_sync') { - return `${command.id} · ${command.canvasProjectId}`; - } - if (command.id === 'canvas.asset_import') { - return `${command.id} · ${command.localPath}`; - } - if (command.id === 'canvas.asset_generate') { - return command.id; - } - if (command.id === 'canvas.export_import') { - return `${command.id} · ${command.exportPath}`; - } - return command.id; -} - -export function pendingCommandDetail( - command: PendingCommand, - projectPath: string, - directCodex = false, -) { - if (command.id === 'asset.upload') { - return `保存到 ${projectPath}/assets/uploads/`; - } - if (command.id === 'asset.register') { - return `登记 ${projectPath}/${command.localPath} · ${command.kind} · ${command.mediaType}`; - } - if (command.id === 'command.run_limited') { - return `运行 ${command.commandId} 于 ${projectPath}`; - } - if (command.id === 'game.run_local') { - return directCodex - ? `启动 ${projectPath}/game/` - : `运行自检并启动 ${projectPath}/game/`; - } - if (command.id === 'game.generate_draft') { - return `调用 LLM Planner / Generator,编排 6 组角色 brief,写入 ${projectPath}/game、assets、memory、exports,通过 Evaluator 和自检后启动本地 HTTP 预览并载入客户端运行视图`; - } - if (command.id === 'project.create') { - return `创建 ${command.projectPath}`; - } - if (command.id === 'project.checkpoint') { - return `保存 ${projectPath} 当前项目快照`; - } - if (command.id === 'project.export_package') { - return `从 ${projectPath}/game、assets 和 exports/README.md 导出本地试玩包`; - } - if (command.id === 'project.index') { - return `刷新 ${projectPath}/.agent/project.index.json`; - } - if (command.id === 'project.restore') { - return `从 ${command.checkpointId} 恢复 ${projectPath} 的已跟踪项目文件`; - } - if (command.id === 'project.policy_write') { - return `写入 ${projectPath}/.agent/policy.json · 拒绝:${formatProjectPolicyCommandList( - command.policy.deniedCommands, - )} · 确认:${formatProjectPolicyCommandList( - command.policy.confirmCommands, - )} · Agent:${formatAgentPolicySummary(command.policy)}`; - } - if (command.id === 'preview.start') { - return `启动 ${projectPath}/game/ 并载入客户端运行视图`; - } - if (command.id === 'preview.open') { - return `打开 ${projectPath} 的当前本地预览`; - } - if (command.id === 'agent.kill') { - return `标记 ${projectPath}/.agent/run.latest.json 为 killed,并写入 activity/output`; - } - if (command.id === 'agent.retry') { - return `使用 ${projectPath}/.agent/run.latest.json 的目标重新运行一次`; - } - if (command.id === 'agent.resume') { - return `附加说明${ - command.detail ? `「${command.detail}」` : '' - },继续运行 ${projectPath}/.agent/run.latest.json 的目标`; - } - if (command.id === 'memory.write') { - return `${ - command.mode === 'replace' ? '覆盖保存到' : '追加到' - } ${projectPath}/${memoryScopePath(command.scope)}`; - } - if (command.id === 'memory.delete') { - return `删除 ${projectPath}/${memoryScopePath(command.scope)}`; - } - if (command.id === 'canvas.project_open') { - return `打开本机画板项目 ${command.canvasProjectId}`; - } - if (command.id === 'canvas.project_sync') { - return `从画板项目 ${command.canvasProjectId} 同步资源到 ${projectPath}/assets/canvas-sync/`; - } - if (command.id === 'canvas.asset_import') { - return `导入 ${projectPath}/${command.localPath} · 画板 ${formatCanvasAssetSource( - command, - )} · ${command.kind} · ${command.mediaType}`; - } - if (command.id === 'canvas.asset_generate') { - return `通过平台 External Editor API 生成美术素材并写入 ${projectPath}/assets/canvas-generated/`; - } - if (command.id === 'canvas.export_import') { - return `导入 ${command.exportPath} 到 ${projectPath}/assets/canvas-imports/ · 画板 ${command.canvasProjectId}`; - } - return `写入 ${projectPath}`; -} diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/projectCommandPolicy.ts b/apps/ai-game-creator-shell/src/features/project-workspace/projectCommandPolicy.ts index 34663c563..152837459 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/projectCommandPolicy.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/projectCommandPolicy.ts @@ -1,5 +1,4 @@ import type { GameCreationAppCommandDescriptor } from '../../../../../packages/shared/src/contracts/gameCreationApp'; -import type { PendingCommand } from '../../app/types'; import { isAbsoluteProjectPath, projectPathHasControlCharacter, @@ -60,17 +59,3 @@ export function needsInitializedChatProject( 'conversation.write', ].includes(commandId); } - -export function resolvePendingCommandProjectPath( - command: PendingCommand, - localProject: { projectPath: string } | null, - draftProjectPath: string, -) { - if (command.id === 'project.create') { - return command.projectPath; - } - if (needsInitializedChatProject(command.id)) { - return resolveChatProjectPath(localProject) ?? draftProjectPath; - } - return draftProjectPath; -} diff --git a/apps/ai-game-creator-shell/src/view/project-development/planning/PlanningChatView.tsx b/apps/ai-game-creator-shell/src/view/project-development/planning/PlanningChatView.tsx index 9c128b07f..d145eaa2d 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/planning/PlanningChatView.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/planning/PlanningChatView.tsx @@ -9,7 +9,6 @@ import type { DesignClarificationRequest, DesignReasoningEntry, DesignView, - PendingCommand, PendingUiConfirmation, PlanGddDecisionAction, PlanGddStateViewV1, @@ -19,11 +18,6 @@ import { projectNameFromPath, projectRuntimeVisibleError, } from '../../../features/agent-runtime'; -import { - pendingCommandDetail, - pendingCommandTitle, -} from '../../../features/project-workspace/pendingCommandPresentation'; -import { resolvePendingCommandProjectPath } from '../../../features/project-workspace/projectCommandPolicy'; import { ResourceReferenceInput, type ResourceReferenceInputHandle, @@ -103,10 +97,8 @@ type PlanningChatViewProps = { messagesRef: RefObject; needsUserInput: boolean; onCancelConfirmation: () => void; - onCancelPendingCommand: () => void; onChatInputChange: (draft: ChatComposerDraft) => void; onConfirmConfirmation: () => void; - onConfirmPendingCommand: () => void; onScroll: UIEventHandler; onShowEarlierMessages: () => void; onSubmit: FormEventHandler; @@ -116,7 +108,6 @@ type PlanningChatViewProps = { answers: Record, ) => void | Promise; pendingConfirmation: PendingUiConfirmation | null; - pendingCommand: PendingCommand | null; projectPath: string; runtime: AgentRuntimeState | null; transientReply: string; @@ -161,16 +152,13 @@ export function PlanningChatView({ messagesRef, needsUserInput, onCancelConfirmation, - onCancelPendingCommand, onChatInputChange, onConfirmConfirmation, - onConfirmPendingCommand, onScroll, onShowEarlierMessages, onSubmit, onUserInput, pendingConfirmation, - pendingCommand, projectPath, runtime, transientReply, @@ -346,30 +334,6 @@ export function PlanningChatView({ controlBusy={controlBusy} onUserInput={onUserInput} /> - {pendingCommand ? ( -
- - {pendingCommandTitle(pendingCommand)} - - {pendingCommandDetail( - pendingCommand, - resolvePendingCommandProjectPath( - pendingCommand, - { projectPath }, - projectPath, - ), - false, - )} - - - - -
- ) : null} {pendingConfirmation ? (
diff --git a/apps/ai-game-creator-shell/tests/rememberCommand.test.ts b/apps/ai-game-creator-shell/tests/rememberCommand.test.ts index 8367ca203..95f4ed246 100644 --- a/apps/ai-game-creator-shell/tests/rememberCommand.test.ts +++ b/apps/ai-game-creator-shell/tests/rememberCommand.test.ts @@ -10,9 +10,7 @@ import { isAbsoluteProjectPath, needsInitializedChatProject, parseRememberInput, - pendingCommandDetail, resolveChatProjectPath, - resolvePendingCommandProjectPath, } from '../src/App'; describe('AI 游戏创作聊天记忆命令', () => { @@ -50,43 +48,7 @@ describe('AI 游戏创作聊天记忆命令', () => { }); }); - it('describes append and replace memory writes before confirmation', () => { - expect( - pendingCommandDetail( - { - id: 'memory.write', - scope: 'long', - content: '保留厨房主题', - mode: 'append', - }, - '/tmp/game', - ), - ).toBe('追加到 /tmp/game/memory/project.md'); - expect( - pendingCommandDetail( - { - id: 'memory.write', - scope: 'short', - content: '覆盖本轮上下文', - mode: 'replace', - }, - '/tmp/game', - ), - ).toBe('覆盖保存到 /tmp/game/memory/session.md'); - expect( - pendingCommandDetail( - { - id: 'memory.write', - scope: 'blackboard', - content: '共享美术约束', - mode: 'append', - }, - '/tmp/game', - ), - ).toBe('追加到 /tmp/game/memory/blackboard.md'); - }); - - it('requires an initialized local project before chat memory writes', () => { + it('requires an initialized local project path before chat memory commands', () => { expect(resolveChatProjectPath(null)).toBeNull(); expect(resolveChatProjectPath({ projectPath: '/tmp/game' })).toBe( '/tmp/game', @@ -103,47 +65,6 @@ describe('AI 游戏创作聊天记忆命令', () => { scope: 'short', content: '覆盖本轮上下文', }); - expect( - pendingCommandDetail( - { - id: 'memory.delete', - scope: 'short', - }, - '/tmp/game', - ), - ).toBe('删除 /tmp/game/memory/session.md'); - expect( - pendingCommandDetail( - { - id: 'memory.delete', - scope: 'blackboard', - }, - '/tmp/game', - ), - ).toBe('删除 /tmp/game/memory/blackboard.md'); - }); - - it('describes local generation side effects before confirmation', () => { - expect( - pendingCommandDetail( - { - id: 'game.generate_draft', - prompt: '做一个厨房弹幕游戏', - }, - '/tmp/game', - ), - ).toBe( - '调用 LLM Planner / Generator,编排 6 组角色 brief,写入 /tmp/game/game、assets、memory、exports,通过 Evaluator 和自检后启动本地 HTTP 预览并载入客户端运行视图', - ); - }); - - it('describes preview side effects before confirmation', () => { - expect(pendingCommandDetail({ id: 'preview.start' }, '/tmp/game')).toBe( - '启动 /tmp/game/game/ 并载入客户端运行视图', - ); - expect(pendingCommandDetail({ id: 'preview.open' }, '/tmp/game')).toBe( - '打开 /tmp/game 的当前本地预览', - ); }); it('requires a project before chat commands write or run local artifacts', () => { @@ -166,97 +87,6 @@ describe('AI 游戏创作聊天记忆命令', () => { expect(needsInitializedChatProject('canvas.project_open')).toBe(false); }); - it('describes project safety commands before confirmation', () => { - expect( - pendingCommandDetail({ id: 'project.checkpoint' }, '/tmp/game'), - ).toBe('保存 /tmp/game 当前项目快照'); - expect( - pendingCommandDetail( - { id: 'project.restore', checkpointId: 'checkpoint-1' }, - '/tmp/game', - ), - ).toBe('从 checkpoint-1 恢复 /tmp/game 的已跟踪项目文件'); - expect( - pendingCommandDetail( - { - id: 'project.policy_write', - policy: { - deniedCommands: ['file.write'], - confirmCommands: [], - }, - }, - '/tmp/game', - ), - ).toBe( - '写入 /tmp/game/.agent/policy.json · 拒绝:file.write · 确认:无 · Agent:无 Agent 独立策略', - ); - }); - - it('describes canvas asset imports before confirmation', () => { - expect( - pendingCommandDetail( - { - id: 'canvas.asset_import', - localPath: 'assets/hero.png', - canvasProjectId: 'canvas-project-1', - canvasAssetId: '', - canvasAssetObjectId: 'asset-object-1', - kind: 'character', - mediaType: 'image/png', - }, - '/tmp/game', - ), - ).toBe( - '导入 /tmp/game/assets/hero.png · 画板 canvas-project-1 / object:asset-object-1 · character · image/png', - ); - expect( - pendingCommandDetail( - { - id: 'canvas.export_import', - exportPath: '/tmp/canvas-export.zip', - canvasProjectId: 'canvas-project-1', - }, - '/tmp/game', - ), - ).toBe( - '导入 /tmp/canvas-export.zip 到 /tmp/game/assets/canvas-imports/ · 画板 canvas-project-1', - ); - }); - - it('describes agent run lifecycle controls before confirmation', () => { - expect(pendingCommandDetail({ id: 'agent.kill' }, '/tmp/game')).toBe( - '标记 /tmp/game/.agent/run.latest.json 为 killed,并写入 activity/output', - ); - expect(pendingCommandDetail({ id: 'agent.retry' }, '/tmp/game')).toBe( - '使用 /tmp/game/.agent/run.latest.json 的目标重新运行一次', - ); - expect( - pendingCommandDetail( - { id: 'agent.resume', detail: '继续修复输入监听' }, - '/tmp/game', - ), - ).toBe( - '附加说明「继续修复输入监听」,继续运行 /tmp/game/.agent/run.latest.json 的目标', - ); - }); - - it('shows the authorized project path in pending write command details', () => { - expect( - resolvePendingCommandProjectPath( - { id: 'game.generate_draft', prompt: '做一个厨房弹幕游戏' }, - { projectPath: '/authorized/game' }, - '/draft/dev-input', - ), - ).toBe('/authorized/game'); - expect( - resolvePendingCommandProjectPath( - { id: 'project.create', projectPath: '/new/game' }, - { projectPath: '/authorized/game' }, - '/draft/dev-input', - ), - ).toBe('/new/game'); - }); - it('derives per-agent status cards from manifest and latest trace steps', () => { const manifest = createGameCreationAppManifest( 'local-project-draft',