Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
16f5e8d
share reasoning dropdown and add ultra tiers
charlesvien Jul 26, 2026
b80efc8
add context window and fast mode to menu
charlesvien Jul 26, 2026
bfd7597
add pr screenshots
charlesvien Jul 26, 2026
6a26a99
remove pr screenshots
charlesvien Jul 26, 2026
be30811
wire context window and fast mode everywhere
charlesvien Jul 27, 2026
1924705
centralize reasoning effort type and labels
charlesvien Jul 27, 2026
b57fd9b
add pr screenshots
charlesvien Jul 27, 2026
47d7f95
remove pr screenshots
charlesvien Jul 27, 2026
d43649e
chore(visual): update storybook baselines
posthog[bot] Jul 27, 2026
4a90e56
use shared dropdown for all effort pickers
charlesvien Jul 27, 2026
25ec9b4
fix codex default badge test
charlesvien Jul 27, 2026
5620f3a
remove ultrathink effort level
charlesvien Jul 27, 2026
d16911e
gate fast mode behind feature flag
charlesvien Jul 27, 2026
0cbb959
merge model and reasoning into slider control
charlesvien Jul 27, 2026
028a29b
make slider drag continuous with snap
charlesvien Jul 27, 2026
c967e1f
reset picks the middle ladder notch
charlesvien Jul 27, 2026
65c426f
stabilize fast toggle and sort model list
charlesvien Jul 27, 2026
2a7de99
add pr screenshots
charlesvien Jul 27, 2026
b792aa8
remove pr screenshots
charlesvien Jul 27, 2026
cb6795d
persist composer selections across restarts
charlesvien Jul 27, 2026
c214aff
sort model submenu with picker order
charlesvien Jul 27, 2026
ef3dd14
add cost hints to slider labels
charlesvien Jul 27, 2026
4dc8e35
open advanced when combo is off the ladder
charlesvien Jul 27, 2026
009f05a
bust saved selections with store version bump
charlesvien Jul 27, 2026
c0be0c1
add fast mode tooltip and docs link
charlesvien Jul 27, 2026
1e0d0ae
reset to harness defaults on switch
charlesvien Jul 27, 2026
6f43cda
stabilize reasoning menu tests
charlesvien Jul 27, 2026
355fd23
add pr screenshots
charlesvien Jul 27, 2026
8825dee
remove pr screenshots
charlesvien Jul 27, 2026
c3ac3e7
move fast mode docs into tooltip
charlesvien Jul 27, 2026
5a8b6cf
default fast mode flag off
charlesvien Jul 27, 2026
d09daf3
theme fast mode tooltip and center it
charlesvien Jul 27, 2026
a270f85
force dark tooltip styling over quill default
charlesvien Jul 27, 2026
8a5e956
escape arrow selector so tooltip arrow hides
charlesvien Jul 27, 2026
bbad850
add pr screenshots
charlesvien Jul 27, 2026
ef9bdbd
remove pr screenshots
charlesvien Jul 27, 2026
16f6f46
fix react doctor warnings
charlesvien Jul 27, 2026
84efd2a
chore(visual): update storybook baselines
posthog[bot] Jul 27, 2026
63d94ba
fix reasoning and context review findings
charlesvien Jul 27, 2026
8fefdbd
add tests for reasoning config plumbing
charlesvien Jul 27, 2026
464f27b
default claude slider to opus 5 medium
charlesvien Jul 27, 2026
4e1d23a
make session config authoritative on reconnect
charlesvien Jul 28, 2026
1a06d38
add ultracode to shared effort policy
charlesvien Jul 30, 2026
7c74725
align loops header with inbox page header
charlesvien Jul 30, 2026
e7f4791
flush zoom timer in artifact preview test
charlesvien Jul 30, 2026
446ccff
keep page header actions from shifting title
charlesvien Jul 30, 2026
1d83ffb
chore(visual): update storybook baselines
posthog[bot] Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions apps/code/snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -537,21 +537,17 @@ snapshots:
git-dialogs--sync-success--light:
hash: v1.k4693efd2.8d79d77c9338aeaa73734836f0e17fde987f6d3239914f4013889d3110e5088d.Rq-w2o1uhes_a5hUWqMXeiDkfnoPCZPt932LkEQXCco
loops-loopslistview--comprehensive--dark:
hash: v1.k4693efd2.c128012b7f910839e65e8eaf14d5456c93f90d77747d0625ca35fddd8d5adf7a.fuugB5Ov6boP7fg6fWP7A7IG2yYnPUAAWLKZLuV8rZ0
hash: v1.k4693efd2.25744a9471ec664c70e346ac5cb330c025a031626bc486abe8820b7b6dbc1a19.Shh8Yl7SdombnNc4-uJjlIbx-n4C0MGXxtOGB1bjJTY
loops-loopslistview--comprehensive--light:
hash: v1.k4693efd2.4d25a48e0bd7e60d3bbb433e246486e4c9ac95979db86e20eb8bf5cb119d1eb2.EzrENjVHwVMyV00pEA-71TeVomJtchiym5DqWFkujlA
hash: v1.k4693efd2.ef8e012cc506a0bf5e761ec8e89943820d4b391f7023f506da7d79586b49a356.ATiN8Y0hUmfGvr0rUytYWX-fITyHRTgeN1i_HOOGZuc
loops-loopslistview--long-mixed-list--dark:
hash: v1.k4693efd2.fdbf0c24cb386c4345279db3e648efe72aa8a2a984d491361522f62074a771c9.2QmRxRMWYFFfBFrbMm0Dm9rWcvd1lfRbc0egfzoESnY
hash: v1.k4693efd2.c9915e22655fe1f6b77eabaf11d8876648c387ceebac6cd8be1880e7255bd7a5.y-gyTKSp7wYbuS57TABRbEyMSL9_U1wvHYvbyCeAbiQ
loops-loopslistview--long-mixed-list--light:
hash: v1.k4693efd2.aa17d9b6f40fbd3914bd742951db9f90add0bd233c54170803c8483aeb45b43b.yXpqqGczOq1uW5k4Ie7ddNTnne-cFTglZ9cL800pXB4
loops-loopslistview--shared-page-header--dark:
hash: v1.k4693efd2.14ab860dcb0df51b4019f012288e45f3a2c4c23fa08e809efef8c04b5687fe36.qoMLHprZmV2wON156NBbl3sqP84a4nXgdYm1H6WpTVM
loops-loopslistview--shared-page-header--light:
hash: v1.k4693efd2.23be5408ff3b4a912a0ce0457af8638124b99a01c88a499d0f4204cee2c62487.dGBP1K08MA2Fvhml-BPl5eFra8U83JXmy04NK9wOQnM
hash: v1.k4693efd2.5fec9e1a3c14f1bc319cd7e5abc24b3d66ed787986a103b5330f49341d4983ad.mpD-eTS6vkSbsJdOYaHXASHQd5IiJGWRCb6uDGlzhpk
loops-loopslistview--with-builder-sessions--dark:
hash: v1.k4693efd2.df4a1a4764e1de7b69905814b09c30d9b43f705f5e66c7ad83829c567d4faa1a.jthufoX_KPbbxScjj3pPBA4lEtT9Ym6zUCH-v1759GM
hash: v1.k4693efd2.06b404e4016ba0eb86769331a574bc640be1ad26ebf3a04d0256334d1da041a3.fPfF8c2wNFREfy8dVRdsw7W9Fpqj7HEtuaERMroNHMA
loops-loopslistview--with-builder-sessions--light:
hash: v1.k4693efd2.eb65c38bb0de7efedbc0d7a6c27a6695528e2e6b60949617e7d22335b1e68c8b.fJr9uykvc8S7LI_ASXk5KRqxXCwIqh6ld9zfzs4shP0
hash: v1.k4693efd2.30f89915a12cadf313216403fb0f3e221f3c33cee763f0692ae2b96b4332e2b9.j00vexnH4QS0wJAHn7pQNpeITYYRtOxlvB2ngyzqU0c
primitives-pageheader--title-only--dark:
hash: v1.k4693efd2.f7f8c560b1e5cf20050189e10fc0f129e555ba672300a7a09bef5827c171a743.yP9Pp_k51agu_9Ri55Y7uCAPBQ1UTeUQJA9U9dPMKfQ
primitives-pageheader--title-only--light:
Expand Down
13 changes: 8 additions & 5 deletions apps/mobile/src/app/settings/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Text } from "@components/text";
import {
EFFORT_LEVEL_LABELS,
EFFORT_LEVELS,
} from "@posthog/shared/domain-types";
import { router } from "expo-router";
import { ArrowSquareOut, CaretRight, SpeakerHigh } from "phosphor-react-native";
import { useState } from "react";
Expand Down Expand Up @@ -103,11 +107,10 @@ const REASONING_EFFORT_OPTIONS: ReadonlyArray<{
label: "Last used",
description: "Remember the effort level you picked last time",
},
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High" },
{ value: "xhigh", label: "Extra High" },
{ value: "max", label: "Max" },
...EFFORT_LEVELS.map((value) => ({
value,
label: EFFORT_LEVEL_LABELS[value],
})),
];

