authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-07-21 17:13:44-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-21 17:13:44-07:00
log62cf4617594c2a0d8c9c5dc4bbc2012d0ead802d
tree1bafc0486a2dd7fbaebe765f4783bd9a4e935aa7
parent23c18fda7a969634a30888e36b2da45f6d66a83b
signature Signed by PGP key B5690EEEBB952194

fix: better defaults — Claude 1M context, Codex gpt-5.6, worktrees from origin main (#4240)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

17 files changed, 218 insertions(+), 50 deletions(-)

apps/mobile/src/features/threads/new-task-flow-provider.tsx+29-10
......@@ -58,6 +58,8 @@ import { type VcsRef } from "@t3tools/client-runtime/state/vcs";
5858
5959type WorkspaceMode = "local" | "worktree";
6060
61const EMPTY_BRANCH_REFS: ReadonlyArray<VcsRef> = [];
62
6163function pendingTaskDraftKey(messageId: string): string {
6264 return `pending-task:${messageId}`;
6365}
......@@ -348,7 +350,14 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
348350 const workspaceMode = selectedProjectDraft.workspaceSelection?.mode ?? "local";
349351 const selectedBranchName = selectedProjectDraft.workspaceSelection?.branch ?? null;
350352 const selectedWorktreePath = selectedProjectDraft.workspaceSelection?.worktreePath ?? null;
351 const startFromOrigin = selectedProjectDraft.workspaceSelection?.startFromOrigin ?? false;
353 // Keep the user's explicit choice separate from the resolved display value:
354 // only the explicit flag is ever written back to the draft, so the resolved
355 // value keeps tracking the server setting when the config loads late.
356 const draftStartFromOrigin = selectedProjectDraft.workspaceSelection?.startFromOrigin;
357 const startFromOrigin =
358 draftStartFromOrigin ??
359 selectedEnvironmentServerConfig?.settings.newWorktreesStartFromOrigin ??
360 true;
352361 const runtimeMode = selectedProjectDraft.runtimeMode ?? DEFAULT_RUNTIME_MODE;
353362 const interactionMode = selectedProjectDraft.interactionMode ?? DEFAULT_PROVIDER_INTERACTION_MODE;
354363
......@@ -368,6 +377,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
368377 const selectedModel =
369378 selectedProjectDraft.modelSelection ??
370379 selectedProject?.defaultModelSelection ??
380 modelOptions.find((option) => option.isDefault)?.selection ??
371381 modelOptions[0]?.selection ??
372382 null;
373383 const selectedModelKey = selectedModel
......@@ -475,13 +485,14 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
475485 );
476486 const branchState = useBranches(branchTarget);
477487 const branchesLoading = branchState.isPending;
488 const allBranchRefs = branchState.data?.refs ?? EMPTY_BRANCH_REFS;
478489 const availableBranches = useMemo(
479490 () =>
480491 pipe(
481 branchState.data?.refs ?? [],
492 allBranchRefs,
482493 Arr.filter((branch) => !branch.isRemote),
483494 ),
484 [branchState.data?.refs],
495 [allBranchRefs],
485496 );
486497
487498 const filteredBranches = useMemo(() => {
......@@ -543,11 +554,11 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
543554 mode,
544555 branch: selectedBranchName,
545556 worktreePath: selectedWorktreePath,
546 startFromOrigin,
557 ...(draftStartFromOrigin !== undefined ? { startFromOrigin: draftStartFromOrigin } : {}),
547558 },
548559 });
549560 },
550 [selectedBranchName, selectedProjectDraftKey, selectedWorktreePath, startFromOrigin],
561 [draftStartFromOrigin, selectedBranchName, selectedProjectDraftKey, selectedWorktreePath],
551562 );
552563
553564 const selectBranch = useCallback(
......@@ -560,11 +571,11 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
560571 mode: workspaceMode,
561572 branch: branch.name,
562573 worktreePath: normalizeSelectedWorktreePath(selectedProject, branch),
563 startFromOrigin,
574 ...(draftStartFromOrigin !== undefined ? { startFromOrigin: draftStartFromOrigin } : {}),
564575 },
565576 });
566577 },
567 [selectedProject, selectedProjectDraftKey, startFromOrigin, workspaceMode],
578 [draftStartFromOrigin, selectedProject, selectedProjectDraftKey, workspaceMode],
568579 );
569580
570581 const setStartFromOrigin = useCallback(
......@@ -597,14 +608,16 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
597608 if (workspaceMode !== "worktree" || selectedBranchName !== null) {
598609 return;
599610 }
611 // The default may only exist as origin/<default> (isRemote), which
612 // availableBranches filters out — search the unfiltered refs for it.
600613 const preferredBranch =
614 allBranchRefs.find((branch) => branch.isDefault) ??
601615 availableBranches.find((branch) => branch.current) ??
602 availableBranches.find((branch) => branch.isDefault) ??
603616 null;
604617 if (preferredBranch) {
605618 selectBranch(preferredBranch);
606619 }
607 }, [availableBranches, selectBranch, selectedBranchName, workspaceMode]);
620 }, [allBranchRefs, availableBranches, selectBranch, selectedBranchName, workspaceMode]);
608621
609622 const setRuntimeMode = useCallback(
610623 (value: RuntimeMode) => {
......@@ -696,7 +709,12 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
696709 workspaceMode: mode,
697710 branch: workspaceSelection?.branch ?? null,
698711 worktreePath: mode === "worktree" ? null : (workspaceSelection?.worktreePath ?? null),
699 ...(workspaceSelection?.startFromOrigin ? { startFromOrigin: true } : {}),
712 // The draft only carries the flag when the user touched it; fall
713 // back to the resolved default (server settings) so queued tasks
714 // drain with the same origin mode the composer displayed.
715 ...((workspaceSelection?.startFromOrigin ?? startFromOrigin)
716 ? { startFromOrigin: true }
717 : {}),
700718 },
701719 createdAt: metadata.createdAt,
702720 };
......@@ -707,6 +725,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
707725 selectedModel,
708726 selectedProject,
709727 selectedProjectDraftKey,
728 startFromOrigin,
710729 ],
711730 );
712731
apps/mobile/src/lib/modelOptions.ts+3
......@@ -15,6 +15,7 @@ export type ModelOption = {
1515 readonly providerKey: string;
1616 readonly providerLabel: string;
1717 readonly providerDriver: string;
18 readonly isDefault: boolean;
1819 readonly capabilities: ModelCapabilities | null;
1920 readonly selection: ModelSelection;
2021};
......@@ -78,6 +79,7 @@ export function buildModelOptions(
7879 providerKey: provider.instanceId,
7980 providerLabel,
8081 providerDriver: provider.driver,
82 isDefault: model.isDefault === true,
8183 capabilities: model.capabilities,
8284 selection: normalizeSelectionOptions(
8385 {
......@@ -107,6 +109,7 @@ export function buildModelOptions(
107109 providerKey: fallbackModelSelection.instanceId,
108110 providerLabel,
109111 providerDriver: fallbackModelSelection.instanceId,
112 isDefault: false,
110113 capabilities: null,
111114 selection: fallbackModelSelection,
112115 });
apps/server/src/provider/Layers/ClaudeAdapter.test.ts+6-5
......@@ -3045,7 +3045,7 @@ describe("ClaudeAdapterLive", () => {
30453045 attachments: [],
30463046 });
30473047
3048 assert.deepEqual(harness.query.setModelCalls, ["claude-opus-4-6"]);
3048 assert.deepEqual(harness.query.setModelCalls, ["claude-opus-4-6[1m]"]);
30493049 }).pipe(
30503050 Effect.provideService(Random.Random, makeDeterministicRandomService()),
30513051 Effect.provide(harness.layer),
......@@ -3143,10 +3143,11 @@ describe("ClaudeAdapterLive", () => {
31433143 yield* adapter.sendTurn({
31443144 threadId: session.threadId,
31453145 input: "hello again",
3146 modelSelection: {
3147 instanceId: ProviderInstanceId.make("claudeAgent"),
3148 model: "claude-opus-4-6",
3149 },
3146 modelSelection: createModelSelection(
3147 ProviderInstanceId.make("claudeAgent"),
3148 "claude-opus-4-6",
3149 [{ id: "contextWindow", value: "200k" }],
3150 ),
31503151 attachments: [],
31513152 });
31523153
apps/server/src/provider/Layers/ClaudeAdapter.ts+9-14
......@@ -77,6 +77,7 @@ import {
7777 isClaudeUltracodeEffort,
7878 normalizeClaudeCliEffort,
7979 resolveClaudeApiModelId,
80 resolveClaudeContextWindow,
8081 resolveClaudeEffort,
8182} from "./ClaudeProvider.ts";
8283import {
......@@ -341,24 +342,18 @@ function selectedClaudeContextWindow(
341342 switch (modelSelection?.model) {
342343 case "claude-opus-4-8":
343344 case "claude-opus-4-7":
345 // Always 1M at the API; these models expose no contextWindow option.
344346 return 1_000_000;
345347 }
346348
347 const optionValue = getModelSelectionStringOptionValue(modelSelection, "contextWindow");
348 if (optionValue === "1m") {
349 return 1_000_000;
350 }
351 if (optionValue === "200k") {
352 return 200_000;
353 }
354 const caps = getClaudeModelCapabilities(modelSelection?.model);
355 const hasContextWindowOption = getProviderOptionDescriptors({ caps }).some(
356 (descriptor) => descriptor.type === "select" && descriptor.id === "contextWindow",
357 );
358 if (hasContextWindowOption) {
359 return 200_000;
349 switch (resolveClaudeContextWindow(modelSelection)) {
350 case "1m":
351 return 1_000_000;
352 case "200k":
353 return 200_000;
354 default:
355 return undefined;
360356 }
361 return undefined;
362357}
363358
364359function finiteNonNegativeInteger(value: unknown): number | undefined {
apps/server/src/provider/Layers/ClaudeProvider.ts+21-5
......@@ -76,8 +76,8 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
7676 id: "contextWindow",
7777 label: "Context Window",
7878 options: [
79 { value: "200k", label: "200k", isDefault: true },
80 { value: "1m", label: "1M" },
79 { value: "200k", label: "200k" },
80 { value: "1m", label: "1M", isDefault: true },
8181 ],
8282 }),
8383 ],
......@@ -162,8 +162,8 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
162162 id: "contextWindow",
163163 label: "Context Window",
164164 options: [
165 { value: "200k", label: "200k", isDefault: true },
166 { value: "1m", label: "1M" },
165 { value: "200k", label: "200k" },
166 { value: "1m", label: "1M", isDefault: true },
167167 ],
168168 }),
169169 ],
......@@ -214,6 +214,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
214214 buildSelectOptionDescriptor({
215215 id: "contextWindow",
216216 label: "Context Window",
217 // Sonnet is 200k-default in Claude Code (1M is opt-in there too).
217218 options: [
218219 { value: "200k", label: "200k", isDefault: true },
219220 { value: "1m", label: "1M" },
......@@ -243,6 +244,7 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
243244 buildSelectOptionDescriptor({
244245 id: "contextWindow",
245246 label: "Context Window",
247 // Sonnet is 200k-default in Claude Code (1M is opt-in there too).
246248 options: [
247249 { value: "200k", label: "200k", isDefault: true },
248250 { value: "1m", label: "1M" },
......@@ -369,8 +371,22 @@ export function isClaudeUltracodeEffort(effort: string | null | undefined): bool
369371 return effort === "ultracode";
370372}
371373
374export function resolveClaudeContextWindow(
375 modelSelection: ModelSelection | undefined,
376): string | undefined {
377 const caps = getClaudeModelCapabilities(modelSelection?.model);
378 const raw = getModelSelectionStringOptionValue(modelSelection, "contextWindow");
379 const descriptors = getProviderOptionDescriptors({
380 caps,
381 ...(raw ? { selections: [{ id: "contextWindow", value: raw }] } : {}),
382 });
383 const descriptor = descriptors.find((candidate) => candidate.id === "contextWindow");
384 const value = getProviderOptionCurrentValue(descriptor);
385 return typeof value === "string" ? value : undefined;
386}
387
372388export function resolveClaudeApiModelId(modelSelection: ModelSelection): string {
373 switch (getModelSelectionStringOptionValue(modelSelection, "contextWindow")) {
389 switch (resolveClaudeContextWindow(modelSelection)) {
374390 case "1m":
375391 return `${modelSelection.model}[1m]`;
376392 default:
apps/server/src/provider/Layers/CodexProvider.test.ts+43-1
......@@ -1,6 +1,6 @@
11import { assert, it } from "@effect/vitest";
22
3import { mapCodexModelCapabilities } from "./CodexProvider.ts";
3import { applyPreferredCodexDefaultModel, mapCodexModelCapabilities } from "./CodexProvider.ts";
44
55it("maps current Codex model capability fields", () => {
66 const capabilities = mapCodexModelCapabilities({
......@@ -102,3 +102,45 @@ it("uses standard routing when the catalog has no default service tier", () => {
102102 },
103103 ]);
104104});
105
106it("marks the most preferred available model as default", () => {
107 const models = applyPreferredCodexDefaultModel([
108 { slug: "gpt-5.6-terra", name: "GPT-5.6-Terra", isCustom: false, capabilities: null },
109 { slug: "gpt-5.4", name: "GPT-5.4", isCustom: false, isDefault: true, capabilities: null },
110 ]);
111
112 assert.deepStrictEqual(
113 models.map((model) => ({ slug: model.slug, isDefault: model.isDefault })),
114 [
115 { slug: "gpt-5.6-terra", isDefault: true },
116 { slug: "gpt-5.4", isDefault: undefined },
117 ],
118 );
119});
120
121it("prefers sol over terra when both are available", () => {
122 const models = applyPreferredCodexDefaultModel([
123 { slug: "gpt-5.6-terra", name: "GPT-5.6-Terra", isCustom: false, capabilities: null },
124 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
125 ]);
126
127 assert.deepStrictEqual(models.find((model) => model.isDefault)?.slug, "gpt-5.6-sol");
128});
129
130it("keeps Codex's own default when no preferred model is available", () => {
131 const models = applyPreferredCodexDefaultModel([
132 { slug: "gpt-5.5", name: "GPT-5.5", isCustom: false, capabilities: null },
133 { slug: "gpt-5.4", name: "GPT-5.4", isCustom: false, isDefault: true, capabilities: null },
134 ]);
135
136 assert.deepStrictEqual(models.find((model) => model.isDefault)?.slug, "gpt-5.4");
137});
138
139it("ignores custom models that shadow a preferred slug", () => {
140 const models = applyPreferredCodexDefaultModel([
141 { slug: "gpt-5.6-sol", name: "gpt-5.6-sol", isCustom: true, capabilities: null },
142 { slug: "gpt-5.4", name: "GPT-5.4", isCustom: false, isDefault: true, capabilities: null },
143 ]);
144
145 assert.deepStrictEqual(models.find((model) => model.isDefault)?.slug, "gpt-5.4");
146});
apps/server/src/provider/Layers/CodexProvider.ts+30-2
......@@ -22,7 +22,7 @@ import type {
2222 ServerProviderModel,
2323 ServerProviderSkill,
2424} from "@t3tools/contracts";
25import { ServerSettingsError } from "@t3tools/contracts";
25import { PREFERRED_DEFAULT_CODEX_MODELS, ServerSettingsError } from "@t3tools/contracts";
2626
2727import { createModelCapabilities } from "@t3tools/shared/model";
2828import { resolveSpawnCommand } from "@t3tools/shared/shell";
......@@ -189,10 +189,36 @@ function parseCodexModelListResponse(
189189 slug: model.model,
190190 name: toDisplayName(model),
191191 isCustom: false,
192 ...(model.isDefault ? { isDefault: true } : {}),
192193 capabilities: mapCodexModelCapabilities(model),
193194 }));
194195}
195196
197/**
198 * Prefer our own default-model ranking when one of the preferred slugs is in
199 * the live catalog; otherwise keep whatever Codex itself flagged as default.
200 */
201export function applyPreferredCodexDefaultModel(
202 models: ReadonlyArray<ServerProviderModel>,
203): ReadonlyArray<ServerProviderModel> {
204 const preferredSlug = PREFERRED_DEFAULT_CODEX_MODELS.find((slug) =>
205 models.some((model) => model.slug === slug && !model.isCustom),
206 );
207 if (!preferredSlug) {
208 return models;
209 }
210 return models.map((model) => {
211 if (model.slug === preferredSlug) {
212 return model.isDefault ? model : { ...model, isDefault: true };
213 }
214 if (!model.isDefault) {
215 return model;
216 }
217 const { isDefault: _isDefault, ...rest } = model;
218 return rest;
219 });
220}
221
196222function appendCustomCodexModels(
197223 models: ReadonlyArray<ServerProviderModel>,
198224 customModels: ReadonlyArray<string>,
......@@ -376,7 +402,9 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
376402 return {
377403 account: accountResponse,
378404 version,
379 models: appendCustomCodexModels(models, input.customModels ?? []),
405 models: applyPreferredCodexDefaultModel(
406 appendCustomCodexModels(models, input.customModels ?? []),
407 ),
380408 skills: parseCodexSkillsListResponse(skillsResponse, input.cwd),
381409 } satisfies CodexAppServerProviderSnapshot;
382410});
apps/server/src/vcs/GitVcsDriverCore.test.ts+20
......@@ -596,6 +596,26 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
596596 }),
597597 );
598598
599 it.effect("marks the origin default ref as default when no local copy exists", () =>
600 Effect.gen(function* () {
601 const cwd = yield* makeTmpDir();
602 const remote = yield* makeTmpDir("git-vcs-driver-remote-");
603 const { initialBranch } = yield* initRepoWithCommit(cwd);
604 yield* git(remote, ["init", "--bare"]);
605 yield* git(cwd, ["remote", "add", "origin", remote]);
606 yield* git(cwd, ["push", "-u", "origin", initialBranch]);
607 yield* git(cwd, ["remote", "set-head", "origin", initialBranch]);
608 yield* git(cwd, ["checkout", "-b", "feature/only-local"]);
609 yield* git(cwd, ["branch", "-D", initialBranch]);
610 const driver = yield* GitVcsDriver.GitVcsDriver;
611
612 const refs = yield* driver.listRefs({ cwd });
613 const remoteDefault = refs.refs.find((ref) => ref.name === `origin/${initialBranch}`);
614 assert.equal(remoteDefault?.isRemote, true);
615 assert.equal(remoteDefault?.isDefault, true);
616 }),
617 );
618
599619 it.effect("creates, checks out, renames, and lists refs", () =>
600620 Effect.gen(function* () {
601621 const cwd = yield* makeTmpDir();
apps/server/src/vcs/GitVcsDriverCore.ts+14-2
......@@ -2217,7 +2217,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
22172217 name: refName.name,
22182218 current: false,
22192219 isRemote: true,
2220 isDefault: false,
2220 // origin/HEAD's target is the repo default even when no local
2221 // copy of the default branch exists.
2222 isDefault:
2223 defaultBranch !== null &&
2224 parsedRemoteRef?.remoteName === "origin" &&
2225 parsedRemoteRef.branchName === defaultBranch,
22212226 worktreePath: null,
22222227 };
22232228 if (parsedRemoteRef) {
......@@ -2232,9 +2237,16 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
22322237 })
22332238 : [];
22342239
2235 const allBranches = input.includeMatchingRemoteRefs
2240 const combinedBranches = input.includeMatchingRemoteRefs
22362241 ? [...localBranches, ...remoteBranches]
22372242 : dedupeRemoteBranchesWithLocalMatches([...localBranches, ...remoteBranches]);
2243 // Keep current/default refs on the first page even when the default
2244 // only exists as origin/<default> (remote refs sort after all locals).
2245 const allBranches = combinedBranches.toSorted((a, b) => {
2246 const aPriority = a.current ? 0 : a.isDefault ? 1 : 2;
2247 const bPriority = b.current ? 0 : b.isDefault ? 1 : 2;
2248 return aPriority - bPriority;
2249 });
22382250 const branchesForKind =
22392251 input.refKind === "local"
22402252 ? allBranches.filter((ref) => !ref.isRemote)
apps/web/src/components/BranchToolbarBranchSelector.tsx+18-3
......@@ -422,17 +422,32 @@ export function BranchToolbarBranchSelector({
422422 });
423423 };
424424
425 // Default the worktree base to the repo default branch (origin/HEAD), only
426 // falling back to the checked-out branch when no default is known.
427 const defaultBranchName = useMemo(
428 () => refs.find((refName) => refName.isDefault)?.name ?? null,
429 [refs],
430 );
431 const worktreeBaseBranchCandidate = isInitialBranchesLoadPending
432 ? null
433 : (defaultBranchName ?? currentGitBranch);
425434 useEffect(() => {
426435 if (
427436 effectiveEnvMode !== "worktree" ||
428437 activeWorktreePath ||
429438 activeThreadBranch ||
430 !currentGitBranch
439 !worktreeBaseBranchCandidate
431440 ) {
432441 return;
433442 }
434 setThreadBranch(currentGitBranch, null);
435 }, [activeThreadBranch, activeWorktreePath, currentGitBranch, effectiveEnvMode, setThreadBranch]);
443 setThreadBranch(worktreeBaseBranchCandidate, null);
444 }, [
445 activeThreadBranch,
446 activeWorktreePath,
447 effectiveEnvMode,
448 setThreadBranch,
449 worktreeBaseBranchCandidate,
450 ]);
436451
437452 // ---------------------------------------------------------------------------
438453 // Combobox / list plumbing
apps/web/src/modelSelection.ts+4
......@@ -75,6 +75,7 @@ export interface AppModelOption {
7575 shortName?: string;
7676 subProvider?: string;
7777 isCustom: boolean;
78 isDefault?: boolean;
7879}
7980
8081function toAppModelOption(model: ServerProvider["models"][number]): AppModelOption {
......@@ -85,6 +86,7 @@ function toAppModelOption(model: ServerProvider["models"][number]): AppModelOpti
8586 };
8687 if (model.shortName) option.shortName = model.shortName;
8788 if (model.subProvider) option.subProvider = model.subProvider;
89 if (model.isDefault) option.isDefault = true;
8890 return option;
8991}
9092
......@@ -247,7 +249,9 @@ export function resolveAppModelSelectionForInstance(
247249 const options = getAppModelOptionsForInstance(settings, entry);
248250 return (
249251 resolveSelectableModel(entry.driverKind, selectedModel, options) ??
252 options.find((option) => option.isDefault)?.slug ??
250253 options[0]?.slug ??
254 entry.models.find((model) => model.isDefault)?.slug ??
251255 entry.models[0]?.slug ??
252256 null
253257 );
apps/web/src/providerModels.ts+1
......@@ -92,6 +92,7 @@ export function getDefaultServerModel(
9292): string {
9393 const models = getProviderModels(providers, provider);
9494 return (
95 models.find((model) => model.isDefault && !model.isCustom)?.slug ??
9596 models.find((model) => !model.isCustom)?.slug ??
9697 models[0]?.slug ??
9798 DEFAULT_MODEL_BY_PROVIDER[provider] ??
packages/client-runtime/src/operations/projects.test.ts+2-1
......@@ -1,5 +1,6 @@
11import { describe, expect, it } from "vite-plus/test";
22import {
3 DEFAULT_MODEL,
34 EnvironmentId,
45 ProjectId,
56 CommandId,
......@@ -139,7 +140,7 @@ describe("add project shared logic", () => {
139140 createWorkspaceRootIfMissing: true,
140141 defaultModelSelection: {
141142 instanceId: "codex",
142 model: "gpt-5.4",
143 model: DEFAULT_MODEL,
143144 },
144145 });
145146 });
packages/contracts/src/model.ts+12-2
......@@ -133,8 +133,18 @@ const CURSOR_DRIVER_KIND = ProviderDriverKind.make("cursor");
133133const GROK_DRIVER_KIND = ProviderDriverKind.make("grok");
134134const OPENCODE_DRIVER_KIND = ProviderDriverKind.make("opencode");
135135
136export const DEFAULT_MODEL = "gpt-5.4";
137export const DEFAULT_GIT_TEXT_GENERATION_MODEL = "gpt-5.4-mini";
136export const DEFAULT_MODEL = "gpt-5.6-sol";
137
138/**
139 * Codex default-model preference, most preferred first. The provider snapshot
140 * marks the first of these present in the live `model/list` response as
141 * default; when none are available, Codex's own `isDefault` flag wins.
142 */
143export const PREFERRED_DEFAULT_CODEX_MODELS: ReadonlyArray<string> = [
144 "gpt-5.6-sol",
145 "gpt-5.6-terra",
146];
147export const DEFAULT_GIT_TEXT_GENERATION_MODEL = "gpt-5.6-luna";
138148
139149export const DEFAULT_MODEL_BY_PROVIDER: Partial<Record<ProviderDriverKind, string>> = {
140150 [CODEX_DRIVER_KIND]: DEFAULT_MODEL,
packages/contracts/src/server.ts+1
......@@ -64,6 +64,7 @@ export const ServerProviderModel = Schema.Struct({
6464 shortName: Schema.optional(TrimmedNonEmptyString),
6565 subProvider: Schema.optional(TrimmedNonEmptyString),
6666 isCustom: Schema.Boolean,
67 isDefault: Schema.optional(Schema.Boolean),
6768 capabilities: Schema.NullOr(ModelCapabilities),
6869});
6970export type ServerProviderModel = typeof ServerProviderModel.Type;
packages/contracts/src/settings.test.ts+4-4
......@@ -88,14 +88,14 @@ describe("ServerSettings.providerInstances (slice-2 invariant)", () => {
8888});
8989
9090describe("ServerSettings worktree defaults", () => {
91 it("defaults start-from-origin off for legacy configs", () => {
92 expect(decodeServerSettings({}).newWorktreesStartFromOrigin).toBe(false);
91 it("defaults start-from-origin on for legacy configs", () => {
92 expect(decodeServerSettings({}).newWorktreesStartFromOrigin).toBe(true);
9393 });
9494
9595 it("accepts start-from-origin updates", () => {
9696 expect(
97 decodeServerSettingsPatch({ newWorktreesStartFromOrigin: true }).newWorktreesStartFromOrigin,
98 ).toBe(true);
97 decodeServerSettingsPatch({ newWorktreesStartFromOrigin: false }).newWorktreesStartFromOrigin,
98 ).toBe(false);
9999 });
100100});
101101
packages/contracts/src/settings.ts+1-1
......@@ -382,7 +382,7 @@ export const ServerSettings = Schema.Struct({
382382 Schema.withDecodingDefault(Effect.succeed("local" as const satisfies ThreadEnvMode)),
383383 ),
384384 newWorktreesStartFromOrigin: Schema.Boolean.pipe(
385 Schema.withDecodingDefault(Effect.succeed(false)),
385 Schema.withDecodingDefault(Effect.succeed(true)),
386386 ),
387387 addProjectBaseDirectory: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
388388 textGenerationModelSelection: ModelSelection.pipe(