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

@@ -585,6 +585,24 @@ async function findProjectById(ctx: HostApiContext, projectId: string) {
return projects.find((project) => project.id === projectId) ?? null;
}
async function closeAgentBrowserForProject(
ctx: HostApiContext,
projectPath: string,
): Promise<void> {
try {
await ctx.agentBrowser?.close(projectPath);
} catch (error) {
if (
error
&& typeof error === 'object'
&& (error as { code?: unknown }).code === 'PROJECT_MISMATCH'
) {
return;
}
throw error;
}
}
type ProjectActivationCheck =
| { ok: true }
| { ok: false; status: Exclude<ProjectConfigReadResult['status'], 'valid'> | 'incomplete'; error: string };
@@ -1161,7 +1179,14 @@ export async function handleOpencodeRoutes(
try {
const body = await parseJsonBody<{ projectId?: string }>(req);
if (!body.projectId) throw new Error('Missing project id');
const activeProject = await ctx.opencodeProjectStore.getActiveProject();
if (activeProject?.id === body.projectId) {
await closeAgentBrowserForProject(ctx, activeProject.path);
}
await ctx.opencodeProjectStore.removeProject(body.projectId);
if (activeProject?.id === body.projectId) {
await closeAgentBrowserForProject(ctx, activeProject.path);
}
await sendProjectSnapshot(res, ctx, { success: true, removedProjectId: body.projectId });
} catch (error) {
sendJson(res, 500, { success: false, error: String(error) });
@@ -1193,7 +1218,14 @@ export async function handleOpencodeRoutes(
sendProjectActivationFailure(res, projectCheck);
return true;
}
const activeProject = await ctx.opencodeProjectStore.getActiveProject();
if (activeProject?.id !== body.projectId && activeProject) {
await closeAgentBrowserForProject(ctx, activeProject.path);
}
const project = await ctx.opencodeProjectStore.setActiveProject(body.projectId);
if (activeProject?.id !== body.projectId && activeProject) {
await closeAgentBrowserForProject(ctx, activeProject.path);
}
await sendProjectSnapshot(res, ctx, { success: true, project });
} catch (error) {
sendJson(res, 500, { success: false, error: String(error) });