function themeLabel(theme: ThemePreference): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { EffortLevel } from "@posthog/shared/domain-types";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Platform } from "react-native";
import { create } from "zustand";
Expand Down Expand Up @@ -40,13 +41,7 @@ export type CompletionSound =

export type InitialTaskMode = "plan" | "last_used";

export type DefaultReasoningEffort =
| "low"
| "medium"
| "high"
| "xhigh"
| "max"
| "last_used";
export type DefaultReasoningEffort = EffortLevel | "last_used";

interface PreferencesState {
pingsEnabled: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,59 @@ describe("ClaudeAcpAgent session creation", () => {
},
);

// The shared claude settings file is cross-session state: the session's own
// pick must win over it, with the file only as a fallback.
it.each([
{
name: "prefers meta.model over the settings file model",
sessionId: "0197a000-0000-7000-8000-000000000011",
settingsModel: "claude-opus-4-8",
model: "claude-fable-5",
expectedSetModel: "claude-fable-5",
expectedCurrentValue: "claude-fable-5",
},
{
name: "falls back to the settings file model without meta.model",
sessionId: "0197a000-0000-7000-8000-000000000012",
settingsModel: "claude-fable-5",
model: undefined,
expectedSetModel: "claude-fable-5",
expectedCurrentValue: "claude-fable-5",
},
])(
"$name",
async ({
sessionId,
settingsModel,
model,
expectedSetModel,
expectedCurrentValue,
}) => {
const settingsPath = path.join(configDir, "settings.json");
writeFileSync(settingsPath, JSON.stringify({ model: settingsModel }));
try {
const agent = makeAgent();

const response = await agent.resumeSession({
sessionId,
cwd,
mcpServers: [],
_meta: { taskRunId: "run-1", ...(model ? { model } : {}) },
});

expect(createdQueries).toHaveLength(1);
expect(createdQueries[0].setModel).toHaveBeenCalledWith(
expectedSetModel,
);
expect(getModelConfigOption(response)?.currentValue).toBe(
expectedCurrentValue,
);
} finally {
rmSync(settingsPath, { force: true });
}
},
);

// New sessions pass the model to the SDK at spawn, never via setModel. The
// Codex-model row guards the desync that surfaced as "picked gpt-5.5, session
// ran Opus": a non-Anthropic id on the Claude adapter must fall back to the
Expand Down
120 changes: 96 additions & 24 deletions packages/agent/src/adapters/claude/claude-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,12 @@ import {
resolveInitialModelId,
} from "./session/model-config";
import {
CONTEXT_WINDOW_1M_BETA,
CONTEXT_WINDOW_200K_TOKENS,
DEFAULT_EFFORT,
DEFAULT_MODEL,
fastModeStateEnabled,
getContextWindowOptions,
getEffortOptions,
resolveEffortForModel,
resolveModelPreference,
Expand All @@ -131,6 +134,8 @@ import {
buildSystemPrompt,
type GatewayEnv,
type ProcessSpawnedInfo,
toEffortFlagSettings,
toSdkEffort,
} from "./session/options";
import { SettingsManager } from "./session/settings";
import {
Expand Down Expand Up @@ -1752,15 +1757,24 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
this.session.lastContextWindowSize =
this.getContextWindowForModel(resolvedValue);
this.rebuildEffortConfigOption(resolvedValue);
this.rebuildContextWindowConfigOption(resolvedValue);
this.rebuildFastModeConfigOption(resolvedValue);
} else if (params.configId === "effort") {
const newEffort = resolvedValue as EffortLevel;
this.session.effort = newEffort;
this.session.queryOptions.effort = newEffort;
await this.session.query.applyFlagSettings({
// @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it
effortLevel: newEffort,
});
this.session.queryOptions.effort = toSdkEffort(newEffort);
await this.session.query.applyFlagSettings(
toEffortFlagSettings(newEffort),
);
} else if (params.configId === "context_window") {
const enable1M = resolvedValue === "1m";
// queryOptions is read per prompt, so this applies from the next turn.
this.session.queryOptions.betas = enable1M
? [CONTEXT_WINDOW_1M_BETA]
: undefined;
this.session.lastContextWindowSize = enable1M
? this.getContextWindowForModel(this.session.modelId ?? "")
: CONTEXT_WINDOW_200K_TOKENS;
} else if (params.configId === "fast") {
// SDK flag first: a rejected control request leaves state untouched.
const enabled = resolvedValue === "on";
Expand Down Expand Up @@ -1949,8 +1963,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
const settingsManager = new SettingsManager(cwd);
await settingsManager.initialize();

// The session's explicit pick outranks the shared claude settings file:
// that file is cross-session state and must only ever be a fallback.
const earlyModelId =
settingsManager.getSettings().model || meta?.model || "";
meta?.model || settingsManager.getSettings().model || "";

// Register the in-process general local-tools MCP server. Tools self-gate
// via the registry (e.g. signed-commit is cloud-only and needs a GH token),
Expand Down Expand Up @@ -2184,7 +2200,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {

const [rawModelOptions] = await Promise.all([
this.getModelConfigOptions(
settingsManager.getSettings().model || meta?.model || undefined,
meta?.model || settingsManager.getSettings().model || undefined,
this.options?.gatewayEnv?.anthropicBaseUrl,
this.options?.gatewayEnv?.anthropicAuthToken,
),
Expand Down Expand Up @@ -2249,13 +2265,15 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
}

const resolvedModelId = resolveInitialModelId(modelOptions, [
settingsManager.getSettings().model,
meta?.model,
settingsManager.getSettings().model,
]);
modelOptions.currentModelId = resolvedModelId;
session.modelId = resolvedModelId;
session.lastContextWindowSize =
this.getContextWindowForModel(resolvedModelId);
meta?.contextWindow === "200k"
? CONTEXT_WINDOW_200K_TOKENS
: this.getContextWindowForModel(resolvedModelId);

const resolvedSdkModel = toSdkModelId(resolvedModelId);

Expand All @@ -2270,17 +2288,26 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
// Keep thinking enabled by default for effort-capable models (see
// DEFAULT_EFFORT).
const resolvedEffort = resolveEffortForModel(resolvedModelId, effort);
if (resolvedEffort && resolvedEffort !== effort) {
// Ultracode re-applies even when the requested effort stands: the flag
// only reaches the session through applyFlagSettings.
if (
resolvedEffort &&
(resolvedEffort !== effort || resolvedEffort === "ultracode")
) {
this.session.effort = resolvedEffort;
this.session.queryOptions.effort = resolvedEffort;
await this.session.query.applyFlagSettings({
// @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it
effortLevel: resolvedEffort,
});
this.session.queryOptions.effort = toSdkEffort(resolvedEffort);
await this.session.query.applyFlagSettings(
toEffortFlagSettings(resolvedEffort),
);
}

if (supports1MContext(resolvedModelId) && meta?.contextWindow !== "200k") {
options.betas = [CONTEXT_WINDOW_1M_BETA];
}

if (supports1MContext(resolvedModelId)) {
options.betas = ["context-1m-2025-08-07"];
if (meta?.fastMode && supportsFastMode(resolvedModelId)) {
this.session.fastModeEnabled = true;
await this.session.query.applyFlagSettings({ fastMode: true });
}

const availableModes = getAvailableModes();
Expand Down Expand Up @@ -2460,23 +2487,67 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
});
}

const contextOption = this.contextWindowConfigOption(
modelOptions.currentModelId,
);
if (contextOption) {
configOptions.push(contextOption);
}

if (supportsFastMode(modelOptions.currentModelId)) {
configOptions.push(this.fastModeConfigOption(fastModeEnabled ?? false));
}

return configOptions;
}

private contextWindowConfigOption(
modelId: string,
): SessionConfigOption | null {
const contextOptions = getContextWindowOptions(modelId);
if (!contextOptions) return null;
const is1M = this.session.queryOptions.betas?.includes(
CONTEXT_WINDOW_1M_BETA,
);
return {
id: "context_window",
name: "Context Window",
type: "select",
currentValue: is1M ? "1m" : "200k",
options: contextOptions,
category: "_context_window" as SessionConfigOptionCategory,
description: "Choose the context window size for this session",
};
}

private rebuildContextWindowConfigOption(modelId: string): void {
const withoutContext = this.session.configOptions.filter(
(o) => o.id !== "context_window",
);
if (!supports1MContext(modelId)) {
this.session.queryOptions.betas = undefined;
this.session.configOptions = withoutContext;
return;
}
// Switching onto a 1M-capable model restores the default 1M window.
this.session.queryOptions.betas = [CONTEXT_WINDOW_1M_BETA];
const contextOption = this.contextWindowConfigOption(modelId);
this.session.configOptions = contextOption
? [...withoutContext, contextOption]
: withoutContext;
}

private fastModeConfigOption(enabled: boolean): SessionConfigOption {
return {
id: "fast",
name: "Fast mode",
name: "Fast Mode",
type: "select",
currentValue: enabled ? "on" : "off",
options: [
{ value: "on", name: "On" },
{ value: "off", name: "Off" },
],
category: "_fast_mode" as SessionConfigOptionCategory,
description: "Faster responses on supported models",
};
}
Expand Down Expand Up @@ -2564,6 +2635,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
this.session.queryOptions.effort = undefined;
void this.session.query.applyFlagSettings({
effortLevel: undefined,
ultracode: false,
});
}
return;
Expand All @@ -2578,12 +2650,12 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
// Set the default when none is chosen yet (see DEFAULT_EFFORT), or re-apply
// when the prior level is invalid for the newly selected model.
if (!this.session.effort || resolvedValue !== currentValue) {
this.session.effort = resolvedValue as EffortLevel;
this.session.queryOptions.effort = resolvedValue as EffortLevel;
void this.session.query.applyFlagSettings({
// @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it
effortLevel: resolvedValue,
});
const resolvedEffort = resolvedValue as EffortLevel;
this.session.effort = resolvedEffort;
this.session.queryOptions.effort = toSdkEffort(resolvedEffort);
void this.session.query.applyFlagSettings(
toEffortFlagSettings(resolvedEffort),
);
}

const effortConfig: SessionConfigOption = {
Expand Down
Loading
Loading