接通 DirectProject 中间阶段进度
订阅 game-creator-agent-progress 并按项目过滤 在执行过程卡显示真实阶段文案并补充回归测试
This commit is contained in:
+47
@@ -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<DirectProjectComposerHandle | null>(null);
|
||||
const [runtimeNotice, setRuntimeNotice] = useState('');
|
||||
const [agentProgress, setAgentProgress] = useState('');
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [approvalOpen, setApprovalOpen] = useState(false);
|
||||
const [approvalMode, setApprovalMode] = useState<ApprovalMode>('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()}
|
||||
|
||||
+4
-1
@@ -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({
|
||||
>
|
||||
<header>
|
||||
<span aria-hidden="true" />
|
||||
<strong>陶泥儿正在处理</strong>
|
||||
<strong>{activeProgress || '陶泥儿正在处理'}</strong>
|
||||
<DirectTurnElapsed startedAt={activeTurnStartedAt} />
|
||||
</header>
|
||||
</AgentMessageContent>
|
||||
|
||||
@@ -77,3 +77,22 @@ test('没有运行中的回合时不订阅时钟', () => {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
test('宿主阶段进度会显示在运行中过程卡,而不是只显示通用忙态', () => {
|
||||
const view = render(
|
||||
<DirectProjectConversation
|
||||
conversationKey="direct-process-progress"
|
||||
turns={[]}
|
||||
historyHasMore={false}
|
||||
historyLoading={false}
|
||||
historyError={null}
|
||||
turnInFlight
|
||||
activeProgress="正在验证游戏"
|
||||
activeTurnStartedAt={0}
|
||||
onLoadEarlierHistory={() => undefined}
|
||||
onRetryEarlierHistory={() => undefined}
|
||||
/>,
|
||||
);
|
||||
expect(view.getByText('正在验证游戏')).toBeTruthy();
|
||||
expect(view.queryByText('陶泥儿正在处理')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -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`。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user