From 08f0e953e4016ded71a7127f424d522b91a5649c Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 2 Nov 2026 02:24:38 +0000 Subject: [PATCH 1/8] test(desktop): reproduce agent-clicked _blank links opening in the foreground (#29009) These tests reproduce pingdotgg/t3code#29009: a target=_blank link the agent's CDP input clicked is emitted with background: false, so the web side opens it as a user action and steals focus. Co-Authored-By: Claude Opus 5.5 --- apps/desktop/src/preview/Manager.test.ts | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/apps/desktop/src/preview/Manager.test.ts b/apps/desktop/src/preview/Manager.test.ts index d917af72d389b..b6f2dd7e50abe 100644 --- a/apps/desktop/src/preview/Manager.test.ts +++ b/apps/desktop/src/preview/Manager.test.ts @@ -1812,6 +1812,64 @@ describe("PreviewManager", () => { }).pipe(Effect.provide(managerLayer()), Effect.scoped), ); + effectIt.effect("opens a new-tab link the agent clicked in the background", () => + Effect.gen(function* () { + const host = yield* DesktopBrowserHost.DesktopBrowserHost; + const serverTab = { threadId: "thread-1", tabId: "server-tab-1" }; + const contents = Object.assign( + makeTestPreviewWebContents(async () => { + throw new Error("unused"); + }, 42), + { isDevToolsOpened: () => false, getUserAgent: () => "Electron" }, + ); + fromId.mockReturnValue(contents); + const manager = yield* PreviewManager.PreviewManager; + const openLinks = yield* Queue.unbounded<{ url: string; background: boolean }>(); + yield* manager.subscribeOpenLinks(({ url, background }) => + Queue.offer(openLinks, { url, background }).pipe(Effect.asVoid), + ); + const attached = yield* Deferred.make(); + yield* host.events.pipe( + Stream.runForEach((line) => + new TextDecoder().decode(line).includes('"type":"attached"') + ? Deferred.succeed(attached, undefined) + : Effect.void, + ), + Effect.forkScoped, + ); + yield* manager.createTab("tab_1", { serverTab }); + yield* manager.registerWebview("tab_1", 42); + yield* Deferred.await(attached); + const openWindow = vi.mocked(contents.setWindowOpenHandler).mock.calls[0]![0]; + const clickBlankLink = (url: string) => + openWindow({ url, disposition: "foreground-tab" } as Electron.HandlerDetails); + + clickBlankLink("https://example.com/person"); + expect(yield* Queue.take(openLinks)).toEqual({ + url: "https://example.com/person", + background: false, + }); + + yield* host.handleCommandLine( + JSON.stringify({ + type: "cdp", + ...serverTab, + message: JSON.stringify({ + id: 1, + method: "Input.dispatchMouseEvent", + params: { type: "mousePressed", x: 10, y: 10, button: "left", clickCount: 1 }, + sessionId: "t3-preview-page", + }), + }), + ); + clickBlankLink("https://example.com/agent"); + expect(yield* Queue.take(openLinks)).toEqual({ + url: "https://example.com/agent", + background: true, + }); + }).pipe(Effect.provide(managerLayer()), Effect.scoped), + ); + effectIt.effect("discards a screenshot that resolves after its guest is replaced", () => withManager((manager) => Effect.gen(function* () { From 77abaf63c519f54a4a272154a6e7920e9a5f30f2 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 2 Nov 2026 02:27:30 +0000 Subject: [PATCH 2/8] fix(desktop): new-tab links the agent clicked open in the background A target=_blank link clicked by the agent's CDP input reported a foreground-tab disposition, so the web opened it as a user action and moved focus off the composer. The window-open handler now also treats a link as background when the guest had agent input in the last 5 seconds, reusing the signal downloads already used (renamed agentActedRecently). Fixes pingdotgg/t3code#29009 Co-Authored-By: Claude Opus 5.5 --- apps/desktop/src/preview/DesktopBrowserHost.test.ts | 6 +++--- apps/desktop/src/preview/DesktopBrowserHost.ts | 10 +++++----- apps/desktop/src/preview/Manager.ts | 7 ++++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/preview/DesktopBrowserHost.test.ts b/apps/desktop/src/preview/DesktopBrowserHost.test.ts index 2e6e54a5293a7..ab013e65bd5a0 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.test.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.test.ts @@ -947,7 +947,7 @@ describe("DesktopBrowserHost", () => { }).pipe(Effect.scoped), ); - it.effect("tells a download the person clicked from one the agent's input started", () => + it.effect("tells a person's action from one the agent's input started", () => Effect.gen(function* () { const host = yield* DesktopBrowserHost.make; const debuggee = makeDebuggee(); @@ -960,7 +960,7 @@ describe("DesktopBrowserHost", () => { message: encodeJson({ id: 1, method: "DOM.getDocument", params: {} }), }), ); - expect(host.humanStartedDownload(debuggee.tab.webContents)).toBe(true); + expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(false); yield* host.handleCommandLine( encodeJson({ type: "cdp", @@ -972,7 +972,7 @@ describe("DesktopBrowserHost", () => { }), }), ); - expect(host.humanStartedDownload(debuggee.tab.webContents)).toBe(false); + expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(true); }), ); diff --git a/apps/desktop/src/preview/DesktopBrowserHost.ts b/apps/desktop/src/preview/DesktopBrowserHost.ts index 3ac6c6d73caec..d377469101bfe 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.ts @@ -24,7 +24,7 @@ const AGENT_INPUT_COMMAND = /"method":"(?:Input\.|Page\.navigate"|Runtime\.(?:evaluate|callFunctionOn)")/; /** The same commands as above, by method, for the relay's sends. */ const AGENT_INPUT_METHOD = /^(?:Input\.|Page\.navigate$|Runtime\.(?:evaluate|callFunctionOn)$)/; -const AGENT_DOWNLOAD_WINDOW_MS = 5_000; +const AGENT_INPUT_WINDOW_MS = 5_000; const FOCUS_IDLE = "5 seconds"; export interface DesktopBrowserTabKey { @@ -87,7 +87,7 @@ export class DesktopBrowserHost extends Context.Service< readonly handleCommandLine: (line: string) => Effect.Effect; readonly attach: (key: DesktopBrowserTabKey, debuggee: DesktopBrowserTabDebugger) => void; readonly detach: (key: DesktopBrowserTabKey) => void; - readonly humanStartedDownload: (source: Electron.WebContents) => boolean; + readonly agentActedRecently: (source: Electron.WebContents) => boolean; readonly placeDownload: (source: Electron.WebContents, item: Electron.DownloadItem) => boolean; readonly pointers: Stream.Stream<{ readonly key: DesktopBrowserTabKey; @@ -482,9 +482,9 @@ export const make = Effect.gen(function* () { relayFor(tab).receive(command.value.message); }); - const humanStartedDownload = (source: Electron.WebContents) => { + const agentActedRecently = (source: Electron.WebContents) => { const tab = [...tabs.values()].find((candidate) => candidate.debuggee.webContents === source); - return tab !== undefined && performance.now() - tab.agentInputAt > AGENT_DOWNLOAD_WINDOW_MS; + return tab !== undefined && performance.now() - tab.agentInputAt <= AGENT_INPUT_WINDOW_MS; }; const announceAll = Effect.suspend(() => { @@ -515,7 +515,7 @@ export const make = Effect.gen(function* () { attach, detach, placeDownload, - humanStartedDownload, + agentActedRecently, }); }); diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index 0087444a8412c..9f8077ffa8103 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -1548,7 +1548,8 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function emitOpenLink({ tabId, url: details.url, - background: details.disposition === "background-tab", + background: + details.disposition === "background-tab" || browserHost.agentActedRecently(wc), }), ); return { action: "deny" }; @@ -3598,8 +3599,8 @@ export const make = Effect.gen(function* PreviewManagerMake() { downloadSessions.add(session); session.on("will-download", (_event, item, source) => { // Decided before the server's path is set: placing it consumes the guid. - const human = browserHost.humanStartedDownload(source); - if (!browserHost.placeDownload(source, item) || !human) return; + const agentStarted = browserHost.agentActedRecently(source); + if (!browserHost.placeDownload(source, item) || agentStarted) return; item.once("done", (_doneEvent, state) => { if (state !== "completed") return; runForkDownload( From 2d2b819582911d2e56c8fd2c7b294b2ad50e69ce Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 2 Nov 2026 02:41:51 +0000 Subject: [PATCH 3/8] fix(desktop): a person's new-tab click after agent input stays in the foreground Agent input marks a guest for 5 s, so a person who clicked into that guest and opened a _blank link inside the window got a background tab. A person's click focuses the guest while the agent's leaves focus with the composer, so background the tab only when the guest does not hold keyboard focus. Co-Authored-By: Claude Opus 5.5 --- apps/desktop/src/preview/Manager.test.ts | 15 ++++++++++++++- apps/desktop/src/preview/Manager.ts | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/preview/Manager.test.ts b/apps/desktop/src/preview/Manager.test.ts index b6f2dd7e50abe..3cad6f8f94e1a 100644 --- a/apps/desktop/src/preview/Manager.test.ts +++ b/apps/desktop/src/preview/Manager.test.ts @@ -1820,7 +1820,12 @@ describe("PreviewManager", () => { makeTestPreviewWebContents(async () => { throw new Error("unused"); }, 42), - { isDevToolsOpened: () => false, getUserAgent: () => "Electron" }, + { + isDevToolsOpened: () => false, + getUserAgent: () => "Electron", + isFocused: () => true, + focusedFrame: {}, + }, ); fromId.mockReturnValue(contents); const manager = yield* PreviewManager.PreviewManager; @@ -1850,6 +1855,7 @@ describe("PreviewManager", () => { background: false, }); + Object.assign(contents, { focusedFrame: null }); yield* host.handleCommandLine( JSON.stringify({ type: "cdp", @@ -1867,6 +1873,13 @@ describe("PreviewManager", () => { url: "https://example.com/agent", background: true, }); + + Object.assign(contents, { focusedFrame: {} }); + clickBlankLink("https://example.com/person-after-agent"); + expect(yield* Queue.take(openLinks)).toEqual({ + url: "https://example.com/person-after-agent", + background: false, + }); }).pipe(Effect.provide(managerLayer()), Effect.scoped), ); diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index 9f8077ffa8103..37510c5d1bc49 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -1548,8 +1548,10 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function emitOpenLink({ tabId, url: details.url, + // A person's click focuses the guest; the agent's leaves focus with the composer. background: - details.disposition === "background-tab" || browserHost.agentActedRecently(wc), + details.disposition === "background-tab" || + (browserHost.agentActedRecently(wc) && !holdsKeyboardFocus(wc)), }), ); return { action: "deny" }; From 3d102d06e3f764914193effe82747d31684fe533 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 2 Nov 2026 02:49:38 +0000 Subject: [PATCH 4/8] fix(desktop): tell a person's click from the agent's by the guest's focused frame Co-Authored-By: Claude Opus 5.5 --- apps/desktop/src/preview/Manager.test.ts | 7 +------ apps/desktop/src/preview/Manager.ts | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src/preview/Manager.test.ts b/apps/desktop/src/preview/Manager.test.ts index 3cad6f8f94e1a..971d3e1f97d82 100644 --- a/apps/desktop/src/preview/Manager.test.ts +++ b/apps/desktop/src/preview/Manager.test.ts @@ -1820,12 +1820,7 @@ describe("PreviewManager", () => { makeTestPreviewWebContents(async () => { throw new Error("unused"); }, 42), - { - isDevToolsOpened: () => false, - getUserAgent: () => "Electron", - isFocused: () => true, - focusedFrame: {}, - }, + { isDevToolsOpened: () => false, getUserAgent: () => "Electron", focusedFrame: {} }, ); fromId.mockReturnValue(contents); const manager = yield* PreviewManager.PreviewManager; diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index 37510c5d1bc49..b4b70c0516d34 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -1548,10 +1548,10 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function emitOpenLink({ tabId, url: details.url, - // A person's click focuses the guest; the agent's leaves focus with the composer. + // A person's click focuses a guest frame; the agent's leaves focus with the composer. background: details.disposition === "background-tab" || - (browserHost.agentActedRecently(wc) && !holdsKeyboardFocus(wc)), + (browserHost.agentActedRecently(wc) && wc.focusedFrame === null), }), ); return { action: "deny" }; From ae2972e160d1b6a81cf549a102827bd00272176f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 2 Nov 2026 03:05:14 +0000 Subject: [PATCH 5/8] docs(desktop): name the focus restore the new-tab check relies on Co-Authored-By: Claude Opus 5.5 --- apps/desktop/src/preview/Manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index b4b70c0516d34..b036aaf0f6a2f 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -1548,7 +1548,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function emitOpenLink({ tabId, url: details.url, - // A person's click focuses a guest frame; the agent's leaves focus with the composer. + // A person's click focuses a guest frame; GuestInput hands focus back after the agent's press. background: details.disposition === "background-tab" || (browserHost.agentActedRecently(wc) && wc.focusedFrame === null), From 4d26bdee0c62ab5079cd949eb206ca5ec32ef70f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 10 Nov 2026 23:44:00 +0000 Subject: [PATCH 6/8] fix(desktop): a person's click through a viewer is not the agent's input Stamp agent input where the relay already tells a streaming viewer's commands from the agent's, so a person clicking a new-tab link or a download from a remote viewer is not treated as the agent. AI-assisted; verified with the tests and tools listed above. --- .../src/preview/DesktopBrowserHost.test.ts | 36 +++++++++---------- .../desktop/src/preview/DesktopBrowserHost.ts | 5 +-- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/apps/desktop/src/preview/DesktopBrowserHost.test.ts b/apps/desktop/src/preview/DesktopBrowserHost.test.ts index 647f659dcb262..0ba1c6495edc7 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.test.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.test.ts @@ -1066,28 +1066,26 @@ describe("DesktopBrowserHost", () => { const host = yield* DesktopBrowserHost.make; const debuggee = makeDebuggee(); host.attach(key, debuggee.tab); + const replies = yield* watchReplies(host); + const raw = (message: Record) => + host.handleCommandLine(encodeJson({ type: "cdp", ...key, message: encodeJson(message) })); - yield* host.handleCommandLine( - encodeJson({ - type: "cdp", - ...key, - message: encodeJson({ id: 1, method: "DOM.getDocument", params: {} }), - }), - ); + yield* raw({ id: 1, method: "DOM.getDocument", sessionId: "t3-preview-page" }); expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(false); - yield* host.handleCommandLine( - encodeJson({ - type: "cdp", - ...key, - message: encodeJson({ - id: 2, - method: "Input.dispatchMouseEvent", - params: { type: "mousePressed", x: 1, y: 1 }, - }), - }), - ); + // A person clicking through a viewer that streams the guest. + yield* raw({ id: 2, method: "Target.attachToTarget", params: { targetId: "GUEST" } }); + expect(yield* Queue.take(replies)).toBe(2); + yield* raw({ id: 3, method: "Page.startScreencast", sessionId: "t3-preview-page-1" }); + yield* raw({ id: 4, method: "Input.dispatchMouseEvent", sessionId: "t3-preview-page-1" }); + expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(false); + yield* raw({ + id: 5, + method: "Input.dispatchMouseEvent", + params: { type: "mousePressed", x: 1, y: 1 }, + sessionId: "t3-preview-page", + }); expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(true); - }), + }).pipe(Effect.scoped), ); it.effect("detaches only the tab whose CDP event is too large to relay", () => diff --git a/apps/desktop/src/preview/DesktopBrowserHost.ts b/apps/desktop/src/preview/DesktopBrowserHost.ts index fe663a5d9f1af..b338e6139e8b8 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.ts @@ -21,9 +21,6 @@ import { sendPageCommand } from "./GuestInput.ts"; const encodeEvent = Schema.encodeSync(Schema.fromJsonString(DesktopBrowserEvent)); const decodeCommand = Schema.decodeUnknownOption(Schema.fromJsonString(DesktopBrowserCommand)); const lineEncoder = new TextEncoder(); -const AGENT_INPUT_COMMAND = - /"method":"(?:Input\.|Page\.navigate"|Runtime\.(?:evaluate|callFunctionOn)")/; -/** The same commands as above, by method, for the relay's sends. */ const AGENT_INPUT_METHOD = /^(?:Input\.|Page\.navigate$|Runtime\.(?:evaluate|callFunctionOn)$)/; const AGENT_INPUT_WINDOW_MS = 5_000; const FOCUS_IDLE = "5 seconds"; @@ -332,6 +329,7 @@ export const make = Effect.gen(function* () { (owner === undefined || !streamingOwners.has(owner)) ) { tab.agentUsed = true; + tab.agentInputAt = performance.now(); } // Waits for focus emulation; insertText and other non-CDP input would not queue behind it. const forward = (current?: () => boolean) => { @@ -543,7 +541,6 @@ export const make = Effect.gen(function* () { dropRelay(tab); return; } - if (AGENT_INPUT_COMMAND.test(command.value.message)) tab.agentInputAt = performance.now(); relayFor(tab).receive(command.value.message); }); From 0e6fea630bd8df862c576f68fa3f6e7d02bee91e Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 10 Nov 2026 23:57:48 +0000 Subject: [PATCH 7/8] fix(desktop): a viewer's click right after the agent's opens new tabs in front A person's input through a streaming viewer now ends the agent's input window, since its press hands focus back just like the agent's. AI-assisted; verified with the tests and tools listed above. --- .../src/preview/DesktopBrowserHost.test.ts | 19 +++++++------------ .../desktop/src/preview/DesktopBrowserHost.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps/desktop/src/preview/DesktopBrowserHost.test.ts b/apps/desktop/src/preview/DesktopBrowserHost.test.ts index 0ba1c6495edc7..5c0633a1dfc85 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.test.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.test.ts @@ -1072,19 +1072,14 @@ describe("DesktopBrowserHost", () => { yield* raw({ id: 1, method: "DOM.getDocument", sessionId: "t3-preview-page" }); expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(false); - // A person clicking through a viewer that streams the guest. - yield* raw({ id: 2, method: "Target.attachToTarget", params: { targetId: "GUEST" } }); - expect(yield* Queue.take(replies)).toBe(2); - yield* raw({ id: 3, method: "Page.startScreencast", sessionId: "t3-preview-page-1" }); - yield* raw({ id: 4, method: "Input.dispatchMouseEvent", sessionId: "t3-preview-page-1" }); - expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(false); - yield* raw({ - id: 5, - method: "Input.dispatchMouseEvent", - params: { type: "mousePressed", x: 1, y: 1 }, - sessionId: "t3-preview-page", - }); + yield* raw({ id: 2, method: "Input.dispatchMouseEvent", sessionId: "t3-preview-page" }); expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(true); + // A person clicking through a viewer that streams the guest, right after the agent. + yield* raw({ id: 3, method: "Target.attachToTarget", params: { targetId: "GUEST" } }); + expect(yield* Queue.take(replies)).toBe(3); + yield* raw({ id: 4, method: "Page.startScreencast", sessionId: "t3-preview-page-1" }); + yield* raw({ id: 5, method: "Input.dispatchMouseEvent", sessionId: "t3-preview-page-1" }); + expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(false); }).pipe(Effect.scoped), ); diff --git a/apps/desktop/src/preview/DesktopBrowserHost.ts b/apps/desktop/src/preview/DesktopBrowserHost.ts index b338e6139e8b8..9547f5f403db9 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.ts @@ -324,12 +324,14 @@ export const make = Effect.gen(function* () { tab.screencasts.delete(owner); } // A person's input through a viewer, or Playwright's own housekeeping, is not the agent's. - if ( - AGENT_INPUT_METHOD.test(method) && - (owner === undefined || !streamingOwners.has(owner)) - ) { - tab.agentUsed = true; - tab.agentInputAt = performance.now(); + if (AGENT_INPUT_METHOD.test(method)) { + if (owner === undefined || !streamingOwners.has(owner)) { + tab.agentUsed = true; + tab.agentInputAt = performance.now(); + } else { + // The viewer's press also hands focus back, so it must end the agent's window. + tab.agentInputAt = Number.NEGATIVE_INFINITY; + } } // Waits for focus emulation; insertText and other non-CDP input would not queue behind it. const forward = (current?: () => boolean) => { From 241bfdb12b463f9643005f96fe3f7375aed6e837 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 00:11:02 +0000 Subject: [PATCH 8/8] fix(desktop): a viewer hovering the stream keeps the agent's input window Only a viewer's press hands guest focus back, so only a press ends the agent's input window; hover moves no longer reclassify an agent's download or new tab as the person's. AI-assisted; verified with the tests and tools listed above. --- .../desktop/src/preview/DesktopBrowserHost.test.ts | 14 +++++++++++++- apps/desktop/src/preview/DesktopBrowserHost.ts | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/preview/DesktopBrowserHost.test.ts b/apps/desktop/src/preview/DesktopBrowserHost.test.ts index 5c0633a1dfc85..de116667ecf9a 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.test.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.test.ts @@ -1078,7 +1078,19 @@ describe("DesktopBrowserHost", () => { yield* raw({ id: 3, method: "Target.attachToTarget", params: { targetId: "GUEST" } }); expect(yield* Queue.take(replies)).toBe(3); yield* raw({ id: 4, method: "Page.startScreencast", sessionId: "t3-preview-page-1" }); - yield* raw({ id: 5, method: "Input.dispatchMouseEvent", sessionId: "t3-preview-page-1" }); + yield* raw({ + id: 5, + method: "Input.dispatchMouseEvent", + params: { type: "mouseMoved", x: 1, y: 1 }, + sessionId: "t3-preview-page-1", + }); + expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(true); + yield* raw({ + id: 6, + method: "Input.dispatchMouseEvent", + params: { type: "mousePressed", x: 1, y: 1 }, + sessionId: "t3-preview-page-1", + }); expect(host.agentActedRecently(debuggee.tab.webContents)).toBe(false); }).pipe(Effect.scoped), ); diff --git a/apps/desktop/src/preview/DesktopBrowserHost.ts b/apps/desktop/src/preview/DesktopBrowserHost.ts index 9547f5f403db9..e9b7ca4df8965 100644 --- a/apps/desktop/src/preview/DesktopBrowserHost.ts +++ b/apps/desktop/src/preview/DesktopBrowserHost.ts @@ -328,7 +328,7 @@ export const make = Effect.gen(function* () { if (owner === undefined || !streamingOwners.has(owner)) { tab.agentUsed = true; tab.agentInputAt = performance.now(); - } else { + } else if (params["type"] === "mousePressed" || params["type"] === "touchStart") { // The viewer's press also hands focus back, so it must end the agent's window. tab.agentInputAt = Number.NEGATIVE_INFINITY; }