@@ -22,6 +22,7 @@ import {
2222} from "@posthog/agent/gateway-models" ;
2323import { getLlmGatewayUrl } from "@posthog/agent/posthog-api" ;
2424import type { OnLogCallback } from "@posthog/agent/types" ;
25+ import { getCurrentBranch } from "@posthog/git/queries" ;
2526import { isAuthError } from "@shared/errors" ;
2627import type { AcpMessage } from "@shared/types/session-events" ;
2728import { app , powerMonitor } from "electron" ;
@@ -1124,8 +1125,8 @@ For git operations while detached:
11241125 } ;
11251126 emitToRenderer ( acpMessage ) ;
11261127
1127- // Detect PR URLs in bash tool results and attach to task
1128- this . detectAndAttachPrUrl ( taskRunId , message as AcpMessage [ "message" ] ) ;
1128+ // Inspect tool call updates for PR URLs and file activity
1129+ this . handleToolCallUpdate ( taskRunId , message as AcpMessage [ "message" ] ) ;
11291130 } ;
11301131
11311132 const tappedReadable = createTappedReadableStream (
@@ -1404,11 +1405,7 @@ For git operations while detached:
14041405 } ;
14051406 }
14061407
1407- /**
1408- * Detect GitHub PR URLs in bash tool results and attach to task.
1409- * This enables webhook tracking by populating the pr_url in TaskRun output.
1410- */
1411- private detectAndAttachPrUrl ( taskRunId : string , message : unknown ) : void {
1408+ private handleToolCallUpdate ( taskRunId : string , message : unknown ) : void {
14121409 try {
14131410 const msg = message as {
14141411 method ?: string ;
@@ -1430,86 +1427,136 @@ For git operations while detached:
14301427 if ( msg . method !== "session/update" ) return ;
14311428 if ( msg . params ?. update ?. sessionUpdate !== "tool_call_update" ) return ;
14321429
1433- const toolMeta = msg . params . update . _meta ?. claudeCode ;
1430+ const update = msg . params . update ;
1431+ const toolMeta = update . _meta ?. claudeCode ;
14341432 const toolName = toolMeta ?. toolName ;
1433+ if ( ! toolName ) return ;
14351434
1436- // Only process Bash tool results
1437- if (
1438- ! toolName ||
1439- ( ! toolName . includes ( "Bash" ) && ! toolName . includes ( "bash" ) )
1440- ) {
1441- return ;
1435+ const session = this . sessions . get ( taskRunId ) ;
1436+
1437+ // PR URLs only appear in Bash tool output
1438+ if ( toolName . includes ( "Bash" ) || toolName . includes ( "bash" ) ) {
1439+ this . detectAndAttachPrUrl ( taskRunId , session , toolMeta , update . content ) ;
14421440 }
14431441
1444- // Extract text content from tool response or update content
1445- let textToSearch = "" ;
1446-
1447- // Check toolResponse (hook response with raw output)
1448- const toolResponse = toolMeta ?. toolResponse ;
1449- if ( toolResponse ) {
1450- if ( typeof toolResponse === "string" ) {
1451- textToSearch = toolResponse ;
1452- } else if ( typeof toolResponse === "object" && toolResponse !== null ) {
1453- // May be { stdout?: string, stderr?: string } or similar
1454- const respObj = toolResponse as Record < string , unknown > ;
1455- textToSearch =
1456- String ( respObj . stdout || "" ) + String ( respObj . stderr || "" ) ;
1457- if ( ! textToSearch && respObj . output ) {
1458- textToSearch = String ( respObj . output ) ;
1459- }
1442+ this . trackAgentFileActivity ( taskRunId , session , toolName ) ;
1443+ } catch ( err ) {
1444+ log . debug ( "Error in tool call update handling" , {
1445+ taskRunId,
1446+ error : err ,
1447+ } ) ;
1448+ }
1449+ }
1450+
1451+ /**
1452+ * Detect GitHub PR URLs in bash tool results and attach to task.
1453+ * This enables webhook tracking by populating the pr_url in TaskRun output.
1454+ */
1455+ private detectAndAttachPrUrl (
1456+ taskRunId : string ,
1457+ session : ManagedSession | undefined ,
1458+ toolMeta : { toolName ?: string ; toolResponse ?: unknown } ,
1459+ content ?: Array < { type ?: string ; text ?: string } > ,
1460+ ) : void {
1461+ let textToSearch = "" ;
1462+
1463+ // Check toolResponse (hook response with raw output)
1464+ const toolResponse = toolMeta ?. toolResponse ;
1465+ if ( toolResponse ) {
1466+ if ( typeof toolResponse === "string" ) {
1467+ textToSearch = toolResponse ;
1468+ } else if ( typeof toolResponse === "object" && toolResponse !== null ) {
1469+ // May be { stdout?: string, stderr?: string } or similar
1470+ const respObj = toolResponse as Record < string , unknown > ;
1471+ textToSearch =
1472+ String ( respObj . stdout || "" ) + String ( respObj . stderr || "" ) ;
1473+ if ( ! textToSearch && respObj . output ) {
1474+ textToSearch = String ( respObj . output ) ;
14601475 }
14611476 }
1477+ }
14621478
1463- // Also check content array
1464- const content = msg . params . update . content ;
1465- if ( Array . isArray ( content ) ) {
1466- for ( const item of content ) {
1467- if ( item . type === "text" && item . text ) {
1468- textToSearch += ` ${ item . text } ` ;
1469- }
1479+ // Also check content array
1480+ if ( Array . isArray ( content ) ) {
1481+ for ( const item of content ) {
1482+ if ( item . type === "text" && item . text ) {
1483+ textToSearch += ` ${ item . text } ` ;
14701484 }
14711485 }
1486+ }
14721487
1473- if ( ! textToSearch ) return ;
1488+ if ( ! textToSearch ) return ;
14741489
1475- // Match GitHub PR URLs
1476- const prUrlMatch = textToSearch . match (
1477- / h t t p s : \/ \/ g i t h u b \. c o m \/ [ ^ / ] + \/ [ ^ / ] + \/ p u l l \/ \d + / ,
1478- ) ;
1479- if ( ! prUrlMatch ) return ;
1490+ // Match GitHub PR URLs
1491+ const prUrlMatch = textToSearch . match (
1492+ / h t t p s : \/ \/ g i t h u b \. c o m \/ [ ^ / ] + \/ [ ^ / ] + \/ p u l l \/ \d + / ,
1493+ ) ;
1494+ if ( ! prUrlMatch ) return ;
14801495
1481- const prUrl = prUrlMatch [ 0 ] ;
1482- log . info ( "Detected PR URL in bash output" , { taskRunId, prUrl } ) ;
1496+ const prUrl = prUrlMatch [ 0 ] ;
1497+ log . info ( "Detected PR URL in bash output" , { taskRunId, prUrl } ) ;
14831498
1484- // Find session and attach PR URL
1485- const session = this . sessions . get ( taskRunId ) ;
1486- if ( ! session ) {
1487- log . warn ( "Session not found for PR attachment" , { taskRunId } ) ;
1488- return ;
1489- }
1499+ // Attach PR URL
1500+ if ( ! session ) {
1501+ log . warn ( "Session not found for PR attachment" , { taskRunId } ) ;
1502+ return ;
1503+ }
14901504
1491- // Attach asynchronously without blocking message flow
1492- session . agent
1493- . attachPullRequestToTask ( session . taskId , prUrl )
1494- . then ( ( ) => {
1495- log . info ( "PR URL attached to task" , {
1496- taskRunId,
1497- taskId : session . taskId ,
1498- prUrl,
1499- } ) ;
1500- } )
1501- . catch ( ( err ) => {
1502- log . error ( "Failed to attach PR URL to task" , {
1503- taskRunId,
1504- taskId : session . taskId ,
1505- prUrl,
1506- error : err ,
1507- } ) ;
1505+ // Attach asynchronously without blocking message flow
1506+ session . agent
1507+ . attachPullRequestToTask ( session . taskId , prUrl )
1508+ . then ( ( ) => {
1509+ log . info ( "PR URL attached to task" , {
1510+ taskRunId,
1511+ taskId : session . taskId ,
1512+ prUrl,
15081513 } ) ;
1509- } catch ( err ) {
1510- // Don't let detection errors break message flow
1511- log . debug ( "Error in PR URL detection" , { taskRunId, error : err } ) ;
1512- }
1514+ } )
1515+ . catch ( ( err ) => {
1516+ log . error ( "Failed to attach PR URL to task" , {
1517+ taskRunId,
1518+ taskId : session . taskId ,
1519+ prUrl,
1520+ error : err ,
1521+ } ) ;
1522+ } ) ;
1523+ }
1524+
1525+ /**
1526+ * Track agent file activity for branch association observability.
1527+ */
1528+ private static readonly FILE_MODIFYING_TOOLS = new Set ( [
1529+ "Edit" ,
1530+ "Write" ,
1531+ "FileEditTool" ,
1532+ "FileWriteTool" ,
1533+ "MultiEdit" ,
1534+ "NotebookEdit" ,
1535+ ] ) ;
1536+
1537+ private trackAgentFileActivity (
1538+ taskRunId : string ,
1539+ session : ManagedSession | undefined ,
1540+ toolName : string ,
1541+ ) : void {
1542+ if ( ! session ) return ;
1543+ if ( ! AgentService . FILE_MODIFYING_TOOLS . has ( toolName ) ) return ;
1544+
1545+ getCurrentBranch ( session . repoPath )
1546+ . then ( ( branchName ) => {
1547+ this . emit ( AgentServiceEvent . AgentFileActivity , {
1548+ taskId : session . taskId ,
1549+ branchName,
1550+ } ) ;
1551+ } )
1552+ . catch ( ( err ) => {
1553+ log . error ( "Failed to emit agent file activity event" , {
1554+ taskRunId,
1555+ taskId : session . taskId ,
1556+ toolName,
1557+ error : err ,
1558+ } ) ;
1559+ } ) ;
15131560 }
15141561
15151562 async getGatewayModels ( apiHost : string ) {
0 commit comments