merge: 集成共享 Agent Browser 调试能力
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

需求:将 Agent Browser 独立功能提交合并到包含 AI 设计 Workspace 的最新主线。

实现:保留双方模块初始化、Host API 路由和 README 产品说明。

# Conflicts:
#	electron/api/context.ts
This commit is contained in:
2026-07-31 14:56:45 +08:00
39 changed files with 6734 additions and 9 deletions

View File

@@ -13,6 +13,7 @@ import {
} from '../opencode/playwright-mcp';
import { resolveOpencodeRuntimePaths } from '../opencode/paths';
import {
resolveBundledAgentBrowserPluginPath,
resolveBundledCourseSkillsDir,
resolveBundledSuperpowersDir,
} from '../opencode/superpowers';
@@ -61,6 +62,7 @@ import { acquireProcessInstanceFileLock } from './process-instance-lock';
import { getHostApiToken, startHostApiServer } from '../api/server';
import { HostEventBus } from '../api/event-bus';
import { AgentBrowserModule, ElectronAgentBrowserAdapter } from '../agent-browser';
import { browserOAuthManager } from '../utils/browser-oauth';
import { createProjectProgressSync } from '../services/project-progress-sync';
import { createWorksCloudDeployment } from '../services/works-cloud-deployment';
@@ -149,6 +151,7 @@ let hostEventBus!: HostEventBus;
let hostApiServer: Server | null = null;
let projectProgressSync: ReturnType<typeof createProjectProgressSync> | null = null;
let worksCloudDeployment: ReturnType<typeof createWorksCloudDeployment> | null = null;
let agentBrowser: AgentBrowserModule | null = null;
const mainWindowFocusState = createMainWindowFocusState();
const quitLifecycleState = createQuitLifecycleState();
const launchDeepLinkUrl = findNianCodeDeepLinkUrl(process.argv);
@@ -310,6 +313,23 @@ function registerMakeloreProtocolClient(): void {
function createMainWindow(): BrowserWindow {
const win = createWindow();
const closeAgentBrowserForHostRenderer = (reason: string): void => {
void agentBrowser?.close().catch((error) => {
logger.warn(`Failed to close Agent Browser after ${reason}:`, error);
});
};
win.webContents.on('render-process-gone', () => {
closeAgentBrowserForHostRenderer('the main renderer exited');
});
win.webContents.on(
'did-start-navigation',
(_event, _url, isInPlace, isMainFrame) => {
if (isMainFrame && !isInPlace) {
closeAgentBrowserForHostRenderer('the main renderer started navigation');
}
},
);
win.once('ready-to-show', () => {
if (mainWindow !== win) {
return;
@@ -332,6 +352,11 @@ function createMainWindow(): BrowserWindow {
});
win.on('closed', () => {
const browser = agentBrowser;
agentBrowser = null;
void browser?.dispose().catch((error) => {
logger.warn('Failed to dispose Agent Browser after the main window closed:', error);
});
if (mainWindow === win) {
mainWindow = null;
}
@@ -406,6 +431,7 @@ async function initialize(): Promise<void> {
// Create the main window
const window = createMainWindow();
agentBrowser = new AgentBrowserModule(new ElectronAgentBrowserAdapter(window));
// Create system tray
if (!isE2EMode) {
@@ -420,6 +446,7 @@ async function initialize(): Promise<void> {
opencodeProjectStore,
eventBus: hostEventBus,
mainWindow: window,
agentBrowser,
worksCloudDeployment: worksCloudDeployment ?? undefined,
imageWorkspace,
});
@@ -508,12 +535,18 @@ if (gotTheLock) {
resourcesPath: process.resourcesPath,
appPath: app.getAppPath(),
});
const bundledAgentBrowserPluginPath = resolveBundledAgentBrowserPluginPath({
isPackaged: app.isPackaged,
resourcesPath: process.resourcesPath,
appPath: app.getAppPath(),
});
opencodeManager = new OpencodeManager({
port: 4096,
binPath: opencodePaths.binPath,
userDataDir: app.getPath('userData'),
bundledSuperpowersDir,
bundledCourseSkillsDir,
bundledAgentBrowserPluginPath,
pythonRuntime,
runtimeConfigProvider: async () => {
const runtime = await buildOpencodeRuntimeConfigFromNianCodeProviders({
@@ -611,7 +644,13 @@ if (gotTheLock) {
const stopOpencodePromise = opencodeManager.stop().catch((err) => {
logger.warn('opencodeManager.stop() error during quit:', err);
});
const stopPromise = Promise.allSettled([stopOpencodePromise]);
const stopAgentBrowserPromise = agentBrowser?.dispose().catch((err) => {
logger.warn('agentBrowser.dispose() error during quit:', err);
}) ?? Promise.resolve();
const stopPromise = Promise.allSettled([
stopOpencodePromise,
stopAgentBrowserPromise,
]);
const timeoutPromise = new Promise<'timeout'>((resolve) => {
setTimeout(() => resolve('timeout'), 5000);
});
@@ -632,6 +671,11 @@ if (gotTheLock) {
logger.error(`${reason}:`, error);
projectProgressSync?.stop();
worksCloudDeployment?.stop();
try {
void agentBrowser?.dispose().catch(() => { /* ignore */ });
} catch {
// ignore — dispose() may not be callable if state is corrupted
}
try {
void opencodeManager?.stop().catch(() => { /* ignore */ });
} catch {