@@ -3,6 +3,7 @@ import * as fs from "node:fs";
33import * as fsPromises from "node:fs/promises" ;
44import path from "node:path" ;
55import { promisify } from "node:util" ;
6+ import { trackAppEvent } from "@main/services/posthog-analytics" ;
67import { createGitClient } from "@posthog/git/client" ;
78import {
89 getCurrentBranch ,
@@ -22,6 +23,7 @@ import { MAIN_TOKENS } from "../../di/tokens";
2223import { logger } from "../../utils/logger" ;
2324import { TypedEventEmitter } from "../../utils/typed-event-emitter" ;
2425import { deriveWorktreePath } from "../../utils/worktree-helpers" ;
26+ import { AgentServiceEvent } from "../agent/schemas" ;
2527import type { AgentService } from "../agent/service" ;
2628import { FileWatcherEvent } from "../file-watcher/schemas" ;
2729import type { FileWatcherService } from "../file-watcher/service" ;
@@ -237,6 +239,11 @@ export class WorkspaceService extends TypedEventEmitter<WorkspaceServiceEvents>
237239 this . handleFocusBranchRenamed . bind ( this ) ,
238240 ) ;
239241
242+ this . agentService . on (
243+ AgentServiceEvent . AgentFileActivity ,
244+ this . handleAgentFileActivity . bind ( this ) ,
245+ ) ;
246+
240247 log . info ( "Branch watcher initialized" ) ;
241248 }
242249
@@ -310,27 +317,75 @@ export class WorkspaceService extends TypedEventEmitter<WorkspaceServiceEvents>
310317 }
311318 }
312319
320+ private async handleAgentFileActivity ( {
321+ taskId,
322+ branchName,
323+ } : {
324+ taskId : string ;
325+ branchName : string | null ;
326+ } ) : Promise < void > {
327+ if ( ! branchName ) return ;
328+
329+ const dbRow = this . workspaceRepo . findByTaskId ( taskId ) ;
330+ if ( ! dbRow || dbRow . mode !== "local" ) return ;
331+ if ( ! dbRow . repositoryId ) return ;
332+
333+ const folderPath = this . getFolderPath ( dbRow . repositoryId ) ;
334+ if ( ! folderPath ) return ;
335+
336+ try {
337+ const defaultBranch = await getDefaultBranch ( folderPath ) ;
338+ if ( branchName === defaultBranch ) return ;
339+ } catch ( error ) {
340+ log . warn ( "Failed to determine default branch, skipping branch link" , {
341+ taskId,
342+ branchName,
343+ error,
344+ } ) ;
345+ trackAppEvent ( "branch_link_default_branch_unknown" , {
346+ taskId,
347+ branchName,
348+ } ) ;
349+ return ;
350+ }
351+
352+ const currentLinked = dbRow . linkedBranch ?? null ;
353+ if ( currentLinked === branchName ) return ;
354+
355+ this . linkBranch ( taskId , branchName , "agent" ) ;
356+ }
357+
313358 private updateAssociationBranchName (
314359 _taskId : string ,
315360 _branchName : string ,
316361 ) : void { }
317362
318- public linkBranch ( taskId : string , branchName : string ) : void {
363+ public linkBranch (
364+ taskId : string ,
365+ branchName : string ,
366+ source ?: "agent" | "user" ,
367+ ) : void {
319368 this . workspaceRepo . updateLinkedBranch ( taskId , branchName ) ;
320369 this . emit ( WorkspaceServiceEvent . LinkedBranchChanged , {
321370 taskId,
322371 branchName,
323372 } ) ;
324- log . info ( "Linked branch to task" , { taskId, branchName } ) ;
373+ trackAppEvent ( "branch_linked" , {
374+ source : source ?? "unknown" ,
375+ } ) ;
376+ log . info ( "Linked branch to task" , { taskId, branchName, source } ) ;
325377 }
326378
327- public unlinkBranch ( taskId : string ) : void {
379+ public unlinkBranch ( taskId : string , source ?: "agent" | "user" ) : void {
328380 this . workspaceRepo . updateLinkedBranch ( taskId , null ) ;
329381 this . emit ( WorkspaceServiceEvent . LinkedBranchChanged , {
330382 taskId,
331383 branchName : null ,
332384 } ) ;
333- log . info ( "Unlinked branch from task" , { taskId } ) ;
385+ trackAppEvent ( "branch_unlinked" , {
386+ source : source ?? "unknown" ,
387+ } ) ;
388+ log . info ( "Unlinked branch from task" , { taskId, source } ) ;
334389 }
335390
336391 private async getLocalWorktreePathIfExists (
0 commit comments