feat: 增加共享 Agent Browser 调试能力

需求:在 AI 编程会话中让用户与 Agent 共享同一浏览器页面,并查看控制台与网络信息。

实现:新增沙箱浏览器内核、Host API/渲染器面板、OpenCode 工具接入及安全边界测试。
This commit is contained in:
2026-07-31 14:53:36 +08:00
parent 80e8386fa6
commit e97a7ce9df
39 changed files with 6734 additions and 9 deletions

View File

@@ -15,6 +15,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
import { OpencodeManager } from '@electron/opencode/manager';
import {
ensureBundledSuperpowersPlugin,
resolveBundledAgentBrowserPluginPath,
resolveBundledSuperpowersDir,
} from '@electron/opencode/superpowers';
import { logger } from '@electron/utils/logger';
@@ -82,6 +83,23 @@ describe('OpencodeManager', () => {
})).toBe(join(resourcesPath, 'resources', 'skills', 'superpowers'));
});
it('resolves the bundled Agent Browser plugin from the course skills bundle', () => {
const resourcesPath = 'C:\\Program Files\\Makelore\\resources';
expect(resolveBundledAgentBrowserPluginPath({
isPackaged: true,
resourcesPath,
appPath: 'C:\\Program Files\\Makelore\\resources\\app.asar',
})).toBe(join(
resourcesPath,
'course-skills',
'agent-browser',
'.opencode',
'plugins',
'niancode-agent-browser.js',
));
});
it('spawns the bundled opencode server and becomes running after listening output', async () => {
const { children, calls, spawn } = createSpawnHarness();
const manager = new OpencodeManager({
@@ -858,6 +876,50 @@ describe('OpencodeManager', () => {
}
});
it('installs the bundled Agent Browser tool plugin before spawning', async () => {
const userDataDir = mkdtempSync(join(tmpdir(), 'niancode-opencode-manager-'));
const sourceDir = mkdtempSync(join(tmpdir(), 'niancode-agent-browser-plugin-source-'));
const sourcePluginPath = join(sourceDir, 'niancode-agent-browser.js');
try {
writeFileSync(
sourcePluginPath,
'export const NianCodeAgentBrowserPlugin = async () => ({});\n',
);
const { children, spawn } = createSpawnHarness();
const manager = new OpencodeManager({
port: 4337,
binPath: '/opt/opencode',
userDataDir,
bundledAgentBrowserPluginPath: sourcePluginPath,
runtimeConfigProvider: () => ({ config: {}, env: {} }),
spawn,
});
const startPromise = manager.start();
children[0].stdout.emit(
'data',
Buffer.from('opencode server listening on http://127.0.0.1:4337\n'),
);
await startPromise;
const installedPluginPath = join(
userDataDir,
'opencode',
'niancode-config',
'plugins',
'niancode-agent-browser.js',
);
expect(existsSync(installedPluginPath)).toBe(true);
expect(readFileSync(installedPluginPath, 'utf8')).toContain(
'NianCodeAgentBrowserPlugin',
);
} finally {
rmSync(userDataDir, { recursive: true, force: true });
rmSync(sourceDir, { recursive: true, force: true });
}
});
it('removes retired bundled skills from the managed config before spawning', async () => {
const userDataDir = mkdtempSync(join(tmpdir(), 'niancode-opencode-manager-'));
const bundledCourseSkillsDir = mkdtempSync(join(tmpdir(), 'niancode-course-skills-source-'));