Skip to content

Commit b968bf5

Browse files
refactor(mobile): share agent configuration controls
Keep task creation, in-task changes, and terminal retries on the same adapter configuration policy. Generated-By: PostHog Code Task-Id: c1bbe3cf-742b-4b24-bf96-d11a18b4cf22
1 parent c4b8e2f commit b968bf5

7 files changed

Lines changed: 432 additions & 373 deletions

File tree

apps/mobile/src/app/task/[id].tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
DEFAULT_GATEWAY_MODEL,
1313
DEFAULT_REASONING_EFFORT,
1414
type ExecutionMode,
15-
getReasoningEffortOptions,
1615
isSupportedReasoningEffort,
1716
type SupportedReasoningEffort,
1817
serializeCloudPrompt,
@@ -61,6 +60,7 @@ import {
6160
import { useTaskSessionStore } from "@/features/tasks/stores/taskSessionStore";
6261
import { useTaskStore } from "@/features/tasks/stores/taskStore";
6362
import { confirmStopRun } from "@/features/tasks/utils/archiveGuard";
63+
import { buildCloudTaskRunConfig } from "@/features/tasks/utils/cloudTaskRunConfig";
6464
import { useScreenInsets } from "@/hooks/useScreenInsets";
6565
import {
6666
ANALYTICS_EVENTS,
@@ -341,18 +341,18 @@ export default function TaskDetailScreen() {
341341
)
342342
: text;
343343

344-
const supportsReasoning =
345-
getReasoningEffortOptions(composerAdapter, composerModel) !== null;
346344
const updatedTask = await getPostHogApiClient().runTaskInCloud(
347345
taskId,
348346
undefined,
349347
{
350348
resumeFromRunId: task.latest_run?.id,
351349
pendingUserMessage,
352-
adapter: composerAdapter,
353-
model: composerModel,
354-
reasoningLevel: supportsReasoning ? composerReasoning : undefined,
355-
initialPermissionMode: composerMode,
350+
...buildCloudTaskRunConfig({
351+
adapter: composerAdapter,
352+
mode: composerMode,
353+
model: composerModel,
354+
reasoning: composerReasoning,
355+
}),
356356
rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud,
357357
},
358358
);
@@ -616,13 +616,12 @@ export default function TaskDetailScreen() {
616616
undefined,
617617
{
618618
resumeFromRunId: task.latest_run?.id,
619-
adapter: composerAdapter,
620-
model: composerModel,
621-
reasoningLevel:
622-
getReasoningEffortOptions(composerAdapter, composerModel) !== null
623-
? composerReasoning
624-
: undefined,
625-
initialPermissionMode: composerMode,
619+
...buildCloudTaskRunConfig({
620+
adapter: composerAdapter,
621+
mode: composerMode,
622+
model: composerModel,
623+
reasoning: composerReasoning,
624+
}),
626625
rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud,
627626
},
628627
);

apps/mobile/src/app/task/index.tsx

Lines changed: 23 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ import { Text } from "@components/text";
22
import {
33
DEFAULT_CLAUDE_EXECUTION_MODE,
44
getAvailableModesForAdapter,
5-
getDefaultExecutionModeForAdapter,
65
} from "@posthog/core/sessions/executionModes";
76
import { resolveCloudComposerModelChange } from "@posthog/core/task-detail/composerModelPolicy";
87
import {
98
type Adapter,
10-
DEFAULT_CODEX_MODEL,
119
DEFAULT_GATEWAY_MODEL,
1210
DEFAULT_REASONING_EFFORT,
1311
type ExecutionMode,
@@ -20,17 +18,10 @@ import { LinearGradient } from "expo-linear-gradient";
2018
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
2119
import {
2220
ArrowUp,
23-
BrainIcon,
2421
CaretDown,
25-
Cpu,
2622
GithubLogo,
2723
MicrophoneIcon,
2824
PaperclipIcon,
29-
PauseIcon,
30-
PencilIcon,
31-
Robot,
32-
ShieldCheck,
33-
Sparkle,
3425
StopIcon,
3526
} from "phosphor-react-native";
3627
import { useCallback, useEffect, useState } from "react";
@@ -50,6 +41,7 @@ import { useVoiceRecording } from "@/features/chat";
5041
import { usePreferencesStore } from "@/features/preferences/stores/preferencesStore";
5142
import { GitHubConnectionPrompt } from "@/features/tasks/components/GitHubConnectionPrompt";
5243
import { GitHubLoadNotice } from "@/features/tasks/components/GitHubLoadNotice";
44+
import { AgentConfigControls } from "@/features/tasks/composer/AgentConfigControls";
5345
import { AttachmentSheet } from "@/features/tasks/composer/attachments/AttachmentSheet";
5446
import { AttachmentsBar } from "@/features/tasks/composer/attachments/AttachmentsBar";
5547
import { buildCloudPromptBlocks } from "@/features/tasks/composer/attachments/buildCloudPrompt";
@@ -61,14 +53,10 @@ import {
6153
import type { PendingAttachment } from "@/features/tasks/composer/attachments/types";
6254
import { DotBackground } from "@/features/tasks/composer/DotBackground";
6355
import {
64-
getComposerModelOptions,
65-
getConfigOptionLabel,
6656
getMobileExecutionModes,
6757
getModelConfigOption,
6858
} from "@/features/tasks/composer/options";
69-
import { Pill } from "@/features/tasks/composer/Pill";
7059
import { RepositoryPickerInline } from "@/features/tasks/composer/RepositoryPickerInline";
71-
import { SelectSheet } from "@/features/tasks/composer/SelectSheet";
7260
import { useCloudTaskConfigOptions } from "@/features/tasks/hooks/useCloudTaskConfigOptions";
7361
import { useUserIntegrations } from "@/features/tasks/hooks/useUserIntegrations";
7462
import { useWarmTask } from "@/features/tasks/hooks/useWarmTask";
@@ -82,6 +70,7 @@ import type {
8270
CreateTaskOptions,
8371
RepositorySelection,
8472
} from "@/features/tasks/types";
73+
import { buildCloudTaskRunConfig } from "@/features/tasks/utils/cloudTaskRunConfig";
8574
import {
8675
findRepositoryOption,
8776
isRepositorySelectionComplete,
@@ -93,31 +82,12 @@ import { getPostHogApiClient } from "@/lib/posthogApiClient";
9382
import { toRgba, useThemeColors } from "@/lib/theme";
9483

9584
const log = logger.scope("task-create");
96-
const SWITCH_ADAPTER_VALUE = "__switch_adapter__";
9785
const SUGGESTIONS = [
9886
"Create or update my CLAUDE.md file",
9987
"Search for a TODO comment and fix it",
10088
"Recommend areas to improve our tests",
10189
] as const;
10290

103-
function modeIcon(mode: ExecutionMode, color: string, size = 14) {
104-
switch (mode) {
105-
case "plan":
106-
return <PauseIcon size={size} color={color} weight="bold" />;
107-
case "default":
108-
return <PencilIcon size={size} color={color} />;
109-
case "acceptEdits":
110-
return <ShieldCheck size={size} color={color} />;
111-
case "bypassPermissions":
112-
case "full-access":
113-
return <ShieldCheck size={size} color={color} weight="fill" />;
114-
case "read-only":
115-
return <PauseIcon size={size} color={color} />;
116-
case "auto":
117-
return <Sparkle size={size} color={color} weight="fill" />;
118-
}
119-
}
120-
12191
export default function NewTaskScreen() {
12292
const {
12393
prompt: initialPrompt,
@@ -136,11 +106,7 @@ export default function NewTaskScreen() {
136106
const [adapter, setAdapter] = useState<Adapter>("claude");
137107
const { configOptions, hasLiveConfig, isConfigReady } =
138108
useCloudTaskConfigOptions(adapter);
139-
const executionModes = getMobileExecutionModes(
140-
getAvailableModesForAdapter(adapter),
141-
);
142109
const modelConfigOption = getModelConfigOption(configOptions);
143-
const mobileModelOptions = getComposerModelOptions(modelConfigOption);
144110
const {
145111
error,
146112
hasGithubIntegration,
@@ -236,9 +202,6 @@ export default function NewTaskScreen() {
236202
}, [adapter, hasLiveConfig, model, modelConfigOption, reasoning]);
237203
const [creating, setCreating] = useState(false);
238204
const [repoSheetOpen, setRepoSheetOpen] = useState(false);
239-
const [modeSheetOpen, setModeSheetOpen] = useState(false);
240-
const [modelSheetOpen, setModelSheetOpen] = useState(false);
241-
const [reasoningSheetOpen, setReasoningSheetOpen] = useState(false);
242205
const [attachments, setAttachments] = useState<PendingAttachment[]>([]);
243206
const [attachmentSheetOpen, setAttachmentSheetOpen] = useState(false);
244207

@@ -381,15 +344,9 @@ export default function NewTaskScreen() {
381344
)
382345
: trimmedPrompt;
383346

384-
const supportsReasoning =
385-
getReasoningEffortOptions(adapter, model) !== null;
386-
387347
await client.runTaskInCloud(task.id, undefined, {
388348
pendingUserMessage,
389-
adapter,
390-
model,
391-
reasoningLevel: supportsReasoning ? reasoning : undefined,
392-
initialPermissionMode: mode,
349+
...buildCloudTaskRunConfig({ adapter, mode, model, reasoning }),
393350
autoPublish: usePreferencesStore.getState().autoPublishCloudRuns,
394351
rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud,
395352
...(signalReport
@@ -618,51 +575,27 @@ export default function NewTaskScreen() {
618575
paddingRight: 16,
619576
}}
620577
>
621-
<Pill
622-
icon={modeIcon(
623-
mode,
624-
mode === "plan"
625-
? themeColors.accent[11]
626-
: themeColors.gray[11],
627-
)}
628-
label={
629-
executionModes.find((option) => option.id === mode)
630-
?.name ?? mode
631-
}
632-
accent={mode === "plan"}
633-
onPress={() => setModeSheetOpen(true)}
578+
<AgentConfigControls
579+
adapter={adapter}
580+
mode={mode}
581+
model={model}
582+
reasoning={reasoning}
583+
configOptions={configOptions}
584+
onAdapterChange={setAdapter}
585+
onModeChange={(next) => {
586+
setMode(next);
587+
usePreferencesStore
588+
.getState()
589+
.setLastNewTaskMode(next);
590+
}}
591+
onModelChange={setModel}
592+
onReasoningChange={(next) => {
593+
setReasoning(next);
594+
usePreferencesStore
595+
.getState()
596+
.setLastUsedReasoningEffort(next);
597+
}}
634598
/>
635-
636-
<Pill
637-
icon={
638-
adapter === "codex" ? (
639-
<Cpu size={14} color={themeColors.gray[11]} />
640-
) : (
641-
<Robot size={14} color={themeColors.gray[11]} />
642-
)
643-
}
644-
label={
645-
getConfigOptionLabel(
646-
modelConfigOption.options,
647-
model,
648-
) ?? model
649-
}
650-
onPress={() => setModelSheetOpen(true)}
651-
/>
652-
653-
{showReasoningPill ? (
654-
<Pill
655-
icon={
656-
<BrainIcon size={14} color={themeColors.gray[11]} />
657-
}
658-
label={
659-
reasoningOptions.find(
660-
(option) => option.value === reasoning,
661-
)?.name ?? reasoning
662-
}
663-
onPress={() => setReasoningSheetOpen(true)}
664-
/>
665-
) : null}
666599
</ScrollView>
667600
{/* Right-edge fade hints that more pills exist when the row
668601
overflows. Non-interactive so taps fall through. */}
@@ -771,98 +704,6 @@ export default function NewTaskScreen() {
771704
</Animated.View>
772705
</View>
773706

774-
<SelectSheet
775-
open={modeSheetOpen}
776-
title="Execution mode"
777-
value={mode}
778-
onChange={(value) => {
779-
const next = value as ExecutionMode;
780-
setMode(next);
781-
usePreferencesStore.getState().setLastNewTaskMode(next);
782-
}}
783-
onClose={() => setModeSheetOpen(false)}
784-
options={executionModes.map((executionMode) => ({
785-
value: executionMode.id,
786-
label: executionMode.name,
787-
description: executionMode.description,
788-
icon: modeIcon(
789-
executionMode.id as ExecutionMode,
790-
executionMode.id === "plan"
791-
? themeColors.accent[11]
792-
: themeColors.gray[11],
793-
16,
794-
),
795-
}))}
796-
/>
797-
798-
<SelectSheet
799-
open={modelSheetOpen}
800-
title="Model"
801-
value={model}
802-
onChange={(value) => {
803-
if (value === SWITCH_ADAPTER_VALUE) {
804-
const nextAdapter: Adapter =
805-
adapter === "claude" ? "codex" : "claude";
806-
setAdapter(nextAdapter);
807-
setMode(getDefaultExecutionModeForAdapter(nextAdapter));
808-
setModel(
809-
nextAdapter === "codex"
810-
? DEFAULT_CODEX_MODEL
811-
: DEFAULT_GATEWAY_MODEL,
812-
);
813-
setReasoning(DEFAULT_REASONING_EFFORT);
814-
return;
815-
}
816-
const next = resolveCloudComposerModelChange({
817-
adapter,
818-
modelOption: modelConfigOption,
819-
requestedModel: value,
820-
reasoning,
821-
});
822-
setModel(next.model);
823-
setReasoning(next.reasoning);
824-
}}
825-
onClose={() => setModelSheetOpen(false)}
826-
options={[
827-
...mobileModelOptions.map((modelOption) => ({
828-
value: modelOption.value,
829-
label: modelOption.label,
830-
description: modelOption.description,
831-
disabled: modelOption.disabled,
832-
icon:
833-
adapter === "codex" ? (
834-
<Cpu size={16} color={themeColors.gray[11]} />
835-
) : (
836-
<Robot size={16} color={themeColors.gray[11]} />
837-
),
838-
})),
839-
{
840-
value: SWITCH_ADAPTER_VALUE,
841-
label: `Switch to ${adapter === "claude" ? "Codex" : "Claude Code"}`,
842-
description: "Change coding agent",
843-
disabled: false,
844-
icon: <Cpu size={16} color={themeColors.accent[11]} />,
845-
},
846-
]}
847-
/>
848-
849-
<SelectSheet
850-
open={reasoningSheetOpen}
851-
title="Reasoning"
852-
value={reasoning}
853-
onChange={(value) => {
854-
const next = value as SupportedReasoningEffort;
855-
setReasoning(next);
856-
usePreferencesStore.getState().setLastUsedReasoningEffort(next);
857-
}}
858-
onClose={() => setReasoningSheetOpen(false)}
859-
options={reasoningOptions.map((reasoningLevel) => ({
860-
value: reasoningLevel.value,
861-
label: reasoningLevel.name,
862-
icon: <BrainIcon size={16} color={themeColors.gray[11]} />,
863-
}))}
864-
/>
865-
866707
<AttachmentSheet
867708
open={attachmentSheetOpen}
868709
onClose={() => setAttachmentSheetOpen(false)}

0 commit comments

Comments
 (0)