Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3f9daa8
feat(settings): providers can be turned on or off per project
yordis Oct 10, 2026
24d7802
fix(settings): project provider switches hold on every launch and pic…
yordis Oct 11, 2026
43f1802
fix(settings): project provider disablement holds through antigravity…
yordis Oct 11, 2026
143e3fc
fix(mobile): a project's antigravity opt-out holds before provider st…
yordis Oct 11, 2026
06721e1
fix(settings): project opt-ins keep their writer model and task draft…
yordis Oct 11, 2026
1b840d7
fix(server): retries of accepted commands replay even after a project…
yordis Oct 11, 2026
d66561c
fix(settings): text generation leaves a provider the project turned off
yordis Oct 11, 2026
f6dd4bd
fix(settings): text generation finds a configured provider when every…
yordis Oct 11, 2026
4e89219
fix(server): agent-started child threads respect a project's provider…
yordis Oct 11, 2026
43f1322
fix(server): driver-only delegation skips instances the project turne…
juliusmarminge Oct 11, 2026
3fcad58
fix(web): automatic routing respects per-project provider switches
juliusmarminge Oct 11, 2026
e4360cd
fix(mobile): sending honors the project's provider choices
yordis Oct 11, 2026
6391b85
fix(web): a project's provider choice applies to all its checkouts
yordis Oct 11, 2026
ea184dd
fix(mobile): a project-disabled model points to project settings, not…
yordis Oct 11, 2026
9a404e8
fix(mobile): sending a model the project turned off restores the draft
yordis Oct 11, 2026
7687488
refactor(settings): project provider switches use the settings.json s…
yordis Oct 11, 2026
af9b5c5
refactor(web): the provider switch reads the project value with a pla…
yordis Oct 11, 2026
86d5eb8
fix(server): launch gates provider instances through the thread manag…
yordis Oct 11, 2026
25371cc
fix(server): a retried driver-only delegation replays after the proje…
yordis Oct 11, 2026
c32428e
feat(web): the provider switch shows at a glance whether the project …
yordis Oct 11, 2026
0299975
fix(shared): machine-level text generation fallback ignores configure…
yordis Oct 11, 2026
9151323
fix(settings): a project's providerInstances override survives an old…
yordis Oct 11, 2026
fc501eb
fix(server): a deleted project's instance overrides can no longer out…
yordis Oct 11, 2026
e90c49e
fix(web,mobile,server): require projectId where provider enablement i…
yordis Oct 11, 2026
2639638
fix(mobile): point the disabled-model refusal at web or desktop
yordis Oct 11, 2026
a1df628
fix(server): an off provider instance gates every internal wake, not …
yordis Oct 11, 2026
e6f5ee5
fix(server): a refused delegated-completion wake is acknowledged, not…
yordis Oct 11, 2026
f5e6f0b
docs: state what a project's provider switch means for queued and in-…
yordis Oct 11, 2026
2bffa2a
fix(mobile): pickers keep hiding providers the machine turned off whe…
yordis Oct 11, 2026
3bc3b05
test(web): provider settings routing tests mock the server configs th…
yordis Oct 11, 2026
615e65a
fix(server): a refused continuation wake still clears the adapter's s…
yordis Oct 11, 2026
c6696e8
fix(server): queue.resume no longer starts a queued run on a project-…
yordis Oct 11, 2026
f7ddbce
fix(mobile): a project with no override no longer inherits another pr…
yordis Oct 11, 2026
3d03fb5
style(server): drop an em dash from the override sweep header
yordis Oct 11, 2026
fb98811
fix(server): a refused continuation still clears offers that cannot s…
yordis Oct 11, 2026
e17b8e0
fix(server): queued runs only hold for a provider that was explicitly…
yordis Oct 11, 2026
8ea8d0d
fix(mobile): a model inherited as disabled now takes the same recover…
yordis Oct 11, 2026
d2cafa2
fix(mobile,server): restore merge-introduced project threading lost i…
juliusmarminge Oct 11, 2026
5b257f4
fix(settings): review guides skip providers a project turned off
yordis Oct 11, 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
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ export function ScheduledTaskModelPickerRouteScreen() {
const { editor, setEditor } = useScheduledTaskEditor();
const config = useEnvironmentServerConfig(editor?.environmentId ?? null);
const selectedModel = editor?.draft.modelSelection ?? null;
const models = useMemo(() => buildModelOptions(config, selectedModel), [config, selectedModel]);
const projectId = editor?.draft.projectId ?? null;
const models = useMemo(
() => buildModelOptions(config, selectedModel, undefined, projectId),
Comment thread
yordis marked this conversation as resolved.
[config, selectedModel, projectId],
);
const providerGroups = useMemo(() => groupByProvider(models), [models]);
const selectedOption = models.find(
(option) =>
Expand All @@ -46,6 +50,7 @@ export function ScheduledTaskModelPickerRouteScreen() {
return (
<ThreadSettingsPickerScreen
environmentId={editor.environmentId}
projectId={projectId}
providerGroups={providerGroups}
selectedModel={selectedModel}
onSelectModel={(option) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { useAtomValue } from "@effect/atom-react";
import { AuthOrchestrationOperateScope } from "@t3tools/contracts";
import {
AuthOrchestrationOperateScope,
resolveProjectProviderInstanceEnabled,
} from "@t3tools/contracts";
import { readEnvironmentScope } from "../../state/session";
import type {
EnvironmentId,
Expand Down Expand Up @@ -582,7 +585,10 @@ function TaskForm({
);
const projects = useProjects().filter((project) => project.environmentId === environmentId);
const config = useEnvironmentServerConfig(environmentId);
const modelOptions = useMemo(() => buildModelOptions(config, null), [config]);
const modelOptions = useMemo(
() => buildModelOptions(config, null, undefined, draft.projectId),
[config, draft.projectId],
);
const canOperate = useAtomValue(
serverEnvironment.upsertScheduledTask.permissionAtom(environmentId),
);
Expand Down Expand Up @@ -757,14 +763,24 @@ function TaskForm({
}))}
onSelect={(id) => {
const project = projects.find((item) => item.id === id);
if (project)
setDraft({
...draft,
projectId: project.id,
modelSelection: draft.modelSelectionIsExplicit
? draft.modelSelection
: scheduledTaskDefaultModel(config, project),
});
if (!project) return;
const keepsExplicitModel =
draft.modelSelectionIsExplicit &&
(draft.modelSelection === null ||
!config?.settings ||
resolveProjectProviderInstanceEnabled(
config.settings,
project.id,
draft.modelSelection.instanceId,
));
setDraft({
...draft,
projectId: project.id,
modelSelection: keepsExplicitModel
? draft.modelSelection
: scheduledTaskDefaultModel(config, project),
modelSelectionIsExplicit: keepsExplicitModel,
});
}}
/>
<PickerRow
Expand Down
7 changes: 7 additions & 0 deletions apps/mobile/src/features/settings/scheduledTaskDraft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vite-plus/test";
import {
DEFAULT_SERVER_SETTINGS,
type ServerConfig,
ProviderDriverKind,
ProviderInstanceId,
ProjectId,
ScheduledTaskId,
Expand Down Expand Up @@ -339,6 +340,12 @@ describe("scheduled task model defaults", () => {
resolve(
{
...config,
settings: {
...config.settings,
providerInstances: {
[instanceId]: { driver: ProviderDriverKind.make("codex"), enabled: false },
},
},
providers: config.providers.map((provider) => ({ ...provider, enabled: false })),
},
null,
Expand Down
11 changes: 8 additions & 3 deletions apps/mobile/src/features/settings/scheduledTaskDraft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ export function scheduledTaskDefaultModel(
const configured = resolveProjectSettings(settings, project?.id ?? null, project).settings
.defaultModelSelection;
const projectDefaultSelection =
resolveDefaultableModelSelection(config, configured) ??
resolveDefaultableModelSelection(config, settings.defaultModelSelection);
resolveDefaultableModelSelection(config, configured, project?.id ?? null) ??
resolveDefaultableModelSelection(config, settings.defaultModelSelection, project?.id ?? null);
return resolveNewTaskModelSelection({
draftSelection: null,
projectDefaultSelection,
stickySelection: null,
modelOptions: buildModelOptions(config, projectDefaultSelection),
modelOptions: buildModelOptions(
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
config,
projectDefaultSelection,
undefined,
project?.id ?? null,
),
Comment thread
yordis marked this conversation as resolved.
});
}

Expand Down
16 changes: 12 additions & 4 deletions apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import { sourceControlEnvironment } from "../../state/sourceControl";
import { useAtomCommand } from "../../state/use-atom-command";
import { ProjectCloneBanner } from "../../components/ProjectCloneBanner";
import {
describeUnavailableModelSelection,
isModelSelectionUnavailable,
resolveSelectableModelSelection,
} from "../../lib/modelOptions";
Expand Down Expand Up @@ -1236,6 +1237,7 @@ export function NewTaskDraftScreen(props: {
resolveSelectableModelSelection(
selectedEnvironmentServerConfig,
draft.modelSelection ?? null,
selectedProject.id,
) ?? flow.selectedModel;
const workspaceMode = draft.workspaceSelection?.mode ?? flow.workspaceMode;
const selectedBranchName = draft.workspaceSelection?.branch ?? flow.selectedBranchName;
Expand All @@ -1252,12 +1254,18 @@ export function NewTaskDraftScreen(props: {
}
if (
environmentConnected &&
isModelSelectionUnavailable(selectedEnvironmentServerConfig, modelSelection)
isModelSelectionUnavailable(
selectedEnvironmentServerConfig,
modelSelection,
selectedProject.id,
)
) {
Alert.alert(
"Antigravity model unavailable",
"Set up Antigravity on web or desktop, or choose another model.",
const { title, detail } = describeUnavailableModelSelection(
selectedEnvironmentServerConfig,
modelSelection,
selectedProject.id,
);
Alert.alert(title, detail);
return;
}
// T3's own limits command is answered by the thread composer; a new task would
Expand Down
23 changes: 20 additions & 3 deletions apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,11 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
const currentRuntimeMode = props.selectedThread.runtimeMode;
const modelUnavailable =
props.connectionState === "connected" &&
isModelSelectionUnavailable(props.serverConfig, currentModelSelection);
isModelSelectionUnavailable(
props.serverConfig,
currentModelSelection,
props.selectedThread.projectId,
);
const selectedProviderStatus = useMemo(() => {
if (!props.serverConfig) return null;
return (
Expand Down Expand Up @@ -651,8 +655,19 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
? undefined
: currentModelSelection.instanceId;
const modelOptions = useMemo(
() => buildModelOptions(props.serverConfig, currentModelSelection, lockedProviderInstanceId),
[props.serverConfig, currentModelSelection, lockedProviderInstanceId],
() =>
buildModelOptions(
props.serverConfig,
currentModelSelection,
lockedProviderInstanceId,
props.selectedThread.projectId,
Comment thread
yordis marked this conversation as resolved.
),
[
props.serverConfig,
currentModelSelection,
lockedProviderInstanceId,
props.selectedThread.projectId,
],
);
const threadProviderGroups = useMemo(() => groupByProvider(modelOptions), [modelOptions]);
const currentModelOption =
Expand All @@ -674,6 +689,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
() => ({
ownerId: settingsOwnerId,
environmentId: props.environmentId,
projectId: props.selectedThread.projectId,
providerInstanceId: currentModelSelection.instanceId,
...(lockedProviderInstanceId ? { lockedProviderInstanceId } : {}),
providerGroups: threadProviderGroups,
Expand Down Expand Up @@ -703,6 +719,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
settingsOwnerId,
lockedProviderInstanceId,
threadProviderGroups,
props.selectedThread.projectId,
],
);
const openSettings = useCallback(() => {
Expand Down
12 changes: 11 additions & 1 deletion apps/mobile/src/features/threads/ThreadSettingsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import type {
EnvironmentId,
ModelSelection,
ProjectId,
ProviderInstanceId,
ProviderOptionDescriptor,
ProviderOptionSelection,
Expand Down Expand Up @@ -252,6 +253,7 @@ type ThreadSettingsSubmenuPage =

type ThreadSettingsSessionProps = {
readonly environmentId: EnvironmentId | null;
readonly projectId: ProjectId | null;
readonly providerInstanceId?: ProviderInstanceId;
readonly lockedProviderInstanceId?: ProviderInstanceId;
readonly providerGroups: ReadonlyArray<ProviderGroup>;
Expand Down Expand Up @@ -360,7 +362,13 @@ function ThreadSettingsSessionProvider(
() =>
manageHidden && config
? groupByProvider(
buildModelOptions(config, props.selectedModel, props.lockedProviderInstanceId, true),
buildModelOptions(
config,
props.selectedModel,
props.lockedProviderInstanceId,
props.projectId,
true,
),
)
: props.providerGroups,
[
Expand All @@ -369,6 +377,7 @@ function ThreadSettingsSessionProvider(
props.providerGroups,
props.selectedModel,
props.lockedProviderInstanceId,
props.projectId,
],
);
const legacyFavorites = AsyncResult.isSuccess(preferences)
Expand Down Expand Up @@ -1605,6 +1614,7 @@ export function NewTaskThreadSettingsRouteScreen() {
return (
<ThreadSettingsPickerScreen
environmentId={flow.selectedEnvironmentId}
projectId={flow.selectedProject?.id ?? null}
{...(flow.selectedModel ? { providerInstanceId: flow.selectedModel.instanceId } : {})}
providerGroups={flow.providerGroups}
selectedModel={flow.selectedModel}
Expand Down
7 changes: 7 additions & 0 deletions apps/mobile/src/features/threads/new-task-flow-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -581,27 +581,33 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
const draftModelSelection = resolveSelectableModelSelection(
selectedEnvironmentServerConfig,
selectedProjectDraft.modelSelection ?? null,
selectedProject?.id ?? null,
);
const projectDefaultModelSelection = resolveDefaultableModelSelection(
selectedEnvironmentServerConfig,
projectSettings.settings.defaultModelSelection,
selectedProject?.id ?? null,
);
const storedStickyModelSelection = useStickyComposerModelSelection();
const stickyModelSelection = resolveDefaultableModelSelection(
selectedEnvironmentServerConfig,
storedStickyModelSelection,
selectedProject?.id ?? null,
);
const modelOptions = useMemo(
() =>
buildModelOptions(
selectedEnvironmentServerConfig,
draftModelSelection ?? projectDefaultModelSelection ?? stickyModelSelection,
undefined,
selectedProject?.id ?? null,
),
[
selectedEnvironmentServerConfig,
draftModelSelection,
projectDefaultModelSelection,
stickyModelSelection,
selectedProject?.id,
],
);

Expand Down Expand Up @@ -1117,6 +1123,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
resolveSelectableModelSelection(
selectedEnvironmentServerConfig,
draft.modelSelection ?? null,
selectedProject.id,
) ?? selectedModel;
// A shared image or file is a task on its own; text is optional.
if ((text.length === 0 && draft.attachments.length === 0) || !draftModelSelection) {
Expand Down
Loading
Loading