diff --git a/apps/backend/cmd/kandev/main.go b/apps/backend/cmd/kandev/main.go index 561f73ddef..5aa5c06b7b 100644 --- a/apps/backend/cmd/kandev/main.go +++ b/apps/backend/cmd/kandev/main.go @@ -599,10 +599,23 @@ func (a *lifecycleAdapter) LaunchAgent(ctx context.Context, req *executor.Launch // Streaming is now handled by the lifecycle manager + // Extract worktree info from metadata if available + var worktreePath, worktreeBranch string + if instance.Metadata != nil { + if path, ok := instance.Metadata["worktree_path"].(string); ok { + worktreePath = path + } + if branch, ok := instance.Metadata["worktree_branch"].(string); ok { + worktreeBranch = branch + } + } + return &executor.LaunchAgentResponse{ AgentInstanceID: instance.ID, ContainerID: instance.ContainerID, Status: instance.Status, + WorktreePath: worktreePath, + WorktreeBranch: worktreeBranch, }, nil } diff --git a/apps/backend/internal/agent/lifecycle/manager.go b/apps/backend/internal/agent/lifecycle/manager.go index 90221694d1..9eaf13ade0 100644 --- a/apps/backend/internal/agent/lifecycle/manager.go +++ b/apps/backend/internal/agent/lifecycle/manager.go @@ -304,6 +304,7 @@ func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstanc workspacePath := req.WorkspacePath var mainRepoGitDir string // Path to main repo's .git directory for container mount var worktreeID string // Store worktree ID for session resumption + var worktreeBranch string // Store worktree branch for API responses if req.UseWorktree && m.worktreeMgr != nil && req.RepositoryPath != "" { wt, err := m.getOrCreateWorktree(ctx, req) if err != nil { @@ -314,6 +315,7 @@ func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstanc } else { workspacePath = wt.Path worktreeID = wt.ID + worktreeBranch = wt.Branch // Git worktrees reference the main repo's .git directory via a .git file // The worktree metadata in .git/worktrees/{name} contains a 'commondir' file // that points back to the main .git directory (usually '../..') @@ -365,9 +367,11 @@ func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstanc if instanceMetadata == nil { instanceMetadata = make(map[string]interface{}) } - // Store worktree_id in metadata for session resumption + // Store worktree info in metadata for session resumption and API responses if worktreeID != "" { instanceMetadata["worktree_id"] = worktreeID + instanceMetadata["worktree_path"] = workspacePath + instanceMetadata["worktree_branch"] = worktreeBranch } instance := &AgentInstance{ ID: instanceID, diff --git a/apps/backend/internal/orchestrator/controller/controller.go b/apps/backend/internal/orchestrator/controller/controller.go index 96e7522887..2b684c4e21 100644 --- a/apps/backend/internal/orchestrator/controller/controller.go +++ b/apps/backend/internal/orchestrator/controller/controller.go @@ -67,12 +67,22 @@ func (c *Controller) StartTask(ctx context.Context, req dto.StartTaskRequest) (d return dto.StartTaskResponse{}, err } - return dto.StartTaskResponse{ + resp := dto.StartTaskResponse{ Success: true, TaskID: execution.TaskID, AgentInstanceID: execution.AgentInstanceID, Status: string(execution.Status), - }, nil + } + + // Include worktree info if available + if execution.WorktreePath != "" { + resp.WorktreePath = &execution.WorktreePath + } + if execution.WorktreeBranch != "" { + resp.WorktreeBranch = &execution.WorktreeBranch + } + + return resp, nil } // StopTask stops task execution diff --git a/apps/backend/internal/orchestrator/dto/dto.go b/apps/backend/internal/orchestrator/dto/dto.go index c2ae4eb53c..f1f717632e 100644 --- a/apps/backend/internal/orchestrator/dto/dto.go +++ b/apps/backend/internal/orchestrator/dto/dto.go @@ -51,10 +51,12 @@ type StartTaskRequest struct { // StartTaskResponse is the response for orchestrator.start type StartTaskResponse struct { - Success bool `json:"success"` - TaskID string `json:"task_id"` - AgentInstanceID string `json:"agent_instance_id"` - Status string `json:"status"` + Success bool `json:"success"` + TaskID string `json:"task_id"` + AgentInstanceID string `json:"agent_instance_id"` + Status string `json:"status"` + WorktreePath *string `json:"worktree_path,omitempty"` + WorktreeBranch *string `json:"worktree_branch,omitempty"` } // StopTaskRequest is the payload for orchestrator.stop diff --git a/apps/backend/internal/orchestrator/executor/executor.go b/apps/backend/internal/orchestrator/executor/executor.go index 7d621d8df3..9431654ddb 100644 --- a/apps/backend/internal/orchestrator/executor/executor.go +++ b/apps/backend/internal/orchestrator/executor/executor.go @@ -77,6 +77,8 @@ type LaunchAgentResponse struct { AgentInstanceID string ContainerID string Status v1.AgentStatus + WorktreePath string + WorktreeBranch string } // TaskExecution tracks an active task execution (kept for API compatibility) @@ -90,6 +92,9 @@ type TaskExecution struct { LastUpdate time.Time // SessionID is the database ID of the agent session SessionID string + // Worktree info for the agent + WorktreePath string + WorktreeBranch string } // FromAgentSession converts a models.AgentSession to TaskExecution @@ -341,6 +346,8 @@ func (e *Executor) Execute(ctx context.Context, task *v1.Task) (*TaskExecution, Progress: 0, LastUpdate: now, SessionID: sessionID, + WorktreePath: resp.WorktreePath, + WorktreeBranch: resp.WorktreeBranch, } e.mu.Lock() @@ -351,7 +358,9 @@ func (e *Executor) Execute(ctx context.Context, task *v1.Task) (*TaskExecution, zap.String("task_id", task.ID), zap.String("session_id", sessionID), zap.String("agent_instance_id", resp.AgentInstanceID), - zap.String("container_id", resp.ContainerID)) + zap.String("container_id", resp.ContainerID), + zap.String("worktree_path", resp.WorktreePath), + zap.String("worktree_branch", resp.WorktreeBranch)) return execution, nil } diff --git a/apps/web/app/task/[id]/page-client.tsx b/apps/web/app/task/[id]/page-client.tsx index 98480b3b50..bbc12470a1 100644 --- a/apps/web/app/task/[id]/page-client.tsx +++ b/apps/web/app/task/[id]/page-client.tsx @@ -162,25 +162,25 @@ export default function TaskPage({ task: initialTask }: TaskPageClientProps) { try { // Use task's agent_type if set, otherwise default to 'augment-agent' const agentType = task.agent_type ?? 'augment-agent'; - await client.request('orchestrator.start', { + interface StartResponse { + success: boolean; + task_id: string; + agent_instance_id: string; + status: string; + worktree_path?: string; + worktree_branch?: string; + } + const response = await client.request('orchestrator.start', { task_id: task.id, agent_type: agentType, }, 15000); setIsAgentRunning(true); - // Fetch updated task info after agent starts (to get worktree path/branch) - // Wait a bit for the worktree to be created - setTimeout(async () => { - try { - const updatedTask = await client.request('task.get', { id: task.id }, 5000); - if (updatedTask?.worktree_path) { - setWorktreePath(updatedTask.worktree_path); - setWorktreeBranch(updatedTask.worktree_branch ?? null); - } - } catch (err) { - console.error('Failed to fetch updated task info:', err); - } - }, 1000); + // Update worktree info from response + if (response?.worktree_path) { + setWorktreePath(response.worktree_path); + setWorktreeBranch(response.worktree_branch ?? null); + } } catch (error) { console.error('Failed to start agent:', error); } finally {