-
Notifications
You must be signed in to change notification settings - Fork 31
chore(inbox): Slim down inbox Create PR prompt and auto-launch #2370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2a322fd
a73a7c3
6ebc9d8
fed962b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| import { useAuthStateValue } from "@features/auth/hooks/authQueries"; | ||
| import { useSettingsStore } from "@features/settings/stores/settingsStore"; | ||
| import { useCreateTask } from "@features/tasks/hooks/useTasks"; | ||
| import { useUserRepositoryIntegration } from "@hooks/useIntegrations"; | ||
| import { get } from "@renderer/di/container"; | ||
| import { RENDERER_TOKENS } from "@renderer/di/tokens"; | ||
| import { toast } from "@renderer/utils/toast"; | ||
| import { ANALYTICS_EVENTS } from "@shared/types/analytics"; | ||
| import { getCloudUrlFromRegion } from "@shared/utils/urls"; | ||
| import { useNavigationStore } from "@stores/navigationStore"; | ||
| import { track } from "@utils/analytics"; | ||
| import { logger } from "@utils/logger"; | ||
| import { useCallback, useState } from "react"; | ||
| import { toast as sonnerToast } from "sonner"; | ||
| import type { | ||
| TaskCreationInput, | ||
| TaskService, | ||
| } from "../../task-detail/service/service"; | ||
| import { buildCreatePrReportPrompt } from "../utils/buildCreatePrReportPrompt"; | ||
| import { resolveDefaultModel } from "../utils/resolveDefaultModel"; | ||
|
|
||
| const log = logger.scope("create-pr-report"); | ||
|
|
||
| interface UseCreatePrReportOptions { | ||
| reportId: string; | ||
| reportTitle: string | null; | ||
| cloudRepository: string | null; | ||
| } | ||
|
|
||
| interface UseCreatePrReportReturn { | ||
| /** Create an auto-mode implementation task for the report and navigate to it on success. */ | ||
| createPrReport: () => Promise<void>; | ||
| /** True while the task is being created. */ | ||
| isCreatingPr: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Create an implementation (PR) task directly from the inbox detail pane. | ||
| * | ||
| * Mirrors the Discuss flow: bypasses TaskInput so the user stays on the inbox | ||
| * until the task is ready, then jumps straight to the task detail page. The | ||
| * agent gets a short prompt that points it at the inbox MCP tools instead of | ||
| * inlining the entire report summary. | ||
| */ | ||
| export function useCreatePrReport({ | ||
| reportId, | ||
| reportTitle, | ||
| cloudRepository, | ||
| }: UseCreatePrReportOptions): UseCreatePrReportReturn { | ||
| const [isCreatingPr, setIsCreatingPr] = useState(false); | ||
| const { navigateToTask } = useNavigationStore(); | ||
| const { getUserIntegrationIdForRepo } = useUserRepositoryIntegration(); | ||
| const { invalidateTasks } = useCreateTask(); | ||
| const cloudRegion = useAuthStateValue((state) => state.cloudRegion); | ||
|
|
||
| const createPrReport = useCallback(async () => { | ||
| if (isCreatingPr) return; | ||
| if (!cloudRepository) { | ||
| toast.error("Pick a cloud repository before creating a PR"); | ||
| return; | ||
| } | ||
|
|
||
| const githubUserIntegrationId = | ||
| getUserIntegrationIdForRepo(cloudRepository); | ||
| if (!githubUserIntegrationId) { | ||
| toast.error("Connect a GitHub integration to create a PR"); | ||
| return; | ||
|
Comment on lines
+63
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This new Create PR path treats Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| if (!cloudRegion) { | ||
| toast.error("Sign in to create a PR"); | ||
| return; | ||
| } | ||
|
|
||
| setIsCreatingPr(true); | ||
| const toastId = toast.loading( | ||
| "Starting PR task...", | ||
| reportTitle ?? undefined, | ||
| ); | ||
|
|
||
| const prompt = buildCreatePrReportPrompt({ | ||
| reportId, | ||
| isDevBuild: import.meta.env.DEV, | ||
| }); | ||
|
|
||
| const settings = useSettingsStore.getState(); | ||
| const adapter = settings.lastUsedAdapter ?? "claude"; | ||
| const apiHost = getCloudUrlFromRegion(cloudRegion); | ||
|
|
||
| const model = | ||
| settings.lastUsedModel ?? (await resolveDefaultModel(apiHost, adapter)); | ||
|
|
||
| if (!model) { | ||
| sonnerToast.dismiss(toastId); | ||
| toast.error("Failed to start PR task", { | ||
| description: | ||
| "Couldn't resolve a default model. Open the task page once and pick a model, then try again.", | ||
| }); | ||
| setIsCreatingPr(false); | ||
| return; | ||
| } | ||
|
|
||
| const input: TaskCreationInput = { | ||
| content: prompt, | ||
| taskDescription: prompt, | ||
| repository: cloudRepository, | ||
| githubUserIntegrationId, | ||
| workspaceMode: "cloud", | ||
| executionMode: "auto", | ||
| adapter, | ||
| model, | ||
|
Comment on lines
+108
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This flow now always creates tasks with Useful? React with 👍 / 👎. |
||
| reasoningLevel: settings.lastUsedReasoningEffort ?? undefined, | ||
| cloudPrAuthorshipMode: "user", | ||
| cloudRunSource: "signal_report", | ||
| signalReportId: reportId, | ||
| }; | ||
|
|
||
| try { | ||
| const taskService = get<TaskService>(RENDERER_TOKENS.TaskService); | ||
| const result = await taskService.createTask(input, (output) => { | ||
| invalidateTasks(output.task); | ||
| navigateToTask(output.task); | ||
| }); | ||
|
|
||
| if (result.success) { | ||
| sonnerToast.dismiss(toastId); | ||
| track(ANALYTICS_EVENTS.TASK_CREATED, { | ||
| auto_run: true, | ||
| created_from: "command-menu", | ||
| repository_provider: "github", | ||
| workspace_mode: "cloud", | ||
| has_branch: false, | ||
| cloud_run_source: "signal_report", | ||
| cloud_pr_authorship_mode: "user", | ||
| adapter, | ||
| }); | ||
|
andrewm4894 marked this conversation as resolved.
|
||
| } else { | ||
| sonnerToast.dismiss(toastId); | ||
| toast.error("Failed to start PR task", { | ||
| description: result.error, | ||
| }); | ||
| log.error("Create PR task creation failed", { | ||
| failedStep: result.failedStep, | ||
| error: result.error, | ||
| reportId, | ||
| reportTitle, | ||
| }); | ||
| } | ||
| } catch (error) { | ||
| sonnerToast.dismiss(toastId); | ||
| const description = | ||
| error instanceof Error ? error.message : "Unknown error"; | ||
| toast.error("Failed to start PR task", { description }); | ||
| log.error("Unexpected error during Create PR task creation", { | ||
| error, | ||
| reportId, | ||
| }); | ||
| } finally { | ||
| setIsCreatingPr(false); | ||
| } | ||
| }, [ | ||
| isCreatingPr, | ||
| cloudRepository, | ||
| cloudRegion, | ||
| reportId, | ||
| reportTitle, | ||
| getUserIntegrationIdForRepo, | ||
| invalidateTasks, | ||
| navigateToTask, | ||
| ]); | ||
|
|
||
| return { createPrReport, isCreatingPr }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { buildCreatePrReportPrompt } from "./buildCreatePrReportPrompt"; | ||
|
|
||
| describe("buildCreatePrReportPrompt", () => { | ||
| it.each([ | ||
| { isDevBuild: false, expectedScheme: "posthog-code" }, | ||
| { isDevBuild: true, expectedScheme: "posthog-code-dev" }, | ||
| ])( | ||
| "uses the $expectedScheme deeplink scheme when isDevBuild=$isDevBuild", | ||
| ({ isDevBuild, expectedScheme }) => { | ||
| const prompt = buildCreatePrReportPrompt({ | ||
| reportId: "abc123", | ||
| isDevBuild, | ||
| }); | ||
| expect(prompt).toContain(`${expectedScheme}://inbox/abc123`); | ||
| }, | ||
| ); | ||
|
|
||
| it("references the inbox MCP tools so the agent fetches the detail itself", () => { | ||
| const prompt = buildCreatePrReportPrompt({ | ||
| reportId: "abc123", | ||
| isDevBuild: false, | ||
| }); | ||
| expect(prompt).toContain("inbox MCP tools"); | ||
| }); | ||
|
|
||
| it("asks the agent to open a PR", () => { | ||
| const prompt = buildCreatePrReportPrompt({ | ||
| reportId: "abc123", | ||
| isDevBuild: false, | ||
| }); | ||
| expect(prompt).toMatch(/open a PR/i); | ||
| }); | ||
|
|
||
| it("tells the agent to stop rather than guess if the report can't be fetched", () => { | ||
| const prompt = buildCreatePrReportPrompt({ | ||
| reportId: "abc123", | ||
| isDevBuild: false, | ||
| }); | ||
| expect(prompt).toMatch(/can't fetch the report/i); | ||
| expect(prompt).toMatch(/instead of guessing/i); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { getDeeplinkProtocol } from "@shared/deeplink"; | ||
|
|
||
| interface BuildCreatePrReportPromptOptions { | ||
| reportId: string; | ||
| isDevBuild: boolean; | ||
| } | ||
|
|
||
| export function buildCreatePrReportPrompt({ | ||
| reportId, | ||
| isDevBuild, | ||
| }: BuildCreatePrReportPromptOptions): string { | ||
| const reportLink = `${getDeeplinkProtocol(isDevBuild)}://inbox/${reportId}`; | ||
| return `Act on PostHog inbox report ${reportId} ([inbox item](${reportLink})). Use the inbox MCP tools to fetch the report, its signals, and any suggested reviewers; investigate the root cause; implement the fix; and open a PR. If you can't fetch the report, stop and report that instead of guessing what it contains.`; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
isCreatingPrguard is state-based and is checked beforesetIsCreatingPr(true)runs, so two very fast triggers (e.g., double-click or key-repeat on Cmd/Ctrl+Enter) in the same render frame can both pass this check and start parallelcreateTaskcalls. That can create duplicate PR tasks for a single report. Use a synchronous in-flight ref/mutex (or set-and-check atomically) so re-entrant calls are blocked immediately.Useful? React with 👍 / 👎.