@@ -92,7 +92,7 @@ import { initTerminalTransport } from '@/lib/terminal/transport'
9292import { getQueryClient } from '@/app/_shell/providers/get-query-client'
9393import { chatUrl } from '@/app/workspace/[workspaceId]/home/hooks/chat-url'
9494import {
95- desktopToolLifetime ,
95+ leaseDesktopTool ,
9696 stopDesktopTools ,
9797} from '@/app/workspace/[workspaceId]/home/hooks/desktop-tool-lifetimes'
9898import { useFilePreviewController } from '@/app/workspace/[workspaceId]/home/hooks/preview'
@@ -472,10 +472,18 @@ function startClientBrowserTool(
472472 toolArgs : Record < string , unknown > ,
473473 scopeId : string ,
474474 eventTs ?: string ,
475- signal ?: AbortSignal
475+ turnStreamId ?: string
476476) : void {
477477 if ( ! isCurrentBrowserToolName ( toolName ) ) return
478- executeBrowserToolOnClient ( toolCallId , toolName , toolArgs , scopeId , eventTs , signal )
478+ const lease = turnStreamId ? leaseDesktopTool ( turnStreamId ) : undefined
479+ void executeBrowserToolOnClient (
480+ toolCallId ,
481+ toolName ,
482+ toolArgs ,
483+ scopeId ,
484+ eventTs ,
485+ lease ?. signal
486+ ) . finally ( ( ) => lease ?. release ( ) )
479487}
480488
481489/**
@@ -1580,10 +1588,11 @@ export function useChat(
15801588 return
15811589 }
15821590 handledClientLocalFilesystemToolIds . add ( toolCallId )
1591+ const lease = streamIdRef . current ? leaseDesktopTool ( streamIdRef . current ) : undefined
15831592 const options = {
15841593 workspaceId,
15851594 chatId : chatIdRef . current ?? selectedChatIdRef . current ,
1586- signal : streamIdRef . current ? desktopToolLifetime ( streamIdRef . current ) : undefined ,
1595+ signal : lease ?. signal ,
15871596 }
15881597 /**
15891598 * Dynamic on purpose: the local-filesystem executor only runs for desktop-local
@@ -1594,35 +1603,40 @@ export function useChat(
15941603 * report an error completion rather than leaving it hanging with the dedupe ref
15951604 * already marked handled.
15961605 */
1597- import ( '@/lib/mothership/tools/client/local-filesystem' ) . then (
1598- ( m ) => m . executeLocalFilesystemTool ( toolCallId , toolName , toolArgs , options ) ,
1599- async ( error ) => {
1600- logger . error ( 'Failed to load local filesystem tool executor' , { error } )
1601- /**
1602- * The recovery itself can reject (the helper chunks or the completion POST can
1603- * fail for the same reason the executor chunk did). Contain it: an unhandled
1604- * rejection here would settle nothing and surface as a console error, exactly
1605- * like the executor's own report-failure path, which also degrades to a log.
1606- */
1607- try {
1608- const [ { reportClientToolCompletion } , { ASYNC_TOOL_CONFIRMATION_STATUS } ] =
1609- await Promise . all ( [
1610- import ( '@/lib/mothership/tools/client/completion' ) ,
1611- import ( '@/lib/mothership/async-runs/lifecycle' ) ,
1612- ] )
1613- await reportClientToolCompletion (
1614- toolCallId ,
1615- ASYNC_TOOL_CONFIRMATION_STATUS . error ,
1616- 'Local filesystem tool failed to load'
1617- )
1618- } catch ( reportError ) {
1619- logger . error ( 'Failed to report local filesystem tool load failure' , {
1620- toolCallId,
1621- error : reportError ,
1622- } )
1606+ import ( '@/lib/mothership/tools/client/local-filesystem' )
1607+ . then (
1608+ ( m ) => m . executeLocalFilesystemTool ( toolCallId , toolName , toolArgs , options ) ,
1609+ async ( error ) => {
1610+ logger . error ( 'Failed to load local filesystem tool executor' , { error } )
1611+ /**
1612+ * The recovery itself can reject (the helper chunks or the completion POST can
1613+ * fail for the same reason the executor chunk did). Contain it: an unhandled
1614+ * rejection here would settle nothing and surface as a console error, exactly
1615+ * like the executor's own report-failure path, which also degrades to a log.
1616+ */
1617+ try {
1618+ const [ { reportClientToolCompletion } , { ASYNC_TOOL_CONFIRMATION_STATUS } ] =
1619+ await Promise . all ( [
1620+ import ( '@/lib/mothership/tools/client/completion' ) ,
1621+ import ( '@/lib/mothership/async-runs/lifecycle' ) ,
1622+ ] )
1623+ await reportClientToolCompletion (
1624+ toolCallId ,
1625+ ASYNC_TOOL_CONFIRMATION_STATUS . error ,
1626+ 'Local filesystem tool failed to load'
1627+ )
1628+ } catch ( reportError ) {
1629+ logger . error ( 'Failed to report local filesystem tool load failure' , {
1630+ toolCallId,
1631+ error : reportError ,
1632+ } )
1633+ }
16231634 }
1624- }
1625- )
1635+ )
1636+ . catch ( ( error ) => {
1637+ logger . error ( 'Local filesystem tool execution failed unexpectedly' , { toolCallId, error } )
1638+ } )
1639+ . finally ( ( ) => lease ?. release ( ) )
16261640 } ,
16271641 [ workspaceId , organizationId , scopeKey ]
16281642 )
@@ -2159,9 +2173,7 @@ export function useChat(
21592173 shouldContinue ?: ( ) => boolean
21602174 }
21612175 ) => {
2162- const browserToolSignal = streamIdRef . current
2163- ? desktopToolLifetime ( streamIdRef . current )
2164- : undefined
2176+ const turnStreamId = streamIdRef . current
21652177 const activityTracker = getResourceActivityTracker (
21662178 expectedGen ?? streamGenRef . current ,
21672179 options ?. targetChatId
@@ -2182,7 +2194,7 @@ export function useChat(
21822194 eventTs ?: string
21832195 ) => {
21842196 const scopeId = activityScopeId ( )
2185- startClientBrowserTool ( toolCallId , toolName , toolArgs , scopeId , eventTs , browserToolSignal )
2197+ startClientBrowserTool ( toolCallId , toolName , toolArgs , scopeId , eventTs , turnStreamId )
21862198 }
21872199 const startClientTerminalToolForStream = (
21882200 toolCallId : string ,
0 commit comments