diff --git a/apps/mobile/src/app/inbox/[...id].tsx b/apps/mobile/src/app/inbox/[...id].tsx index 8ad989bfa9..e50007fbac 100644 --- a/apps/mobile/src/app/inbox/[...id].tsx +++ b/apps/mobile/src/app/inbox/[...id].tsx @@ -312,6 +312,7 @@ export default function ReportDetailScreen() { prompt, ...(reportRepo ? { repo: reportRepo } : {}), signalReport: report.id, + signalReportRelationship: "implementation", }, }); }, @@ -374,6 +375,7 @@ export default function ReportDetailScreen() { prompt, ...(reportRepo ? { repo: reportRepo } : {}), signalReport: report.id, + signalReportRelationship: "discussion", }, }); }, diff --git a/apps/mobile/src/app/task/index.tsx b/apps/mobile/src/app/task/index.tsx index 04050437c7..c15e778d9a 100644 --- a/apps/mobile/src/app/task/index.tsx +++ b/apps/mobile/src/app/task/index.tsx @@ -96,10 +96,12 @@ export default function NewTaskScreen() { prompt: initialPrompt, repo: initialRepo, signalReport, + signalReportRelationship, } = useLocalSearchParams<{ prompt?: string; repo?: string; signalReport?: string; + signalReportRelationship?: "implementation" | "discussion"; }>(); const router = useRouter(); const themeColors = useThemeColors(); @@ -320,7 +322,7 @@ export default function NewTaskScreen() { : `Attached ${attachments.length} files`); const client = getPostHogApiClient(); - const task = await client.createTask({ + const taskOptions = { description: descriptionText, title: descriptionText.slice(0, 100), repository: selection.repository ?? undefined, @@ -329,14 +331,15 @@ export default function NewTaskScreen() { // UUID the API expects. The backend also auto-resolves this from the // repository for user-created tasks, so it's a best-effort hint. github_user_integration: getUserIntegrationId(selection.integrationId), - ...(signalReport - ? { - origin_product: "signal_report", - signal_report: signalReport, - signal_report_task_relationship: "implementation", - } - : {}), - } as CreateTaskOptions); + }; + const task = signalReport + ? await client.createSignalReportTask({ + ...taskOptions, + signal_report: signalReport, + signal_report_task_relationship: + signalReportRelationship ?? "implementation", + }) + : await client.createTask(taskOptions as CreateTaskOptions); pendingTaskPromptStoreApi.move(pendingKey, task.id); currentPendingKey = task.id; @@ -387,6 +390,7 @@ export default function NewTaskScreen() { router, selection, signalReport, + signalReportRelationship, getUserIntegrationId, setComposerConfig, ]); diff --git a/apps/mobile/src/features/inbox/components/TinderView.tsx b/apps/mobile/src/features/inbox/components/TinderView.tsx index 2703dfc014..95ef7fd928 100644 --- a/apps/mobile/src/features/inbox/components/TinderView.tsx +++ b/apps/mobile/src/features/inbox/components/TinderView.tsx @@ -28,10 +28,7 @@ import { MarkdownText } from "@/features/chat/components/MarkdownText"; import { usePreferencesStore } from "@/features/preferences/stores/preferencesStore"; import { getModelConfigOption } from "@/features/tasks/composer/options"; import { useCloudTaskConfigOptions } from "@/features/tasks/hooks/useCloudTaskConfigOptions"; -import type { - CreateTaskOptions, - RepositoryOption, -} from "@/features/tasks/types"; +import type { RepositoryOption } from "@/features/tasks/types"; import { ANALYTICS_EVENTS, computeReportAgeHours, @@ -246,15 +243,14 @@ export function TinderView({ // 3. Create the task const prompt = `Act on this signal report. Investigate the root cause, implement the fix, and open a PR if appropriate.\n\n${report.summary ?? ""}`; const client = getPostHogApiClient(); - const task = await client.createTask({ + const task = await client.createSignalReportTask({ description: prompt, title: prompt.slice(0, 255), repository: match?.repository ?? repo ?? undefined, github_integration: match?.integrationId ?? undefined, - origin_product: "signal_report", signal_report: report.id, signal_report_task_relationship: "implementation", - } as CreateTaskOptions); + }); // 4. Run it await client.runTaskInCloud(task.id, undefined, { diff --git a/packages/api-client/src/posthog-client.ts b/packages/api-client/src/posthog-client.ts index 9a42697e08..48861b6902 100644 --- a/packages/api-client/src/posthog-client.ts +++ b/packages/api-client/src/posthog-client.ts @@ -2642,6 +2642,39 @@ export class PostHogAPIClient { return normalizeTaskResponse(data, { teamId }); } + async createSignalReportTask( + options: Record & { + description: string; + signal_report: string; + signal_report_task_relationship: "implementation" | "discussion"; + }, + ): Promise { + const teamId = await this.getTeamId(); + const path = `/api/projects/${teamId}/tasks/signal_report/`; + const response = await this.api.fetcher.fetch({ + method: "post", + path, + url: new URL(`${this.api.baseUrl}${path}`), + overrides: { body: JSON.stringify(options) }, + throwOnStatusError: false, + }); + if (response.status === 404) { + return this.createTask({ + ...options, + origin_product: "signal_report", + } as Parameters[0]); + } + if (!response.ok) { + throw new Error( + `Failed to create signal report task: ${response.statusText}`, + ); + } + return normalizeTaskResponse( + (await response.json()) as Parameters[0], + { teamId }, + ); + } + async updateTask( taskId: string, updates: Partial, diff --git a/packages/core/src/inbox/reportTaskCreation.ts b/packages/core/src/inbox/reportTaskCreation.ts index 67f5d9cb95..64bff1fa1b 100644 --- a/packages/core/src/inbox/reportTaskCreation.ts +++ b/packages/core/src/inbox/reportTaskCreation.ts @@ -76,6 +76,7 @@ export interface BuildSignalReportTaskInput { model: string; reasoningLevel?: string; baseBranch?: string | null; + relationship: "implementation" | "discussion"; } /** Build the `TaskCreationInput` for an inbox direct-create (Discuss / Create-PR) flow. */ @@ -91,6 +92,7 @@ export function buildSignalReportTaskInput( model, reasoningLevel, baseBranch, + relationship, } = args; return { content: prompt, @@ -106,5 +108,6 @@ export function buildSignalReportTaskInput( cloudPrAuthorshipMode: "user", cloudRunSource: "signal_report", signalReportId: reportId, + signalReportTaskRelationship: relationship, }; } diff --git a/packages/core/src/inbox/signalReportTaskService.ts b/packages/core/src/inbox/signalReportTaskService.ts index abc787011d..22e45b0c6c 100644 --- a/packages/core/src/inbox/signalReportTaskService.ts +++ b/packages/core/src/inbox/signalReportTaskService.ts @@ -115,6 +115,7 @@ export class SignalReportTaskService { model, reasoningLevel: input.reasoningLevel, baseBranch: input.baseBranch, + relationship: input.kind === "discuss" ? "discussion" : "implementation", }); let result: CreateTaskResult; diff --git a/packages/core/src/task-detail/taskCreationApiClient.ts b/packages/core/src/task-detail/taskCreationApiClient.ts index 428189f9b0..5cdc6230f5 100644 --- a/packages/core/src/task-detail/taskCreationApiClient.ts +++ b/packages/core/src/task-detail/taskCreationApiClient.ts @@ -37,6 +37,7 @@ export interface TaskCreationApiClient { getTask(taskId: string): Promise; getTaskRun(taskId: string, runId: string): Promise; createTask(options: Record): Promise; + createSignalReportTask(options: Record): Promise; deleteTask(taskId: string): Promise; createTaskRun( taskId: string, diff --git a/packages/core/src/task-detail/taskCreationSaga.test.ts b/packages/core/src/task-detail/taskCreationSaga.test.ts index f4893f95a4..1ffea3e815 100644 --- a/packages/core/src/task-detail/taskCreationSaga.test.ts +++ b/packages/core/src/task-detail/taskCreationSaga.test.ts @@ -92,6 +92,7 @@ function makeSaga( return new TaskCreationSaga({ posthogClient: { createTask: vi.fn(), + createSignalReportTask: vi.fn(), deleteTask: vi.fn(), getTask: vi.fn(), createTaskRun: vi.fn(), @@ -943,11 +944,13 @@ describe("TaskCreationSaga", () => { latest_run: createRun(), }); const createTaskMock = vi.fn().mockResolvedValue(createdTask); + const createSignalReportTaskMock = vi.fn().mockResolvedValue(createdTask); const createTaskRunMock = vi.fn().mockResolvedValue(createRun()); const startTaskRunMock = vi.fn().mockResolvedValue(startedTask); const saga = makeSaga({ createTask: createTaskMock, + createSignalReportTask: createSignalReportTaskMock, createTaskRun: createTaskRunMock, startTaskRun: startTaskRunMock, }); @@ -959,15 +962,18 @@ describe("TaskCreationSaga", () => { branch: "main", cloudRunSource: "signal_report", signalReportId: "report-123", + signalReportTaskRelationship: "implementation", githubIntegrationId: 123, }); expect(result.success).toBe(true); - expect(createTaskMock).toHaveBeenCalledWith( + expect(createTaskMock).not.toHaveBeenCalled(); + expect(createSignalReportTaskMock).toHaveBeenCalledWith( expect.objectContaining({ github_integration: 123, github_user_integration: undefined, - origin_product: "signal_report", + origin_product: undefined, + signal_report_task_relationship: "implementation", }), ); expect(createTaskRunMock).toHaveBeenCalledWith( diff --git a/packages/core/src/task-detail/taskCreationSaga.ts b/packages/core/src/task-detail/taskCreationSaga.ts index a2f384cb1b..fb3fe854ab 100644 --- a/packages/core/src/task-detail/taskCreationSaga.ts +++ b/packages/core/src/task-detail/taskCreationSaga.ts @@ -790,7 +790,12 @@ export class TaskCreationSaga extends Saga< const description = input.taskDescription ?? input.content ?? ""; const canActivateWarmRun = input.runtime !== "pi" && !warmPayload?.suppressWarmReuse; - const result = await this.deps.posthogClient.createTask({ + const createTask = input.signalReportId + ? this.deps.posthogClient.createSignalReportTask.bind( + this.deps.posthogClient, + ) + : this.deps.posthogClient.createTask.bind(this.deps.posthogClient); + const result = await createTask({ description, repository: repository ?? undefined, github_integration: @@ -803,11 +808,7 @@ export class TaskCreationSaga extends Saga< input.cloudRunSource !== "signal_report" ? input.githubUserIntegrationId : undefined, - origin_product: input.signalReportId - ? "signal_report" - : "user_created", - // The server associates the task with the report and records the implementation - // task_run artefact — no relationship label is sent (associations are unlabelled). + origin_product: input.signalReportId ? undefined : "user_created", branch: input.workspaceMode === "cloud" && canActivateWarmRun ? (input.branch ?? null) @@ -839,6 +840,8 @@ export class TaskCreationSaga extends Saga< ? input.customImageId : undefined, signal_report: input.signalReportId ?? undefined, + signal_report_task_relationship: + input.signalReportTaskRelationship ?? undefined, channel: input.channelId ?? undefined, runtime: input.runtime ?? "acp", pending_user_message: warmPayload?.pendingUserMessage, diff --git a/packages/shared/src/task-creation-domain.ts b/packages/shared/src/task-creation-domain.ts index a330efaa2d..fda19d4224 100644 --- a/packages/shared/src/task-creation-domain.ts +++ b/packages/shared/src/task-creation-domain.ts @@ -55,6 +55,7 @@ export interface TaskCreationInput { */ cloudRtkEnabled?: boolean; signalReportId?: string; + signalReportTaskRelationship?: "implementation" | "discussion"; additionalDirectories?: string[]; /** * CONTEXT.md of the channel a task was created in, if any. Appended to the diff --git a/packages/ui/src/features/inbox/hooks/useCreatePrReport.ts b/packages/ui/src/features/inbox/hooks/useCreatePrReport.ts index 7187c869e0..f6f4b34412 100644 --- a/packages/ui/src/features/inbox/hooks/useCreatePrReport.ts +++ b/packages/ui/src/features/inbox/hooks/useCreatePrReport.ts @@ -93,6 +93,7 @@ export function useCreatePrReport({ cloudPrAuthorshipMode: "user", cloudRunSource: "signal_report", signalReportId: reportId, + signalReportTaskRelationship: "implementation", }; }, [baseBranchOverrides, reportId, cloudRegion, projectId], diff --git a/packages/ui/src/features/inbox/hooks/useDiscussReport.ts b/packages/ui/src/features/inbox/hooks/useDiscussReport.ts index e62d77cf66..4fa41169e5 100644 --- a/packages/ui/src/features/inbox/hooks/useDiscussReport.ts +++ b/packages/ui/src/features/inbox/hooks/useDiscussReport.ts @@ -49,6 +49,7 @@ export function useDiscussReport({ cloudPrAuthorshipMode: "user", cloudRunSource: "signal_report", signalReportId: reportId, + signalReportTaskRelationship: "discussion", }; }, [reportId, reportTitle],