feat: 对接设计 Agent Gateway WebSocket

需求:服务端统一 Agent Gateway 将设计任务状态流切换为 WebSocket,客户端需要实时展示生成任务并支持断线恢复。

实现:Electron Main 管理 Session、一次性 Ticket、WebSocket 心跳与游标续传,按关闭码回收会话;Renderer 继续通过本机 Host API 的 SSE 投影接收任务事件,并保留 REST 降级同步。

验证:typecheck、变更文件 ESLint、37 个聚焦测试及 build:vite 通过。
This commit is contained in:
2026-08-02 20:15:37 +08:00
parent 83e21563dc
commit 518d7ab4cd
17 changed files with 1816 additions and 50 deletions

View File

@@ -32,6 +32,10 @@ import { logger } from '../utils/logger';
import { warmupNetworkOptimization } from '../utils/uv-env';
import { resolvePythonRuntime } from '../utils/python-runtime';
import { initTelemetry } from '../utils/telemetry';
import {
getOrCreateAgentGatewaySessionClientId,
rotateAgentGatewaySessionClientId,
} from '../utils/store';
import { isQuitting, setQuitting } from './app-state';
import { applyProxySettings } from './proxy';
@@ -73,6 +77,7 @@ import {
LocalImageWorkspace,
} from '../image-workspace/local-workspace';
import { WorksSquareDesignWorkspace } from '../image-workspace/works-square-workspace';
import type { DesignWorkspaceModule } from '../image-workspace/module';
const WINDOWS_APP_USER_MODEL_ID = 'app.niancode.desktop';
const isE2EMode = process.env.NIANCODE_E2E === '1';
@@ -152,6 +157,7 @@ let hostApiServer: Server | null = null;
let projectProgressSync: ReturnType<typeof createProjectProgressSync> | null = null;
let worksCloudDeployment: ReturnType<typeof createWorksCloudDeployment> | null = null;
let agentBrowser: AgentBrowserModule | null = null;
let imageWorkspaceModule: DesignWorkspaceModule | null = null;
const mainWindowFocusState = createMainWindowFocusState();
const quitLifecycleState = createQuitLifecycleState();
const launchDeepLinkUrl = findNianCodeDeepLinkUrl(process.argv);
@@ -419,7 +425,13 @@ async function initialize(): Promise<void> {
});
const imageWorkspace = localImageWorkspaceEnabled
? new LocalImageWorkspace({ userDataDir: app.getPath('userData') })
: new WorksSquareDesignWorkspace();
: new WorksSquareDesignWorkspace({
eventSessionClientIdStore: {
getOrCreate: getOrCreateAgentGatewaySessionClientId,
rotate: rotateAgentGatewaySessionClientId,
},
});
imageWorkspaceModule = imageWorkspace;
if (localImageWorkspaceEnabled) {
logger.info('AI painting workspace is using local development storage');
} else {
@@ -647,9 +659,13 @@ if (gotTheLock) {
const stopAgentBrowserPromise = agentBrowser?.dispose().catch((err) => {
logger.warn('agentBrowser.dispose() error during quit:', err);
}) ?? Promise.resolve();
const closeImageWorkspacePromise = imageWorkspaceModule?.closeEventSessions?.().catch((err) => {
logger.warn('imageWorkspace.closeEventSessions() error during quit:', err);
}) ?? Promise.resolve();
const stopPromise = Promise.allSettled([
stopOpencodePromise,
stopAgentBrowserPromise,
closeImageWorkspacePromise,
]);
const timeoutPromise = new Promise<'timeout'>((resolve) => {
setTimeout(() => resolve('timeout'), 5000);