From 196a5979ce61606741ddefd7b99d478f15ad505c Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:31:41 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E6=94=B6=E6=95=9B=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E4=B8=8E=E7=94=9F=E6=88=90=E9=98=9F=E5=88=97=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=AD=A3=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保留预览复用 IPC 拒绝与重启回退的具体原因 保留素材生成事件订阅失败并补充预览与队列回归测试 --- apps/ai-game-creator-shell/src/App.tsx | 49 ++++++++++--------- .../resourceCanvasAssetGenerationQueue.ts | 23 +++++---- .../tests/previewActivation.test.tsx | 4 +- ...resourceCanvasAssetGenerationQueue.test.ts | 16 ++++++ ...€�里程碑】AGC错误具体文本展示-2026-10-04.md | 1 + 5 files changed, 59 insertions(+), 34 deletions(-) diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index eafde83f5..266892262 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -1513,24 +1513,19 @@ export function App({ invoke: TauriInvoke, nextProjectPath: string, ): Promise { - try { - const status = await invoke( - 'activate_local_game_preview', - { projectPath: nextProjectPath }, - ); - if ( - status.status !== 'running' || - !status.url || - !status.port || - !status.root - ) { - return null; - } - return { url: status.url, port: status.port, root: status.root }; - } catch { - // 没在跑、不属于这个项目,或权限位要求确认:都不是错误,回落到重新启动预览。 + const status = await invoke( + 'activate_local_game_preview', + { projectPath: nextProjectPath }, + ); + if ( + status.status !== 'running' || + !status.url || + !status.port || + !status.root + ) { return null; } + return { url: status.url, port: status.port, root: status.root }; } async function executeRunLocal() { @@ -1548,11 +1543,14 @@ export function App({ return; } + let previewActivationError: string | null = null; try { - const activePreview = await activateRunningPreview( - invoke, - nextProjectPath, - ); + let activePreview: LocalPreviewResult | null = null; + try { + activePreview = await activateRunningPreview(invoke, nextProjectPath); + } catch (error) { + previewActivationError = visibleClientErrorMessage(error); + } if (activePreview) { updateClientPreview(activePreview); // 运行成功的反馈走工作台壳的 toast(对话区只保留对话内容),因此不再往聊天里 @@ -1571,11 +1569,18 @@ export function App({ * `announceToChat` 约束(它是旧的「聊天播报」开关)。当前唯一调用点由播放请求驱动、 * 恒为 true(见 `executeRunLocalRef.current(true)`)。 */ - onRunNotice?.({ message: '运行通过,已载入客户端运行视图' }); + onRunNotice?.({ + message: previewActivationError + ? `已有预览复用失败,已重新启动运行视图:${previewActivationError}` + : '运行通过,已载入客户端运行视图', + }); } catch (error) { + const startError = visibleClientErrorMessage(error); onRunNotice?.({ tone: 'error', - message: `运行游戏失败:${visibleClientErrorMessage(error)}`, + message: previewActivationError + ? `已有预览复用失败:${previewActivationError};重新启动运行也失败:${startError}` + : `运行游戏失败:${startError}`, }); } } diff --git a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts index 48c13571e..2840b589d 100644 --- a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts +++ b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts @@ -153,13 +153,13 @@ export function createResourceCanvasAssetGenerationQueue( function replaceFromRecord( task: ResourceCanvasAssetGenerationTask, record: LocalProjectAssetGenerationTaskRecord, + subscriptionWarning?: string | null, ) { - deps.replaceTask( - mergeLocalProjectAssetGenerationRecord( - task, - safeGenerationRecord(record), - ), - ); + const safeRecord = safeGenerationRecord(record); + if (subscriptionWarning) { + safeRecord.phaseDetail = `${subscriptionWarning};${safeRecord.phaseDetail}`; + } + deps.replaceTask(mergeLocalProjectAssetGenerationRecord(task, safeRecord)); } async function dispatch( @@ -167,6 +167,7 @@ export function createResourceCanvasAssetGenerationQueue( ): Promise { const projectPath = deps.projectPath(); let releaseTaskSubscription: (() => void) | null = null; + let subscriptionWarning: string | null = null; let taskEventPending = false; let resolveTaskEvent: (() => void) | null = null; const waitForTaskEvent = () => { @@ -190,8 +191,10 @@ export function createResourceCanvasAssetGenerationQueue( resolve?.(); }, ); - } catch { - // 事件桥不可用时仅回退到测试/浏览器预览的注入等待器。 + } catch (error) { + // 事件桥不可用时回退到轮询,但不能把 IPC 失败伪装成“没有事件”;把安全正文附到 + // 任务阶段,用户仍能看到本轮为何降级以及后续账本读取是否成功。 + subscriptionWarning = `任务事件订阅失败,已回退轮询:${errorMessage(error)}`; releaseTaskSubscription = null; } } @@ -239,7 +242,7 @@ export function createResourceCanvasAssetGenerationQueue( error: errorMessage(error), }; } - replaceFromRecord(task, started); + replaceFromRecord(task, started, subscriptionWarning); let missingRecordPolls = 0; let readFailed = false; let lastReadError = RESOURCE_CANVAS_ASSET_GENERATION_MALFORMED_READ_ERROR; @@ -266,7 +269,7 @@ export function createResourceCanvasAssetGenerationQueue( missingRecordPolls = 0; readFailed = false; const safeRecord = safeGenerationRecord(record); - replaceFromRecord(task, safeRecord); + replaceFromRecord(task, safeRecord, subscriptionWarning); if (isTerminalStatus(safeRecord.status)) { return { taskId: task.taskId, diff --git a/apps/ai-game-creator-shell/tests/previewActivation.test.tsx b/apps/ai-game-creator-shell/tests/previewActivation.test.tsx index 8816daaac..bfabc2cd0 100644 --- a/apps/ai-game-creator-shell/tests/previewActivation.test.tsx +++ b/apps/ai-game-creator-shell/tests/previewActivation.test.tsx @@ -154,7 +154,7 @@ describe('运行入口切到已经在跑的客户端预览', () => { ); }); - it('权限位要求确认时不把拒绝当错误,直接回落到重新启动预览', async () => { + it('预览复用 IPC 被拒绝时回落重启但保留具体正文', async () => { const { invokeCounts } = installTauri(() => { throw new Error('需要先确认预览权限'); }); @@ -168,7 +168,7 @@ describe('运行入口切到已经在跑的客户端预览', () => { ); await expectRunNoticeWithoutChatTip( onRunNotice, - '运行通过,已载入客户端运行视图', + '已有预览复用失败,已重新启动运行视图:需要先确认预览权限', ); }); diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationQueue.test.ts b/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationQueue.test.ts index 3e2491016..0a7be8171 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationQueue.test.ts +++ b/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationQueue.test.ts @@ -348,6 +348,22 @@ describe('本地排队驱动器', () => { expect(releaseCount).toBe(1); }); + test('事件桥 IPC 失败回退轮询时保留安全错误正文', async () => { + const harness = createHarness((pollCount, api) => { + if (pollCount === 1) api.advance('task-subscribe-failed', 'completed'); + }); + harness.deps.subscribe = async () => { + throw new Error('IPC EPIPE:事件订阅失败'); + }; + const queue = createResourceCanvasAssetGenerationQueue(harness.deps); + + await queue.submit(localTask('task-subscribe-failed', 10)); + + expect(harness.tasks()[0]?.phaseDetail).toContain('任务事件订阅失败'); + expect(harness.tasks()[0]?.phaseDetail).toContain('IPC EPIPE'); + expect(harness.settlements[0]?.status).toBe('completed'); + }); + test('第一条未完成时提交第二条:第二条仍是排队中且生成提交 IPC 只发了一次', async () => { let assertedMidFlight = false; const harness = createHarness((pollCount, api) => { diff --git a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md index ba14a072d..90ab4729f 100644 --- a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md +++ b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md @@ -72,3 +72,4 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时, - 继续真实客户端启动和旁路扫描又发现三处仍会损失事实:旧历史通用中断文案遇到换行/多空格时未命中“正文缺失”标记,非 2xx 响应正文读取失败被 `unwrap_or_default()` 抹掉,CC sidecar 的 stderr 排空超时/读取任务失败也被当成“没有 stderr”。现分别改为折叠空白识别、保留“响应正文读取失败:底层原因”和保留 stderr 排空阶段/底层原因;不改变状态码、重试和脱敏边界。 - 第二轮 Agent/CC 旁路扫描继续发现执行许可/执行会话/MCP 本地读取/编辑器 worker/Cocos worker 的 JoinError,以及 MCP future panic,会被固定成“任务未返回/异常/需要核对”;现把 join/panic 正文并入既有 ToolFailure / MCP 错误载荷,保留 `needs-reconciliation` 的不可自动重放语义。Native 定向测试:Direct MCP 33 项、Direct Tool Bridge 41 项、CC 14 项均通过。 - 最新漏扫又补出 Direct Tools MCP 启动/运行退出码、Skill `resources/read` 索引与资源读取、交付 `finish_sealing` 错误三处固定句;现在分别写入 stderr、MCP 错误正文和交付复核底层原因,避免只剩退出码或“暂不可用/复核失败”。 +- 前端继续实测发现预览活体复用的 IPC / 权限拒绝会被当成“没有活体”并静默重启,资源生成事件订阅失败也只回退轮询;现在两条回退仍保留,但分别把复用失败正文附到重启结果、把订阅失败正文附到任务阶段。预览与生成队列定向测试 25 项通过。