source dump of claude code
at main 86 lines 3.3 kB view raw
1import type { ToolUseContext } from '../../Tool.js' 2 3import { logForDebugging } from '../debug.js' 4import { errorMessage } from '../errors.js' 5import { withResolvers } from '../withResolvers.js' 6import { isLockHeldLocally, releaseComputerUseLock } from './computerUseLock.js' 7import { unregisterEscHotkey } from './escHotkey.js' 8 9// cu.apps.unhide is NOT one of the four @MainActor methods wrapped by 10// drainRunLoop's 30s backstop. On abort paths (where the user hit Ctrl+C 11// because something was slow) a hang here would wedge the abort. Generous 12// timeout — unhide should be ~instant; if it takes 5s something is wrong 13// and proceeding is better than waiting. The Swift call continues in the 14// background regardless; we just stop blocking on it. 15const UNHIDE_TIMEOUT_MS = 5000 16 17/** 18 * Turn-end cleanup for the chicago MCP surface: auto-unhide apps that 19 * `prepareForAction` hid, then release the file-based lock. 20 * 21 * Called from three sites: natural turn end (`stopHooks.ts`), abort during 22 * streaming (`query.ts` aborted_streaming), abort during tool execution 23 * (`query.ts` aborted_tools). All three reach this via dynamic import gated 24 * on `feature('CHICAGO_MCP')`. `executor.js` (which pulls both native 25 * modules) is dynamic-imported below so non-CU turns don't load native 26 * modules just to no-op. 27 * 28 * No-ops cheaply on non-CU turns: both gate checks are zero-syscall. 29 */ 30export async function cleanupComputerUseAfterTurn( 31 ctx: Pick< 32 ToolUseContext, 33 'getAppState' | 'setAppState' | 'sendOSNotification' 34 >, 35): Promise<void> { 36 const appState = ctx.getAppState() 37 38 const hidden = appState.computerUseMcpState?.hiddenDuringTurn 39 if (hidden && hidden.size > 0) { 40 const { unhideComputerUseApps } = await import('./executor.js') 41 const unhide = unhideComputerUseApps([...hidden]).catch(err => 42 logForDebugging( 43 `[Computer Use MCP] auto-unhide failed: ${errorMessage(err)}`, 44 ), 45 ) 46 const timeout = withResolvers<void>() 47 const timer = setTimeout(timeout.resolve, UNHIDE_TIMEOUT_MS) 48 await Promise.race([unhide, timeout.promise]).finally(() => 49 clearTimeout(timer), 50 ) 51 ctx.setAppState(prev => 52 prev.computerUseMcpState?.hiddenDuringTurn === undefined 53 ? prev 54 : { 55 ...prev, 56 computerUseMcpState: { 57 ...prev.computerUseMcpState, 58 hiddenDuringTurn: undefined, 59 }, 60 }, 61 ) 62 } 63 64 // Zero-syscall pre-check so non-CU turns don't touch disk. Release is still 65 // idempotent (returns false if already released or owned by another session). 66 if (!isLockHeldLocally()) return 67 68 // Unregister before lock release so the pump-retain drops as soon as the 69 // CU session ends. Idempotent — no-ops if registration failed at acquire. 70 // Swallow throws so a NAPI unregister error never prevents lock release — 71 // a held lock blocks the next CU session with "in use by another session". 72 try { 73 unregisterEscHotkey() 74 } catch (err) { 75 logForDebugging( 76 `[Computer Use MCP] unregisterEscHotkey failed: ${errorMessage(err)}`, 77 ) 78 } 79 80 if (await releaseComputerUseLock()) { 81 ctx.sendOSNotification?.({ 82 message: 'Claude is done using your computer', 83 notificationType: 'computer_use_exit', 84 }) 85 } 86}