diff --git a/packages/core/src/deep-links/newTaskLinkResolver.test.ts b/packages/core/src/deep-links/newTaskLinkResolver.test.ts
index b417fde5ff..2434fb28e5 100644
--- a/packages/core/src/deep-links/newTaskLinkResolver.test.ts
+++ b/packages/core/src/deep-links/newTaskLinkResolver.test.ts
@@ -49,6 +49,54 @@ describe("NewTaskLinkResolver", () => {
expect(result.analytics.properties.has_prompt).toBe(true);
});
+ it.each([
+ {
+ name: "one pull request",
+ prompt:
+ '',
+ expected: "acme/web",
+ },
+ {
+ name: "several pull requests from one repository",
+ prompt:
+ '\n',
+ expected: "acme/web",
+ },
+ {
+ name: "pull requests from different repositories",
+ prompt:
+ '\n',
+ expected: undefined,
+ },
+ {
+ name: "an ordinary prompt",
+ prompt: "fix the tests",
+ expected: undefined,
+ },
+ ])("infers the repository for $name", async ({ prompt, expected }) => {
+ const resolver = makeResolver(vi.fn());
+
+ const result = await resolver.resolve({ action: "new", prompt });
+
+ if (result.kind !== "navigate") throw new Error("expected navigate");
+ expect(result.navigation.initialCloudRepository).toBe(expected);
+ });
+
+ it("prefers an explicit repository over the pull request prompt", async () => {
+ const resolver = makeResolver(vi.fn());
+ const prompt =
+ '';
+
+ const result = await resolver.resolve({
+ action: "new",
+ prompt,
+ repo: "acme/api",
+ });
+
+ if (result.kind !== "navigate") throw new Error("expected navigate");
+ expect(result.navigation.initialCloudRepository).toBe("acme/api");
+ });
+
it("uses the decoded plan as the prompt for a plan-action payload", async () => {
const resolver = makeResolver(vi.fn());
const payload: NewTaskLinkPayload = { action: "plan", plan: "step one" };
diff --git a/packages/core/src/deep-links/newTaskLinkResolver.ts b/packages/core/src/deep-links/newTaskLinkResolver.ts
index 25d0c7b7e8..7af6907de1 100644
--- a/packages/core/src/deep-links/newTaskLinkResolver.ts
+++ b/packages/core/src/deep-links/newTaskLinkResolver.ts
@@ -10,6 +10,34 @@ import {
export { NEW_TASK_LINK_RESOLVER };
+const GITHUB_PR_TAG_REGEX = /]*\burl="([^"]+)"[^>]*\/>/g;
+
+function inferRepositoryFromPullRequests(
+ prompt: string | undefined,
+): string | undefined {
+ if (!prompt) return undefined;
+
+ const repositories = new Set();
+ for (const match of prompt.matchAll(GITHUB_PR_TAG_REGEX)) {
+ try {
+ const url = new URL(match[1]);
+ const pathParts = url.pathname.split("/").filter(Boolean);
+ if (
+ url.hostname !== "github.com" ||
+ pathParts.length !== 4 ||
+ pathParts[2] !== "pull"
+ ) {
+ continue;
+ }
+ repositories.add(`${pathParts[0]}/${pathParts[1]}`);
+ } catch {}
+ }
+
+ return repositories.size === 1
+ ? repositories.values().next().value
+ : undefined;
+}
+
@injectable()
export class NewTaskLinkResolver {
constructor(
@@ -35,7 +63,8 @@ export class NewTaskLinkResolver {
kind: "navigate",
navigation: {
initialPrompt: payload.prompt,
- initialCloudRepository: payload.repo,
+ initialCloudRepository:
+ payload.repo ?? inferRepositoryFromPullRequests(payload.prompt),
initialModel: payload.model,
initialMode: payload.mode,
},