{!isEmbedded && (
diff --git a/site/src/pages/AgentsPage/AgentDetailView.tsx b/site/src/pages/AgentsPage/AgentDetailView.tsx
index a2bb9c365b..698f9cdcc0 100644
--- a/site/src/pages/AgentsPage/AgentDetailView.tsx
+++ b/site/src/pages/AgentsPage/AgentDetailView.tsx
@@ -238,6 +238,7 @@ export const AgentDetailView: FC
= ({
onArchiveAndDeleteWorkspace={handleArchiveAndDeleteWorkspaceAction}
hasWorkspace={hasWorkspace}
isArchived={isArchived}
+ diffStatusData={diffStatusData}
isSidebarCollapsed={isSidebarCollapsed}
onToggleSidebarCollapsed={onToggleSidebarCollapsed}
/>
diff --git a/site/src/pages/AgentsPage/RemoteDiffPanel.tsx b/site/src/pages/AgentsPage/RemoteDiffPanel.tsx
index 029b4cd9dd..d882bed02c 100644
--- a/site/src/pages/AgentsPage/RemoteDiffPanel.tsx
+++ b/site/src/pages/AgentsPage/RemoteDiffPanel.tsx
@@ -28,6 +28,7 @@ import type { ChatMessageInputRef } from "./AgentChatInput";
import { DiffStatBadge } from "./DiffStats";
import type { DiffStyle } from "./DiffViewer";
import { DiffViewer } from "./DiffViewer";
+import { parsePullRequestUrl } from "./pullRequest";
// -------------------------------------------------------------------
// Diff content extraction
@@ -104,10 +105,6 @@ function extractDiffContent(
return collected.join("\n");
}
-/**
- * Parses a GitHub PR URL into its components.
- * Returns null if parsing fails.
- */
// -------------------------------------------------------------------
// PR state badge
// -------------------------------------------------------------------
@@ -147,22 +144,6 @@ const PullRequestStateBadge: FC<{
);
};
-function parsePullRequestUrl(url: string): {
- owner: string;
- repo: string;
- number: string;
-} | null {
- try {
- const match = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
- if (match) {
- return { owner: match[1], repo: match[2], number: match[3] };
- }
- } catch {
- // Fall through.
- }
- return null;
-}
-
// -------------------------------------------------------------------
// Inline prompt input
// -------------------------------------------------------------------
diff --git a/site/src/pages/AgentsPage/pullRequest.test.ts b/site/src/pages/AgentsPage/pullRequest.test.ts
new file mode 100644
index 0000000000..b7e5810e32
--- /dev/null
+++ b/site/src/pages/AgentsPage/pullRequest.test.ts
@@ -0,0 +1,60 @@
+import { describe, expect, it } from "vitest";
+import { parsePullRequestUrl } from "./pullRequest";
+
+describe("parsePullRequestUrl", () => {
+ it("parses canonical GitHub pull request URLs", () => {
+ expect(
+ parsePullRequestUrl("https://github.com/coder/coder/pull/42"),
+ ).toEqual({
+ owner: "coder",
+ repo: "coder",
+ number: "42",
+ });
+ });
+
+ it("parses pull request URLs behind enterprise path prefixes", () => {
+ expect(
+ parsePullRequestUrl("https://git.example.com/git/org/repo/pull/42"),
+ ).toEqual({
+ owner: "org",
+ repo: "repo",
+ number: "42",
+ });
+ });
+
+ it("parses pull request URLs with suffix pages", () => {
+ expect(
+ parsePullRequestUrl("https://github.com/coder/coder/pull/42/files"),
+ ).toEqual({
+ owner: "coder",
+ repo: "coder",
+ number: "42",
+ });
+ });
+
+ it("parses enterprise pull request URLs with suffix pages", () => {
+ expect(
+ parsePullRequestUrl("https://git.example.com/git/org/repo/pull/42/files"),
+ ).toEqual({
+ owner: "org",
+ repo: "repo",
+ number: "42",
+ });
+ });
+
+ it("ignores branch URLs that only contain pull-like path segments", () => {
+ expect(
+ parsePullRequestUrl(
+ "https://github.com/coder/coder/tree/feature/pull/123/fix",
+ ),
+ ).toBeNull();
+ });
+
+ it("ignores non-pull request repository pages", () => {
+ expect(
+ parsePullRequestUrl(
+ "https://git.example.com/git/org/repo/compare/main...feature",
+ ),
+ ).toBeNull();
+ });
+});
diff --git a/site/src/pages/AgentsPage/pullRequest.ts b/site/src/pages/AgentsPage/pullRequest.ts
new file mode 100644
index 0000000000..b8d2d4033b
--- /dev/null
+++ b/site/src/pages/AgentsPage/pullRequest.ts
@@ -0,0 +1,48 @@
+const repoContentRoutePattern =
+ /\/(?:tree|blob|compare|commit|commits|branches|releases|tags|wiki)\//;
+
+export const parsePullRequestUrl = (
+ url: string | null | undefined,
+): { owner: string; repo: string; number: string } | null => {
+ if (!url) {
+ return null;
+ }
+
+ try {
+ const { pathname } = new URL(url);
+ const segments = pathname.split("/").filter(Boolean);
+ if (segments.length < 4) {
+ return null;
+ }
+
+ const pullSegmentIndex = segments.findIndex((segment, index) => {
+ if (segment !== "pull") {
+ return false;
+ }
+
+ const number = segments.at(index + 1);
+ if (!number || !/^\d+$/.test(number)) {
+ return false;
+ }
+
+ const leadingPath = `/${segments.slice(0, index).join("/")}/`;
+ return !repoContentRoutePattern.test(leadingPath);
+ });
+ if (pullSegmentIndex < 2) {
+ return null;
+ }
+
+ const number = segments.at(pullSegmentIndex + 1);
+ if (!number) {
+ return null;
+ }
+
+ return {
+ owner: segments.at(pullSegmentIndex - 2) ?? "",
+ repo: segments.at(pullSegmentIndex - 1) ?? "",
+ number,
+ };
+ } catch {
+ return null;
+ }
+};