From 2c285fbd9984ed741c0281edb40fe7b7e8539dbd Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:05:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E9=80=9A=20DirectProject=20=E4=B8=AD?= =?UTF-8?q?=E9=97=B4=E9=98=B6=E6=AE=B5=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 订阅 game-creator-agent-progress 并按项目过滤 在执行过程卡显示真实阶段文案并补充回归测试 --- .../chat/DirectProjectChatView.tsx | 47 +++++++++++++++++++ .../DirectProjectConversation.tsx | 5 +- .../tests/directProjectProcessStatus.test.tsx | 19 ++++++++ docs/project-memory/shared-memory/pitfalls.md | 2 +- 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx b/apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx index dd128bf37..f508d5b0a 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx @@ -13,6 +13,11 @@ import type { PendingUiConfirmation } from '../../../app/types'; import { projectNameFromPath } from '../../../features/agent-runtime'; import { registerActiveChatComposer } from '../../../features/project-workspace/activeChatComposer'; import { userItemFromContent } from '../../../features/project-workspace/resourceReferences'; +import { visibleClientErrorMessage } from '../../../services/errorReporting'; +import { + canSubscribeTauriEvents, + subscribeTauriEvent, +} from '../../../services/tauriEventSubscription'; import { type ApprovalMode, approvalModeLabel } from '../approvalMode'; import { ApprovalModeDialog } from '../ApprovalModeDialog'; import { DirectProjectChatHeader } from './components/DirectProjectChatHeader/DirectProjectChatHeader'; @@ -115,6 +120,7 @@ export function DirectProjectChatView({ } = useDirectProjectManifest(projectPath); const composerHandleRef = useRef(null); const [runtimeNotice, setRuntimeNotice] = useState(''); + const [agentProgress, setAgentProgress] = useState(''); const [settingsOpen, setSettingsOpen] = useState(false); const [approvalOpen, setApprovalOpen] = useState(false); const [approvalMode, setApprovalMode] = useState('strict'); @@ -123,6 +129,39 @@ export function DirectProjectChatView({ const handleRuntimeError = useCallback((message: string) => { setRuntimeNotice(message); }, []); + useEffect(() => { + setAgentProgress(''); + if (!projectPath || !canSubscribeTauriEvents()) return undefined; + let disposed = false; + let unsubscribe: (() => void) | null = null; + void subscribeTauriEvent<{ + project_path?: string; + projectPath?: string; + message?: string; + }>('game-creator-agent-progress', (event) => { + if (disposed) return; + const eventProjectPath = + event.payload.project_path ?? event.payload.projectPath ?? ''; + if (eventProjectPath !== projectPath) return; + const message = event.payload.message?.trim(); + if (message) setAgentProgress(message); + }) + .then((release) => { + if (disposed) release(); + else unsubscribe = release; + }) + .catch((error) => { + if (!disposed) { + setRuntimeNotice( + `执行过程进度订阅失败:${visibleClientErrorMessage(error)}`, + ); + } + }); + return () => { + disposed = true; + unsubscribe?.(); + }; + }, [projectPath]); const chat = useDirectProjectChatController({ enabled: Boolean(projectPath), ensureConversationReadAllowed, @@ -156,6 +195,13 @@ export function DirectProjectChatView({ turnCancelling, uploadFiles, } = chat; + useEffect(() => { + // 阶段文案属于当前回合;清掉上一轮的最后阶段,避免下一轮尚未收到首个进度事件时 + // 继续显示旧的“正在验证/正在生成”。 + if (commandInFlight || (!directTurnRunning && pendingTurns.length === 0)) { + setAgentProgress(''); + } + }, [commandInFlight, directTurnRunning, pendingTurns.length]); // 这份投影要参与下游 memo 的判据,必须自己先缓存:不缓存的话每次渲染都是一份新数组, // `useDirectProjectTurnStatus` 里以 `turns` 为判据的 `useMemo` 永远命中不了,那层 memo // 就成了死代码,读代码的人还会误以为 `turnStatus` 是引用稳定的。 @@ -265,6 +311,7 @@ export function DirectProjectChatView({ historyLoading={historyLoading} historyError={historyError} turnInFlight={turnStatus.displayBusy} + activeProgress={agentProgress} activeTurnStartedAt={activeTurnStartedAt} onLoadEarlierHistory={() => void loadEarlierHistory()} onRetryEarlierHistory={() => void retryEarlierHistory()} diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectConversation/DirectProjectConversation.tsx b/apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectConversation/DirectProjectConversation.tsx index 3009776b6..2555bca97 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectConversation/DirectProjectConversation.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectConversation/DirectProjectConversation.tsx @@ -26,6 +26,7 @@ export function DirectProjectConversation({ historyLoading, historyError, turnInFlight, + activeProgress = '', activeTurnStartedAt, onLoadEarlierHistory, onRetryEarlierHistory, @@ -46,6 +47,8 @@ export function DirectProjectConversation({ * `activeTurnStartedAt` 还是 0,卡片只报"正在处理";宿主开始事件一到就开始读秒。 */ turnInFlight: boolean; + /** Rust `game-creator-agent-progress` 的当前阶段;没有事件时保留通用忙态。 */ + activeProgress?: string; activeTurnStartedAt: number; onLoadEarlierHistory: () => void; onRetryEarlierHistory: () => void; @@ -105,7 +108,7 @@ export function DirectProjectConversation({ >
diff --git a/apps/ai-game-creator-shell/tests/directProjectProcessStatus.test.tsx b/apps/ai-game-creator-shell/tests/directProjectProcessStatus.test.tsx index 70be0266c..218950034 100644 --- a/apps/ai-game-creator-shell/tests/directProjectProcessStatus.test.tsx +++ b/apps/ai-game-creator-shell/tests/directProjectProcessStatus.test.tsx @@ -77,3 +77,22 @@ test('没有运行中的回合时不订阅时钟', () => { vi.useRealTimers(); } }); + +test('宿主阶段进度会显示在运行中过程卡,而不是只显示通用忙态', () => { + const view = render( + undefined} + onRetryEarlierHistory={() => undefined} + />, + ); + expect(view.getByText('正在验证游戏')).toBeTruthy(); + expect(view.queryByText('陶泥儿正在处理')).toBeNull(); +}); diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 8f2a543b2..82a5fd91f 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -5,7 +5,7 @@ ## 2026-10-06 AGC 失败终态与 CC/MCP 边界不能吞掉协议、清理和 I/O 错误 - **现象**:DirectProject 的 `turn.completed(status=failed)` 若跨 IPC 丢失 `failure` 载荷,前端会只收口回合而不显示任何说明;Claude Code sidecar 的非 UTF-8 stderr、进程树清理失败、会话表锁损坏,以及 MCP loopback 服务退出、插件能力桥超时、损坏 manifest,会分别被压成空 detail、继续成功取消、伪造 hash projectId 或静默不广告插件。 -- **现行口径**:失败载荷缺失显示明确的协议错误;app-server 只给 `failed` 状态却不带 `error` 对象时,正文保留 `status=failed error=missing` 协议事实;旧历史里的已知通用失败句明确标成“历史错误事件未记录具体错误正文”,不伪装成当前仍在吞错;CC 清理、stderr 解码、会话锁都保留原始正文;MCP server / capability bridge 失败写入统一应用日志,MCP manifest / journal / 时间 / metadata 读取失败返回具体错误;完成判定在把素材缺失归因于“未引用平台图”前先验证源码、manifest 和已登记图片的 I/O/PNG 错误;资源生成队列提交异常直接收口失败并恢复可重试草稿。切片文件是可选投影,完整 spritesheet 不因无切片而误失败。 +- **现行口径**:失败载荷缺失显示明确的协议错误;app-server 只给 `failed` 状态却不带 `error` 对象时,正文保留 `status=failed error=missing` 协议事实;旧历史里的已知通用失败句明确标成“历史错误事件未记录具体错误正文”,不伪装成当前仍在吞错;CC 清理、stderr 解码、会话锁都保留原始正文;后端 `game-creator-agent-progress` 阶段事件由 DirectProject 按项目路径订阅并显示在执行过程卡,不再只显示空白忙态;MCP server / capability bridge 失败写入统一应用日志,MCP manifest / journal / 时间 / metadata 读取失败返回具体错误;完成判定在把素材缺失归因于“未引用平台图”前先验证源码、manifest 和已登记图片的 I/O/PNG 错误;资源生成队列提交异常直接收口失败并恢复可重试草稿。切片文件是可选投影,完整 spritesheet 不因无切片而误失败。 - **验证**:`cargo test agent::claude_code_cli::tests::` 14 passed;`cargo test agent::direct_tools_mcp::tests::` 34 passed;`cargo test agent::direct_runtime::tests::` 92 passed;`npx vitest run tests/directThreadChat.test.ts tests/directTurnFailure.test.ts` 64 passed;另有损坏 manifest 回归用例 1 passed。新失败正文仍经既有脱敏边界处理。 - **关联**:`agent/claude_code_cli.rs`、`agent/direct_tools_mcp.rs`、`agent/direct_runtime/mod.rs`、`agent/thread_manager/dispatch.rs`、`chat/conversation/directThreadChat.ts`。