diff --git a/packages/core/src/__tests__/utils.test.ts b/packages/core/src/__tests__/utils.test.ts index 10dee00e7..5cf410aaf 100644 --- a/packages/core/src/__tests__/utils.test.ts +++ b/packages/core/src/__tests__/utils.test.ts @@ -203,6 +203,15 @@ describe("parsePrFromUrl", () => { }); }); + it("parses GitHub pull request URLs with trailing path segments", () => { + expect(parsePrFromUrl("https://github.com/foo/bar/pull/123/files")).toEqual({ + owner: "foo", + repo: "bar", + number: 123, + url: "https://github.com/foo/bar/pull/123/files", + }); + }); + it("returns null when the URL has no PR number", () => { expect(parsePrFromUrl("https://example.com/foo/bar/pull/not-a-number")).toBeNull(); }); diff --git a/packages/core/src/utils/pr.ts b/packages/core/src/utils/pr.ts index 71ce6f2e1..1e6d590b8 100644 --- a/packages/core/src/utils/pr.ts +++ b/packages/core/src/utils/pr.ts @@ -9,7 +9,7 @@ export function parsePrFromUrl(prUrl: string): ParsedPrUrl | null { const pathSegments = parsedUrl?.pathname.split("/").filter(Boolean) ?? []; const githubStylePullIndex = pathSegments.findIndex((segment) => segment === "pull"); - if (githubStylePullIndex >= 2 && githubStylePullIndex === pathSegments.length - 2) { + if (githubStylePullIndex >= 2 && githubStylePullIndex + 1 < pathSegments.length) { const owner = pathSegments[githubStylePullIndex - 2]; const repo = pathSegments[githubStylePullIndex - 1]; const prNumber = pathSegments[githubStylePullIndex + 1]; diff --git a/packages/web/src/components/SessionDetailPRCard.tsx b/packages/web/src/components/SessionDetailPRCard.tsx index 15b0f0343..49046d49d 100644 --- a/packages/web/src/components/SessionDetailPRCard.tsx +++ b/packages/web/src/components/SessionDetailPRCard.tsx @@ -226,7 +226,8 @@ export function SessionDetailPRCard({ {pr.ciChecks.length > 0 ? ( <>
- {pr.ciChecks.map((check) => { + {pr.ciChecks.map((check, index) => { + const key = check.url ?? `${check.name}-${index}`; const chip = ( ) : ( - {chip} + {chip} ); })} diff --git a/packages/web/src/components/__tests__/SessionDetail.desktop.test.tsx b/packages/web/src/components/__tests__/SessionDetail.desktop.test.tsx index 911ced5c6..f5a0209cc 100644 --- a/packages/web/src/components/__tests__/SessionDetail.desktop.test.tsx +++ b/packages/web/src/components/__tests__/SessionDetail.desktop.test.tsx @@ -1,6 +1,7 @@ import { act, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { SessionDetail } from "../SessionDetail"; +import { buildAgentFixMessage } from "../session-detail-agent-actions"; import { makePR, makeSession } from "../../__tests__/helpers"; vi.mock("next/navigation", () => ({ @@ -243,6 +244,41 @@ describe("SessionDetail desktop layout", () => { expect(screen.getByRole("button", { name: "Ask Agent to Fix" })).toBeInTheDocument(); }); + it("builds branch links from the PR host for GitHub Enterprise repos", () => { + render( + , + ); + + expect(screen.getByRole("link", { name: "feat/ghe-detail" })).toHaveAttribute( + "href", + "https://github.enterprise.local/acme/app/tree/feat/ghe-detail", + ); + }); + + it("truncates review-comment messages below the API payload cap", () => { + const message = buildAgentFixMessage({ + url: "https://github.com/acme/app/pull/311#discussion_r2", + path: `packages/web/${"deep/".repeat(200)}component.tsx`, + body: `### ${"T".repeat(500)}\n${"D".repeat(15_000)}`, + }); + + expect(message.length).toBeLessThanOrEqual(9_500); + expect(message).toContain("Resolve the comment at https://github.com/acme/app/pull/311#discussion_r2"); + }); + it("shows terminal-ended placeholder for exited desktop sessions", () => { render( void, ) { try { - const { title, description } = cleanBugbotComment(comment.body); - const message = `Please address this review comment:\n\nFile: ${comment.path}\nComment: ${title}\nDescription: ${description}\n\nComment URL: ${comment.url}\n\nAfter fixing, mark the comment as resolved at ${comment.url}`; + const message = buildAgentFixMessage(comment); const response = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/message`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -21,3 +45,5 @@ export async function askAgentToFix( onError(); } } + +export { buildAgentFixMessage }; diff --git a/packages/web/src/components/session-detail-utils.ts b/packages/web/src/components/session-detail-utils.ts index 125b50abe..d3e8c3f5d 100644 --- a/packages/web/src/components/session-detail-utils.ts +++ b/packages/web/src/components/session-detail-utils.ts @@ -52,7 +52,15 @@ export function cleanBugbotComment(body: string): { title: string; description: } export function buildGitHubBranchUrl(pr: DashboardPR): string { - return `https://github.com/${pr.owner}/${pr.repo}/tree/${pr.branch}`; + let origin = "https://github.com"; + + try { + origin = new URL(pr.url).origin; + } catch { + // Fall back to the public GitHub host if the PR URL is missing or invalid. + } + + return `${origin}/${pr.owner}/${pr.repo}/tree/${pr.branch}`; } export function activityStateClass(activityLabel: string): string {