| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 1 | package app |
| 2 | |
| 3 | import ( |
| iomodo | 13a10fc | 2025-07-31 17:47:06 +0400 | [diff] [blame] | 4 | "context" |
| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 5 | "fmt" |
| iomodo | 13a10fc | 2025-07-31 17:47:06 +0400 | [diff] [blame] | 6 | "log/slog" |
| 7 | "time" |
| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 8 | |
| 9 | "github.com/iomodo/staff/git" |
| 10 | ) |
| 11 | |
| 12 | func (a *App) ProposalApproval(body []byte, signature string) error { |
| iomodo | 13a10fc | 2025-07-31 17:47:06 +0400 | [diff] [blame] | 13 | // Set reasonable timeout for webhook processing |
| 14 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) |
| 15 | defer cancel() |
| 16 | |
| 17 | // Validate required dependencies |
| 18 | if a.git == nil { |
| 19 | return fmt.Errorf("git interface not initialized") |
| 20 | } |
| 21 | if a.taskManager == nil { |
| 22 | return fmt.Errorf("task manager not initialized") |
| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 23 | } |
| 24 | |
| iomodo | 13a10fc | 2025-07-31 17:47:06 +0400 | [diff] [blame] | 25 | // Process the webhook payload (includes signature validation) |
| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 26 | taskID, err := git.ProcessMergeWebhook(body, signature, a.config.GitHub.WebhookSecret) |
| 27 | if err != nil { |
| iomodo | 8acd08d | 2025-07-31 16:22:08 +0400 | [diff] [blame] | 28 | return fmt.Errorf("failed to process webhook: %w", err) |
| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 29 | } |
| 30 | |
| iomodo | e5970ba | 2025-07-31 17:59:52 +0400 | [diff] [blame^] | 31 | // Complete the task for the agent that was performing it |
| 32 | if err := a.manager.CompleteTaskForAgent(taskID); err != nil { |
| 33 | a.logger.Warn("Failed to complete task for agent", |
| 34 | slog.String("task_id", taskID), |
| 35 | slog.String("error", err.Error())) |
| 36 | // Don't fail the webhook if agent completion fails |
| 37 | } |
| 38 | |
| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 39 | a.logger.Info("Proposal approved via webhook", |
| iomodo | 13a10fc | 2025-07-31 17:47:06 +0400 | [diff] [blame] | 40 | slog.String("task_id", taskID), |
| 41 | slog.String("repository", fmt.Sprintf("%s/%s", a.config.GitHub.Owner, a.config.GitHub.Repo)), |
| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 42 | ) |
| iomodo | 13a10fc | 2025-07-31 17:47:06 +0400 | [diff] [blame] | 43 | |
| 44 | // Synchronize repository to get any new tasks that were added |
| 45 | a.syncRepository(ctx) |
| 46 | a.logger.Info("Webhook processing completed successfully", |
| 47 | slog.String("task_id", taskID), |
| 48 | ) |
| 49 | |
| iomodo | b23799d | 2025-07-31 14:08:22 +0400 | [diff] [blame] | 50 | return nil |
| 51 | } |
| iomodo | 13a10fc | 2025-07-31 17:47:06 +0400 | [diff] [blame] | 52 | |
| 53 | // syncRepository pulls the remote repository to fetch new tasks |
| 54 | // This is non-critical for webhook success, so failures are logged but don't fail the webhook |
| 55 | func (a *App) syncRepository(ctx context.Context) { |
| 56 | a.logger.Info("Synchronizing repository to fetch new tasks") |
| 57 | |
| 58 | // Get current branch to determine the upstream branch to pull from |
| 59 | currentBranch, err := a.git.GetCurrentBranch(ctx) |
| 60 | if err != nil { |
| 61 | a.logger.Warn("Failed to get current branch, using default pull", |
| 62 | slog.String("error", err.Error()), |
| 63 | ) |
| 64 | // Fallback to simple pull without specifying branch |
| 65 | if err := a.git.Pull(ctx, "origin", ""); err != nil { |
| 66 | a.logger.Warn("Failed to synchronize repository", |
| 67 | slog.String("remote", "origin"), |
| 68 | slog.String("error", err.Error()), |
| 69 | ) |
| 70 | } else { |
| 71 | a.logger.Info("Successfully synchronized repository", |
| 72 | slog.String("remote", "origin"), |
| 73 | ) |
| 74 | } |
| 75 | return |
| 76 | } |
| 77 | |
| 78 | // Get the upstream branch for the current branch |
| 79 | upstreamBranch, err := a.git.GetUpstreamBranch(ctx, currentBranch) |
| 80 | if err != nil { |
| 81 | a.logger.Warn("Failed to get upstream branch, trying simple pull", |
| 82 | slog.String("current_branch", currentBranch), |
| 83 | slog.String("error", err.Error()), |
| 84 | ) |
| 85 | // Fallback to simple pull without specifying branch |
| 86 | if err := a.git.Pull(ctx, "origin", ""); err != nil { |
| 87 | a.logger.Warn("Failed to synchronize repository", |
| 88 | slog.String("remote", "origin"), |
| 89 | slog.String("error", err.Error()), |
| 90 | ) |
| 91 | } else { |
| 92 | a.logger.Info("Successfully synchronized repository", |
| 93 | slog.String("remote", "origin"), |
| 94 | ) |
| 95 | } |
| 96 | return |
| 97 | } |
| 98 | |
| 99 | // Use the upstream branch for pulling |
| 100 | if err := a.git.Pull(ctx, "origin", upstreamBranch); err != nil { |
| 101 | // Don't fail the entire webhook if git pull fails - agents can still work with existing tasks |
| 102 | a.logger.Warn("Failed to synchronize repository", |
| 103 | slog.String("remote", "origin"), |
| 104 | slog.String("branch", upstreamBranch), |
| 105 | slog.String("error", err.Error()), |
| 106 | ) |
| 107 | } else { |
| 108 | a.logger.Info("Successfully synchronized repository", |
| 109 | slog.String("remote", "origin"), |
| 110 | slog.String("branch", upstreamBranch), |
| 111 | ) |
| 112 | } |
| 113 | } |