fix(browser): preserve chat focus during agent browsing
This commit is contained in:
@@ -20,7 +20,7 @@ function isAllowedTopLevelUrl(value: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function wrapDebugger(contents: WebContents): AgentBrowserDebuggerPort {
|
||||
function wrapDebugger(contents: WebContents, isVisible: () => boolean): AgentBrowserDebuggerPort {
|
||||
const debuggerEvents = contents.debugger as unknown as {
|
||||
on(event: string, listener: PortListener): void;
|
||||
removeListener(event: string, listener: PortListener): void;
|
||||
@@ -29,8 +29,14 @@ function wrapDebugger(contents: WebContents): AgentBrowserDebuggerPort {
|
||||
attach: (version) => contents.debugger.attach(version),
|
||||
detach: () => contents.debugger.detach(),
|
||||
isAttached: () => contents.debugger.isAttached(),
|
||||
sendCommand: (method, params, sessionRef) =>
|
||||
contents.debugger.sendCommand(method, params, sessionRef),
|
||||
sendCommand: async (method, params, sessionRef) => {
|
||||
if (method === 'Page.captureScreenshot' && !isVisible()) {
|
||||
// CDP capture can wait forever for a hidden view's compositor. Electron's
|
||||
// native capture requests a frame without showing or focusing the view.
|
||||
await contents.capturePage(undefined, { stayHidden: true, stayAwake: false });
|
||||
}
|
||||
return contents.debugger.sendCommand(method, params, sessionRef);
|
||||
},
|
||||
on: (event, listener) => debuggerEvents.on(event, listener),
|
||||
removeListener: (event, listener) =>
|
||||
debuggerEvents.removeListener(event, listener),
|
||||
@@ -53,13 +59,13 @@ function wrapNavigation(contents: WebContents): AgentBrowserNavigationPort {
|
||||
};
|
||||
}
|
||||
|
||||
function wrapWebContents(contents: WebContents): AgentBrowserWebContentsPort {
|
||||
function wrapWebContents(contents: WebContents, isVisible: () => boolean): AgentBrowserWebContentsPort {
|
||||
const contentsEvents = contents as unknown as {
|
||||
on(event: string, listener: PortListener): void;
|
||||
removeListener(event: string, listener: PortListener): void;
|
||||
};
|
||||
return {
|
||||
debugger: wrapDebugger(contents),
|
||||
debugger: wrapDebugger(contents, isVisible),
|
||||
navigationHistory: wrapNavigation(contents),
|
||||
loadURL: (url) => contents.loadURL(url),
|
||||
getURL: () => contents.getURL(),
|
||||
@@ -111,7 +117,7 @@ export class ElectronAgentBrowserAdapter implements AgentBrowserAdapter {
|
||||
this.guardSession(contents.session);
|
||||
|
||||
const view: AgentBrowserViewPort = {
|
||||
webContents: wrapWebContents(contents),
|
||||
webContents: wrapWebContents(contents, () => nativeView.getVisible()),
|
||||
setBounds: (bounds: AgentBrowserBounds) =>
|
||||
nativeView.setBounds(this.toNativeBounds(bounds)),
|
||||
setVisible: (visible: boolean) => nativeView.setVisible(visible),
|
||||
|
||||
@@ -615,7 +615,7 @@ export class AgentBrowserModule {
|
||||
}
|
||||
|
||||
async readEvents(input: AgentBrowserReadEventsInput): Promise<AgentBrowserCdpEventPage> {
|
||||
let record = this.requirePresented(input.projectPath);
|
||||
let record = this.requireViewport(input.projectPath);
|
||||
const after = normalizeNonNegativeInteger(input.after, 0, 'after');
|
||||
const limit = normalizeIntegerRange(input.limit, 100, 1, 200, 'limit');
|
||||
const waitMs = normalizeIntegerRange(input.waitMs, 0, 0, MAX_WAIT_MS, 'waitMs');
|
||||
@@ -645,13 +645,13 @@ export class AgentBrowserModule {
|
||||
|| current !== record
|
||||
|| current.eventBuffer.cursor !== cursorBeforeWait;
|
||||
});
|
||||
record = this.requirePresented(input.projectPath);
|
||||
record = this.requireViewport(input.projectPath);
|
||||
page = record.eventBuffer.read(page.nextCursor, input.methods, limit);
|
||||
return page;
|
||||
}
|
||||
|
||||
async readPayload(input: AgentBrowserReadPayloadInput): Promise<AgentBrowserPayloadChunk> {
|
||||
this.requirePresented(input.projectPath);
|
||||
this.requireViewport(input.projectPath);
|
||||
return this.payloadStore.read(input.handle, input.offset, input.maxBytes);
|
||||
}
|
||||
|
||||
@@ -1589,7 +1589,7 @@ export class AgentBrowserModule {
|
||||
}
|
||||
|
||||
private requireAttached(projectPath: string): BrowserRecord {
|
||||
const record = this.requirePresented(projectPath);
|
||||
const record = this.requireViewport(projectPath);
|
||||
if (record.state === 'suspended_devtools') {
|
||||
throw new AgentBrowserFault(
|
||||
'DEVTOOLS_CONFLICT',
|
||||
@@ -1617,12 +1617,14 @@ export class AgentBrowserModule {
|
||||
return record;
|
||||
}
|
||||
|
||||
private requirePresented(projectPath: string): BrowserRecord {
|
||||
private requireViewport(projectPath: string): BrowserRecord {
|
||||
const record = this.requireRecord(projectPath);
|
||||
if (!record.visible || !record.bounds) {
|
||||
// Switching to chat hides the native view without ending its debugging session.
|
||||
// A new view still needs its first real viewport before tools can operate.
|
||||
if (!record.bounds) {
|
||||
throw new AgentBrowserFault(
|
||||
'VIEWPORT_NOT_READY',
|
||||
'开发浏览器已收起,智能体调试已暂停。',
|
||||
'开发浏览器显示区域尚未准备好。',
|
||||
true,
|
||||
record.generation,
|
||||
);
|
||||
|
||||
@@ -119,7 +119,9 @@ export class PiAgentBrowserTool {
|
||||
});
|
||||
const runKey = `${context.conversationId}:${context.runId}`;
|
||||
this.diagnosticRuns.set(runKey, { projectPath: context.projectPath, owner });
|
||||
if (this.requestPresentation) {
|
||||
// Only a new viewport needs foreground presentation. Reusing an initialized
|
||||
// page must respect the user's selected tab, including while navigating.
|
||||
if (this.requestPresentation && !snapshot.bounds) {
|
||||
try {
|
||||
this.requestPresentation(snapshot);
|
||||
snapshot = await this.browser.waitForPresentation({
|
||||
@@ -139,6 +141,8 @@ export class PiAgentBrowserTool {
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
this.publishState?.(snapshot);
|
||||
}
|
||||
return result(action, publicSnapshot(snapshot));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user