diff --git a/apps/mobile/src/app/task/index.tsx b/apps/mobile/src/app/task/index.tsx index 3b0b0a2c3..d2da28fc9 100644 --- a/apps/mobile/src/app/task/index.tsx +++ b/apps/mobile/src/app/task/index.tsx @@ -383,6 +383,9 @@ export default function NewTaskScreen() { repository: selection.repository, githubIntegrationId: selection.integrationId, composerIsEmpty: !hasContent, + runtimeAdapter: "claude", + model, + reasoningEffort: showReasoningPill ? reasoning : null, }); if (isLoading && hasGithubIntegration === null) { diff --git a/apps/mobile/src/features/tasks/api.ts b/apps/mobile/src/features/tasks/api.ts index cd399b109..0bc88a4d9 100644 --- a/apps/mobile/src/features/tasks/api.ts +++ b/apps/mobile/src/features/tasks/api.ts @@ -306,6 +306,9 @@ export async function warmTask(options: { repository: string; github_integration: number; branch?: string | null; + runtime_adapter?: string | null; + model?: string | null; + reasoning_effort?: string | null; }): Promise<{ task_id: string; run_id: string } | null> { const baseUrl = getBaseUrl(); const projectId = getProjectId(); @@ -318,6 +321,9 @@ export async function warmTask(options: { repository: options.repository, github_integration: options.github_integration, branch: options.branch ?? null, + runtime_adapter: options.runtime_adapter ?? null, + model: options.model ?? null, + reasoning_effort: options.reasoning_effort ?? null, }), }, ); diff --git a/apps/mobile/src/features/tasks/api.warm.test.ts b/apps/mobile/src/features/tasks/api.warm.test.ts index 16ab7f479..d8e8f882e 100644 --- a/apps/mobile/src/features/tasks/api.warm.test.ts +++ b/apps/mobile/src/features/tasks/api.warm.test.ts @@ -51,6 +51,40 @@ describe("warmTask", () => { repository: "posthog/posthog", github_integration: 7, branch: "main", + runtime_adapter: null, + model: null, + reasoning_effort: null, + }), + }), + ); + }); + + it("forwards the selected runtime, model, and reasoning effort", async () => { + mockFetch.mockResolvedValueOnce( + new Response(JSON.stringify({ task_id: "task-1", run_id: "run-1" }), { + status: 200, + }), + ); + + await warmTask({ + repository: "posthog/posthog", + github_integration: 7, + branch: "main", + runtime_adapter: "claude", + model: "claude-opus-4-8", + reasoning_effort: "high", + }); + + expect(mockFetch).toHaveBeenCalledWith( + "https://app.posthog.test/api/projects/42/tasks/warm/", + expect.objectContaining({ + body: JSON.stringify({ + repository: "posthog/posthog", + github_integration: 7, + branch: "main", + runtime_adapter: "claude", + model: "claude-opus-4-8", + reasoning_effort: "high", }), }), ); @@ -72,6 +106,9 @@ describe("warmTask", () => { repository: "posthog/posthog", github_integration: 7, branch: null, + runtime_adapter: null, + model: null, + reasoning_effort: null, }), }), ); diff --git a/apps/mobile/src/features/tasks/hooks/useWarmTask.test.tsx b/apps/mobile/src/features/tasks/hooks/useWarmTask.test.tsx index 7765c76be..8ed33df81 100644 --- a/apps/mobile/src/features/tasks/hooks/useWarmTask.test.tsx +++ b/apps/mobile/src/features/tasks/hooks/useWarmTask.test.tsx @@ -29,6 +29,9 @@ interface Props { githubIntegrationId?: number | null; branch?: string | null; composerIsEmpty: boolean; + runtimeAdapter?: string | null; + model?: string | null; + reasoningEffort?: string | null; } const composing: Props = { @@ -38,6 +41,12 @@ const composing: Props = { composerIsEmpty: false, }; +const NULL_RUNTIME = { + runtime_adapter: null, + model: null, + reasoning_effort: null, +}; + function render(initial: Props) { let current = initial; function Wrapper() { @@ -87,6 +96,7 @@ describe("useWarmTask", () => { repository: "acme/repo", github_integration: 42, branch: "main", + ...NULL_RUNTIME, }); }); @@ -131,37 +141,59 @@ describe("useWarmTask", () => { it.each<{ name: string; + initial?: Partial; change: Partial; - expectedRepository: string; - expectedBranch: string; + expected: Record; }>([ { name: "repository", change: { repository: "acme/other" }, - expectedRepository: "acme/other", - expectedBranch: "main", + expected: { + repository: "acme/other", + github_integration: 42, + branch: "main", + ...NULL_RUNTIME, + }, }, { name: "branch", change: { branch: "feature/x" }, - expectedRepository: "acme/repo", - expectedBranch: "feature/x", + expected: { + repository: "acme/repo", + github_integration: 42, + branch: "feature/x", + ...NULL_RUNTIME, + }, + }, + { + name: "model", + initial: { + runtimeAdapter: "claude", + model: "claude-opus-4-8", + reasoningEffort: "high", + }, + change: { model: "claude-sonnet-4-6" }, + expected: { + repository: "acme/repo", + github_integration: 42, + branch: "main", + runtime_adapter: "claude", + model: "claude-sonnet-4-6", + reasoning_effort: "high", + }, }, ])( "warms the new selection when the $name changes", - async ({ change, expectedRepository, expectedBranch }) => { - const { rerender } = render(composing); + async ({ initial, change, expected }) => { + const base = { ...composing, ...initial }; + const { rerender } = render(base); await flushDebounce(); expect(mockWarmTask).toHaveBeenCalledOnce(); - rerender({ ...composing, ...change }); + rerender({ ...base, ...change }); await flushDebounce(); - expect(mockWarmTask).toHaveBeenLastCalledWith({ - repository: expectedRepository, - github_integration: 42, - branch: expectedBranch, - }); + expect(mockWarmTask).toHaveBeenLastCalledWith(expected); expect(mockWarmTask).toHaveBeenCalledTimes(2); }, ); diff --git a/apps/mobile/src/features/tasks/hooks/useWarmTask.ts b/apps/mobile/src/features/tasks/hooks/useWarmTask.ts index 0173ed952..e69ce6555 100644 --- a/apps/mobile/src/features/tasks/hooks/useWarmTask.ts +++ b/apps/mobile/src/features/tasks/hooks/useWarmTask.ts @@ -13,6 +13,9 @@ interface UseWarmTaskOptions { githubIntegrationId?: number | null; branch?: string | null; composerIsEmpty: boolean; + runtimeAdapter?: string | null; + model?: string | null; + reasoningEffort?: string | null; } export function useWarmTask({ @@ -20,6 +23,9 @@ export function useWarmTask({ githubIntegrationId, branch, composerIsEmpty, + runtimeAdapter, + model, + reasoningEffort, }: UseWarmTaskOptions): void { const enabled = useFeatureFlag(TASKS_PREWARM_SANDBOX_FLAG); @@ -27,6 +33,9 @@ export function useWarmTask({ const lastWarmedKeyRef = useRef(null); const normalizedBranch = branch ?? null; + const normalizedRuntimeAdapter = runtimeAdapter ?? null; + const normalizedModel = model ?? null; + const normalizedReasoningEffort = reasoningEffort ?? null; const eligible = !!enabled && !!repository && @@ -34,7 +43,7 @@ export function useWarmTask({ !composerIsEmpty; const key = repository && githubIntegrationId != null - ? `${githubIntegrationId}:${repository}:${normalizedBranch ?? ""}` + ? `${githubIntegrationId}:${repository}:${normalizedBranch ?? ""}:${normalizedRuntimeAdapter ?? ""}:${normalizedModel ?? ""}:${normalizedReasoningEffort ?? ""}` : null; useEffect(() => { @@ -56,6 +65,9 @@ export function useWarmTask({ const repo = repository; const githubIntegration = githubIntegrationId; const warmBranch = normalizedBranch; + const warmRuntimeAdapter = normalizedRuntimeAdapter; + const warmModel = normalizedModel; + const warmReasoningEffort = normalizedReasoningEffort; debounceRef.current = setTimeout(() => { debounceRef.current = null; lastWarmedKeyRef.current = key; @@ -63,6 +75,9 @@ export function useWarmTask({ repository: repo, github_integration: githubIntegration, branch: warmBranch, + runtime_adapter: warmRuntimeAdapter, + model: warmModel, + reasoning_effort: warmReasoningEffort, }).catch((error) => { lastWarmedKeyRef.current = null; log.warn("Failed to warm task", error); @@ -70,5 +85,14 @@ export function useWarmTask({ }, WARM_DEBOUNCE_MS); return clearDebounce; - }, [eligible, key, repository, githubIntegrationId, normalizedBranch]); + }, [ + eligible, + key, + repository, + githubIntegrationId, + normalizedBranch, + normalizedRuntimeAdapter, + normalizedModel, + normalizedReasoningEffort, + ]); }