diff --git a/apps/web/src/components/settings/ScheduledTasksSettings.tsx b/apps/web/src/components/settings/ScheduledTasksSettings.tsx
index 7b941f97a79e..cfb08f408beb 100644
--- a/apps/web/src/components/settings/ScheduledTasksSettings.tsx
+++ b/apps/web/src/components/settings/ScheduledTasksSettings.tsx
@@ -12,7 +12,6 @@ import {
import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
import type {
EnvironmentId,
- ModelSelection,
OrchestrationV2ThreadLaunchWorkspaceStrategy,
ProjectId,
ScheduledTask,
@@ -24,6 +23,7 @@ import type {
ThreadId,
} from "@t3tools/contracts";
import { DEFAULT_WEBHOOK_PROMPT } from "@t3tools/client-runtime/scheduled-task-webhook";
+import { createModelSelection } from "@t3tools/shared/model";
import {
MAX_WEBHOOK_DELIVERY_AGE_MINUTES,
MIN_SCHEDULED_TASK_INTERVAL_MS,
@@ -77,6 +77,8 @@ import { Menu, MenuTrigger, MenuPopup, MenuItem, MenuSeparator } from "../ui/men
import { ToggleGroup, Toggle } from "../ui/toggle-group";
import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyMedia } from "../ui/empty";
import { ProviderModelPicker } from "../chat/ProviderModelPicker";
+import { runtimeModeConfig, runtimeModeOptions } from "../chat/runtimeModeConfig";
+import { TraitsPicker } from "../chat/TraitsPicker";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import {
@@ -128,10 +130,9 @@ const EMPTY_DRAFT: DraftState = {
baseRef: "main",
startFromOrigin: true,
existingWorktreePath: "",
- modelKey: "",
+ modelSelection: null,
runtimeMode: "full-access",
interactionMode: "default",
- baseModelSelection: null,
signatureEnabled: false,
...WEBHOOK_SIGNATURE_DEFAULTS,
signatureSecret: "",
@@ -163,15 +164,6 @@ function Field({
);
}
-function splitModelKey(value: string): ModelSelection | null {
- const index = value.indexOf(":");
- if (index <= 0 || index === value.length - 1) return null;
- return {
- instanceId: ProviderInstanceId.make(value.slice(0, index)),
- model: value.slice(index + 1),
- };
-}
-
export function scheduleLabel(schedule: ScheduledTaskSchedule): string {
if (schedule.type === "webhook") return "On webhook";
if (schedule.type === "interval") {
@@ -828,15 +820,25 @@ function ScheduledTaskEditorDialog({
const selectedProjectId = draft.projectId || projects[0]?.id || "";
const selectedProject = projects.find((project) => project.id === selectedProjectId);
- // The real model picker is keyed by a `${instanceId}:${model}` string, which
- // is exactly how the draft stores its selection.
const firstInstance = instanceEntries[0];
- const activeSelection = draft.modelKey
- ? splitModelKey(draft.modelKey)
- : scheduledTaskDefaultModel(settings, selectedProject ?? null, instanceEntries);
+ const activeSelection =
+ draft.modelSelection ??
+ scheduledTaskDefaultModel(settings, selectedProject ?? null, instanceEntries);
const activeInstanceId =
activeSelection?.instanceId ?? firstInstance?.instanceId ?? ("" as ProviderInstanceId);
const activeModel = activeSelection?.model ?? "";
+ const activeEntry = instanceEntries.find((entry) => entry.instanceId === activeInstanceId);
+ const supportedRuntimeModes = activeEntry?.snapshot.supportedRuntimeModes;
+ const compatibleRuntimeModes =
+ supportedRuntimeModes && supportedRuntimeModes.length > 0
+ ? runtimeModeOptions.filter((mode) => supportedRuntimeModes.includes(mode))
+ : runtimeModeOptions;
+ // A mode the selected provider does not offer would run as Supervised, so the
+ // dialog shows and saves the provider's first supported mode instead.
+ const runtimeMode = compatibleRuntimeModes.includes(draft.runtimeMode)
+ ? draft.runtimeMode
+ : (compatibleRuntimeModes[0] ?? draft.runtimeMode);
+ const PermissionIcon = runtimeModeConfig[runtimeMode].icon;
const modelOptionsByInstance = useMemo(
() => getCustomModelOptionsByInstance(settings, providers, activeInstanceId, activeModel),
[settings, providers, activeInstanceId, activeModel],
@@ -901,14 +903,6 @@ function ScheduledTaskEditorDialog({
reportFailure("Checkout path is required", "Enter the path of the checkout to run in.");
return;
}
- // Keep the original selection object (with provider options) when the
- // picker still points at the same instance+model.
- const modelSelection =
- draft.baseModelSelection !== null &&
- draft.baseModelSelection.instanceId === selection.instanceId &&
- draft.baseModelSelection.model === selection.model
- ? draft.baseModelSelection
- : selection;
const workspaceStrategy: OrchestrationV2ThreadLaunchWorkspaceStrategy =
draft.workspaceMode === "root"
? { type: "root" }
@@ -928,8 +922,8 @@ function ScheduledTaskEditorDialog({
projectId: selectedProjectId as ProjectId,
threadId: draft.threadId ? (draft.threadId as ThreadId) : null,
workspaceStrategy,
- modelSelection,
- runtimeMode: draft.runtimeMode,
+ modelSelection: selection,
+ runtimeMode,
interactionMode: draft.interactionMode,
creationSource: "web",
};
@@ -980,8 +974,7 @@ function ScheduledTaskEditorDialog({
setDraft((current) => ({
...current,
projectId: "",
- modelKey: "",
- baseModelSelection: null,
+ modelSelection: null,
baseRef: "main",
startFromOrigin: true,
existingWorktreePath: "",
@@ -1121,22 +1114,93 @@ function ScheduledTaskEditorDialog({