@@ -27,6 +27,7 @@ import {
2727} from "@posthog/agent/gateway-models" ;
2828import { getLlmGatewayUrl } from "@posthog/agent/posthog-api" ;
2929import type { OnLogCallback } from "@posthog/agent/types" ;
30+ import { getCurrentBranch } from "@posthog/git/queries" ;
3031import { isAuthError } from "@shared/errors" ;
3132import type { AcpMessage } from "@shared/types/session-events" ;
3233import { app , powerMonitor } from "electron" ;
@@ -1134,8 +1135,8 @@ For git operations while detached:
11341135 } ;
11351136 emitToRenderer ( acpMessage ) ;
11361137
1137- // Detect PR URLs in bash tool results and attach to task
1138- this . detectAndAttachPrUrl ( taskRunId , message as AcpMessage [ "message" ] ) ;
1138+ // Inspect tool call updates for PR URLs and file activity
1139+ this . handleToolCallUpdate ( taskRunId , message as AcpMessage [ "message" ] ) ;
11391140 } ;
11401141
11411142 const tappedReadable = createTappedReadableStream (
@@ -1415,11 +1416,7 @@ For git operations while detached:
14151416 } ;
14161417 }
14171418
1418- /**
1419- * Detect GitHub PR URLs in bash tool results and attach to task.
1420- * This enables webhook tracking by populating the pr_url in TaskRun output.
1421- */
1422- private detectAndAttachPrUrl ( taskRunId : string , message : unknown ) : void {
1419+ private handleToolCallUpdate ( taskRunId : string , message : unknown ) : void {
14231420 try {
14241421 const msg = message as {
14251422 method ?: string ;
@@ -1441,86 +1438,136 @@ For git operations while detached:
14411438 if ( msg . method !== "session/update" ) return ;
14421439 if ( msg . params ?. update ?. sessionUpdate !== "tool_call_update" ) return ;
14431440
1444- const toolMeta = msg . params . update . _meta ?. claudeCode ;
1441+ const update = msg . params . update ;
1442+ const toolMeta = update . _meta ?. claudeCode ;
14451443 const toolName = toolMeta ?. toolName ;
1444+ if ( ! toolName ) return ;
14461445
1447- // Only process Bash tool results
1448- if (
1449- ! toolName ||
1450- ( ! toolName . includes ( "Bash" ) && ! toolName . includes ( "bash" ) )
1451- ) {
1452- return ;
1446+ const session = this . sessions . get ( taskRunId ) ;
1447+
1448+ // PR URLs only appear in Bash tool output
1449+ if ( toolName . includes ( "Bash" ) || toolName . includes ( "bash" ) ) {
1450+ this . detectAndAttachPrUrl ( taskRunId , session , toolMeta , update . content ) ;
14531451 }
14541452
1455- // Extract text content from tool response or update content
1456- let textToSearch = "" ;
1457-
1458- // Check toolResponse (hook response with raw output)
1459- const toolResponse = toolMeta ?. toolResponse ;
1460- if ( toolResponse ) {
1461- if ( typeof toolResponse === "string" ) {
1462- textToSearch = toolResponse ;
1463- } else if ( typeof toolResponse === "object" && toolResponse !== null ) {
1464- // May be { stdout?: string, stderr?: string } or similar
1465- const respObj = toolResponse as Record < string , unknown > ;
1466- textToSearch =
1467- String ( respObj . stdout || "" ) + String ( respObj . stderr || "" ) ;
1468- if ( ! textToSearch && respObj . output ) {
1469- textToSearch = String ( respObj . output ) ;
1470- }
1453+ this . trackAgentFileActivity ( taskRunId , session , toolName ) ;
1454+ } catch ( err ) {
1455+ log . debug ( "Error in tool call update handling" , {
1456+ taskRunId,
1457+ error : err ,
1458+ } ) ;
1459+ }
1460+ }
1461+
1462+ /**
1463+ * Detect GitHub PR URLs in bash tool results and attach to task.
1464+ * This enables webhook tracking by populating the pr_url in TaskRun output.
1465+ */
1466+ private detectAndAttachPrUrl (
1467+ taskRunId : string ,
1468+ session : ManagedSession | undefined ,
1469+ toolMeta : { toolName ?: string ; toolResponse ?: unknown } ,
1470+ content ?: Array < { type ?: string ; text ?: string } > ,
1471+ ) : void {
1472+ let textToSearch = "" ;
1473+
1474+ // Check toolResponse (hook response with raw output)
1475+ const toolResponse = toolMeta ?. toolResponse ;
1476+ if ( toolResponse ) {
1477+ if ( typeof toolResponse === "string" ) {
1478+ textToSearch = toolResponse ;
1479+ } else if ( typeof toolResponse === "object" && toolResponse !== null ) {
1480+ // May be { stdout?: string, stderr?: string } or similar
1481+ const respObj = toolResponse as Record < string , unknown > ;
1482+ textToSearch =
1483+ String ( respObj . stdout || "" ) + String ( respObj . stderr || "" ) ;
1484+ if ( ! textToSearch && respObj . output ) {
1485+ textToSearch = String ( respObj . output ) ;
14711486 }
14721487 }
1488+ }
14731489
1474- // Also check content array
1475- const content = msg . params . update . content ;
1476- if ( Array . isArray ( content ) ) {
1477- for ( const item of content ) {
1478- if ( item . type === "text" && item . text ) {
1479- textToSearch += ` ${ item . text } ` ;
1480- }
1490+ // Also check content array
1491+ if ( Array . isArray ( content ) ) {
1492+ for ( const item of content ) {
1493+ if ( item . type === "text" && item . text ) {
1494+ textToSearch += ` ${ item . text } ` ;
14811495 }
14821496 }
1497+ }
14831498
1484- if ( ! textToSearch ) return ;
1499+ if ( ! textToSearch ) return ;
14851500
1486- // Match GitHub PR URLs
1487- const prUrlMatch = textToSearch . match (
1488- / h t t p s : \/ \/ g i t h u b \. c o m \/ [ ^ / ] + \/ [ ^ / ] + \/ p u l l \/ \d + / ,
1489- ) ;
1490- if ( ! prUrlMatch ) return ;
1501+ // Match GitHub PR URLs
1502+ const prUrlMatch = textToSearch . match (
1503+ / h t t p s : \/ \/ g i t h u b \. c o m \/ [ ^ / ] + \/ [ ^ / ] + \/ p u l l \/ \d + / ,
1504+ ) ;
1505+ if ( ! prUrlMatch ) return ;
14911506
1492- const prUrl = prUrlMatch [ 0 ] ;
1493- log . info ( "Detected PR URL in bash output" , { taskRunId, prUrl } ) ;
1507+ const prUrl = prUrlMatch [ 0 ] ;
1508+ log . info ( "Detected PR URL in bash output" , { taskRunId, prUrl } ) ;
14941509
1495- // Find session and attach PR URL
1496- const session = this . sessions . get ( taskRunId ) ;
1497- if ( ! session ) {
1498- log . warn ( "Session not found for PR attachment" , { taskRunId } ) ;
1499- return ;
1500- }
1510+ // Attach PR URL
1511+ if ( ! session ) {
1512+ log . warn ( "Session not found for PR attachment" , { taskRunId } ) ;
1513+ return ;
1514+ }
15011515
1502- // Attach asynchronously without blocking message flow
1503- session . agent
1504- . attachPullRequestToTask ( session . taskId , prUrl )
1505- . then ( ( ) => {
1506- log . info ( "PR URL attached to task" , {
1507- taskRunId,
1508- taskId : session . taskId ,
1509- prUrl,
1510- } ) ;
1511- } )
1512- . catch ( ( err ) => {
1513- log . error ( "Failed to attach PR URL to task" , {
1514- taskRunId,
1515- taskId : session . taskId ,
1516- prUrl,
1517- error : err ,
1518- } ) ;
1516+ // Attach asynchronously without blocking message flow
1517+ session . agent
1518+ . attachPullRequestToTask ( session . taskId , prUrl )
1519+ . then ( ( ) => {
1520+ log . info ( "PR URL attached to task" , {
1521+ taskRunId,
1522+ taskId : session . taskId ,
1523+ prUrl,
15191524 } ) ;
1520- } catch ( err ) {
1521- // Don't let detection errors break message flow
1522- log . debug ( "Error in PR URL detection" , { taskRunId, error : err } ) ;
1523- }
1525+ } )
1526+ . catch ( ( err ) => {
1527+ log . error ( "Failed to attach PR URL to task" , {
1528+ taskRunId,
1529+ taskId : session . taskId ,
1530+ prUrl,
1531+ error : err ,
1532+ } ) ;
1533+ } ) ;
1534+ }
1535+
1536+ /**
1537+ * Track agent file activity for branch association observability.
1538+ */
1539+ private static readonly FILE_MODIFYING_TOOLS = new Set ( [
1540+ "Edit" ,
1541+ "Write" ,
1542+ "FileEditTool" ,
1543+ "FileWriteTool" ,
1544+ "MultiEdit" ,
1545+ "NotebookEdit" ,
1546+ ] ) ;
1547+
1548+ private trackAgentFileActivity (
1549+ taskRunId : string ,
1550+ session : ManagedSession | undefined ,
1551+ toolName : string ,
1552+ ) : void {
1553+ if ( ! session ) return ;
1554+ if ( ! AgentService . FILE_MODIFYING_TOOLS . has ( toolName ) ) return ;
1555+
1556+ getCurrentBranch ( session . repoPath )
1557+ . then ( ( branchName ) => {
1558+ this . emit ( AgentServiceEvent . AgentFileActivity , {
1559+ taskId : session . taskId ,
1560+ branchName,
1561+ } ) ;
1562+ } )
1563+ . catch ( ( err ) => {
1564+ log . error ( "Failed to emit agent file activity event" , {
1565+ taskRunId,
1566+ taskId : session . taskId ,
1567+ toolName,
1568+ error : err ,
1569+ } ) ;
1570+ } ) ;
15241571 }
15251572
15261573 async getGatewayModels ( apiHost : string ) {
0 commit comments