Skip to content

Commit eb7c282

Browse files
committed
fix(desktop): snapshot running work after the window probes and count queued browser actions
- Read terminal, computer-use, browser and agent activity after the unsaved-edits probes finish, so work started meanwhile still prompts - A browser action waiting in the queue counts as running - A window whose probe errors counts as possibly unsaved, like one that times out
1 parent 8c46343 commit eb7c282

5 files changed

Lines changed: 38 additions & 4 deletions

File tree

‎apps/desktop/src/main/browser-agent/driver.test.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1150,11 +1150,15 @@ describe('executeTool', () => {
11501150
}
11511151
})
11521152

1153-
it('reports a browser action as running from admission until it settles', async () => {
1153+
it('reports a browser action as running from queueing until it settles', async () => {
11541154
vi.useFakeTimers()
11551155
try {
11561156
await driver.executeTool('chat-test', 'browser_open_tab', {})
11571157
expect(driver.isBrowserToolRunning()).toBe(false)
1158+
const queued = driver.captureBrowserToolQueueBoundary('chat-test')
1159+
expect(driver.isBrowserToolRunning()).toBe(true)
1160+
if (queued) driver.releaseBrowserToolQueueBoundary(queued)
1161+
expect(driver.isBrowserToolRunning()).toBe(false)
11581162
const contents = session.requireTab().view.webContents
11591163
vi.mocked(contents.getURL).mockReturnValue('https://example.com/')
11601164
vi.mocked(contents.executeJavaScript).mockImplementation(() => new Promise<never>(() => {}))

‎apps/desktop/src/main/browser-agent/driver.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ function releaseBrowserToolAdmission(state: DriverScopeState, admission: symbol)
459459

460460
/** Whether any chat has a browser action queued or running in this process. */
461461
export function isBrowserToolRunning(): boolean {
462-
return activeBrowserToolAdmissions.size > 0
462+
return activeBrowserToolAdmissions.size > 0 || pendingBrowserToolQueueBoundaries.size > 0
463463
}
464464

465465
function retireDriverScopeState(state: DriverScopeState): void {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { WebContents } from 'electron'
2+
import { describe, expect, it, vi } from 'vitest'
3+
import { countWindowsWithUnsavedChanges } from '@/main/in-flight-work'
4+
5+
vi.mock('electron', () => import('@/test/electron-mock'))
6+
7+
function windowAnswering(answer: () => Promise<unknown>, state = { destroyed: false }) {
8+
return {
9+
isDestroyed: () => state.destroyed,
10+
isCrashed: () => false,
11+
executeJavaScript: vi.fn(answer),
12+
} as unknown as WebContents
13+
}
14+
15+
describe('countWindowsWithUnsavedChanges', () => {
16+
it('never reports a window it could not ask as clean', async () => {
17+
const counts = await countWindowsWithUnsavedChanges([
18+
windowAnswering(() => Promise.resolve(true)),
19+
windowAnswering(() => Promise.resolve(false)),
20+
windowAnswering(() => Promise.reject(new Error('Script failed to execute'))),
21+
windowAnswering(() => Promise.resolve(true), { destroyed: true }),
22+
])
23+
24+
expect(counts).toEqual({ unsavedWindowCount: 1, unresponsiveWindowCount: 1 })
25+
})
26+
})

‎apps/desktop/src/main/in-flight-work.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async function askAboutUnsavedChanges(contents: WebContents): Promise<UnloadAnsw
4141
logger.warn('Could not ask a window about unsaved changes', {
4242
message: getErrorMessage(error, 'unknown'),
4343
})
44-
return 'clean'
44+
return 'unresponsive'
4545
}
4646
}
4747

‎apps/desktop/src/main/index.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1167,13 +1167,17 @@ function main(): void {
11671167
rememberWindowsForRelaunch(pending)
11681168
},
11691169
describeInFlightWork: async () => {
1170+
// Native activity is read after the window probes so work started meanwhile still counts.
1171+
const windows = await countWindowsWithUnsavedChanges(
1172+
getWindows().map((win) => win.webContents)
1173+
)
11701174
const computerUseActivity = computerUse.getActivity()
11711175
return describeInFlightWork({
11721176
terminalCommands: terminal.runningCommands(),
11731177
computerUseApp: computerUseActivity ? (computerUseActivity.appName ?? '') : null,
11741178
browserToolRunning: isBrowserToolRunning(),
11751179
agentWorkRunning,
1176-
...(await countWindowsWithUnsavedChanges(getWindows().map((win) => win.webContents))),
1180+
...windows,
11771181
})
11781182
},
11791183
beforeInstall: async () => {

0 commit comments

Comments
 (0)