diff --git a/web/src/components/projects/project-harness-form.tsx b/web/src/components/projects/project-harness-form.tsx
index b895cc35..abf65152 100644
--- a/web/src/components/projects/project-harness-form.tsx
+++ b/web/src/components/projects/project-harness-form.tsx
@@ -17,6 +17,13 @@ import {
} from '@/components/ui/card.js';
import { Input } from '@/components/ui/input.js';
import { Label } from '@/components/ui/label.js';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select.js';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs.js';
import {
Tooltip,
@@ -74,8 +81,9 @@ export function ProjectHarnessForm({ project }: { project: Project }) {
// The effective project-level engine: either explicitly set or the system default
const effectiveEngineId = agentEngine || systemDefaultEngineId;
- // Default tab to show: project's selected engine, or system default
- const defaultTab = effectiveEngineId;
+ // Controlled active tab — null means "follow effectiveEngineId reactively" (handles async defaultsQuery)
+ const [activeTab, setActiveTab] = useState(null);
+ const currentTab = activeTab ?? effectiveEngineId;
// Resolved engine defaults for EngineSettingsFields
function getEngineDefaults(engineId: string): Record | undefined {
@@ -84,6 +92,14 @@ export function ProjectHarnessForm({ project }: { project: Project }) {
: undefined;
}
+ function handleEngineSelectChange(value: string) {
+ const newEngine = value === '_system' ? '' : value;
+ setAgentEngine(newEngine);
+ // Switch active tab to the newly selected default engine
+ const newEffective = newEngine || systemDefaultEngineId;
+ setActiveTab(newEffective);
+ }
+
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const activeEngine = agentEngine || null;
@@ -107,224 +123,208 @@ export function ProjectHarnessForm({ project }: { project: Project }) {
- {/* Model & Iterations Card — engine-agnostic, always visible */}
- Model & Runtime
+ Engine
- Global model and iteration settings applied to all agents unless overridden per-agent.
+ Choose the default engine, then configure its model, settings, and credentials.
-
-
-
- {/* Per-engine tabs: credentials + settings + default toggle */}
-
-
- Engine Settings & Credentials
-
- Configure each engine's credentials and settings. The default engine tab is
- highlighted. New engines are added automatically as the catalog expands.
-
-
-
- {engines.length === 0 ? (
- Loading engines…
- ) : (
-
-
+ {/* Per-engine configuration tabs */}
+ {engines.length > 0 && (
+
+
+ {engines.map((engine) => {
+ const isDefault = engine.id === effectiveEngineId;
+ const isUsedByAgents = agentEnginesInUse.includes(engine.id);
+ return (
+
+ {engine.label}
+ {isDefault && (
+
+ Default
+
+ )}
+ {!isDefault && isUsedByAgents && (
+
+ In use
+
+ )}
+
+ );
+ })}
+
+
{engines.map((engine) => {
const isDefault = engine.id === effectiveEngineId;
- const isUsedByAgents = agentEnginesInUse.includes(engine.id);
- return (
-
- {engine.label}
- {isDefault && (
-
- Default
-
- )}
- {!isDefault && isUsedByAgents && (
-
- In use
-
- )}
-
+ const engineSecrets = ENGINE_SECRETS.filter((s) =>
+ s.engines?.includes(engine.id),
);
- })}
-
-
- {engines.map((engine) => {
- const isDefault = engine.id === effectiveEngineId;
- const isUsedByAgents = agentEnginesInUse.includes(engine.id);
- const engineSecrets = ENGINE_SECRETS.filter((s) =>
- s.engines?.includes(engine.id),
- );
- // Secrets shared with other engines: show a note
- const sharedSecretEngines = (envVarKey: string): string[] => {
- const secret = ENGINE_SECRETS.find((s) => s.envVarKey === envVarKey);
- if (!secret?.engines) return [];
- return secret.engines.filter((e) => e !== engine.id);
- };
+ const sharedSecretEngines = (envVarKey: string): string[] => {
+ const secret = ENGINE_SECRETS.find((s) => s.envVarKey === envVarKey);
+ if (!secret?.engines) return [];
+ return secret.engines.filter((e) => e !== engine.id);
+ };
+ const engineDefaults = getEngineDefaults(engine.id);
- const engineDefaults = getEngineDefaults(engine.id);
-
- return (
-
- {/* Engine description */}
- {engine.description && (
- {engine.description}
- )}
+ return (
+
+ {/* Engine description */}
+ {engine.description && (
+ {engine.description}
+ )}
- {/* Default engine indicator / Set as Default button */}
-
- {isDefault ? (
-
-
- ✓ Default engine for this project
- {agentEngine === '' &&
- ` (inheriting system default: ${capitalize(systemDefaultEngineId)})`}
-
- {agentEngine !== '' && (
-
setAgentEngine('')}
- className="ml-2 text-xs text-muted-foreground underline hover:text-foreground transition-colors"
- >
- Reset to system default
-
- )}
+ {/* Model — only shown for the default engine (project-level setting) */}
+ {isDefault && (
+
+
+ Model
+
+
+
+
+
+ Individual agents can override this in the Agents tab.
+
+
+
+
+
+ Project default. Per-agent overrides in the Agents tab.
+
- ) : (
-
setAgentEngine(engine.id)}
- className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors"
- >
- Set as Default Engine
-
- )}
- {!isDefault && isUsedByAgents && (
-
- Used by agent config overrides
-
)}
-
- {/* Engine settings */}
-
setEngineSettings(next ?? {})}
- engineDefaults={engineDefaults}
- />
+ {/* Engine Settings */}
+ setEngineSettings(next ?? {})}
+ engineDefaults={engineDefaults}
+ />
- {/* Engine credentials */}
- {engineSecrets.length > 0 ? (
-
-
-
Credentials
-
- API keys and tokens for {engine.label}. Values are stored encrypted
- and never returned to the browser.
+ {/* Max Iterations — only shown for the default engine (project-level setting) */}
+ {isDefault && (
+
+
+ Max Iterations
+
+
+
+
+
+ Individual agents can override this in the Agents tab.
+
+
+
+
setMaxIterations(e.target.value)}
+ placeholder={
+ defaults ? `${defaults.maxIterations} (default)` : 'e.g. 50'
+ }
+ />
+
+ Safety limit on tool-call iterations per run.
- {engineSecrets.map((secret) => {
- const sharedWith = sharedSecretEngines(secret.envVarKey);
- const sharedNote =
- sharedWith.length > 0
- ? `Also used by: ${sharedWith.map((id) => engines.find((e) => e.id === id)?.label ?? id).join(', ')}`
- : undefined;
- const description =
- secret.description + (sharedNote ? ` · ${sharedNote}` : '');
- return (
-
c.envVarKey === secret.envVarKey,
- )}
- />
- );
- })}
-
- ) : (
-
- No credentials required for {engine.label}.
-
- )}
-
- );
- })}
-
- )}
+ )}
+
+ {/* Credentials */}
+ {engineSecrets.length > 0 ? (
+
+
+
Credentials
+
+ API keys and tokens for {engine.label}. Values are stored encrypted
+ and never returned to the browser.
+
+
+ {engineSecrets.map((secret) => {
+ const sharedWith = sharedSecretEngines(secret.envVarKey);
+ const sharedNote =
+ sharedWith.length > 0
+ ? `Also used by: ${sharedWith.map((id) => engines.find((e) => e.id === id)?.label ?? id).join(', ')}`
+ : undefined;
+ const description =
+ secret.description + (sharedNote ? ` · ${sharedNote}` : '');
+ return (
+
c.envVarKey === secret.envVarKey,
+ )}
+ />
+ );
+ })}
+
+ ) : (
+
+ No credentials required for {engine.label}.
+
+ )}
+
+ );
+ })}
+
+ )}
+